Jonny Borges
Committed by GitHub

Merge pull request #1621 from Bdaya-Dev/master

Adding route guards and improving navigation
  1 +import 'package:get/get.dart';
  2 +
  3 +import '../../services/auth_service.dart';
  4 +import '../routes/app_pages.dart';
  5 +
  6 +class EnsureAuthMiddleware extends GetMiddleware {
  7 + @override
  8 + GetNavConfig? redirectDelegate(GetNavConfig route) {
  9 + if (!AuthService.to.isLoggedInValue) {
  10 + final newRoute = Routes.LOGIN_THEN(route.location!);
  11 + return GetNavConfig.fromRoute(newRoute);
  12 + }
  13 + return super.redirectDelegate(route);
  14 + }
  15 +}
  16 +
  17 +class EnsureNotAuthedMiddleware extends GetMiddleware {
  18 + @override
  19 + GetNavConfig? redirectDelegate(GetNavConfig route) {
  20 + if (AuthService.to.isLoggedInValue) {
  21 + //NEVER navigate to auth screen, when user is already authed
  22 + return null;
  23 +
  24 + //OR redirect user to another screen
  25 + //return GetNavConfig.fromRoute(Routes.PROFILE);
  26 + }
  27 + return super.redirectDelegate(route);
  28 + }
  29 +}
  1 +import 'package:get/get.dart';
  2 +
  3 +import '../controllers/login_controller.dart';
  4 +
  5 +class LoginBinding extends Bindings {
  6 + @override
  7 + void dependencies() {
  8 + Get.lazyPut<LoginController>(
  9 + () => LoginController(),
  10 + );
  11 + }
  12 +}
  1 +import 'package:get/get.dart';
  2 +
  3 +class LoginController extends GetxController {
  4 + //TODO: Implement LoginController
  5 +
  6 + final count = 0.obs;
  7 + @override
  8 + void onInit() {
  9 + super.onInit();
  10 + }
  11 +
  12 + @override
  13 + void onReady() {
  14 + super.onReady();
  15 + }
  16 +
  17 + @override
  18 + void onClose() {}
  19 + void increment() => count.value++;
  20 +}
  1 +import 'package:example_nav2/app/routes/app_pages.dart';
  2 +import 'package:example_nav2/services/auth_service.dart';
  3 +import 'package:flutter/material.dart';
  4 +
  5 +import 'package:get/get.dart';
  6 +
  7 +import '../controllers/login_controller.dart';
  8 +
  9 +class LoginView extends GetView<LoginController> {
  10 + @override
  11 + Widget build(BuildContext context) {
  12 + return Scaffold(
  13 + body: Center(
  14 + child: Column(
  15 + mainAxisSize: MainAxisSize.min,
  16 + children: [
  17 + Obx(
  18 + () {
  19 + final isLoggedIn = AuthService.to.isLoggedInValue;
  20 + return Text(
  21 + 'You are currently:'
  22 + ' ${isLoggedIn ? "Logged In" : "Not Logged In"}'
  23 + "\nIt's impossible to enter this "
  24 + "route when you are logged in!",
  25 + );
  26 + },
  27 + ),
  28 + MaterialButton(
  29 + child: Text(
  30 + 'Do LOGIN !!',
  31 + style: TextStyle(color: Colors.blue, fontSize: 20),
  32 + ),
  33 + onPressed: () {
  34 + AuthService.to.login();
  35 + final thenTo = Get.getDelegate()!
  36 + .currentConfiguration!
  37 + .currentPage!
  38 + .parameter?['then'];
  39 + Get.getDelegate()!.toNamed(thenTo ?? Routes.HOME);
  40 + },
  41 + ),
  42 + ],
  43 + ),
  44 + ),
  45 + );
  46 + }
  47 +}
  1 +import 'package:example_nav2/models/demo_product.dart';
