Committed by
GitHub
Merge pull request #1621 from Bdaya-Dev/master
Adding route guards and improving navigation
Showing
20 changed files
with
398 additions
and
98 deletions
| 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 | +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 |
| 1 | -import 'package:flutter/material.dart'; | ||
| 2 | - | ||
| 3 | -import 'package:get/get.dart'; | ||
| 4 | - | ||
| 5 | -import '../controllers/profile_controller.dart'; | ||
| 6 | - | ||
| 7 | -class ProfileView extends GetView<ProfileController> { | ||
| 8 | - @override | ||
| 9 | - Widget build(BuildContext context) { | ||
| 10 | - return Scaffold( | ||
| 11 | - body: Center( | ||
| 12 | - child: Text( | ||
| 13 | - 'ProfileView is working', | ||
| 14 | - style: TextStyle(fontSize: 20), | ||
| 15 | - ), | ||
| 16 | - ), | ||
| 17 | - ); | ||
| 18 | - } | ||
| 19 | -} | 1 | +import 'package:flutter/material.dart'; |
| 2 | + | ||
| 3 | +import 'package:get/get.dart'; | ||
| 4 | + | ||
| 5 | +import '../controllers/profile_controller.dart'; | ||
| 6 | + | ||
| 7 | +class ProfileView extends GetView<ProfileController> { | ||
| 8 | + @override | ||
| 9 | + Widget build(BuildContext context) { | ||
| 10 | + return Scaffold( | ||
| 11 | + backgroundColor: Colors.amber, | ||
| 12 | + body: Center( | ||
| 13 | + child: Text( | ||
| 14 | + 'ProfileView is working', | ||
| 15 | + style: TextStyle(fontSize: 20), | ||
| 16 | + ), | ||
| 17 | + ), | ||
| 18 | + ); | ||
| 19 | + } | ||
| 20 | +} |
| 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:flutter/material.dart'; | ||
| 2 | - | ||
| 3 | -import 'package:get/get.dart'; | ||
| 4 | -import 'package:get/get_navigation/src/nav2/get_router_delegate.dart'; | ||
| 5 | - | ||
| 6 | -import 'app/routes/app_pages.dart'; | ||
| 7 | - | ||
| 8 | -void main() { | ||
| 9 | - runApp( | ||
| 10 | - GetMaterialApp.router( | ||
| 11 | - title: "Application", | ||
| 12 | - getPages: AppPages.routes, | ||
| 13 | - routeInformationParser: GetInformationParser( | ||
| 14 | - // initialRoute: Routes.HOME, | ||
| 15 | - ), | ||
| 16 | - routerDelegate: GetDelegate( | ||
| 17 | - backButtonPopMode: PopMode.History, | ||
| 18 | - preventDuplicateHandlingMode: | ||
| 19 | - PreventDuplicateHandlingMode.PopUntilOriginalRoute, | ||
| 20 | - ), | ||
| 21 | - ), | ||
| 22 | - ); | ||
| 23 | -} | 1 | +import 'package:example_nav2/services/auth_service.dart'; |
| 2 | +import 'package:flutter/material.dart'; | ||
| 3 | + | ||
| 4 | +import 'package:get/get.dart'; | ||
| 5 | +import 'package:get/get_navigation/src/nav2/get_router_delegate.dart'; | ||
| 6 | + | ||
| 7 | +import 'app/routes/app_pages.dart'; | ||
| 8 | + | ||
| 9 | +void main() { | ||
| 10 | + runApp( | ||
| 11 | + GetMaterialApp.router( | ||
| 12 | + title: "Application", | ||
| 13 | + initialBinding: BindingsBuilder( | ||
| 14 | + () { | ||
| 15 | + Get.put(AuthService()); | ||
| 16 | + }, | ||
| 17 | + ), | ||
| 18 | + getPages: AppPages.routes, | ||
| 19 | + routeInformationParser: GetInformationParser( | ||
| 20 | + // initialRoute: Routes.HOME, | ||
| 21 | + ), | ||
| 22 | + routerDelegate: GetDelegate( | ||
| 23 | + backButtonPopMode: PopMode.History, | ||
| 24 | + preventDuplicateHandlingMode: | ||
| 25 | + PreventDuplicateHandlingMode.ReorderRoutes, | ||
| 26 | + ), | ||
| 27 | + ), | ||
| 28 | + ); | ||
| 29 | +} |
example_nav2/lib/services/auth_service.dart
0 → 100644
| 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, |
| 1 | -export 'context_extensions.dart'; | ||
| 2 | -export 'double_extensions.dart'; | ||
| 3 | -export 'duration_extensions.dart'; | ||
| 4 | -export 'dynamic_extensions.dart'; | ||
| 5 | -export 'event_loop_extensions.dart'; | ||
| 6 | -export 'internacionalization.dart'; | ||
| 7 | -export 'num_extensions.dart'; | ||
| 8 | -export 'string_extensions.dart'; | ||
| 9 | -export 'widget_extensions.dart'; | 1 | +export 'context_extensions.dart'; |
| 2 | +export 'double_extensions.dart'; | ||
| 3 | +export 'duration_extensions.dart'; | ||
| 4 | +export 'dynamic_extensions.dart'; | ||
| 5 | +export 'event_loop_extensions.dart'; | ||
| 6 | +export 'internacionalization.dart'; | ||
| 7 | +export 'num_extensions.dart'; | ||
| 8 | +export 'string_extensions.dart'; | ||
| 9 | +export 'widget_extensions.dart'; | ||
| 10 | +export 'iterable_extensions.dart'; |
-
Please register or login to post a comment