From 5bfa1e3d33954239be2685135186dee30c427d31 Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Mon, 10 Jul 2023 23:26:57 -0400 Subject: [PATCH 01/20] Reordable quicklinks --- ios/Podfile.lock | 230 +++++++++--------- lib/core/constants/quick_links.dart | 9 + lib/core/models/quick_link.dart | 7 +- lib/ui/views/quick_links_view.dart | 27 +- pubspec.lock | 7 + pubspec.yaml | 1 + test/ui/widgets/link_web_view_test.dart | 1 + test/ui/widgets/web_link_card_test.dart | 4 +- .../web_link_card_viewmodel_test.dart | 4 +- 9 files changed, 167 insertions(+), 123 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b9a272696..55ad16582 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -4,90 +4,84 @@ PODS: - ReachabilitySwift - device_info_plus (0.0.1): - Flutter - - Firebase/Analytics (9.2.0): + - Firebase/Analytics (10.3.0): - Firebase/Core - - Firebase/Core (9.2.0): + - Firebase/Core (10.3.0): - Firebase/CoreOnly - - FirebaseAnalytics (~> 9.2.0) - - Firebase/CoreOnly (9.2.0): - - FirebaseCore (= 9.2.0) - - Firebase/Crashlytics (9.2.0): + - FirebaseAnalytics (~> 10.3.0) + - Firebase/CoreOnly (10.3.0): + - FirebaseCore (= 10.3.0) + - Firebase/Crashlytics (10.3.0): - Firebase/CoreOnly - - FirebaseCrashlytics (~> 9.2.0) - - Firebase/RemoteConfig (9.2.0): + - FirebaseCrashlytics (~> 10.3.0) + - Firebase/RemoteConfig (10.3.0): - Firebase/CoreOnly - - FirebaseRemoteConfig (~> 9.2.0) - - firebase_analytics (9.2.0): - - Firebase/Analytics (= 9.2.0) + - FirebaseRemoteConfig (~> 10.3.0) + - firebase_analytics (10.1.0): + - Firebase/Analytics (= 10.3.0) - firebase_core - Flutter - - firebase_core (1.19.2): - - Firebase/CoreOnly (= 9.2.0) + - firebase_core (2.4.1): + - Firebase/CoreOnly (= 10.3.0) - Flutter - - firebase_crashlytics (2.6.1): - - Firebase/Crashlytics (= 9.2.0) + - firebase_crashlytics (3.0.9): + - Firebase/Crashlytics (= 10.3.0) - firebase_core - Flutter - - firebase_remote_config (2.0.10): - - Firebase/RemoteConfig (= 9.2.0) + - firebase_remote_config (3.0.9): + - Firebase/RemoteConfig (= 10.3.0) - firebase_core - Flutter - - FirebaseABTesting (9.6.0): - - FirebaseCore (~> 9.0) - - FirebaseAnalytics (9.2.0): - - FirebaseAnalytics/AdIdSupport (= 9.2.0) - - FirebaseCore (~> 9.0) - - FirebaseInstallations (~> 9.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - - GoogleUtilities/MethodSwizzler (~> 7.7) - - GoogleUtilities/Network (~> 7.7) - - "GoogleUtilities/NSData+zlib (~> 7.7)" + - FirebaseABTesting (10.5.0): + - FirebaseCore (~> 10.0) + - FirebaseAnalytics (10.3.0): + - FirebaseAnalytics/AdIdSupport (= 10.3.0) + - FirebaseCore (~> 10.0) + - FirebaseInstallations (~> 10.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.8) + - GoogleUtilities/MethodSwizzler (~> 7.8) + - GoogleUtilities/Network (~> 7.8) + - "GoogleUtilities/NSData+zlib (~> 7.8)" - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseAnalytics/AdIdSupport (9.2.0): - - FirebaseCore (~> 9.0) - - FirebaseInstallations (~> 9.0) - - GoogleAppMeasurement (= 9.2.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - - GoogleUtilities/MethodSwizzler (~> 7.7) - - GoogleUtilities/Network (~> 7.7) - - "GoogleUtilities/NSData+zlib (~> 7.7)" + - FirebaseAnalytics/AdIdSupport (10.3.0): + - FirebaseCore (~> 10.0) + - FirebaseInstallations (~> 10.0) + - GoogleAppMeasurement (= 10.3.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.8) + - GoogleUtilities/MethodSwizzler (~> 7.8) + - GoogleUtilities/Network (~> 7.8) + - "GoogleUtilities/NSData+zlib (~> 7.8)" - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseCore (9.2.0): - - FirebaseCoreDiagnostics (~> 9.0) - - FirebaseCoreInternal (~> 9.0) - - GoogleUtilities/Environment (~> 7.7) - - GoogleUtilities/Logger (~> 7.7) - - FirebaseCoreDiagnostics (9.5.0): - - GoogleDataTransport (< 10.0.0, >= 9.1.4) - - GoogleUtilities/Environment (~> 7.7) - - GoogleUtilities/Logger (~> 7.7) - - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseCoreInternal (9.5.0): - - "GoogleUtilities/NSData+zlib (~> 7.7)" - - FirebaseCrashlytics (9.2.0): - - FirebaseCore (~> 9.0) - - FirebaseInstallations (~> 9.0) - - GoogleDataTransport (< 10.0.0, >= 9.1.4) - - GoogleUtilities/Environment (~> 7.7) + - FirebaseCore (10.3.0): + - FirebaseCoreInternal (~> 10.0) + - GoogleUtilities/Environment (~> 7.8) + - GoogleUtilities/Logger (~> 7.8) + - FirebaseCoreInternal (10.5.0): + - "GoogleUtilities/NSData+zlib (~> 7.8)" + - FirebaseCrashlytics (10.3.0): + - FirebaseCore (~> 10.0) + - FirebaseInstallations (~> 10.0) + - GoogleDataTransport (~> 9.2) + - GoogleUtilities/Environment (~> 7.8) - nanopb (< 2.30910.0, >= 2.30908.0) - PromisesObjC (~> 2.1) - - FirebaseInstallations (9.5.0): - - FirebaseCore (~> 9.0) - - GoogleUtilities/Environment (~> 7.7) - - GoogleUtilities/UserDefaults (~> 7.7) + - FirebaseInstallations (10.5.0): + - FirebaseCore (~> 10.0) + - GoogleUtilities/Environment (~> 7.8) + - GoogleUtilities/UserDefaults (~> 7.8) - PromisesObjC (~> 2.1) - - FirebaseRemoteConfig (9.2.0): - - FirebaseABTesting (~> 9.0) - - FirebaseCore (~> 9.0) - - FirebaseInstallations (~> 9.0) - - GoogleUtilities/Environment (~> 7.7) - - "GoogleUtilities/NSData+zlib (~> 7.7)" + - FirebaseRemoteConfig (10.3.0): + - FirebaseABTesting (~> 10.0) + - FirebaseCore (~> 10.0) + - FirebaseInstallations (~> 10.0) + - GoogleUtilities/Environment (~> 7.8) + - "GoogleUtilities/NSData+zlib (~> 7.8)" - Flutter (1.0.0) - flutter_config (0.0.1): - Flutter - flutter_custom_tabs (0.0.1): - Flutter - - flutter_secure_storage (3.3.1): + - flutter_secure_storage (6.0.0): - Flutter - fluttertoast (0.0.2): - Flutter @@ -95,30 +89,30 @@ PODS: - FMDB (2.7.5): - FMDB/standard (= 2.7.5) - FMDB/standard (2.7.5) - - google_maps_flutter (0.0.1): + - google_maps_flutter_ios (0.0.1): - Flutter - GoogleMaps - - GoogleAppMeasurement (9.2.0): - - GoogleAppMeasurement/AdIdSupport (= 9.2.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - - GoogleUtilities/MethodSwizzler (~> 7.7) - - GoogleUtilities/Network (~> 7.7) - - "GoogleUtilities/NSData+zlib (~> 7.7)" + - GoogleAppMeasurement (10.3.0): + - GoogleAppMeasurement/AdIdSupport (= 10.3.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.8) + - GoogleUtilities/MethodSwizzler (~> 7.8) + - GoogleUtilities/Network (~> 7.8) + - "GoogleUtilities/NSData+zlib (~> 7.8)" - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleAppMeasurement/AdIdSupport (9.2.0): - - GoogleAppMeasurement/WithoutAdIdSupport (= 9.2.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - - GoogleUtilities/MethodSwizzler (~> 7.7) - - GoogleUtilities/Network (~> 7.7) - - "GoogleUtilities/NSData+zlib (~> 7.7)" + - GoogleAppMeasurement/AdIdSupport (10.3.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 10.3.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.8) + - GoogleUtilities/MethodSwizzler (~> 7.8) + - GoogleUtilities/Network (~> 7.8) + - "GoogleUtilities/NSData+zlib (~> 7.8)" - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleAppMeasurement/WithoutAdIdSupport (9.2.0): - - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - - GoogleUtilities/MethodSwizzler (~> 7.7) - - GoogleUtilities/Network (~> 7.7) - - "GoogleUtilities/NSData+zlib (~> 7.7)" + - GoogleAppMeasurement/WithoutAdIdSupport (10.3.0): + - GoogleUtilities/AppDelegateSwizzler (~> 7.8) + - GoogleUtilities/MethodSwizzler (~> 7.8) + - GoogleUtilities/Network (~> 7.8) + - "GoogleUtilities/NSData+zlib (~> 7.8)" - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleDataTransport (9.2.0): + - GoogleDataTransport (9.2.1): - GoogleUtilities/Environment (~> 7.7) - nanopb (< 2.30910.0, >= 2.30908.0) - PromisesObjC (< 3.0, >= 1.2) @@ -127,24 +121,24 @@ PODS: - GoogleMaps/Base (5.2.0) - GoogleMaps/Maps (5.2.0): - GoogleMaps/Base - - GoogleUtilities/AppDelegateSwizzler (7.7.0): + - GoogleUtilities/AppDelegateSwizzler (7.11.0): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - - GoogleUtilities/Environment (7.7.0): + - GoogleUtilities/Environment (7.11.0): - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Logger (7.7.0): + - GoogleUtilities/Logger (7.11.0): - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (7.7.0): + - GoogleUtilities/MethodSwizzler (7.11.0): - GoogleUtilities/Logger - - GoogleUtilities/Network (7.7.0): + - GoogleUtilities/Network (7.11.0): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (7.7.0)" - - GoogleUtilities/Reachability (7.7.0): + - "GoogleUtilities/NSData+zlib (7.11.0)" + - GoogleUtilities/Reachability (7.11.0): - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (7.7.0): + - GoogleUtilities/UserDefaults (7.11.0): - GoogleUtilities/Logger - home_widget (0.0.1): - Flutter @@ -166,6 +160,7 @@ PODS: - sqflite (0.0.2): - Flutter - FMDB (>= 2.7.5) + - SwiftyXMLParser (5.5.0) - Toast (4.0.0) - url_launcher_ios (0.0.1): - Flutter @@ -184,13 +179,14 @@ DEPENDENCIES: - flutter_custom_tabs (from `.symlinks/plugins/flutter_custom_tabs/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - - google_maps_flutter (from `.symlinks/plugins/google_maps_flutter/ios`) + - google_maps_flutter_ios (from `.symlinks/plugins/google_maps_flutter_ios/ios`) - home_widget (from `.symlinks/plugins/home_widget/ios`) - in_app_review (from `.symlinks/plugins/in_app_review/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) + - SwiftyXMLParser (from `https://github.com/yahoojapan/SwiftyXMLParser.git`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) @@ -200,7 +196,6 @@ SPEC REPOS: - FirebaseABTesting - FirebaseAnalytics - FirebaseCore - - FirebaseCoreDiagnostics - FirebaseCoreInternal - FirebaseCrashlytics - FirebaseInstallations @@ -238,8 +233,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_secure_storage/ios" fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" - google_maps_flutter: - :path: ".symlinks/plugins/google_maps_flutter/ios" + google_maps_flutter_ios: + :path: ".symlinks/plugins/google_maps_flutter_ios/ios" home_widget: :path: ".symlinks/plugins/home_widget/ios" in_app_review: @@ -252,40 +247,46 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/shared_preferences_ios/ios" sqflite: :path: ".symlinks/plugins/sqflite/ios" + SwiftyXMLParser: + :git: https://github.com/yahoojapan/SwiftyXMLParser.git url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" webview_flutter_wkwebview: :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" +CHECKOUT OPTIONS: + SwiftyXMLParser: + :commit: d7a1d23f04c86c1cd2e8f19247dd15d74e0ea8be + :git: https://github.com/yahoojapan/SwiftyXMLParser.git + SPEC CHECKSUMS: connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed - Firebase: 4ba896cb8e5105d4b9e247e1c1b6222b548df55a - firebase_analytics: e6754d7dd82bd2006b002d88c0f44f8f50da26e6 - firebase_core: ada8be870601fe3c2684dae2356f634189bd598f - firebase_crashlytics: fed0cb9004bc3bb7005f04cf2e01765c441e941a - firebase_remote_config: a99557e31b31cb583d3ca70c66134099ad4b054b - FirebaseABTesting: 61826730ce9eee8781ba99a2b3420e9bce148dc9 - FirebaseAnalytics: af5a03a8dff7648c7b8486f6a78b1368e0268dd3 - FirebaseCore: 0e27f2a15d8f7b7ef11e7d93e23b1cbab55d748c - FirebaseCoreDiagnostics: 17cbf4e72b1dbd64bfdc33d4b1f07bce4f16f1d8 - FirebaseCoreInternal: 50a8e39cae8abf72d5145d07ea34c3244f70862b - FirebaseCrashlytics: 9fff819edb2bfc9d3eff612225b207d41945a935 - FirebaseInstallations: 41f811b530c41dd90973d0174381cdb3fcb5e839 - FirebaseRemoteConfig: 16e29297f0dd0c7d2415c4506d614fe0b54875d1 + Firebase: f92fc551ead69c94168d36c2b26188263860acd9 + firebase_analytics: 9f3a4cb560a59976b2c48707abae2d4cb94bcb3a + firebase_core: bf59c32d2e53814f558efa20840c1902fa2fe461 + firebase_crashlytics: a45cced3521640e1e389d8b3662936ea9afd6055 + firebase_remote_config: 5007603d4cec2dc1e5016077a7ec36ed93c5041b + FirebaseABTesting: 8cb5cc4e395c8dce8a2820a6a329020ead56fe2f + FirebaseAnalytics: 036232b6a1e2918e5f67572417be1173576245f3 + FirebaseCore: 988754646ab3bd4bdcb740f1bfe26b9f6c0d5f2a + FirebaseCoreInternal: e463f41bb935cd049505bf7e9a5bdd7dcea90df6 + FirebaseCrashlytics: f20d956f8229010b645e534693c39e0b7843c268 + FirebaseInstallations: 935bc4abb6f7a035cab7a0c31cb777b2be3dd254 + FirebaseRemoteConfig: c24f767c17b0440ee63c7e93380d599173556113 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_config: 2226c1df19c78fe34a05eb7f1363445f18e76fc1 flutter_custom_tabs: 7a10a08686955cb748e5d26e0ae586d30689bf89 - flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec + flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - google_maps_flutter: c59fc576c0d0c7f4dc4bd63832c862d22d5a7c6d - GoogleAppMeasurement: 7a33224321f975d58c166657260526775d9c6b1a - GoogleDataTransport: 1c8145da7117bd68bbbed00cf304edb6a24de00f + google_maps_flutter_ios: 66201f392bf62d500f07670a30488a247b9bb5b9 + GoogleAppMeasurement: c7d6fff39bf2d829587d74088d582e32d75133c3 + GoogleDataTransport: ea169759df570f4e37bdee1623ec32a7e64e67c4 GoogleMaps: 025272d5876d3b32604e5c080dc25eaf68764693 - GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1 + GoogleUtilities: c2bdc4cf2ce786c4d2e6b3bcfd599a25ca78f06f home_widget: 2829415127ee92e876f816cbbe44c0b6601b8a37 - in_app_review: 4a97249f7a2f539a0f294c2d9196b7fe35e49541 + in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 @@ -293,10 +294,11 @@ SPEC CHECKSUMS: ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 + SwiftyXMLParser: 027d9e6fb54a38d95dccec025bcea9693f699c47 Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de webview_flutter_wkwebview: 005fbd90c888a42c5690919a1527ecc6649e1162 -PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 +PODFILE CHECKSUM: a6ab79567d5a527b85ace1f55bd19f140d2d619c COCOAPODS: 1.11.3 diff --git a/lib/core/constants/quick_links.dart b/lib/core/constants/quick_links.dart index e6acff0d6..f22f7290c 100644 --- a/lib/core/constants/quick_links.dart +++ b/lib/core/constants/quick_links.dart @@ -12,6 +12,7 @@ import 'package:notredame/ui/utils/app_theme.dart'; List quickLinks(AppIntl intl) => [ QuickLink( + id: 1, name: intl.ets_security_title, image: const FaIcon( FontAwesomeIcons.shieldHalved, @@ -20,6 +21,7 @@ List quickLinks(AppIntl intl) => [ ), link: 'security'), QuickLink( + id: 2, name: intl.ets_monets_title, image: Image.asset( 'assets/images/ic_monets_sans_nom_red.png', @@ -27,6 +29,7 @@ List quickLinks(AppIntl intl) => [ ), link: 'https://portail.etsmtl.ca/home'), QuickLink( + id: 3, name: intl.ets_bibliotech_title, image: const FaIcon( FontAwesomeIcons.book, @@ -35,6 +38,7 @@ List quickLinks(AppIntl intl) => [ ), link: 'https://www.etsmtl.ca/Bibliotheque/Accueil'), QuickLink( + id: 4, name: intl.ets_news_title, image: const FaIcon( FontAwesomeIcons.newspaper, @@ -43,6 +47,7 @@ List quickLinks(AppIntl intl) => [ ), link: 'https://www.etsmtl.ca/nouvelles'), QuickLink( + id: 5, name: intl.ets_directory_title, image: const FaIcon( FontAwesomeIcons.addressBook, @@ -51,6 +56,7 @@ List quickLinks(AppIntl intl) => [ ), link: 'https://www.etsmtl.ca/bottin'), QuickLink( + id: 6, name: intl.ets_moodle_title, image: Image.asset( 'assets/images/ic_moodle_red.png', @@ -58,6 +64,7 @@ List quickLinks(AppIntl intl) => [ ), link: 'https://ena.etsmtl.ca/'), QuickLink( + id: 7, name: intl.ets_schedule_generator, image: const FaIcon( FontAwesomeIcons.calendar, @@ -66,11 +73,13 @@ List quickLinks(AppIntl intl) => [ ), link: 'http://rubik.clubnaova.ca/'), QuickLink( + id: 8, name: intl.ets_gus, image: SvgPicture.asset('assets/images/ic_gus_red.svg', color: AppTheme.etsLightRed), link: 'https://gus.etsmtl.ca/c2atom/mobile/login'), QuickLink( + id: 9, name: intl.ets_papercut_title, image: const FaIcon( FontAwesomeIcons.print, diff --git a/lib/core/models/quick_link.dart b/lib/core/models/quick_link.dart index c2fe715b6..d2699d7e3 100644 --- a/lib/core/models/quick_link.dart +++ b/lib/core/models/quick_link.dart @@ -2,9 +2,14 @@ import 'package:flutter/material.dart'; class QuickLink { + final int id; final Widget image; final String name; final String link; - QuickLink({@required this.image, @required this.name, @required this.link}); + QuickLink( + {@required this.id, + @required this.image, + @required this.name, + @required this.link}); } diff --git a/lib/ui/views/quick_links_view.dart b/lib/ui/views/quick_links_view.dart index fda35c08e..bccdadc3d 100644 --- a/lib/ui/views/quick_links_view.dart +++ b/lib/ui/views/quick_links_view.dart @@ -1,5 +1,7 @@ // FLUTTER / DART / THIRD-PARTIES import 'package:flutter/material.dart'; +import 'package:notredame/core/models/quick_link.dart'; +import 'package:reorderable_grid_view/reorderable_grid_view.dart'; import 'package:stacked/stacked.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -31,14 +33,31 @@ class _QuickLinksViewState extends State { body: SafeArea( child: Align( alignment: Alignment.topCenter, - child: SingleChildScrollView( + child: Padding( padding: const EdgeInsets.only(top: 8.0), - child: Wrap( - alignment: WrapAlignment.center, + child: ReorderableGridView.count( + mainAxisSpacing: 2.0, + crossAxisSpacing: 2.0, + crossAxisCount: 3, children: List.generate( model.quickLinkList.length, - (index) => WebLinkCard(model.quickLinkList[index]), + (index) { + return KeyedSubtree( + key: ValueKey(model.quickLinkList[index].id), + child: WebLinkCard(model.quickLinkList[index]), + ); + }, ), + onReorder: (oldIndex, newIndex) { + setState(() { + if (oldIndex < newIndex) { + newIndex -= 1; + } + final QuickLink item = + model.quickLinkList.removeAt(oldIndex); + model.quickLinkList.insert(newIndex, item); + }); + }, ), ), ), diff --git a/pubspec.lock b/pubspec.lock index 97d7fc88d..354f66e16 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -905,6 +905,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + reorderable_grid_view: + dependency: "direct main" + description: + name: reorderable_grid_view + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.6" rive: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 49f6f8ec3..58c68292f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -71,6 +71,7 @@ dependencies: auto_size_text: ^3.0.0 easter_egg_trigger: ^1.0.1 calendar_view: ^1.0.1 + reorderable_grid_view: ^2.2.6 dev_dependencies: flutter_test: diff --git a/test/ui/widgets/link_web_view_test.dart b/test/ui/widgets/link_web_view_test.dart index b7b1e48a1..93ce97810 100644 --- a/test/ui/widgets/link_web_view_test.dart +++ b/test/ui/widgets/link_web_view_test.dart @@ -13,6 +13,7 @@ import 'package:notredame/ui/widgets/link_web_view.dart'; import '../../helpers.dart'; final _quickLink = QuickLink( + id: 1, image: const Icon(Icons.ac_unit), name: 'test', link: 'https://clubapplets.ca/'); diff --git a/test/ui/widgets/web_link_card_test.dart b/test/ui/widgets/web_link_card_test.dart index a730f53b8..f1ea7dec2 100644 --- a/test/ui/widgets/web_link_card_test.dart +++ b/test/ui/widgets/web_link_card_test.dart @@ -15,8 +15,8 @@ import 'package:notredame/ui/widgets/web_link_card.dart'; import '../../helpers.dart'; -final _quickLink = - QuickLink(image: const Icon(Icons.ac_unit), name: 'test', link: 'testlink'); +final _quickLink = QuickLink( + id: 1, image: const Icon(Icons.ac_unit), name: 'test', link: 'testlink'); void main() { AnalyticsService analyticsService; diff --git a/test/viewmodels/web_link_card_viewmodel_test.dart b/test/viewmodels/web_link_card_viewmodel_test.dart index ce3a5d501..1fe34450e 100644 --- a/test/viewmodels/web_link_card_viewmodel_test.dart +++ b/test/viewmodels/web_link_card_viewmodel_test.dart @@ -30,9 +30,9 @@ void main() { WebLinkCardViewModel viewModel; final quickLink = QuickLink( - image: const Icon(Icons.ac_unit), name: 'test', link: 'testlink'); + id: 1, image: const Icon(Icons.ac_unit), name: 'test', link: 'testlink'); final securityQuickLink = QuickLink( - image: const Icon(Icons.ac_unit), name: 'test', link: 'security'); + id: 1, image: const Icon(Icons.ac_unit), name: 'test', link: 'security'); group('WebLinkCardViewModel - ', () { setUp(() async { From 92a287ad3fbdfc3dcfb8fcf6f63b5a91bf81fed1 Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Mon, 10 Jul 2023 23:39:23 -0400 Subject: [PATCH 02/20] Make quicklinks deletable --- lib/ui/views/quick_links_view.dart | 83 ++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/lib/ui/views/quick_links_view.dart b/lib/ui/views/quick_links_view.dart index bccdadc3d..2bc874b51 100644 --- a/lib/ui/views/quick_links_view.dart +++ b/lib/ui/views/quick_links_view.dart @@ -20,6 +20,9 @@ class QuickLinksView extends StatefulWidget { } class _QuickLinksViewState extends State { + // Show delete icon flag + bool _showDeleteIcon = false; + @override Widget build(BuildContext context) => ViewModelBuilder.reactive( @@ -30,34 +33,62 @@ class _QuickLinksViewState extends State { title: Text(AppIntl.of(context).title_ets), automaticallyImplyLeading: false, ), - body: SafeArea( - child: Align( - alignment: Alignment.topCenter, - child: Padding( - padding: const EdgeInsets.only(top: 8.0), - child: ReorderableGridView.count( - mainAxisSpacing: 2.0, - crossAxisSpacing: 2.0, - crossAxisCount: 3, - children: List.generate( - model.quickLinkList.length, - (index) { - return KeyedSubtree( - key: ValueKey(model.quickLinkList[index].id), - child: WebLinkCard(model.quickLinkList[index]), - ); + body: GestureDetector( + onTap: () { + setState(() { + _showDeleteIcon = false; + }); + }, + child: SafeArea( + child: Align( + alignment: Alignment.topCenter, + child: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: ReorderableGridView.count( + mainAxisSpacing: 2.0, + crossAxisSpacing: 2.0, + crossAxisCount: 3, + children: List.generate( + model.quickLinkList.length, + (index) { + return KeyedSubtree( + key: ValueKey(model.quickLinkList[index].id), + child: GestureDetector( + onLongPress: () { + setState(() { + _showDeleteIcon = true; + }); + }, + child: Stack( + alignment: Alignment.topRight, + children: [ + WebLinkCard(model.quickLinkList[index]), + if (_showDeleteIcon) + IconButton( + icon: const Icon(Icons.close), + onPressed: () { + setState(() { + model.quickLinkList.removeAt(index); + }); + }, + ), + ], + ), + ), + ); + }, + ), + onReorder: (oldIndex, newIndex) { + setState(() { + if (oldIndex < newIndex) { + newIndex -= 1; + } + final QuickLink item = + model.quickLinkList.removeAt(oldIndex); + model.quickLinkList.insert(newIndex, item); + }); }, ), - onReorder: (oldIndex, newIndex) { - setState(() { - if (oldIndex < newIndex) { - newIndex -= 1; - } - final QuickLink item = - model.quickLinkList.removeAt(oldIndex); - model.quickLinkList.insert(newIndex, item); - }); - }, ), ), ), From 18b321a9621045c891a643bde489b99caa0c6e6a Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Tue, 11 Jul 2023 00:01:29 -0400 Subject: [PATCH 03/20] Edit mode required for reordering --- lib/ui/views/quick_links_view.dart | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/ui/views/quick_links_view.dart b/lib/ui/views/quick_links_view.dart index 2bc874b51..911e11842 100644 --- a/lib/ui/views/quick_links_view.dart +++ b/lib/ui/views/quick_links_view.dart @@ -20,8 +20,8 @@ class QuickLinksView extends StatefulWidget { } class _QuickLinksViewState extends State { - // Show delete icon flag - bool _showDeleteIcon = false; + // Enable/Disable the edit state + bool _editMode = false; @override Widget build(BuildContext context) => @@ -36,7 +36,7 @@ class _QuickLinksViewState extends State { body: GestureDetector( onTap: () { setState(() { - _showDeleteIcon = false; + _editMode = false; }); }, child: SafeArea( @@ -54,16 +54,18 @@ class _QuickLinksViewState extends State { return KeyedSubtree( key: ValueKey(model.quickLinkList[index].id), child: GestureDetector( - onLongPress: () { - setState(() { - _showDeleteIcon = true; - }); - }, + onLongPress: _editMode + ? null + : () { + setState(() { + _editMode = true; + }); + }, child: Stack( alignment: Alignment.topRight, children: [ WebLinkCard(model.quickLinkList[index]), - if (_showDeleteIcon) + if (_editMode) IconButton( icon: const Icon(Icons.close), onPressed: () { From 0708194cde99a01e7d4431098b421eb921456c27 Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Tue, 11 Jul 2023 19:39:32 -0400 Subject: [PATCH 04/20] Make delete icon a badge --- lib/ui/views/quick_links_view.dart | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/lib/ui/views/quick_links_view.dart b/lib/ui/views/quick_links_view.dart index 911e11842..6406c1501 100644 --- a/lib/ui/views/quick_links_view.dart +++ b/lib/ui/views/quick_links_view.dart @@ -62,17 +62,30 @@ class _QuickLinksViewState extends State { }); }, child: Stack( - alignment: Alignment.topRight, children: [ WebLinkCard(model.quickLinkList[index]), if (_editMode) - IconButton( - icon: const Icon(Icons.close), - onPressed: () { - setState(() { - model.quickLinkList.removeAt(index); - }); - }, + Positioned( + top: 0, + right: 0, + child: Container( + width: 32, + height: 32, + decoration: const BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + child: IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.close, + color: Colors.white, size: 16), + onPressed: () { + setState(() { + model.quickLinkList.removeAt(index); + }); + }, + ), + ), ), ], ), From 621a898b0e79780e8d44da4a777d8190db557db4 Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Tue, 11 Jul 2023 19:59:34 -0400 Subject: [PATCH 05/20] Shake animation --- lib/ui/views/quick_links_view.dart | 99 ++++++++++++++++++++---------- 1 file changed, 67 insertions(+), 32 deletions(-) diff --git a/lib/ui/views/quick_links_view.dart b/lib/ui/views/quick_links_view.dart index 6406c1501..f25639dbf 100644 --- a/lib/ui/views/quick_links_view.dart +++ b/lib/ui/views/quick_links_view.dart @@ -19,10 +19,27 @@ class QuickLinksView extends StatefulWidget { _QuickLinksViewState createState() => _QuickLinksViewState(); } -class _QuickLinksViewState extends State { +class _QuickLinksViewState extends State + with SingleTickerProviderStateMixin { // Enable/Disable the edit state bool _editMode = false; + // Animation Controller for Shake Animation + AnimationController _controller; + Animation _animation; + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + duration: const Duration(milliseconds: 250), + vsync: this, + ); + + _animation = Tween(begin: -0.05, end: 0.05).animate(_controller); + } + @override Widget build(BuildContext context) => ViewModelBuilder.reactive( @@ -35,9 +52,13 @@ class _QuickLinksViewState extends State { ), body: GestureDetector( onTap: () { - setState(() { - _editMode = false; - }); + if (_editMode) { + _controller + .reset(); // Reset the animation when _editMode is set to false + setState(() { + _editMode = false; + }); + } }, child: SafeArea( child: Align( @@ -57,37 +78,48 @@ class _QuickLinksViewState extends State { onLongPress: _editMode ? null : () { + _controller.repeat(reverse: true); setState(() { _editMode = true; }); }, - child: Stack( - children: [ - WebLinkCard(model.quickLinkList[index]), - if (_editMode) - Positioned( - top: 0, - right: 0, - child: Container( - width: 32, - height: 32, - decoration: const BoxDecoration( - color: Colors.red, - shape: BoxShape.circle, - ), - child: IconButton( - padding: EdgeInsets.zero, - icon: const Icon(Icons.close, - color: Colors.white, size: 16), - onPressed: () { - setState(() { - model.quickLinkList.removeAt(index); - }); - }, + child: AnimatedBuilder( + animation: _animation, + builder: (BuildContext context, Widget child) { + return Transform.rotate( + angle: _editMode ? _animation.value : 0, + child: child, + ); + }, + child: Stack( + children: [ + WebLinkCard(model.quickLinkList[index]), + if (_editMode) + Positioned( + top: 0, + left: 0, + child: Container( + width: 32, + height: 32, + decoration: const BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + child: IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.close, + color: Colors.white, size: 16), + onPressed: () { + setState(() { + model.quickLinkList + .removeAt(index); + }); + }, + ), ), ), - ), - ], + ], + ), ), ), ); @@ -95,9 +127,6 @@ class _QuickLinksViewState extends State { ), onReorder: (oldIndex, newIndex) { setState(() { - if (oldIndex < newIndex) { - newIndex -= 1; - } final QuickLink item = model.quickLinkList.removeAt(oldIndex); model.quickLinkList.insert(newIndex, item); @@ -110,4 +139,10 @@ class _QuickLinksViewState extends State { ), ), ); + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } } From bb3b5f5b49216ecef7cba6575fb28487f0076d91 Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Thu, 13 Jul 2023 20:38:06 -0400 Subject: [PATCH 06/20] Refactor for readability --- lib/ui/views/quick_links_view.dart | 196 ++++++++++++++++------------- 1 file changed, 107 insertions(+), 89 deletions(-) diff --git a/lib/ui/views/quick_links_view.dart b/lib/ui/views/quick_links_view.dart index f25639dbf..3a0fadf28 100644 --- a/lib/ui/views/quick_links_view.dart +++ b/lib/ui/views/quick_links_view.dart @@ -1,6 +1,7 @@ // FLUTTER / DART / THIRD-PARTIES import 'package:flutter/material.dart'; import 'package:notredame/core/models/quick_link.dart'; +import 'package:notredame/ui/utils/app_theme.dart'; import 'package:reorderable_grid_view/reorderable_grid_view.dart'; import 'package:stacked/stacked.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -46,99 +47,116 @@ class _QuickLinksViewState extends State viewModelBuilder: () => QuickLinksViewModel(AppIntl.of(context)), builder: (context, model, child) => BaseScaffold( isLoading: model.isBusy, - appBar: AppBar( - title: Text(AppIntl.of(context).title_ets), - automaticallyImplyLeading: false, + appBar: _buildAppBar(context), + body: _buildBody(context, model), + ), + ); + + AppBar _buildAppBar(BuildContext context) { + return AppBar( + title: Text(AppIntl.of(context).title_ets), + automaticallyImplyLeading: false, + ); + } + + Widget _buildBody(BuildContext context, QuickLinksViewModel model) { + return GestureDetector( + onTap: () { + if (_editMode) { + _controller.reset(); + setState(() { + _editMode = false; + }); + } + }, + child: SafeArea( + child: Align( + alignment: Alignment.topCenter, + child: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: _buildReorderableGridView(model), ), - body: GestureDetector( - onTap: () { - if (_editMode) { - _controller - .reset(); // Reset the animation when _editMode is set to false - setState(() { - _editMode = false; - }); - } + ), + ), + ); + } + + ReorderableGridView _buildReorderableGridView(QuickLinksViewModel model) { + return ReorderableGridView.count( + mainAxisSpacing: 2.0, + crossAxisSpacing: 2.0, + crossAxisCount: 3, + children: List.generate( + model.quickLinkList.length, + (index) { + return KeyedSubtree( + key: ValueKey(model.quickLinkList[index].id), + child: _buildGridChild(context, model, index), + ); + }, + ), + onReorder: (oldIndex, newIndex) { + setState(() { + final QuickLink item = model.quickLinkList.removeAt(oldIndex); + model.quickLinkList.insert(newIndex, item); + }); + }, + ); + } + + Widget _buildGridChild( + BuildContext context, QuickLinksViewModel model, int index) { + return GestureDetector( + onLongPress: _editMode + ? null + : () { + _controller.repeat(reverse: true); + setState(() { + _editMode = true; + }); }, - child: SafeArea( - child: Align( - alignment: Alignment.topCenter, - child: Padding( - padding: const EdgeInsets.only(top: 8.0), - child: ReorderableGridView.count( - mainAxisSpacing: 2.0, - crossAxisSpacing: 2.0, - crossAxisCount: 3, - children: List.generate( - model.quickLinkList.length, - (index) { - return KeyedSubtree( - key: ValueKey(model.quickLinkList[index].id), - child: GestureDetector( - onLongPress: _editMode - ? null - : () { - _controller.repeat(reverse: true); - setState(() { - _editMode = true; - }); - }, - child: AnimatedBuilder( - animation: _animation, - builder: (BuildContext context, Widget child) { - return Transform.rotate( - angle: _editMode ? _animation.value : 0, - child: child, - ); - }, - child: Stack( - children: [ - WebLinkCard(model.quickLinkList[index]), - if (_editMode) - Positioned( - top: 0, - left: 0, - child: Container( - width: 32, - height: 32, - decoration: const BoxDecoration( - color: Colors.red, - shape: BoxShape.circle, - ), - child: IconButton( - padding: EdgeInsets.zero, - icon: const Icon(Icons.close, - color: Colors.white, size: 16), - onPressed: () { - setState(() { - model.quickLinkList - .removeAt(index); - }); - }, - ), - ), - ), - ], - ), - ), - ), - ); - }, - ), - onReorder: (oldIndex, newIndex) { - setState(() { - final QuickLink item = - model.quickLinkList.removeAt(oldIndex); - model.quickLinkList.insert(newIndex, item); - }); - }, - ), - ), + child: AnimatedBuilder( + animation: _animation, + builder: (BuildContext context, Widget child) { + return Transform.rotate( + angle: _editMode ? _animation.value : 0, + child: child, + ); + }, + child: Stack( + children: [ + WebLinkCard(model.quickLinkList[index]), + if (_editMode) + Positioned( + top: 0, + left: 0, + child: _buildDeleteButton(model, index), ), - ), - ), + ], ), - ); + ), + ); + } + + Container _buildDeleteButton(QuickLinksViewModel model, int index) { + return Container( + width: 32, + height: 32, + decoration: const BoxDecoration( + color: AppTheme.etsDarkGrey, + shape: BoxShape.circle, + ), + child: IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.close, color: Colors.white, size: 16), + onPressed: () { + setState(() { + model.quickLinkList.removeAt(index); + }); + }, + ), + ); + } @override void dispose() { From 6dbc16de31fe08d74fbe254549777ecbcd8e3db6 Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Thu, 13 Jul 2023 22:41:43 -0400 Subject: [PATCH 07/20] Keep quicklinks in cache --- lib/core/models/quick_link_data.dart | 23 +++++ .../viewmodels/quick_links_viewmodel.dart | 94 ++++++++++++++++++- lib/ui/views/quick_links_view.dart | 6 +- 3 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 lib/core/models/quick_link_data.dart diff --git a/lib/core/models/quick_link_data.dart b/lib/core/models/quick_link_data.dart new file mode 100644 index 000000000..274b89b29 --- /dev/null +++ b/lib/core/models/quick_link_data.dart @@ -0,0 +1,23 @@ +// FLUTTER / DART / THIRD-PARTIES +import 'package:flutter/material.dart'; + +class QuickLinkData { + final int id; + final int index; + + QuickLinkData({@required this.id, @required this.index}); + + factory QuickLinkData.fromJson(Map json) { + return QuickLinkData( + id: json['id'] as int, + index: json['index'] as int, + ); + } + + Map toJson() { + return { + 'id': id, + 'index': index, + }; + } +} diff --git a/lib/core/viewmodels/quick_links_viewmodel.dart b/lib/core/viewmodels/quick_links_viewmodel.dart index 7ceee69cd..99f6a94b2 100644 --- a/lib/core/viewmodels/quick_links_viewmodel.dart +++ b/lib/core/viewmodels/quick_links_viewmodel.dart @@ -1,5 +1,8 @@ // FLUTTER / DART / THIRD-PARTIES +import 'dart:convert'; +import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:notredame/core/managers/cache_manager.dart'; import 'package:stacked/stacked.dart'; // CONSTANTS @@ -7,10 +10,95 @@ import 'package:notredame/core/constants/quick_links.dart'; // MODELS import 'package:notredame/core/models/quick_link.dart'; +import 'package:notredame/core/models/quick_link_data.dart'; + +// OTHERS +import 'package:notredame/locator.dart'; + +class QuickLinksViewModel extends FutureViewModel> { + /// Localization class of the application. + final AppIntl _appIntl; + + @visibleForTesting + static const String quickLinksCacheKey = "quickLinksCache"; -class QuickLinksViewModel extends BaseViewModel { /// used to get all links for ETS page - final List quickLinkList; + List quickLinkList = List.empty(); + + List deletedQuickLinks = List.empty(); + + /// Cache manager to access and update the cache. + final CacheManager _cacheManager = locator(); + + QuickLinksViewModel(AppIntl intl) : _appIntl = intl; + + Future> getQuickLinks() async { + List quickLinkDataList; + try { + final cacheData = await _cacheManager.get(quickLinksCacheKey); + final responseCache = jsonDecode(cacheData) as List; + + quickLinkDataList = responseCache + .map((e) => QuickLinkData.fromJson(e as Map)) + .toList(); + } catch (e) { + // if cache is not initialized, return default list + return quickLinks(_appIntl); + } + + // otherwise, return quickLinks according to the cache + final defaultQuickLinks = quickLinks(_appIntl); + quickLinkDataList.sort((a, b) => a.index.compareTo(b.index)); + return quickLinkDataList + .map((data) => defaultQuickLinks + .firstWhere((quickLink) => quickLink.id == data.id)) + .toList(); + } + + Future> getDeletedQuickLinks() async { + // Get ids from current quick links + final currentQuickLinkIds = quickLinkList.map((e) => e.id).toList(); + + // Return those not in current quick links but in default list + return quickLinks(_appIntl) + .where((element) => !currentQuickLinkIds.contains(element.id)) + .toList(); + } + + Future deleteQuickLink(int index) async { + // Map current quick links to quick link data + final deletedQuickLink = quickLinkList.removeAt(index); + deletedQuickLinks.add(deletedQuickLink); + final quickLinkDataList = quickLinkList + .asMap() + .entries + .map((e) => QuickLinkData(id: e.value.id, index: e.key)) + .toList(); + await _cacheManager.update( + quickLinksCacheKey, jsonEncode(quickLinkDataList)); + notifyListeners(); + } + + // Function that updates the cache with the new order of quick links + Future reorderQuickLinks(int oldIndex, int newIndex) async { + final QuickLink item = quickLinkList.removeAt(oldIndex); + quickLinkList.insert(newIndex, item); + final quickLinkDataList = quickLinkList + .asMap() + .entries + .map((e) => QuickLinkData(id: e.value.id, index: e.key)) + .toList(); + await _cacheManager.update( + quickLinksCacheKey, jsonEncode(quickLinkDataList)); + notifyListeners(); + } - QuickLinksViewModel(AppIntl intl) : quickLinkList = quickLinks(intl); + @override + Future> futureToRun() async { + setBusyForObject(quickLinkList, true); + quickLinkList = await getQuickLinks(); + deletedQuickLinks = await getDeletedQuickLinks(); + setBusyForObject(quickLinkList, false); + return quickLinkList; + } } diff --git a/lib/ui/views/quick_links_view.dart b/lib/ui/views/quick_links_view.dart index 3a0fadf28..3d43f2949 100644 --- a/lib/ui/views/quick_links_view.dart +++ b/lib/ui/views/quick_links_view.dart @@ -1,6 +1,5 @@ // FLUTTER / DART / THIRD-PARTIES import 'package:flutter/material.dart'; -import 'package:notredame/core/models/quick_link.dart'; import 'package:notredame/ui/utils/app_theme.dart'; import 'package:reorderable_grid_view/reorderable_grid_view.dart'; import 'package:stacked/stacked.dart'; @@ -97,8 +96,7 @@ class _QuickLinksViewState extends State ), onReorder: (oldIndex, newIndex) { setState(() { - final QuickLink item = model.quickLinkList.removeAt(oldIndex); - model.quickLinkList.insert(newIndex, item); + model.reorderQuickLinks(oldIndex, newIndex); }); }, ); @@ -151,7 +149,7 @@ class _QuickLinksViewState extends State icon: const Icon(Icons.close, color: Colors.white, size: 16), onPressed: () { setState(() { - model.quickLinkList.removeAt(index); + model.deleteQuickLink(index); }); }, ), From 2795f23a3d7ad4ced1995b49736173cb5a47927a Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Sat, 15 Jul 2023 12:52:13 -0400 Subject: [PATCH 08/20] Restore quicklinks --- .../viewmodels/quick_links_viewmodel.dart | 14 ++++++ lib/ui/views/quick_links_view.dart | 49 +++++++++++++++++-- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/lib/core/viewmodels/quick_links_viewmodel.dart b/lib/core/viewmodels/quick_links_viewmodel.dart index 99f6a94b2..94a1a8413 100644 --- a/lib/core/viewmodels/quick_links_viewmodel.dart +++ b/lib/core/viewmodels/quick_links_viewmodel.dart @@ -79,6 +79,20 @@ class QuickLinksViewModel extends FutureViewModel> { notifyListeners(); } + Future restoreQuickLink(int index) async { + // Map current quick links to quick link data + final deletedQuickLink = deletedQuickLinks.removeAt(index); + quickLinkList.add(deletedQuickLink); + final quickLinkDataList = quickLinkList + .asMap() + .entries + .map((e) => QuickLinkData(id: e.value.id, index: e.key)) + .toList(); + await _cacheManager.update( + quickLinksCacheKey, jsonEncode(quickLinkDataList)); + notifyListeners(); + } + // Function that updates the cache with the new order of quick links Future reorderQuickLinks(int oldIndex, int newIndex) async { final QuickLink item = quickLinkList.removeAt(oldIndex); diff --git a/lib/ui/views/quick_links_view.dart b/lib/ui/views/quick_links_view.dart index 3d43f2949..5cabc4a88 100644 --- a/lib/ui/views/quick_links_view.dart +++ b/lib/ui/views/quick_links_view.dart @@ -46,18 +46,33 @@ class _QuickLinksViewState extends State viewModelBuilder: () => QuickLinksViewModel(AppIntl.of(context)), builder: (context, model, child) => BaseScaffold( isLoading: model.isBusy, - appBar: _buildAppBar(context), + appBar: _buildAppBar(context, model), body: _buildBody(context, model), ), ); - AppBar _buildAppBar(BuildContext context) { + AppBar _buildAppBar(BuildContext context, QuickLinksViewModel model) { return AppBar( title: Text(AppIntl.of(context).title_ets), automaticallyImplyLeading: false, + actions: [if (_editMode) _buildQuickLinkAction(context, model)], ); } + IconButton _buildQuickLinkAction( + BuildContext context, QuickLinksViewModel model) { + return IconButton( + icon: const Icon(Icons.restore), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return _buildRestoreDialog(context, model); + }, + ); + }); + } + Widget _buildBody(BuildContext context, QuickLinksViewModel model) { return GestureDetector( onTap: () { @@ -72,7 +87,7 @@ class _QuickLinksViewState extends State child: Align( alignment: Alignment.topCenter, child: Padding( - padding: const EdgeInsets.only(top: 8.0), + padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0), child: _buildReorderableGridView(model), ), ), @@ -156,6 +171,34 @@ class _QuickLinksViewState extends State ); } + Widget _buildRestoreDialog(BuildContext context, QuickLinksViewModel model) { + return AlertDialog( + title: const Text('Restore QuickLinks'), + content: SizedBox( + width: double.maxFinite, + child: model.deletedQuickLinks.isEmpty + ? Text("No quick links to restore!") + : ListView.builder( + shrinkWrap: true, + itemCount: model.deletedQuickLinks.length, + itemBuilder: (context, index) { + final deletedQuickLink = model.deletedQuickLinks[index]; + return ListTile( + title: Text(deletedQuickLink.name), + trailing: IconButton( + icon: const Icon(Icons.add), + onPressed: () { + model.restoreQuickLink(index); + Navigator.of(context).pop(); + }, + ), + ); + }, + ), + ), + ); + } + @override void dispose() { _controller.dispose(); From 64881829ce3afe37c39b280c410c53b20dd14b7c Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Sat, 15 Jul 2023 13:14:50 -0400 Subject: [PATCH 09/20] Restore quick links second option --- lib/ui/views/quick_links_view.dart | 76 +++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 13 deletions(-) diff --git a/lib/ui/views/quick_links_view.dart b/lib/ui/views/quick_links_view.dart index 5cabc4a88..11f5a55f2 100644 --- a/lib/ui/views/quick_links_view.dart +++ b/lib/ui/views/quick_links_view.dart @@ -12,6 +12,8 @@ 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'; +import '../../core/models/quick_link.dart'; + // OTHER class QuickLinksView extends StatefulWidget { @@ -84,28 +86,53 @@ class _QuickLinksViewState extends State } }, child: SafeArea( - child: Align( - alignment: Alignment.topCenter, - child: Padding( - padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0), - child: _buildReorderableGridView(model), - ), + child: Column( + children: [ + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0), + child: _buildReorderableGridView( + model, model.quickLinkList, _buildDeleteButton), + ), + ), + if (_editMode) ...[ + //Text("Restore QuickLinks!"), + const Divider( + thickness: 2, + indent: 10, + endIndent: 10, + ), + Expanded( + child: Padding( + padding: + const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0), + child: _buildReorderableGridView( + model, model.deletedQuickLinks, _buildAddButton), + ), + ), + ], + ], ), ), ); } - ReorderableGridView _buildReorderableGridView(QuickLinksViewModel model) { + ReorderableGridView _buildReorderableGridView( + QuickLinksViewModel model, + List quickLinks, + Widget Function(QuickLinksViewModel, int) buildButtonFunction) { return ReorderableGridView.count( mainAxisSpacing: 2.0, crossAxisSpacing: 2.0, crossAxisCount: 3, children: List.generate( - model.quickLinkList.length, + quickLinks.length, (index) { return KeyedSubtree( - key: ValueKey(model.quickLinkList[index].id), - child: _buildGridChild(context, model, index), + key: ValueKey(quickLinks[index].id), + child: + _buildGridChild(model, index, quickLinks, buildButtonFunction), ); }, ), @@ -118,7 +145,10 @@ class _QuickLinksViewState extends State } Widget _buildGridChild( - BuildContext context, QuickLinksViewModel model, int index) { + QuickLinksViewModel model, + int index, + List quickLinks, + Widget Function(QuickLinksViewModel, int) buildButtonFunction) { return GestureDetector( onLongPress: _editMode ? null @@ -138,12 +168,12 @@ class _QuickLinksViewState extends State }, child: Stack( children: [ - WebLinkCard(model.quickLinkList[index]), + WebLinkCard(quickLinks[index]), if (_editMode) Positioned( top: 0, left: 0, - child: _buildDeleteButton(model, index), + child: buildButtonFunction(model, index), ), ], ), @@ -171,6 +201,26 @@ class _QuickLinksViewState extends State ); } + Container _buildAddButton(QuickLinksViewModel model, int index) { + return Container( + width: 32, + height: 32, + decoration: const BoxDecoration( + color: AppTheme.etsDarkGrey, + shape: BoxShape.circle, + ), + child: IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.add, color: Colors.white, size: 20), + onPressed: () { + setState(() { + model.restoreQuickLink(index); + }); + }, + ), + ); + } + Widget _buildRestoreDialog(BuildContext context, QuickLinksViewModel model) { return AlertDialog( title: const Text('Restore QuickLinks'), From 890bcd705fa6a4d53251cba370a845e1232dca26 Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Sat, 15 Jul 2023 13:24:31 -0400 Subject: [PATCH 10/20] Divider visibility --- lib/ui/views/quick_links_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ui/views/quick_links_view.dart b/lib/ui/views/quick_links_view.dart index 11f5a55f2..033908a75 100644 --- a/lib/ui/views/quick_links_view.dart +++ b/lib/ui/views/quick_links_view.dart @@ -96,7 +96,7 @@ class _QuickLinksViewState extends State model, model.quickLinkList, _buildDeleteButton), ), ), - if (_editMode) ...[ + if (_editMode && model.deletedQuickLinks.isNotEmpty) ...[ //Text("Restore QuickLinks!"), const Divider( thickness: 2, From 5e114a423d1c65a40bbb37165adb832a3dfc03e1 Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Mon, 17 Jul 2023 18:42:28 -0400 Subject: [PATCH 11/20] green badge --- lib/ui/views/quick_links_view.dart | 46 ++---------------------------- 1 file changed, 2 insertions(+), 44 deletions(-) diff --git a/lib/ui/views/quick_links_view.dart b/lib/ui/views/quick_links_view.dart index 033908a75..2243ac472 100644 --- a/lib/ui/views/quick_links_view.dart +++ b/lib/ui/views/quick_links_view.dart @@ -57,24 +57,10 @@ class _QuickLinksViewState extends State return AppBar( title: Text(AppIntl.of(context).title_ets), automaticallyImplyLeading: false, - actions: [if (_editMode) _buildQuickLinkAction(context, model)], + actions: [], ); } - IconButton _buildQuickLinkAction( - BuildContext context, QuickLinksViewModel model) { - return IconButton( - icon: const Icon(Icons.restore), - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) { - return _buildRestoreDialog(context, model); - }, - ); - }); - } - Widget _buildBody(BuildContext context, QuickLinksViewModel model) { return GestureDetector( onTap: () { @@ -206,7 +192,7 @@ class _QuickLinksViewState extends State width: 32, height: 32, decoration: const BoxDecoration( - color: AppTheme.etsDarkGrey, + color: Colors.green, shape: BoxShape.circle, ), child: IconButton( @@ -221,34 +207,6 @@ class _QuickLinksViewState extends State ); } - Widget _buildRestoreDialog(BuildContext context, QuickLinksViewModel model) { - return AlertDialog( - title: const Text('Restore QuickLinks'), - content: SizedBox( - width: double.maxFinite, - child: model.deletedQuickLinks.isEmpty - ? Text("No quick links to restore!") - : ListView.builder( - shrinkWrap: true, - itemCount: model.deletedQuickLinks.length, - itemBuilder: (context, index) { - final deletedQuickLink = model.deletedQuickLinks[index]; - return ListTile( - title: Text(deletedQuickLink.name), - trailing: IconButton( - icon: const Icon(Icons.add), - onPressed: () { - model.restoreQuickLink(index); - Navigator.of(context).pop(); - }, - ), - ); - }, - ), - ), - ); - } - @override void dispose() { _controller.dispose(); From 823922e7d9438807871bf0d4eeb1b75c0940d0bd Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Mon, 17 Jul 2023 18:54:50 -0400 Subject: [PATCH 12/20] Added repository for cache logic --- lib/core/managers/quick_link_repository.dart | 37 ++++++++++++++ .../viewmodels/quick_links_viewmodel.dart | 48 +++++-------------- lib/locator.dart | 2 + 3 files changed, 52 insertions(+), 35 deletions(-) create mode 100644 lib/core/managers/quick_link_repository.dart diff --git a/lib/core/managers/quick_link_repository.dart b/lib/core/managers/quick_link_repository.dart new file mode 100644 index 000000000..c49a5c856 --- /dev/null +++ b/lib/core/managers/quick_link_repository.dart @@ -0,0 +1,37 @@ +import 'dart:convert'; +import 'package:notredame/core/managers/cache_manager.dart'; +import 'package:notredame/core/models/quick_link.dart'; +import 'package:notredame/core/models/quick_link_data.dart'; +import 'package:notredame/core/constants/quick_links.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:notredame/locator.dart'; + +class QuickLinkRepository { + final CacheManager _cacheManager = locator(); + + static const String quickLinksCacheKey = "quickLinksCache"; + + Future> getQuickLinkDataFromCache() async { + final cacheData = await _cacheManager.get(quickLinksCacheKey); + final responseCache = jsonDecode(cacheData) as List; + + return responseCache + .map((e) => QuickLinkData.fromJson(e as Map)) + .toList(); + } + + Future updateQuickLinkDataToCache(List quickLinkList) async { + final quickLinkDataList = quickLinkList + .asMap() + .entries + .map((e) => QuickLinkData(id: e.value.id, index: e.key)) + .toList(); + + await _cacheManager.update( + quickLinksCacheKey, jsonEncode(quickLinkDataList)); + } + + List getDefaultQuickLinks(AppIntl intl) { + return quickLinks(intl); + } +} diff --git a/lib/core/viewmodels/quick_links_viewmodel.dart b/lib/core/viewmodels/quick_links_viewmodel.dart index 94a1a8413..980710716 100644 --- a/lib/core/viewmodels/quick_links_viewmodel.dart +++ b/lib/core/viewmodels/quick_links_viewmodel.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:notredame/core/managers/cache_manager.dart'; +import 'package:notredame/core/managers/quick_link_repository.dart'; import 'package:stacked/stacked.dart'; // CONSTANTS @@ -19,35 +20,29 @@ class QuickLinksViewModel extends FutureViewModel> { /// Localization class of the application. final AppIntl _appIntl; - @visibleForTesting - static const String quickLinksCacheKey = "quickLinksCache"; - /// used to get all links for ETS page List quickLinkList = List.empty(); List deletedQuickLinks = List.empty(); - /// Cache manager to access and update the cache. - final CacheManager _cacheManager = locator(); + final QuickLinkRepository _quickLinkRepository = + locator(); QuickLinksViewModel(AppIntl intl) : _appIntl = intl; Future> getQuickLinks() async { List quickLinkDataList; try { - final cacheData = await _cacheManager.get(quickLinksCacheKey); - final responseCache = jsonDecode(cacheData) as List; - - quickLinkDataList = responseCache - .map((e) => QuickLinkData.fromJson(e as Map)) - .toList(); + quickLinkDataList = + await _quickLinkRepository.getQuickLinkDataFromCache(); } catch (e) { // if cache is not initialized, return default list - return quickLinks(_appIntl); + return _quickLinkRepository.getDefaultQuickLinks(_appIntl); } // otherwise, return quickLinks according to the cache - final defaultQuickLinks = quickLinks(_appIntl); + final defaultQuickLinks = + _quickLinkRepository.getDefaultQuickLinks(_appIntl); quickLinkDataList.sort((a, b) => a.index.compareTo(b.index)); return quickLinkDataList .map((data) => defaultQuickLinks @@ -60,7 +55,8 @@ class QuickLinksViewModel extends FutureViewModel> { final currentQuickLinkIds = quickLinkList.map((e) => e.id).toList(); // Return those not in current quick links but in default list - return quickLinks(_appIntl) + return _quickLinkRepository + .getDefaultQuickLinks(_appIntl) .where((element) => !currentQuickLinkIds.contains(element.id)) .toList(); } @@ -69,13 +65,7 @@ class QuickLinksViewModel extends FutureViewModel> { // Map current quick links to quick link data final deletedQuickLink = quickLinkList.removeAt(index); deletedQuickLinks.add(deletedQuickLink); - final quickLinkDataList = quickLinkList - .asMap() - .entries - .map((e) => QuickLinkData(id: e.value.id, index: e.key)) - .toList(); - await _cacheManager.update( - quickLinksCacheKey, jsonEncode(quickLinkDataList)); + await _quickLinkRepository.updateQuickLinkDataToCache(quickLinkList); notifyListeners(); } @@ -83,13 +73,7 @@ class QuickLinksViewModel extends FutureViewModel> { // Map current quick links to quick link data final deletedQuickLink = deletedQuickLinks.removeAt(index); quickLinkList.add(deletedQuickLink); - final quickLinkDataList = quickLinkList - .asMap() - .entries - .map((e) => QuickLinkData(id: e.value.id, index: e.key)) - .toList(); - await _cacheManager.update( - quickLinksCacheKey, jsonEncode(quickLinkDataList)); + await _quickLinkRepository.updateQuickLinkDataToCache(quickLinkList); notifyListeners(); } @@ -97,13 +81,7 @@ class QuickLinksViewModel extends FutureViewModel> { Future reorderQuickLinks(int oldIndex, int newIndex) async { final QuickLink item = quickLinkList.removeAt(oldIndex); quickLinkList.insert(newIndex, item); - final quickLinkDataList = quickLinkList - .asMap() - .entries - .map((e) => QuickLinkData(id: e.value.id, index: e.key)) - .toList(); - await _cacheManager.update( - quickLinksCacheKey, jsonEncode(quickLinkDataList)); + await _quickLinkRepository.updateQuickLinkDataToCache(quickLinkList); notifyListeners(); } diff --git a/lib/locator.dart b/lib/locator.dart index 012a95d7e..b2ec076f6 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -2,6 +2,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get_it/get_it.dart'; import 'package:logger/logger.dart'; +import 'package:notredame/core/managers/quick_link_repository.dart'; // SERVICES import 'package:notredame/core/services/navigation_service.dart'; @@ -49,6 +50,7 @@ void setupLocator() { locator.registerLazySingleton(() => CourseRepository()); locator.registerLazySingleton(() => CacheManager()); locator.registerLazySingleton(() => SettingsManager()); + locator.registerLazySingleton(() => QuickLinkRepository()); // Other locator.registerLazySingleton(() => SignetsAPIClient()); From e8c03395c5f25aac389d4349b03d853f93c91844 Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Mon, 17 Jul 2023 18:55:54 -0400 Subject: [PATCH 13/20] Remove outdated comments --- lib/core/viewmodels/quick_links_viewmodel.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/core/viewmodels/quick_links_viewmodel.dart b/lib/core/viewmodels/quick_links_viewmodel.dart index 980710716..d61f03577 100644 --- a/lib/core/viewmodels/quick_links_viewmodel.dart +++ b/lib/core/viewmodels/quick_links_viewmodel.dart @@ -62,7 +62,6 @@ class QuickLinksViewModel extends FutureViewModel> { } Future deleteQuickLink(int index) async { - // Map current quick links to quick link data final deletedQuickLink = quickLinkList.removeAt(index); deletedQuickLinks.add(deletedQuickLink); await _quickLinkRepository.updateQuickLinkDataToCache(quickLinkList); @@ -70,14 +69,12 @@ class QuickLinksViewModel extends FutureViewModel> { } Future restoreQuickLink(int index) async { - // Map current quick links to quick link data final deletedQuickLink = deletedQuickLinks.removeAt(index); quickLinkList.add(deletedQuickLink); await _quickLinkRepository.updateQuickLinkDataToCache(quickLinkList); notifyListeners(); } - // Function that updates the cache with the new order of quick links Future reorderQuickLinks(int oldIndex, int newIndex) async { final QuickLink item = quickLinkList.removeAt(oldIndex); quickLinkList.insert(newIndex, item); From 84b4a434bbf156f1834d690cc11d43b904f8f01d Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Tue, 19 Sep 2023 17:25:08 -0400 Subject: [PATCH 14/20] Added/fixed tests related to quicklinks --- .../viewmodels/quick_links_viewmodel.dart | 6 +- test/helpers.dart | 12 ++ test/managers/quick_link_repository_test.dart | 100 ++++++++++++++ .../managers/quick_links_repository_mock.dart | 49 +++++++ test/ui/views/quick_links_view_test.dart | 17 ++- .../quick_links_viewmodel_test.dart | 125 ++++++++++++++++++ 6 files changed, 304 insertions(+), 5 deletions(-) create mode 100644 test/managers/quick_link_repository_test.dart create mode 100644 test/mock/managers/quick_links_repository_mock.dart create mode 100644 test/viewmodels/quick_links_viewmodel_test.dart diff --git a/lib/core/viewmodels/quick_links_viewmodel.dart b/lib/core/viewmodels/quick_links_viewmodel.dart index d61f03577..c27c6199a 100644 --- a/lib/core/viewmodels/quick_links_viewmodel.dart +++ b/lib/core/viewmodels/quick_links_viewmodel.dart @@ -21,9 +21,9 @@ class QuickLinksViewModel extends FutureViewModel> { final AppIntl _appIntl; /// used to get all links for ETS page - List quickLinkList = List.empty(); + List quickLinkList = []; - List deletedQuickLinks = List.empty(); + List deletedQuickLinks = []; final QuickLinkRepository _quickLinkRepository = locator(); @@ -31,7 +31,7 @@ class QuickLinksViewModel extends FutureViewModel> { QuickLinksViewModel(AppIntl intl) : _appIntl = intl; Future> getQuickLinks() async { - List quickLinkDataList; + List quickLinkDataList = []; try { quickLinkDataList = await _quickLinkRepository.getQuickLinkDataFromCache(); diff --git a/test/helpers.dart b/test/helpers.dart index 52e32a81d..8af8ed0c6 100644 --- a/test/helpers.dart +++ b/test/helpers.dart @@ -6,6 +6,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:logger/logger.dart'; +import 'package:notredame/core/managers/quick_link_repository.dart'; // OTHER import 'package:notredame/locator.dart'; @@ -33,6 +34,7 @@ import 'package:notredame/core/services/remote_config_service.dart'; import 'package:ets_api_clients/testing.dart'; import 'mock/managers/cache_manager_mock.dart'; import 'mock/managers/course_repository_mock.dart'; +import 'mock/managers/quick_links_repository_mock.dart'; import 'mock/managers/settings_manager_mock.dart'; import 'mock/managers/user_repository_mock.dart'; import 'mock/services/analytics_service_mock.dart'; @@ -328,6 +330,16 @@ RemoteConfigService setupRemoteConfigServiceMock() { return service; } +/// Load a mock of the [QuickLinkRepository] +QuickLinkRepository setupQuickLinkRepositoryMock() { + unregister(); + final repository = QuickLinkRepositoryMock(); + + locator.registerSingleton(repository); + + return repository; +} + bool getCalendarViewEnabled() { final RemoteConfigService remoteConfigService = locator(); diff --git a/test/managers/quick_link_repository_test.dart b/test/managers/quick_link_repository_test.dart new file mode 100644 index 000000000..b893d5a8f --- /dev/null +++ b/test/managers/quick_link_repository_test.dart @@ -0,0 +1,100 @@ +// FLUTTER / DART / THIRD-PARTIES +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:intl/intl.dart'; +import 'package:mockito/mockito.dart'; +import 'package:notredame/core/managers/quick_link_repository.dart'; + +// SERVICES / MANAGER +import 'package:notredame/core/managers/cache_manager.dart'; +import 'package:notredame/core/models/quick_link.dart'; +import 'package:notredame/core/models/quick_link_data.dart'; + +// MODELS + +// UTILS +import '../helpers.dart'; + +// MOCKS +import 'package:ets_api_clients/testing.dart'; +import '../mock/managers/cache_manager_mock.dart'; + +void main() { + CacheManager cacheManager; + QuickLinkRepository quickLinkRepository; + + group("QuickLinkRepository - ", () { + setUp(() { + // Setup needed services and managers + cacheManager = setupCacheManagerMock(); + + quickLinkRepository = QuickLinkRepository(); + }); + + tearDown(() { + clearInteractions(cacheManager); + unregister(); + }); + + group("getQuickLinkDataFromCache - ", () { + test("QuickLinkData is loaded from cache.", () async { + // Stub the cache to return some QuickLinkData + final quickLinkData = QuickLinkData(id: 1, index: 0); + CacheManagerMock.stubGet( + cacheManager as CacheManagerMock, + QuickLinkRepository.quickLinksCacheKey, + jsonEncode([quickLinkData])); + + final List results = + await quickLinkRepository.getQuickLinkDataFromCache(); + + expect(results, isInstanceOf>()); + expect(results[0].id, quickLinkData.id); + expect(results[0].index, quickLinkData.index); + + verify(cacheManager.get(QuickLinkRepository.quickLinksCacheKey)) + .called(1); + }); + + test( + "Trying to recover QuickLinkData from cache but an exception is raised.", + () async { + // Stub the cache to throw an exception + CacheManagerMock.stubGetException(cacheManager as CacheManagerMock, + QuickLinkRepository.quickLinksCacheKey); + + expect(quickLinkRepository.getQuickLinkDataFromCache(), + throwsA(isInstanceOf())); + }); + }); + + group("updateQuickLinkDataToCache - ", () { + test("Updating QuickLinkData to cache.", () async { + // Prepare some QuickLinkData to be cached + final quickLink = + QuickLink(id: 1, image: const Text(""), name: 'name', link: 'url'); + final quickLinkData = QuickLinkData(id: quickLink.id, index: 0); + + await quickLinkRepository.updateQuickLinkDataToCache([quickLink]); + + verify(cacheManager.update(QuickLinkRepository.quickLinksCacheKey, + jsonEncode([quickLinkData]))).called(1); + }); + + test( + "Trying to update QuickLinkData to cache but an exception is raised.", + () async { + // Stub the cache to throw an exception + CacheManagerMock.stubUpdateException(cacheManager as CacheManagerMock, + QuickLinkRepository.quickLinksCacheKey); + + final quickLink = + QuickLink(id: 1, image: const Text(""), name: 'name', link: 'url'); + + expect(quickLinkRepository.updateQuickLinkDataToCache([quickLink]), + throwsA(isInstanceOf())); + }); + }); + }); +} diff --git a/test/mock/managers/quick_links_repository_mock.dart b/test/mock/managers/quick_links_repository_mock.dart new file mode 100644 index 000000000..af529884d --- /dev/null +++ b/test/mock/managers/quick_links_repository_mock.dart @@ -0,0 +1,49 @@ +// FLUTTER / DART / THIRD-PARTIES +import 'package:mockito/mockito.dart'; + +// MANAGER +import 'package:notredame/core/managers/quick_link_repository.dart'; + +// MODELS +import 'package:notredame/core/models/quick_link.dart'; +import 'package:notredame/core/models/quick_link_data.dart'; + +// UTILS +import 'package:ets_api_clients/exceptions.dart'; + +class QuickLinkRepositoryMock extends Mock implements QuickLinkRepository { + /// Stub the function [getQuickLinkDataFromCache] of [mock] when called will return [toReturn]. + static void stubGetQuickLinkDataFromCache(QuickLinkRepositoryMock mock, + {List toReturn = const []}) { + when(mock.getQuickLinkDataFromCache()).thenAnswer((_) async => toReturn); + } + + /// Stub the function [getQuickLinkDataFromCache] of [mock] when called will throw [toThrow]. + static void stubGetQuickLinkDataFromCacheException( + QuickLinkRepositoryMock mock, + {Exception toThrow = const ApiException(prefix: 'ApiException')}) { + when(mock.getQuickLinkDataFromCache()).thenAnswer((_) => + Future.delayed(const Duration(milliseconds: 50)) + .then((value) => throw toThrow)); + } + + /// Stub the function [getDefaultQuickLinks] of [mock] when called will return [toReturn]. + static void stubGetDefaultQuickLinks(QuickLinkRepositoryMock mock, + {List toReturn = const []}) { + when(mock.getDefaultQuickLinks(any)).thenAnswer((_) => toReturn); + } + + /// Stub the function [updateQuickLinkDataToCache] of [mock] when called will complete without errors. + static void stubUpdateQuickLinkDataToCache(QuickLinkRepositoryMock mock) { + when(mock.updateQuickLinkDataToCache(any)).thenAnswer((_) async => {}); + } + + /// Stub the function [updateQuickLinkDataToCache] of [mock] when called will throw [toThrow]. + static void stubUpdateQuickLinkDataToCacheException( + QuickLinkRepositoryMock mock, + {Exception toThrow = const ApiException(prefix: 'ApiException')}) { + when(mock.updateQuickLinkDataToCache(any)).thenAnswer((_) => + Future.delayed(const Duration(milliseconds: 50)) + .then((value) => throw toThrow)); + } +} diff --git a/test/ui/views/quick_links_view_test.dart b/test/ui/views/quick_links_view_test.dart index a308896e8..092032e16 100644 --- a/test/ui/views/quick_links_view_test.dart +++ b/test/ui/views/quick_links_view_test.dart @@ -1,5 +1,6 @@ // FLUTTER / DART / THIRD-PARTIES import 'dart:io'; +import 'package:ets_api_clients/exceptions.dart'; import 'package:feature_discovery/feature_discovery.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -7,6 +8,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; // CONSTANTS import 'package:notredame/core/constants/quick_links.dart'; +import 'package:notredame/core/managers/quick_link_repository.dart'; // SERVICES import 'package:notredame/core/services/networking_service.dart'; @@ -18,6 +20,7 @@ import 'package:notredame/ui/views/quick_links_view.dart'; import 'package:notredame/ui/widgets/web_link_card.dart'; import '../../helpers.dart'; +import '../../mock/managers/quick_links_repository_mock.dart'; import '../../mock/services/analytics_service_mock.dart'; import '../../mock/services/internal_info_service_mock.dart'; import '../../mock/services/navigation_service_mock.dart'; @@ -25,6 +28,8 @@ import '../../mock/services/navigation_service_mock.dart'; void main() { AppIntl intl; + QuickLinkRepository quickLinkRepository; + group('QuickLinksView - ', () { setUp(() async { intl = await setupAppIntl(); @@ -32,6 +37,13 @@ void main() { setupAnalyticsServiceMock(); setupInternalInfoServiceMock(); setupNetworkingServiceMock(); + quickLinkRepository = setupQuickLinkRepositoryMock(); + QuickLinkRepositoryMock.stubGetDefaultQuickLinks( + quickLinkRepository as QuickLinkRepositoryMock, + toReturn: quickLinks(intl)); + + QuickLinkRepositoryMock.stubGetQuickLinkDataFromCacheException( + quickLinkRepository as QuickLinkRepositoryMock); }); tearDown(() { @@ -39,6 +51,7 @@ void main() { unregister(); unregister(); unregister(); + unregister(); }); group('UI - ', () { @@ -48,8 +61,8 @@ void main() { useScaffold: false)); await tester.pumpAndSettle(); - expect( - find.byType(WebLinkCard), findsNWidgets(quickLinks(intl).length)); + expect(find.byType(WebLinkCard, skipOffstage: false), + findsNWidgets(quickLinks(intl).length)); }); group("golden - ", () { diff --git a/test/viewmodels/quick_links_viewmodel_test.dart b/test/viewmodels/quick_links_viewmodel_test.dart new file mode 100644 index 000000000..2b7b8d22d --- /dev/null +++ b/test/viewmodels/quick_links_viewmodel_test.dart @@ -0,0 +1,125 @@ +// FLUTTER / DART / THIRD-PARTIES +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:notredame/core/constants/quick_links.dart'; + +// MODELS +import 'package:notredame/core/models/quick_link.dart'; +import 'package:notredame/core/models/quick_link_data.dart'; + +// VIEWMODEL +import 'package:notredame/core/viewmodels/quick_links_viewmodel.dart'; + +// MANAGER +import 'package:notredame/core/managers/quick_link_repository.dart'; + +// OTHERS +import '../helpers.dart'; +import '../mock/managers/quick_links_repository_mock.dart'; + +void main() { + AppIntl intl; + QuickLinksViewModel viewModel; + QuickLinkRepository quickLinkRepository; + + // Sample data for tests + QuickLinkData quickLinkDataSample; + QuickLink quickLinkSample; + + group("QuickLinksViewModel - ", () { + setUp(() async { + // Setting up mocks + quickLinkRepository = setupQuickLinkRepositoryMock(); + intl = await setupAppIntl(); + + viewModel = QuickLinksViewModel(intl); + quickLinkDataSample = QuickLinkData(id: 1, index: 0); + quickLinkSample = quickLinks(intl).first; + }); + + tearDown(() { + unregister(); + }); + + group('getQuickLinks -', () { + test('Should get quick links from cache', () async { + QuickLinkRepositoryMock.stubGetQuickLinkDataFromCache( + quickLinkRepository as QuickLinkRepositoryMock, + toReturn: [quickLinkDataSample]); + + QuickLinkRepositoryMock.stubGetDefaultQuickLinks( + quickLinkRepository as QuickLinkRepositoryMock, + toReturn: [quickLinkSample]); + + final result = await viewModel.getQuickLinks(); + + expect(result, [quickLinkSample]); + }); + + test('Should return default quick links if cache is not initialized', + () async { + QuickLinkRepositoryMock.stubGetQuickLinkDataFromCacheException( + quickLinkRepository as QuickLinkRepositoryMock); + + QuickLinkRepositoryMock.stubGetDefaultQuickLinks( + quickLinkRepository as QuickLinkRepositoryMock, + toReturn: [quickLinkSample]); + + final result = await viewModel.getQuickLinks(); + + expect(result, [quickLinkSample]); + }); + }); + + group('deleteQuickLink -', () { + test('Should delete a quick link and update cache', () async { + viewModel.quickLinkList = [quickLinkSample]; + + await viewModel.deleteQuickLink(0); + + expect(viewModel.quickLinkList, isEmpty); + expect(viewModel.deletedQuickLinks, [quickLinkSample]); + }); + }); + + group('restoreQuickLink -', () { + test('Should restore a deleted quick link and update cache', () async { + viewModel.deletedQuickLinks = [quickLinkSample]; + + await viewModel.restoreQuickLink(0); + + expect(viewModel.deletedQuickLinks, isEmpty); + expect(viewModel.quickLinkList, [quickLinkSample]); + }); + }); + + group('reorderQuickLinks -', () { + test('Should reorder quick links and update cache', () async { + final anotherQuickLink = quickLinks(intl).last; + viewModel.quickLinkList = [quickLinkSample, anotherQuickLink]; + + await viewModel.reorderQuickLinks(0, 1); + + expect(viewModel.quickLinkList, [anotherQuickLink, quickLinkSample]); + }); + }); + + group('futureToRun -', () { + test('Should fetch and set quick links', () async { + QuickLinkRepositoryMock.stubGetQuickLinkDataFromCache( + quickLinkRepository as QuickLinkRepositoryMock, + toReturn: [quickLinkDataSample]); + + QuickLinkRepositoryMock.stubGetDefaultQuickLinks( + quickLinkRepository as QuickLinkRepositoryMock, + toReturn: [quickLinkSample]); + + final result = await viewModel.futureToRun(); + + expect(result, [quickLinkSample]); + }); + }); + }); +} From d8ee48751e4231bcb92b50b02c5e4cc0d5e5bd68 Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Tue, 19 Sep 2023 17:56:18 -0400 Subject: [PATCH 15/20] Removed unused imports --- lib/core/viewmodels/quick_links_viewmodel.dart | 6 ------ test/managers/quick_link_repository_test.dart | 2 -- test/ui/views/quick_links_view_test.dart | 1 - test/viewmodels/quick_links_viewmodel_test.dart | 2 -- 4 files changed, 11 deletions(-) diff --git a/lib/core/viewmodels/quick_links_viewmodel.dart b/lib/core/viewmodels/quick_links_viewmodel.dart index c27c6199a..f7f5dbde2 100644 --- a/lib/core/viewmodels/quick_links_viewmodel.dart +++ b/lib/core/viewmodels/quick_links_viewmodel.dart @@ -1,14 +1,8 @@ // FLUTTER / DART / THIRD-PARTIES -import 'dart:convert'; -import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:notredame/core/managers/cache_manager.dart'; import 'package:notredame/core/managers/quick_link_repository.dart'; import 'package:stacked/stacked.dart'; -// CONSTANTS -import 'package:notredame/core/constants/quick_links.dart'; - // MODELS import 'package:notredame/core/models/quick_link.dart'; import 'package:notredame/core/models/quick_link_data.dart'; diff --git a/test/managers/quick_link_repository_test.dart b/test/managers/quick_link_repository_test.dart index b893d5a8f..fc16277da 100644 --- a/test/managers/quick_link_repository_test.dart +++ b/test/managers/quick_link_repository_test.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:intl/intl.dart'; import 'package:mockito/mockito.dart'; import 'package:notredame/core/managers/quick_link_repository.dart'; @@ -17,7 +16,6 @@ import 'package:notredame/core/models/quick_link_data.dart'; import '../helpers.dart'; // MOCKS -import 'package:ets_api_clients/testing.dart'; import '../mock/managers/cache_manager_mock.dart'; void main() { diff --git a/test/ui/views/quick_links_view_test.dart b/test/ui/views/quick_links_view_test.dart index 092032e16..64813f640 100644 --- a/test/ui/views/quick_links_view_test.dart +++ b/test/ui/views/quick_links_view_test.dart @@ -1,6 +1,5 @@ // FLUTTER / DART / THIRD-PARTIES import 'dart:io'; -import 'package:ets_api_clients/exceptions.dart'; import 'package:feature_discovery/feature_discovery.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/test/viewmodels/quick_links_viewmodel_test.dart b/test/viewmodels/quick_links_viewmodel_test.dart index 2b7b8d22d..0f075cd17 100644 --- a/test/viewmodels/quick_links_viewmodel_test.dart +++ b/test/viewmodels/quick_links_viewmodel_test.dart @@ -1,8 +1,6 @@ // FLUTTER / DART / THIRD-PARTIES -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:mockito/mockito.dart'; import 'package:notredame/core/constants/quick_links.dart'; // MODELS From ace9a6258919e9f181ca82ff8be77dc329fefc3f Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Tue, 19 Sep 2023 22:44:10 +0000 Subject: [PATCH 16/20] [BOT] Applying version. --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 294c18776..acc62915d 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.25.2+1 +version: 4.26.0+1 environment: sdk: ">=2.10.0 <3.0.0" From 73f5b49267a5a40866efdf4a1c30d715a56fd144 Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Tue, 19 Sep 2023 18:48:13 -0400 Subject: [PATCH 17/20] Fix warning --- lib/ui/views/quick_links_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ui/views/quick_links_view.dart b/lib/ui/views/quick_links_view.dart index 509243f7e..c85532545 100644 --- a/lib/ui/views/quick_links_view.dart +++ b/lib/ui/views/quick_links_view.dart @@ -56,7 +56,7 @@ class _QuickLinksViewState extends State return AppBar( title: Text(AppIntl.of(context).title_ets), automaticallyImplyLeading: false, - actions: [], + actions: const [], ); } From 7e664f4506167462b6ea97de14ee677e40a01ba4 Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Tue, 19 Sep 2023 22:52:54 +0000 Subject: [PATCH 18/20] [BOT] Update golden files --- .../views/goldenFiles/quicksLinksView_1.png | Bin 8748 -> 9500 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/ui/views/goldenFiles/quicksLinksView_1.png b/test/ui/views/goldenFiles/quicksLinksView_1.png index 3172fac2edaf215a1c8a5c20da75b1377e255f59..0d550ce2fb2799102b24df8a26812fe0a47ea840 100644 GIT binary patch literal 9500 zcmeHtcT`i`_AX)pML<9)0tS&H8bDC06bl`U0@9n5h;*cvK#tNyKw9YJNEM_Rnju1z zA|(_70jZJRK{_M>ev5PNefQk^?tQ;8?%$6w_Sj^Xwf358&iT#nTPxxbOqKr3#WPe? zRP^dYKYtwO1jP>dC$YRr` zD{q|aRCI_CE1N9}rSn-%W~MK`ux4p~9ujkD3$eM``*4RH;x}WaOT`lrDafE1I~qdu z^K9@*stfhhzn!r>b|<8a>i2twRF5u0sotGqrFu+DM|JIgxy?^E`q@R&0G`bi{d}wL zIWCgw$4k_I-6AVRF49qv`1V_7%rWVc!vE;L?IjtCiLy1N#zy@2t^a*E_m#A8bjNsh zx-tJ4ZbIq$Fpl| zN4M8yoZN7uGe3VmssCAtL%`CvSBdD+vbm+Div2LRg$m1t&}7om@^WRZDJ)~+SJ|pDsV3FVQ3a(dE6#)$W+o;M zzQoy$lrRSFEQRcJ>F4zb(tN)4%ik+2CG6f0EPJJynVHR`+_YUfU~+2%=k}5kk@Av~ zP8|z+diufBk#jR{y1MbBite3@b8}_ijRFD!>Sx@{%(ySd)^}&CT2uQ3?yBHb)%d)} z>qC;MU><%_iclSM+2z&M>Q7@;P9}wVXT~OD6rFto)~8yrhpIcB)fE*v#Or2H`io5j z70_McqB=3%TZ7-f^LcuDYU)c00+aOg^jM|jW9<{fp)-&<-mE%4!?_vA$d@OsD{=3L ztJTnI=g}H|;UrzBOb=w^`q-flLXNYDqz!I#y? zW-mk6Q!E?N7N$^M-j%vR%OCTn-YoWICi}{%Uq4(s3!_md%OnS^t+u)zP9-{?r#Tsl zQhq_03G(T6GBc$NvDgVS`gdbo-!y$(c(d%}2IoWmHTV5Xs@Ld@Du2R$!)aQt(taqD;)i8CC}^i_FU75t zFGZTA*uY%X;|uXQt2sdUvr7V*x%CbLVT5CffyCEAtfE&ej3ylW2Zd4%MD8}&p zu$>=0gtbY!=sMkx>^=7nxC>wn*Y5GDwr{_W>2#ce7=tlm#xU%KJVRvz`+={(GA*VXxidDN1a#L~8?1aS8JAa3qdaKwIL3arbAffB3uiy6w z=_=B%+^wI1>+Y(5G#`T3%j&EpI$z8k62BTsc%x zLeieI@Q_y?u$B{Q;Ptx_#t4Cx8;0cP=QpqW1^(1Q&vSBc#Mad*5I!kcKHvRi$-xJ&Q^QbHb2Zs?xT(-5PCA^}d!qAvi zLPA1Jc+BVZqpLP~#&d7OF2&XQ?|g0F*+Zm9cCqXaJXC6&Tx(e@#XG0$jIIV6)cpz( zZG@T_7zl#Q9I3C6A7OwNNzx1r4TUzF0TAVM`6Tw#sCrmf0qvqqnBxn?E9R}LD&5vr#Wzo?v!CbsIsZ^3i2#7`fiZe?bTp4Jjj1hyC7X&Q z4Gs<#ke2efSvQcEx3;z@I?P(&^wi%USEE2fL(}o~tBSGnX@Qt#Ye^}owCwEcLK4@- zi{hM|oDaY?|3UZXg#`s-;Ah9%w{I&HcE(+Io%GPkyjQMVF)j3q8D7@kx+=(W_7o@^ zYzmY*^XEM@Lp1jdd}_yDliBGTq;bEOQ9WWm+aR5`ZI=J#$)jlQ+X8`j3+fkCNtBs_ z(o!~bm*Axs!D6P+v)FK)dWSWG05NEHh=&(XI0S-32?XATVwVM*=p zE~Iz*YPz_T8Ug$!`x=xybgmvA!6Wetl$y71zih9|TJ~%nkBW{?HK}s^aAXQe#~;71 z&}iU-;xozZftMo|+z$`-^M;qJYG1=rKbgDKM`OQyxqI*?R|Y$}l3=jZ!w?Aygkj{3 zeAwCnF9SV2(|HaUK>X)1+NA>y{)6e#ef|A~#_WafFN0B)U}r88T1snbWU;GtWSh|% z&sKe5X)sPdKR+v#OAHJQ#+^(q*lAWx(c0nV%=Zsf8qLMz8M@?tw(9$sE;C}EQs za~EQrS(OXX*=VMYyb_<6CjbWgL zSt5Yn8Z8uk_|V<6dh<{oL};dr`_~oT*$Ok9SJU@FiuL2H1LI5be&3I-u?WH%zZ(@k zX9H8YoFEmaOuRVK-F??M|65-l{HO>?bYX(9vzL7u=;^87=Mi!ot?6@%QN6*Qj%#C* zkFKBH=$?4+Wp<-!Wa%9TG5*g2WRT-({OX8vH9wV#ipmUZGK=PeGEqC?sN`^SRfSA_ z&xd7^v;^RWN*4KfdT-Ezwkr|kFBUxfx@RL$vz5y+-)6n_XVs3PEYfvEwC0Y% z^*Js~d$E1F#>MLH_w|qB<2s9w_>#z}^C&s!S7#^F1+%A#xWn*K<+6{vjq%J1ULz}3 zHjYiz+Zts?UE{P&vapiOhC!&4Z{FTJbB4*7 z_0G^4VLo13x;u6hf<kOZr7Ngp9m}NOLcS zin(PbYy`1mCZ=DchAo{~EJ?gT-nMbV4=Nc_sW;vZY2j1KBg$UqhmfDOEo&XF51{t2 z9%mZtXvN?^ck{Y+% z)K{!~~CoE<$9z+z)k@VY^vE&%FzFws0S3QFW+@TuG)4o&H zkje-{ak<3oeaQ5j`#_b63A=!YyqS2fnBb9NN+0S2TBDz7R2tdimoin{K~BaTv@lbr zq*w8sVY>ZL`RJ~7wR8AwZ%F{VKyIBb`6FS$M8C4w5trDL9i@K@R zt6zb#aata0ysOs83;pc`?b+*p=X*5U)OIbK-}&OV+?H>J|AqD(F~PVkhR#SSV#4Nr zPtWeggL0*_*4L&s&uDz}zgL6Czc%qB%^2VM{3CIswNRDp&t;U5T!n5ro=YAN&5ZMsMD5{>4?&#-Xg4w;*xS4VYehD z1%|7f$}8Bj8oUEp{Cs>2C7p)&tgWpzjbVbmzrN52TaLS7t?o%(x^n(ZN&#!l-b@<& z$&=6GU9%ezU9<5uH8p2nr6FH_`F(f)Ai2>CYbt*07N2vqzw_Ak@-Uq2wYVFtW@eun z6p~e1`j8TcY%gf~`SbVbpMmqaNj8&TqfqdLg@v4w5*VOq)qHSIYCKUC0Q}7N>hf~k zD#yV%53`0Fof?20>plR~7htluaYSM6!i4LgQ#W2bX|cY0^G~nTaf-IiR3p-n9mW?egr7LyNAbj zM<=JsBOJR3V7j}zdnxQB{@ng!3yabNet<1K_L^e8a+d(}ex!%4t*xbKYl`KP?xU_k z9|y3m0X+cJLE}0L{`R0cTHGp>eKisBoqtQ_de1!T=;fjde_(s#X5|G8=y%Pp7Ikn_ z7!2$w7c147FiUpZn>CBKfOOCrFNh7k*aVE*JQ=}}mL+!M2G5H-zui^d_}0a zWTibZHPsq_)-SDj*(-8jQ7@_>{Q9UuB!IOaft`tq@!%5Ly1Kf0*nG?s4bX(dAd?xPOAfBW_X zAU0eIdWwd|>F`UsY!f+BT2|KnsAN4dba8Pp!10&7<*kcbPj~EFqNK*ydo_{G+}<~a zZnXXSRkpdgX-MJ*U+@Wd4eHWd*d;%;?Uhjnqk-9tR&MW+4>oYmmjIcM@Oz&D*cA1i z&wqOX`#bz{!H#+z>LGjt|f>gqufNT3SpOg-K_0 z%*-<64t6Is%~b>=yxZ+cF6N$%j2C=pY;G&7miGoslu;^vTHCS+HEZwpgk;@buOwb;74;8ltrLi%q;5Zo>rvh!Izq9LA1~_g!ThHy=jW?GJ-v$d496#(jd7GjW?yu#`F3kMGDI!JDQemD#M7 z{Mu=RlNBD7;StRCxi znu(b8*BIqJ%s{oWnI`~4ovvo{n+^vLV~RZ8w*%jB>8>@2LWiVJMYXO%s+_pgIzvvf ziXfJJ+5&@tRWzFNo{AL~&WL2g@V>^c6dpMlInQ{{=1#?%3lV+tp~xxAf*+f{*osM^ zly&rL+-U2{eVrPw4%6!=_kzk=ZqDrb^c)<|#^|6F*UJ)0%&q{#Pp?bus7G{NA=FhE zz0%ijHWLIhp;h-Qt391FaXO|#y>A5u8Y~Ddeyw?17T)Aec2vw&@?v836&TFb25l8? zduXgJ=(){vW{7X#+;u8(x}Y7+-O5Sv=RbdMr*sZUX;a4abn4b%LS{G>FrnDpACusx zh|xhygU%jJLUfg*4xVv{bff+hBPh=7_tmAbJ!+T4*ReQnI!|*53JA zd&kA=DjytmFmKzhHt1BX2Ku&&wn#{EpSAIut9b2V7oWvgl8Pe`0*X8d+=MZ1-YQeXT|7rt(%en&LZLcft}Y`6$+t?m}7055}yM|#4 z$K|IPTYk*0#7*?Qw*g@YqOwa9*j^l5-Yg*UEj`YVW%60wJOxuF@IKT+%W``Mt}VRJ zeFm=tlcwV}><8w-xP8q#w{bSHm5m{HKS>b-aYrCl1TZ$jw8pG^LEt;oHs3;xu{ugm z6~7UTr3gv`cZK%b^Oco!7)D-s+sC$SezOx3HGiF=$a&TDUIz^b@5r-*(dh!93-^qS zf|a0xp<=v@Bf`O?UOS_O5!_eXy4zv%DQH`PBU96u9XOTcWBsr4^x@$n%Bc(wXr~BK z-fB^0Ei6qC^9$9UOfr4Rd!yLFY5mgFTA3x%cPYOITxt}q*L&3!**9Dz__~x!%+Diz z62Sn4`4vioEz$y_pSCfyC$A1=X5_(6*enE>AYYD;{V|%WYrd0JP>?H|xFAVAT}814 zEI^E%75O7TfxBOmlxXJm<%peADT^raXv@WCy&O>i*5f0a$Nc&*spCZ^VKs9>KoD^f zqt^*_n@z!sCpr6@wd|4>8xTabIv(h{QtMX4;me0y$+gYdn>QV7)FajA($i#z09dn5fnSMIPOQc%8>)!pe|$nfp3#E6Xje-3nNcdcMPLBxr0SKSfi| z8I zY1O%^i7d~(o+l??lI72J7-(5n9kP}uiEUi{k^=lhcVa_e z33m>Vx7d8^5RfA#O@?xIQ}uKvHjeV0RZ%iGrzJ={5yxqW?Zj^q=7}o0FhIt1$>6hV?Fi0Oc+Q%sLjAwfSTgno{vDFDw4~_h6kEoe@q9C z1YC@Nvn@8~;Use`HjL_uB1q4LK3muHo`kmt#{aknDdF5pJr?RcD|!k({Xy*sAE#?# z#=fcW^|}qHf#N!sW$f%LgHpd$?SsghcM6xPOlIod(e6d{zM*~sx}T2cF1IlF=xx#F zqBL-|umlD26GyJm9ok50fG$x0NN(I7qH95Y%~V<0fKH;P)tP`XiR__+8o()!D=knuiKip-X%*oLN(lE!4w zuQ0J#bghuCqQ7!pU83LZ`NCb%E{zYfq?0Z*kueOgKP1+~^xO2~cHJ%Tq#d~{;IQ@< zbG-XEb@<;cLTV~>-rMLH`D}F>f`HxYLf&5SS2id`r4ZBOdVCtjlylb6F_Gulngc`! zT`5+pf^gYi8#&~5rB+f`>-SiQ@yvte;mf*YFSRv6gwEP#HFHr)N>Nx-Q<}@G+xXqk zG#gSBx~WdvSb&)U?v#9alwO+#jX8+j;m0FS$ovaRKqn`yX4+G-M@mCGV3{Rzlvmn27vlf4*MS!#N(>>!rE z%P>45N!O>vg&OK7*9^^LUca_6&!V2#Aq$t*nwN%$fA8-%o}mf)!hbzJ9uOiU^ML^X zjw?K>K$+OwLkJD1bFg;PcyGeaIoQ}VGE)QXYr+esJf-nW=@DACp`iga5bO8)53VR` zl#&kB4iEqw^y0;f(9+wovS7sv+L3i3`ojmY!78UT_!z}P$s=H8s8W;q#pk$$j)F^w zUv~*#6G*WnQ?Nc4mnf5T zGU)CadB101fUTFbu(E;zRH(gntlT!HqO#HmWoK{C4D?Z;9*Y>BF|)8Bj)=)xI5@mt z0!(PWCe$)L@kpso_4D_Cz+~C>WMOIPbwO8mw{|b!??*fb=*Z7wy8obyGmWj2Yso<0 z6}rg-=tJ?DNbu?k5syAD*HHl0IxPc$NSO+ZS(VhSu6wkIdEEcmvzARCxuXj}W`0QB z%?P#z)Ew;v5Ch7w9{RAN{-s5D;E_J;@9(eMKRY+~Zt%yCXa2tK0d82+=O)kA^0v3V zh6#(opa;FatYiqQ5E(jg`UExVbh$y)C-YJu6DPx}A0KIk3m7~e8LKe|1p+z{Hot>e zA6R_wo@faq0I*mIsQVpCpd$CWTdu;D=4YFFosW-1Wo<*Mt59GF_zA&(yU6%23q$|q z7J>g|2f_cp&BNb&>oEC0@4e7amy{Hjzv5I zZOEZPC<0#qrQ>gl2K>6n(Y867+h`CbNtshsjGJtZ0nY%DC>{tk(1d5>DypjRff6&i zvCdQpha8Xsj@~o9eIQML3`m9B&tos^pj0%S{Z#nm;+J)7h-Bj7K?Uk?e-(*W<8RH} zsa*}QlfV&uXSq|-(@h(hg-v~Zv8JAVdJEHF1#6lSx!b^tx_aAfe3#&7Xu8uppP@mS zDqc#jhgOLq_Xu{vhb>oPpgQW9*$wm|!Z@E<6i;!}hgq`r(o2oJ=`U#9+n6nPJ5&Ax zv@rP}Wb6O49QV)rU0O*|Huzm+9>NCcOiW2WEjYrC5)mA^lUM?*FiYuhPH735jQN{9 z`ikjH2bxJhKdH^up+)ku z2zEoy=nmJsLIAmDu_WH&U_B|a3Q-ck)U9(Zyi7Wf^6?X8ov<*?M*fZPwc0o1wl~ac zb*$KY3p)(oUK$jt|`q6$G-ji;78^0AFOk<8;F8jtgkO!vBxoi9Zpp0$}pvp Idrw~e4{ZoE!~g&Q literal 8748 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 Bk|+QG From f029839de7c11388c84a5a45db0320a3a06dd722 Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Tue, 19 Sep 2023 19:14:20 -0400 Subject: [PATCH 19/20] Make security not deletable --- ios/Podfile.lock | 2 +- lib/ui/views/quick_links_view.dart | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 55ad16582..80c95058f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -260,7 +260,7 @@ CHECKOUT OPTIONS: :git: https://github.com/yahoojapan/SwiftyXMLParser.git SPEC CHECKSUMS: - connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e + connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed Firebase: f92fc551ead69c94168d36c2b26188263860acd9 firebase_analytics: 9f3a4cb560a59976b2c48707abae2d4cb94bcb3a diff --git a/lib/ui/views/quick_links_view.dart b/lib/ui/views/quick_links_view.dart index c85532545..256d1101b 100644 --- a/lib/ui/views/quick_links_view.dart +++ b/lib/ui/views/quick_links_view.dart @@ -82,7 +82,6 @@ class _QuickLinksViewState extends State ), ), if (_editMode && model.deletedQuickLinks.isNotEmpty) ...[ - //Text("Restore QuickLinks!"), const Divider( thickness: 2, indent: 10, @@ -154,7 +153,9 @@ class _QuickLinksViewState extends State child: Stack( children: [ WebLinkCard(quickLinks[index]), - if (_editMode) + if (_editMode && + quickLinks[index].id != + 1) // Don't show delete button for Security QuickLink Positioned( top: 0, left: 0, From 6c64ea9ed3f7b67fe2d275a43d28fe46e0228bb6 Mon Sep 17 00:00:00 2001 From: JonathanDuvalV Date: Wed, 20 Sep 2023 14:28:50 -0400 Subject: [PATCH 20/20] Fixed imports order and annotations --- lib/core/managers/quick_link_repository.dart | 11 ++++++++++- lib/core/viewmodels/quick_links_viewmodel.dart | 4 +++- lib/locator.dart | 2 +- lib/ui/views/quick_links_view.dart | 6 ++++-- test/helpers.dart | 2 +- test/managers/quick_link_repository_test.dart | 6 +++--- test/ui/views/quick_links_view_test.dart | 7 ++++++- 7 files changed, 28 insertions(+), 10 deletions(-) diff --git a/lib/core/managers/quick_link_repository.dart b/lib/core/managers/quick_link_repository.dart index c49a5c856..9250445b2 100644 --- a/lib/core/managers/quick_link_repository.dart +++ b/lib/core/managers/quick_link_repository.dart @@ -1,9 +1,18 @@ +// FLUTTER / DART / THIRD-PARTIES import 'dart:convert'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +// SERVICES import 'package:notredame/core/managers/cache_manager.dart'; + +// MODELS import 'package:notredame/core/models/quick_link.dart'; import 'package:notredame/core/models/quick_link_data.dart'; + +// CONSTANTS import 'package:notredame/core/constants/quick_links.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +// OTHERS import 'package:notredame/locator.dart'; class QuickLinkRepository { diff --git a/lib/core/viewmodels/quick_links_viewmodel.dart b/lib/core/viewmodels/quick_links_viewmodel.dart index f7f5dbde2..ca59736b8 100644 --- a/lib/core/viewmodels/quick_links_viewmodel.dart +++ b/lib/core/viewmodels/quick_links_viewmodel.dart @@ -1,8 +1,10 @@ // FLUTTER / DART / THIRD-PARTIES import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:notredame/core/managers/quick_link_repository.dart'; import 'package:stacked/stacked.dart'; +// MANAGERS +import 'package:notredame/core/managers/quick_link_repository.dart'; + // MODELS import 'package:notredame/core/models/quick_link.dart'; import 'package:notredame/core/models/quick_link_data.dart'; diff --git a/lib/locator.dart b/lib/locator.dart index b2ec076f6..abc371eab 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -2,7 +2,6 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get_it/get_it.dart'; import 'package:logger/logger.dart'; -import 'package:notredame/core/managers/quick_link_repository.dart'; // SERVICES import 'package:notredame/core/services/navigation_service.dart'; @@ -23,6 +22,7 @@ import 'package:notredame/core/managers/user_repository.dart'; import 'package:notredame/core/managers/course_repository.dart'; import 'package:notredame/core/managers/cache_manager.dart'; import 'package:notredame/core/managers/settings_manager.dart'; +import 'package:notredame/core/managers/quick_link_repository.dart'; // OTHER import 'package:ets_api_clients/clients.dart'; diff --git a/lib/ui/views/quick_links_view.dart b/lib/ui/views/quick_links_view.dart index 256d1101b..3c3fb8d8d 100644 --- a/lib/ui/views/quick_links_view.dart +++ b/lib/ui/views/quick_links_view.dart @@ -1,7 +1,5 @@ // FLUTTER / DART / THIRD-PARTIES import 'package:flutter/material.dart'; -import 'package:notredame/ui/utils/app_theme.dart'; -import 'package:reorderable_grid_view/reorderable_grid_view.dart'; import 'package:stacked/stacked.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -11,9 +9,13 @@ import 'package:notredame/core/models/quick_link.dart'; // VIEW-MODEL import 'package:notredame/core/viewmodels/quick_links_viewmodel.dart'; +// UTILS +import 'package:notredame/ui/utils/app_theme.dart'; + // WIDGETS import 'package:notredame/ui/widgets/base_scaffold.dart'; import 'package:notredame/ui/widgets/web_link_card.dart'; +import 'package:reorderable_grid_view/reorderable_grid_view.dart'; class QuickLinksView extends StatefulWidget { @override diff --git a/test/helpers.dart b/test/helpers.dart index 8af8ed0c6..2b8e67a91 100644 --- a/test/helpers.dart +++ b/test/helpers.dart @@ -6,7 +6,6 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:logger/logger.dart'; -import 'package:notredame/core/managers/quick_link_repository.dart'; // OTHER import 'package:notredame/locator.dart'; @@ -29,6 +28,7 @@ import 'package:notredame/core/services/app_widget_service.dart'; import 'package:notredame/core/services/in_app_review_service.dart'; import 'package:notredame/core/services/launch_url_service.dart'; import 'package:notredame/core/services/remote_config_service.dart'; +import 'package:notredame/core/managers/quick_link_repository.dart'; // MOCKS import 'package:ets_api_clients/testing.dart'; diff --git a/test/managers/quick_link_repository_test.dart b/test/managers/quick_link_repository_test.dart index fc16277da..f65b26e8e 100644 --- a/test/managers/quick_link_repository_test.dart +++ b/test/managers/quick_link_repository_test.dart @@ -3,14 +3,14 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; -import 'package:notredame/core/managers/quick_link_repository.dart'; // SERVICES / MANAGER import 'package:notredame/core/managers/cache_manager.dart'; -import 'package:notredame/core/models/quick_link.dart'; -import 'package:notredame/core/models/quick_link_data.dart'; +import 'package:notredame/core/managers/quick_link_repository.dart'; // MODELS +import 'package:notredame/core/models/quick_link.dart'; +import 'package:notredame/core/models/quick_link_data.dart'; // UTILS import '../helpers.dart'; diff --git a/test/ui/views/quick_links_view_test.dart b/test/ui/views/quick_links_view_test.dart index 6dd7e4afe..4e0c7e0c3 100644 --- a/test/ui/views/quick_links_view_test.dart +++ b/test/ui/views/quick_links_view_test.dart @@ -7,19 +7,24 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; // CONSTANTS import 'package:notredame/core/constants/quick_links.dart'; -import 'package:notredame/core/managers/quick_link_repository.dart'; // SERVICES import 'package:notredame/core/services/networking_service.dart'; import 'package:notredame/core/services/launch_url_service.dart'; +// MANAGERS +import 'package:notredame/core/managers/quick_link_repository.dart'; + // VIEW import 'package:notredame/ui/views/quick_links_view.dart'; // WIDGETS import 'package:notredame/ui/widgets/web_link_card.dart'; +// UTILS import '../../helpers.dart'; + +// MOCKS import '../../mock/managers/quick_links_repository_mock.dart'; import '../../mock/services/analytics_service_mock.dart'; import '../../mock/services/internal_info_service_mock.dart';