diff --git a/l10n/intl_en.arb b/l10n/intl_en.arb index 616262ef6..1de411fe8 100644 --- a/l10n/intl_en.arb +++ b/l10n/intl_en.arb @@ -308,6 +308,7 @@ "progress_bar_suffix": "days", "in_app_review_title": "Rate us!", + "forgot_password": "Forgot your password?", "need_help_contact_us": "Need help? Contact us!", "universal_code_example": "Ex: AB12345", "my_tickets": "My tickets", diff --git a/l10n/intl_fr.arb b/l10n/intl_fr.arb index 2f304e4cb..2b48ccd5d 100644 --- a/l10n/intl_fr.arb +++ b/l10n/intl_fr.arb @@ -307,7 +307,8 @@ }, "progress_bar_suffix": "jours", - "in_app_review_title": "Évaluez-nous!", + "in_app_review_title": "Évaluez-nous!", + "forgot_password": "Mot de passe oublié?", "need_help_contact_us": "Besoin d'aide? Contactez-nous!", "universal_code_example": "Ex: AB12345", "my_tickets": "Mes billets", diff --git a/lib/core/constants/urls.dart b/lib/core/constants/urls.dart index dd446f05f..88aefc776 100644 --- a/lib/core/constants/urls.dart +++ b/lib/core/constants/urls.dart @@ -9,4 +9,6 @@ class Urls { static const String clubYoutube = "https://youtube.com/channel/UCiSzzfW1bVbE_0KcEZO52ew"; static const String clubDiscord = "https://discord.gg/adMkWptn6Y"; + static const String signetsForgottenPassword = + "https://signets-ens.etsmtl.ca/Public/MotDePassePerdu.aspx"; } diff --git a/lib/core/managers/settings_manager.dart b/lib/core/managers/settings_manager.dart index 73d3acaa7..fba0d85c8 100644 --- a/lib/core/managers/settings_manager.dart +++ b/lib/core/managers/settings_manager.dart @@ -42,11 +42,14 @@ class SettingsManager with ChangeNotifier { /// Get ThemeMode ThemeMode get themeMode { - _preferencesService.getString(PreferencesFlag.theme).then((value) { - if (value != null) { - _themeMode = ThemeMode.values.firstWhere((e) => e.toString() == value); - } - }); + _preferencesService.getString(PreferencesFlag.theme).then((value) => { + if (value != null) + { + _themeMode = + ThemeMode.values.firstWhere((e) => e.toString() == value) + } + }); + return _themeMode; } diff --git a/lib/core/services/launch_url_service.dart b/lib/core/services/launch_url_service.dart index a2dc5eec2..145a527e2 100644 --- a/lib/core/services/launch_url_service.dart +++ b/lib/core/services/launch_url_service.dart @@ -1,14 +1,70 @@ // FLUTTER / DART / THIRD-PARTIES -import 'package:url_launcher/url_launcher.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_custom_tabs/flutter_custom_tabs.dart' as custom_tabs; +import 'package:url_launcher/url_launcher.dart' as url_launch; + +// Managers +import 'package:notredame/core/managers/settings_manager.dart'; + +// UTILS +import 'package:notredame/ui/utils/app_theme.dart'; + +// OTHER +import 'package:notredame/locator.dart'; class LaunchUrlService { + final SettingsManager settingsManager = locator(); + Future canLaunch(String url) async { final uri = Uri.parse(url); - return canLaunchUrl(uri); + return url_launch.canLaunchUrl(uri); } Future launch(String url) async { final uri = Uri.parse(url); - return launchUrl(uri); + return url_launch.launchUrl(uri); + } + + Future launchInBrowser(String url, Brightness brightness) async { + await custom_tabs.launch( + url, + customTabsOption: custom_tabs.CustomTabsOption( + toolbarColor: brightness == Brightness.light + ? AppTheme.etsLightRed + : AppTheme.etsDarkRed, + enableDefaultShare: false, + enableUrlBarHiding: true, + showPageTitle: true, + animation: custom_tabs.CustomTabsSystemAnimation.slideIn(), + extraCustomTabs: const [ + // ref. https://play.google.com/store/apps/details?id=org.mozilla.firefox + 'org.mozilla.firefox', + // https://play.google.com/store/apps/details?id=com.brave.browser + 'com.brave.browser', + // https://play.google.com/store/apps/details?id=com.opera.browser + 'com.opera.browser', + 'com.opera.mini.native', + 'com.opera.gx', + // https://play.google.com/store/apps/details?id=com.sec.android.app.sbrowser + 'com.sec.android.app.sbrowser', + // ref. https://play.google.com/store/apps/details?id=com.microsoft.emmx + 'com.microsoft.emmx', + // https://play.google.com/store/apps/details?id=com.UCMobile.intl + 'com.UCMobile.intl', + ], + ), + safariVCOption: custom_tabs.SafariViewControllerOption( + preferredBarTintColor: brightness == Brightness.light + ? AppTheme.etsLightRed + : AppTheme.etsDarkRed, + preferredControlTintColor: brightness == Brightness.light + ? AppTheme.lightThemeBackground + : AppTheme.darkThemeBackground, + barCollapsingEnabled: true, + entersReaderIfAvailable: false, + dismissButtonStyle: + custom_tabs.SafariViewControllerDismissButtonStyle.close, + ), + ); } } diff --git a/lib/core/viewmodels/web_link_card_viewmodel.dart b/lib/core/viewmodels/web_link_card_viewmodel.dart index 87eca97a8..27e285bb2 100644 --- a/lib/core/viewmodels/web_link_card_viewmodel.dart +++ b/lib/core/viewmodels/web_link_card_viewmodel.dart @@ -1,7 +1,8 @@ // FLUTTER / DART / THIRD-PARTIES +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:notredame/core/models/quick_link.dart'; import 'package:stacked/stacked.dart'; -import 'package:flutter_custom_tabs/flutter_custom_tabs.dart'; // CONSTANTS import 'package:notredame/core/constants/router_paths.dart'; @@ -9,9 +10,7 @@ import 'package:notredame/core/constants/router_paths.dart'; // SERVICES import 'package:notredame/core/services/analytics_service.dart'; import 'package:notredame/core/services/navigation_service.dart'; - -// UTILS -import 'package:notredame/ui/utils/app_theme.dart'; +import 'package:notredame/core/services/launch_url_service.dart'; // OTHER import 'package:notredame/locator.dart'; @@ -22,15 +21,17 @@ class WebLinkCardViewModel extends BaseViewModel { final AnalyticsService _analyticsService = locator(); + final LaunchUrlService _launchUrlService = locator(); + /// used to open a website or the security view - Future onLinkClicked(QuickLink link) async { + Future onLinkClicked(QuickLink link, Brightness brightness) async { _analyticsService.logEvent("QuickLink", "QuickLink clicked: ${link.name}"); if (link.link == 'security') { _navigationService.pushNamed(RouterPaths.security); } else { try { - await launchInBrowser(link.link); + await _launchUrlService.launchInBrowser(link.link, brightness); } catch (error) { // An exception is thrown if browser app is not installed on Android device. await launchWebView(link); @@ -38,43 +39,6 @@ class WebLinkCardViewModel extends BaseViewModel { } } - /// used to open a website inside AndroidChromeCustomTabs or SFSafariViewController - Future launchInBrowser(String url) async { - await launch( - url, - customTabsOption: CustomTabsOption( - toolbarColor: AppTheme.etsLightRed, - enableDefaultShare: false, - enableUrlBarHiding: true, - showPageTitle: true, - animation: CustomTabsSystemAnimation.slideIn(), - extraCustomTabs: const [ - // ref. https://play.google.com/store/apps/details?id=org.mozilla.firefox - 'org.mozilla.firefox', - // https://play.google.com/store/apps/details?id=com.brave.browser - 'com.brave.browser', - // https://play.google.com/store/apps/details?id=com.opera.browser - 'com.opera.browser', - 'com.opera.mini.native', - 'com.opera.gx', - // https://play.google.com/store/apps/details?id=com.sec.android.app.sbrowser - 'com.sec.android.app.sbrowser', - // ref. https://play.google.com/store/apps/details?id=com.microsoft.emmx - 'com.microsoft.emmx', - // https://play.google.com/store/apps/details?id=com.UCMobile.intl - 'com.UCMobile.intl', - ], - ), - safariVCOption: const SafariViewControllerOption( - preferredBarTintColor: AppTheme.etsLightRed, - preferredControlTintColor: AppTheme.lightThemeBackground, - barCollapsingEnabled: true, - entersReaderIfAvailable: false, - dismissButtonStyle: SafariViewControllerDismissButtonStyle.close, - ), - ); - } - Future launchWebView(QuickLink link) async { _navigationService.pushNamed(RouterPaths.webView, arguments: link); } diff --git a/lib/ui/views/login_view.dart b/lib/ui/views/login_view.dart index f16d9a9b7..db999169a 100644 --- a/lib/ui/views/login_view.dart +++ b/lib/ui/views/login_view.dart @@ -21,6 +21,7 @@ import 'package:notredame/ui/widgets/password_text_field.dart'; // CONSTANTS import 'package:notredame/core/constants/app_info.dart'; +import 'package:notredame/core/constants/urls.dart'; // OTHER import 'package:notredame/ui/utils/app_theme.dart'; @@ -71,7 +72,7 @@ class _LoginViewState extends State { child: Column( children: [ const SizedBox( - height: 60, + height: 48, ), Hero( tag: 'ets_logo', @@ -86,7 +87,7 @@ class _LoginViewState extends State { : AppTheme.etsLightRed, )), const SizedBox( - height: 60, + height: 48, ), TextFormField( autofillHints: const [AutofillHints.username], @@ -138,13 +139,32 @@ class _LoginViewState extends State { ], ), const SizedBox( - height: 20, + height: 16, ), PasswordFormField( validator: model.validatePassword, onEditionComplete: _focusNode.nextFocus), + Align( + alignment: Alignment.topRight, + child: Padding( + padding: const EdgeInsets.only(top: 4), + child: InkWell( + child: Text( + AppIntl.of(context).forgot_password, + style: const TextStyle( + decoration: TextDecoration.underline, + color: Colors.white), + ), + onTap: () { + _launchUrlService.launchInBrowser( + Urls.signetsForgottenPassword, + Theme.of(context).brightness); + }, + ), + ), + ), const SizedBox( - height: 30.0, + height: 24, ), SizedBox( width: double.infinity, @@ -184,7 +204,7 @@ class _LoginViewState extends State { ), Center( child: Padding( - padding: const EdgeInsets.only(top: 30), + padding: const EdgeInsets.only(top: 24), child: InkWell( child: Text( AppIntl.of(context).need_help_contact_us, diff --git a/lib/ui/views/quick_links_view.dart b/lib/ui/views/quick_links_view.dart index fda35c08e..97ce90ef5 100644 --- a/lib/ui/views/quick_links_view.dart +++ b/lib/ui/views/quick_links_view.dart @@ -10,8 +10,6 @@ import 'package:notredame/core/viewmodels/quick_links_viewmodel.dart'; import 'package:notredame/ui/widgets/base_scaffold.dart'; import 'package:notredame/ui/widgets/web_link_card.dart'; -// OTHER - class QuickLinksView extends StatefulWidget { @override _QuickLinksViewState createState() => _QuickLinksViewState(); diff --git a/lib/ui/widgets/web_link_card.dart b/lib/ui/widgets/web_link_card.dart index 244ccf5c2..f0d4e5a6d 100644 --- a/lib/ui/widgets/web_link_card.dart +++ b/lib/ui/widgets/web_link_card.dart @@ -27,7 +27,8 @@ class WebLinkCard extends StatelessWidget { child: Card( elevation: 4.0, child: InkWell( - onTap: () => model.onLinkClicked(_links), + onTap: () => + model.onLinkClicked(_links, Theme.of(context).brightness), splashColor: AppTheme.etsLightRed.withAlpha(50), child: Padding( padding: const EdgeInsets.all(8.0), diff --git a/pubspec.yaml b/pubspec.yaml index 3658fad4b..5703b4d7c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ description: The 4th generation of ÉTSMobile, the main gateway between the Éco # pub.dev using `pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 4.24.0+1 +version: 4.25.0+1 environment: sdk: ">=2.10.0 <3.0.0" diff --git a/test/ui/views/quick_links_view_test.dart b/test/ui/views/quick_links_view_test.dart index a308896e8..3ee1dc07a 100644 --- a/test/ui/views/quick_links_view_test.dart +++ b/test/ui/views/quick_links_view_test.dart @@ -10,6 +10,7 @@ import 'package:notredame/core/constants/quick_links.dart'; // SERVICES import 'package:notredame/core/services/networking_service.dart'; +import 'package:notredame/core/services/launch_url_service.dart'; // VIEW import 'package:notredame/ui/views/quick_links_view.dart'; @@ -32,6 +33,7 @@ void main() { setupAnalyticsServiceMock(); setupInternalInfoServiceMock(); setupNetworkingServiceMock(); + setupLaunchUrlServiceMock(); }); tearDown(() { @@ -39,6 +41,7 @@ void main() { unregister(); unregister(); unregister(); + unregister(); }); group('UI - ', () { diff --git a/test/ui/widgets/web_link_card_test.dart b/test/ui/widgets/web_link_card_test.dart index a730f53b8..beb09a3b7 100644 --- a/test/ui/widgets/web_link_card_test.dart +++ b/test/ui/widgets/web_link_card_test.dart @@ -9,6 +9,7 @@ import 'package:notredame/core/models/quick_link.dart'; import 'package:notredame/core/services/navigation_service.dart'; import 'package:notredame/core/services/analytics_service.dart'; import 'package:notredame/core/services/internal_info_service.dart'; +import 'package:notredame/core/services/launch_url_service.dart'; // WIDGETS import 'package:notredame/ui/widgets/web_link_card.dart'; @@ -20,10 +21,12 @@ final _quickLink = void main() { AnalyticsService analyticsService; + LaunchUrlService launchUrlService; group('WebLinkCard - ', () { setUp(() { analyticsService = setupAnalyticsServiceMock(); + launchUrlService = setupLaunchUrlServiceMock(); setupInternalInfoServiceMock(); setupNavigationServiceMock(); }); @@ -31,6 +34,7 @@ void main() { tearDown(() { unregister(); clearInteractions(analyticsService); + clearInteractions(launchUrlService); unregister(); unregister(); }); diff --git a/test/viewmodels/web_link_card_viewmodel_test.dart b/test/viewmodels/web_link_card_viewmodel_test.dart index ce3a5d501..990cb269a 100644 --- a/test/viewmodels/web_link_card_viewmodel_test.dart +++ b/test/viewmodels/web_link_card_viewmodel_test.dart @@ -6,12 +6,16 @@ import 'package:mockito/mockito.dart'; // CONSTANTS import 'package:notredame/core/constants/router_paths.dart'; +// MANAGERS +import 'package:notredame/core/managers/settings_manager.dart'; + // MODELS import 'package:notredame/core/models/quick_link.dart'; // SERVICES import 'package:notredame/core/services/analytics_service.dart'; import 'package:notredame/core/services/internal_info_service.dart'; +import 'package:notredame/core/services/launch_url_service.dart'; import 'package:notredame/core/services/navigation_service.dart'; // VIEWMODELS @@ -26,6 +30,7 @@ void main() { NavigationService navigationService; AnalyticsService analyticsService; InternalInfoService internalInfoService; + LaunchUrlService launchUrlService; WebLinkCardViewModel viewModel; @@ -39,6 +44,8 @@ void main() { navigationService = setupNavigationServiceMock(); analyticsService = setupAnalyticsServiceMock(); internalInfoService = setupInternalInfoServiceMock(); + launchUrlService = setupLaunchUrlServiceMock(); + setupSettingsManagerMock(); setupLogger(); @@ -48,13 +55,15 @@ void main() { tearDown(() { unregister(); clearInteractions(analyticsService); + clearInteractions(launchUrlService); unregister(); unregister(); + unregister(); }); group('onLinkClicked -', () { test('navigate to security', () async { - await viewModel.onLinkClicked(securityQuickLink); + await viewModel.onLinkClicked(securityQuickLink, Brightness.light); verify( analyticsService.logEvent("QuickLink", "QuickLink clicked: test")); @@ -66,10 +75,10 @@ void main() { InternalInfoServiceMock.stubGetDeviceInfoForErrorReporting( internalInfoService as InternalInfoServiceMock); - await viewModel.onLinkClicked(quickLink); + await viewModel.onLinkClicked(quickLink, Brightness.light); - verify(navigationService.pushNamed(RouterPaths.webView, - arguments: quickLink)); + verify( + launchUrlService.launchInBrowser(quickLink.link, Brightness.light)); verifyNoMoreInteractions(navigationService); }); });