diff --git a/CHANGELOG.md b/CHANGELOG.md index caf6606f..9cb6ee01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## [2.11.5] - 2021-10-01 +### Fixed +- Fixed null pointer crash [#725](https://github.com/rokwire/safer-illinois-app/issues/725). + +## [2.11.4] - 2021-10-01 +### Changed +- Show when the vaccine will become effective in vaccination widget [#720](https://github.com/rokwire/safer-illinois-app/issues/720). +- Show upconing entries in history panel [#723](https://github.com/rokwire/safer-illinois-app/issues/723). + ## [2.11.3] - 2021-09-29 ### Deleted - Removed vaccine taken event handling [#715](https://github.com/rokwire/safer-illinois-app/issues/715). diff --git a/assets/health.rules.json b/assets/health.rules.json index f26e2176..88204d79 100644 --- a/assets/health.rules.json +++ b/assets/health.rules.json @@ -557,14 +557,14 @@ "fail": "vaccinated-force" }, "fail": "release.unvaccinated" - } - }, + }, - "release.unvaccinated": { - "code": "orange", - "priority": -1, - "next_step": "release.step", - "reason": "release.reason" + "release.unvaccinated": { + "code": "orange", + "priority": -1, + "next_step": "release.step", + "reason": "release.reason" + } }, "tests" : { diff --git a/assets/strings.en.json b/assets/strings.en.json index 4ffe7914..43ced0b2 100644 --- a/assets/strings.en.json +++ b/assets/strings.en.json @@ -340,6 +340,9 @@ "panel.covid19home.vaccination.none.description": "• COVID-19 vaccines are safe\n• COVID-19 vaccines are effective\n• COVID-19 vaccines allow you to safely do more\n• COVID-19 vaccines build safer protection", "panel.covid19home.vaccination.vaccinated.title": "Vaccinated", "panel.covid19home.vaccination.vaccinated.description": "Your vaccination is not effective yet.", + "panel.covid19home.vaccination.vaccinated.effective.0.description": "Your vaccine will be effective today.", + "panel.covid19home.vaccination.vaccinated.effective.1.description": "Your vaccine will be effective tomorrow.", + "panel.covid19home.vaccination.vaccinated.effective.n.description": "Your vaccine will be effective after %s days.", "panel.covid19home.vaccination.button.appointment.title": "Make an appointment", "panel.covid19home.vaccination.button.appointment.hint": "", diff --git a/assets/strings.es.json b/assets/strings.es.json index 84578310..b808570d 100644 --- a/assets/strings.es.json +++ b/assets/strings.es.json @@ -340,6 +340,9 @@ "panel.covid19home.vaccination.none.description": "• Las vacunas COVID-19 son seguras\n• Las vacunas COVID-19 son efectivas\n• Las vacunas COVID-19 le permiten hacer más de manera segura\n• Las vacunas COVID-19 crean una protección más segura", "panel.covid19home.vaccination.vaccinated.title": "Vacunado", "panel.covid19home.vaccination.vaccinated.description": "Su vacunación aún no es efectiva.", + "panel.covid19home.vaccination.vaccinated.effective.0.description": "Su vacuna será efectiva hoy.", + "panel.covid19home.vaccination.vaccinated.effective.1.description": "Su vacuna será efectiva mañana.", + "panel.covid19home.vaccination.vaccinated.effective.n.description": "Su vacuna será efectiva después de %s días.", "panel.covid19home.vaccination.button.appointment.title": "Haga una cita", "panel.covid19home.vaccination.button.appointment.hint": "", diff --git a/assets/strings.ja.json b/assets/strings.ja.json index 2b28685b..229d5606 100644 --- a/assets/strings.ja.json +++ b/assets/strings.ja.json @@ -340,6 +340,9 @@ "panel.covid19home.vaccination.none.description": "• COVID-19 疫苗是安全的\n• COVID-19ワクチンは効果的です\n• COVID-19ワクチンはあなたが安全にもっと多くのことをすることを可能にします\n• COVID-19ワクチンはより安全な保護を構築します", "panel.covid19home.vaccination.vaccinated.title": "ワクチン接種", "panel.covid19home.vaccination.vaccinated.description": "あなたの予防接種はまだ効果的ではありません。", + "panel.covid19home.vaccination.vaccinated.effective.0.description": "あなたのワクチンは今日有効になります。", + "panel.covid19home.vaccination.vaccinated.effective.1.description": "あなたのワクチンは明日有効になります。", + "panel.covid19home.vaccination.vaccinated.effective.n.description": "あなたのワクチンは%s日後に有効になります。", "panel.covid19home.vaccination.button.appointment.title": "予約する", "panel.covid19home.vaccination.button.appointment.hint": "", diff --git a/assets/strings.zh.json b/assets/strings.zh.json index 2c74cf7e..b226aa63 100644 --- a/assets/strings.zh.json +++ b/assets/strings.zh.json @@ -340,6 +340,9 @@ "panel.covid19home.vaccination.none.description": "• COVID-19 疫苗是安全的\n• COVID-19 疫苗是有效的\n• COVID-19 疫苗可讓您安全地做更多事情\n• COVID-19 疫苗可建立更安全的保護", "panel.covid19home.vaccination.vaccinated.title": "已接種", "panel.covid19home.vaccination.vaccinated.description": "您的疫苗接種尚未生效。", + "panel.covid19home.vaccination.vaccinated.effective.0.description": "你的疫苗今天會有效。", + "panel.covid19home.vaccination.vaccinated.effective.1.description": "你的疫苗明天就會生效。", + "panel.covid19home.vaccination.vaccinated.effective.n.description": "您的疫苗將在 %s 天后生效。", "panel.covid19home.vaccination.button.appointment.title": "預約", "panel.covid19home.vaccination.button.appointment.hint": "", diff --git a/images/2.0x/icon-time2.png b/images/2.0x/icon-time2.png new file mode 100644 index 00000000..e3d1ee33 Binary files /dev/null and b/images/2.0x/icon-time2.png differ diff --git a/images/2.0x/icon-time3.png b/images/2.0x/icon-time3.png new file mode 100644 index 00000000..c6b52363 Binary files /dev/null and b/images/2.0x/icon-time3.png differ diff --git a/images/3.0x/icon-time2.png b/images/3.0x/icon-time2.png new file mode 100644 index 00000000..e816a55a Binary files /dev/null and b/images/3.0x/icon-time2.png differ diff --git a/images/3.0x/icon-time3.png b/images/3.0x/icon-time3.png new file mode 100644 index 00000000..1d6bb28d Binary files /dev/null and b/images/3.0x/icon-time3.png differ diff --git a/images/icon-time2.png b/images/icon-time2.png new file mode 100644 index 00000000..d46d7a0b Binary files /dev/null and b/images/icon-time2.png differ diff --git a/images/icon-time3.png b/images/icon-time3.png new file mode 100644 index 00000000..581f97cd Binary files /dev/null and b/images/icon-time3.png differ diff --git a/lib/service/Health.dart b/lib/service/Health.dart index 317b5812..c69dd103 100644 --- a/lib/service/Health.dart +++ b/lib/service/Health.dart @@ -1645,7 +1645,7 @@ class Health with Service implements NotificationsListener { bool get isVaccinated { HealthHistory vaccine = HealthHistory.mostRecentVaccine(Health().history); - return (vaccine.blob != null) && (vaccine?.blob?.isVaccineEffective ?? false) && (vaccine.dateUtc != null) && vaccine.dateUtc.isBefore(DateTime.now().toUtc()); + return (vaccine != null) && (vaccine.blob != null) && vaccine.blob.isVaccineEffective && (vaccine.dateUtc != null) && vaccine.dateUtc.isBefore(DateTime.now().toUtc()); } // Current Server Time diff --git a/lib/ui/health/HealthHistoryPanel.dart b/lib/ui/health/HealthHistoryPanel.dart index 053561a7..ff9c9096 100644 --- a/lib/ui/health/HealthHistoryPanel.dart +++ b/lib/ui/health/HealthHistoryPanel.dart @@ -55,7 +55,7 @@ class _HealthHistoryPanelState extends State implements Noti Health.notifyHistoryUpdated, ]); - _history = HealthHistory.pastList(Health().history); + _history = Health().history; _refreshHistory(); } @@ -75,7 +75,7 @@ class _HealthHistoryPanelState extends State implements Noti else if (name == Health.notifyHistoryUpdated) { if (mounted) { setState(() { - _history = HealthHistory.pastList(Health().history); + _history = Health().history; }); } } @@ -89,7 +89,7 @@ class _HealthHistoryPanelState extends State implements Noti Health().refreshStatus().then((_) { if (mounted) { setState(() { - _history = HealthHistory.pastList(Health().history); + _history = Health().history; _isRefreshing = false; }); } @@ -446,35 +446,19 @@ class _HealthHistoryEntryState extends State<_HealthHistoryEntry> with SingleTic Widget build(BuildContext context) { List content = []; - content.add(Container(height: 16,)); content.add(_buildCommonInfo(),); if ((widget.historyEntry?.isTestVerified ?? false) || HealthEventExtra.listHasVisible(widget.historyEntry?.blob?.extras)) { content.add(_buildMoreButton()); - if (_expanded) { - - Widget testResult = ((widget.historyEntry?.isTestVerified ?? false) && (widget.historyEntry?.blob?.testResult != null)) ? _buildTestResult() : null; - Widget additionalInfo = _buildAdditionalInfo(); - - if (testResult != null) { - content.add(testResult); - } - - if ((testResult != null) && (additionalInfo != null)) { - content.add(_buildSplitter()); - } - - if (additionalInfo != null) { - content.add(additionalInfo); - } + Widget exandedConent = _expanded ? _buildExpandedContent() : null; + if (exandedConent != null) { + content.add(exandedConent); } } - content.add(Container(height: 16,)); - return Container( - padding: EdgeInsets.symmetric(), + padding: EdgeInsets.symmetric(vertical: 16), child: Column(children: content,), ); } @@ -570,13 +554,22 @@ class _HealthHistoryEntryState extends State<_HealthHistoryEntry> with SingleTic } return Semantics(sortKey: OrdinalSortKey(1), container: true, child: - Container(color: Styles().colors.white, padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: - Row(children: [ - Expanded(child: - Column(crossAxisAlignment: CrossAxisAlignment.start, children: contentList,) + Stack(children: [ + Container(decoration: BoxDecoration(color: Styles().colors.white, border: Border.all(color: Styles().colors.surfaceAccent,)), padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: + Row(children: [ + Expanded(child: + Column(crossAxisAlignment: CrossAxisAlignment.start, children: contentList,) + ), + ]), + ), + Visibility(visible: widget.historyEntry.dateUtc?.isAfter(DateTime.now().toUtc()) ?? false, child: + Align(alignment: Alignment.topRight, child: + Padding(padding: EdgeInsets.all(16), child: + Image.asset('images/icon-time3.png') + ), ), - ]), - ), + ), + ],), ); } @@ -668,17 +661,11 @@ class _HealthHistoryEntryState extends State<_HealthHistoryEntry> with SingleTic final Animatable _halfTween = Tween(begin: 0.0, end: 0.5); final Animatable _easeInTween = CurveTween(curve: Curves.easeIn); Animation _iconTurns = _controller.drive(_halfTween.chain(_easeInTween)); + BorderSide borderSide = BorderSide(color: Styles().colors.surfaceAccent,); - return - Semantics( - sortKey: OrdinalSortKey(2), - container: true, - button: true, - child: InkWell( - onTap: (){ - setState(() { - _expanded = !_expanded; - }); + return Semantics(sortKey: OrdinalSortKey(2), container: true, button: true, child: + InkWell(onTap: () { + setState(() { _expanded = !_expanded; }); if (_expanded) { _controller.forward(); } else { @@ -686,7 +673,7 @@ class _HealthHistoryEntryState extends State<_HealthHistoryEntry> with SingleTic } }, child: Container( - decoration: BoxDecoration(color: Styles().colors.background, border: Border.all(color: Styles().colors.surfaceAccent,)), + decoration: BoxDecoration(color: Styles().colors.background, border: Border(left: borderSide, right: borderSide, bottom: /* _expanded ? BorderSide.none : */ borderSide)), padding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Row(children: [ Text(Localization().getStringEx("panel.health.covid19.history.label.more_info.title","More Info"), @@ -694,14 +681,35 @@ class _HealthHistoryEntryState extends State<_HealthHistoryEntry> with SingleTic ), Expanded(child: Container(),), Container(width: 4,), - RotationTransition( - turns: _iconTurns, - child: Image.asset("images/icon-down-orange.png", color: Styles().colors.fillColorSecondary, excludeFromSemantics: true,)), - + RotationTransition(turns: _iconTurns, child: + Image.asset("images/icon-down-orange.png", color: Styles().colors.fillColorSecondary, excludeFromSemantics: true,)), ],) ))); } + Widget _buildExpandedContent() { + List expandedContent = []; + + Widget testResult = ((widget.historyEntry?.isTestVerified ?? false) && (widget.historyEntry?.blob?.testResult != null)) ? _buildTestResult() : null; + Widget additionalInfo = _buildAdditionalInfo(); + + if (testResult != null) { + expandedContent.add(testResult); + } + + if ((testResult != null) && (additionalInfo != null)) { + expandedContent.add(_buildSplitter()); + } + + if (additionalInfo != null) { + expandedContent.add(additionalInfo); + } + + return (0 < expandedContent.length) ? Container( + // decoration: BoxDecoration(color: Styles().colors.white, border: Border.all(color: Styles().colors.surfaceAccent,)), + child: Column(children: expandedContent,),) : null; + } + Widget _buildDetail(String title, String data, {GestureTapCallback onTapData, String onTapHint}) { TextStyle dataTextStyle = (onTapData != null) ? diff --git a/lib/ui/health/HealthHomePanel.dart b/lib/ui/health/HealthHomePanel.dart index aa8717e9..511b9482 100644 --- a/lib/ui/health/HealthHomePanel.dart +++ b/lib/ui/health/HealthHomePanel.dart @@ -49,6 +49,7 @@ import 'package:illinois/ui/widgets/RoundedButton.dart'; import 'package:illinois/ui/widgets/SectionTitlePrimary.dart'; import 'package:illinois/ui/widgets/StatusInfoDialog.dart'; import 'package:illinois/utils/Utils.dart'; +import 'package:sprintf/sprintf.dart'; import 'package:url_launcher/url_launcher.dart'; class HealthHomePanel extends StatefulWidget { @@ -685,8 +686,9 @@ class _HealthHomePanelState extends State implements Notificati String statusDescriptionText, statusDescriptionHtml; String headingTitle = Localization().getStringEx('panel.covid19home.vaccination.heading.title', 'VACCINATION'); - HealthHistory vaccine = HealthHistory.mostRecentVaccine(Health().history); - if (vaccine == null) { + HealthHistory recentVaccine = getRecentVaccine(); + + if (recentVaccine == null) { // No vaccine at all - promote it. statusTitleText = Localization().getStringEx('panel.covid19home.vaccination.none.title', 'Get a vaccine now'); statusDescriptionText = Localization().getStringEx('panel.covid19home.vaccination.none.description', """ @@ -695,15 +697,35 @@ class _HealthHomePanelState extends State implements Notificati • COVID-19 vaccines allow you to safely do more • COVID-19 vaccines build safer protection"""); } - else if ((vaccine.blob != null) && (vaccine.blob.isVaccineEffective) && (vaccine.dateUtc != null) && vaccine.dateUtc.isBefore(DateTime.now().toUtc())) { - // 5.2.4 When effective then hide the widget - return null; - } else { - // Vaccinated, but not effective yet. - headingDate = AppDateTime.formatDateTime(vaccine.dateUtc?.toLocal(), format:"MMMM dd, yyyy", locale: Localization().currentLocale?.languageCode) ?? ''; + headingDate = AppDateTime.formatDateTime(recentVaccine.dateUtc?.toLocal(), format:"MMMM dd, yyyy", locale: Localization().currentLocale?.languageCode); statusTitleText = Localization().getStringEx('panel.covid19home.vaccination.vaccinated.title', 'Vaccinated'); - statusDescriptionText = Localization().getStringEx('panel.covid19home.vaccination.vaccinated.description', 'Your vaccination is not effective yet.'); + + if ((recentVaccine.blob?.isVaccineEffective ?? false) && (recentVaccine.dateUtc != null)) { + DateTime now = DateTime.now(); + if (recentVaccine.dateUtc.isBefore(now.toUtc())) { + // 5.2.4 When effective then hide the widget + return null; + } + else { + // Vaccinated, but not effective yet. + int delayInDays = AppDateTime.midnight(recentVaccine.dateUtc.toLocal()).difference(AppDateTime.midnight(now)).inDays; + + if (delayInDays > 1) { + statusDescriptionText = sprintf(Localization().getStringEx('panel.covid19home.vaccination.vaccinated.effective.n.description', 'Your vaccine will be effective after %s days.'), [delayInDays]); + } + else if (delayInDays == 1) { + statusDescriptionText = Localization().getStringEx('panel.covid19home.vaccination.vaccinated.effective.1.description', 'Your vaccine will be effective tomorrow.'); + } + else { + statusDescriptionText = Localization().getStringEx('panel.covid19home.vaccination.vaccinated.effective.0.description', 'Your vaccine will be effective today.'); + } + } + } + else { + // Vaccinated, unknown status or date. + statusDescriptionText = Localization().getStringEx('panel.covid19home.vaccination.vaccinated.description', 'Your vaccination is not effective yet.'); + } } List contentWidgets = [ @@ -770,7 +792,7 @@ class _HealthHomePanelState extends State implements Notificati ],), ]; - if ((vaccine == null) && (Config().vaccinationAppointUrl != null)) { + if ((recentVaccine == null) && (Config().vaccinationAppointUrl != null)) { contentList.addAll([ Container(margin: EdgeInsets.only(top: 14, bottom: 14), height: 1, color: Styles().colors.fillColorPrimaryTransparent015,), @@ -794,6 +816,23 @@ class _HealthHomePanelState extends State implements Notificati )); } + HealthHistory getRecentVaccine() { + HealthHistory mostRecentVaccine; + if (Health().history != null) { + for (HealthHistory historyEntry in Health().history) { + if (historyEntry.isVaccine) { + if (historyEntry.blob?.isVaccineEffective ?? false) { + return historyEntry; + } + else if (mostRecentVaccine == null) { + mostRecentVaccine = historyEntry; + } + } + } + } + return mostRecentVaccine; + } + Widget _buildTileButtons() { List contentList = []; diff --git a/pubspec.yaml b/pubspec.yaml index b00799c6..79bdf4a5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Illinois client application. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 2.11.3+1103 +version: 2.11.5+1105 environment: sdk: ">=2.2.0 <3.0.0"