LCOV - code coverage report
Current view: top level - src/vrouter - vrouter_delegate.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 277 420 66.0 %
Date: 2021-04-29 14:25:52 Functions: 0 0 -

          Line data    Source code
       1             : part of '../main.dart';
       2             : 
       3             : class VRouterDelegate extends RouterDelegate<RouteInformation> with ChangeNotifier {
       4             :   /// This list holds every possible routes of your app
       5             :   final List<VRouteElement> routes;
       6             : 
       7             :   /// If implemented, this becomes the default transition for every route transition
       8             :   /// except those who implement there own buildTransition
       9             :   /// Also see:
      10             :   ///   * [VRouteElement.buildTransition] for custom local transitions
      11             :   ///
      12             :   /// Note that if this is not implemented, every route which does not implement
      13             :   /// its own buildTransition will be given a default transition: this of a
      14             :   /// [MaterialPage] or a [CupertinoPage] depending on the platform
      15             :   final Widget Function(
      16             :           Animation<double> animation, Animation<double> secondaryAnimation, Widget child)?
      17             :       buildTransition;
      18             : 
      19             :   /// The duration of [VRouter.buildTransition]
      20             :   final Duration? transitionDuration;
      21             : 
      22             :   /// The reverse duration of [VRouter.buildTransition]
      23             :   final Duration? reverseTransitionDuration;
      24             : 
      25             :   /// Two router mode are possible:
      26             :   ///    - "hash": This is the default, the url will be serverAddress/#/localUrl
      27             :   ///    - "history": This will display the url in the way we are used to, without
      28             :   ///       the #. However note that you will need to configure your server to make this work.
      29             :   ///       Follow the instructions here: [https://router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations]
      30             :   final VRouterModes mode;
      31             : 
      32             :   /// This allows you to change the initial url
      33             :   ///
      34             :   /// The default is '/'
      35             :   final String initialUrl;
      36             : 
      37             :   /// {@macro flutter.widgets.widgetsApp.navigatorObservers}
      38             :   final List<NavigatorObserver> navigatorObservers;
      39             : 
      40             :   /// This is a context which contains the VRouter.
      41             :   /// It is used is VRouter.beforeLeave for example.
      42             :   late BuildContext _rootVRouterContext;
      43             : 
      44             :   /// Designates the number of page we navigated since
      45             :   /// entering the app.
      46             :   /// If is only used in the web to know where we are when
      47             :   /// the user interacts with the browser instead of the app
      48             :   /// (e.g back button)
      49             :   late int _serialCount;
      50             : 
      51             :   /// When set to true, urlToAppState will be ignored
      52             :   /// You must manually reset it to true otherwise it will
      53             :   /// be ignored forever.
      54             :   bool _ignoreNextBrowserCalls = false;
      55             : 
      56             :   /// When set to false, appStateToUrl will be "ignored"
      57             :   /// i.e. no new history entry will be created
      58             :   /// You must manually reset it to true otherwise it will
      59             :   /// be ignored forever.
      60             :   bool _doReportBackUrlToBrowser = true;
      61             : 
      62             :   /// Build widget before the pages
      63             :   /// The context can be used to access VRouter.of
      64             :   final TransitionBuilder? builder;
      65             : 
      66          12 :   VRouterDelegate({
      67             :     required this.routes,
      68             :     this.builder,
      69             :     this.navigatorObservers = const [],
      70             :     Future<void> Function(VRedirector vRedirector) beforeEnter = VGuard._voidBeforeEnter,
      71             :     Future<void> Function(
      72             :       VRedirector vRedirector,
      73             :       void Function(Map<String, String> historyState) saveHistoryState,
      74             :     )
      75             :         beforeLeave = VGuard._voidBeforeLeave,
      76             :     void Function(BuildContext context, String? from, String to) afterEnter =
      77             :         VGuard._voidAfterEnter,
      78             :     Future<void> Function(VRedirector vRedirector) onPop = VPopHandler._voidOnPop,
      79             :     Future<void> Function(VRedirector vRedirector) onSystemPop = VPopHandler._voidOnSystemPop,
      80             :     this.buildTransition,
      81             :     this.transitionDuration,
      82             :     this.reverseTransitionDuration,
      83             :     this.mode = VRouterModes.hash,
      84             :     this.initialUrl = '/',
      85          12 :   })  : _navigatorKey = GlobalKey<NavigatorState>(),
      86          12 :         _rootVRouter = RootVRouter(
      87             :           routes: routes,
      88             :           afterEnter: afterEnter,
      89             :           beforeEnter: beforeEnter,
      90             :           beforeLeave: beforeLeave,
      91             :           onPop: onPop,
      92             :           onSystemPop: onSystemPop,
      93             :         ) {
      94             :     // When the app starts, get the serialCount. Default to 0.
      95          12 :     _serialCount = (kIsWeb) ? (BrowserHelpers.getHistorySerialCount() ?? 0) : 0;
      96             : 
      97             :     // Setup the url strategy (if hash, do nothing since it is the default)
      98          24 :     if (mode == VRouterModes.history) {
      99           0 :       setPathUrlStrategy();
     100             :     }
     101             : 
     102             :     // Check if this is the first route
     103          24 :     if (_serialCount == 0) {
     104             :       // If it is, navigate to initial url if this is not the default one
     105          24 :       if (initialUrl != '/') {
     106             :         // If we are deep-linking, do not use initial url
     107           0 :         if (!kIsWeb || BrowserHelpers.getPathAndQuery(routerMode: mode).isEmpty) {
     108          15 :           WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
     109          10 :             pushReplacement(initialUrl);
     110             :           });
     111             :         }
     112             :       }
     113             :     }
     114             : 
     115             :     // If we are on the web, we listen to any unload event.
     116             :     // This allows us to call beforeLeave when the browser or the tab
     117             :     // is being closed for example
     118             :     if (kIsWeb) {
     119           0 :       BrowserHelpers.onBrowserBeforeUnload.listen((e) => _onBeforeUnload());
     120             :     }
     121             :   }
     122             : 
     123             :   /// Those are used in the root navigator
     124             :   /// They are here to prevent breaking animations
     125             :   final GlobalKey<NavigatorState> _navigatorKey;
     126             : 
     127             :   /// The VRouter associated to this VRouterDelegate
     128             :   final RootVRouter _rootVRouter;
     129             : 
     130             :   /// The child of this widget
     131             :   ///
     132             :   /// This will contain the navigator etc.
     133             :   //
     134             :   // When the app starts, before we process the '/' route, we display
     135             :   // nothing.
     136             :   // Ideally this should never be needed, or replaced with a splash screen
     137             :   // Should we add the option ?
     138          24 :   late VRoute _vRoute = VRoute(
     139          12 :     pages: [],
     140          12 :     pathParameters: {},
     141          24 :     vRouteElementNode: VRouteElementNode(_rootVRouter, localPath: null),
     142          24 :     vRouteElements: [_rootVRouter],
     143             :   );
     144             : 
     145             :   /// Every VWidgetGuard will be registered here
     146             :   List<VWidgetGuardMessageRoot> _vWidgetGuardMessagesRoot = [];
     147             : 
     148             :   /// Url currently synced with the state
     149             :   /// This url can differ from the once of the browser if
     150             :   /// the state has been yet been updated
     151             :   String? url;
     152             : 
     153             :   /// Previous url that was synced with the state
     154             :   String? previousUrl;
     155             : 
     156             :   /// This state is saved in the browser history. This means that if the user presses
     157             :   /// the back or forward button on the navigator, this historyState will be the same
     158             :   /// as the last one you saved.
     159             :   ///
     160             :   /// It can be changed by using [context.vRouter.replaceHistoryState(newState)]
     161             :   Map<String, String> historyState = {};
     162             : 
     163             :   /// Maps all route parameters (i.e. parameters of the path
     164             :   /// mentioned as ":someId")
     165             :   Map<String, String> pathParameters = <String, String>{};
     166             : 
     167             :   /// Contains all query parameters (i.e. parameters after
     168             :   /// the "?" in the url) of the current url
     169             :   Map<String, String> queryParameters = <String, String>{};
     170             : 
     171             :   /// Updates every state variables of [VRouter]
     172             :   ///
     173             :   /// Note that this does not call setState
     174          12 :   void _updateStateVariables(
     175             :     VRoute vRoute,
     176             :     String newUrl, {
     177             :     required Map<String, String> queryParameters,
     178             :     required Map<String, String> historyState,
     179             :     required List<VRouteElement> deactivatedVRouteElements,
     180             :   }) {
     181             :     // Update the vRoute
     182          12 :     this._vRoute = vRoute;
     183             : 
     184             :     // Update the urls
     185          24 :     previousUrl = url;
     186          12 :     url = newUrl;
     187             : 
     188             :     // Update the history state
     189          12 :     this.historyState = historyState;
     190             : 
     191             :     // Update the path parameters
     192          24 :     this.pathParameters = vRoute.pathParameters;
     193             : 
     194             :     // Update the query parameters
     195          12 :     this.queryParameters = queryParameters;
     196             : 
     197             :     // Update _vWidgetGuardMessagesRoot by removing the no-longer actives VWidgetGuards
     198          25 :     _vWidgetGuardMessagesRoot.removeWhere((vWidgetGuardMessageRoot) =>
     199           2 :         deactivatedVRouteElements.contains(vWidgetGuardMessageRoot.associatedVRouteElement));
     200             :   }
     201             : 
     202             :   /// See [VRouterMethodsHolder.pushNamed]
     203           6 :   void _updateUrlFromName(
     204             :     String name, {
     205             :     Map<String, String> pathParameters = const {},
     206             :     Map<String, String> queryParameters = const {},
     207             :     Map<String, String> newHistoryState = const {},
     208             :     bool isReplacement = false,
     209             :   }) {
     210             :     // Encode the path parameters
     211             :     pathParameters =
     212          15 :         pathParameters.map((key, value) => MapEntry(key, Uri.encodeComponent(value)));
     213             : 
     214             :     // We use VRouteElement.getPathFromName
     215          12 :     final getPathFromNameResult = _rootVRouter.getPathFromName(
     216             :       name,
     217             :       pathParameters: pathParameters,
     218          12 :       parentPathResult: ValidParentPathResult(path: null, pathParameters: {}),
     219             :       remainingPathParameters: pathParameters,
     220             :     );
     221             : 
     222           6 :     if (getPathFromNameResult is ErrorGetPathFromNameResult) {
     223             :       throw getPathFromNameResult;
     224             :     }
     225             : 
     226           5 :     var newPath = (getPathFromNameResult as ValidNameResult).path;
     227             : 
     228             :     // Encode the path parameters
     229           5 :     final encodedPathParameters = pathParameters.map<String, String>(
     230           6 :       (key, value) => MapEntry(key, Uri.encodeComponent(value)),
     231             :     );
     232             : 
     233             :     // Inject the encoded path parameters into the new path
     234          10 :     newPath = pathToFunction(newPath)(encodedPathParameters);
     235             : 
     236             :     // Update the url with the found and completed path
     237           5 :     _updateUrl(newPath, queryParameters: queryParameters, isReplacement: isReplacement);
     238             :   }
     239             : 
     240             :   /// This should be the only way to change a url.
     241             :   /// Navigation cycle:
     242             :   /// 1. Call beforeLeave in all deactivated [VWidgetGuard]
     243             :   /// 2. Call beforeLeave in all deactivated [VRouteElement]
     244             :   /// 3. Call beforeLeave in the [VRouter]
     245             :   /// 4. Call beforeEnter in the [VRouter]
     246             :   /// 5. Call beforeEnter in all initialized [VRouteElement] of the new route
     247             :   /// 6. Call beforeUpdate in all reused [VWidgetGuard]
     248             :   /// 7. Call beforeUpdate in all reused [VRouteElement]
     249             :   ///
     250             :   /// ## The history state got in beforeLeave are stored
     251             :   /// ## The state is updated
     252             :   ///
     253             :   /// 8. Call afterEnter in all initialized [VWidgetGuard]
     254             :   /// 9. Call afterEnter all initialized [VRouteElement]
     255             :   /// 10. Call afterEnter in the [VRouter]
     256             :   /// 11. Call afterUpdate in all reused [VWidgetGuard]
     257             :   /// 12. Call afterUpdate in all reused [VRouteElement]
     258          12 :   Future<void> _updateUrl(
     259             :     String newUrl, {
     260             :     Map<String, String> newHistoryState = const {},
     261             :     bool fromBrowser = false,
     262             :     int? newSerialCount,
     263             :     Map<String, String> queryParameters = const {},
     264             :     bool isUrlExternal = false,
     265             :     bool isReplacement = false,
     266             :     bool openNewTab = false,
     267             :   }) async {
     268           0 :     assert(!kIsWeb || (!fromBrowser || newSerialCount != null));
     269             : 
     270             :     // Reset this to true, new url = new chance to report
     271          12 :     _doReportBackUrlToBrowser = true;
     272             : 
     273             :     // This should never happen, if it does this is in error in this package
     274             :     // We take care of passing the right parameters depending on the platform
     275          12 :     assert(kIsWeb || isReplacement == false,
     276             :         'This does not make sense to replace the route if you are not on the web. Please set isReplacement to false.');
     277             : 
     278          12 :     var newUri = Uri.parse(newUrl);
     279          12 :     final newPath = newUri.path;
     280          24 :     assert(!(newUri.queryParameters.isNotEmpty && queryParameters.isNotEmpty),
     281             :         'You used the queryParameters attribute but the url already contained queryParameters. The latter will be overwritten by the argument you gave');
     282          12 :     if (queryParameters.isEmpty) {
     283          12 :       queryParameters = newUri.queryParameters;
     284             :     }
     285             :     // Decode queryParameters
     286          12 :     queryParameters = queryParameters.map(
     287           3 :       (key, value) => MapEntry(key, Uri.decodeComponent(value)),
     288             :     );
     289             : 
     290             :     // Add the queryParameters to the url if needed
     291          12 :     if (queryParameters.isNotEmpty) {
     292           1 :       newUri = Uri(path: newPath, queryParameters: queryParameters);
     293             :     }
     294             : 
     295             :     // Get only the path from the url
     296          48 :     final path = (url != null) ? Uri.parse(url!).path : null;
     297             : 
     298             :     late final List<VRouteElement> deactivatedVRouteElements;
     299             :     late final List<VRouteElement> reusedVRouteElements;
     300             :     late final List<VRouteElement> initializedVRouteElements;
     301             :     late final List<VWidgetGuardMessageRoot> deactivatedVWidgetGuardsMessagesRoot;
     302             :     late final List<VWidgetGuardMessageRoot> reusedVWidgetGuardsMessagesRoot;
     303             :     VRoute? newVRoute;
     304             :     if (isUrlExternal) {
     305             :       newVRoute = null;
     306           0 :       deactivatedVRouteElements = <VRouteElement>[];
     307           0 :       reusedVRouteElements = <VRouteElement>[];
     308           0 :       initializedVRouteElements = <VRouteElement>[];
     309           0 :       deactivatedVWidgetGuardsMessagesRoot = <VWidgetGuardMessageRoot>[];
     310           0 :       reusedVWidgetGuardsMessagesRoot = <VWidgetGuardMessageRoot>[];
     311             :     } else {
     312             :       // Get the new route
     313          24 :       newVRoute = _rootVRouter.buildRoute(
     314          12 :         VPathRequestData(
     315          12 :           previousUrl: url,
     316             :           uri: newUri,
     317             :           historyState: newHistoryState,
     318          12 :           rootVRouterContext: _rootVRouterContext,
     319             :         ),
     320          12 :         parentVPathMatch: ValidVPathMatch(
     321             :           remainingPath: newPath,
     322          12 :           pathParameters: {},
     323             :           localPath: null,
     324             :         ),
     325             :       );
     326             : 
     327             :       if (newVRoute == null) {
     328           1 :         throw UnknownUrlVError(url: newUrl);
     329             :       }
     330             : 
     331             :       // This copy is necessary in order not to modify newVRoute.vRouteElements
     332          24 :       final newVRouteElements = List<VRouteElement>.from(newVRoute.vRouteElements);
     333             : 
     334          12 :       deactivatedVRouteElements = <VRouteElement>[];
     335          12 :       reusedVRouteElements = <VRouteElement>[];
     336          36 :       if (_vRoute.vRouteElements.isNotEmpty) {
     337          48 :         for (var vRouteElement in _vRoute.vRouteElements.reversed) {
     338             :           try {
     339          12 :             reusedVRouteElements.add(
     340          12 :               newVRouteElements.firstWhere(
     341          24 :                 (newVRouteElement) => (newVRouteElement == vRouteElement),
     342             :               ),
     343             :             );
     344          10 :           } on StateError {
     345          10 :             deactivatedVRouteElements.add(vRouteElement);
     346             :           }
     347             :         }
     348             :       }
     349           0 :       initializedVRouteElements = newVRouteElements
     350          12 :           .where(
     351          12 :             (newVRouteElement) =>
     352          24 :                 _vRoute.vRouteElements
     353          48 :                     .indexWhere((vRouteElement) => vRouteElement == newVRouteElement) ==
     354          12 :                 -1,
     355             :           )
     356          12 :           .toList();
     357             : 
     358             :       // Get deactivated and reused VWidgetGuards
     359          12 :       deactivatedVWidgetGuardsMessagesRoot = _vWidgetGuardMessagesRoot
     360          13 :           .where((vWidgetGuardMessageRoot) => deactivatedVRouteElements
     361           2 :               .contains(vWidgetGuardMessageRoot.associatedVRouteElement))
     362          12 :           .toList();
     363          12 :       reusedVWidgetGuardsMessagesRoot = _vWidgetGuardMessagesRoot
     364          13 :           .where((vWidgetGuardMessageRoot) =>
     365           2 :               reusedVRouteElements.contains(vWidgetGuardMessageRoot.associatedVRouteElement))
     366          12 :           .toList();
     367             :     }
     368             : 
     369          12 :     Map<String, String> historyStateToSave = {};
     370           0 :     void saveHistoryState(Map<String, String> historyState) {
     371           0 :       historyStateToSave.addAll(historyState);
     372             :     }
     373             : 
     374             :     // Instantiate VRedirector
     375          12 :     final vRedirector = VRedirector(
     376          12 :       context: _rootVRouterContext,
     377          12 :       from: url,
     378          12 :       to: newUri.toString(),
     379          12 :       previousVRouterData: RootVRouterData(
     380          12 :         child: Container(),
     381          12 :         historyState: historyState,
     382          24 :         pathParameters: _vRoute.pathParameters,
     383          12 :         queryParameters: this.queryParameters,
     384             :         state: this,
     385          12 :         url: url,
     386          12 :         previousUrl: previousUrl,
     387             :       ),
     388          12 :       newVRouterData: RootVRouterData(
     389          12 :         child: Container(),
     390             :         historyState: newHistoryState,
     391          12 :         pathParameters: newVRoute?.pathParameters ?? {},
     392             :         queryParameters: queryParameters,
     393             :         state: this,
     394          12 :         url: newUri.toString(),
     395          12 :         previousUrl: url,
     396             :       ),
     397             :     );
     398             : 
     399          12 :     if (url != null) {
     400             :       ///   1. Call beforeLeave in all deactivated [VWidgetGuard]
     401          12 :       for (var vWidgetGuardMessageRoot in deactivatedVWidgetGuardsMessagesRoot) {
     402           4 :         await vWidgetGuardMessageRoot.vWidgetGuard.beforeLeave(vRedirector, saveHistoryState);
     403           1 :         if (!vRedirector._shouldUpdate) {
     404           2 :           await _abortUpdateUrl(
     405             :             fromBrowser: fromBrowser,
     406           1 :             serialCount: _serialCount,
     407             :             newSerialCount: newSerialCount,
     408             :           );
     409             : 
     410           1 :           vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
     411           0 :                   .getChildVRouteElementNode(
     412           0 :                       vRouteElement: vWidgetGuardMessageRoot.associatedVRouteElement) ??
     413           0 :               _vRoute.vRouteElementNode);
     414             :           return;
     415             :         }
     416             :       }
     417             : 
     418             :       ///   2. Call beforeLeave in all deactivated [VRouteElement]
     419          20 :       for (var vRouteElement in deactivatedVRouteElements) {
     420          18 :         await vRouteElement.beforeLeave(vRedirector, saveHistoryState);
     421           9 :         if (!vRedirector._shouldUpdate) {
     422           2 :           await _abortUpdateUrl(
     423             :             fromBrowser: fromBrowser,
     424           1 :             serialCount: _serialCount,
     425             :             newSerialCount: newSerialCount,
     426             :           );
     427           1 :           vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
     428           0 :                   .getChildVRouteElementNode(vRouteElement: vRouteElement) ??
     429           0 :               _vRoute.vRouteElementNode);
     430             :           return;
     431             :         }
     432             :       }
     433             : 
     434             :       /// 3. Call beforeLeave in the [VRouter]
     435          33 :       await _rootVRouter.beforeLeave(vRedirector, saveHistoryState);
     436          11 :       if (!vRedirector._shouldUpdate) {
     437           2 :         await _abortUpdateUrl(
     438             :           fromBrowser: fromBrowser,
     439           1 :           serialCount: _serialCount,
     440             :           newSerialCount: newSerialCount,
     441             :         );
     442           4 :         vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode);
     443             :         return;
     444             :       }
     445             :     }
     446             : 
     447             :     if (!isUrlExternal) {
     448             :       /// 4. Call beforeEnter in the [VRouter]
     449          36 :       await _rootVRouter.beforeEnter(vRedirector);
     450          12 :       if (!vRedirector._shouldUpdate) {
     451           0 :         await _abortUpdateUrl(
     452             :           fromBrowser: fromBrowser,
     453           0 :           serialCount: _serialCount,
     454             :           newSerialCount: newSerialCount,
     455             :         );
     456           0 :         vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode);
     457             :         return;
     458             :       }
     459             : 
     460             :       /// 5. Call beforeEnter in all initialized [VRouteElement] of the new route
     461          24 :       for (var vRouteElement in initializedVRouteElements) {
     462          24 :         await vRouteElement.beforeEnter(vRedirector);
     463          12 :         if (!vRedirector._shouldUpdate) {
     464           4 :           await _abortUpdateUrl(
     465             :             fromBrowser: fromBrowser,
     466           2 :             serialCount: _serialCount,
     467             :             newSerialCount: newSerialCount,
     468             :           );
     469           5 :           vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
     470           1 :                   .getChildVRouteElementNode(vRouteElement: vRouteElement) ??
     471           2 :               _vRoute.vRouteElementNode);
     472             :           return;
     473             :         }
     474             :       }
     475             : 
     476             :       /// 6. Call beforeUpdate in all reused [VWidgetGuard]
     477          13 :       for (var vWidgetGuardMessageRoot in reusedVWidgetGuardsMessagesRoot) {
     478           4 :         await vWidgetGuardMessageRoot.vWidgetGuard.beforeUpdate(vRedirector);
     479           1 :         if (!vRedirector._shouldUpdate) {
     480           2 :           await _abortUpdateUrl(
     481             :             fromBrowser: fromBrowser,
     482           1 :             serialCount: _serialCount,
     483             :             newSerialCount: newSerialCount,
     484             :           );
     485             : 
     486           1 :           vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
     487           0 :                   .getChildVRouteElementNode(
     488           0 :                       vRouteElement: vWidgetGuardMessageRoot.associatedVRouteElement) ??
     489           0 :               _vRoute.vRouteElementNode);
     490             :           return;
     491             :         }
     492             :       }
     493             : 
     494             :       /// 7. Call beforeUpdate in all reused [VRouteElement]
     495          24 :       for (var vRouteElement in reusedVRouteElements) {
     496          24 :         await vRouteElement.beforeUpdate(vRedirector);
     497          12 :         if (!vRedirector._shouldUpdate) {
     498           2 :           await _abortUpdateUrl(
     499             :             fromBrowser: fromBrowser,
     500           1 :             serialCount: _serialCount,
     501             :             newSerialCount: newSerialCount,
     502             :           );
     503             : 
     504           1 :           vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
     505           0 :                   .getChildVRouteElementNode(vRouteElement: vRouteElement) ??
     506           0 :               _vRoute.vRouteElementNode);
     507             :           return;
     508             :         }
     509             :       }
     510             :     }
     511             : 
     512          12 :     final oldSerialCount = _serialCount;
     513             : 
     514          12 :     if (historyStateToSave.isNotEmpty && path != null) {
     515             :       if (!kIsWeb) {
     516           0 :         log(
     517             :           ' WARNING: Tried to store the state $historyStateToSave while not on the web. State saving/restoration only work on the web.\n'
     518             :           'You can safely ignore this message if you just want this functionality on the web.',
     519             :           name: 'VRouter',
     520             :         );
     521             :       } else {
     522             :         ///   The historyStates got in beforeLeave are stored   ///
     523             :         // If we come from the browser, chances are we already left the page
     524             :         // So we need to:
     525             :         //    1. Go back to where we were
     526             :         //    2. Save the historyState
     527             :         //    3. And go back again to the place
     528           0 :         if (kIsWeb && fromBrowser && oldSerialCount != newSerialCount) {
     529           0 :           _ignoreNextBrowserCalls = true;
     530           0 :           BrowserHelpers.browserGo(oldSerialCount - newSerialCount!);
     531           0 :           await BrowserHelpers.onBrowserPopState.firstWhere((element) {
     532           0 :             return BrowserHelpers.getHistorySerialCount() == oldSerialCount;
     533             :           });
     534             :         }
     535           0 :         BrowserHelpers.replaceHistoryState(jsonEncode({
     536             :           'serialCount': oldSerialCount,
     537           0 :           'historyState': jsonEncode(historyStateToSave),
     538             :         }));
     539             : 
     540           0 :         if (kIsWeb && fromBrowser && oldSerialCount != newSerialCount) {
     541           0 :           BrowserHelpers.browserGo(newSerialCount! - oldSerialCount);
     542           0 :           await BrowserHelpers.onBrowserPopState.firstWhere(
     543           0 :               (element) => BrowserHelpers.getHistorySerialCount() == newSerialCount);
     544           0 :           _ignoreNextBrowserCalls = false;
     545             :         }
     546             :       }
     547             :     }
     548             : 
     549             :     /// Leave if the url is external
     550             :     if (isUrlExternal) {
     551           0 :       _ignoreNextBrowserCalls = true;
     552           0 :       await BrowserHelpers.pushExternal(newUri.toString(), openNewTab: openNewTab);
     553             :       return;
     554             :     }
     555             : 
     556             :     ///   The state of the VRouter changes            ///
     557          12 :     final oldUrl = url;
     558             : 
     559             :     if (isReplacement) {
     560           0 :       _doReportBackUrlToBrowser = false;
     561           0 :       _ignoreNextBrowserCalls = true;
     562           0 :       if (BrowserHelpers.getPathAndQuery(routerMode: mode) != newUri.toString()) {
     563           0 :         BrowserHelpers.pushReplacement(newUri.toString(), routerMode: mode);
     564           0 :         if (BrowserHelpers.getPathAndQuery(routerMode: mode) != newUri.toString()) {
     565           0 :           await BrowserHelpers.onBrowserPopState.firstWhere((element) =>
     566           0 :               BrowserHelpers.getPathAndQuery(routerMode: mode) == newUri.toString());
     567             :         }
     568             :       }
     569           0 :       BrowserHelpers.replaceHistoryState(jsonEncode({
     570           0 :         'serialCount': _serialCount,
     571           0 :         'historyState': jsonEncode(newHistoryState),
     572             :       }));
     573           0 :       _ignoreNextBrowserCalls = false;
     574             :     } else {
     575             :       // If this comes from the browser, newSerialCount is not null
     576             :       // If this comes from a user:
     577             :       //    - If he/she pushes the same url+historyState, flutter does not create a new history entry so the serialCount remains the same
     578             :       //    - Else the serialCount gets increased by 1
     579          12 :       _serialCount = newSerialCount ??
     580          48 :           _serialCount + ((newUrl != url || newHistoryState != historyState) ? 1 : 0);
     581             :     }
     582          12 :     _updateStateVariables(
     583             :       newVRoute!,
     584          12 :       newUri.toString(),
     585             :       historyState: newHistoryState,
     586             :       queryParameters: queryParameters,
     587           0 :       deactivatedVRouteElements: deactivatedVRouteElements,
     588             :     );
     589          12 :     notifyListeners();
     590             : 
     591             :     // We need to do this after rebuild as completed so that the user can have access
     592             :     // to the new state variables
     593          36 :     WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
     594             :       /// 8. Call afterEnter in all initialized [VWidgetGuard]
     595             :       // This is done automatically by VNotificationGuard
     596             : 
     597             :       /// 9. Call afterEnter all initialized [VRouteElement]
     598          24 :       for (var vRouteElement in initializedVRouteElements) {
     599          12 :         vRouteElement.afterEnter(
     600          12 :           _rootVRouterContext,
     601             :           // TODO: Change this to local context? This might imply that we need a global key which is not ideal
     602             :           oldUrl,
     603          12 :           newUri.toString(),
     604             :         );
     605             :       }
     606             : 
     607             :       /// 10. Call afterEnter in the [VRouter]
     608          48 :       _rootVRouter.afterEnter(_rootVRouterContext, oldUrl, newUri.toString());
     609             : 
     610             :       /// 11. Call afterUpdate in all reused [VWidgetGuard]
     611          13 :       for (var vWidgetGuardMessageRoot in reusedVWidgetGuardsMessagesRoot) {
     612           3 :         vWidgetGuardMessageRoot.vWidgetGuard.afterUpdate(
     613           1 :           vWidgetGuardMessageRoot.localContext,
     614             :           oldUrl,
     615           1 :           newUri.toString(),
     616             :         );
     617             :       }
     618             : 
     619             :       /// 12. Call afterUpdate in all reused [VRouteElement]
     620          24 :       for (var vRouteElement in reusedVRouteElements) {
     621          12 :         vRouteElement.afterUpdate(
     622          12 :           _rootVRouterContext,
     623             :           // TODO: Change this to local context? This might imply that we need a global key which is not ideal
     624             :           oldUrl,
     625          12 :           newUri.toString(),
     626             :         );
     627             :       }
     628             :     });
     629             :   }
     630             : 
     631             :   /// This function is used in [updateUrl] when the update should be canceled
     632             :   /// This happens and vRedirector is used to stop the navigation
     633             :   ///
     634             :   /// On mobile nothing happens
     635             :   /// On the web, if the browser already navigated away, we have to navigate back to where we were
     636             :   ///
     637             :   /// Note that this should be called before setState, otherwise it is useless and cannot prevent a state spread
     638             :   ///
     639             :   /// newSerialCount should not be null if the updateUrl came from the Browser
     640           4 :   Future<void> _abortUpdateUrl({
     641             :     required bool fromBrowser,
     642             :     required int serialCount,
     643             :     required int? newSerialCount,
     644             :   }) async {
     645             :     // If the url change comes from the browser, chances are the url is already changed
     646             :     // So we have to navigate back to the old url (stored in _url)
     647             :     // Note: in future version it would be better to delete the last url of the browser
     648             :     //        but it is not yet possible
     649             :     if (kIsWeb &&
     650             :         fromBrowser &&
     651           0 :         (BrowserHelpers.getHistorySerialCount() ?? 0) != serialCount) {
     652           0 :       _ignoreNextBrowserCalls = true;
     653           0 :       BrowserHelpers.browserGo(serialCount - newSerialCount!);
     654           0 :       await BrowserHelpers.onBrowserPopState.firstWhere((element) {
     655           0 :         return BrowserHelpers.getHistorySerialCount() == serialCount;
     656             :       });
     657           0 :       _ignoreNextBrowserCalls = false;
     658             :     }
     659             :     return;
     660             :   }
     661             : 
     662             :   /// Performs a systemPop cycle:
     663             :   ///   1. Call onPop in all active [VWidgetGuards]
     664             :   ///   2. Call onPop in all [VRouteElement]
     665             :   ///   3. Call onPop of VRouter
     666             :   ///   4. Update the url to the one found in [_defaultPop]
     667           8 :   Future<void> _pop(
     668             :     VRouteElement elementToPop, {
     669             :     Map<String, String> pathParameters = const {},
     670             :     Map<String, String> queryParameters = const {},
     671             :     Map<String, String> newHistoryState = const {},
     672             :   }) async {
     673           8 :     assert(url != null);
     674             : 
     675             :     // Get information on where to pop from _defaultPop
     676           8 :     final defaultPopResult = _defaultPop(
     677             :       elementToPop,
     678             :       pathParameters: pathParameters,
     679             :       queryParameters: queryParameters,
     680             :       newHistoryState: newHistoryState,
     681             :     );
     682             : 
     683           7 :     final vRedirector = defaultPopResult.vRedirector;
     684             : 
     685             :     final poppedVRouteElements = defaultPopResult.poppedVRouteElements;
     686          21 : 
     687             :     final List<VWidgetGuardMessageRoot> poppedVWidgetGuardsMessagesRoot =
     688           0 :         _vWidgetGuardMessagesRoot
     689             :             .where((vWidgetGuardMessageRoot) =>
     690             :                 poppedVRouteElements.contains(vWidgetGuardMessageRoot.associatedVRouteElement))
     691             :             .toList();
     692           7 : 
     693             :     /// 1. Call onPop in all popped [VWidgetGuards]
     694             :     for (var vWidgetGuardMessageRoot in poppedVWidgetGuardsMessagesRoot) {
     695             :       await vWidgetGuardMessageRoot.vWidgetGuard.onPop(vRedirector);
     696             :       if (!vRedirector.shouldUpdate) {
     697             :         vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
     698          21 :                 .getChildVRouteElementNode(
     699             :                     vRouteElement: vWidgetGuardMessageRoot.associatedVRouteElement) ??
     700             :             _vRoute.vRouteElementNode);
     701           7 :         return;
     702             :       }
     703             :     }
     704          14 : 
     705             :     /// 2. Call onPop in all popped [VRouteElement]
     706             :     for (var vRouteElement in poppedVRouteElements) {
     707           7 :       await vRouteElement.onPop(vRedirector);
     708           7 :       if (!vRedirector.shouldUpdate) {
     709           0 :         vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
     710           7 :                 .getChildVRouteElementNode(vRouteElement: vRouteElement) ??
     711             :             _vRoute.vRouteElementNode);
     712             :         return;
     713           7 :       }
     714           0 :     }
     715           0 : 
     716           0 :     /// 3. Call onPop of VRouter
     717           0 :     await _rootVRouter.onPop(vRedirector);
     718           0 :     if (!vRedirector.shouldUpdate) {
     719           0 :       vRedirector._redirectFunction?.call(
     720             :           _vRoute.vRouteElementNode.getChildVRouteElementNode(vRouteElement: _rootVRouter) ??
     721             :               _vRoute.vRouteElementNode);
     722             :       return;
     723             :     }
     724             : 
     725          14 :     /// 4. Update the url to the one found in [_defaultPop]
     726          14 :     if (vRedirector.newVRouterData != null) {
     727           7 :       _updateUrl(vRedirector.to!,
     728           1 :           queryParameters: queryParameters, newHistoryState: newHistoryState);
     729           0 :     } else if (Platform.isAndroid || Platform.isIOS) {
     730           0 :       // If we didn't find a url to go to, we are at the start of the stack
     731             :       // so we close the app on mobile
     732             :       MoveToBackground.moveTaskToBack();
     733             :     }
     734             :   }
     735             : 
     736          21 :   /// Performs a systemPop cycle:
     737           7 :   /// 1. Call onSystemPop in all active [VWidgetGuards] if implemented, else onPop
     738           0 :   /// 2. Call onSystemPop in all [VRouteElement] if implemented, else onPop
     739           0 :   /// 3. Call onSystemPop of VRouter if implemented, else onPop
     740           0 :   /// 4. Update the url to the one found in [_defaultPop]
     741             :   Future<void> _systemPop(
     742             :     VRouteElement elementToPop, {
     743             :     Map<String, String> pathParameters = const {},
     744             :     Map<String, String> queryParameters = const {},
     745           7 :     Map<String, String> newHistoryState = const {},
     746          14 :   }) async {
     747             :     assert(url != null);
     748           0 : 
     749             :     // Get information on where to pop from _defaultPop
     750             :     final defaultPopResult = _defaultPop(
     751           0 :       elementToPop,
     752             :       pathParameters: pathParameters,
     753             :       queryParameters: queryParameters,
     754             :       newHistoryState: newHistoryState,
     755             :     );
     756             : 
     757             :     final vRedirector = defaultPopResult.vRedirector;
     758             : 
     759             :     final poppedVRouteElements = defaultPopResult.poppedVRouteElements;
     760           6 : 
     761             :     final List<VWidgetGuardMessageRoot> poppedVWidgetGuardsMessagesRoot =
     762             :         _vWidgetGuardMessagesRoot
     763             :             .where((vWidgetGuardMessageRoot) =>
     764             :                 poppedVRouteElements.contains(vWidgetGuardMessageRoot.associatedVRouteElement))
     765             :             .toList();
     766           6 : 
     767             :     /// 1. Call onSystemPop in all popping [VWidgetGuards] if implemented, else onPop
     768             :     for (var vWidgetGuardMessageRoot in poppedVWidgetGuardsMessagesRoot) {
     769           6 :       if (vWidgetGuardMessageRoot.vWidgetGuard.onSystemPop != VPopHandler._voidOnSystemPop) {
     770             :         await vWidgetGuardMessageRoot.vWidgetGuard.onSystemPop(vRedirector);
     771             :       } else {
     772             :         await vWidgetGuardMessageRoot.vWidgetGuard.onPop(vRedirector);
     773             :       }
     774             :       if (!vRedirector.shouldUpdate) {
     775             :         vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
     776           6 :                 .getChildVRouteElementNode(
     777             :                     vRouteElement: vWidgetGuardMessageRoot.associatedVRouteElement) ??
     778           6 :             _vRoute.vRouteElementNode);
     779             :         return;
     780             :       }
     781           6 :     }
     782           6 : 
     783           0 :     /// 2. Call onSystemPop in all popped [VRouteElement] if implemented, else onPop
     784           6 :     for (var vRouteElement in poppedVRouteElements) {
     785             :       if (vRouteElement.onSystemPop != VPopHandler._voidOnSystemPop) {
     786             :         await vRouteElement.onSystemPop(vRedirector);
     787           6 :       } else {
     788           0 :         await vRouteElement.onPop(vRedirector);
     789           0 :       }
     790             :       if (!vRedirector.shouldUpdate) {
     791           0 :         vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
     792             :                 .getChildVRouteElementNode(vRouteElement: vRouteElement) ??
     793           0 :             _vRoute.vRouteElementNode);
     794           0 :         return;
     795           0 :       }
     796           0 :     }
     797           0 : 
     798             :     /// 3. Call onSystemPop of VRouter if implemented, else onPop
     799             :     if (_rootVRouter.onSystemPop != VPopHandler._voidOnSystemPop) {
     800             :       await _rootVRouter.onSystemPop(vRedirector);
     801             :     } else {
     802             :       await _rootVRouter.onPop(vRedirector);
     803          12 :     }
     804          12 :     if (!vRedirector.shouldUpdate) {
     805          12 :       vRedirector._redirectFunction?.call(
     806             :           _vRoute.vRouteElementNode.getChildVRouteElementNode(vRouteElement: _rootVRouter) ??
     807           0 :               _vRoute.vRouteElementNode);
     808             :       return;
     809           6 :     }
     810           1 : 
     811           0 :     /// 4. Update the url to the one found in [_defaultPop]
     812           0 :     if (vRedirector.newVRouterData != null) {
     813             :       _updateUrl(vRedirector.to!,
     814             :           queryParameters: queryParameters, newHistoryState: newHistoryState);
     815             :     } else if (!kIsWeb) {
     816             :       // If we didn't find a url to go to, we are at the start of the stack
     817             :       // so we close the app on mobile
     818          18 :       MoveToBackground.moveTaskToBack();
     819          18 :     }
     820             :   }
     821           0 : 
     822             :   /// Uses [VRouteElement.getPathFromPop] to determine the new path after popping [elementToPop]
     823           6 :   ///
     824           0 :   /// See:
     825           0 :   ///   * [VWidgetGuard.onPop] to override this behaviour locally
     826           0 :   ///   * [VRouteElement.onPop] to override this behaviour on a on a route level
     827             :   ///   * [VRouter.onPop] to override this behaviour on a global level
     828             :   ///   * [VWidgetGuard.onSystemPop] to override this behaviour locally
     829             :   ///                               when the call comes from the system
     830             :   ///   * [VRouteElement.onSystemPop] to override this behaviour on a route level
     831           6 :   ///                               when the call comes from the system
     832          12 :   ///   * [VRouter.onSystemPop] to override this behaviour on a global level
     833             :   ///                               when the call comes from the system
     834             :   DefaultPopResult _defaultPop(
     835             :     VRouteElement elementToPop, {
     836             :     Map<String, String> pathParameters = const {},
     837           0 :     Map<String, String> queryParameters = const {},
     838             :     Map<String, String> newHistoryState = const {},
     839             :   }) {
     840             :     assert(url != null);
     841             :     // Encode the path parameters
     842             :     pathParameters =
     843             :         pathParameters.map((key, value) => MapEntry(key, Uri.encodeComponent(value)));
     844             : 
     845             :     // We don't use widget.getPathFromPop because widget.routes might have changed with a setState
     846             :     final getPathFromPopResult = _vRoute.vRouteElementNode.vRouteElement.getPathFromPop(
     847             :       elementToPop,
     848             :       pathParameters: pathParameters,
     849             :       parentPathResult: ValidParentPathResult(path: null, pathParameters: {}),
     850             :     );
     851             : 
     852             :     if (getPathFromPopResult is ErrorGetPathFromPopResult) {
     853           8 :       throw getPathFromPopResult;
     854             :     }
     855             : 
     856             :     final newPath = (getPathFromPopResult as FoundPopResult).path;
     857             : 
     858             :     // This url will be not null if we find a route to go to
     859           8 :     late final String? newUrl;
     860             :     late final RootVRouterData? newVRouterData;
     861             : 
     862          14 :     // If newPath is empty then the app should be put in the background (for mobile)
     863             :     if (newPath != null) {
     864             :       // Integrate the given query parameters
     865          32 :       newUrl = Uri.tryParse(newPath)
     866             :           ?.replace(queryParameters: (queryParameters.isNotEmpty) ? queryParameters : null)
     867             :           .toString();
     868          16 : 
     869             :       newVRouterData = RootVRouterData(
     870             :         child: Container(),
     871           8 :         historyState: newHistoryState,
     872             :         pathParameters: pathParameters,
     873             :         queryParameters: queryParameters,
     874             :         url: newUrl,
     875           7 :         previousUrl: url,
     876             :         state: this,
     877             :       );
     878             :     } else {
     879             :       newUrl = null;
     880             :       newVRouterData = null;
     881             :     }
     882             : 
     883             :     final vNodeToPop =
     884           7 :         _vRoute.vRouteElementNode.getVRouteElementNodeFromVRouteElement(elementToPop);
     885          14 : 
     886           7 :     assert(vNodeToPop != null);
     887             : 
     888           7 :     // This is the list of [VRouteElement]s that where not necessary expected to pop but did because of
     889           7 :     // the pop of [elementToPop]
     890             :     final poppedVRouteElementsFromPopResult = getPathFromPopResult.poppedVRouteElements;
     891             : 
     892             :     // This is predictable list of [VRouteElement]s that are expected to pop because they are
     893           0 :     // in the nestedRoutes or stackedRoutes of [elementToPop] [VRouteElementNode]
     894           7 :     // We take the reversed because we when to call onPop in the deepest nested
     895             :     // [VRouteElement] first
     896             :     final poppedVRouteElementsFromVNode = vNodeToPop!.getVRouteElements().reversed.toList();
     897             : 
     898           0 :     // This is the list of every [VRouteElement] which should pop
     899           0 :     final poppedVRouteElements =
     900             :         poppedVRouteElementsFromVNode + poppedVRouteElementsFromPopResult;
     901             : 
     902           7 :     // elementToPop should have a duplicate so we remove it
     903           7 :     poppedVRouteElements.removeAt(poppedVRouteElements.indexOf(elementToPop));
     904           7 : 
     905           7 :     return DefaultPopResult(
     906           0 :       vRedirector: VRedirector(
     907           7 :         context: _rootVRouterContext,
     908           7 :         from: url,
     909           7 :         to: newUrl,
     910          14 :         previousVRouterData: RootVRouterData(
     911             :           child: Container(),
     912             :           historyState: historyState,
     913           7 :           pathParameters: _vRoute.pathParameters,
     914           7 :           queryParameters: queryParameters,
     915             :           state: this,
     916           0 :           previousUrl: previousUrl,
     917             :           url: url,
     918           7 :         ),
     919             :         newVRouterData: newVRouterData,
     920             :       ),
     921             :       poppedVRouteElements: poppedVRouteElements,
     922             :     );
     923           0 :   }
     924           0 : 
     925             :   /// This replaces the current history state of [VRouter] with given one
     926             :   void replaceHistoryState(Map<String, String> newHistoryState) {
     927             :     pushReplacement((url != null) ? Uri.parse(url!).path : '/', historyState: newHistoryState);
     928             :   }
     929             : 
     930             :   /// WEB ONLY
     931           0 :   /// Save the state if needed before the app gets unloaded
     932           0 :   /// Mind that this happens when the user enter a url manually in the
     933             :   /// browser so we can't prevent him from leaving the page
     934           0 :   void _onBeforeUnload() async {
     935           0 :     if (url == null) return;
     936           0 : 
     937             :     Map<String, String> historyStateToSave = {};
     938             :     void saveHistoryState(Map<String, String> historyState) {
     939             :       historyStateToSave.addAll(historyState);
     940           0 :     }
     941           0 : 
     942           0 :     // Instantiate VRedirector
     943             :     final vRedirector = VRedirector(
     944           0 :       context: _rootVRouterContext,
     945           0 :       from: url,
     946           0 :       to: null,
     947           0 :       previousVRouterData: RootVRouterData(
     948           0 :         child: Container(),
     949             :         historyState: historyState,
     950           0 :         pathParameters: _vRoute.pathParameters,
     951           0 :         queryParameters: this.queryParameters,
     952             :         state: this,
     953             :         url: url,
     954             :         previousUrl: previousUrl,
     955             :       ),
     956             :       newVRouterData: null,
     957           0 :     );
     958           0 : 
     959             :     ///   1. Call beforeLeave in all deactivated [VWidgetGuard]
     960             :     for (var vWidgetGuardMessageRoot in _vWidgetGuardMessagesRoot) {
     961             :       await vWidgetGuardMessageRoot.vWidgetGuard.beforeLeave(vRedirector, saveHistoryState);
     962           0 :     }
     963           0 : 
     964             :     ///   2. Call beforeLeave in all deactivated [VRouteElement] and [VRouter]
     965             :     for (var vRouteElement in _vRoute.vRouteElements.reversed) {
     966           0 :       await vRouteElement.beforeLeave(vRedirector, saveHistoryState);
     967             :     }
     968           0 : 
     969           0 :     if (historyStateToSave.isNotEmpty) {
     970           0 :       ///   The historyStates got in beforeLeave are stored   ///
     971             :       BrowserHelpers.replaceHistoryState(jsonEncode({
     972             :         'serialCount': _serialCount,
     973             :         'historyState': jsonEncode(historyStateToSave),
     974             :       }));
     975             :     }
     976             :   }
     977             : 
     978             :   /// Starts a pop cycle
     979             :   ///
     980             :   /// Pop cycle:
     981             :   ///   1. onPop is called in all [VNavigationGuard]s
     982             :   ///   2. onPop is called in all [VRouteElement]s of the current route
     983             :   ///   3. onPop is called in [VRouter]
     984           3 :   ///
     985             :   /// In any of the above steps, we can use [vRedirector] if you want to redirect or
     986             :   /// stop the navigation
     987             :   Future<void> pop({
     988             :     Map<String, String> pathParameters = const {},
     989           3 :     Map<String, String> queryParameters = const {},
     990           9 :     Map<String, String> newHistoryState = const {},
     991             :   }) async {
     992             :     _pop(
     993             :       _vRoute.vRouteElementNode.getVRouteElementToPop(),
     994             :       pathParameters: pathParameters,
     995             :       queryParameters: queryParameters,
     996             :       newHistoryState: newHistoryState,
     997             :     );
     998             :   }
     999             : 
    1000             :   /// Starts a systemPop cycle
    1001             :   ///
    1002             :   /// systemPop cycle:
    1003             :   ///   1. onSystemPop (or onPop if not implemented) is called in all VNavigationGuards
    1004             :   ///   2. onSystemPop (or onPop if not implemented) is called in the nested-most VRouteElement of the current route
    1005             :   ///   3. onSystemPop (or onPop if not implemented) is called in VRouter
    1006           2 :   ///
    1007             :   /// In any of the above steps, we can use a [VRedirector] if you want to redirect or
    1008             :   /// stop the navigation
    1009             :   Future<void> systemPop({
    1010             :     Map<String, String> pathParameters = const {},
    1011           2 :     Map<String, String> queryParameters = const {},
    1012           6 :     Map<String, String> newHistoryState = const {},
    1013             :   }) async {
    1014             :     _systemPop(
    1015             :       _vRoute.vRouteElementNode.getVRouteElementToPop(),
    1016             :       pathParameters: pathParameters,
    1017             :       queryParameters: queryParameters,
    1018             :       newHistoryState: newHistoryState,
    1019             :     );
    1020             :   }
    1021             : 
    1022             :   /// Pushes the new route of the given url on top of the current one
    1023             :   /// A path can be of one of two forms:
    1024             :   ///   * stating with '/', in which case we just navigate
    1025             :   ///     to the given path
    1026             :   ///   * not starting with '/', in which case we append the
    1027             :   ///     current path to the given one
    1028             :   ///
    1029             :   /// We can also specify queryParameters, either by directly
    1030             :   /// putting them is the url or by providing a Map using [queryParameters]
    1031             :   ///
    1032          11 :   /// We can also put a state to the next route, this state will
    1033             :   /// be a router state (this is the only kind of state that we can
    1034             :   /// push) accessible with VRouter.of(context).historyState
    1035             :   void push(
    1036             :     String newUrl, {
    1037          11 :     Map<String, String> queryParameters = const {},
    1038           2 :     Map<String, String> historyState = const {},
    1039           1 :   }) {
    1040             :     if (!newUrl.startsWith('/')) {
    1041           3 :       if (url == null) {
    1042           4 :         throw InvalidPushVError(url: newUrl);
    1043             :       }
    1044             :       final currentPath = Uri.parse(url!).path;
    1045          11 :       newUrl = currentPath + (currentPath.endsWith('/') ? '' : '/') + '$newUrl';
    1046             :     }
    1047             : 
    1048             :     _updateUrl(
    1049             :       newUrl,
    1050             :       queryParameters: queryParameters,
    1051             :       newHistoryState: historyState,
    1052             :     );
    1053             :   }
    1054             : 
    1055             :   /// Updates the url given a [VRouteElement] name
    1056             :   ///
    1057             :   /// We can also specify path parameters to inject into the new path
    1058             :   ///
    1059             :   /// We can also specify queryParameters, either by directly
    1060             :   /// putting them is the url or by providing a Map using [queryParameters]
    1061             :   ///
    1062             :   /// We can also put a state to the next route, this state will
    1063             :   /// be a router state (this is the only kind of state that we can
    1064             :   /// push) accessible with VRouter.of(context).historyState
    1065             :   ///
    1066             :   /// After finding the url and taking charge of the path parameters,
    1067           6 :   /// it updates the url
    1068             :   ///
    1069             :   /// To specify a name, see [VRouteElement.name]
    1070             :   void pushNamed(
    1071             :     String name, {
    1072             :     Map<String, String> pathParameters = const {},
    1073           6 :     Map<String, String> queryParameters = const {},
    1074             :     Map<String, String> historyState = const {},
    1075             :   }) {
    1076             :     _updateUrlFromName(name,
    1077             :         pathParameters: pathParameters,
    1078             :         queryParameters: queryParameters,
    1079             :         newHistoryState: historyState);
    1080             :   }
    1081             : 
    1082             :   /// Replace the current one by the new route corresponding to the given url
    1083             :   /// The difference with [push] is that this overwrites the current browser history entry
    1084             :   /// If you are on mobile, this is the same as push
    1085             :   /// Path can be of one of two forms:
    1086             :   ///   * stating with '/', in which case we just navigate
    1087             :   ///     to the given path
    1088             :   ///   * not starting with '/', in which case we append the
    1089             :   ///     current path to the given one
    1090             :   ///
    1091             :   /// We can also specify queryParameters, either by directly
    1092             :   /// putting them is the url or by providing a Map using [queryParameters]
    1093             :   ///
    1094           5 :   /// We can also put a state to the next route, this state will
    1095             :   /// be a router state (this is the only kind of state that we can
    1096             :   /// push) accessible with VRouter.of(context).historyState
    1097             :   void pushReplacement(
    1098             :     String newUrl, {
    1099             :     Map<String, String> queryParameters = const {},
    1100             :     Map<String, String> historyState = const {},
    1101           5 :   }) {
    1102             :     // If not on the web, this is the same as push
    1103             :     if (!kIsWeb) {
    1104           0 :       return push(newUrl, queryParameters: queryParameters, historyState: historyState);
    1105           0 :     }
    1106           0 : 
    1107             :     if (!newUrl.startsWith('/')) {
    1108           0 :       if (url == null) {
    1109           0 :         throw InvalidPushVError(url: newUrl);
    1110             :       }
    1111             :       final currentPath = Uri.parse(url!).path;
    1112             :       newUrl = currentPath + '/$newUrl';
    1113           0 :     }
    1114             : 
    1115             :     // Update the url, setting isReplacement to true
    1116             :     _updateUrl(
    1117             :       newUrl,
    1118             :       queryParameters: queryParameters,
    1119             :       newHistoryState: historyState,
    1120             :       isReplacement: true,
    1121             :     );
    1122             :   }
    1123             : 
    1124             :   /// Replace the url given a [VRouteElement] name
    1125             :   /// The difference with [pushNamed] is that this overwrites the current browser history entry
    1126             :   ///
    1127             :   /// We can also specify path parameters to inject into the new path
    1128             :   ///
    1129             :   /// We can also specify queryParameters, either by directly
    1130             :   /// putting them is the url or by providing a Map using [queryParameters]
    1131             :   ///
    1132             :   /// We can also put a state to the next route, this state will
    1133             :   /// be a router state (this is the only kind of state that we can
    1134             :   /// push) accessible with VRouter.of(context).historyState
    1135             :   ///
    1136             :   /// After finding the url and taking charge of the path parameters
    1137           0 :   /// it updates the url
    1138             :   ///
    1139             :   /// To specify a name, see [VPath.name]
    1140             :   void pushReplacementNamed(
    1141             :     String name, {
    1142             :     Map<String, String> pathParameters = const {},
    1143           0 :     Map<String, String> queryParameters = const {},
    1144             :     Map<String, String> historyState = const {},
    1145             :   }) {
    1146             :     _updateUrlFromName(name,
    1147             :         pathParameters: pathParameters,
    1148             :         queryParameters: queryParameters,
    1149             :         newHistoryState: historyState,
    1150             :         isReplacement: true);
    1151             :   }
    1152             : 
    1153             :   /// Goes to an url which is not in the app
    1154           0 :   ///
    1155           0 :   /// On the web, you can set [openNewTab] to true to open this url
    1156             :   /// in a new tab
    1157             :   void pushExternal(String newUrl, {bool openNewTab = false}) =>
    1158             :       _updateUrl(newUrl, isUrlExternal: true, openNewTab: openNewTab);
    1159           0 : 
    1160           0 :   /// handles systemPop
    1161           0 :   @override
    1162           0 :   Future<bool> popRoute() async {
    1163             :     await _systemPop(
    1164             :       _vRoute.vRouteElementNode.getVRouteElementToPop(),
    1165             :       pathParameters: pathParameters,
    1166             :     );
    1167             :     return true;
    1168             :   }
    1169          12 : 
    1170          24 :   /// Navigation state to app state
    1171             :   @override
    1172             :   Future<void> setNewRoutePath(RouteInformation routeInformation) async {
    1173           0 :     if (routeInformation.location != null && !_ignoreNextBrowserCalls) {
    1174           0 :       // Get the new state
    1175          12 :       final newState = (kIsWeb)
    1176             :           ? Map<String, dynamic>.from(jsonDecode((routeInformation.state as String?) ??
    1177             :               (BrowserHelpers.getHistoryState() ?? '{}')))
    1178             :           : <String, dynamic>{};
    1179             : 
    1180          12 :       // Get the new serial count
    1181             :       int? newSerialCount;
    1182           0 :       try {
    1183             :         newSerialCount = newState['serialCount'];
    1184             :         // ignore: empty_catches
    1185             :       } on FormatException {}
    1186          36 : 
    1187             :       // Get the new history state
    1188             :       final newHistoryState =
    1189           0 :           Map<String, String>.from(jsonDecode(newState['historyState'] ?? '{}'));
    1190             : 
    1191             :       // Check if this is the first route
    1192          60 :       if (newSerialCount == null || newSerialCount == 0) {
    1193             :         // If so, check is the url reported by the browser is the same as the initial url
    1194             :         // We check "routeInformation.location == '/'" to enable deep linking
    1195             :         if (routeInformation.location == '/' && routeInformation.location != initialUrl) {
    1196             :           return;
    1197             :         }
    1198          24 :       }
    1199          12 : 
    1200             :       // Update the app with the new url
    1201             :       await _updateUrl(
    1202          24 :         routeInformation.location!,
    1203             :         newHistoryState: newHistoryState,
    1204             :         fromBrowser: true,
    1205             :         newSerialCount: newSerialCount ?? _serialCount + 1,
    1206             :       );
    1207             :     }
    1208          12 :   }
    1209          12 : 
    1210          12 :   /// App state to navigation state
    1211          12 :   @override
    1212          24 :   RouteInformation? get currentConfiguration => _doReportBackUrlToBrowser
    1213          12 :       ? RouteInformation(
    1214          24 :           location: url ?? '/',
    1215             :           state: jsonEncode({
    1216             :             'serialCount': _serialCount,
    1217             :             'historyState': jsonEncode(historyState),
    1218             :           }),
    1219          12 :         )
    1220             :       : null;
    1221          12 : 
    1222           1 :   @override
    1223           2 :   Widget build(BuildContext context) {
    1224           0 :     return NotificationListener<VWidgetGuardMessageRoot>(
    1225           2 :       onNotification: (VWidgetGuardMessageRoot vWidgetGuardMessageRoot) {
    1226             :         _vWidgetGuardMessagesRoot.removeWhere(
    1227             :             (message) => message.vWidgetGuard.key == vWidgetGuardMessageRoot.vWidgetGuard.key);
    1228             :         _vWidgetGuardMessagesRoot.add(vWidgetGuardMessageRoot);
    1229          12 : 
    1230             :         return true;
    1231          12 :       },
    1232          12 :       child: RootVRouterData(
    1233          12 :         state: this,
    1234          12 :         previousUrl: previousUrl,
    1235          12 :         url: url,
    1236          12 :         pathParameters: pathParameters,
    1237          12 :         historyState: historyState,
    1238          12 :         queryParameters: queryParameters,
    1239             :         child: Builder(
    1240          12 :           builder: (context) {
    1241          36 :             _rootVRouterContext = context;
    1242          24 : 
    1243          12 :             final child = Navigator(
    1244          24 :               pages: _vRoute.pages.isNotEmpty
    1245             :                   ? _vRoute.pages
    1246          12 :                   : [
    1247          12 :                       MaterialPage(child: Container()),
    1248           0 :                     ],
    1249           0 :               key: _navigatorKey,
    1250           0 :               observers: navigatorObservers,
    1251           0 :               onPopPage: (_, __) {
    1252             :                 _pop(
    1253             :                   _vRoute.vRouteElementNode.getVRouteElementToPop(),
    1254             :                   pathParameters: pathParameters,
    1255             :                 );
    1256             :                 return false;
    1257          12 :               },
    1258             :             );
    1259             : 
    1260             :             return builder?.call(context, child) ?? child;
    1261             :           },
    1262             :         ),
    1263             :       ),
    1264             :     );
    1265             :   }
    1266             : }
    1267             : 
    1268             : class DefaultPopResult {
    1269           7 :   VRedirector vRedirector;
    1270             :   List<VRouteElement> poppedVRouteElements;
    1271             : 
    1272             :   DefaultPopResult({
    1273             :     required this.vRedirector,
    1274             :     required this.poppedVRouteElements,
    1275             :   });
    1276             : }

Generated by: LCOV version 1.14