Committed by
GitHub
Merge pull request #1511 from Bdaya-Dev/router-outlet
More Improvements to the new RouterOutlet
Showing
17 changed files
with
575 additions
and
222 deletions
@@ -20,6 +20,13 @@ | @@ -20,6 +20,13 @@ | ||
20 | "cwd": "example_nav2", | 20 | "cwd": "example_nav2", |
21 | "request": "launch", | 21 | "request": "launch", |
22 | "type": "dart" | 22 | "type": "dart" |
23 | + }, | ||
24 | + { | ||
25 | + "name": "example_nav2 WEB", | ||
26 | + "cwd": "example_nav2", | ||
27 | + "request": "launch", | ||
28 | + "type": "dart", | ||
29 | + "deviceId": "Chrome" | ||
23 | } | 30 | } |
24 | ] | 31 | ] |
25 | } | 32 | } |
1 | -import 'package:example_nav2/app/modules/home/views/dashboard_view.dart'; | ||
2 | -import 'package:example_nav2/app/routes/app_pages.dart'; | ||
3 | import 'package:flutter/material.dart'; | 1 | import 'package:flutter/material.dart'; |
4 | - | ||
5 | import 'package:get/get.dart'; | 2 | import 'package:get/get.dart'; |
6 | -import 'package:get/get_navigation/src/nav2/get_router_delegate.dart'; | ||
7 | -import 'package:get/get_navigation/src/nav2/router_outlet.dart'; | ||
8 | 3 | ||
4 | +import '../../../routes/app_pages.dart'; | ||
9 | import '../controllers/home_controller.dart'; | 5 | import '../controllers/home_controller.dart'; |
6 | +import 'dashboard_view.dart'; | ||
10 | 7 | ||
11 | class HomeView extends GetView<HomeController> { | 8 | class HomeView extends GetView<HomeController> { |
12 | @override | 9 | @override |
@@ -14,26 +11,21 @@ class HomeView extends GetView<HomeController> { | @@ -14,26 +11,21 @@ class HomeView extends GetView<HomeController> { | ||
14 | return GetRouterOutlet.builder( | 11 | return GetRouterOutlet.builder( |
15 | builder: (context, delegate, currentRoute) { | 12 | builder: (context, delegate, currentRoute) { |
16 | //This router outlet handles the appbar and the bottom navigation bar | 13 | //This router outlet handles the appbar and the bottom navigation bar |
17 | - final title = currentRoute?.title; | ||
18 | - final currentName = currentRoute?.name; | 14 | + final currentLocation = currentRoute?.location; |
19 | var currentIndex = 0; | 15 | var currentIndex = 0; |
20 | - if (currentName?.startsWith(Routes.PRODUCTS) == true) currentIndex = 2; | ||
21 | - if (currentName?.startsWith(Routes.PROFILE) == true) currentIndex = 1; | 16 | + if (currentLocation?.startsWith(Routes.PRODUCTS) == true) { |
17 | + currentIndex = 2; | ||
18 | + } | ||
19 | + if (currentLocation?.startsWith(Routes.PROFILE) == true) { | ||
20 | + currentIndex = 1; | ||
21 | + } | ||
22 | return Scaffold( | 22 | return Scaffold( |
23 | - appBar: title == null | ||
24 | - ? null | ||
25 | - : AppBar( | ||
26 | - title: Text(title), | ||
27 | - centerTitle: true, | ||
28 | - ), | ||
29 | body: GetRouterOutlet( | 23 | body: GetRouterOutlet( |
30 | emptyPage: (delegate) => DashboardView(), | 24 | emptyPage: (delegate) => DashboardView(), |
31 | pickPages: (currentNavStack) { | 25 | pickPages: (currentNavStack) { |
32 | // will take any route after home | 26 | // will take any route after home |
33 | - final res = currentNavStack.pickAfterRoute(Routes.HOME); | ||
34 | - // print('''RouterOutlet rebuild: | ||
35 | - // currentStack: $currentNavStack | ||
36 | - // pickedStack: $res'''); | 27 | + final res = |
28 | + currentNavStack.currentTreeBranch.pickAfterRoute(Routes.HOME); | ||
37 | return res; | 29 | return res; |
38 | }, | 30 | }, |
39 | ), | 31 | ), |
@@ -42,7 +34,7 @@ class HomeView extends GetView<HomeController> { | @@ -42,7 +34,7 @@ class HomeView extends GetView<HomeController> { | ||
42 | onTap: (value) { | 34 | onTap: (value) { |
43 | switch (value) { | 35 | switch (value) { |
44 | case 0: | 36 | case 0: |
45 | - delegate.offUntil(Routes.HOME); | 37 | + delegate.until(Routes.HOME); |
46 | break; | 38 | break; |
47 | case 1: | 39 | case 1: |
48 | delegate.toNamed(Routes.PROFILE); | 40 | delegate.toNamed(Routes.PROFILE); |
1 | +import 'package:get/get.dart'; | ||
2 | + | ||
3 | +class RootController extends GetxController { | ||
4 | + //TODO: Implement RootController | ||
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:flutter/material.dart'; | ||
3 | +import 'package:get/get.dart'; | ||
4 | + | ||
5 | +class DrawerWidget extends StatelessWidget { | ||
6 | + const DrawerWidget({ | ||
7 | + Key? key, | ||
8 | + }) : super(key: key); | ||
9 | + | ||
10 | + @override | ||
11 | + Widget build(BuildContext context) { | ||
12 | + return Drawer( | ||
13 | + child: Column( | ||
14 | + children: [ | ||
15 | + Container( | ||
16 | + height: 100, | ||
17 | + color: Colors.red, | ||
18 | + ), | ||
19 | + ListTile( | ||
20 | + title: Text('Home'), | ||
21 | + onTap: () { | ||
22 | + Get.getDelegate()?.toNamed(Routes.HOME); | ||
23 | + //to close the drawer | ||
24 | + | ||
25 | + Navigator.of(context).pop(); | ||
26 | + }, | ||
27 | + ), | ||
28 | + ListTile( | ||
29 | + title: Text('Settings'), | ||
30 | + onTap: () { | ||
31 | + Get.getDelegate()?.toNamed(Routes.SETTINGS); | ||
32 | + //to close the drawer | ||
33 | + | ||
34 | + Navigator.of(context).pop(); | ||
35 | + }, | ||
36 | + ), | ||
37 | + ], | ||
38 | + ), | ||
39 | + ); | ||
40 | + } | ||
41 | +} |
1 | +import 'package:example_nav2/app/routes/app_pages.dart'; | ||
2 | +import 'package:flutter/material.dart'; | ||
3 | + | ||
4 | +import 'package:get/get.dart'; | ||
5 | + | ||
6 | +import '../controllers/root_controller.dart'; | ||
7 | +import 'drawer.dart'; | ||
8 | + | ||
9 | +class RootView extends GetView<RootController> { | ||
10 | + @override | ||
11 | + Widget build(BuildContext context) { | ||
12 | + return GetRouterOutlet.builder( | ||
13 | + builder: (context, rDelegate, currentRoute) { | ||
14 | + final title = currentRoute?.location; | ||
15 | + return Scaffold( | ||
16 | + drawer: DrawerWidget(), | ||
17 | + appBar: AppBar( | ||
18 | + title: Text(title ?? ''), | ||
19 | + centerTitle: true, | ||
20 | + ), | ||
21 | + body: GetRouterOutlet( | ||
22 | + emptyPage: (delegate) { | ||
23 | + return Center( | ||
24 | + child: Column( | ||
25 | + mainAxisSize: MainAxisSize.min, | ||
26 | + children: [ | ||
27 | + Text('<<<< Select something from the drawer on the left'), | ||
28 | + Builder( | ||
29 | + builder: (context) => MaterialButton( | ||
30 | + child: Icon(Icons.open_in_new_outlined), | ||
31 | + onPressed: () { | ||
32 | + Scaffold.of(context).openDrawer(); | ||
33 | + }, | ||
34 | + ), | ||
35 | + ) | ||
36 | + ], | ||
37 | + ), | ||
38 | + ); | ||
39 | + }, | ||
40 | + pickPages: (currentNavStack) { | ||
41 | + //show all routes here except the root view | ||
42 | + print('Root RouterOutlet: $currentNavStack'); | ||
43 | + return currentNavStack.currentTreeBranch.skip(1).take(1).toList(); | ||
44 | + }, | ||
45 | + ), | ||
46 | + ); | ||
47 | + }, | ||
48 | + ); | ||
49 | + } | ||
50 | +} |
@@ -8,10 +8,6 @@ class SettingsView extends GetView<SettingsController> { | @@ -8,10 +8,6 @@ class SettingsView extends GetView<SettingsController> { | ||
8 | @override | 8 | @override |
9 | Widget build(BuildContext context) { | 9 | Widget build(BuildContext context) { |
10 | return Scaffold( | 10 | return Scaffold( |
11 | - appBar: AppBar( | ||
12 | - title: Text('SettingsView'), | ||
13 | - centerTitle: true, | ||
14 | - ), | ||
15 | body: Center( | 11 | body: Center( |
16 | child: Text( | 12 | child: Text( |
17 | 'SettingsView is working', | 13 | 'SettingsView is working', |
1 | +import 'package:example_nav2/app/modules/root/bindings/root_binding.dart'; | ||
2 | +import 'package:example_nav2/app/modules/root/views/root_view.dart'; | ||
1 | import 'package:get/get.dart'; | 3 | import 'package:get/get.dart'; |
2 | import 'package:get/get_navigation/src/nav2/router_outlet.dart'; | 4 | import 'package:get/get_navigation/src/nav2/router_outlet.dart'; |
3 | import '../modules/home/bindings/home_binding.dart'; | 5 | import '../modules/home/bindings/home_binding.dart'; |
@@ -20,41 +22,49 @@ class AppPages { | @@ -20,41 +22,49 @@ class AppPages { | ||
20 | 22 | ||
21 | static final routes = [ | 23 | static final routes = [ |
22 | GetPage( | 24 | GetPage( |
23 | - name: _Paths.HOME, | ||
24 | - page: () => HomeView(), | ||
25 | - bindings: [ | ||
26 | - HomeBinding(), | ||
27 | - ], | ||
28 | - title: null, | 25 | + name: '/', |
26 | + page: () => RootView(), | ||
29 | middlewares: [ | 27 | middlewares: [ |
30 | - RouterOutletContainerMiddleWare(_Paths.HOME), | 28 | + RouterOutletContainerMiddleWare('/'), |
31 | ], | 29 | ], |
30 | + binding: RootBinding(), | ||
32 | children: [ | 31 | children: [ |
33 | GetPage( | 32 | GetPage( |
34 | - name: _Paths.PROFILE, | ||
35 | - page: () => ProfileView(), | ||
36 | - title: 'Profile', | ||
37 | - binding: ProfileBinding(), | ||
38 | - ), | ||
39 | - GetPage( | ||
40 | - name: _Paths.PRODUCTS, | ||
41 | - page: () => ProductsView(), | ||
42 | - title: 'Products', | ||
43 | - binding: ProductsBinding(), | 33 | + name: _Paths.HOME, |
34 | + preventDuplicates: true, | ||
35 | + page: () => HomeView(), | ||
36 | + bindings: [ | ||
37 | + HomeBinding(), | ||
38 | + ], | ||
39 | + title: null, | ||
44 | children: [ | 40 | children: [ |
45 | GetPage( | 41 | GetPage( |
46 | - name: _Paths.PRODUCT_DETAILS, | ||
47 | - page: () => ProductDetailsView(), | ||
48 | - binding: ProductDetailsBinding(), | 42 | + name: _Paths.PROFILE, |
43 | + page: () => ProfileView(), | ||
44 | + title: 'Profile', | ||
45 | + binding: ProfileBinding(), | ||
46 | + ), | ||
47 | + GetPage( | ||
48 | + name: _Paths.PRODUCTS, | ||
49 | + page: () => ProductsView(), | ||
50 | + title: 'Products', | ||
51 | + binding: ProductsBinding(), | ||
52 | + children: [ | ||
53 | + GetPage( | ||
54 | + name: _Paths.PRODUCT_DETAILS, | ||
55 | + page: () => ProductDetailsView(), | ||
56 | + binding: ProductDetailsBinding(), | ||
57 | + ), | ||
58 | + ], | ||
49 | ), | 59 | ), |
50 | ], | 60 | ], |
51 | ), | 61 | ), |
62 | + GetPage( | ||
63 | + name: _Paths.SETTINGS, | ||
64 | + page: () => SettingsView(), | ||
65 | + binding: SettingsBinding(), | ||
66 | + ), | ||
52 | ], | 67 | ], |
53 | ), | 68 | ), |
54 | - GetPage( | ||
55 | - name: _Paths.SETTINGS, | ||
56 | - page: () => SettingsView(), | ||
57 | - binding: SettingsBinding(), | ||
58 | - ), | ||
59 | ]; | 69 | ]; |
60 | } | 70 | } |
@@ -10,8 +10,14 @@ void main() { | @@ -10,8 +10,14 @@ void main() { | ||
10 | GetMaterialApp.router( | 10 | GetMaterialApp.router( |
11 | title: "Application", | 11 | title: "Application", |
12 | getPages: AppPages.routes, | 12 | getPages: AppPages.routes, |
13 | - routeInformationParser: GetInformationParser(), | ||
14 | - routerDelegate: GetDelegate(), | 13 | + routeInformationParser: GetInformationParser( |
14 | + // initialRoute: Routes.HOME, | ||
15 | + ), | ||
16 | + routerDelegate: GetDelegate( | ||
17 | + backButtonPopMode: PopMode.History, | ||
18 | + preventDuplicateHandlingMode: | ||
19 | + PreventDuplicateHandlingMode.PopUntilOriginalRoute, | ||
20 | + ), | ||
15 | ), | 21 | ), |
16 | ); | 22 | ); |
17 | } | 23 | } |
1 | import 'package:flutter/widgets.dart'; | 1 | import 'package:flutter/widgets.dart'; |
2 | -import 'package:get/get_navigation/src/nav2/get_router_delegate.dart'; | ||
3 | 2 | ||
4 | import '../../get_core/src/get_interface.dart'; | 3 | import '../../get_core/src/get_interface.dart'; |
5 | - | ||
6 | import '../../route_manager.dart'; | 4 | import '../../route_manager.dart'; |
7 | import 'get_instance.dart'; | 5 | import 'get_instance.dart'; |
8 | 6 | ||
@@ -130,5 +128,5 @@ extension Inst on GetInterface { | @@ -130,5 +128,5 @@ extension Inst on GetInterface { | ||
130 | TDelegate? delegate<TDelegate extends RouterDelegate<TPage>, TPage>() => | 128 | TDelegate? delegate<TDelegate extends RouterDelegate<TPage>, TPage>() => |
131 | routerDelegate as TDelegate?; | 129 | routerDelegate as TDelegate?; |
132 | 130 | ||
133 | - GetDelegate? getDelegate() => delegate<GetDelegate, GetPage>(); | 131 | + GetDelegate? getDelegate() => delegate<GetDelegate, GetNavConfig>(); |
134 | } | 132 | } |
@@ -3,6 +3,9 @@ library get_navigation; | @@ -3,6 +3,9 @@ library get_navigation; | ||
3 | export 'src/bottomsheet/bottomsheet.dart'; | 3 | export 'src/bottomsheet/bottomsheet.dart'; |
4 | export 'src/extension_navigation.dart'; | 4 | export 'src/extension_navigation.dart'; |
5 | export 'src/nav2/get_information_parser.dart'; | 5 | export 'src/nav2/get_information_parser.dart'; |
6 | +export 'src/nav2/get_nav_config.dart'; | ||
7 | +export 'src/nav2/get_router_delegate.dart'; | ||
8 | +export 'src/nav2/router_outlet.dart'; | ||
6 | export 'src/root/get_cupertino_app.dart'; | 9 | export 'src/root/get_cupertino_app.dart'; |
7 | export 'src/root/get_material_app.dart'; | 10 | export 'src/root/get_material_app.dart'; |
8 | export 'src/root/internacionalization.dart'; | 11 | export 'src/root/internacionalization.dart'; |
@@ -2,28 +2,44 @@ import 'package:flutter/foundation.dart'; | @@ -2,28 +2,44 @@ import 'package:flutter/foundation.dart'; | ||
2 | import 'package:flutter/widgets.dart'; | 2 | import 'package:flutter/widgets.dart'; |
3 | import '../../../get.dart'; | 3 | import '../../../get.dart'; |
4 | 4 | ||
5 | -class GetInformationParser extends RouteInformationParser<GetPage> { | 5 | +class GetInformationParser extends RouteInformationParser<GetNavConfig> { |
6 | + final String initialRoute; | ||
7 | + | ||
8 | + GetInformationParser({ | ||
9 | + this.initialRoute = '/', | ||
10 | + }); | ||
6 | @override | 11 | @override |
7 | - SynchronousFuture<GetPage> parseRouteInformation( | 12 | + SynchronousFuture<GetNavConfig> parseRouteInformation( |
8 | RouteInformation routeInformation, | 13 | RouteInformation routeInformation, |
9 | ) { | 14 | ) { |
10 | - if (routeInformation.location == '/') { | ||
11 | - return SynchronousFuture(Get.routeTree.routes.first); | 15 | + print('GetInformationParser: route location: ${routeInformation.location}'); |
16 | + var location = routeInformation.location; | ||
17 | + if (location == '/') { | ||
18 | + //check if there is a corresponding page | ||
19 | + //if not, relocate to initialRoute | ||
20 | + if (!Get.routeTree.routes.any((element) => element.name == '/')) { | ||
21 | + location = initialRoute; | ||
22 | + } | ||
12 | } | 23 | } |
13 | - print('route location: ${routeInformation.location}'); | ||
14 | - final page = Get.routeTree.matchRoute(routeInformation.location!); | ||
15 | - print(page.parameters); | ||
16 | - final val = page.route!.copy( | ||
17 | - name: routeInformation.location, | ||
18 | - parameter: Map.from(page.parameters), | 24 | + |
25 | + final matchResult = Get.routeTree.matchRoute(location ?? initialRoute); | ||
26 | + | ||
27 | + return SynchronousFuture( | ||
28 | + GetNavConfig( | ||
29 | + currentTreeBranch: matchResult.treeBranch, | ||
30 | + location: location, | ||
31 | + state: routeInformation.state, | ||
32 | + ), | ||
19 | ); | 33 | ); |
20 | - return SynchronousFuture(val); | ||
21 | } | 34 | } |
22 | 35 | ||
23 | @override | 36 | @override |
24 | - RouteInformation restoreRouteInformation(GetPage uri) { | ||
25 | - print('restore $uri'); | 37 | + RouteInformation restoreRouteInformation(GetNavConfig config) { |
38 | + print('restore $config'); | ||
26 | 39 | ||
27 | - return RouteInformation(location: uri.name); | 40 | + return RouteInformation( |
41 | + location: config.location, | ||
42 | + state: config.state, | ||
43 | + ); | ||
28 | } | 44 | } |
29 | } | 45 | } |
1 | +import 'package:flutter/widgets.dart'; | ||
2 | + | ||
3 | +import 'package:get/get.dart'; | ||
4 | + | ||
5 | +/// This config enables us to navigate directly to a sub-url | ||
6 | +class GetNavConfig extends RouteInformation { | ||
7 | + final List<GetPage> currentTreeBranch; | ||
8 | + GetPage? get currentPage => currentTreeBranch.last; | ||
9 | + | ||
10 | + GetNavConfig({ | ||
11 | + required this.currentTreeBranch, | ||
12 | + required String? location, | ||
13 | + required Object? state, | ||
14 | + }) : super( | ||
15 | + location: location, | ||
16 | + state: state, | ||
17 | + ); | ||
18 | + | ||
19 | + GetNavConfig copyWith({ | ||
20 | + List<GetPage>? currentTreeBranch, | ||
21 | + GetPage? currentPage, | ||
22 | + required String? location, | ||
23 | + required Object? state, | ||
24 | + }) { | ||
25 | + return GetNavConfig( | ||
26 | + currentTreeBranch: currentTreeBranch ?? this.currentTreeBranch, | ||
27 | + location: location ?? this.location, | ||
28 | + state: state ?? this.state, | ||
29 | + ); | ||
30 | + } | ||
31 | + | ||
32 | + @override | ||
33 | + String toString() => ''' | ||
34 | + ======GetNavConfig===== | ||
35 | + currentTreeBranch: $currentTreeBranch | ||
36 | + currentPage: $currentPage | ||
37 | + ======GetNavConfig====='''; | ||
38 | +} |
1 | import 'dart:async'; | 1 | import 'dart:async'; |
2 | 2 | ||
3 | +import 'package:flutter/foundation.dart'; | ||
3 | import 'package:flutter/material.dart'; | 4 | import 'package:flutter/material.dart'; |
4 | import 'package:get/get_navigation/src/nav2/router_outlet.dart'; | 5 | import 'package:get/get_navigation/src/nav2/router_outlet.dart'; |
5 | import '../../../get.dart'; | 6 | import '../../../get.dart'; |
6 | import '../../../get_state_manager/src/simple/list_notifier.dart'; | 7 | import '../../../get_state_manager/src/simple/list_notifier.dart'; |
7 | 8 | ||
8 | -class GetDelegate extends RouterDelegate<GetPage> | ||
9 | - with ListenableMixin, ListNotifierMixin { | ||
10 | - final List<GetPage> routes = <GetPage>[]; | 9 | +/// Enables the user to customize the intended pop behavior |
10 | +/// | ||
11 | +/// Goes to either the previous history entry or the previous page entry | ||
12 | +/// | ||
13 | +/// e.g. if the user navigates to these pages | ||
14 | +/// 1) /home | ||
15 | +/// 2) /home/products/1234 | ||
16 | +/// | ||
17 | +/// when popping on [History] mode, it will emulate a browser back button. | ||
18 | +/// | ||
19 | +/// so the new history stack will be: | ||
20 | +/// 1) /home | ||
21 | +/// | ||
22 | +/// when popping on [Page] mode, it will only remove the last part of the route | ||
23 | +/// so the new history stack will be: | ||
24 | +/// 1) /home | ||
25 | +/// 2) /home/products | ||
26 | +/// | ||
27 | +/// another pop will change the history stack to: | ||
28 | +/// 1) /home | ||
29 | +enum PopMode { | ||
30 | + History, | ||
31 | + Page, | ||
32 | +} | ||
33 | + | ||
34 | +/// Enables the user to customize the behavior when pushing multiple routes that | ||
35 | +/// shouldn't be duplicates | ||
36 | +enum PreventDuplicateHandlingMode { | ||
37 | + /// Removes the history entries until it reaches the old route | ||
38 | + PopUntilOriginalRoute, | ||
11 | 39 | ||
12 | - final pageRoutes = <GetPage, GetPageRoute>{}; | 40 | + /// Simply don't push the new route |
41 | + DoNothing, | ||
42 | +} | ||
43 | + | ||
44 | +class GetDelegate extends RouterDelegate<GetNavConfig> | ||
45 | + with ListenableMixin, ListNotifierMixin { | ||
46 | + final List<GetNavConfig> history = <GetNavConfig>[]; | ||
47 | + final PopMode backButtonPopMode; | ||
48 | + final PreventDuplicateHandlingMode preventDuplicateHandlingMode; | ||
49 | + final pageRoutes = <String, GetPageRoute>{}; | ||
13 | 50 | ||
14 | GetPage? notFoundRoute; | 51 | GetPage? notFoundRoute; |
15 | 52 | ||
16 | - final List<NavigatorObserver>? dipNavObservers; | 53 | + final List<NavigatorObserver>? navigatorObservers; |
17 | final TransitionDelegate<dynamic>? transitionDelegate; | 54 | final TransitionDelegate<dynamic>? transitionDelegate; |
55 | + final _resultCompleter = <GetNavConfig, Completer<Object?>>{}; | ||
18 | 56 | ||
19 | GlobalKey<NavigatorState> get navigatorKey => | 57 | GlobalKey<NavigatorState> get navigatorKey => |
20 | GetNavigation.getxController.key; | 58 | GetNavigation.getxController.key; |
21 | 59 | ||
22 | - GetDelegate( | ||
23 | - {this.notFoundRoute, this.dipNavObservers, this.transitionDelegate}); | 60 | + GetDelegate({ |
61 | + this.notFoundRoute, | ||
62 | + this.navigatorObservers, | ||
63 | + this.transitionDelegate, | ||
64 | + this.backButtonPopMode = PopMode.History, | ||
65 | + this.preventDuplicateHandlingMode = PreventDuplicateHandlingMode.DoNothing, | ||
66 | + }); | ||
67 | + | ||
68 | + /// Adds a new history entry and waits for the result | ||
69 | + Future<T?> pushHistory<T>( | ||
70 | + GetNavConfig config, { | ||
71 | + bool rebuildStack = true, | ||
72 | + }) { | ||
73 | + //this changes the currentConfiguration | ||
74 | + final completer = Completer<T?>(); | ||
75 | + _resultCompleter[config] = completer; | ||
76 | + _pushHistory(config); | ||
77 | + if (rebuildStack) { | ||
78 | + refresh(); | ||
79 | + } | ||
80 | + return completer.future; | ||
81 | + } | ||
82 | + | ||
83 | + void _removeHistoryEntry(GetNavConfig entry) { | ||
84 | + history.remove(entry); | ||
85 | + pageRoutes.remove(entry.location); | ||
86 | + final lastCompleter = _resultCompleter.remove(entry); | ||
87 | + lastCompleter?.complete(entry); | ||
88 | + } | ||
89 | + | ||
90 | + void _pushHistory(GetNavConfig config) { | ||
91 | + if (config.currentPage!.preventDuplicates) { | ||
92 | + if (history.any((element) => element.location == config.location)) { | ||
93 | + switch (preventDuplicateHandlingMode) { | ||
94 | + case PreventDuplicateHandlingMode.PopUntilOriginalRoute: | ||
95 | + until(config.location!, popMode: PopMode.History); | ||
96 | + return; | ||
97 | + case PreventDuplicateHandlingMode.DoNothing: | ||
98 | + default: | ||
99 | + return; | ||
100 | + } | ||
101 | + } | ||
102 | + } | ||
103 | + history.add(config); | ||
104 | + pageRoutes[config.location!] = | ||
105 | + PageRedirect(config.currentPage!, _notFound()).page(); | ||
106 | + } | ||
107 | + | ||
108 | + GetNavConfig? _popHistory() { | ||
109 | + if (!_canPopHistory()) return null; | ||
110 | + return _doPopHistory(); | ||
111 | + } | ||
112 | + | ||
113 | + GetNavConfig _doPopHistory() { | ||
114 | + final res = history.removeLast(); | ||
115 | + pageRoutes.remove(res.location); | ||
116 | + return res; | ||
117 | + } | ||
118 | + | ||
119 | + GetNavConfig? _popPage() { | ||
120 | + if (!_canPopPage()) return null; | ||
121 | + return _doPopPage(); | ||
122 | + } | ||
123 | + | ||
124 | + GetNavConfig? _pop(PopMode mode) { | ||
125 | + switch (mode) { | ||
126 | + case PopMode.History: | ||
127 | + return _popHistory(); | ||
128 | + case PopMode.Page: | ||
129 | + return _popPage(); | ||
130 | + default: | ||
131 | + return null; | ||
132 | + } | ||
133 | + } | ||
134 | + | ||
135 | + // returns the popped page | ||
136 | + GetNavConfig? _doPopPage() { | ||
137 | + final currentBranch = currentConfiguration?.currentTreeBranch; | ||
138 | + if (currentBranch != null && currentBranch.length > 1) { | ||
139 | + //remove last part only | ||
140 | + final remaining = currentBranch.take(currentBranch.length - 1); | ||
141 | + final prevHistoryEntry = | ||
142 | + history.length > 1 ? history[history.length - 2] : null; | ||
143 | + | ||
144 | + //check if current route is the same as the previous route | ||
145 | + if (prevHistoryEntry != null) { | ||
146 | + //if so, pop the entire history entry | ||
147 | + final newLocation = remaining.last.name; | ||
148 | + final prevLocation = prevHistoryEntry.location; | ||
149 | + if (newLocation == prevLocation) { | ||
150 | + //pop the entire history entry | ||
151 | + return _popHistory(); | ||
152 | + } | ||
153 | + } | ||
154 | + | ||
155 | + //create a new route with the remaining tree branch | ||
156 | + final res = _popHistory(); | ||
157 | + _pushHistory( | ||
158 | + GetNavConfig( | ||
159 | + currentTreeBranch: remaining.toList(), | ||
160 | + location: remaining.last.name, | ||
161 | + state: null, //TOOD: persist state?? | ||
162 | + ), | ||
163 | + ); | ||
164 | + return res; | ||
165 | + } else { | ||
166 | + //remove entire entry | ||
167 | + return _popHistory(); | ||
168 | + } | ||
169 | + } | ||
170 | + | ||
171 | + Future<GetNavConfig?> popHistory() { | ||
172 | + return SynchronousFuture(_popHistory()); | ||
173 | + } | ||
174 | + | ||
175 | + bool _canPopHistory() { | ||
176 | + return history.length > 1; | ||
177 | + } | ||
178 | + | ||
179 | + Future<bool> canPopHistory() { | ||
180 | + return SynchronousFuture(_canPopHistory()); | ||
181 | + } | ||
182 | + | ||
183 | + bool _canPopPage() { | ||
184 | + final currentTreeBranch = currentConfiguration?.currentTreeBranch; | ||
185 | + if (currentTreeBranch == null) return false; | ||
186 | + return currentTreeBranch.length > 1 ? true : _canPopHistory(); | ||
187 | + } | ||
188 | + | ||
189 | + Future<bool> canPopPage() { | ||
190 | + return SynchronousFuture(_canPopPage()); | ||
191 | + } | ||
24 | 192 | ||
25 | - List<GetPage> getVisiblePages() { | ||
26 | - return routes.where((r) { | 193 | + /// gets the visual pages from the current history entry |
194 | + /// | ||
195 | + /// visual pages must have the [RouterOutletContainerMiddleWare] middleware | ||
196 | + /// with `stayAt` equal to the route name of the visual page | ||
197 | + List<GetPage> getVisualPages() { | ||
198 | + final currentHistory = currentConfiguration; | ||
199 | + if (currentHistory == null) return <GetPage>[]; | ||
200 | + return currentHistory.currentTreeBranch.where((r) { | ||
27 | final mware = | 201 | final mware = |
28 | (r.middlewares ?? []).whereType<RouterOutletContainerMiddleWare>(); | 202 | (r.middlewares ?? []).whereType<RouterOutletContainerMiddleWare>(); |
29 | if (mware.length == 0) return true; | 203 | if (mware.length == 0) return true; |
@@ -31,63 +205,77 @@ class GetDelegate extends RouterDelegate<GetPage> | @@ -31,63 +205,77 @@ class GetDelegate extends RouterDelegate<GetPage> | ||
31 | }).toList(); | 205 | }).toList(); |
32 | } | 206 | } |
33 | 207 | ||
34 | - /// Called by the [Router] at startup with the structure that the | ||
35 | - /// [RouteInformationParser] obtained from parsing the initial route. | ||
36 | @override | 208 | @override |
37 | Widget build(BuildContext context) { | 209 | Widget build(BuildContext context) { |
38 | - final pages = getVisiblePages(); | 210 | + final pages = getVisualPages(); |
211 | + final extraObservers = navigatorObservers; | ||
39 | return Navigator( | 212 | return Navigator( |
40 | key: navigatorKey, | 213 | key: navigatorKey, |
41 | - onPopPage: _onPopPage, | 214 | + onPopPage: _onPopVisualRoute, |
42 | pages: pages, | 215 | pages: pages, |
43 | observers: [ | 216 | observers: [ |
44 | GetObserver(), | 217 | GetObserver(), |
218 | + if (extraObservers != null) ...extraObservers, | ||
45 | ], | 219 | ], |
46 | transitionDelegate: | 220 | transitionDelegate: |
47 | transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(), | 221 | transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(), |
48 | ); | 222 | ); |
49 | } | 223 | } |
50 | 224 | ||
51 | - final _resultCompleter = <GetPage, Completer<Object?>>{}; | ||
52 | - | ||
53 | @override | 225 | @override |
54 | - Future<void> setInitialRoutePath(GetPage configuration) async { | ||
55 | - await pushRoute(configuration); | 226 | + Future<void> setInitialRoutePath(GetNavConfig configuration) async { |
227 | + history.clear(); | ||
228 | + pageRoutes.clear(); | ||
229 | + _resultCompleter.clear(); | ||
230 | + await pushHistory(configuration); | ||
56 | } | 231 | } |
57 | 232 | ||
58 | @override | 233 | @override |
59 | - Future<void> setNewRoutePath(GetPage configuration) { | ||
60 | - routes.clear(); | ||
61 | - pageRoutes.clear(); | ||
62 | - return pushRoute(configuration); | 234 | + Future<void> setNewRoutePath(GetNavConfig configuration) async { |
235 | + await pushHistory(configuration); | ||
63 | } | 236 | } |
64 | 237 | ||
65 | - /// Called by the [Router] when it detects a route information may have | ||
66 | - /// changed as a result of rebuild. | ||
67 | @override | 238 | @override |
68 | - GetPage get currentConfiguration { | ||
69 | - final route = routes.last; | 239 | + GetNavConfig? get currentConfiguration { |
240 | + if (history.isEmpty) return null; | ||
241 | + final route = history.last; | ||
70 | return route; | 242 | return route; |
71 | } | 243 | } |
72 | 244 | ||
73 | - GetPageRoute? get currentRoute => pageRoutes[currentConfiguration]; | 245 | + GetPageRoute? get currentRoute { |
246 | + final curPage = currentConfiguration?.currentPage; | ||
247 | + return curPage == null ? null : pageRoutes[curPage]; | ||
248 | + } | ||
74 | 249 | ||
75 | - Future<T?> toNamed<T>(String route) { | ||
76 | - final page = Get.routeTree.matchRoute(route); | ||
77 | - if (page.route != null) { | ||
78 | - return pushRoute(page.route!.copy(name: route)); | ||
79 | - } else { | ||
80 | - return pushRoute(_notFound()); | ||
81 | - } | 250 | + Future<T?> toNamed<T>(String fullRoute) { |
251 | + final decoder = Get.routeTree.matchRoute(fullRoute); | ||
252 | + return pushHistory<T>( | ||
253 | + GetNavConfig( | ||
254 | + currentTreeBranch: decoder.treeBranch, | ||
255 | + location: fullRoute, | ||
256 | + state: null, //TODO: persist state? | ||
257 | + ), | ||
258 | + ); | ||
82 | } | 259 | } |
83 | 260 | ||
84 | - Future<T?> offUntil<T>(String route) { | ||
85 | - final page = Get.routeTree.matchRoute(route); | ||
86 | - if (page.route != null) { | ||
87 | - return pushRoute(page.route!.copy(name: route), removeUntil: true); | ||
88 | - } else { | ||
89 | - return pushRoute(_notFound()); | 261 | + /// Removes routes according to [PopMode] |
262 | + /// until it reaches the specifc [fullRoute], | ||
263 | + /// DOES NOT remove the [fullRoute] | ||
264 | + void until( | ||
265 | + String fullRoute, { | ||
266 | + PopMode popMode = PopMode.History, | ||
267 | + }) { | ||
268 | + // remove history or page entries until you meet route | ||
269 | + final currentEntry = currentConfiguration; | ||
270 | + var iterator = currentEntry; | ||
271 | + while (history.length > 0 && | ||
272 | + iterator != null && | ||
273 | + iterator.location != fullRoute) { | ||
274 | + _pop(popMode); | ||
275 | + // replace iterator | ||
276 | + iterator = currentConfiguration; | ||
90 | } | 277 | } |
278 | + refresh(); | ||
91 | } | 279 | } |
92 | 280 | ||
93 | GetPage _notFound() { | 281 | GetPage _notFound() { |
@@ -99,33 +287,6 @@ class GetDelegate extends RouterDelegate<GetPage> | @@ -99,33 +287,6 @@ class GetDelegate extends RouterDelegate<GetPage> | ||
99 | ); | 287 | ); |
100 | } | 288 | } |
101 | 289 | ||
102 | - Future<T?> pushRoute<T>( | ||
103 | - GetPage page, { | ||
104 | - bool removeUntil = false, | ||
105 | - bool replaceCurrent = false, | ||
106 | - bool rebuildStack = true, | ||
107 | - }) { | ||
108 | - final completer = Completer<T?>(); | ||
109 | - _resultCompleter[page] = completer; | ||
110 | - | ||
111 | - page = page.copy(unknownRoute: _notFound()); | ||
112 | - assert(!(removeUntil && replaceCurrent), | ||
113 | - 'Only removeUntil or replaceCurrent should by true!'); | ||
114 | - if (removeUntil) { | ||
115 | - routes.clear(); | ||
116 | - pageRoutes.clear(); | ||
117 | - } else if (replaceCurrent && routes.isNotEmpty) { | ||
118 | - final lastPage = routes.removeLast(); | ||
119 | - pageRoutes.remove(lastPage); | ||
120 | - } | ||
121 | - addPage(page); | ||
122 | - if (rebuildStack) { | ||
123 | - refresh(); | ||
124 | - } | ||
125 | - //emulate the old push with result | ||
126 | - return completer.future; | ||
127 | - } | ||
128 | - | ||
129 | Future<bool> handlePopupRoutes({ | 290 | Future<bool> handlePopupRoutes({ |
130 | Object? result, | 291 | Object? result, |
131 | }) async { | 292 | }) async { |
@@ -143,79 +304,38 @@ class GetDelegate extends RouterDelegate<GetPage> | @@ -143,79 +304,38 @@ class GetDelegate extends RouterDelegate<GetPage> | ||
143 | @override | 304 | @override |
144 | Future<bool> popRoute({ | 305 | Future<bool> popRoute({ |
145 | Object? result, | 306 | Object? result, |
307 | + PopMode popMode = PopMode.History, | ||
146 | }) async { | 308 | }) async { |
309 | + //Returning false will cause the entire app to be popped. | ||
147 | final wasPopup = await handlePopupRoutes(result: result); | 310 | final wasPopup = await handlePopupRoutes(result: result); |
148 | if (wasPopup) return true; | 311 | if (wasPopup) return true; |
149 | - | ||
150 | - if (canPop()) { | 312 | + final _popped = _pop(popMode); |
313 | + refresh(); | ||
314 | + if (_popped != null) { | ||
151 | //emulate the old pop with result | 315 | //emulate the old pop with result |
152 | - final lastRoute = routes.last; | ||
153 | - final lastCompleter = _resultCompleter.remove(lastRoute); | 316 | + final lastCompleter = _resultCompleter.remove(_popped); |
154 | lastCompleter?.complete(result); | 317 | lastCompleter?.complete(result); |
155 | - //route to be removed | ||
156 | - removePage(lastRoute); | ||
157 | return Future.value(true); | 318 | return Future.value(true); |
158 | } | 319 | } |
159 | return Future.value(false); | 320 | return Future.value(false); |
160 | } | 321 | } |
161 | 322 | ||
162 | - bool canPop() { | ||
163 | - return routes.length > 1; | ||
164 | - } | ||
165 | - | ||
166 | - bool _onPopPage(Route<dynamic> route, dynamic result) { | 323 | + bool _onPopVisualRoute(Route<dynamic> route, dynamic result) { |
167 | final didPop = route.didPop(result); | 324 | final didPop = route.didPop(result); |
168 | if (!didPop) { | 325 | if (!didPop) { |
169 | return false; | 326 | return false; |
170 | } | 327 | } |
171 | final settings = route.settings; | 328 | final settings = route.settings; |
172 | if (settings is GetPage) { | 329 | if (settings is GetPage) { |
173 | - removePage(settings); | 330 | + final config = history.cast<GetNavConfig?>().firstWhere( |
331 | + (element) => element?.currentPage == settings, | ||
332 | + orElse: () => null, | ||
333 | + ); | ||
334 | + if (config != null) { | ||
335 | + _removeHistoryEntry(config); | ||
336 | + } | ||
174 | } | 337 | } |
175 | refresh(); | 338 | refresh(); |
176 | return true; | 339 | return true; |
177 | } | 340 | } |
178 | - | ||
179 | - void removePage(GetPage page) { | ||
180 | - final isLast = routes.last == page; | ||
181 | - //check if it's last | ||
182 | - routes.remove(page); | ||
183 | - final oldPageRoute = pageRoutes.remove(page); | ||
184 | - if (isLast && oldPageRoute != null) { | ||
185 | - _currentRoutePopped(oldPageRoute); | ||
186 | - final newPageRoute = pageRoutes[routes.last]; | ||
187 | - if (newPageRoute != null) _currentRouteChanged(newPageRoute); | ||
188 | - } | ||
189 | - refresh(); | ||
190 | - } | ||
191 | - | ||
192 | - void addPage(GetPage route) { | ||
193 | - routes.add( | ||
194 | - route, | ||
195 | - ); | ||
196 | - final pageRoute = | ||
197 | - pageRoutes[route] = PageRedirect(route, _notFound()).page(); | ||
198 | - _currentRouteChanged(pageRoute); | ||
199 | - refresh(); | ||
200 | - } | ||
201 | - | ||
202 | - void addRoutes(List<GetPage> pages) { | ||
203 | - routes.addAll(pages); | ||
204 | - for (var item in pages) { | ||
205 | - pageRoutes[item] = PageRedirect(item, _notFound()).page(); | ||
206 | - } | ||
207 | - final pageRoute = pageRoutes[routes.last]; | ||
208 | - if (pageRoute != null) _currentRouteChanged(pageRoute); | ||
209 | - refresh(); | ||
210 | - } | ||
211 | - | ||
212 | - void _currentRoutePopped(GetPageRoute route) { | ||
213 | - route.dispose(); | ||
214 | - } | ||
215 | - | ||
216 | - void _currentRouteChanged(GetPageRoute route) { | ||
217 | - //is this method useful ? | ||
218 | - //transition? -> in router outlet ?? | ||
219 | - //buildPage? -> in router outlet | ||
220 | - } | ||
221 | } | 341 | } |
@@ -20,16 +20,21 @@ class RouterOutlet<TDelegate extends RouterDelegate<T>, T extends Object> | @@ -20,16 +20,21 @@ class RouterOutlet<TDelegate extends RouterDelegate<T>, T extends Object> | ||
20 | 20 | ||
21 | RouterOutlet({ | 21 | RouterOutlet({ |
22 | TDelegate? delegate, | 22 | TDelegate? delegate, |
23 | - required List<T> Function(TDelegate routerDelegate) currentNavStack, | ||
24 | - required List<T> Function(List<T> currentNavStack) pickPages, | ||
25 | - required Widget Function(BuildContext context, TDelegate, T? page) | 23 | + required List<RouteSettings> Function(T currentNavStack) pickPages, |
24 | + required Widget Function( | ||
25 | + BuildContext context, | ||
26 | + TDelegate, | ||
27 | + RouteSettings? page, | ||
28 | + ) | ||
26 | pageBuilder, | 29 | pageBuilder, |
27 | }) : this.builder( | 30 | }) : this.builder( |
28 | builder: (context, rDelegate, currentConfig) { | 31 | builder: (context, rDelegate, currentConfig) { |
29 | - final currentStack = currentNavStack(rDelegate); | ||
30 | - final picked = pickPages(currentStack); | ||
31 | - if (picked.length == 0) | 32 | + final picked = currentConfig == null |
33 | + ? <RouteSettings>[] | ||
34 | + : pickPages(currentConfig); | ||
35 | + if (picked.length == 0) { | ||
32 | return pageBuilder(context, rDelegate, null); | 36 | return pageBuilder(context, rDelegate, null); |
37 | + } | ||
33 | return pageBuilder(context, rDelegate, picked.last); | 38 | return pageBuilder(context, rDelegate, picked.last); |
34 | }, | 39 | }, |
35 | delegate: delegate, | 40 | delegate: delegate, |
@@ -68,12 +73,12 @@ class _RouterOutletState<TDelegate extends RouterDelegate<T>, T extends Object> | @@ -68,12 +73,12 @@ class _RouterOutletState<TDelegate extends RouterDelegate<T>, T extends Object> | ||
68 | } | 73 | } |
69 | } | 74 | } |
70 | 75 | ||
71 | -class GetRouterOutlet extends RouterOutlet<GetDelegate, GetPage> { | 76 | +class GetRouterOutlet extends RouterOutlet<GetDelegate, GetNavConfig> { |
72 | GetRouterOutlet.builder({ | 77 | GetRouterOutlet.builder({ |
73 | required Widget Function( | 78 | required Widget Function( |
74 | BuildContext context, | 79 | BuildContext context, |
75 | GetDelegate delegate, | 80 | GetDelegate delegate, |
76 | - GetPage? currentRoute, | 81 | + GetNavConfig? currentRoute, |
77 | ) | 82 | ) |
78 | builder, | 83 | builder, |
79 | GetDelegate? routerDelegate, | 84 | GetDelegate? routerDelegate, |
@@ -84,10 +89,10 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, GetPage> { | @@ -84,10 +89,10 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, GetPage> { | ||
84 | 89 | ||
85 | GetRouterOutlet({ | 90 | GetRouterOutlet({ |
86 | Widget Function(GetDelegate delegate)? emptyPage, | 91 | Widget Function(GetDelegate delegate)? emptyPage, |
87 | - required List<GetPage> Function(List<GetPage> currentNavStack) pickPages, | 92 | + required List<GetPage> Function(GetNavConfig currentNavStack) pickPages, |
88 | }) : super( | 93 | }) : super( |
89 | pageBuilder: (context, rDelegate, page) { | 94 | pageBuilder: (context, rDelegate, page) { |
90 | - final pageRoute = rDelegate.pageRoutes[page]; | 95 | + final pageRoute = rDelegate.pageRoutes[page?.name]; |
91 | if (pageRoute != null) { | 96 | if (pageRoute != null) { |
92 | //TODO: transitions go here ! | 97 | //TODO: transitions go here ! |
93 | return pageRoute.buildPage( | 98 | return pageRoute.buildPage( |
@@ -102,21 +107,18 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, GetPage> { | @@ -102,21 +107,18 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, GetPage> { | ||
102 | rDelegate.notFoundRoute?.page()) ?? | 107 | rDelegate.notFoundRoute?.page()) ?? |
103 | SizedBox.shrink(); | 108 | SizedBox.shrink(); |
104 | }, | 109 | }, |
105 | - currentNavStack: (routerDelegate) => routerDelegate.routes, | ||
106 | pickPages: pickPages, | 110 | pickPages: pickPages, |
107 | delegate: Get.getDelegate(), | 111 | delegate: Get.getDelegate(), |
108 | ); | 112 | ); |
109 | } | 113 | } |
110 | 114 | ||
115 | +/// A marker outlet to identify which pages are visual | ||
116 | +/// (handled by the navigator) and which are logical | ||
117 | +/// (handled by the delegate) | ||
111 | class RouterOutletContainerMiddleWare extends GetMiddleware { | 118 | class RouterOutletContainerMiddleWare extends GetMiddleware { |
112 | final String stayAt; | 119 | final String stayAt; |
113 | 120 | ||
114 | RouterOutletContainerMiddleWare(this.stayAt); | 121 | RouterOutletContainerMiddleWare(this.stayAt); |
115 | - // @override | ||
116 | - // RouteSettings? redirect(String? route) { | ||
117 | - // print('RouterOutletContainerMiddleWare: Redirect called ($route)'); | ||
118 | - // return null; | ||
119 | - // } | ||
120 | } | 122 | } |
121 | 123 | ||
122 | extension PagesListExt on List<GetPage> { | 124 | extension PagesListExt on List<GetPage> { |
1 | -import '../../../get_core/src/get_main.dart'; | ||
2 | import '../../get_navigation.dart'; | 1 | import '../../get_navigation.dart'; |
3 | import '../routes/get_route.dart'; | 2 | import '../routes/get_route.dart'; |
4 | 3 | ||
5 | class RouteDecoder { | 4 | class RouteDecoder { |
6 | - final GetPage? route; | ||
7 | - final Map<String, String?> parameters; | ||
8 | - const RouteDecoder(this.route, this.parameters); | 5 | + final List<GetPage> treeBranch; |
6 | + GetPage? get route => treeBranch.isEmpty ? null : treeBranch.last; | ||
7 | + final Map<String, String> parameters; | ||
8 | + const RouteDecoder( | ||
9 | + this.treeBranch, | ||
10 | + this.parameters, | ||
11 | + ); | ||
9 | } | 12 | } |
10 | 13 | ||
11 | class ParseRouteTree { | 14 | class ParseRouteTree { |
@@ -17,20 +20,53 @@ class ParseRouteTree { | @@ -17,20 +20,53 @@ class ParseRouteTree { | ||
17 | 20 | ||
18 | RouteDecoder matchRoute(String name) { | 21 | RouteDecoder matchRoute(String name) { |
19 | final uri = Uri.parse(name); | 22 | final uri = Uri.parse(name); |
20 | - final route = _findRoute(uri.path); | ||
21 | - final params = Map<String, String?>.from(uri.queryParameters); | ||
22 | - if (route != null) { | ||
23 | - final parsedParams = _parseParams(name, route.path); | 23 | + // /home/profile/123 => home,profile,123 => /,/home,/home/profile,/home/profile/123 |
24 | + final split = uri.path.split('/').where((element) => element.isNotEmpty); | ||
25 | + var curPath = '/'; | ||
26 | + final cumulativePaths = <String>[ | ||
27 | + '/', | ||
28 | + ]; | ||
29 | + for (var item in split) { | ||
30 | + if (curPath.endsWith('/')) { | ||
31 | + curPath += '$item'; | ||
32 | + } else { | ||
33 | + curPath += '/$item'; | ||
34 | + } | ||
35 | + cumulativePaths.add(curPath); | ||
36 | + } | ||
37 | + | ||
38 | + final treeBranch = cumulativePaths | ||
39 | + .map((e) => MapEntry(e, _findRoute(e))) | ||
40 | + .where((element) => element.value != null) | ||
41 | + .toList(); | ||
42 | + | ||
43 | + final params = Map<String, String>.from(uri.queryParameters); | ||
44 | + if (treeBranch.isNotEmpty) { | ||
45 | + //route is found, do further parsing to get nested query params | ||
46 | + final lastRoute = treeBranch.last; | ||
47 | + final parsedParams = _parseParams(name, lastRoute.value!.path); | ||
24 | if (parsedParams.isNotEmpty) { | 48 | if (parsedParams.isNotEmpty) { |
25 | params.addAll(parsedParams); | 49 | params.addAll(parsedParams); |
26 | } | 50 | } |
51 | + //copy parameters to all pages. | ||
52 | + final mappedTreeBranch = treeBranch | ||
53 | + .map( | ||
54 | + (e) => e.value!.copy( | ||
55 | + parameter: params, | ||
56 | + ), | ||
57 | + ) | ||
58 | + .toList(); | ||
59 | + return RouteDecoder( | ||
60 | + mappedTreeBranch, | ||
61 | + params, | ||
62 | + ); | ||
27 | } | 63 | } |
28 | - // This logger sends confusing messages | ||
29 | - // else { | ||
30 | - // // Get.log('Route "${uri.path}" not found'); | ||
31 | - // } | ||
32 | 64 | ||
33 | - return RouteDecoder(route, params); | 65 | + //route not found |
66 | + return RouteDecoder( | ||
67 | + treeBranch.map((e) => e.value!).toList(), | ||
68 | + params, | ||
69 | + ); | ||
34 | } | 70 | } |
35 | 71 | ||
36 | void addRoutes(List<GetPage> getPages) { | 72 | void addRoutes(List<GetPage> getPages) { |
@@ -88,6 +124,7 @@ class ParseRouteTree { | @@ -88,6 +124,7 @@ class ParseRouteTree { | ||
88 | opaque: origin.opaque, | 124 | opaque: origin.opaque, |
89 | parameter: origin.parameter, | 125 | parameter: origin.parameter, |
90 | popGesture: origin.popGesture, | 126 | popGesture: origin.popGesture, |
127 | + | ||
91 | // settings: origin.settings, | 128 | // settings: origin.settings, |
92 | transitionDuration: origin.transitionDuration, | 129 | transitionDuration: origin.transitionDuration, |
93 | middlewares: middlewares, | 130 | middlewares: middlewares, |
@@ -99,8 +136,8 @@ class ParseRouteTree { | @@ -99,8 +136,8 @@ class ParseRouteTree { | ||
99 | ); | 136 | ); |
100 | } | 137 | } |
101 | 138 | ||
102 | - Map<String, String?> _parseParams(String path, PathDecoded routePath) { | ||
103 | - final params = <String, String?>{}; | 139 | + Map<String, String> _parseParams(String path, PathDecoded routePath) { |
140 | + final params = <String, String>{}; | ||
104 | var idx = path.indexOf('?'); | 141 | var idx = path.indexOf('?'); |
105 | if (idx > -1) { | 142 | if (idx > -1) { |
106 | path = path.substring(0, idx); | 143 | path = path.substring(0, idx); |
@@ -41,7 +41,7 @@ class GetPage<T> extends Page<T> { | @@ -41,7 +41,7 @@ class GetPage<T> extends Page<T> { | ||
41 | final CustomTransition? customTransition; | 41 | final CustomTransition? customTransition; |
42 | final Duration? transitionDuration; | 42 | final Duration? transitionDuration; |
43 | final bool fullscreenDialog; | 43 | final bool fullscreenDialog; |
44 | - | 44 | + final bool preventDuplicates; |
45 | // @override | 45 | // @override |
46 | // final LocalKey? key; | 46 | // final LocalKey? key; |
47 | 47 | ||
@@ -79,6 +79,7 @@ class GetPage<T> extends Page<T> { | @@ -79,6 +79,7 @@ class GetPage<T> extends Page<T> { | ||
79 | this.children, | 79 | this.children, |
80 | this.middlewares, | 80 | this.middlewares, |
81 | this.unknownRoute, | 81 | this.unknownRoute, |
82 | + this.preventDuplicates = false, | ||
82 | }) : path = _nameToRegex(name), | 83 | }) : path = _nameToRegex(name), |
83 | super( | 84 | super( |
84 | key: ValueKey(name), | 85 | key: ValueKey(name), |
@@ -128,8 +129,10 @@ class GetPage<T> extends Page<T> { | @@ -128,8 +129,10 @@ class GetPage<T> extends Page<T> { | ||
128 | List<GetPage>? children, | 129 | List<GetPage>? children, |
129 | GetPage? unknownRoute, | 130 | GetPage? unknownRoute, |
130 | List<GetMiddleware>? middlewares, | 131 | List<GetMiddleware>? middlewares, |
132 | + bool? preventDuplicates, | ||
131 | }) { | 133 | }) { |
132 | return GetPage( | 134 | return GetPage( |
135 | + preventDuplicates: preventDuplicates ?? this.preventDuplicates, | ||
133 | name: name ?? this.name, | 136 | name: name ?? this.name, |
134 | page: page ?? this.page, | 137 | page: page ?? this.page, |
135 | popGesture: popGesture ?? this.popGesture, | 138 | popGesture: popGesture ?? this.popGesture, |
@@ -145,7 +148,6 @@ class GetPage<T> extends Page<T> { | @@ -145,7 +148,6 @@ class GetPage<T> extends Page<T> { | ||
145 | customTransition: customTransition ?? this.customTransition, | 148 | customTransition: customTransition ?? this.customTransition, |
146 | transitionDuration: transitionDuration ?? this.transitionDuration, | 149 | transitionDuration: transitionDuration ?? this.transitionDuration, |
147 | fullscreenDialog: fullscreenDialog ?? this.fullscreenDialog, | 150 | fullscreenDialog: fullscreenDialog ?? this.fullscreenDialog, |
148 | - // settings: settings ?? this.settings, | ||
149 | children: children ?? this.children, | 151 | children: children ?? this.children, |
150 | unknownRoute: unknownRoute ?? this.unknownRoute, | 152 | unknownRoute: unknownRoute ?? this.unknownRoute, |
151 | middlewares: middlewares ?? this.middlewares, | 153 | middlewares: middlewares ?? this.middlewares, |
@@ -171,6 +173,8 @@ class GetPage<T> extends Page<T> { | @@ -171,6 +173,8 @@ class GetPage<T> extends Page<T> { | ||
171 | other.page.runtimeType == page.runtimeType && | 173 | other.page.runtimeType == page.runtimeType && |
172 | other.popGesture == popGesture && | 174 | other.popGesture == popGesture && |
173 | // mapEquals(other.parameter, parameter) && | 175 | // mapEquals(other.parameter, parameter) && |
176 | + | ||
177 | + other.preventDuplicates == preventDuplicates && | ||
174 | other.title == title && | 178 | other.title == title && |
175 | other.transition == transition && | 179 | other.transition == transition && |
176 | other.curve == curve && | 180 | other.curve == curve && |
@@ -194,6 +198,7 @@ class GetPage<T> extends Page<T> { | @@ -194,6 +198,7 @@ class GetPage<T> extends Page<T> { | ||
194 | return //page.hashCode ^ | 198 | return //page.hashCode ^ |
195 | popGesture.hashCode ^ | 199 | popGesture.hashCode ^ |
196 | // parameter.hashCode ^ | 200 | // parameter.hashCode ^ |
201 | + preventDuplicates.hashCode ^ | ||
197 | title.hashCode ^ | 202 | title.hashCode ^ |
198 | transition.hashCode ^ | 203 | transition.hashCode ^ |
199 | curve.hashCode ^ | 204 | curve.hashCode ^ |
-
Please register or login to post a comment