From 90f28a58202af44b948a24a0ebb5dfefd5cdbf20 Mon Sep 17 00:00:00 2001 From: BenjaminBelanger Date: Sat, 21 Sep 2024 15:10:20 -0400 Subject: [PATCH 1/6] Add navigation history observer --- .../navigation_history_observer.dart | 32 +++++++++++++++++++ lib/main.dart | 2 ++ 2 files changed, 34 insertions(+) create mode 100644 lib/features/app/navigation/navigation_history_observer.dart diff --git a/lib/features/app/navigation/navigation_history_observer.dart b/lib/features/app/navigation/navigation_history_observer.dart new file mode 100644 index 000000000..bf79cee77 --- /dev/null +++ b/lib/features/app/navigation/navigation_history_observer.dart @@ -0,0 +1,32 @@ +import 'package:built_collection/built_collection.dart'; +import 'package:flutter/widgets.dart'; + +class NavigationHistoryObserver extends NavigatorObserver { + final List?> _history = ?>[]; + + /// Gets a clone of the navigation history as an immutable list. + BuiltList> get history => + BuiltList>.from(_history); + + /// Implements a singleton pattern for NavigationHistoryObserver. + static final NavigationHistoryObserver _singleton = NavigationHistoryObserver._internal(); + factory NavigationHistoryObserver() { + return _singleton; + } + NavigationHistoryObserver._internal(); + + @override + void didPop(Route route, Route? previousRoute) { + _history.removeLast(); + } + + @override + void didPush(Route route, Route? previousRoute) { + _history.add(route); + } + + @override + void didRemove(Route route, Route? previousRoute) { + _history.remove(route); + } +} diff --git a/lib/main.dart b/lib/main.dart index 9487293d2..6e226e06d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -23,6 +23,7 @@ import 'package:notredame/features/app/error/outage/outage_view.dart'; import 'package:notredame/features/app/integration/firebase_options.dart'; import 'package:notredame/features/app/navigation/navigation_service.dart'; import 'package:notredame/features/app/navigation/router.dart'; +import 'package:notredame/features/app/navigation/navigation_history_observer.dart'; import 'package:notredame/features/app/startup/startup_view.dart'; import 'package:notredame/features/ets/events/api-client/hello_api_client.dart'; import 'package:notredame/features/more/feedback/models/custom_feedback_localization.dart'; @@ -108,6 +109,7 @@ class ETSMobile extends StatelessWidget { navigatorKey: locator().navigatorKey, navigatorObservers: [ locator().getAnalyticsObserver(), + NavigationHistoryObserver(), ], home: outage ? OutageView() : StartUpView(), onGenerateRoute: generateRoute, From 30667dc9282735f775d70d2a526b576f408a4e05 Mon Sep 17 00:00:00 2001 From: BenjaminBelanger Date: Sat, 21 Sep 2024 15:14:29 -0400 Subject: [PATCH 2/6] Fix Android back gesture to navigate through route history --- .../app/navigation/navigation_service.dart | 26 +++++++++++++++++++ lib/features/app/widgets/bottom_bar.dart | 10 +++---- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/lib/features/app/navigation/navigation_service.dart b/lib/features/app/navigation/navigation_service.dart index f3b2e75f2..fbeb92cbe 100644 --- a/lib/features/app/navigation/navigation_service.dart +++ b/lib/features/app/navigation/navigation_service.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; // Project imports: import 'package:notredame/features/app/analytics/analytics_service.dart'; import 'package:notredame/features/app/analytics/remote_config_service.dart'; +import 'package:notredame/features/app/navigation/navigation_history_observer.dart'; import 'package:notredame/features/app/navigation/router_paths.dart'; import 'package:notredame/utils/locator.dart'; @@ -24,6 +25,9 @@ class NavigationService { /// Will be used to report event and error. final AnalyticsService _analyticsService = locator(); + /// Will be used to remove duplicate routes + final NavigationHistoryObserver historyObserver = NavigationHistoryObserver(); + GlobalKey get navigatorKey => _navigatorKey; /// Pop the last route of the navigator if possible @@ -53,6 +57,28 @@ class NavigationService { return currentState.pushNamed(routeName, arguments: arguments); } + /// Push a named route [routeName] onto the navigator and remove existing routes with the same [routeName] + Future pushNamedAndRemoveDuplicates(String routeName, {dynamic arguments}) { + final currentState = _navigatorKey.currentState; + + if (currentState == null) { + _analyticsService.logError(tag, "Navigator state is null"); + return Future.error("Navigator state is null"); + } + + if (remoteConfigService.outage) { + return currentState.pushNamedAndRemoveUntil( + RouterPaths.serviceOutage, (route) => false); + } + + final route = historyObserver.history.where((r) => r.settings.name == routeName).firstOrNull; + + if (route != null) { + currentState.removeRoute(route); + } + return currentState.pushNamed(routeName, arguments: arguments); + } + /// Replace the current route of the navigator by pushing the route named /// [routeName] and then delete the stack of previous routes Future pushNamedAndRemoveUntil(String routeName, diff --git a/lib/features/app/widgets/bottom_bar.dart b/lib/features/app/widgets/bottom_bar.dart index c634e50dc..a209643ab 100644 --- a/lib/features/app/widgets/bottom_bar.dart +++ b/lib/features/app/widgets/bottom_bar.dart @@ -73,19 +73,19 @@ class _BottomBarState extends State { switch (index) { case BottomBar.dashboardView: - _navigationService.pushNamedAndRemoveUntil(RouterPaths.dashboard); + _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.dashboard); _analyticsService.logEvent("BottomBar", "DashboardView clicked"); case BottomBar.scheduleView: - _navigationService.pushNamedAndRemoveUntil(RouterPaths.schedule); + _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.schedule); _analyticsService.logEvent("BottomBar", "ScheduleView clicked"); case BottomBar.studentView: - _navigationService.pushNamedAndRemoveUntil(RouterPaths.student); + _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.student); _analyticsService.logEvent("BottomBar", "StudentView clicked"); case BottomBar.etsView: - _navigationService.pushNamedAndRemoveUntil(RouterPaths.ets); + _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.ets); _analyticsService.logEvent("BottomBar", "EtsView clicked"); case BottomBar.moreView: - _navigationService.pushNamedAndRemoveUntil(RouterPaths.more); + _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.more); _analyticsService.logEvent("BottomBar", "MoreView clicked"); } _currentView = index; From d39f0d9ddcebf200368b59d667a08574de909f93 Mon Sep 17 00:00:00 2001 From: BenjaminBelanger Date: Sat, 21 Sep 2024 15:17:17 -0400 Subject: [PATCH 3/6] Implement route history navigation for landscape orientation --- lib/features/app/widgets/navigation_rail.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/features/app/widgets/navigation_rail.dart b/lib/features/app/widgets/navigation_rail.dart index 6a025ae5e..d660554ba 100644 --- a/lib/features/app/widgets/navigation_rail.dart +++ b/lib/features/app/widgets/navigation_rail.dart @@ -72,19 +72,19 @@ class _NavRailState extends State { switch (index) { case NavRail.dashboardView: - _navigationService.pushNamedAndRemoveUntil(RouterPaths.dashboard); + _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.dashboard); _analyticsService.logEvent("BottomBar", "DashboardView clicked"); case NavRail.scheduleView: - _navigationService.pushNamedAndRemoveUntil(RouterPaths.schedule); + _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.schedule); _analyticsService.logEvent("BottomBar", "ScheduleView clicked"); case NavRail.studentView: - _navigationService.pushNamedAndRemoveUntil(RouterPaths.student); + _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.student); _analyticsService.logEvent("BottomBar", "StudentView clicked"); case NavRail.etsView: - _navigationService.pushNamedAndRemoveUntil(RouterPaths.ets); + _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.ets); _analyticsService.logEvent("BottomBar", "EtsView clicked"); case NavRail.moreView: - _navigationService.pushNamedAndRemoveUntil(RouterPaths.more); + _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.more); _analyticsService.logEvent("BottomBar", "MoreView clicked"); } _currentView = index; From 6a4ca9ddd9e9a28224035ba645b015b863235fba Mon Sep 17 00:00:00 2001 From: BenjaminBelanger Date: Sat, 21 Sep 2024 15:21:12 -0400 Subject: [PATCH 4/6] Update bottom bar tests to work with Android back gesture navigation bugfix --- test/features/app/widgets/bottom_bar_test.dart | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/test/features/app/widgets/bottom_bar_test.dart b/test/features/app/widgets/bottom_bar_test.dart index 8b467f2c0..baa34f1a5 100644 --- a/test/features/app/widgets/bottom_bar_test.dart +++ b/test/features/app/widgets/bottom_bar_test.dart @@ -58,7 +58,7 @@ void main() { await tester.tap(find.byIcon(Icons.school_outlined)); await tester.tap(find.byIcon(Icons.school_outlined)); - verify(navigationServiceMock.pushNamedAndRemoveUntil(RouterPaths.student)) + verify(navigationServiceMock.pushNamedAndRemoveDuplicates(RouterPaths.student)) .called(1); }); @@ -71,8 +71,7 @@ void main() { await tester.tap(find.byIcon(Icons.schedule_outlined)); await tester.tap(find.byIcon(Icons.dashboard_outlined)); - verify(navigationServiceMock - .pushNamedAndRemoveUntil(RouterPaths.dashboard)); + verify(navigationServiceMock.pushNamedAndRemoveDuplicates(RouterPaths.dashboard)); }); testWidgets('schedule', (WidgetTester tester) async { @@ -82,8 +81,7 @@ void main() { await tester.tap(find.byIcon(Icons.schedule_outlined)); - verify(navigationServiceMock - .pushNamedAndRemoveUntil(RouterPaths.schedule)); + verify(navigationServiceMock.pushNamedAndRemoveDuplicates(RouterPaths.schedule)); }); testWidgets('student', (WidgetTester tester) async { @@ -93,8 +91,7 @@ void main() { await tester.tap(find.byIcon(Icons.school_outlined)); - verify( - navigationServiceMock.pushNamedAndRemoveUntil(RouterPaths.student)); + verify(navigationServiceMock.pushNamedAndRemoveDuplicates(RouterPaths.student)); }); testWidgets('ets', (WidgetTester tester) async { @@ -104,7 +101,7 @@ void main() { await tester.tap(find.byIcon(Icons.account_balance_outlined)); - verify(navigationServiceMock.pushNamedAndRemoveUntil(RouterPaths.ets)); + verify(navigationServiceMock.pushNamedAndRemoveDuplicates(RouterPaths.ets)); }); testWidgets('more', (WidgetTester tester) async { @@ -114,7 +111,7 @@ void main() { await tester.tap(find.byIcon(Icons.menu_outlined)); - verify(navigationServiceMock.pushNamedAndRemoveUntil(RouterPaths.more)); + verify(navigationServiceMock.pushNamedAndRemoveDuplicates(RouterPaths.more)); }); }); }); From 398da73f012076b97fb13dff823c85baea2bf4f8 Mon Sep 17 00:00:00 2001 From: BenjaminBelanger Date: Sun, 22 Sep 2024 14:01:06 -0400 Subject: [PATCH 5/6] Fix typo and code formatting in navigation_service --- .../app/navigation/navigation_service.dart | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/features/app/navigation/navigation_service.dart b/lib/features/app/navigation/navigation_service.dart index fbeb92cbe..8db288af6 100644 --- a/lib/features/app/navigation/navigation_service.dart +++ b/lib/features/app/navigation/navigation_service.dart @@ -25,8 +25,8 @@ class NavigationService { /// Will be used to report event and error. final AnalyticsService _analyticsService = locator(); - /// Will be used to remove duplicate routes - final NavigationHistoryObserver historyObserver = NavigationHistoryObserver(); + /// Will be used to remove duplicate routes. + final NavigationHistoryObserver _navigationHistoryObserver = NavigationHistoryObserver(); GlobalKey get navigatorKey => _navigatorKey; @@ -41,7 +41,7 @@ class NavigationService { return false; } - /// Push a named route ([routeName] onto the navigator. + /// Push a named route [routeName] onto the navigator. Future pushNamed(String routeName, {dynamic arguments}) { final currentState = _navigatorKey.currentState; @@ -57,7 +57,8 @@ class NavigationService { return currentState.pushNamed(routeName, arguments: arguments); } - /// Push a named route [routeName] onto the navigator and remove existing routes with the same [routeName] + /// Push a named route [routeName] onto the navigator + /// and remove existing routes with the same [routeName] Future pushNamedAndRemoveDuplicates(String routeName, {dynamic arguments}) { final currentState = _navigatorKey.currentState; @@ -71,8 +72,9 @@ class NavigationService { RouterPaths.serviceOutage, (route) => false); } - final route = historyObserver.history.where((r) => r.settings.name == routeName).firstOrNull; - + final route = _navigationHistoryObserver.history + .where((r) => r.settings.name == routeName).firstOrNull; + if (route != null) { currentState.removeRoute(route); } From c1c403d1828f5068774ae1650474cda1a09fdb2e Mon Sep 17 00:00:00 2001 From: BenjaminBelanger Date: Sun, 22 Sep 2024 20:10:22 +0000 Subject: [PATCH 6/6] [BOT] Applying version. --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 61db8c21c..6c8545bd7 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.52.1+1 +version: 4.52.2+1 environment: sdk: '>=3.3.0 <4.0.0'