diff --git a/CHANGELOG.md b/CHANGELOG.md index 9224c0c5..6ebeda77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## [2.10.22] - 2021-04-27 +### Fixed +- Fixed app misbehavior when system date time is much behind the current date time [#615](https://github.com/rokwire/safer-illinois-app/issues/615). + +## [2.10.21] - 2021-04-26 +### Added +- Check server time before displaying StatusCardPanel [#611](https://github.com/rokwire/safer-illinois-app/issues/611). + +## [2.10.20] - 2021-04-23 +### Changed +- Show current date time under Building Access Status [#604](https://github.com/rokwire/safer-illinois-app/issues/604). +- Remove Covid-19 section from Settings panel if the user is not logged in, improved control of progress indicator inside [#602](https://github.com/rokwire/safer-illinois-app/issues/602). +- Indicate that location text is clickable in TestLocations panel [#597](https://github.com/rokwire/safer-illinois-app/issues/597). + +### Fixed +- Fixed mailto and tel URLs in Wellness Center Panel [#601](https://github.com/rokwire/safer-illinois-app/issues/601). + +## [2.10.19] - 2021-04-21 +Build number 1019 was taken by 2.9.22 release + ## [2.10.18] - 2021-04-19 ### Changed - Update FlexUI content from app assets [#595](https://github.com/rokwire/safer-illinois-app/issues/595). diff --git a/assets/flexUI.json b/assets/flexUI.json index a8742e6c..10c51361 100644 --- a/assets/flexUI.json +++ b/assets/flexUI.json @@ -42,6 +42,7 @@ "settings.connect" : { "loggedIn": false }, "settings.connected" : { "loggedIn": true }, "settings.account" : { "loggedIn": true }, + "settings.covid19" : { "loggedIn": true }, "settings.connected.netid" : { "shibbolethLoggedIn": true }, "settings.connected.phone" : { "phoneLoggedIn": true }, "settings.connected.netid.info" : { "shibbolethLoggedIn": true }, diff --git a/assets/strings.en.json b/assets/strings.en.json index 0b56834f..c009e615 100644 --- a/assets/strings.en.json +++ b/assets/strings.en.json @@ -274,6 +274,7 @@ "panel.covid19_passport.label.access.granted": "GRANTED", "panel.covid19_passport.label.access.denied": "DENIED", "panel.covid19_passport.message.missing_id_info": "No Illini ID information found. You may have an expired i-card. Please contact the ID Center.", + "panel.covid19_passport.message.incorrect_time": "Your Date & Time is incorrect.", "panel.covid19_passport.button.show_page_1.title": "Show page 1 of 2", "panel.covid19_passport.button.show_page_1.hint": "", "panel.covid19_passport.button.show_page_2.title": "Show page 2 of 2", @@ -338,8 +339,9 @@ "panel.covid19_test_locations.header.title": "Test Locations", "panel.covid19_test_locations.label.contact.title": "Contact", - "panel.covid19_test_locations.distance.text": "mi away get directions", - "panel.covid19_test_locations.distance.unknown": "unknown distance", + "panel.covid19_test_locations.distance.text": "mi away", + "panel.covid19_test_locations.distance.directions.text": "get directions", + "panel.covid19_test_locations.location.unknown": "unknown location", "panel.covid19_test_locations.work_time.unknown": "Unknown working time", "panel.covid19_test_locations.work_time.open_until": "Open until", "panel.covid19_test_locations.work_time.closed_until": "Closed until", diff --git a/assets/strings.es.json b/assets/strings.es.json index f113819e..77782403 100644 --- a/assets/strings.es.json +++ b/assets/strings.es.json @@ -274,6 +274,7 @@ "panel.covid19_passport.label.access.granted": "CONCEDIDO", "panel.covid19_passport.label.access.denied": "NEGADO", "panel.covid19_passport.message.missing_id_info": "No se encontró información de identificación de Illini. Es posible que tenga una i-card vencida. Comuníquese con el Centro de identificación.", + "panel.covid19_passport.message.incorrect_time": "Tu fecha y hora son incorrectas.", "panel.covid19_passport.button.show_page_1.title": "Mostrar página 1 de 2", "panel.covid19_passport.button.show_page_1.hint": "", "panel.covid19_passport.button.show_page_2.title": "Mostrar página 2 de 2", @@ -338,8 +339,9 @@ "panel.covid19_test_locations.header.title": "Lugares de prueba", "panel.covid19_test_locations.label.contact.title": "Contacto", - "panel.covid19_test_locations.distance.text": "mi distancia y obtener direcciones", - "panel.covid19_test_locations.distance.unknown": "distancia desconocida", + "panel.covid19_test_locations.distance.text": "mi distancia", + "panel.covid19_test_locations.distance.directions.text": "obtener las direcciones", + "panel.covid19_test_locations.location.unknown": "ubicación desconocida", "panel.covid19_test_locations.work_time.unknown": "Tiempo de trabajo desconocido", "panel.covid19_test_locations.work_time.open_until": "Abierto hasta", "panel.covid19_test_locations.work_time.closed_until": "Cerrado hasta", diff --git a/assets/strings.zh.json b/assets/strings.zh.json index 4336ff54..e2ca3b98 100644 --- a/assets/strings.zh.json +++ b/assets/strings.zh.json @@ -274,6 +274,7 @@ "panel.covid19_passport.label.access.granted": "授予", "panel.covid19_passport.label.access.denied": "否认", "panel.covid19_passport.message.missing_id_info": "找不到Illini ID信息. 您的I-Card可能已过期。请与ID中心联系.", + "panel.covid19_passport.message.incorrect_time": "您的日期和時間不正確。", "panel.covid19_passport.button.show_page_1.title": "Show page 1 of 2", "panel.covid19_passport.button.show_page_1.hint": "", "panel.covid19_passport.button.show_page_2.title": "Show page 2 of 2", @@ -338,8 +339,9 @@ "panel.covid19_test_locations.header.title": "测试位置", "panel.covid19_test_locations.label.contact.title": "联系人", - "panel.covid19_test_locations.distance.text": "寻路", - "panel.covid19_test_locations.distance.unknown": "未知距离", + "panel.covid19_test_locations.distance.text": "英里遠", + "panel.covid19_test_locations.distance.directions.text": "獲取路線", + "panel.covid19_test_locations.location.unknown": "未知的位置", "panel.covid19_test_locations.work_time.unknown": "未知工作时间", "panel.covid19_test_locations.work_time.open_until": "打开到", "panel.covid19_test_locations.work_time.closed_until": "关闭到", diff --git a/lib/service/Config.dart b/lib/service/Config.dart index ff3980a1..2d4b0be4 100644 --- a/lib/service/Config.dart +++ b/lib/service/Config.dart @@ -359,9 +359,7 @@ class Config with Service implements NotificationsListener { String get rokmetroAuthUrl { return platformBuildingBlocks['rokmetro_auth_url']; } // "https://api-dev.rokwire.illinois.edu/authbb/77779" String get sportsServiceUrl { return platformBuildingBlocks['sports_service_url']; } // "https://api-dev.rokwire.illinois.edu/sports-service"; String get healthUrl { return platformBuildingBlocks['health_url']; } // "https://api-dev.rokwire.illinois.edu/health" - String get talentChooserUrl { return platformBuildingBlocks['talent_chooser_url']; } // "https://api-dev.rokwire.illinois.edu/talent-chooser/api/ui-content" String get transportationUrl { return platformBuildingBlocks["transportation_url"]; } // "https://api-dev.rokwire.illinois.edu/transportation" - String get locationsUrl { return platformBuildingBlocks["locations_url"]; } // "https://api-dev.rokwire.illinois.edu/location/api"; String get imagesServiceUrl { return platformBuildingBlocks['images_service_url']; } // "https://api-dev.rokwire.illinois.edu/images-service"; String get osfBaseUrl { return thirdPartyServices['osf_base_url']; } // "https://ssproxy.osfhealthcare.org/fhir-proxy" diff --git a/lib/service/Health.dart b/lib/service/Health.dart index b4ffdf54..b7897014 100644 --- a/lib/service/Health.dart +++ b/lib/service/Health.dart @@ -222,6 +222,10 @@ class Health with Service implements NotificationsListener { return (_refreshFuture != null); } + bool get refreshingUser { + return (_refreshFuture != null) && (_refreshOptions?.user == true); + } + Future refreshStatus() async { return _refresh(_RefreshOptions.fromList([_RefreshOption.userInterval, _RefreshOption.history, _RefreshOption.rules, _RefreshOption.buildingAccessRules])); } @@ -396,7 +400,7 @@ class Health with Service implements NotificationsListener { String url = "${Config().healthUrl}/covid19/login"; String post = AppJson.encode(user?.toJson()); Response response = await Network().post(url, body: post, auth: Network.RokmetroUserAuth); - if ((response != null) && (response.statusCode == 200)) { + if (response?.statusCode == 200) { return true; } } @@ -1644,6 +1648,20 @@ class Health with Service implements NotificationsListener { return (HealthHistory.mostRecentVaccine(_history, vaccine: HealthHistoryBlob.VaccineEffective) != null); } + // Current Server Time + + Future getServerTimeUtc() async { + //TMP: return DateTime.now().toUtc(); + String url = (Config().healthUrl != null) ? "${Config().healthUrl}/covid19/time" : null; + Response response = (url != null) ? await Network().get(url, auth: Network.AppAuth) : null; + String responseBody = (response?.statusCode == 200) ? response.body : null; + Map responseJson = (responseBody != null) ? AppJson.decodeMap(responseBody) : null; + String timeString = (responseJson != null) ? AppJson.stringValue(responseJson['time']) : null; + try { return (timeString != null) ? DateTime.parse(timeString) : null; } + catch (e) { print(e?.toString()); } + return null; + } + // Health Family Members List get familyMembers { diff --git a/lib/service/Network.dart b/lib/service/Network.dart index 270e5ddb..b9b4f09d 100644 --- a/lib/service/Network.dart +++ b/lib/service/Network.dart @@ -111,7 +111,7 @@ class Network { return (responseStream != null) ? Http.Response.fromStream(responseStream) : null; } } catch (e) { - Log.e(e.toString()); + Log.d(e?.toString()); FirebaseCrashlytics().recordError(e, null); } return null; @@ -142,7 +142,7 @@ class Network { return response; } } catch (e) { - Log.e(e.toString()); + Log.d(e?.toString()); FirebaseCrashlytics().recordError(e, null); } } @@ -150,12 +150,18 @@ class Network { } Future get(url, { String body, Encoding encoding, Map headers, int auth, Http.Client client, int timeout = 60, bool sendAnalytics = true, String analyticsUrl, bool analyticsAnonymous }) async { - Http.Response response = await _get(url, headers: headers, body: body, encoding: encoding, auth: auth, client: client, timeout: timeout); - - if (_requiresTokenRefresh(response, auth)) { - if (await _refreshToken(auth) != null) { - response = await _get(url, headers: headers, body: body, encoding: encoding, auth: auth, client: client, timeout: timeout); + Http.Response response; + try { + response = await _get(url, headers: headers, body: body, encoding: encoding, auth: auth, client: client, timeout: timeout); + + if (_requiresTokenRefresh(response, auth)) { + if (await _refreshToken(auth) != null) { + response = await _get(url, headers: headers, body: body, encoding: encoding, auth: auth, client: client, timeout: timeout); + } } + } catch (e) { + Log.d(e?.toString()); + FirebaseCrashlytics().recordError(e, null); } if (sendAnalytics) { @@ -173,7 +179,7 @@ class Network { Future response = (url != null) ? Http.post(url, headers: _prepareHeaders(headers, auth, url), body: body, encoding: encoding) : null; return ((response != null) && (timeout != null)) ? response.timeout(Duration(seconds: timeout), onTimeout: _responseTimeoutHandler) : response; } catch (e) { - Log.e(e.toString()); + Log.d(e?.toString()); FirebaseCrashlytics().recordError(e, null); } } @@ -181,14 +187,21 @@ class Network { } Future post(url, { body, Encoding encoding, Map headers, int auth, int timeout = 60, bool sendAnalytics = true, String analyticsUrl, bool analyticsAnonymous }) async{ - Http.Response response = await _post(url, body: body, encoding: encoding, headers: headers, auth: auth, timeout: timeout); + Http.Response response; + try { + response = await _post(url, body: body, encoding: encoding, headers: headers, auth: auth, timeout: timeout); - if (_requiresTokenRefresh(response, auth)) { - if (await _refreshToken(auth) != null) { - response = await _post(url, body: body, encoding: encoding, headers: headers, auth: auth, timeout: timeout); + if (_requiresTokenRefresh(response, auth)) { + if (await _refreshToken(auth) != null) { + response = await _post(url, body: body, encoding: encoding, headers: headers, auth: auth, timeout: timeout); + } } + } catch (e) { + Log.d(e?.toString()); + FirebaseCrashlytics().recordError(e, null); } + if (sendAnalytics) { Analytics().logHttpResponse(response, requestMethod:'POST', requestUrl: analyticsUrl ?? url, anonymous: analyticsAnonymous); } @@ -210,7 +223,7 @@ class Network { return ((response != null) && (timeout != null)) ? response.timeout(Duration(seconds: timeout), onTimeout: _responseTimeoutHandler) : response; } catch (e) { - Log.e(e.toString()); + Log.d(e?.toString()); FirebaseCrashlytics().recordError(e, null); } } @@ -218,12 +231,18 @@ class Network { } Future put(url, { body, Encoding encoding, Map headers, int auth, int timeout = 60, Http.Client client, bool sendAnalytics = true, String analyticsUrl, bool analyticsAnonymous }) async { - Http.Response response = await _put(url, body: body, encoding: encoding, headers: headers, auth: auth, timeout: timeout, client: client); + Http.Response response; + try { + response = await _put(url, body: body, encoding: encoding, headers: headers, auth: auth, timeout: timeout, client: client); - if (_requiresTokenRefresh(response, auth)) { - if (await _refreshToken(auth) != null) { - response = await _put(url, body: body, encoding: encoding, headers: headers, auth: auth, timeout: timeout, client: client); + if (_requiresTokenRefresh(response, auth)) { + if (await _refreshToken(auth) != null) { + response = await _put(url, body: body, encoding: encoding, headers: headers, auth: auth, timeout: timeout, client: client); + } } + } catch (e) { + Log.d(e?.toString()); + FirebaseCrashlytics().recordError(e, null); } if (sendAnalytics) { @@ -241,7 +260,7 @@ class Network { Future response = (url != null) ? Http.patch(url, headers: _prepareHeaders(headers, auth, url), body: body, encoding: encoding) : null; return ((response != null) && (timeout != null)) ? response.timeout(Duration(seconds: timeout), onTimeout: _responseTimeoutHandler) : response; } catch (e) { - Log.e(e.toString()); + Log.d(e?.toString()); FirebaseCrashlytics().recordError(e, null); } } @@ -249,12 +268,18 @@ class Network { } Future patch(url, { body, Encoding encoding, Map headers, int auth, int timeout = 60, bool sendAnalytics = true, String analyticsUrl, bool analyticsAnonymous }) async { - Http.Response response = await _patch(url, body: body, encoding: encoding, headers: headers, auth: auth, timeout: timeout); + Http.Response response; + try { + response = await _patch(url, body: body, encoding: encoding, headers: headers, auth: auth, timeout: timeout); - if (_requiresTokenRefresh(response, auth)) { - if (await _refreshToken(auth) != null) { - response = await _patch(url, body: body, encoding: encoding, headers: headers, auth: auth, timeout: timeout); + if (_requiresTokenRefresh(response, auth)) { + if (await _refreshToken(auth) != null) { + response = await _patch(url, body: body, encoding: encoding, headers: headers, auth: auth, timeout: timeout); + } } + } catch (e) { + Log.d(e?.toString()); + FirebaseCrashlytics().recordError(e, null); } if (sendAnalytics) { @@ -272,7 +297,7 @@ class Network { Future response = (url != null) ? Http.delete(url, headers: _prepareHeaders(headers, auth, url)) : null; return ((response != null) && (timeout != null)) ? response.timeout(Duration(seconds: timeout), onTimeout: _responseTimeoutHandler) : response; } catch (e) { - Log.e(e.toString()); + Log.d(e?.toString()); FirebaseCrashlytics().recordError(e, null); } } @@ -280,12 +305,18 @@ class Network { } Future delete(url, { Map headers, int auth, int timeout = 60, bool sendAnalytics = true, String analyticsUrl, bool analyticsAnonymous }) async { - Http.Response response = await _delete(url, headers: headers, auth: auth, timeout: timeout); + Http.Response response; + try { + response = await _delete(url, headers: headers, auth: auth, timeout: timeout); - if (_requiresTokenRefresh(response, auth)) { - if (await _refreshToken(auth) != null) { - response = await _delete(url, headers: headers, auth: auth, timeout: timeout); + if (_requiresTokenRefresh(response, auth)) { + if (await _refreshToken(auth) != null) { + response = await _delete(url, headers: headers, auth: auth, timeout: timeout); + } } + } catch (e) { + Log.d(e?.toString()); + FirebaseCrashlytics().recordError(e, null); } if (sendAnalytics) { @@ -303,7 +334,7 @@ class Network { Future response = (url != null) ? Http.read(url, headers: _prepareHeaders(headers, auth, url)) : null; return ((response != null) && (timeout != null)) ? response.timeout(Duration(seconds: timeout)) : response; } catch (e) { - Log.e(e.toString()); + Log.d(e?.toString()); FirebaseCrashlytics().recordError(e, null); } } @@ -320,7 +351,7 @@ class Network { Future response = (url != null) ? Http.readBytes(url, headers: _prepareHeaders(headers, auth, url)) : null; return ((response != null) && (timeout != null)) ? response.timeout(Duration(seconds: timeout), onTimeout: _responseBytesHandler) : response; } catch (e) { - Log.e(e.toString()); + Log.d(e?.toString()); FirebaseCrashlytics().recordError(e, null); } } diff --git a/lib/ui/health/HealthStatusPanel.dart b/lib/ui/health/HealthStatusPanel.dart index 52d90785..86f4e054 100644 --- a/lib/ui/health/HealthStatusPanel.dart +++ b/lib/ui/health/HealthStatusPanel.dart @@ -14,7 +14,10 @@ * limitations under the License. */ +import 'dart:async'; import 'dart:typed_data'; +import 'package:illinois/service/Config.dart'; +import 'package:intl/intl.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; @@ -47,8 +50,10 @@ class _HealthStatusPanelState extends State implements Notifi List _counties; Color _colorOfTheDay; + DateTime _serverTimeUtc; + String _currentDateTime; + Timer _currentDateTimeTimer; MemoryImage _photoImage; - bool _netIdStatusChecked; bool _loading; final SwiperController _swiperController = SwiperController(); @@ -56,9 +61,14 @@ class _HealthStatusPanelState extends State implements Notifi @override void initState() { super.initState(); + NotificationService().subscribe(this, [ Health.notifyStatusUpdated, ]); + + _currentDateTime = _getCurrentDateTime(); + _currentDateTimeTimer = Timer.periodic(const Duration(seconds: 1), _updateCurrentDateTime); + _initData(); } @@ -66,6 +76,11 @@ class _HealthStatusPanelState extends State implements Notifi void dispose() { super.dispose(); NotificationService().unsubscribe(this); + + if (_currentDateTimeTimer != null) { + _currentDateTimeTimer.cancel(); + _currentDateTimeTimer = null; + } } @override @@ -83,16 +98,28 @@ class _HealthStatusPanelState extends State implements Notifi Health().refreshStatusAndUser(), Health().loadCounties(), _loadColorOfTheDay(), + _loadServerTimeUtc(), _loadPhotoBytes(), ]).then((List results) { if (mounted) { setState(() { _counties = ((results != null) && (1 < results.length)) ? results[1] : null; _colorOfTheDay = ((results != null) && (2 < results.length)) ? results[2] : null; - _photoImage = ((results != null) && (3 < results.length)) ? results[3] : null; + _serverTimeUtc = ((results != null) && (3 < results.length)) ? results[3] : null; + _photoImage = ((results != null) && (4 < results.length)) ? results[4] : null; _loading = false; }); - _checkNetIdStatus(); + + _checkServerTime().then((bool timeValid) { + if (mounted) { + if (timeValid) { + _checkNetIdStatus(); + } + else { + Navigator.of(context).pop(); + } + } + }); } }); } @@ -101,18 +128,31 @@ class _HealthStatusPanelState extends State implements Notifi return await TransportationService().loadBussColor(deviceId: await NativeCommunicator().getDeviceId(), userId: UserProfile().uuid); } + static Future _loadServerTimeUtc() async { + return await Health().getServerTimeUtc(); + } + static Future _loadPhotoBytes() async { Uint8List photoBytes = await Auth().photoImageBytes; return AppCollection.isCollectionNotEmpty(photoBytes) ? await compute(AppImage.memoryImageWithBytes, photoBytes) : null; } - void _checkNetIdStatus() { - if ((_loading != true) && (_netIdStatusChecked != true)) { - _netIdStatusChecked = true; - if (Auth().isShibbolethLoggedIn && (Auth().authCard?.photoBase64?.length ?? 0) == 0) { - AppAlert.showDialogResult(context, Localization().getStringEx('panel.covid19_passport.message.missing_id_info', 'No Illini ID information found. You may have an expired i-card. Please contact the ID Center.')); - } + Future _checkNetIdStatus() async { + if (Auth().isShibbolethLoggedIn && (Auth().authCard?.photoBase64?.length ?? 0) == 0) { + await AppAlert.showDialogResult(context, Localization().getStringEx('panel.covid19_passport.message.missing_id_info', 'No Illini ID information found. You may have an expired i-card. Please contact the ID Center.')); + return false; } + return true; + } + + Future _checkServerTime() async { + int offsetInSecs = _serverTimeUtc?.difference(DateTime.now().toUtc())?.inSeconds?.abs(); + int maximumOffsetInSecs = (Config().settings['covid19MaximumServerTimeOffset'] ?? 300); + if ((offsetInSecs == null) || (maximumOffsetInSecs < offsetInSecs)) { + await AppAlert.showDialogResult(context, Localization().getStringEx('panel.covid19_passport.message.incorrect_time', 'Your Date & Time is incorrect.')); + return false; + } + return true; } @override @@ -211,6 +251,7 @@ class _HealthStatusPanelState extends State implements Notifi Widget _buildAccesLayout() { String imageAsset = (Health().buildingAccessGranted == true) ? 'images/group-20.png' : 'images/group-28.png'; + String currentDateTime = DateFormat("MMM d, yyyy HH:mm a").format(DateTime.now()); String accessText; switch (Health().buildingAccessGranted) { case true: accessText = Localization().getStringEx("panel.covid19_passport.label.access.granted", "GRANTED"); break; @@ -221,9 +262,10 @@ class _HealthStatusPanelState extends State implements Notifi Column(children: [ Container(height: 15,), Image.asset(imageAsset, excludeFromSemantics: true,), - Container(height: 7,), + Container(height: 5,), Text(Localization().getStringEx("panel.covid19_passport.label.access.heading", "Building Access"), style: TextStyle(fontFamily: Styles().fontFamilies.medium, fontSize: 16, color: Styles().colors.fillColorPrimary),), - Container(height: 6,), + Text(currentDateTime, style: TextStyle(fontFamily: Styles().fontFamilies.bold, fontSize: 16, color: Styles().colors.fillColorPrimary),), + Container(height: 15,), Text(accessText ?? '', style: TextStyle(fontFamily: Styles().fontFamilies.medium, fontSize: 28, color: Styles().colors.fillColorPrimary),), ],), )), @@ -457,6 +499,22 @@ class _HealthStatusPanelState extends State implements Notifi Color get _backgroundColor { return Styles().colors.background; } + + static String _getCurrentDateTime() { + return DateFormat("MMM d, yyyy HH:mm a").format(DateTime.now()); + } + + void _updateCurrentDateTime(_) { + if (mounted && (_loading != true)) { + String currentDateTime = _getCurrentDateTime(); + if (currentDateTime != _currentDateTime) { + setState(() { + _currentDateTime = currentDateTime; + }); + } + } + } + } class _RotatingBorder extends StatefulWidget{ diff --git a/lib/ui/health/HealthTestLocationsPanel.dart b/lib/ui/health/HealthTestLocationsPanel.dart index 2cefa7ff..9f05b06b 100644 --- a/lib/ui/health/HealthTestLocationsPanel.dart +++ b/lib/ui/health/HealthTestLocationsPanel.dart @@ -387,13 +387,43 @@ class _TestLocation extends StatelessWidget { final HealthServiceLocation testLocation; final double distance; - _TestLocation({this.testLocation, this.distance = 0}); + _TestLocation({this.testLocation, this.distance}); @override Widget build(BuildContext context) { - - String distanceSufix = Localization().getStringEx("panel.covid19_test_locations.distance.text","mi away get directions"); - String distanceText = distance?.toStringAsFixed(2); + + bool canLocation = (testLocation?.latitude != null) && (testLocation?.longitude != null); + TextStyle textStyle = TextStyle(fontFamily: Styles().fontFamilies.regular, fontSize: 16, color: Styles().colors.textSurface,); + TextStyle linkStyle = TextStyle(fontFamily: Styles().fontFamilies.regular, fontSize: 16, color: Styles().colors.accentColor3, decoration: TextDecoration.underline); + + List locationContent = [ + Image.asset('images/icon-location.png',excludeFromSemantics: true), + Container(width: 8), + ]; + + if ((distance != null) && (distance > 0)) { + String distanceText = distance.toStringAsFixed(2) + Localization().getStringEx("panel.covid19_test_locations.distance.text", "mi away"); + locationContent.add(Text(distanceText, style: textStyle,)); + if (canLocation) { + String directionsText = Localization().getStringEx("panel.covid19_test_locations.distance.directions.text", "get directions"); + locationContent.addAll([ + Text(" (", style: textStyle,), + Text(directionsText, style: linkStyle,), + Text(")", style: textStyle,), + ]); + } + } + else if (testLocation?.fullAddress != null) { + locationContent.add( + Text(testLocation.fullAddress, style: canLocation ? linkStyle : textStyle, + )); + } + else { + String unknownLocationText = Localization().getStringEx("panel.covid19_test_locations.location.unknown", "unknown location"); + locationContent.add( + Text(unknownLocationText, style: canLocation ? linkStyle : textStyle, + )); + } return Semantics(button: false, container: true, child: @@ -413,21 +443,12 @@ class _TestLocation extends StatelessWidget { Semantics(button: true, child: GestureDetector( onTap: _onTapAddress, - child: Container( - padding: EdgeInsets.only(top: 8, bottom: 4), - child: Row( - children: [ - Image.asset('images/icon-location.png',excludeFromSemantics: true), - Container(width: 8,), - Expanded(child: - Text( - distance > 0 ? '$distanceText' + distanceSufix: - (testLocation?.fullAddress?? Localization().getStringEx("panel.covid19_test_locations.distance.unknown","unknown distance")), - style: TextStyle(fontFamily: Styles().fontFamilies.regular, fontSize: 16, color: Styles().colors.textSurface, ), - ) - ) - ], - )), + child: Padding( + padding: EdgeInsets.only(top: 8, bottom: 4), + child: Row( + children: locationContent, + ) + ), )), /*Semantics(label: Localization().getStringEx("panel.covid19_test_locations.call.hint","Call"), button: true, child: GestureDetector( diff --git a/lib/ui/health/HealthWellnessCenterPanel.dart b/lib/ui/health/HealthWellnessCenterPanel.dart index 9366cb81..cdb94cd1 100644 --- a/lib/ui/health/HealthWellnessCenterPanel.dart +++ b/lib/ui/health/HealthWellnessCenterPanel.dart @@ -90,10 +90,10 @@ class HealthWellnessCenterPanel extends StatelessWidget{ } void onEmailTapped(){ - launch('mailto: covidwellness@illinois.edu'); + launch('mailto:covidwellness@illinois.edu'); } void onCallTapped(){ - launch('tel://+12173331900'); + launch('tel:+12173331900'); } } \ No newline at end of file diff --git a/lib/ui/settings/SettingsHomePanel.dart b/lib/ui/settings/SettingsHomePanel.dart index 9ac77e61..27fd35f5 100644 --- a/lib/ui/settings/SettingsHomePanel.dart +++ b/lib/ui/settings/SettingsHomePanel.dart @@ -90,6 +90,7 @@ class _SettingsHomePanelState extends State implements Notifi Auth.notifyUserPiiDataChanged, UserProfile.notifyProfileUpdated, Health.notifyUserUpdated, + Health.notifyRefreshing, FirebaseMessaging.notifySettingUpdated, FlexUI.notifyChanged, ]); @@ -116,6 +117,8 @@ class _SettingsHomePanelState extends State implements Notifi _updateState(); } else if (name == Health.notifyUserUpdated) { _verifyHealthUserKeys(); + } else if (name == Health.notifyRefreshing) { + _updateState(); } else if (name == FirebaseMessaging.notifySettingUpdated) { _updateState(); } else if (name == FlexUI.notifyChanged) { @@ -645,70 +648,77 @@ class _SettingsHomePanelState extends State implements Notifi Widget _buildCovid19Settings() { - List contentList = new List(); + List contentList = List(); - if (_refreshingHealthUser == true) { - contentList.add(Container( - padding: EdgeInsets.all(16), - child: Center(child: - CircularProgressIndicator(valueColor: AlwaysStoppedAnimation(Styles().colors.fillColorSecondary), strokeWidth: 2,) - ,), - )); - } - else if (Health().user == null) { - contentList.add(Container( - padding: EdgeInsets.only(left: 8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(Localization().getStringEx('panel.settings.home.covid19.text.user.fail', 'Unable to retrieve user COVID-19 settings.') , style: TextStyle(color: Styles().colors.textBackground, fontFamily: Styles().fontFamilies.regular, fontSize: 16)), - Container(height: 4,), -// Row(children: [ - ScalableRoundedButton( - label: Localization().getStringEx('panel.settings.home.covid19.button.retry.title', 'Retry'), - backgroundColor: Styles().colors.background, - fontSize: 16.0, - padding: EdgeInsets.symmetric(horizontal: 24), - textColor: Styles().colors.fillColorPrimary, - borderColor: Styles().colors.fillColorPrimary, - onTap: _onTapCovid19Login + if (Auth().isLoggedIn) { + if ((_refreshingHealthUser == true) || Health().refreshingUser) { + contentList.add(Container( + padding: EdgeInsets.all(16), + child: Center(child: + CircularProgressIndicator(valueColor: AlwaysStoppedAnimation(Styles().colors.fillColorSecondary), strokeWidth: 2,) + ,), + )); + } + else if (Health().user == null) { + contentList.add(Container( + padding: EdgeInsets.only(left: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container(height: 10,), + Text(Localization().getStringEx('panel.settings.home.covid19.text.user.fail', 'Unable to retrieve user COVID-19 settings.') , style: TextStyle(color: Styles().colors.textBackground, fontFamily: Styles().fontFamilies.regular, fontSize: 16)), + Container(height: 10,), + Column(children: [ + ScalableRoundedButton( + label: Localization().getStringEx('panel.settings.home.covid19.button.retry.title', 'Retry'), + backgroundColor: Styles().colors.background, + fontSize: 16.0, + padding: EdgeInsets.symmetric(horizontal: 24), + textColor: Styles().colors.fillColorPrimary, + borderColor: Styles().colors.fillColorPrimary, + onTap: _onTapCovid19Login + ), + Container(height: 10,), + ], ), -// ],) - ],) - )); - } - else { - List codes = FlexUI()['settings.covid19'] ?? []; - for (int index = 0; index < codes.length; index++) { - String code = codes[index]; - BorderRadius borderRadius = _borderRadiusFromIndex(index, codes.length); - if (code == 'exposure_notifications') { - contentList.add(ToggleRibbonButton( - height: null, - borderRadius: borderRadius, - label: Localization().getStringEx("panel.settings.home.covid19.exposure_notifications", "Exposure Notifications"), - toggled: (Health().user?.exposureNotification == true), - context: context, - onTap: _onExposureNotifications)); - } - else if (code == 'provider_test_result') { - contentList.add(ToggleRibbonButton( - height: null, - borderRadius: borderRadius, - label: Localization().getStringEx("panel.settings.home.covid19.provider_test_result", "Health Provider Test Results"), - toggled: (Health().user?.consent == true), - context: context, - onTap: _onProviderTestResult)); - } - else if (code == 'qr_code') { - contentList.add(Padding(padding: EdgeInsets.only(left: 8, top: 16), child: _buildCovid19KeysSection(),)); + ],) + )); + } + else { + List codes = FlexUI()['settings.covid19'] ?? []; + for (int index = 0; index < codes.length; index++) { + String code = codes[index]; + BorderRadius borderRadius = _borderRadiusFromIndex(index, codes.length); + if (code == 'exposure_notifications') { + contentList.add(ToggleRibbonButton( + height: null, + borderRadius: borderRadius, + label: Localization().getStringEx("panel.settings.home.covid19.exposure_notifications", "Exposure Notifications"), + toggled: (Health().user?.exposureNotification == true), + context: context, + onTap: _onExposureNotifications)); + } + else if (code == 'provider_test_result') { + contentList.add(ToggleRibbonButton( + height: null, + borderRadius: borderRadius, + label: Localization().getStringEx("panel.settings.home.covid19.provider_test_result", "Health Provider Test Results"), + toggled: (Health().user?.consent == true), + context: context, + onTap: _onProviderTestResult)); + } + else if (code == 'qr_code') { + contentList.add(Padding(padding: EdgeInsets.only(left: 8, top: 16), child: _buildCovid19KeysSection(),)); + } } } } - return _OptionsSection( + return (0 < contentList.length) ? + _OptionsSection( title: Localization().getStringEx("panel.settings.home.covid19.title", "COVID-19"), - widgets: contentList); + widgets: contentList) : + Container(); } Widget _buildCovid19KeysSection() { diff --git a/pubspec.yaml b/pubspec.yaml index b70cbdc1..7c5254c0 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.10.18+1018 +version: 2.10.22+1022 environment: sdk: ">=2.2.0 <3.0.0"