Committed by
GitHub
Merge pull request #2087 from Bdaya-Dev/master
Minor improvements with small breaking changes
Showing
16 changed files
with
539 additions
and
439 deletions
| @@ -75,9 +75,7 @@ class HomeView extends GetView<HomeController> { | @@ -75,9 +75,7 @@ class HomeView extends GetView<HomeController> { | ||
| 75 | shape: StadiumBorder(), | 75 | shape: StadiumBorder(), | 
| 76 | ), | 76 | ), | 
| 77 | onPressed: () async { | 77 | onPressed: () async { | 
| 78 | - final data = | ||
| 79 | - await Get.rootDelegate.toNamed('/home/country'); | ||
| 80 | - print('DATA: $data'); | 78 | + await Get.rootDelegate.toNamed('/home/country'); | 
| 81 | }, | 79 | }, | 
| 82 | child: Text( | 80 | child: Text( | 
| 83 | 'fetch_country'.tr, | 81 | 'fetch_country'.tr, | 
| 1 | +import 'dart:async'; | ||
| 2 | + | ||
| 3 | +import 'package:get/get.dart'; | ||
| 4 | +import 'package:async/async.dart'; | ||
| 5 | + | ||
| 6 | +class SplashService extends GetxService { | ||
| 7 | + final welcomeStr = ['GetX', 'Rules!']; | ||
| 8 | + final activeStr = 0.obs; | ||
| 9 | + | ||
| 10 | + final memo = AsyncMemoizer<void>(); | ||
| 11 | + Future<void> init() { | ||
| 12 | + return memo.runOnce(_initFunction); | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + void _changeActiveString() { | ||
| 16 | + activeStr.value = (activeStr.value + 1) % welcomeStr.length; | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + Future<void> _initFunction() async { | ||
| 20 | + final t = Timer.periodic( | ||
| 21 | + Duration(milliseconds: 500), | ||
| 22 | + (t) => _changeActiveString(), | ||
| 23 | + ); | ||
| 24 | + //simulate some long running operation | ||
| 25 | + await Future.delayed(Duration(seconds: 5)); | ||
| 26 | + //cancel the timer once we are done | ||
| 27 | + t.cancel(); | ||
| 28 | + } | ||
| 29 | +} | 
| 1 | +import 'package:flutter/material.dart'; | ||
| 2 | + | ||
| 3 | +import 'package:get/get.dart'; | ||
| 4 | + | ||
| 5 | +import '../controllers/splash_service.dart'; | ||
| 6 | + | ||
| 7 | +class SplashView extends GetView<SplashService> { | ||
| 8 | + @override | ||
| 9 | + Widget build(BuildContext context) { | ||
| 10 | + return Scaffold( | ||
| 11 | + body: Center( | ||
| 12 | + child: Column( | ||
| 13 | + mainAxisSize: MainAxisSize.min, | ||
| 14 | + children: [ | ||
| 15 | + Obx( | ||
| 16 | + () => Text( | ||
| 17 | + controller.welcomeStr[controller.activeStr.value], | ||
| 18 | + style: TextStyle(fontSize: 20), | ||
| 19 | + ), | ||
| 20 | + ), | ||
| 21 | + CircularProgressIndicator(), | ||
| 22 | + ], | ||
| 23 | + ), | ||
| 24 | + ), | ||
| 25 | + ); | ||
| 26 | + } | ||
| 27 | +} | 
| 1 | +import 'package:example_nav2/app/modules/splash/controllers/splash_service.dart'; | ||
| 2 | +import 'package:example_nav2/app/modules/splash/views/splash_view.dart'; | ||
| 1 | import 'package:flutter/material.dart'; | 3 | import 'package:flutter/material.dart'; | 
| 2 | import 'package:get/get.dart'; | 4 | import 'package:get/get.dart'; | 
| 3 | 5 | ||
| @@ -10,10 +12,23 @@ void main() { | @@ -10,10 +12,23 @@ void main() { | ||
| 10 | title: "Application", | 12 | title: "Application", | 
| 11 | initialBinding: BindingsBuilder( | 13 | initialBinding: BindingsBuilder( | 
| 12 | () { | 14 | () { | 
| 15 | + Get.put(SplashService()); | ||
| 13 | Get.put(AuthService()); | 16 | Get.put(AuthService()); | 
| 14 | }, | 17 | }, | 
| 15 | ), | 18 | ), | 
| 16 | getPages: AppPages.routes, | 19 | getPages: AppPages.routes, | 
| 20 | + builder: (context, child) { | ||
| 21 | + return FutureBuilder<void>( | ||
| 22 | + key: ValueKey('initFuture'), | ||
| 23 | + future: Get.find<SplashService>().init(), | ||
| 24 | + builder: (context, snapshot) { | ||
| 25 | + if (snapshot.connectionState == ConnectionState.done) { | ||
| 26 | + return child ?? SizedBox.shrink(); | ||
| 27 | + } | ||
| 28 | + return SplashView(); | ||
| 29 | + }, | ||
| 30 | + ); | ||
| 31 | + }, | ||
| 17 | // routeInformationParser: GetInformationParser( | 32 | // routeInformationParser: GetInformationParser( | 
| 18 | // // initialRoute: Routes.HOME, | 33 | // // initialRoute: Routes.HOME, | 
| 19 | // ), | 34 | // ), | 
| @@ -2,8 +2,6 @@ | @@ -2,8 +2,6 @@ | ||
| 2 | // Generated file. Do not edit. | 2 | // Generated file. Do not edit. | 
| 3 | // | 3 | // | 
| 4 | 4 | ||
| 5 | -// clang-format off | ||
| 6 | - | ||
| 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ | 5 | #ifndef GENERATED_PLUGIN_REGISTRANT_ | 
| 8 | #define GENERATED_PLUGIN_REGISTRANT_ | 6 | #define GENERATED_PLUGIN_REGISTRANT_ | 
| 9 | 7 | 
| @@ -162,29 +162,24 @@ class GetInstance { | @@ -162,29 +162,24 @@ class GetInstance { | ||
| 162 | }) { | 162 | }) { | 
| 163 | final key = _getKey(S, name); | 163 | final key = _getKey(S, name); | 
| 164 | 164 | ||
| 165 | + _InstanceBuilderFactory<S>? dep; | ||
| 165 | if (_singl.containsKey(key)) { | 166 | if (_singl.containsKey(key)) { | 
| 166 | - final dep = _singl[key]; | ||
| 167 | - if (dep != null && dep.isDirty) { | ||
| 168 | - _singl[key] = _InstanceBuilderFactory<S>( | ||
| 169 | - isSingleton, | ||
| 170 | - builder, | ||
| 171 | - permanent, | ||
| 172 | - false, | ||
| 173 | - fenix, | ||
| 174 | - name, | ||
| 175 | - lateRemove: dep as _InstanceBuilderFactory<S>, | ||
| 176 | - ); | 167 | + final _dep = _singl[key]; | 
| 168 | + if (_dep == null || !_dep.isDirty) { | ||
| 169 | + return; | ||
| 170 | + } else { | ||
| 171 | + dep = _dep as _InstanceBuilderFactory<S>; | ||
| 177 | } | 172 | } | 
| 178 | - } else { | ||
| 179 | - _singl[key] = _InstanceBuilderFactory<S>( | ||
| 180 | - isSingleton, | ||
| 181 | - builder, | ||
| 182 | - permanent, | ||
| 183 | - false, | ||
| 184 | - fenix, | ||
| 185 | - name, | ||
| 186 | - ); | ||
| 187 | } | 173 | } | 
| 174 | + _singl[key] = _InstanceBuilderFactory<S>( | ||
| 175 | + isSingleton: isSingleton, | ||
| 176 | + builderFunc: builder, | ||
| 177 | + permanent: permanent, | ||
| 178 | + isInit: false, | ||
| 179 | + fenix: fenix, | ||
| 180 | + tag: name, | ||
| 181 | + lateRemove: dep, | ||
| 182 | + ); | ||
| 188 | } | 183 | } | 
| 189 | 184 | ||
| 190 | /// Initializes the dependencies for a Class Instance [S] (or tag), | 185 | /// Initializes the dependencies for a Class Instance [S] (or tag), | 
| @@ -519,14 +514,14 @@ class _InstanceBuilderFactory<S> { | @@ -519,14 +514,14 @@ class _InstanceBuilderFactory<S> { | ||
| 519 | 514 | ||
| 520 | String? tag; | 515 | String? tag; | 
| 521 | 516 | ||
| 522 | - _InstanceBuilderFactory( | ||
| 523 | - this.isSingleton, | ||
| 524 | - this.builderFunc, | ||
| 525 | - this.permanent, | ||
| 526 | - this.isInit, | ||
| 527 | - this.fenix, | ||
| 528 | - this.tag, { | ||
| 529 | - this.lateRemove, | 517 | + _InstanceBuilderFactory({ | 
| 518 | + required this.isSingleton, | ||
| 519 | + required this.builderFunc, | ||
| 520 | + required this.permanent, | ||
| 521 | + required this.isInit, | ||
| 522 | + required this.fenix, | ||
| 523 | + required this.tag, | ||
| 524 | + required this.lateRemove, | ||
| 530 | }); | 525 | }); | 
| 531 | 526 | ||
| 532 | void _showInitLog() { | 527 | void _showInitLog() { | 
| @@ -4,6 +4,7 @@ export 'src/bottomsheet/bottomsheet.dart'; | @@ -4,6 +4,7 @@ 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'; | 6 | export 'src/nav2/get_nav_config.dart'; | 
| 7 | +export 'src/nav2/get_navigator.dart'; | ||
| 7 | export 'src/nav2/get_router_delegate.dart'; | 8 | export 'src/nav2/get_router_delegate.dart'; | 
| 8 | export 'src/nav2/router_outlet.dart'; | 9 | export 'src/nav2/router_outlet.dart'; | 
| 9 | export 'src/root/get_cupertino_app.dart'; | 10 | export 'src/root/get_cupertino_app.dart'; | 
| @@ -56,5 +56,5 @@ class GetNavConfig extends RouteInformation { | @@ -56,5 +56,5 @@ class GetNavConfig extends RouteInformation { | ||
| 56 | 56 | ||
| 57 | @override | 57 | @override | 
| 58 | String toString() => ''' | 58 | String toString() => ''' | 
| 59 | -======GetNavConfig=====\ncurrentTreeBranch: $currentTreeBranch\ncurrentPage: $currentPage\n======GetNavConfig====='''; | 59 | +======GetNavConfig=====\nlocation: $location\ncurrentTreeBranch: $currentTreeBranch\n======GetNavConfig====='''; | 
| 60 | } | 60 | } | 
| 1 | +import 'package:flutter/widgets.dart'; | ||
| 2 | +import '../routes/default_route.dart'; | ||
| 3 | +import '../routes/get_route.dart'; | ||
| 4 | + | ||
| 5 | +class GetNavigator extends Navigator { | ||
| 6 | + GetNavigator.onGenerateRoute({ | ||
| 7 | + GlobalKey<NavigatorState>? key, | ||
| 8 | + bool Function(Route<dynamic>, dynamic)? onPopPage, | ||
| 9 | + required List<GetPage> pages, | ||
| 10 | + List<NavigatorObserver>? observers, | ||
| 11 | + bool reportsRouteUpdateToEngine = false, | ||
| 12 | + TransitionDelegate? transitionDelegate, | ||
| 13 | + String? initialRoute, | ||
| 14 | + }) : super( | ||
| 15 | + //keys should be optional | ||
| 16 | + key: key, | ||
| 17 | + initialRoute: initialRoute, | ||
| 18 | + onPopPage: onPopPage ?? | ||
| 19 | + (route, result) { | ||
| 20 | + final didPop = route.didPop(result); | ||
| 21 | + if (!didPop) { | ||
| 22 | + return false; | ||
| 23 | + } | ||
| 24 | + return true; | ||
| 25 | + }, | ||
| 26 | + onGenerateRoute: (settings) { | ||
| 27 | + final selectedPageList = | ||
| 28 | + pages.where((element) => element.name == settings.name); | ||
| 29 | + if (selectedPageList.isNotEmpty) { | ||
| 30 | + final selectedPage = selectedPageList.first; | ||
| 31 | + return GetPageRoute( | ||
| 32 | + page: selectedPage.page, | ||
| 33 | + settings: settings, | ||
| 34 | + ); | ||
| 35 | + } | ||
| 36 | + }, | ||
| 37 | + reportsRouteUpdateToEngine: reportsRouteUpdateToEngine, | ||
| 38 | + pages: pages, | ||
| 39 | + observers: [ | ||
| 40 | + // GetObserver(), | ||
| 41 | + ...?observers, | ||
| 42 | + ], | ||
| 43 | + transitionDelegate: | ||
| 44 | + transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(), | ||
| 45 | + ); | ||
| 46 | + | ||
| 47 | + GetNavigator({ | ||
| 48 | + GlobalKey<NavigatorState>? key, | ||
| 49 | + bool Function(Route<dynamic>, dynamic)? onPopPage, | ||
| 50 | + required List<GetPage> pages, | ||
| 51 | + List<NavigatorObserver>? observers, | ||
| 52 | + bool reportsRouteUpdateToEngine = false, | ||
| 53 | + TransitionDelegate? transitionDelegate, | ||
| 54 | + String? initialRoute, | ||
| 55 | + }) : super( | ||
| 56 | + //keys should be optional | ||
| 57 | + key: key, | ||
| 58 | + initialRoute: initialRoute, | ||
| 59 | + onPopPage: onPopPage ?? | ||
| 60 | + (route, result) { | ||
| 61 | + final didPop = route.didPop(result); | ||
| 62 | + if (!didPop) { | ||
| 63 | + return false; | ||
| 64 | + } | ||
| 65 | + return true; | ||
| 66 | + }, | ||
| 67 | + reportsRouteUpdateToEngine: reportsRouteUpdateToEngine, | ||
| 68 | + pages: pages, | ||
| 69 | + observers: [ | ||
| 70 | + // GetObserver(), | ||
| 71 | + ...?observers, | ||
| 72 | + ], | ||
| 73 | + transitionDelegate: | ||
| 74 | + transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(), | ||
| 75 | + ); | ||
| 76 | +} | 
| @@ -2,9 +2,50 @@ import 'dart:async'; | @@ -2,9 +2,50 @@ import 'dart:async'; | ||
| 2 | 2 | ||
| 3 | import 'package:flutter/foundation.dart'; | 3 | import 'package:flutter/foundation.dart'; | 
| 4 | import 'package:flutter/material.dart'; | 4 | import 'package:flutter/material.dart'; | 
| 5 | - | ||
| 6 | import '../../../get.dart'; | 5 | import '../../../get.dart'; | 
| 7 | import '../../../get_state_manager/src/simple/list_notifier.dart'; | 6 | import '../../../get_state_manager/src/simple/list_notifier.dart'; | 
| 7 | +import 'get_navigator.dart'; | ||
| 8 | + | ||
| 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, | ||
| 39 | + | ||
| 40 | + /// Simply don't push the new route | ||
| 41 | + DoNothing, | ||
| 42 | + | ||
| 43 | + /// Recommended - Moves the old route entry to the front | ||
| 44 | + /// | ||
| 45 | + /// With this mode, you guarantee there will be only one | ||
| 46 | + /// route entry for each location | ||
| 47 | + ReorderRoutes | ||
| 48 | +} | ||
| 8 | 49 | ||
| 9 | class GetDelegate extends RouterDelegate<GetNavConfig> | 50 | class GetDelegate extends RouterDelegate<GetNavConfig> | 
| 10 | with ListNotifierSingleMixin { | 51 | with ListNotifierSingleMixin { | 
| @@ -17,7 +58,10 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | @@ -17,7 +58,10 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | ||
| 17 | final List<NavigatorObserver>? navigatorObservers; | 58 | final List<NavigatorObserver>? navigatorObservers; | 
| 18 | final TransitionDelegate<dynamic>? transitionDelegate; | 59 | final TransitionDelegate<dynamic>? transitionDelegate; | 
| 19 | 60 | ||
| 20 | - final _allCompleters = <GetPage, Completer>{}; | 61 | + final Iterable<GetPage> Function(GetNavConfig currentNavStack)? | 
| 62 | + pickPagesForRootNavigator; | ||
| 63 | + | ||
| 64 | + GlobalKey<NavigatorState> get navigatorKey => Get.key; | ||
| 21 | 65 | ||
| 22 | GetDelegate({ | 66 | GetDelegate({ | 
| 23 | GetPage? notFoundRoute, | 67 | GetPage? notFoundRoute, | 
| @@ -26,6 +70,7 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | @@ -26,6 +70,7 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | ||
| 26 | this.backButtonPopMode = PopMode.History, | 70 | this.backButtonPopMode = PopMode.History, | 
| 27 | this.preventDuplicateHandlingMode = | 71 | this.preventDuplicateHandlingMode = | 
| 28 | PreventDuplicateHandlingMode.ReorderRoutes, | 72 | PreventDuplicateHandlingMode.ReorderRoutes, | 
| 73 | + this.pickPagesForRootNavigator, | ||
| 29 | }) : notFoundRoute = notFoundRoute ?? | 74 | }) : notFoundRoute = notFoundRoute ?? | 
| 30 | GetPage( | 75 | GetPage( | 
| 31 | name: '/404', | 76 | name: '/404', | 
| @@ -36,191 +81,232 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | @@ -36,191 +81,232 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | ||
| 36 | Get.log('GetDelegate is created !'); | 81 | Get.log('GetDelegate is created !'); | 
| 37 | } | 82 | } | 
| 38 | 83 | ||
| 39 | - @override | ||
| 40 | - GetNavConfig? get currentConfiguration { | ||
| 41 | - if (history.isEmpty) return null; | ||
| 42 | - final route = history.last; | ||
| 43 | - return route; | 84 | + Future<GetNavConfig?> runMiddleware(GetNavConfig config) async { | 
| 85 | + final middlewares = config.currentTreeBranch.last.middlewares; | ||
| 86 | + if (middlewares == null) { | ||
| 87 | + return config; | ||
| 88 | + } | ||
| 89 | + var iterator = config; | ||
| 90 | + for (var item in middlewares) { | ||
| 91 | + var redirectRes = await item.redirectDelegate(iterator); | ||
| 92 | + if (redirectRes == null) return null; | ||
| 93 | + iterator = redirectRes; | ||
| 94 | + } | ||
| 95 | + return iterator; | ||
| 44 | } | 96 | } | 
| 45 | 97 | ||
| 46 | - GlobalKey<NavigatorState> get navigatorKey => Get.key; | 98 | + Future<void> _unsafeHistoryAdd(GetNavConfig config) async { | 
| 99 | + final res = await runMiddleware(config); | ||
| 100 | + if (res == null) return; | ||
| 101 | + history.add(res); | ||
| 102 | + } | ||
| 47 | 103 | ||
| 48 | - Map<String, String> get parameters { | ||
| 49 | - return currentConfiguration?.currentPage?.parameters ?? {}; | 104 | + Future<void> _unsafeHistoryRemove(GetNavConfig config) async { | 
| 105 | + var index = history.indexOf(config); | ||
| 106 | + if (index >= 0) await _unsafeHistoryRemoveAt(index); | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + Future<GetNavConfig?> _unsafeHistoryRemoveAt(int index) async { | ||
| 110 | + if (index == history.length - 1 && history.length > 1) { | ||
| 111 | + //removing WILL update the current route | ||
| 112 | + final toCheck = history[history.length - 2]; | ||
| 113 | + final resMiddleware = await runMiddleware(toCheck); | ||
| 114 | + if (resMiddleware == null) return null; | ||
| 115 | + history[history.length - 2] = resMiddleware; | ||
| 116 | + } | ||
| 117 | + return history.removeAt(index); | ||
| 50 | } | 118 | } | 
| 51 | 119 | ||
| 52 | T arguments<T>() { | 120 | T arguments<T>() { | 
| 53 | return currentConfiguration?.currentPage?.arguments as T; | 121 | return currentConfiguration?.currentPage?.arguments as T; | 
| 54 | } | 122 | } | 
| 55 | 123 | ||
| 56 | - /// Removes routes according to [PopMode] | ||
| 57 | - /// until it reaches the specifc [fullRoute], | ||
| 58 | - /// DOES NOT remove the [fullRoute] | ||
| 59 | - Future<void> backUntil( | ||
| 60 | - String fullRoute, { | ||
| 61 | - PopMode popMode = PopMode.Page, | 124 | + Map<String, String> get parameters { | 
| 125 | + return currentConfiguration?.currentPage?.parameters ?? {}; | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + /// Adds a new history entry and waits for the result | ||
| 129 | + Future<void> pushHistory( | ||
| 130 | + GetNavConfig config, { | ||
| 131 | + bool rebuildStack = true, | ||
| 62 | }) async { | 132 | }) async { | 
| 63 | - // remove history or page entries until you meet route | ||
| 64 | - var iterator = currentConfiguration; | ||
| 65 | - while (_canPop(popMode) && | ||
| 66 | - iterator != null && | ||
| 67 | - iterator.location != fullRoute) { | ||
| 68 | - await _pop(popMode); | ||
| 69 | - // replace iterator | ||
| 70 | - iterator = currentConfiguration; | 133 | + //this changes the currentConfiguration | 
| 134 | + await _pushHistory(config); | ||
| 135 | + if (rebuildStack) { | ||
| 136 | + refresh(); | ||
| 71 | } | 137 | } | 
| 72 | - refresh(); | ||
| 73 | } | 138 | } | 
| 74 | 139 | ||
| 75 | - @override | ||
| 76 | - Widget build(BuildContext context) { | ||
| 77 | - final pages = getVisualPages(); | ||
| 78 | - if (pages.length == 0) return SizedBox.shrink(); | ||
| 79 | - final extraObservers = navigatorObservers; | ||
| 80 | - return GetNavigator( | ||
| 81 | - key: navigatorKey, | ||
| 82 | - onPopPage: _onPopVisualRoute, | ||
| 83 | - pages: pages, | ||
| 84 | - observers: [ | ||
| 85 | - GetObserver(), | ||
| 86 | - if (extraObservers != null) ...extraObservers, | ||
| 87 | - ], | ||
| 88 | - transitionDelegate: | ||
| 89 | - transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(), | ||
| 90 | - ); | 140 | + Future<void> _removeHistoryEntry(GetNavConfig entry) async { | 
| 141 | + await _unsafeHistoryRemove(entry); | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + Future<void> _pushHistory(GetNavConfig config) async { | ||
| 145 | + if (config.currentPage!.preventDuplicates) { | ||
| 146 | + final originalEntryIndex = | ||
| 147 | + history.indexWhere((element) => element.location == config.location); | ||
| 148 | + if (originalEntryIndex >= 0) { | ||
| 149 | + switch (preventDuplicateHandlingMode) { | ||
| 150 | + case PreventDuplicateHandlingMode.PopUntilOriginalRoute: | ||
| 151 | + await backUntil(config.location!, popMode: PopMode.Page); | ||
| 152 | + break; | ||
| 153 | + case PreventDuplicateHandlingMode.ReorderRoutes: | ||
| 154 | + await _unsafeHistoryRemoveAt(originalEntryIndex); | ||
| 155 | + await _unsafeHistoryAdd(config); | ||
| 156 | + break; | ||
| 157 | + case PreventDuplicateHandlingMode.DoNothing: | ||
| 158 | + default: | ||
| 159 | + break; | ||
| 160 | + } | ||
| 161 | + return; | ||
| 162 | + } | ||
| 163 | + } | ||
| 164 | + await _unsafeHistoryAdd(config); | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + Future<GetNavConfig?> _popHistory() async { | ||
| 168 | + if (!_canPopHistory()) return null; | ||
| 169 | + return await _doPopHistory(); | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + Future<GetNavConfig?> _doPopHistory() async { | ||
| 173 | + return await _unsafeHistoryRemoveAt(history.length - 1); | ||
| 174 | + } | ||
| 175 | + | ||
| 176 | + Future<GetNavConfig?> _popPage() async { | ||
| 177 | + if (!_canPopPage()) return null; | ||
| 178 | + return await _doPopPage(); | ||
| 179 | + } | ||
| 180 | + | ||
| 181 | + Future<GetNavConfig?> _pop(PopMode mode) async { | ||
| 182 | + switch (mode) { | ||
| 183 | + case PopMode.History: | ||
| 184 | + return await _popHistory(); | ||
| 185 | + case PopMode.Page: | ||
| 186 | + return await _popPage(); | ||
| 187 | + default: | ||
| 188 | + return null; | ||
| 189 | + } | ||
| 190 | + } | ||
| 191 | + | ||
| 192 | + // returns the popped page | ||
| 193 | + Future<GetNavConfig?> _doPopPage() async { | ||
| 194 | + final currentBranch = currentConfiguration?.currentTreeBranch; | ||
| 195 | + if (currentBranch != null && currentBranch.length > 1) { | ||
| 196 | + //remove last part only | ||
| 197 | + final remaining = currentBranch.take(currentBranch.length - 1); | ||
| 198 | + final prevHistoryEntry = | ||
| 199 | + history.length > 1 ? history[history.length - 2] : null; | ||
| 200 | + | ||
| 201 | + //check if current route is the same as the previous route | ||
| 202 | + if (prevHistoryEntry != null) { | ||
| 203 | + //if so, pop the entire history entry | ||
| 204 | + final newLocation = remaining.last.name; | ||
| 205 | + final prevLocation = prevHistoryEntry.location; | ||
| 206 | + if (newLocation == prevLocation) { | ||
| 207 | + //pop the entire history entry | ||
| 208 | + return await _popHistory(); | ||
| 209 | + } | ||
| 210 | + } | ||
| 211 | + | ||
| 212 | + //create a new route with the remaining tree branch | ||
| 213 | + final res = await _popHistory(); | ||
| 214 | + await _pushHistory( | ||
| 215 | + GetNavConfig( | ||
| 216 | + currentTreeBranch: remaining.toList(), | ||
| 217 | + location: remaining.last.name, | ||
| 218 | + state: null, //TOOD: persist state?? | ||
| 219 | + ), | ||
| 220 | + ); | ||
| 221 | + return res; | ||
| 222 | + } else { | ||
| 223 | + //remove entire entry | ||
| 224 | + return await _popHistory(); | ||
| 225 | + } | ||
| 226 | + } | ||
| 227 | + | ||
| 228 | + Future<GetNavConfig?> popHistory() async { | ||
| 229 | + return await _popHistory(); | ||
| 91 | } | 230 | } | 
| 92 | 231 | ||
| 93 | - // void _unsafeHistoryClear() { | ||
| 94 | - // history.clear(); | ||
| 95 | - // } | 232 | + bool _canPopHistory() { | 
| 233 | + return history.length > 1; | ||
| 234 | + } | ||
| 96 | 235 | ||
| 97 | Future<bool> canPopHistory() { | 236 | Future<bool> canPopHistory() { | 
| 98 | return SynchronousFuture(_canPopHistory()); | 237 | return SynchronousFuture(_canPopHistory()); | 
| 99 | } | 238 | } | 
| 100 | 239 | ||
| 240 | + bool _canPopPage() { | ||
| 241 | + final currentTreeBranch = currentConfiguration?.currentTreeBranch; | ||
| 242 | + if (currentTreeBranch == null) return false; | ||
| 243 | + return currentTreeBranch.length > 1 ? true : _canPopHistory(); | ||
| 244 | + } | ||
| 245 | + | ||
| 101 | Future<bool> canPopPage() { | 246 | Future<bool> canPopPage() { | 
| 102 | return SynchronousFuture(_canPopPage()); | 247 | return SynchronousFuture(_canPopPage()); | 
| 103 | } | 248 | } | 
| 104 | 249 | ||
| 250 | + bool _canPop(PopMode mode) { | ||
| 251 | + switch (mode) { | ||
| 252 | + case PopMode.History: | ||
| 253 | + return _canPopHistory(); | ||
| 254 | + case PopMode.Page: | ||
| 255 | + default: | ||
| 256 | + return _canPopPage(); | ||
| 257 | + } | ||
| 258 | + } | ||
| 259 | + | ||
| 105 | /// gets the visual pages from the current history entry | 260 | /// gets the visual pages from the current history entry | 
| 106 | /// | 261 | /// | 
| 107 | - /// visual pages must have [participatesInRootNavigator] set to true | ||
| 108 | - List<GetPage> getVisualPages() { | ||
| 109 | - final currentHistory = currentConfiguration; | ||
| 110 | - if (currentHistory == null) return <GetPage>[]; | ||
| 111 | - | 262 | + /// visual pages must have [GetPage.participatesInRootNavigator] set to true | 
| 263 | + Iterable<GetPage> getVisualPages(GetNavConfig currentHistory) { | ||
| 112 | final res = currentHistory.currentTreeBranch | 264 | final res = currentHistory.currentTreeBranch | 
| 113 | .where((r) => r.participatesInRootNavigator != null); | 265 | .where((r) => r.participatesInRootNavigator != null); | 
| 114 | if (res.length == 0) { | 266 | if (res.length == 0) { | 
| 115 | //default behavoir, all routes participate in root navigator | 267 | //default behavoir, all routes participate in root navigator | 
| 116 | - return history.map((e) => e.currentPage!).toList(); | 268 | + return history.map((e) => e.currentPage!); | 
| 117 | } else { | 269 | } else { | 
| 118 | //user specified at least one participatesInRootNavigator | 270 | //user specified at least one participatesInRootNavigator | 
| 119 | return res | 271 | return res | 
| 120 | - .where((element) => element.participatesInRootNavigator == true) | ||
| 121 | - .toList(); | 272 | + .where((element) => element.participatesInRootNavigator == true); | 
| 122 | } | 273 | } | 
| 123 | } | 274 | } | 
| 124 | 275 | ||
| 125 | - // GetPageRoute getPageRoute(RouteSettings? settings) { | ||
| 126 | - // return PageRedirect(settings ?? RouteSettings(name: '/404'), _notFound()) | ||
| 127 | - // .page(); | ||
| 128 | - // } | 276 | + @override | 
| 277 | + Widget build(BuildContext context) { | ||
| 278 | + final currentHistory = currentConfiguration; | ||
| 279 | + final pages = currentHistory == null | ||
| 280 | + ? <GetPage>[] | ||
| 281 | + : pickPagesForRootNavigator?.call(currentHistory) ?? | ||
| 282 | + getVisualPages(currentHistory); | ||
| 283 | + if (pages.length == 0) return SizedBox.shrink(); | ||
| 284 | + return GetNavigator( | ||
| 285 | + key: navigatorKey, | ||
| 286 | + onPopPage: _onPopVisualRoute, | ||
| 287 | + pages: pages.toList(), | ||
| 288 | + observers: [ | ||
| 289 | + GetObserver(), | ||
| 290 | + ...?navigatorObservers, | ||
| 291 | + ], | ||
| 292 | + transitionDelegate: | ||
| 293 | + transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(), | ||
| 294 | + ); | ||
| 295 | + } | ||
| 129 | 296 | ||
| 130 | - Future<bool> handlePopupRoutes({ | ||
| 131 | - Object? result, | ||
| 132 | - }) async { | ||
| 133 | - Route? currentRoute; | ||
| 134 | - navigatorKey.currentState!.popUntil((route) { | ||
| 135 | - currentRoute = route; | ||
| 136 | - return true; | ||
| 137 | - }); | ||
| 138 | - if (currentRoute is PopupRoute) { | ||
| 139 | - return await navigatorKey.currentState!.maybePop(result); | ||
| 140 | - } | ||
| 141 | - return false; | ||
| 142 | - } | ||
| 143 | - | ||
| 144 | - Future<T?>? offAndToNamed<T>( | ||
| 145 | - String page, { | ||
| 146 | - dynamic arguments, | ||
| 147 | - int? id, | ||
| 148 | - dynamic result, | ||
| 149 | - Map<String, String>? parameters, | ||
| 150 | - PopMode popMode = PopMode.History, | ||
| 151 | - }) async { | ||
| 152 | - if (parameters != null) { | ||
| 153 | - final uri = Uri(path: page, queryParameters: parameters); | ||
| 154 | - page = uri.toString(); | ||
| 155 | - } | ||
| 156 | - | ||
| 157 | - await popRoute(result: result); | ||
| 158 | - return toNamed(page, arguments: arguments, parameters: parameters); | ||
| 159 | - } | ||
| 160 | - | ||
| 161 | - Future<T> offNamed<T>( | ||
| 162 | - String page, { | ||
| 163 | - dynamic arguments, | ||
| 164 | - Map<String, String>? parameters, | ||
| 165 | - }) async { | ||
| 166 | - history.removeLast(); | ||
| 167 | - return toNamed<T>(page, arguments: arguments, parameters: parameters); | ||
| 168 | - } | ||
| 169 | - | ||
| 170 | - Future<GetNavConfig?> popHistory() async { | ||
| 171 | - return await _popHistory(); | ||
| 172 | - } | ||
| 173 | - | ||
| 174 | - // returns the popped page | ||
| 175 | @override | 297 | @override | 
| 176 | - Future<bool> popRoute({ | ||
| 177 | - Object? result, | ||
| 178 | - PopMode popMode = PopMode.Page, | ||
| 179 | - }) async { | ||
| 180 | - //Returning false will cause the entire app to be popped. | ||
| 181 | - final wasPopup = await handlePopupRoutes(result: result); | ||
| 182 | - if (wasPopup) return true; | ||
| 183 | - final _popped = await _pop(popMode); | ||
| 184 | - refresh(); | ||
| 185 | - if (_popped != null) { | ||
| 186 | - //emulate the old pop with result | ||
| 187 | - return true; | ||
| 188 | - } | ||
| 189 | - return false; | ||
| 190 | - } | ||
| 191 | - | ||
| 192 | - /// Adds a new history entry and waits for the result | ||
| 193 | - Future<void> pushHistory( | ||
| 194 | - GetNavConfig config, { | ||
| 195 | - bool rebuildStack = true, | ||
| 196 | - }) async { | ||
| 197 | - //this changes the currentConfiguration | ||
| 198 | - await _pushHistory(config); | ||
| 199 | - if (rebuildStack) { | ||
| 200 | - refresh(); | ||
| 201 | - } | ||
| 202 | - } | ||
| 203 | - | ||
| 204 | - Future<GetNavConfig?> runMiddleware(GetNavConfig config) async { | ||
| 205 | - final middlewares = config.currentTreeBranch.last.middlewares; | ||
| 206 | - if (middlewares == null) { | ||
| 207 | - return config; | ||
| 208 | - } | ||
| 209 | - var iterator = config; | ||
| 210 | - for (var item in middlewares) { | ||
| 211 | - var redirectRes = await item.redirectDelegate(iterator); | ||
| 212 | - if (redirectRes == null) return null; | ||
| 213 | - iterator = redirectRes; | ||
| 214 | - } | ||
| 215 | - return iterator; | 298 | + Future<void> setNewRoutePath(GetNavConfig configuration) async { | 
| 299 | + await pushHistory(configuration); | ||
| 216 | } | 300 | } | 
| 217 | 301 | ||
| 218 | @override | 302 | @override | 
| 219 | - Future<void> setNewRoutePath(GetNavConfig configuration) async { | ||
| 220 | - await pushHistory(configuration); | 303 | + GetNavConfig? get currentConfiguration { | 
| 304 | + if (history.isEmpty) return null; | ||
| 305 | + final route = history.last; | ||
| 306 | + return route; | ||
| 221 | } | 307 | } | 
| 222 | 308 | ||
| 223 | - Future<T> toNamed<T>( | 309 | + Future<void> toNamed( | 
| 224 | String page, { | 310 | String page, { | 
| 225 | dynamic arguments, | 311 | dynamic arguments, | 
| 226 | Map<String, String>? parameters, | 312 | Map<String, String>? parameters, | 
| @@ -233,10 +319,7 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | @@ -233,10 +319,7 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | ||
| 233 | final decoder = Get.routeTree.matchRoute(page, arguments: arguments); | 319 | final decoder = Get.routeTree.matchRoute(page, arguments: arguments); | 
| 234 | decoder.replaceArguments(arguments); | 320 | decoder.replaceArguments(arguments); | 
| 235 | 321 | ||
| 236 | - final completer = Completer<T>(); | ||
| 237 | - | ||
| 238 | if (decoder.route != null) { | 322 | if (decoder.route != null) { | 
| 239 | - _allCompleters[decoder.route!] = completer; | ||
| 240 | await pushHistory( | 323 | await pushHistory( | 
| 241 | GetNavConfig( | 324 | GetNavConfig( | 
| 242 | currentTreeBranch: decoder.treeBranch, | 325 | currentTreeBranch: decoder.treeBranch, | 
| @@ -244,80 +327,76 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | @@ -244,80 +327,76 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | ||
| 244 | state: null, //TODO: persist state? | 327 | state: null, //TODO: persist state? | 
| 245 | ), | 328 | ), | 
| 246 | ); | 329 | ); | 
| 247 | - | ||
| 248 | - return completer.future; | ||
| 249 | } else { | 330 | } else { | 
| 250 | - ///TODO: IMPLEMENT ROUTE NOT FOUND | ||
| 251 | - | ||
| 252 | - return Future.value(); | ||
| 253 | - } | ||
| 254 | - } | ||
| 255 | - | ||
| 256 | - bool _canPop(PopMode mode) { | ||
| 257 | - switch (mode) { | ||
| 258 | - case PopMode.History: | ||
| 259 | - return _canPopHistory(); | ||
| 260 | - case PopMode.Page: | ||
| 261 | - default: | ||
| 262 | - return _canPopPage(); | 331 | + await pushHistory( | 
| 332 | + GetNavConfig( | ||
| 333 | + currentTreeBranch: [notFoundRoute], | ||
| 334 | + location: notFoundRoute.name, | ||
| 335 | + state: null, //TODO: persist state? | ||
| 336 | + ), | ||
| 337 | + ); | ||
| 263 | } | 338 | } | 
| 264 | } | 339 | } | 
| 265 | 340 | ||
| 266 | - bool _canPopHistory() { | ||
| 267 | - return history.length > 1; | 341 | + //pops the previous route (if there is one) and goes to new route | 
| 342 | + Future<void> offNamed( | ||
| 343 | + String page, { | ||
| 344 | + dynamic arguments, | ||
| 345 | + Map<String, String>? parameters, | ||
| 346 | + PopMode popMode = PopMode.History, | ||
| 347 | + }) async { | ||
| 348 | + await popRoute(popMode: popMode); | ||
| 349 | + return toNamed(page, arguments: arguments, parameters: parameters); | ||
| 268 | } | 350 | } | 
| 269 | 351 | ||
| 270 | - bool _canPopPage() { | ||
| 271 | - final currentTreeBranch = currentConfiguration?.currentTreeBranch; | ||
| 272 | - if (currentTreeBranch == null) return false; | ||
| 273 | - return currentTreeBranch.length > 1 ? true : _canPopHistory(); | 352 | + /// Removes routes according to [PopMode] | 
| 353 | + /// until it reaches the specifc [fullRoute], | ||
| 354 | + /// DOES NOT remove the [fullRoute] | ||
| 355 | + Future<void> backUntil( | ||
| 356 | + String fullRoute, { | ||
| 357 | + PopMode popMode = PopMode.History, | ||
| 358 | + }) async { | ||
| 359 | + // remove history or page entries until you meet route | ||
| 360 | + var iterator = currentConfiguration; | ||
| 361 | + while (_canPop(popMode) && | ||
| 362 | + iterator != null && | ||
| 363 | + iterator.location != fullRoute) { | ||
| 364 | + await _pop(popMode); | ||
| 365 | + // replace iterator | ||
| 366 | + iterator = currentConfiguration; | ||
| 367 | + } | ||
| 368 | + refresh(); | ||
| 274 | } | 369 | } | 
| 275 | 370 | ||
| 276 | - Future<GetNavConfig?> _doPopHistory() async { | ||
| 277 | - return await _unsafeHistoryRemoveAt(history.length - 1); | 371 | + Future<bool> handlePopupRoutes({ | 
| 372 | + Object? result, | ||
| 373 | + }) async { | ||
| 374 | + Route? currentRoute; | ||
| 375 | + navigatorKey.currentState!.popUntil((route) { | ||
| 376 | + currentRoute = route; | ||
| 377 | + return true; | ||
| 378 | + }); | ||
| 379 | + if (currentRoute is PopupRoute) { | ||
| 380 | + return await navigatorKey.currentState!.maybePop(result); | ||
| 381 | + } | ||
| 382 | + return false; | ||
| 278 | } | 383 | } | 
| 279 | 384 | ||
| 280 | - // @override | ||
| 281 | - // Future<void> setInitialRoutePath(GetNavConfig configuration) async { | ||
| 282 | - // //no need to clear history with Reorder route strategy | ||
| 283 | - // // _unsafeHistoryClear(); | ||
| 284 | - // // _resultCompleter.clear(); | ||
| 285 | - // await pushHistory(configuration); | ||
| 286 | - // } | ||
| 287 | - | ||
| 288 | - Future<GetNavConfig?> _doPopPage() async { | ||
| 289 | - final currentBranch = currentConfiguration?.currentTreeBranch; | ||
| 290 | - if (currentBranch != null && currentBranch.length > 1) { | ||
| 291 | - //remove last part only | ||
| 292 | - final remaining = currentBranch.take(currentBranch.length - 1); | ||
| 293 | - final prevHistoryEntry = | ||
| 294 | - history.length > 1 ? history[history.length - 2] : null; | ||
| 295 | - | ||
| 296 | - //check if current route is the same as the previous route | ||
| 297 | - if (prevHistoryEntry != null) { | ||
| 298 | - //if so, pop the entire history entry | ||
| 299 | - final newLocation = remaining.last.name; | ||
| 300 | - final prevLocation = prevHistoryEntry.location; | ||
| 301 | - if (newLocation == prevLocation) { | ||
| 302 | - //pop the entire history entry | ||
| 303 | - return await _popHistory(); | ||
| 304 | - } | ||
| 305 | - } | ||
| 306 | - | ||
| 307 | - //create a new route with the remaining tree branch | ||
| 308 | - final res = await _popHistory(); | ||
| 309 | - await _pushHistory( | ||
| 310 | - GetNavConfig( | ||
| 311 | - currentTreeBranch: remaining.toList(), | ||
| 312 | - location: remaining.last.name, | ||
| 313 | - state: null, //TOOD: persist state?? | ||
| 314 | - ), | ||
| 315 | - ); | ||
| 316 | - return res; | ||
| 317 | - } else { | ||
| 318 | - //remove entire entry | ||
| 319 | - return await _popHistory(); | 385 | + @override | 
| 386 | + Future<bool> popRoute({ | ||
| 387 | + Object? result, | ||
| 388 | + PopMode? popMode, | ||
| 389 | + }) async { | ||
| 390 | + //Returning false will cause the entire app to be popped. | ||
| 391 | + final wasPopup = await handlePopupRoutes(result: result); | ||
| 392 | + if (wasPopup) return true; | ||
| 393 | + final _popped = await _pop(popMode ?? backButtonPopMode); | ||
| 394 | + refresh(); | ||
| 395 | + if (_popped != null) { | ||
| 396 | + //emulate the old pop with result | ||
| 397 | + return true; | ||
| 320 | } | 398 | } | 
| 399 | + return false; | ||
| 321 | } | 400 | } | 
| 322 | 401 | ||
| 323 | bool _onPopVisualRoute(Route<dynamic> route, dynamic result) { | 402 | bool _onPopVisualRoute(Route<dynamic> route, dynamic result) { | 
| @@ -334,153 +413,9 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | @@ -334,153 +413,9 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | ||
| 334 | if (config != null) { | 413 | if (config != null) { | 
| 335 | _removeHistoryEntry(config); | 414 | _removeHistoryEntry(config); | 
| 336 | } | 415 | } | 
| 337 | - if (_allCompleters.containsKey(settings)) { | ||
| 338 | - _allCompleters[settings]?.complete(route.popped); | ||
| 339 | - } | ||
| 340 | } | 416 | } | 
| 341 | refresh(); | 417 | refresh(); | 
| 342 | 418 | ||
| 343 | return true; | 419 | return true; | 
| 344 | } | 420 | } | 
| 345 | - | ||
| 346 | - Future<GetNavConfig?> _pop(PopMode mode) async { | ||
| 347 | - switch (mode) { | ||
| 348 | - case PopMode.History: | ||
| 349 | - return await _popHistory(); | ||
| 350 | - case PopMode.Page: | ||
| 351 | - return await _popPage(); | ||
| 352 | - default: | ||
| 353 | - return null; | ||
| 354 | - } | ||
| 355 | - } | ||
| 356 | - | ||
| 357 | - Future<GetNavConfig?> _popHistory() async { | ||
| 358 | - if (!_canPopHistory()) return null; | ||
| 359 | - return await _doPopHistory(); | ||
| 360 | - } | ||
| 361 | - | ||
| 362 | - Future<GetNavConfig?> _popPage() async { | ||
| 363 | - if (!_canPopPage()) return null; | ||
| 364 | - return await _doPopPage(); | ||
| 365 | - } | ||
| 366 | - | ||
| 367 | - Future<void> _pushHistory(GetNavConfig config) async { | ||
| 368 | - if (config.currentPage!.preventDuplicates) { | ||
| 369 | - final originalEntryIndex = | ||
| 370 | - history.indexWhere((element) => element.location == config.location); | ||
| 371 | - if (originalEntryIndex >= 0) { | ||
| 372 | - switch (preventDuplicateHandlingMode) { | ||
| 373 | - case PreventDuplicateHandlingMode.PopUntilOriginalRoute: | ||
| 374 | - await backUntil(config.location!, popMode: PopMode.Page); | ||
| 375 | - break; | ||
| 376 | - case PreventDuplicateHandlingMode.ReorderRoutes: | ||
| 377 | - await _unsafeHistoryRemoveAt(originalEntryIndex); | ||
| 378 | - await _unsafeHistoryAdd(config); | ||
| 379 | - break; | ||
| 380 | - case PreventDuplicateHandlingMode.DoNothing: | ||
| 381 | - default: | ||
| 382 | - break; | ||
| 383 | - } | ||
| 384 | - return; | ||
| 385 | - } | ||
| 386 | - } | ||
| 387 | - await _unsafeHistoryAdd(config); | ||
| 388 | - } | ||
| 389 | - | ||
| 390 | - Future<void> _removeHistoryEntry(GetNavConfig entry) async { | ||
| 391 | - await _unsafeHistoryRemove(entry); | ||
| 392 | - } | ||
| 393 | - | ||
| 394 | - Future<void> _unsafeHistoryAdd(GetNavConfig config) async { | ||
| 395 | - final res = await runMiddleware(config); | ||
| 396 | - if (res == null) return; | ||
| 397 | - history.add(res); | ||
| 398 | - } | ||
| 399 | - | ||
| 400 | - Future<void> _unsafeHistoryRemove(GetNavConfig config) async { | ||
| 401 | - var index = history.indexOf(config); | ||
| 402 | - if (index >= 0) await _unsafeHistoryRemoveAt(index); | ||
| 403 | - } | ||
| 404 | - | ||
| 405 | - Future<GetNavConfig?> _unsafeHistoryRemoveAt(int index) async { | ||
| 406 | - if (index == history.length - 1 && history.length > 1) { | ||
| 407 | - //removing WILL update the current route | ||
| 408 | - final toCheck = history[history.length - 2]; | ||
| 409 | - final resMiddleware = await runMiddleware(toCheck); | ||
| 410 | - if (resMiddleware == null) return null; | ||
| 411 | - history[history.length - 2] = resMiddleware; | ||
| 412 | - } | ||
| 413 | - return history.removeAt(index); | ||
| 414 | - } | ||
| 415 | -} | ||
| 416 | - | ||
| 417 | -class GetNavigator extends Navigator { | ||
| 418 | - GetNavigator({ | ||
| 419 | - GlobalKey<NavigatorState>? key, | ||
| 420 | - bool Function(Route<dynamic>, dynamic)? onPopPage, | ||
| 421 | - required List<Page> pages, | ||
| 422 | - List<NavigatorObserver>? observers, | ||
| 423 | - bool reportsRouteUpdateToEngine = false, | ||
| 424 | - TransitionDelegate? transitionDelegate, | ||
| 425 | - }) : super( | ||
| 426 | - //keys should be optional | ||
| 427 | - key: key, | ||
| 428 | - onPopPage: onPopPage ?? | ||
| 429 | - (route, result) { | ||
| 430 | - final didPop = route.didPop(result); | ||
| 431 | - if (!didPop) { | ||
| 432 | - return false; | ||
| 433 | - } | ||
| 434 | - return true; | ||
| 435 | - }, | ||
| 436 | - reportsRouteUpdateToEngine: reportsRouteUpdateToEngine, | ||
| 437 | - pages: pages, | ||
| 438 | - observers: [ | ||
| 439 | - // GetObserver(), | ||
| 440 | - if (observers != null) ...observers, | ||
| 441 | - ], | ||
| 442 | - transitionDelegate: | ||
| 443 | - transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(), | ||
| 444 | - ); | ||
| 445 | -} | ||
| 446 | - | ||
| 447 | -/// Enables the user to customize the intended pop behavior | ||
| 448 | -/// | ||
| 449 | -/// Goes to either the previous history entry or the previous page entry | ||
| 450 | -/// | ||
| 451 | -/// e.g. if the user navigates to these pages | ||
| 452 | -/// 1) /home | ||
| 453 | -/// 2) /home/products/1234 | ||
| 454 | -/// | ||
| 455 | -/// when popping on [History] mode, it will emulate a browser back button. | ||
| 456 | -/// | ||
| 457 | -/// so the new history stack will be: | ||
| 458 | -/// 1) /home | ||
| 459 | -/// | ||
| 460 | -/// when popping on [Page] mode, it will only remove the last part of the route | ||
| 461 | -/// so the new history stack will be: | ||
| 462 | -/// 1) /home | ||
| 463 | -/// 2) /home/products | ||
| 464 | -/// | ||
| 465 | -/// another pop will change the history stack to: | ||
| 466 | -/// 1) /home | ||
| 467 | -enum PopMode { | ||
| 468 | - History, | ||
| 469 | - Page, | ||
| 470 | -} | ||
| 471 | - | ||
| 472 | -/// Enables the user to customize the behavior when pushing multiple routes that | ||
| 473 | -/// shouldn't be duplicates | ||
| 474 | -enum PreventDuplicateHandlingMode { | ||
| 475 | - /// Removes the history entries until it reaches the old route | ||
| 476 | - PopUntilOriginalRoute, | ||
| 477 | - | ||
| 478 | - /// Simply don't push the new route | ||
| 479 | - DoNothing, | ||
| 480 | - | ||
| 481 | - /// Recommended - Moves the old route entry to the front | ||
| 482 | - /// | ||
| 483 | - /// With this mode, you guarantee there will be only one | ||
| 484 | - /// route entry for each location | ||
| 485 | - ReorderRoutes | ||
| 486 | } | 421 | } | 
| 1 | import 'package:flutter/widgets.dart'; | 1 | import 'package:flutter/widgets.dart'; | 
| 2 | 2 | ||
| 3 | +import 'default_route.dart'; | ||
| 4 | + | ||
| 3 | enum Transition { | 5 | enum Transition { | 
| 4 | fade, | 6 | fade, | 
| 5 | fadeIn, | 7 | fadeIn, | 
| @@ -20,3 +22,4 @@ enum Transition { | @@ -20,3 +22,4 @@ enum Transition { | ||
| 20 | } | 22 | } | 
| 21 | 23 | ||
| 22 | typedef GetPageBuilder = Widget Function(); | 24 | typedef GetPageBuilder = Widget Function(); | 
| 25 | +typedef GetRouteAwarePageBuilder<T> = Widget Function([GetPageRoute<T>? route]); | 
| @@ -569,7 +569,4 @@ class BindError<T> extends Error { | @@ -569,7 +569,4 @@ class BindError<T> extends Error { | ||
| 569 | /// instance of Bindings to manage the | 569 | /// instance of Bindings to manage the | 
| 570 | /// dependencies() (via Get.put()) for the Route you are opening. | 570 | /// dependencies() (via Get.put()) for the Route you are opening. | 
| 571 | // ignore: one_member_abstracts | 571 | // ignore: one_member_abstracts | 
| 572 | -abstract class Binding extends BindingsInterface<List<Bind>> { | ||
| 573 | - @override | ||
| 574 | - List<Bind> dependencies(); | ||
| 575 | -} | 572 | +abstract class Binding extends BindingsInterface<Iterable<Bind>> {} | 
| @@ -24,36 +24,32 @@ typedef ValueBuilderBuilder<T> = Widget Function( | @@ -24,36 +24,32 @@ typedef ValueBuilderBuilder<T> = Widget Function( | ||
| 24 | /// ), | 24 | /// ), | 
| 25 | /// ``` | 25 | /// ``` | 
| 26 | class ValueBuilder<T> extends StatefulWidget { | 26 | class ValueBuilder<T> extends StatefulWidget { | 
| 27 | - final T? initialValue; | 27 | + final T initialValue; | 
| 28 | final ValueBuilderBuilder<T> builder; | 28 | final ValueBuilderBuilder<T> builder; | 
| 29 | final void Function()? onDispose; | 29 | final void Function()? onDispose; | 
| 30 | final void Function(T)? onUpdate; | 30 | final void Function(T)? onUpdate; | 
| 31 | 31 | ||
| 32 | const ValueBuilder({ | 32 | const ValueBuilder({ | 
| 33 | Key? key, | 33 | Key? key, | 
| 34 | - this.initialValue, | 34 | + required this.initialValue, | 
| 35 | this.onDispose, | 35 | this.onDispose, | 
| 36 | this.onUpdate, | 36 | this.onUpdate, | 
| 37 | required this.builder, | 37 | required this.builder, | 
| 38 | }) : super(key: key); | 38 | }) : super(key: key); | 
| 39 | 39 | ||
| 40 | @override | 40 | @override | 
| 41 | - _ValueBuilderState<T> createState() => _ValueBuilderState<T>(); | 41 | + _ValueBuilderState<T> createState() => _ValueBuilderState<T>(initialValue); | 
| 42 | } | 42 | } | 
| 43 | 43 | ||
| 44 | -class _ValueBuilderState<T> extends State<ValueBuilder<T?>> { | ||
| 45 | - T? value; | 44 | +class _ValueBuilderState<T> extends State<ValueBuilder<T>> { | 
| 45 | + T value; | ||
| 46 | + _ValueBuilderState(this.value); | ||
| 46 | 47 | ||
| 47 | - @override | ||
| 48 | - void initState() { | ||
| 49 | - super.initState(); | ||
| 50 | - value = widget.initialValue; | ||
| 51 | - } | ||
| 52 | 48 | ||
| 53 | @override | 49 | @override | 
| 54 | Widget build(BuildContext context) => widget.builder(value, updater); | 50 | Widget build(BuildContext context) => widget.builder(value, updater); | 
| 55 | 51 | ||
| 56 | - void updater(T? newValue) { | 52 | + void updater(T newValue) { | 
| 57 | if (widget.onUpdate != null) { | 53 | if (widget.onUpdate != null) { | 
| 58 | widget.onUpdate!(newValue); | 54 | widget.onUpdate!(newValue); | 
| 59 | } | 55 | } | 
| @@ -71,7 +67,6 @@ class _ValueBuilderState<T> extends State<ValueBuilder<T?>> { | @@ -71,7 +67,6 @@ class _ValueBuilderState<T> extends State<ValueBuilder<T?>> { | ||
| 71 | } else if (value is StreamController) { | 67 | } else if (value is StreamController) { | 
| 72 | (value as StreamController?)?.close(); | 68 | (value as StreamController?)?.close(); | 
| 73 | } | 69 | } | 
| 74 | - value = null; | ||
| 75 | } | 70 | } | 
| 76 | } | 71 | } | 
| 77 | 72 | 
| 1 | +import 'package:collection/collection.dart'; | ||
| 1 | import 'package:flutter/material.dart'; | 2 | import 'package:flutter/material.dart'; | 
| 2 | 3 | ||
| 3 | -import '../platform/platform.dart'; | ||
| 4 | - | ||
| 5 | extension ContextExtensionss on BuildContext { | 4 | extension ContextExtensionss on BuildContext { | 
| 6 | /// The same of [MediaQuery.of(context).size] | 5 | /// The same of [MediaQuery.of(context).size] | 
| 7 | Size get mediaQuerySize => MediaQuery.of(this).size; | 6 | Size get mediaQuerySize => MediaQuery.of(this).size; | 
| @@ -100,17 +99,44 @@ extension ContextExtensionss on BuildContext { | @@ -100,17 +99,44 @@ extension ContextExtensionss on BuildContext { | ||
| 100 | /// True if width be larger than 800 | 99 | /// True if width be larger than 800 | 
| 101 | bool get showNavbar => (width > 800); | 100 | bool get showNavbar => (width > 800); | 
| 102 | 101 | ||
| 103 | - /// True if the shortestSide is smaller than 600p | ||
| 104 | - bool get isPhone => (mediaQueryShortestSide < 600); | 102 | + /// True if the width is smaller than 600p | 
| 103 | + bool get isPhoneOrLess => width <= 600; | ||
| 104 | + | ||
| 105 | + /// True if the width is higher than 600p | ||
| 106 | + bool get isPhoneOrWider => width >= 600; | ||
| 107 | + | ||
| 108 | + /// same as [isPhoneOrLess] | ||
| 109 | + bool get isPhone => isPhoneOrLess; | ||
| 110 | + | ||
| 111 | + /// True if the width is smaller than 600p | ||
| 112 | + bool get isSmallTabletOrLess => width <= 600; | ||
| 113 | + | ||
| 114 | + /// True if the width is higher than 600p | ||
| 115 | + bool get isSmallTabletOrWider => width >= 600; | ||
| 116 | + | ||
| 117 | + /// same as [isSmallTabletOrLess] | ||
| 118 | + bool get isSmallTablet => isSmallTabletOrLess; | ||
| 105 | 119 | ||
| 106 | - /// True if the shortestSide is largest than 600p | ||
| 107 | - bool get isSmallTablet => (mediaQueryShortestSide >= 600); | 120 | + /// True if the width is smaller than 720p | 
| 121 | + bool get isLargeTabletOrLess => width <= 720; | ||
| 108 | 122 | ||
| 109 | - /// True if the shortestSide is largest than 720p | ||
| 110 | - bool get isLargeTablet => (mediaQueryShortestSide >= 720); | 123 | + /// True if the width is higher than 720p | 
| 124 | + bool get isLargeTabletOrWider => width >= 720; | ||
| 125 | + | ||
| 126 | + /// same as [isLargeTabletOrLess] | ||
| 127 | + bool get isLargeTablet => isLargeTabletOrLess; | ||
| 111 | 128 | ||
| 112 | /// True if the current device is Tablet | 129 | /// True if the current device is Tablet | 
| 113 | - bool get isTablet => isSmallTablet || isLargeTablet; | 130 | + bool get isTablet => isSmallTablet; | 
| 131 | + | ||
| 132 | + /// True if the width is smaller than 1200p | ||
| 133 | + bool get isDesktopOrLess => width <= 1200; | ||
| 134 | + | ||
| 135 | + /// True if the width is higher than 1200p | ||
| 136 | + bool get isDesktopOrWider => width >= 1200; | ||
| 137 | + | ||
| 138 | + /// same as [isDesktopOrLess] | ||
| 139 | + bool get isDesktop => isDesktopOrLess; | ||
| 114 | 140 | ||
| 115 | /// Returns a specific value according to the screen size | 141 | /// Returns a specific value according to the screen size | 
| 116 | /// if the device width is higher than or equal to 1200 return | 142 | /// if the device width is higher than or equal to 1200 return | 
| @@ -119,23 +145,28 @@ extension ContextExtensionss on BuildContext { | @@ -119,23 +145,28 @@ extension ContextExtensionss on BuildContext { | ||
| 119 | /// if the device width is less than 300 return [watch] value. | 145 | /// if the device width is less than 300 return [watch] value. | 
| 120 | /// in other cases return [mobile] value. | 146 | /// in other cases return [mobile] value. | 
| 121 | T responsiveValue<T>({ | 147 | T responsiveValue<T>({ | 
| 148 | + T? watch, | ||
| 122 | T? mobile, | 149 | T? mobile, | 
| 123 | T? tablet, | 150 | T? tablet, | 
| 124 | T? desktop, | 151 | T? desktop, | 
| 125 | - T? watch, | ||
| 126 | }) { | 152 | }) { | 
| 127 | - var deviceWidth = mediaQuerySize.shortestSide; | ||
| 128 | - if (GetPlatform.isDesktop) { | ||
| 129 | - deviceWidth = mediaQuerySize.width; | ||
| 130 | - } | ||
| 131 | - if (deviceWidth >= 1200 && desktop != null) { | ||
| 132 | - return desktop; | ||
| 133 | - } else if (deviceWidth >= 600 && tablet != null) { | ||
| 134 | - return tablet; | ||
| 135 | - } else if (deviceWidth < 300 && watch != null) { | ||
| 136 | - return watch; | ||
| 137 | - } else { | ||
| 138 | - return mobile!; | ||
| 139 | - } | 153 | + assert( | 
| 154 | + watch != null || mobile != null || tablet != null || desktop != null); | ||
| 155 | + | ||
| 156 | + var deviceWidth = mediaQuerySize.width; | ||
| 157 | + //big screen width can display smaller sizes | ||
| 158 | + final strictValues = [ | ||
| 159 | + if (deviceWidth >= 1200) desktop, //desktop is allowed | ||
| 160 | + if (deviceWidth >= 600) tablet, //tablet is allowed | ||
| 161 | + if (deviceWidth >= 300) mobile, //mobile is allowed | ||
| 162 | + watch, //watch is allowed | ||
| 163 | + ].whereType<T>(); | ||
| 164 | + final looseValues = [ | ||
| 165 | + watch, | ||
| 166 | + mobile, | ||
| 167 | + tablet, | ||
| 168 | + desktop, | ||
| 169 | + ].whereType<T>(); | ||
| 170 | + return strictValues.firstOrNull ?? looseValues.first; | ||
| 140 | } | 171 | } | 
| 141 | } | 172 | } | 
| @@ -34,16 +34,19 @@ void main() { | @@ -34,16 +34,19 @@ void main() { | ||
| 34 | expect(isLandscape, context.isLandscape); | 34 | expect(isLandscape, context.isLandscape); | 
| 35 | var mediaQueryShortestSide = mediaQuerySize.shortestSide; | 35 | var mediaQueryShortestSide = mediaQuerySize.shortestSide; | 
| 36 | expect(mediaQueryShortestSide, context.mediaQueryShortestSide); | 36 | expect(mediaQueryShortestSide, context.mediaQueryShortestSide); | 
| 37 | - var isLargeTablet = (mediaQueryShortestSide >= 720); | ||
| 38 | - expect(isLargeTablet, context.isLargeTablet); | ||
| 39 | - var isPhone = (mediaQueryShortestSide < 600); | ||
| 40 | - expect(isPhone, context.isPhone); | 37 | + var width = mediaQuerySize.width; | 
| 38 | + expect(width, context.width); | ||
| 39 | + | ||
| 40 | + var isLargeTabletOrWider = (width >= 720); | ||
| 41 | + expect(isLargeTabletOrWider, context.isLargeTabletOrWider); | ||
| 42 | + var isPhoneOrLess = (width < 600); | ||
| 43 | + expect(isPhoneOrLess, context.isPhoneOrLess); | ||
| 41 | var isPortrait = orientation == Orientation.portrait; | 44 | var isPortrait = orientation == Orientation.portrait; | 
| 42 | expect(isPortrait, context.isPortrait); | 45 | expect(isPortrait, context.isPortrait); | 
| 43 | - var isSmallTablet = (mediaQueryShortestSide >= 600); | ||
| 44 | - expect(isSmallTablet, context.isSmallTablet); | ||
| 45 | - var isTablet = isSmallTablet || isLargeTablet; | ||
| 46 | - expect(isTablet, context.isTablet); | 46 | + var isSmallTabletOrWider = (width >= 600); | 
| 47 | + expect(isSmallTabletOrWider, context.isSmallTabletOrWider); | ||
| 48 | + var isTablet = isSmallTabletOrWider || isLargeTabletOrWider; | ||
| 49 | + expect(isTablet, context.isSmallTabletOrWider); | ||
| 47 | var mediaQueryPadding = mediaQuery.padding; | 50 | var mediaQueryPadding = mediaQuery.padding; | 
| 48 | expect(mediaQueryPadding, context.mediaQueryPadding); | 51 | expect(mediaQueryPadding, context.mediaQueryPadding); | 
| 49 | var mediaQueryViewInsets = mediaQuery.viewInsets; | 52 | var mediaQueryViewInsets = mediaQuery.viewInsets; | 
| @@ -55,8 +58,7 @@ void main() { | @@ -55,8 +58,7 @@ void main() { | ||
| 55 | expect(widthTransformer, context.widthTransformer()); | 58 | expect(widthTransformer, context.widthTransformer()); | 
| 56 | var ratio = heightTransformer / widthTransformer; | 59 | var ratio = heightTransformer / widthTransformer; | 
| 57 | expect(ratio, context.ratio()); | 60 | expect(ratio, context.ratio()); | 
| 58 | - var width = mediaQuerySize.width; | ||
| 59 | - expect(width, context.width); | 61 | + | 
| 60 | var showNavbar = (width > 800); | 62 | var showNavbar = (width > 800); | 
| 61 | expect(showNavbar, context.showNavbar); | 63 | expect(showNavbar, context.showNavbar); | 
| 62 | var textScaleFactor = mediaQuery.textScaleFactor; | 64 | var textScaleFactor = mediaQuery.textScaleFactor; | 
- 
Please register or login to post a comment