1 import 'package:get/get.dart'; 2 import 'package:get/get.dart';
2 3
3 -import '../../../models/demo_product.dart';  
4 -  
5 class ProductsController extends GetxController { 4 class ProductsController extends GetxController {
6 final products = <DemoProduct>[].obs; 5 final products = <DemoProduct>[].obs;
7 6
@@ -8,6 +8,7 @@ class ProfileView extends GetView<ProfileController> { @@ -8,6 +8,7 @@ class ProfileView extends GetView<ProfileController> {
8 @override 8 @override
9 Widget build(BuildContext context) { 9 Widget build(BuildContext context) {
10 return Scaffold( 10 return Scaffold(
  11 + backgroundColor: Colors.amber,
11 body: Center( 12 body: Center(
12 child: Text( 13 child: Text(
13 'ProfileView is working', 14 'ProfileView is working',
  1 +import 'package:example_nav2/services/auth_service.dart';
1 import 'package:flutter/material.dart'; 2 import 'package:flutter/material.dart';
2 import 'package:get/get.dart'; 3 import 'package:get/get.dart';
3 4
@@ -35,6 +36,37 @@ class DrawerWidget extends StatelessWidget { @@ -35,6 +36,37 @@ class DrawerWidget extends StatelessWidget {
35 Navigator.of(context).pop(); 36 Navigator.of(context).pop();
36 }, 37 },
37 ), 38 ),
  39 + if (AuthService.to.isLoggedInValue)
  40 + ListTile(
  41 + title: Text(
  42 + 'Logout',
  43 + style: TextStyle(
  44 + color: Colors.red,
  45 + ),
  46 + ),
  47 + onTap: () {
  48 + AuthService.to.logout();
  49 + Get.getDelegate()!.toNamed(Routes.LOGIN);
  50 + //to close the drawer
  51 +
  52 + Navigator.of(context).pop();
  53 + },
  54 + ),
  55 + if (!AuthService.to.isLoggedInValue)
  56 + ListTile(
  57 + title: Text(
  58 + 'Login',
  59 + style: TextStyle(
  60 + color: Colors.blue,
  61 + ),
  62 + ),
  63 + onTap: () {
  64 + Get.getDelegate()!.toNamed(Routes.LOGIN);
  65 + //to close the drawer
  66 +
  67 + Navigator.of(context).pop();
  68 + },
  69 + ),
38 ], 70 ],
39 ), 71 ),
40 ); 72 );
  1 +import 'package:example_nav2/app/middleware/auth_middleware.dart';
1 import 'package:get/get.dart'; 2 import 'package:get/get.dart';
2 import 'package:get/get_navigation/src/nav2/router_outlet.dart'; 3 import 'package:get/get_navigation/src/nav2/router_outlet.dart';
3 4
  5 +import 'package:example_nav2/app/modules/login/bindings/login_binding.dart';
  6 +import 'package:example_nav2/app/modules/login/views/login_view.dart';
  7 +
4 import '../modules/home/bindings/home_binding.dart'; 8 import '../modules/home/bindings/home_binding.dart';
5 import '../modules/home/views/home_view.dart'; 9 import '../modules/home/views/home_view.dart';
6 import '../modules/product_details/bindings/product_details_binding.dart'; 10 import '../modules/product_details/bindings/product_details_binding.dart';
@@ -25,14 +29,21 @@ class AppPages { @@ -25,14 +29,21 @@ class AppPages {
25 GetPage( 29 GetPage(
26 name: '/', 30 name: '/',
27 page: () => RootView(), 31 page: () => RootView(),
28 - middlewares: [  
29 - RouterOutletContainerMiddleWare('/'),  
30 - ],  
31 binding: RootBinding(), 32 binding: RootBinding(),
  33 + participatesInRootNavigator: true,
32 children: [ 34 children: [
33 GetPage( 35 GetPage(
  36 + middlewares: [
  37 + //only enter this route when not authed
  38 + EnsureNotAuthedMiddleware(),
  39 + ],
  40 + name: _Paths.LOGIN,
  41 + page: () => LoginView(),
  42 + binding: LoginBinding(),
  43 + ),
  44 + GetPage(
  45 + participatesInRootNavigator: true,
34 name: _Paths.HOME, 46 name: _Paths.HOME,
35 - preventDuplicates: true,  
36 page: () => HomeView(), 47 page: () => HomeView(),
37 bindings: [ 48 bindings: [
38 HomeBinding(), 49 HomeBinding(),
@@ -40,6 +51,10 @@ class AppPages { @@ -40,6 +51,10 @@ class AppPages {
40 title: null, 51 title: null,
41 children: [ 52 children: [
42 GetPage( 53 GetPage(
  54 + middlewares: [
  55 + //only enter this route when authed
  56 + EnsureAuthMiddleware(),
  57 + ],
43 name: _Paths.PROFILE, 58 name: _Paths.PROFILE,
44 page: () => ProfileView(), 59 page: () => ProfileView(),
45 title: 'Profile', 60 title: 'Profile',
@@ -57,12 +72,17 @@ class AppPages { @@ -57,12 +72,17 @@ class AppPages {
57 name: _Paths.PRODUCT_DETAILS, 72 name: _Paths.PRODUCT_DETAILS,
58 page: () => ProductDetailsView(), 73 page: () => ProductDetailsView(),
59 binding: ProductDetailsBinding(), 74 binding: ProductDetailsBinding(),
  75 + middlewares: [
  76 + //only enter this route when authed
  77 + EnsureAuthMiddleware(),
  78 + ],
60 ), 79 ),
61 ], 80 ],
62 ), 81 ),
63 ], 82 ],
64 ), 83 ),
65 GetPage( 84 GetPage(
  85 + participatesInRootNavigator: true,
66 name: _Paths.SETTINGS, 86 name: _Paths.SETTINGS,
67 page: () => SettingsView(), 87 page: () => SettingsView(),
68 binding: SettingsBinding(), 88 binding: SettingsBinding(),
@@ -11,6 +11,9 @@ abstract class Routes { @@ -11,6 +11,9 @@ abstract class Routes {
11 11
12 static const PRODUCTS = _Paths.HOME + _Paths.PRODUCTS; 12 static const PRODUCTS = _Paths.HOME + _Paths.PRODUCTS;
13 static String PRODUCT_DETAILS(String productId) => '$PRODUCTS/$productId'; 13 static String PRODUCT_DETAILS(String productId) => '$PRODUCTS/$productId';
  14 + static const LOGIN = _Paths.LOGIN;
  15 + static String LOGIN_THEN(String afterSuccessfulLogin) =>
  16 + '$LOGIN?then=${Uri.encodeQueryComponent(afterSuccessfulLogin)}';
14 } 17 }
15 18
16 abstract class _Paths { 19 abstract class _Paths {
@@ -19,4 +22,5 @@ abstract class _Paths { @@ -19,4 +22,5 @@ abstract class _Paths {
19 static const PROFILE = '/profile'; 22 static const PROFILE = '/profile';
20 static const SETTINGS = '/settings'; 23 static const SETTINGS = '/settings';
21 static const PRODUCT_DETAILS = '/:productId'; 24 static const PRODUCT_DETAILS = '/:productId';
  25 + static const LOGIN = '/login';
22 } 26 }
  1 +import 'package:example_nav2/services/auth_service.dart';
1 import 'package:flutter/material.dart'; 2 import 'package:flutter/material.dart';
2 3
3 import 'package:get/get.dart'; 4 import 'package:get/get.dart';
@@ -9,6 +10,11 @@ void main() { @@ -9,6 +10,11 @@ void main() {
9 runApp( 10 runApp(
10 GetMaterialApp.router( 11 GetMaterialApp.router(
11 title: "Application", 12 title: "Application",
  13 + initialBinding: BindingsBuilder(
  14 + () {
  15 + Get.put(AuthService());
  16 + },
  17 + ),
12 getPages: AppPages.routes, 18 getPages: AppPages.routes,
13 routeInformationParser: GetInformationParser( 19 routeInformationParser: GetInformationParser(
14 // initialRoute: Routes.HOME, 20 // initialRoute: Routes.HOME,
@@ -16,7 +22,7 @@ void main() { @@ -16,7 +22,7 @@ void main() {
16 routerDelegate: GetDelegate( 22 routerDelegate: GetDelegate(
17 backButtonPopMode: PopMode.History, 23 backButtonPopMode: PopMode.History,
18 preventDuplicateHandlingMode: 24 preventDuplicateHandlingMode:
19 - PreventDuplicateHandlingMode.PopUntilOriginalRoute, 25 + PreventDuplicateHandlingMode.ReorderRoutes,
20 ), 26 ),
21 ), 27 ),
22 ); 28 );
  1 +import 'package:get/get.dart';
  2 +
  3 +class AuthService extends GetxService {
  4 + static AuthService get to => Get.find();
  5 +
  6 + /// Mocks a login process
  7 + final isLoggedIn = false.obs;
  8 + bool get isLoggedInValue => isLoggedIn.value;
  9 +
  10 + void login() {
  11 + isLoggedIn.value = true;
  12 + }
  13 +
  14 + void logout() {
  15 + isLoggedIn.value = false;
  16 + }
  17 +}
@@ -29,6 +29,16 @@ class GetNavConfig extends RouteInformation { @@ -29,6 +29,16 @@ class GetNavConfig extends RouteInformation {
29 ); 29 );
30 } 30 }
31 31
  32 + static GetNavConfig? fromRoute(String route) {
  33 + final res = Get.routeTree.matchRoute(route);
  34 + if (res.treeBranch.isEmpty) return null;
  35 + return GetNavConfig(
  36 + currentTreeBranch: res.treeBranch,
  37 + location: route,
  38 + state: null,
  39 + );
  40 + }
  41 +
32 @override 42 @override
33 String toString() => ''' 43 String toString() => '''
34 ======GetNavConfig=====\ncurrentTreeBranch: $currentTreeBranch\ncurrentPage: $currentPage\n======GetNavConfig====='''; 44 ======GetNavConfig=====\ncurrentTreeBranch: $currentTreeBranch\ncurrentPage: $currentPage\n======GetNavConfig=====''';
@@ -38,6 +38,12 @@ enum PreventDuplicateHandlingMode { @@ -38,6 +38,12 @@ enum PreventDuplicateHandlingMode {
38 38
39 /// Simply don't push the new route 39 /// Simply don't push the new route
40 DoNothing, 40 DoNothing,
  41 +
  42 + /// Recommended - Moves the old route entry to the front
  43 + ///
  44 + /// With this mode, you guarantee there will be only one
  45 + /// route entry for each location
  46 + ReorderRoutes
41 } 47 }
42 48
43 class GetDelegate extends RouterDelegate<GetNavConfig> 49 class GetDelegate extends RouterDelegate<GetNavConfig>
@@ -60,9 +66,47 @@ class GetDelegate extends RouterDelegate<GetNavConfig> @@ -60,9 +66,47 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
60 this.navigatorObservers, 66 this.navigatorObservers,
61 this.transitionDelegate, 67 this.transitionDelegate,
62 this.backButtonPopMode = PopMode.History, 68 this.backButtonPopMode = PopMode.History,
63 - this.preventDuplicateHandlingMode = PreventDuplicateHandlingMode.DoNothing, 69 + this.preventDuplicateHandlingMode =
  70 + PreventDuplicateHandlingMode.ReorderRoutes,
64 }); 71 });
65 72
  73 + GetNavConfig? runMiddleware(GetNavConfig config) {
  74 + final middlewares = config.currentTreeBranch.last.middlewares ?? [];
  75 + var iterator = config;
  76 + for (var item in middlewares) {
  77 + var redirectRes = item.redirectDelegate(iterator);
  78 + if (redirectRes == null) return null;
  79 + iterator = redirectRes;
  80 + }
  81 + return iterator;
  82 + }
  83 +
  84 + void _unsafeHistoryAdd(GetNavConfig config) {
  85 + final res = runMiddleware(config);
  86 + if (res == null) return;
  87 + history.add(res);
  88 + }
  89 +
  90 + void _unsafeHistoryRemove(GetNavConfig config) {
  91 + var index = history.indexOf(config);
  92 + if (index >= 0) _unsafeHistoryRemoveAt(index);
  93 + }
  94 +
  95 + GetNavConfig? _unsafeHistoryRemoveAt(int index) {
  96 + if (index == history.length - 1) {
  97 + //removing WILL update the current route
  98 + final toCheck = history[history.length - 2];
  99 + final resMiddleware = runMiddleware(toCheck);
  100 + if (resMiddleware == null) return null;
  101 + history[history.length - 2] = resMiddleware;
  102 + }
  103 + return history.removeAt(index);
  104 + }
  105 +
  106 + void _unsafeHistoryClear() {
  107 + history.clear();
  108 + }
  109 +
66 /// Adds a new history entry and waits for the result 110 /// Adds a new history entry and waits for the result
67 Future<T?> pushHistory<T>( 111 Future<T?> pushHistory<T>(
68 GetNavConfig config, { 112 GetNavConfig config, {
@@ -79,25 +123,32 @@ class GetDelegate extends RouterDelegate<GetNavConfig> @@ -79,25 +123,32 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
79 } 123 }
80 124
81 void _removeHistoryEntry(GetNavConfig entry) { 125 void _removeHistoryEntry(GetNavConfig entry) {
82 - history.remove(entry); 126 + _unsafeHistoryRemove(entry);
83 final lastCompleter = _resultCompleter.remove(entry); 127 final lastCompleter = _resultCompleter.remove(entry);
84 lastCompleter?.complete(entry); 128 lastCompleter?.complete(entry);
85 } 129 }
86 130
87 void _pushHistory(GetNavConfig config) { 131 void _pushHistory(GetNavConfig config) {
88 if (config.currentPage!.preventDuplicates) { 132 if (config.currentPage!.preventDuplicates) {
89 - if (history.any((element) => element.location == config.location)) { 133 + final originalEntryIndex =
  134 + history.indexWhere((element) => element.location == config.location);
  135 + if (originalEntryIndex >= 0) {
90 switch (preventDuplicateHandlingMode) { 136 switch (preventDuplicateHandlingMode) {
91 case PreventDuplicateHandlingMode.PopUntilOriginalRoute: 137 case PreventDuplicateHandlingMode.PopUntilOriginalRoute:
92 until(config.location!, popMode: PopMode.Page); 138 until(config.location!, popMode: PopMode.Page);
93 - return; 139 + break;
  140 + case PreventDuplicateHandlingMode.ReorderRoutes:
  141 + _unsafeHistoryRemoveAt(originalEntryIndex);
  142 + _unsafeHistoryAdd(config);
  143 + break;
94 case PreventDuplicateHandlingMode.DoNothing: 144 case PreventDuplicateHandlingMode.DoNothing:
95 default: 145 default:
96 - return; 146 + break;
97 } 147 }
  148 + return;
98 } 149 }
99 } 150 }
100 - history.add(config); 151 + _unsafeHistoryAdd(config);
101 } 152 }
102 153
103 // GetPageRoute getPageRoute(RouteSettings? settings) { 154 // GetPageRoute getPageRoute(RouteSettings? settings) {
@@ -110,9 +161,8 @@ class GetDelegate extends RouterDelegate<GetNavConfig> @@ -110,9 +161,8 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
110 return _doPopHistory(); 161 return _doPopHistory();
111 } 162 }
112 163
113 - GetNavConfig _doPopHistory() {  
114 - final res = history.removeLast();  
115 - return res; 164 + GetNavConfig? _doPopHistory() {
  165 + return _unsafeHistoryRemoveAt(history.length - 1);
116 } 166 }
117 167
118 GetNavConfig? _popPage() { 168 GetNavConfig? _popPage() {
@@ -206,12 +256,18 @@ class GetDelegate extends RouterDelegate<GetNavConfig> @@ -206,12 +256,18 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
206 List<GetPage> getVisualPages() { 256 List<GetPage> getVisualPages() {
207 final currentHistory = currentConfiguration; 257 final currentHistory = currentConfiguration;
208 if (currentHistory == null) return <GetPage>[]; 258 if (currentHistory == null) return <GetPage>[];
209 - return currentHistory.currentTreeBranch.where((r) {  
210 - final mware =  
211 - (r.middlewares ?? []).whereType<RouterOutletContainerMiddleWare>();  
212 - if (mware.length == 0) return true;  
213 - return r.name == mware.first.stayAt;  
214 - }).toList(); 259 +
  260 + final res = currentHistory.currentTreeBranch
  261 + .where((r) => r.participatesInRootNavigator != null);
  262 + if (res.length == 0) {
  263 + //default behavoir, all routes participate in root navigator
  264 + return currentHistory.currentTreeBranch;
  265 + } else {
  266 + //user specified at least one participatesInRootNavigator
  267 + return res
  268 + .where((element) => element.participatesInRootNavigator == true)
  269 + .toList();
  270 + }
215 } 271 }
216 272
217 @override 273 @override
@@ -234,7 +290,7 @@ class GetDelegate extends RouterDelegate<GetNavConfig> @@ -234,7 +290,7 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
234 290
235 @override 291 @override
236 Future<void> setInitialRoutePath(GetNavConfig configuration) async { 292 Future<void> setInitialRoutePath(GetNavConfig configuration) async {
237 - history.clear(); 293 + _unsafeHistoryClear();
238 _resultCompleter.clear(); 294 _resultCompleter.clear();
239 await pushHistory(configuration); 295 await pushHistory(configuration);
240 } 296 }
@@ -350,6 +406,7 @@ class GetNavigator extends Navigator { @@ -350,6 +406,7 @@ class GetNavigator extends Navigator {
350 bool Function(Route<dynamic>, dynamic)? onPopPage, 406 bool Function(Route<dynamic>, dynamic)? onPopPage,
351 required List<Page> pages, 407 required List<Page> pages,
352 List<NavigatorObserver>? observers, 408 List<NavigatorObserver>? observers,
  409 + bool reportsRouteUpdateToEngine = false,
353 TransitionDelegate? transitionDelegate, 410 TransitionDelegate? transitionDelegate,
354 String? name, 411 String? name,
355 }) : assert(key != null || name != null, 412 }) : assert(key != null || name != null,
@@ -357,7 +414,7 @@ class GetNavigator extends Navigator { @@ -357,7 +414,7 @@ class GetNavigator extends Navigator {
357 super( 414 super(
358 key: key ?? Get.nestedKey(name), 415 key: key ?? Get.nestedKey(name),
359 onPopPage: onPopPage, 416 onPopPage: onPopPage,
360 - reportsRouteUpdateToEngine: true, 417 + reportsRouteUpdateToEngine: reportsRouteUpdateToEngine,
361 pages: pages, 418 pages: pages,
362 observers: [ 419 observers: [
363 GetObserver(), 420 GetObserver(),
@@ -120,15 +120,6 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, GetNavConfig> { @@ -120,15 +120,6 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, GetNavConfig> {
120 ); 120 );
121 } 121 }
122 122
123 -/// A marker outlet to identify which pages are visual  
124 -/// (handled by the navigator) and which are logical  
125 -/// (handled by the delegate)  
126 -class RouterOutletContainerMiddleWare extends GetMiddleware {  
127 - final String stayAt;  
128 -  
129 - RouterOutletContainerMiddleWare(this.stayAt);  
130 -}  
131 -  
132 extension PagesListExt on List<GetPage> { 123 extension PagesListExt on List<GetPage> {
133 List<GetPage> pickAtRoute(String route) { 124 List<GetPage> pickAtRoute(String route) {
134 return skipWhile((value) => value.name != route).toList(); 125 return skipWhile((value) => value.name != route).toList();
@@ -98,14 +98,28 @@ class ParseRouteTree { @@ -98,14 +98,28 @@ class ParseRouteTree {
98 final parentPath = route.name; 98 final parentPath = route.name;
99 for (var page in route.children!) { 99 for (var page in route.children!) {
100 // Add Parent middlewares to children 100 // Add Parent middlewares to children
101 - final pageMiddlewares = page.middlewares ?? <GetMiddleware>[];  
102 - pageMiddlewares.addAll(route.middlewares ?? <GetMiddleware>[]);  
103 - result.add(_addChild(page, parentPath, pageMiddlewares)); 101 + final parentMiddlewares = [
  102 + if (page.middlewares != null) ...page.middlewares!,
  103 + if (route.middlewares != null) ...route.middlewares!
  104 + ];
  105 + result.add(
  106 + _addChild(
  107 + page,
  108 + parentPath,
  109 + parentMiddlewares,
  110 + ),
  111 + );
104 112
105 final children = _flattenPage(page); 113 final children = _flattenPage(page);
106 for (var child in children) { 114 for (var child in children) {
107 - pageMiddlewares.addAll(child.middlewares ?? <GetMiddleware>[]);  
108 - result.add(_addChild(child, parentPath, pageMiddlewares)); 115 + result.add(_addChild(
  116 + child,
  117 + parentPath,
  118 + [
  119 + ...parentMiddlewares,
  120 + if (child.middlewares != null) ...child.middlewares!,
  121 + ],
  122 + ));
109 } 123 }
110 } 124 }
111 return result; 125 return result;
@@ -34,6 +34,7 @@ class GetPage<T> extends Page<T> { @@ -34,6 +34,7 @@ class GetPage<T> extends Page<T> {
34 final String? title; 34 final String? title;
35 final Transition? transition; 35 final Transition? transition;
36 final Curve curve; 36 final Curve curve;
  37 + final bool? participatesInRootNavigator;
37 final Alignment? alignment; 38 final Alignment? alignment;
38 final bool maintainState; 39 final bool maintainState;
39 final bool opaque; 40 final bool opaque;
@@ -65,6 +66,7 @@ class GetPage<T> extends Page<T> { @@ -65,6 +66,7 @@ class GetPage<T> extends Page<T> {
65 required this.name, 66 required this.name,
66 required this.page, 67 required this.page,
67 this.title, 68 this.title,
  69 + this.participatesInRootNavigator,
68 this.gestureWidth = 20, 70 this.gestureWidth = 20,
69 // RouteSettings settings, 71 // RouteSettings settings,
70 this.maintainState = true, 72 this.maintainState = true,
@@ -82,7 +84,7 @@ class GetPage<T> extends Page<T> { @@ -82,7 +84,7 @@ class GetPage<T> extends Page<T> {
82 this.children, 84 this.children,
83 this.middlewares, 85 this.middlewares,
84 this.unknownRoute, 86 this.unknownRoute,
85 - this.preventDuplicates = false, 87 + this.preventDuplicates = true,
86 }) : path = _nameToRegex(name), 88 }) : path = _nameToRegex(name),
87 super( 89 super(
88 key: ValueKey(name), 90 key: ValueKey(name),
@@ -134,8 +136,11 @@ class GetPage<T> extends Page<T> { @@ -134,8 +136,11 @@ class GetPage<T> extends Page<T> {
134 List<GetMiddleware>? middlewares, 136 List<GetMiddleware>? middlewares,
135 bool? preventDuplicates, 137 bool? preventDuplicates,
136 double? gestureWidth, 138 double? gestureWidth,
  139 + bool? participatesInRootNavigator,
137 }) { 140 }) {
138 return GetPage( 141 return GetPage(
  142 + participatesInRootNavigator:
  143 + participatesInRootNavigator ?? this.participatesInRootNavigator,
139 preventDuplicates: preventDuplicates ?? this.preventDuplicates, 144 preventDuplicates: preventDuplicates ?? this.preventDuplicates,
140 name: name ?? this.name, 145 name: name ?? this.name,
141 page: page ?? this.page, 146 page: page ?? this.page,
@@ -32,6 +32,25 @@ abstract class _RouteMiddleware { @@ -32,6 +32,25 @@ abstract class _RouteMiddleware {
32 /// {@end-tool} 32 /// {@end-tool}
33 RouteSettings? redirect(String route); 33 RouteSettings? redirect(String route);
34 34
  35 + /// Similar to [redirect],
  36 + /// This function will be called when the router delegate changes the
  37 + /// current route.
  38 + ///
  39 + /// The default implmentation is to navigate to
  40 + /// the input route, with no redirection.
  41 + ///
  42 + /// if this returns null, the navigation is stopped,
  43 + /// and no new routes are pushed.
  44 + /// {@tool snippet}
  45 + /// ```dart
  46 + /// GetNavConfig? redirect(GetNavConfig route) {
  47 + /// final authService = Get.find<AuthService>();
  48 + /// return authService.authed.value ? null : RouteSettings(name: '/login');
  49 + /// }
  50 + /// ```
  51 + /// {@end-tool}
  52 + GetNavConfig? redirectDelegate(GetNavConfig route);
  53 +
35 /// This function will be called when this Page is called 54 /// This function will be called when this Page is called
36 /// you can use it to change something about the page or give it new page 55 /// you can use it to change something about the page or give it new page
37 /// {@tool snippet} 56 /// {@tool snippet}
@@ -97,6 +116,9 @@ class GetMiddleware implements _RouteMiddleware { @@ -97,6 +116,9 @@ class GetMiddleware implements _RouteMiddleware {
97 116
98 @override 117 @override
99 void onPageDispose() {} 118 void onPageDispose() {}
  119 +
  120 + @override
  121 + GetNavConfig? redirectDelegate(GetNavConfig route) => route;
100 } 122 }
101 123
102 class MiddlewareRunner { 124 class MiddlewareRunner {
@@ -204,6 +226,10 @@ class PageRedirect { @@ -204,6 +226,10 @@ class PageRedirect {
204 return GetPageRoute<T>( 226 return GetPageRoute<T>(
205 page: _r.page, 227 page: _r.page,
206 parameter: _r.parameter, 228 parameter: _r.parameter,
  229 + alignment: _r.alignment,
  230 + title: _r.title,
  231 + maintainState: _r.maintainState,
  232 + routeName: _r.name,
207 settings: _r, 233 settings: _r,
208 curve: _r.curve, 234 curve: _r.curve,
209 gestureWidth: _r.gestureWidth, 235 gestureWidth: _r.gestureWidth,
@@ -7,3 +7,4 @@ export 'internacionalization.dart'; @@ -7,3 +7,4 @@ export 'internacionalization.dart';
7 export 'num_extensions.dart'; 7 export 'num_extensions.dart';
8 export 'string_extensions.dart'; 8 export 'string_extensions.dart';
9 export 'widget_extensions.dart'; 9 export 'widget_extensions.dart';
  10 +export 'iterable_extensions.dart';
  1 +extension IterableExtensions<T> on Iterable<T> {
  2 + Iterable<TRes> mapMany<TRes>(
  3 + Iterable<TRes>? Function(T item) selector) sync* {
  4 + for (var item in this) {
  5 + final res = selector(item);
  6 + if (res != null) yield* res;
  7 + }
  8 + }
  9 +}