diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 348c409ea..3e2a00a10 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 390b6254f..f84ca6cd1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -60,7 +60,7 @@ PODS: - GoogleUtilities/Logger (~> 8.0) - FirebaseCoreExtension (11.4.1): - FirebaseCore (~> 11.0) - - FirebaseCoreInternal (11.8.0): + - FirebaseCoreInternal (11.9.0): - "GoogleUtilities/NSData+zlib (~> 8.0)" - FirebaseCrashlytics (11.4.0): - FirebaseCore (~> 11.4) @@ -84,7 +84,7 @@ PODS: - FirebaseSharedSwift (~> 11.0) - GoogleUtilities/Environment (~> 8.0) - "GoogleUtilities/NSData+zlib (~> 8.0)" - - FirebaseRemoteConfigInterop (11.8.0) + - FirebaseRemoteConfigInterop (11.9.0) - FirebaseSessions (11.4.0): - FirebaseCore (~> 11.4) - FirebaseCoreExtension (~> 11.4) @@ -94,7 +94,7 @@ PODS: - GoogleUtilities/UserDefaults (~> 8.0) - nanopb (~> 3.30910.0) - PromisesSwift (~> 2.1) - - FirebaseSharedSwift (11.8.0) + - FirebaseSharedSwift (11.9.0) - Flutter (1.0.0) - flutter_inappwebview_ios (0.0.1): - Flutter @@ -305,13 +305,13 @@ SPEC CHECKSUMS: FirebaseAnalytics: 3feef9ae8733c567866342a1000691baaa7cad49 FirebaseCore: e0510f1523bc0eb21653cac00792e1e2bd6f1771 FirebaseCoreExtension: f1bc67a4702931a7caa097d8e4ac0a1b0d16720e - FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629 + FirebaseCoreInternal: 154779013b85b4b290fdba38414df475f42e3096 FirebaseCrashlytics: 41bbdd2b514a8523cede0c217aee6ef7ecf38401 FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414 FirebaseRemoteConfig: 7655681d02417bc9b287338edb9d721ff79e1a4a - FirebaseRemoteConfigInterop: 98897a64aa372eac3c5b3fe2816594ccfaac55ef + FirebaseRemoteConfigInterop: 710954a00e956c5fe5144a8e46164f0361389203 FirebaseSessions: 3f56f177d9e53a85021d16b31f9a111849d1dd8b - FirebaseSharedSwift: 672954eac7b141d6954fab9a32d45d6b1d922df8 + FirebaseSharedSwift: 574e6a5602afe4397a55c8d4f767382d620285de Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 diff --git a/l10n/intl_en.arb b/l10n/intl_en.arb index ad2f6c6a5..5ae3cbf15 100644 --- a/l10n/intl_en.arb +++ b/l10n/intl_en.arb @@ -13,7 +13,14 @@ "github": "GitHub", "email": "Email", "email_subject": "ÉTSMobile Problem", - + + "faq_questions_app_alert_title": "Notice", + "faq_questions_app_alert_confirmation": "Please note that if your question is about gaining access to your account, App|ETS cannot help you. \n\nTap on the \"Password assistance\" button to get help", + "faq_questions_app_alert_password_assistance": "Password assistance", + "continue_to_mail_app": "Continue to mail app", + "cancel_button_text": "Cancel", + "monets_connection_help_page": "https://partage.etsmtl.ca/fs/en.html", + "close_button_text": "Close", "title_schedule": "Schedule", @@ -239,7 +246,7 @@ "more_settings": "Settings", "more_log_out": "Log out", "more_prompt_log_out_confirmation": "Are you sure you want to log out?", - "more_log_out_progress": "Disconnection in progress", + "more_log_out_progress": "Logging you out", "more_applets_about_details": "ÉTSMobile was conceived by the ApplETS scientific club from the École de technologie supérieure.\n\nApplets is, most importantly, a gathering of students who share a typical enthusiasm for the field of telecommunications and mobile applications. The mission of ApplETS is to train students in mobile development and promote the production of mobile applications within the student community.\n\nFollow us on: ", @@ -339,7 +346,6 @@ "progress_bar_suffix": "days", "in_app_review_title": "Rate us!", - "forgot_password": "Forgot your password?", "need_help": "Need help?", "privacy_policy": "Privacy policy", "actions": "Actions", diff --git a/l10n/intl_fr.arb b/l10n/intl_fr.arb index ad3c8e45d..3eca9a56e 100644 --- a/l10n/intl_fr.arb +++ b/l10n/intl_fr.arb @@ -14,6 +14,13 @@ "email": "Courriel", "email_subject": "Problème ÉTSMobile", + "faq_questions_app_alert_title": "Attention", + "faq_questions_app_alert_confirmation": "Veuillez noter que si votre question concerne l'accès à votre compte, le club App|ETS ne peut pas vous aider. \n\nAppuyez sur le bouton \"Assistance mot de passe\" pour obtenir de l'aide", + "faq_questions_app_alert_password_assistance": "Assistance mot de passe", + "continue_to_mail_app": "Continuer vers l'application courriel", + "cancel_button_text": "Annuler", + "monets_connection_help_page": "https://partage.etsmtl.ca/fs/fr.html", + "close_button_text": "Fermer", "title_schedule": "Horaire", @@ -343,7 +350,6 @@ "privacy_policy": "Politique de confidentialité", "questions_and_answers": "Questions et réponses", "actions": "Actions", - "forgot_password": "Mot de passe oublié?", "universal_code_example": "Ex: AB12345", "my_tickets": "Mes billets", "no_ticket": "Aucun billet", diff --git a/lib/ui/core/ui/need_help_notice_dialog.dart b/lib/ui/core/ui/need_help_notice_dialog.dart new file mode 100644 index 000000000..936f72f52 --- /dev/null +++ b/lib/ui/core/ui/need_help_notice_dialog.dart @@ -0,0 +1,101 @@ +// Flutter imports: +import 'package:flutter/material.dart'; + +// Package imports: +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +// Project imports: +import 'package:notredame/ui/core/themes/app_palette.dart'; + +class NeedHelpNoticeDialog extends AlertDialog { + final VoidCallback openMail; + final VoidCallback launchWebsite; + final double radius; + + const NeedHelpNoticeDialog({ + super.key, + required this.openMail, + required this.launchWebsite, + this.radius = 5.0, + }); + + @override + Widget build(BuildContext context) => AlertDialog( + title: Text( + AppIntl.of(context)!.faq_questions_app_alert_title, + style: const TextStyle( + color: AppPalette.etsLightRed, fontWeight: FontWeight.bold), + ), + content: Text(AppIntl.of(context)!.faq_questions_app_alert_confirmation, + style: const TextStyle(fontWeight: FontWeight.bold)), + actions: [ + getButtons(context), + ], + ); + + Column getButtons(BuildContext context) { + SizedBox helpPageButton = SizedBox( + width: double.infinity, + child: TextButton.icon( + style: ButtonStyle( + backgroundColor: + WidgetStateProperty.all(AppPalette.etsLightRed), + foregroundColor: + WidgetStateProperty.all(AppPalette.grey.white), + textStyle: WidgetStateProperty.all( + const TextStyle(fontWeight: FontWeight.bold)), + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radius)))), + onPressed: () { + launchWebsite(); + }, + icon: const Icon(Icons.help_center), + label: Text( + AppIntl.of(context)!.faq_questions_app_alert_password_assistance), + ), + ); + + SizedBox continueButton = SizedBox( + width: double.infinity, + child: TextButton.icon( + style: ButtonStyle( + backgroundColor: + WidgetStateProperty.all(AppPalette.etsLightRed), + foregroundColor: + WidgetStateProperty.all(AppPalette.grey.white), + textStyle: WidgetStateProperty.all( + const TextStyle(fontWeight: FontWeight.bold)), + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radius)))), + onPressed: openMail, + icon: const Icon(Icons.mail), + label: Text(AppIntl.of(context)!.continue_to_mail_app), + ), + ); + + SizedBox cancelButton = SizedBox( + width: double.infinity, + child: TextButton.icon( + onPressed: () => Navigator.of(context).pop(), + style: ButtonStyle( + textStyle: WidgetStateProperty.all( + const TextStyle(fontWeight: FontWeight.bold)), + shape: WidgetStateProperty.all(RoundedRectangleBorder( + side: const BorderSide( + color: AppPalette.etsLightRed, width: 2.0), + borderRadius: BorderRadius.circular(radius)))), + icon: const Icon(Icons.cancel), + label: Text(AppIntl.of(context)!.cancel_button_text)), + ); + + return Column( + children: [ + helpPageButton, + continueButton, + cancelButton, + ], + ); + } +} diff --git a/lib/ui/login/widgets/forgot_password.dart b/lib/ui/login/widgets/forgot_password.dart index 2231f98ef..d655aa899 100644 --- a/lib/ui/login/widgets/forgot_password.dart +++ b/lib/ui/login/widgets/forgot_password.dart @@ -31,7 +31,7 @@ class _ForgotPasswordState extends State { padding: const EdgeInsets.only(top: 4), child: InkWell( child: Text( - AppIntl.of(context)!.forgot_password, + AppIntl.of(context)!.login_password_forgotten, style: TextStyle( decoration: TextDecoration.underline, color: AppPalette.grey.white), diff --git a/lib/ui/more/faq/view_model/faq_viewmodel.dart b/lib/ui/more/faq/view_model/faq_viewmodel.dart index ca8843807..d70e664de 100644 --- a/lib/ui/more/faq/view_model/faq_viewmodel.dart +++ b/lib/ui/more/faq/view_model/faq_viewmodel.dart @@ -7,7 +7,6 @@ import 'package:stacked/stacked.dart'; // Project imports: import 'package:notredame/data/repositories/settings_repository.dart'; -import 'package:notredame/data/services/analytics_service.dart'; import 'package:notredame/data/services/launch_url_service.dart'; import 'package:notredame/domain/constants/app_info.dart'; import 'package:notredame/locator.dart'; @@ -30,10 +29,6 @@ class FaqViewModel extends BaseViewModel { subject = AppIntl.of(context)!.email_subject; } - try { - _launchUrlService.writeEmail(addressEmail, subject); - } catch (e) { - locator().logError("login_view", "Cannot send email."); - } + _launchUrlService.writeEmail(addressEmail, subject); } } diff --git a/lib/ui/more/faq/widgets/faq_view.dart b/lib/ui/more/faq/widgets/faq_view.dart index 5b7040b1c..fe639d3bf 100644 --- a/lib/ui/more/faq/widgets/faq_view.dart +++ b/lib/ui/more/faq/widgets/faq_view.dart @@ -9,6 +9,7 @@ import 'package:stacked/stacked.dart'; // Project imports: import 'package:notredame/ui/core/themes/app_theme.dart'; import 'package:notredame/ui/core/ui/base_scaffold.dart'; +import 'package:notredame/ui/core/ui/need_help_notice_dialog.dart'; import 'package:notredame/ui/more/faq/models/faq.dart'; import 'package:notredame/ui/more/faq/models/faq_actions.dart'; import 'package:notredame/ui/more/faq/view_model/faq_viewmodel.dart'; @@ -168,7 +169,13 @@ class _FaqViewState extends State { if (type.name == ActionType.webview.name) { model.launchWebsite(link); } else if (type.name == ActionType.email.name) { - model.openMail(link, context); + showDialog( + context: context, + builder: (BuildContext context) { + return NeedHelpNoticeDialog( + openMail: () => model.openMail(link, context), + launchWebsite: () => model.launchWebsite(link)); + }); } }, child: getActionCardInfo( diff --git a/test/ui/core/ui/need_help_notice_dialog_test.dart b/test/ui/core/ui/need_help_notice_dialog_test.dart new file mode 100644 index 000000000..8a22686be --- /dev/null +++ b/test/ui/core/ui/need_help_notice_dialog_test.dart @@ -0,0 +1,46 @@ +// Flutter imports: +import 'package:flutter/material.dart'; + +// Package imports: +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +// Project imports: +import 'package:notredame/ui/core/ui/need_help_notice_dialog.dart'; +import '../../../data/mocks/repositories/settings_repository_mock.dart'; +import '../../../helpers.dart'; + +void main() { + SharedPreferences.setMockInitialValues({}); + group('NeedHelpNoticeDialog - ', () { + late SettingsRepositoryMock settingsManagerMock; + + setUp(() async { + setupLaunchUrlServiceMock(); + setupNetworkingServiceMock(); + + settingsManagerMock = setupSettingsManagerMock(); + }); + + tearDown(() {}); + + testWidgets('tapping "Cancel" closes dialog', (WidgetTester tester) async { + SettingsRepositoryMock.stubLocale(settingsManagerMock); + + NeedHelpNoticeDialog dialog = + NeedHelpNoticeDialog(openMail: () {}, launchWebsite: () {}); + + await tester.pumpWidget(localizedWidget(child: dialog)); + await tester.pumpAndSettle(); + + final cancelButton = find.byIcon(Icons.cancel); + expect(cancelButton, findsAny); + + await tester.tap(cancelButton); + await tester.pumpAndSettle(); + + final dialogFound = find.byType(dialog.runtimeType); + expect(dialogFound, findsNothing); + }); + }); +} diff --git a/test/ui/more/faq/view_model/faq_viewmodel_test.dart b/test/ui/more/faq/view_model/faq_viewmodel_test.dart index 7cabd9130..8a420e273 100644 --- a/test/ui/more/faq/view_model/faq_viewmodel_test.dart +++ b/test/ui/more/faq/view_model/faq_viewmodel_test.dart @@ -1,5 +1,6 @@ // Package imports: import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; import 'package:mockito/mockito.dart'; // Project imports: @@ -11,7 +12,6 @@ import '../../../../helpers.dart'; void main() { late LaunchUrlServiceMock launchUrlServiceMock; - late FaqViewModel viewModel; group('FaqViewModel - ', () { @@ -34,6 +34,20 @@ void main() { verify(launchUrlServiceMock.launchInBrowser("https://clubapplets.ca/")) .called(1); }); + + test('ETS password assistance web page (en) returns "200 OK"', () async { + final url = Uri.parse("https://partage.etsmtl.ca/fs/en.html"); + final http.Response response = await http.get(url); + + expect(response.statusCode, 200); + }); + + test('ETS password assistance web page (fr) returns "200 OK"', () async { + final url = Uri.parse("https://partage.etsmtl.ca/fs/fr.html"); + final http.Response response = await http.get(url); + + expect(response.statusCode, 200); + }); }); }); } diff --git a/test/ui/more/faq/widgets/faq_view_test.dart b/test/ui/more/faq/widgets/faq_view_test.dart index 59cdb4469..2e7f575f8 100644 --- a/test/ui/more/faq/widgets/faq_view_test.dart +++ b/test/ui/more/faq/widgets/faq_view_test.dart @@ -22,6 +22,7 @@ void main() { setUp(() async { setupLaunchUrlServiceMock(); setupNetworkingServiceMock(); + settingsManagerMock = setupSettingsManagerMock(); appIntl = await setupAppIntl(); }); @@ -33,7 +34,6 @@ void main() { SettingsRepositoryMock.stubLocale(settingsManagerMock); await tester.pumpWidget(localizedWidget(child: const FaqView())); - await tester.pumpAndSettle(const Duration(milliseconds: 800)); final Faq faq = Faq(); @@ -46,7 +46,6 @@ void main() { final action4 = find.text(faq.actions[3].title["en"]!, skipOffstage: false); - await tester.pump(); await tester.drag(find.byType(ListView), const Offset(0.0, -300)); await tester.pump(); @@ -56,6 +55,29 @@ void main() { expect(action4, findsOneWidget); }); + testWidgets('tapping "Questions About ETS Button" shows dialog', + (WidgetTester tester) async { + SettingsRepositoryMock.stubLocale(settingsManagerMock); + + await tester.pumpWidget(localizedWidget(child: const FaqView())); + + final Faq faq = Faq(); + + await tester.drag(find.byType(ListView), const Offset(0.0, -500)); + await tester.pumpAndSettle(); + + final questionsAbtETSMobileBtn = + find.widgetWithText(Card, faq.actions[3].title["en"]!); + expect(questionsAbtETSMobileBtn, findsOneWidget); + + await tester.tap(questionsAbtETSMobileBtn); + await tester.pumpAndSettle(); + + final dialog = find.byType(AlertDialog); + + expect(dialog, findsOne); + }); + testWidgets('has 2 subtitles', (WidgetTester tester) async { SettingsRepositoryMock.stubLocale(settingsManagerMock);