LCOV - code coverage report
Current view: top level - src/vrouter - vrouter.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 320 464 69.0 %
Date: 2021-04-26 23:10:51 Functions: 0 0 -

          Line data    Source code
       1             : part of '../main.dart';
       2             : 
       3             : /// See [VRouter.mode]
       4          14 : enum VRouterModes { hash, history }
       5             : 
       6             : /// This widget handles most of the routing work
       7             : /// It gives you access to the [routes] attribute where you can start
       8             : /// building your routes using [VRouteElement]s
       9             : ///
      10             : /// Note that this widget also acts as a [MaterialApp] so you can pass
      11             : /// it every argument that you would expect in [MaterialApp]
      12             : class VRouter extends StatefulWidget with VRouteElement, VRouteElementSingleSubRoute {
      13             :   /// This list holds every possible routes of your app
      14             :   final List<VRouteElement> routes;
      15             : 
      16             :   /// If implemented, this becomes the default transition for every route transition
      17             :   /// except those who implement there own buildTransition
      18             :   /// Also see:
      19             :   ///   * [VRouteElement.buildTransition] for custom local transitions
      20             :   ///
      21             :   /// Note that if this is not implemented, every route which does not implement
      22             :   /// its own buildTransition will be given a default transition: this of a
      23             :   /// [MaterialPage] or a [CupertinoPage] depending on the platform
      24             :   final Widget Function(
      25             :           Animation<double> animation, Animation<double> secondaryAnimation, Widget child)?
      26             :       buildTransition;
      27             : 
      28             :   /// The duration of [VRouter.buildTransition]
      29             :   final Duration? transitionDuration;
      30             : 
      31             :   /// The reverse duration of [VRouter.buildTransition]
      32             :   final Duration? reverseTransitionDuration;
      33             : 
      34             :   /// Two router mode are possible:
      35             :   ///    - "hash": This is the default, the url will be serverAddress/#/localUrl
      36             :   ///    - "history": This will display the url in the way we are used to, without
      37             :   ///       the #. However note that you will need to configure your server to make this work.
      38             :   ///       Follow the instructions here: [https://router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations]
      39             :   final VRouterModes mode;
      40             : 
      41          12 :   @override
      42          24 :   Future<void> beforeEnter(VRedirector vRedirector) => _beforeEnter(vRedirector);
      43             :   final Future<void> Function(VRedirector vRedirector) _beforeEnter;
      44             : 
      45          11 :   @override
      46             :   Future<void> beforeLeave(
      47             :     VRedirector vRedirector,
      48             :     void Function(Map<String, String> historyState) saveHistoryState,
      49             :   ) =>
      50          22 :       _beforeLeave(vRedirector, saveHistoryState);
      51             :   final Future<void> Function(
      52             :     VRedirector vRedirector,
      53             :     void Function(Map<String, String> historyState) saveHistoryState,
      54             :   ) _beforeLeave;
      55             : 
      56          12 :   @override
      57             :   void afterEnter(BuildContext context, String? from, String to) =>
      58          24 :       _afterEnter(context, from, to);
      59             :   final void Function(BuildContext context, String? from, String to) _afterEnter;
      60             : 
      61           7 :   @override
      62          14 :   Future<void> onPop(VRedirector vRedirector) => _onPop(vRedirector);
      63             :   final Future<void> Function(VRedirector vRedirector) _onPop;
      64             : 
      65           6 :   @override
      66          12 :   Future<void> onSystemPop(VRedirector vRedirector) => _onSystemPop(vRedirector);
      67             :   final Future<void> Function(VRedirector vRedirector) _onSystemPop;
      68             : 
      69             :   /// This allows you to change the initial url
      70             :   ///
      71             :   /// The default is '/'
      72             :   final String initialUrl;
      73             : 
      74          14 :   VRouter({
      75             :     Key? key,
      76             :     required this.routes,
      77             :     Future<void> Function(VRedirector vRedirector) beforeEnter = VGuard._voidBeforeEnter,
      78             :     Future<void> Function(
      79             :       VRedirector vRedirector,
      80             :       void Function(Map<String, String> historyState) saveHistoryState,
      81             :     )
      82             :         beforeLeave = VGuard._voidBeforeLeave,
      83             :     void Function(BuildContext context, String? from, String to) afterEnter =
      84             :         VGuard._voidAfterEnter,
      85             :     Future<void> Function(VRedirector vRedirector) onPop = VPopHandler._voidOnPop,
      86             :     Future<void> Function(VRedirector vRedirector) onSystemPop = VPopHandler._voidOnSystemPop,
      87             :     this.buildTransition,
      88             :     this.transitionDuration,
      89             :     this.reverseTransitionDuration,
      90             :     this.mode = VRouterModes.hash,
      91             :     this.initialUrl = '/',
      92             :     // Bellow are the MaterialApp parameters
      93             :     this.navigatorObservers = const [],
      94             :     this.builder,
      95             :     this.title = '',
      96             :     this.onGenerateTitle,
      97             :     this.color,
      98             :     this.theme,
      99             :     this.darkTheme,
     100             :     this.highContrastTheme,
     101             :     this.highContrastDarkTheme,
     102             :     this.themeMode = ThemeMode.system,
     103             :     this.locale,
     104             :     this.localizationsDelegates,
     105             :     this.localeListResolutionCallback,
     106             :     this.localeResolutionCallback,
     107             :     this.supportedLocales = const <Locale>[Locale('en', 'US')],
     108             :     this.debugShowMaterialGrid = false,
     109             :     this.showPerformanceOverlay = false,
     110             :     this.checkerboardRasterCacheImages = false,
     111             :     this.checkerboardOffscreenLayers = false,
     112             :     this.showSemanticsDebugger = false,
     113             :     this.debugShowCheckedModeBanner = true,
     114             :     this.shortcuts,
     115             :     this.actions,
     116             :   })  : _beforeEnter = beforeEnter,
     117             :         _beforeLeave = beforeLeave,
     118             :         _afterEnter = afterEnter,
     119             :         _onPop = onPop,
     120             :         _onSystemPop = onSystemPop,
     121          14 :         super(key: key);
     122             : 
     123          12 :   @override
     124          12 :   VRouterState createState() => VRouterState();
     125             : 
     126             :   /// {@macro flutter.widgets.widgetsApp.navigatorObservers}
     127             :   final List<NavigatorObserver> navigatorObservers;
     128             : 
     129             :   /// {@macro flutter.widgets.widgetsApp.builder}
     130             :   ///
     131             :   /// Material specific features such as [showDialog] and [showMenu], and widgets
     132             :   /// such as [Tooltip], [PopupMenuButton], also require a [Navigator] to properly
     133             :   /// function.
     134             :   final TransitionBuilder? builder;
     135             : 
     136             :   /// {@macro flutter.widgets.widgetsApp.title}
     137             :   ///
     138             :   /// This value is passed unmodified to [WidgetsApp.title].
     139             :   final String? title;
     140             : 
     141             :   /// {@macro flutter.widgets.widgetsApp.onGenerateTitle}
     142             :   ///
     143             :   /// This value is passed unmodified to [WidgetsApp.onGenerateTitle].
     144             :   final GenerateAppTitle? onGenerateTitle;
     145             : 
     146             :   /// Default visual properties, like colors fonts and shapes, for this app's
     147             :   /// material widgets.
     148             :   ///
     149             :   /// A second [darkTheme] [ThemeData] value, which is used to provide a dark
     150             :   /// version of the user interface can also be specified. [themeMode] will
     151             :   /// control which theme will be used if a [darkTheme] is provided.
     152             :   ///
     153             :   /// The default value of this property is the value of [ThemeData.light()].
     154             :   ///
     155             :   /// See also:
     156             :   ///
     157             :   ///  * [themeMode], which controls which theme to use.
     158             :   ///  * [MediaQueryData.platformBrightness], which indicates the platform's
     159             :   ///    desired brightness and is used to automatically toggle between [theme]
     160             :   ///    and [darkTheme] in [MaterialApp].
     161             :   ///  * [ThemeData.brightness], which indicates the [Brightness] of a theme's
     162             :   ///    colors.
     163             :   final ThemeData? theme;
     164             : 
     165             :   /// The [ThemeData] to use when a 'dark mode' is requested by the system.
     166             :   ///
     167             :   /// Some host platforms allow the users to select a system-wide 'dark mode',
     168             :   /// or the application may want to offer the user the ability to choose a
     169             :   /// dark theme just for this application. This is theme that will be used for
     170             :   /// such cases. [themeMode] will control which theme will be used.
     171             :   ///
     172             :   /// This theme should have a [ThemeData.brightness] set to [Brightness.dark].
     173             :   ///
     174             :   /// Uses [theme] instead when null. Defaults to the value of
     175             :   /// [ThemeData.light()] when both [darkTheme] and [theme] are null.
     176             :   ///
     177             :   /// See also:
     178             :   ///
     179             :   ///  * [themeMode], which controls which theme to use.
     180             :   ///  * [MediaQueryData.platformBrightness], which indicates the platform's
     181             :   ///    desired brightness and is used to automatically toggle between [theme]
     182             :   ///    and [darkTheme] in [MaterialApp].
     183             :   ///  * [ThemeData.brightness], which is typically set to the value of
     184             :   ///    [MediaQueryData.platformBrightness].
     185             :   final ThemeData? darkTheme;
     186             : 
     187             :   /// The [ThemeData] to use when 'high contrast' is requested by the system.
     188             :   ///
     189             :   /// Some host platforms (for example, iOS) allow the users to increase
     190             :   /// contrast through an accessibility setting.
     191             :   ///
     192             :   /// Uses [theme] instead when null.
     193             :   ///
     194             :   /// See also:
     195             :   ///
     196             :   ///  * [MediaQueryData.highContrast], which indicates the platform's
     197             :   ///    desire to increase contrast.
     198             :   final ThemeData? highContrastTheme;
     199             : 
     200             :   /// The [ThemeData] to use when a 'dark mode' and 'high contrast' is requested
     201             :   /// by the system.
     202             :   ///
     203             :   /// Some host platforms (for example, iOS) allow the users to increase
     204             :   /// contrast through an accessibility setting.
     205             :   ///
     206             :   /// This theme should have a [ThemeData.brightness] set to [Brightness.dark].
     207             :   ///
     208             :   /// Uses [darkTheme] instead when null.
     209             :   ///
     210             :   /// See also:
     211             :   ///
     212             :   ///  * [MediaQueryData.highContrast], which indicates the platform's
     213             :   ///    desire to increase contrast.
     214             :   final ThemeData? highContrastDarkTheme;
     215             : 
     216             :   /// Determines which theme will be used by the application if both [theme]
     217             :   /// and [darkTheme] are provided.
     218             :   ///
     219             :   /// If set to [ThemeMode.system], the choice of which theme to use will
     220             :   /// be based on the user's system preferences. If the [MediaQuery.platformBrightnessOf]
     221             :   /// is [Brightness.light], [theme] will be used. If it is [Brightness.dark],
     222             :   /// [darkTheme] will be used (unless it is null, in which case [theme]
     223             :   /// will be used.
     224             :   ///
     225             :   /// If set to [ThemeMode.light] the [theme] will always be used,
     226             :   /// regardless of the user's system preference.
     227             :   ///
     228             :   /// If set to [ThemeMode.dark] the [darkTheme] will be used
     229             :   /// regardless of the user's system preference. If [darkTheme] is null
     230             :   /// then it will fallback to using [theme].
     231             :   ///
     232             :   /// The default value is [ThemeMode.system].
     233             :   ///
     234             :   /// See also:
     235             :   ///
     236             :   ///  * [theme], which is used when a light mode is selected.
     237             :   ///  * [darkTheme], which is used when a dark mode is selected.
     238             :   ///  * [ThemeData.brightness], which indicates to various parts of the
     239             :   ///    system what kind of theme is being used.
     240             :   final ThemeMode? themeMode;
     241             : 
     242             :   /// {@macro flutter.widgets.widgetsApp.color}
     243             :   final Color? color;
     244             : 
     245             :   /// {@macro flutter.widgets.widgetsApp.locale}
     246             :   final Locale? locale;
     247             : 
     248             :   /// {@macro flutter.widgets.widgetsApp.localizationsDelegates}
     249             :   ///
     250             :   /// Internationalized apps that require translations for one of the locales
     251             :   /// listed in [GlobalMaterialLocalizations] should specify this parameter
     252             :   /// and list the [supportedLocales] that the application can handle.
     253             :   ///
     254             :   /// ```dart
     255             :   /// import 'package:flutter_localizations/flutter_localizations.dart';
     256             :   /// MaterialApp(
     257             :   ///   localizationsDelegates: [
     258             :   ///     // ... app-specific localization delegate[s] here
     259             :   ///     GlobalMaterialLocalizations.delegate,
     260             :   ///     GlobalWidgetsLocalizations.delegate,
     261             :   ///   ],
     262             :   ///   supportedLocales: [
     263             :   ///     const Locale('en', 'US'), // English
     264             :   ///     const Locale('he', 'IL'), // Hebrew
     265             :   ///     // ... other locales the app supports
     266             :   ///   ],
     267             :   ///   // ...
     268             :   /// )
     269             :   /// ```
     270             :   ///
     271             :   /// ## Adding localizations for a new locale
     272             :   ///
     273             :   /// The information that follows applies to the unusual case of an app
     274             :   /// adding translations for a language not already supported by
     275             :   /// [GlobalMaterialLocalizations].
     276             :   ///
     277             :   /// Delegates that produce [WidgetsLocalizations] and [MaterialLocalizations]
     278             :   /// are included automatically. Apps can provide their own versions of these
     279             :   /// localizations by creating implementations of
     280             :   /// [LocalizationsDelegate<WidgetsLocalizations>] or
     281             :   /// [LocalizationsDelegate<MaterialLocalizations>] whose load methods return
     282             :   /// custom versions of [WidgetsLocalizations] or [MaterialLocalizations].
     283             :   ///
     284             :   /// For example: to add support to [MaterialLocalizations] for a
     285             :   /// locale it doesn't already support, say `const Locale('foo', 'BR')`,
     286             :   /// one could just extend [DefaultMaterialLocalizations]:
     287             :   ///
     288             :   /// ```dart
     289             :   /// class FooLocalizations extends DefaultMaterialLocalizations {
     290             :   ///   FooLocalizations(Locale locale) : super(locale);
     291             :   ///   @override
     292             :   ///   String get okButtonLabel {
     293             :   ///     if (locale == const Locale('foo', 'BR'))
     294             :   ///       return 'foo';
     295             :   ///     return super.okButtonLabel;
     296             :   ///   }
     297             :   /// }
     298             :   ///
     299             :   /// ```
     300             :   ///
     301             :   /// A `FooLocalizationsDelegate` is essentially just a method that constructs
     302             :   /// a `FooLocalizations` object. We return a [SynchronousFuture] here because
     303             :   /// no asynchronous work takes place upon "loading" the localizations object.
     304             :   ///
     305             :   /// ```dart
     306             :   /// class FooLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
     307             :   ///   const FooLocalizationsDelegate();
     308             :   ///   @override
     309             :   ///   Future<FooLocalizations> load(Locale locale) {
     310             :   ///     return SynchronousFuture(FooLocalizations(locale));
     311             :   ///   }
     312             :   ///   @override
     313             :   ///   bool shouldReload(FooLocalizationsDelegate old) => false;
     314             :   /// }
     315             :   /// ```
     316             :   ///
     317             :   /// Constructing a [MaterialApp] with a `FooLocalizationsDelegate` overrides
     318             :   /// the automatically included delegate for [MaterialLocalizations] because
     319             :   /// only the first delegate of each [LocalizationsDelegate.type] is used and
     320             :   /// the automatically included delegates are added to the end of the app's
     321             :   /// [localizationsDelegates] list.
     322             :   ///
     323             :   /// ```dart
     324             :   /// MaterialApp(
     325             :   ///   localizationsDelegates: [
     326             :   ///     const FooLocalizationsDelegate(),
     327             :   ///   ],
     328             :   ///   // ...
     329             :   /// )
     330             :   /// ```
     331             :   /// See also:
     332             :   ///
     333             :   ///  * [supportedLocales], which must be specified along with
     334             :   ///    [localizationsDelegates].
     335             :   ///  * [GlobalMaterialLocalizations], a [localizationsDelegates] value
     336             :   ///    which provides material localizations for many languages.
     337             :   ///  * The Flutter Internationalization Tutorial,
     338             :   ///    <https://flutter.dev/tutorials/internationalization/>.
     339             :   final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
     340             : 
     341             :   /// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback}
     342             :   ///
     343             :   /// This callback is passed along to the [WidgetsApp] built by this widget.
     344             :   final LocaleListResolutionCallback? localeListResolutionCallback;
     345             : 
     346             :   /// {@macro flutter.widgets.LocaleResolutionCallback}
     347             :   ///
     348             :   /// This callback is passed along to the [WidgetsApp] built by this widget.
     349             :   final LocaleResolutionCallback? localeResolutionCallback;
     350             : 
     351             :   /// {@macro flutter.widgets.widgetsApp.supportedLocales}
     352             :   ///
     353             :   /// It is passed along unmodified to the [WidgetsApp] built by this widget.
     354             :   ///
     355             :   /// See also:
     356             :   ///
     357             :   ///  * [localizationsDelegates], which must be specified for localized
     358             :   ///    applications.
     359             :   ///  * [GlobalMaterialLocalizations], a [localizationsDelegates] value
     360             :   ///    which provides material localizations for many languages.
     361             :   ///  * The Flutter Internationalization Tutorial,
     362             :   ///    <https://flutter.dev/tutorials/internationalization/>.
     363             :   final Iterable<Locale>? supportedLocales;
     364             : 
     365             :   /// Turns on a performance overlay.
     366             :   ///
     367             :   /// See also:
     368             :   ///
     369             :   ///  * <https://flutter.dev/debugging/#performanceoverlay>
     370             :   final bool? showPerformanceOverlay;
     371             : 
     372             :   /// Turns on checkerboarding of raster cache images.
     373             :   final bool? checkerboardRasterCacheImages;
     374             : 
     375             :   /// Turns on checkerboarding of layers rendered to offscreen bitmaps.
     376             :   final bool? checkerboardOffscreenLayers;
     377             : 
     378             :   /// Turns on an overlay that shows the accessibility information
     379             :   /// reported by the framework.
     380             :   final bool? showSemanticsDebugger;
     381             : 
     382             :   /// {@macro flutter.widgets.widgetsApp.debugShowCheckedModeBanner}
     383             :   final bool? debugShowCheckedModeBanner;
     384             : 
     385             :   /// {@macro flutter.widgets.widgetsApp.shortcuts}
     386             :   /// {@tool snippet}
     387             :   /// This example shows how to add a single shortcut for
     388             :   /// [LogicalKeyboardKey.select] to the default shortcuts without needing to
     389             :   /// add your own [Shortcuts] widget.
     390             :   ///
     391             :   /// Alternatively, you could insert a [Shortcuts] widget with just the mapping
     392             :   /// you want to add between the [WidgetsApp] and its child and get the same
     393             :   /// effect.
     394             :   ///
     395             :   /// ```dart
     396             :   /// Widget build(BuildContext context) {
     397             :   ///   return WidgetsApp(
     398             :   ///     shortcuts: <LogicalKeySet, Intent>{
     399             :   ///       ... WidgetsApp.defaultShortcuts,
     400             :   ///       LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
     401             :   ///     },
     402             :   ///     color: const Color(0xFFFF0000),
     403             :   ///     builder: (BuildContext context, Widget child) {
     404             :   ///       return const Placeholder();
     405             :   ///     },
     406             :   ///   );
     407             :   /// }
     408             :   /// ```
     409             :   /// {@end-tool}
     410             :   /// {@macro flutter.widgets.widgetsApp.shortcuts.seeAlso}
     411             :   final Map<LogicalKeySet, Intent>? shortcuts;
     412             : 
     413             :   /// {@macro flutter.widgets.widgetsApp.actions}
     414             :   /// {@tool snippet}
     415             :   /// This example shows how to add a single action handling an
     416             :   /// [ActivateAction] to the default actions without needing to
     417             :   /// add your own [Actions] widget.
     418             :   ///
     419             :   /// Alternatively, you could insert a [Actions] widget with just the mapping
     420             :   /// you want to add between the [WidgetsApp] and its child and get the same
     421             :   /// effect.
     422             :   ///
     423             :   /// ```dart
     424             :   /// Widget build(BuildContext context) {
     425             :   ///   return WidgetsApp(
     426             :   ///     actions: <Type, Action<Intent>>{
     427             :   ///       ... WidgetsApp.defaultActions,
     428             :   ///       ActivateAction: CallbackAction(
     429             :   ///         onInvoke: (Intent intent) {
     430             :   ///           // Do something here...
     431             :   ///           return null;
     432             :   ///         },
     433             :   ///       ),
     434             :   ///     },
     435             :   ///     color: const Color(0xFFFF0000),
     436             :   ///     builder: (BuildContext context, Widget child) {
     437             :   ///       return const Placeholder();
     438             :   ///     },
     439             :   ///   );
     440             :   /// }
     441             :   /// ```
     442             :   /// {@end-tool}
     443             :   /// {@macro flutter.widgets.widgetsApp.actions.seeAlso}
     444             :   final Map<Type, Action<Intent>>? actions;
     445             : 
     446             :   /// Turns on a [GridPaper] overlay that paints a baseline grid
     447             :   /// Material apps.
     448             :   ///
     449             :   /// Only available in checked mode.
     450             :   ///
     451             :   /// See also:
     452             :   ///
     453             :   ///  * <https://material.io/design/layout/spacing-methods.html>
     454             :   final bool? debugShowMaterialGrid;
     455             : 
     456           9 :   static VRouterData of(BuildContext context) {
     457             :     VRouterData? vRouterData;
     458             : 
     459             :     // First try to get a local VRouterData
     460           9 :     vRouterData = context.dependOnInheritedWidgetOfExactType<LocalVRouterData>();
     461             :     if (vRouterData != null) {
     462             :       return vRouterData;
     463             :     }
     464             : 
     465             :     // Else try to get the root VRouterData
     466           0 :     vRouterData = context.dependOnInheritedWidgetOfExactType<RootVRouterData>();
     467             :     if (vRouterData != null) {
     468             :       return vRouterData;
     469             :     }
     470             : 
     471             :     if (vRouterData == null) {
     472           0 :       throw FlutterError(
     473             :           'VRouter.of(context) was called with a context which does not contain a VRouter.\n'
     474             :           'The context used to retrieve VRouter must be that of a widget that '
     475             :           'is a descendant of a VRouter widget.');
     476             :     }
     477             :     return vRouterData;
     478             :   }
     479             : 
     480          14 :   @override
     481          14 :   List<VRouteElement> buildRoutes() => routes;
     482             : 
     483          12 :   @override
     484             :   void afterUpdate(BuildContext context, String? from, String to) {}
     485             : 
     486             :   @override
     487          12 :   Future<void> beforeUpdate(VRedirector vRedirector) async {}
     488             : }
     489             : 
     490             : class VRouterState extends State<VRouter> {
     491             :   /// This is a context which contains the VRouter.
     492             :   /// It is used is VRouter.beforeLeave for example.
     493             :   late BuildContext _rootVRouterContext;
     494             : 
     495             :   /// Designates the number of page we navigated since
     496             :   /// entering the app.
     497             :   /// If is only used in the web to know where we are when
     498             :   /// the user interacts with the browser instead of the app
     499             :   /// (e.g back button)
     500             :   late int _serialCount;
     501             : 
     502             :   /// When set to true, urlToAppState will be ignored
     503             :   /// You must manually reset it to true otherwise it will
     504             :   /// be ignored forever.
     505             :   bool _ignoreNextBrowserCalls = false;
     506             : 
     507             :   /// When set to false, appStateToUrl will be "ignored"
     508             :   /// i.e. no new history entry will be created
     509             :   /// You must manually reset it to true otherwise it will
     510             :   /// be ignored forever.
     511             :   bool _doReportBackUrlToBrowser = true;
     512             : 
     513             :   /// Those are used in the root navigator
     514             :   /// They are here to prevent breaking animations
     515             :   final GlobalKey<NavigatorState> _navigatorKey;
     516             :   final HeroController _heroController;
     517             : 
     518             :   /// The child of this widget
     519             :   ///
     520             :   /// This will contain the navigator etc.
     521             :   //
     522             :   // When the app starts, before we process the '/' route, we display
     523             :   // nothing.
     524             :   // Ideally this should never be needed, or replaced with a splash screen
     525             :   // Should we add the option ?
     526          24 :   late VRoute _vRoute = VRoute(
     527          12 :     pages: [],
     528          12 :     pathParameters: {},
     529          24 :     vRouteElementNode: VRouteElementNode(widget, localPath: null),
     530          24 :     vRouteElements: [widget],
     531             :   );
     532             : 
     533             :   /// Every VWidgetGuard will be registered here
     534             :   List<VWidgetGuardMessageRoot> _vWidgetGuardMessagesRoot = [];
     535             : 
     536          12 :   VRouterState()
     537          12 :       : _navigatorKey = GlobalKey<NavigatorState>(),
     538          12 :         _heroController = HeroController();
     539             : 
     540             :   /// Url currently synced with the state
     541             :   /// This url can differ from the once of the browser if
     542             :   /// the state has been yet been updated
     543             :   String? url;
     544             : 
     545             :   /// Previous url that was synced with the state
     546             :   String? previousUrl;
     547             : 
     548             :   /// This state is saved in the browser history. This means that if the user presses
     549             :   /// the back or forward button on the navigator, this historyState will be the same
     550             :   /// as the last one you saved.
     551             :   ///
     552             :   /// It can be changed by using [context.vRouter.replaceHistoryState(newState)]
     553             :   Map<String, String> historyState = {};
     554             : 
     555             :   /// Maps all route parameters (i.e. parameters of the path
     556             :   /// mentioned as ":someId")
     557             :   Map<String, String> pathParameters = <String, String>{};
     558             : 
     559             :   /// Contains all query parameters (i.e. parameters after
     560             :   /// the "?" in the url) of the current url
     561             :   Map<String, String> queryParameters = <String, String>{};
     562             : 
     563          12 :   @override
     564             :   void initState() {
     565             :     // When the app starts, get the serialCount. Default to 0.
     566          12 :     _serialCount = (kIsWeb) ? (BrowserHelpers.getHistorySerialCount() ?? 0) : 0;
     567             : 
     568             :     // Setup the url strategy (if hash, do nothing since it is the default)
     569          36 :     if (widget.mode == VRouterModes.history) {
     570           0 :       setPathUrlStrategy();
     571             :     }
     572             : 
     573             :     // Check if this is the first route
     574          24 :     if (_serialCount == 0) {
     575             :       // If it is, navigate to initial url if this is not the default one
     576          36 :       if (widget.initialUrl != '/') {
     577             :         // If we are deep-linking, do not use initial url
     578           0 :         if (!kIsWeb || BrowserHelpers.getPathAndQuery(routerMode: widget.mode).isEmpty) {
     579          15 :           WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
     580          15 :             pushReplacement(widget.initialUrl);
     581             :           });
     582             :         }
     583             :       }
     584             :     }
     585             : 
     586             :     // If we are on the web, we listen to any unload event.
     587             :     // This allows us to call beforeLeave when the browser or the tab
     588             :     // is being closed for example
     589             :     if (kIsWeb) {
     590           0 :       BrowserHelpers.onBrowserBeforeUnload.listen((e) => _onBeforeUnload());
     591             :     }
     592             : 
     593          12 :     super.initState();
     594             :   }
     595             : 
     596          12 :   @override
     597             :   Widget build(BuildContext context) {
     598          12 :     return SimpleUrlHandler(
     599          12 :       urlToAppState: (BuildContext context, RouteInformation routeInformation) async {
     600          24 :         if (routeInformation.location != null && !_ignoreNextBrowserCalls) {
     601             :           // Get the new state
     602             :           final newState = (kIsWeb)
     603           0 :               ? Map<String, dynamic>.from(jsonDecode((routeInformation.state as String?) ??
     604           0 :                   (BrowserHelpers.getHistoryState() ?? '{}')))
     605          12 :               : <String, dynamic>{};
     606             : 
     607             :           // Get the new serial count
     608             :           int? newSerialCount;
     609             :           try {
     610          12 :             newSerialCount = newState['serialCount'];
     611             :             // ignore: empty_catches
     612           0 :           } on FormatException {}
     613             : 
     614             :           // Get the new history state
     615             :           final newHistoryState =
     616          36 :               Map<String, String>.from(jsonDecode(newState['historyState'] ?? '{}'));
     617             : 
     618             :           // Check if this is the first route
     619           0 :           if (newSerialCount == null || newSerialCount == 0) {
     620             :             // If so, check is the url reported by the browser is the same as the initial url
     621             :             // We check "routeInformation.location == '/'" to enable deep linking
     622          24 :             if (routeInformation.location == '/' &&
     623          48 :                 routeInformation.location != widget.initialUrl) {
     624             :               return;
     625             :             }
     626             :           }
     627             : 
     628             :           // Update the app with the new url
     629          24 :           await _updateUrl(
     630          12 :             routeInformation.location!,
     631             :             newHistoryState: newHistoryState,
     632             :             fromBrowser: true,
     633          24 :             newSerialCount: newSerialCount ?? _serialCount + 1,
     634             :           );
     635             :         }
     636             :         return null;
     637             :       },
     638          12 :       appStateToUrl: () {
     639          12 :         return _doReportBackUrlToBrowser
     640          12 :             ? RouteInformation(
     641          12 :                 location: url ?? '/',
     642          24 :                 state: jsonEncode({
     643          12 :                   'serialCount': _serialCount,
     644          24 :                   'historyState': jsonEncode(historyState),
     645             :                 }),
     646             :               )
     647             :             : null;
     648             :       },
     649          12 :       child: NotificationListener<VWidgetGuardMessageRoot>(
     650           1 :         onNotification: (VWidgetGuardMessageRoot vWidgetGuardMessageRoot) {
     651           2 :           _vWidgetGuardMessagesRoot.removeWhere((message) =>
     652           0 :               message.vWidgetGuard.key == vWidgetGuardMessageRoot.vWidgetGuard.key);
     653           2 :           _vWidgetGuardMessagesRoot.add(vWidgetGuardMessageRoot);
     654             : 
     655             :           return true;
     656             :         },
     657          12 :         child: RootVRouterData(
     658             :           state: this,
     659          12 :           previousUrl: previousUrl,
     660          12 :           url: url,
     661          12 :           pathParameters: pathParameters,
     662          12 :           historyState: historyState,
     663          12 :           queryParameters: queryParameters,
     664          12 :           child: Builder(
     665          12 :             builder: (context) {
     666          12 :               _rootVRouterContext = context;
     667             : 
     668          12 :               final child = VRouterHelper(
     669          36 :                 pages: _vRoute.pages.isNotEmpty
     670          24 :                     ? _vRoute.pages
     671          12 :                     : [
     672          24 :                         MaterialPage(child: Container()),
     673             :                       ],
     674          12 :                 navigatorKey: _navigatorKey,
     675          48 :                 observers: [_heroController, ...widget.navigatorObservers],
     676          12 :                 backButtonDispatcher: RootBackButtonDispatcher(),
     677           0 :                 onPopPage: (_, __) {
     678           0 :                   _pop(
     679           0 :                     _vRoute.vRouteElementNode.getVRouteElementToPop(),
     680           0 :                     pathParameters: pathParameters,
     681             :                   );
     682             :                   return false;
     683             :                 },
     684           0 :                 onSystemPopPage: () async {
     685           0 :                   await _systemPop(
     686           0 :                     _vRoute.vRouteElementNode.getVRouteElementToPop(),
     687           0 :                     pathParameters: pathParameters,
     688             :                   );
     689             :                   return true;
     690             :                 },
     691             :               );
     692             : 
     693          24 :               return widget.builder?.call(context, child) ?? child;
     694             :             },
     695             :           ),
     696             :         ),
     697             :       ),
     698          24 :       title: widget.title ?? '',
     699          24 :       onGenerateTitle: widget.onGenerateTitle,
     700          24 :       color: widget.color,
     701          24 :       theme: widget.theme,
     702          24 :       darkTheme: widget.darkTheme,
     703          24 :       highContrastTheme: widget.highContrastTheme,
     704          24 :       highContrastDarkTheme: widget.highContrastDarkTheme,
     705          24 :       themeMode: widget.themeMode,
     706          24 :       locale: widget.locale,
     707          24 :       localizationsDelegates: widget.localizationsDelegates,
     708          24 :       localeListResolutionCallback: widget.localeListResolutionCallback,
     709          24 :       localeResolutionCallback: widget.localeResolutionCallback,
     710          24 :       supportedLocales: widget.supportedLocales ?? const <Locale>[Locale('en', 'US')],
     711          24 :       debugShowMaterialGrid: widget.debugShowMaterialGrid ?? false,
     712          24 :       showPerformanceOverlay: widget.showPerformanceOverlay ?? false,
     713          24 :       checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages ?? false,
     714          24 :       checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers ?? false,
     715          24 :       showSemanticsDebugger: widget.showSemanticsDebugger ?? false,
     716          24 :       debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner ?? true,
     717          24 :       shortcuts: widget.shortcuts,
     718          24 :       actions: widget.actions,
     719             :     );
     720             :   }
     721             : 
     722             :   /// Updates every state variables of [VRouter]
     723             :   ///
     724             :   /// Note that this does not call setState
     725          12 :   void _updateStateVariables(
     726             :     VRoute vRoute,
     727             :     String newUrl, {
     728             :     required Map<String, String> queryParameters,
     729             :     required Map<String, String> historyState,
     730             :     required List<VRouteElement> deactivatedVRouteElements,
     731             :   }) {
     732             :     // Update the vRoute
     733          12 :     this._vRoute = vRoute;
     734             : 
     735             :     // Update the urls
     736          24 :     previousUrl = url;
     737          12 :     url = newUrl;
     738             : 
     739             :     // Update the history state
     740          12 :     this.historyState = historyState;
     741             : 
     742             :     // Update the path parameters
     743          24 :     this.pathParameters = vRoute.pathParameters;
     744             : 
     745             :     // Update the query parameters
     746          12 :     this.queryParameters = queryParameters;
     747             : 
     748             :     // Update _vWidgetGuardMessagesRoot by removing the no-longer actives VWidgetGuards
     749          25 :     _vWidgetGuardMessagesRoot.removeWhere((vWidgetGuardMessageRoot) =>
     750           2 :         deactivatedVRouteElements.contains(vWidgetGuardMessageRoot.associatedVRouteElement));
     751             :   }
     752             : 
     753             :   /// See [VRouterMethodsHolder.pushNamed]
     754           6 :   void _updateUrlFromName(
     755             :     String name, {
     756             :     Map<String, String> pathParameters = const {},
     757             :     Map<String, String> queryParameters = const {},
     758             :     Map<String, String> newHistoryState = const {},
     759             :     bool isReplacement = false,
     760             :   }) {
     761             :     // Encode the path parameters
     762             :     pathParameters =
     763          15 :         pathParameters.map((key, value) => MapEntry(key, Uri.encodeComponent(value)));
     764             : 
     765             :     // We use VRouteElement.getPathFromName
     766          12 :     final getPathFromNameResult = widget.getPathFromName(
     767             :       name,
     768             :       pathParameters: pathParameters,
     769          12 :       parentPathResult: ValidParentPathResult(path: null, pathParameters: {}),
     770             :       remainingPathParameters: pathParameters,
     771             :     );
     772             : 
     773           6 :     if (getPathFromNameResult is ErrorGetPathFromNameResult) {
     774             :       throw getPathFromNameResult;
     775             :     }
     776             : 
     777           5 :     var newPath = (getPathFromNameResult as ValidNameResult).path;
     778             : 
     779             :     // Encode the path parameters
     780           5 :     final encodedPathParameters = pathParameters.map<String, String>(
     781           6 :       (key, value) => MapEntry(key, Uri.encodeComponent(value)),
     782             :     );
     783             : 
     784             :     // Inject the encoded path parameters into the new path
     785          10 :     newPath = pathToFunction(newPath)(encodedPathParameters);
     786             : 
     787             :     // Update the url with the found and completed path
     788           5 :     _updateUrl(newPath, queryParameters: queryParameters, isReplacement: isReplacement);
     789             :   }
     790             : 
     791             :   /// This should be the only way to change a url.
     792             :   /// Navigation cycle:
     793             :   /// 1. Call beforeLeave in all deactivated [VWidgetGuard]
     794             :   /// 2. Call beforeLeave in all deactivated [VRouteElement]
     795             :   /// 3. Call beforeLeave in the [VRouter]
     796             :   /// 4. Call beforeEnter in the [VRouter]
     797             :   /// 5. Call beforeEnter in all initialized [VRouteElement] of the new route
     798             :   /// 6. Call beforeUpdate in all reused [VWidgetGuard]
     799             :   /// 7. Call beforeUpdate in all reused [VRouteElement]
     800             :   ///
     801             :   /// ## The history state got in beforeLeave are stored
     802             :   /// ## The state is updated
     803             :   ///
     804             :   /// 8. Call afterEnter in all initialized [VWidgetGuard]
     805             :   /// 9. Call afterEnter all initialized [VRouteElement]
     806             :   /// 10. Call afterEnter in the [VRouter]
     807             :   /// 11. Call afterUpdate in all reused [VWidgetGuard]
     808             :   /// 12. Call afterUpdate in all reused [VRouteElement]
     809          12 :   Future<void> _updateUrl(
     810             :     String newUrl, {
     811             :     Map<String, String> newHistoryState = const {},
     812             :     bool fromBrowser = false,
     813             :     int? newSerialCount,
     814             :     Map<String, String> queryParameters = const {},
     815             :     bool isUrlExternal = false,
     816             :     bool isReplacement = false,
     817             :     bool openNewTab = false,
     818             :   }) async {
     819           0 :     assert(!kIsWeb || (!fromBrowser || newSerialCount != null));
     820             : 
     821             :     // Reset this to true, new url = new chance to report
     822          12 :     _doReportBackUrlToBrowser = true;
     823             : 
     824             :     // This should never happen, if it does this is in error in this package
     825             :     // We take care of passing the right parameters depending on the platform
     826          12 :     assert(kIsWeb || isReplacement == false,
     827             :         'This does not make sense to replace the route if you are not on the web. Please set isReplacement to false.');
     828             : 
     829          12 :     var newUri = Uri.parse(newUrl);
     830          12 :     final newPath = newUri.path;
     831          24 :     assert(!(newUri.queryParameters.isNotEmpty && queryParameters.isNotEmpty),
     832             :         'You used the queryParameters attribute but the url already contained queryParameters. The latter will be overwritten by the argument you gave');
     833          12 :     if (queryParameters.isEmpty) {
     834          12 :       queryParameters = newUri.queryParameters;
     835             :     }
     836             :     // Decode queryParameters
     837          12 :     queryParameters = queryParameters.map(
     838           3 :       (key, value) => MapEntry(key, Uri.decodeComponent(value)),
     839             :     );
     840             : 
     841             :     // Add the queryParameters to the url if needed
     842          12 :     if (queryParameters.isNotEmpty) {
     843           1 :       newUri = Uri(path: newPath, queryParameters: queryParameters);
     844             :     }
     845             : 
     846             :     // Get only the path from the url
     847          48 :     final path = (url != null) ? Uri.parse(url!).path : null;
     848             : 
     849             :     late final List<VRouteElement> deactivatedVRouteElements;
     850             :     late final List<VRouteElement> reusedVRouteElements;
     851             :     late final List<VRouteElement> initializedVRouteElements;
     852             :     late final List<VWidgetGuardMessageRoot> deactivatedVWidgetGuardsMessagesRoot;
     853             :     late final List<VWidgetGuardMessageRoot> reusedVWidgetGuardsMessagesRoot;
     854             :     VRoute? newVRoute;
     855             :     if (isUrlExternal) {
     856             :       newVRoute = null;
     857           0 :       deactivatedVRouteElements = <VRouteElement>[];
     858           0 :       reusedVRouteElements = <VRouteElement>[];
     859           0 :       initializedVRouteElements = <VRouteElement>[];
     860           0 :       deactivatedVWidgetGuardsMessagesRoot = <VWidgetGuardMessageRoot>[];
     861           0 :       reusedVWidgetGuardsMessagesRoot = <VWidgetGuardMessageRoot>[];
     862             :     } else {
     863             :       // Get the new route
     864          24 :       newVRoute = widget.buildRoute(
     865          12 :         VPathRequestData(
     866          12 :           previousUrl: url,
     867             :           uri: newUri,
     868             :           historyState: newHistoryState,
     869          12 :           rootVRouterContext: _rootVRouterContext,
     870             :         ),
     871          12 :         parentVPathMatch: ValidVPathMatch(
     872             :           remainingPath: newPath,
     873          12 :           pathParameters: {},
     874             :           localPath: null,
     875             :         ),
     876             :       );
     877             : 
     878             :       if (newVRoute == null) {
     879           1 :         throw UnknownUrlVError(url: newUrl);
     880             :       }
     881             : 
     882             :       // This copy is necessary in order not to modify newVRoute.vRouteElements
     883          24 :       final newVRouteElements = List<VRouteElement>.from(newVRoute.vRouteElements);
     884             : 
     885          12 :       deactivatedVRouteElements = <VRouteElement>[];
     886          12 :       reusedVRouteElements = <VRouteElement>[];
     887          36 :       if (_vRoute.vRouteElements.isNotEmpty) {
     888          48 :         for (var vRouteElement in _vRoute.vRouteElements.reversed) {
     889             :           try {
     890          12 :             reusedVRouteElements.add(
     891          12 :               newVRouteElements.firstWhere(
     892          24 :                 (newVRouteElement) => (newVRouteElement == vRouteElement),
     893             :               ),
     894             :             );
     895          10 :           } on StateError {
     896          10 :             deactivatedVRouteElements.add(vRouteElement);
     897             :           }
     898             :         }
     899             :       }
     900           0 :       initializedVRouteElements = newVRouteElements
     901          12 :           .where(
     902          12 :             (newVRouteElement) =>
     903          24 :                 _vRoute.vRouteElements
     904          48 :                     .indexWhere((vRouteElement) => vRouteElement == newVRouteElement) ==
     905          12 :                 -1,
     906             :           )
     907          12 :           .toList();
     908             : 
     909             :       // Get deactivated and reused VWidgetGuards
     910          12 :       deactivatedVWidgetGuardsMessagesRoot = _vWidgetGuardMessagesRoot
     911          13 :           .where((vWidgetGuardMessageRoot) => deactivatedVRouteElements
     912           2 :               .contains(vWidgetGuardMessageRoot.associatedVRouteElement))
     913          12 :           .toList();
     914          12 :       reusedVWidgetGuardsMessagesRoot = _vWidgetGuardMessagesRoot
     915          13 :           .where((vWidgetGuardMessageRoot) =>
     916           2 :               reusedVRouteElements.contains(vWidgetGuardMessageRoot.associatedVRouteElement))
     917          12 :           .toList();
     918             :     }
     919             : 
     920          12 :     Map<String, String> historyStateToSave = {};
     921           0 :     void saveHistoryState(Map<String, String> historyState) {
     922           0 :       historyStateToSave.addAll(historyState);
     923             :     }
     924             : 
     925             :     // Instantiate VRedirector
     926          12 :     final vRedirector = VRedirector(
     927          12 :       context: _rootVRouterContext,
     928          12 :       from: url,
     929          12 :       to: newUri.toString(),
     930          12 :       previousVRouterData: RootVRouterData(
     931          12 :         child: Container(),
     932          12 :         historyState: historyState,
     933          24 :         pathParameters: _vRoute.pathParameters,
     934          12 :         queryParameters: this.queryParameters,
     935             :         state: this,
     936          12 :         url: url,
     937          12 :         previousUrl: previousUrl,
     938             :       ),
     939          12 :       newVRouterData: RootVRouterData(
     940          12 :         child: Container(),
     941             :         historyState: newHistoryState,
     942          12 :         pathParameters: newVRoute?.pathParameters ?? {},
     943             :         queryParameters: queryParameters,
     944             :         state: this,
     945          12 :         url: newUri.toString(),
     946          12 :         previousUrl: url,
     947             :       ),
     948             :     );
     949             : 
     950          12 :     if (url != null) {
     951             :       ///   1. Call beforeLeave in all deactivated [VWidgetGuard]
     952          12 :       for (var vWidgetGuardMessageRoot in deactivatedVWidgetGuardsMessagesRoot) {
     953           4 :         await vWidgetGuardMessageRoot.vWidgetGuard.beforeLeave(vRedirector, saveHistoryState);
     954           1 :         if (!vRedirector._shouldUpdate) {
     955           2 :           await _abortUpdateUrl(
     956             :             fromBrowser: fromBrowser,
     957           1 :             serialCount: _serialCount,
     958             :             newSerialCount: newSerialCount,
     959             :           );
     960             : 
     961           1 :           vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
     962           0 :                   .getChildVRouteElementNode(
     963           0 :                       vRouteElement: vWidgetGuardMessageRoot.associatedVRouteElement) ??
     964           0 :               _vRoute.vRouteElementNode);
     965             :           return;
     966             :         }
     967             :       }
     968             : 
     969             :       ///   2. Call beforeLeave in all deactivated [VRouteElement]
     970          20 :       for (var vRouteElement in deactivatedVRouteElements) {
     971          18 :         await vRouteElement.beforeLeave(vRedirector, saveHistoryState);
     972           9 :         if (!vRedirector._shouldUpdate) {
     973           2 :           await _abortUpdateUrl(
     974             :             fromBrowser: fromBrowser,
     975           1 :             serialCount: _serialCount,
     976             :             newSerialCount: newSerialCount,
     977             :           );
     978           1 :           vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
     979           0 :                   .getChildVRouteElementNode(vRouteElement: vRouteElement) ??
     980           0 :               _vRoute.vRouteElementNode);
     981             :           return;
     982             :         }
     983             :       }
     984             : 
     985             :       /// 3. Call beforeLeave in the [VRouter]
     986          33 :       await widget.beforeLeave(vRedirector, saveHistoryState);
     987          11 :       if (!vRedirector._shouldUpdate) {
     988           2 :         await _abortUpdateUrl(
     989             :           fromBrowser: fromBrowser,
     990           1 :           serialCount: _serialCount,
     991             :           newSerialCount: newSerialCount,
     992             :         );
     993           4 :         vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode);
     994             :         return;
     995             :       }
     996             :     }
     997             : 
     998             :     if (!isUrlExternal) {
     999             :       /// 4. Call beforeEnter in the [VRouter]
    1000          36 :       await widget.beforeEnter(vRedirector);
    1001          12 :       if (!vRedirector._shouldUpdate) {
    1002           0 :         await _abortUpdateUrl(
    1003             :           fromBrowser: fromBrowser,
    1004           0 :           serialCount: _serialCount,
    1005             :           newSerialCount: newSerialCount,
    1006             :         );
    1007           0 :         vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode);
    1008             :         return;
    1009             :       }
    1010             : 
    1011             :       /// 5. Call beforeEnter in all initialized [VRouteElement] of the new route
    1012          24 :       for (var vRouteElement in initializedVRouteElements) {
    1013          24 :         await vRouteElement.beforeEnter(vRedirector);
    1014          12 :         if (!vRedirector._shouldUpdate) {
    1015           4 :           await _abortUpdateUrl(
    1016             :             fromBrowser: fromBrowser,
    1017           2 :             serialCount: _serialCount,
    1018             :             newSerialCount: newSerialCount,
    1019             :           );
    1020           5 :           vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
    1021           1 :                   .getChildVRouteElementNode(vRouteElement: vRouteElement) ??
    1022           2 :               _vRoute.vRouteElementNode);
    1023             :           return;
    1024             :         }
    1025             :       }
    1026             : 
    1027             :       /// 6. Call beforeUpdate in all reused [VWidgetGuard]
    1028          13 :       for (var vWidgetGuardMessageRoot in reusedVWidgetGuardsMessagesRoot) {
    1029           4 :         await vWidgetGuardMessageRoot.vWidgetGuard.beforeUpdate(vRedirector);
    1030           1 :         if (!vRedirector._shouldUpdate) {
    1031           2 :           await _abortUpdateUrl(
    1032             :             fromBrowser: fromBrowser,
    1033           1 :             serialCount: _serialCount,
    1034             :             newSerialCount: newSerialCount,
    1035             :           );
    1036             : 
    1037           1 :           vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
    1038           0 :                   .getChildVRouteElementNode(
    1039           0 :                       vRouteElement: vWidgetGuardMessageRoot.associatedVRouteElement) ??
    1040           0 :               _vRoute.vRouteElementNode);
    1041             :           return;
    1042             :         }
    1043             :       }
    1044             : 
    1045             :       /// 7. Call beforeUpdate in all reused [VRouteElement]
    1046          24 :       for (var vRouteElement in reusedVRouteElements) {
    1047          24 :         await vRouteElement.beforeUpdate(vRedirector);
    1048          12 :         if (!vRedirector._shouldUpdate) {
    1049           2 :           await _abortUpdateUrl(
    1050             :             fromBrowser: fromBrowser,
    1051           1 :             serialCount: _serialCount,
    1052             :             newSerialCount: newSerialCount,
    1053             :           );
    1054             : 
    1055           1 :           vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
    1056           0 :                   .getChildVRouteElementNode(vRouteElement: vRouteElement) ??
    1057           0 :               _vRoute.vRouteElementNode);
    1058             :           return;
    1059             :         }
    1060             :       }
    1061             :     }
    1062             : 
    1063          12 :     final oldSerialCount = _serialCount;
    1064             : 
    1065          12 :     if (historyStateToSave.isNotEmpty && path != null) {
    1066             :       if (!kIsWeb) {
    1067           0 :         log(
    1068             :           ' WARNING: Tried to store the state $historyStateToSave while not on the web. State saving/restoration only work on the web.\n'
    1069             :           'You can safely ignore this message if you just want this functionality on the web.',
    1070             :           name: 'VRouter',
    1071             :         );
    1072             :       } else {
    1073             :         ///   The historyStates got in beforeLeave are stored   ///
    1074             :         // If we come from the browser, chances are we already left the page
    1075             :         // So we need to:
    1076             :         //    1. Go back to where we were
    1077             :         //    2. Save the historyState
    1078             :         //    3. And go back again to the place
    1079           0 :         if (kIsWeb && fromBrowser && oldSerialCount != newSerialCount) {
    1080           0 :           _ignoreNextBrowserCalls = true;
    1081           0 :           BrowserHelpers.browserGo(oldSerialCount - newSerialCount!);
    1082           0 :           await BrowserHelpers.onBrowserPopState.firstWhere((element) {
    1083           0 :             return BrowserHelpers.getHistorySerialCount() == oldSerialCount;
    1084             :           });
    1085             :         }
    1086           0 :         BrowserHelpers.replaceHistoryState(jsonEncode({
    1087             :           'serialCount': oldSerialCount,
    1088           0 :           'historyState': jsonEncode(historyStateToSave),
    1089             :         }));
    1090             : 
    1091           0 :         if (kIsWeb && fromBrowser && oldSerialCount != newSerialCount) {
    1092           0 :           BrowserHelpers.browserGo(newSerialCount! - oldSerialCount);
    1093           0 :           await BrowserHelpers.onBrowserPopState.firstWhere(
    1094           0 :               (element) => BrowserHelpers.getHistorySerialCount() == newSerialCount);
    1095           0 :           _ignoreNextBrowserCalls = false;
    1096             :         }
    1097             :       }
    1098             :     }
    1099             : 
    1100             :     /// Leave if the url is external
    1101             :     if (isUrlExternal) {
    1102           0 :       _ignoreNextBrowserCalls = true;
    1103           0 :       await BrowserHelpers.pushExternal(newUri.toString(), openNewTab: openNewTab);
    1104             :       return;
    1105             :     }
    1106             : 
    1107             :     ///   The state of the VRouter changes            ///
    1108          12 :     final oldUrl = url;
    1109             : 
    1110             :     if (isReplacement) {
    1111           0 :       _doReportBackUrlToBrowser = false;
    1112           0 :       _ignoreNextBrowserCalls = true;
    1113           0 :       if (BrowserHelpers.getPathAndQuery(routerMode: widget.mode) != newUri.toString()) {
    1114           0 :         BrowserHelpers.pushReplacement(newUri.toString(), routerMode: widget.mode);
    1115           0 :         if (BrowserHelpers.getPathAndQuery(routerMode: widget.mode) != newUri.toString()) {
    1116           0 :           await BrowserHelpers.onBrowserPopState.firstWhere((element) =>
    1117           0 :               BrowserHelpers.getPathAndQuery(routerMode: widget.mode) == newUri.toString());
    1118             :         }
    1119             :       }
    1120           0 :       BrowserHelpers.replaceHistoryState(jsonEncode({
    1121           0 :         'serialCount': _serialCount,
    1122           0 :         'historyState': jsonEncode(newHistoryState),
    1123             :       }));
    1124           0 :       _ignoreNextBrowserCalls = false;
    1125             :     } else {
    1126             :       // If this comes from the browser, newSerialCount is not null
    1127             :       // If this comes from a user:
    1128             :       //    - If he/she pushes the same url+historyState, flutter does not create a new history entry so the serialCount remains the same
    1129             :       //    - Else the serialCount gets increased by 1
    1130          12 :       _serialCount = newSerialCount ??
    1131          48 :           _serialCount + ((newUrl != url || newHistoryState != historyState) ? 1 : 0);
    1132             :     }
    1133          24 :     setState(() {
    1134          12 :       _updateStateVariables(
    1135             :         newVRoute!,
    1136          12 :         newUri.toString(),
    1137             :         historyState: newHistoryState,
    1138             :         queryParameters: queryParameters,
    1139           0 :         deactivatedVRouteElements: deactivatedVRouteElements,
    1140             :       );
    1141             :     });
    1142             : 
    1143             :     // We need to do this after rebuild as completed so that the user can have access
    1144             :     // to the new state variables
    1145          36 :     WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
    1146             :       /// 8. Call afterEnter in all initialized [VWidgetGuard]
    1147             :       // This is done automatically by VNotificationGuard
    1148             : 
    1149             :       /// 9. Call afterEnter all initialized [VRouteElement]
    1150          24 :       for (var vRouteElement in initializedVRouteElements) {
    1151          12 :         vRouteElement.afterEnter(
    1152          12 :           _rootVRouterContext,
    1153             :           // TODO: Change this to local context? This might imply that we need a global key which is not ideal
    1154             :           oldUrl,
    1155          12 :           newUri.toString(),
    1156             :         );
    1157             :       }
    1158             : 
    1159             :       /// 10. Call afterEnter in the [VRouter]
    1160          48 :       widget.afterEnter(_rootVRouterContext, oldUrl, newUri.toString());
    1161             : 
    1162             :       /// 11. Call afterUpdate in all reused [VWidgetGuard]
    1163          13 :       for (var vWidgetGuardMessageRoot in reusedVWidgetGuardsMessagesRoot) {
    1164           3 :         vWidgetGuardMessageRoot.vWidgetGuard.afterUpdate(
    1165           1 :           vWidgetGuardMessageRoot.localContext,
    1166             :           oldUrl,
    1167           1 :           newUri.toString(),
    1168             :         );
    1169             :       }
    1170             : 
    1171             :       /// 12. Call afterUpdate in all reused [VRouteElement]
    1172          24 :       for (var vRouteElement in reusedVRouteElements) {
    1173          12 :         vRouteElement.afterUpdate(
    1174          12 :           _rootVRouterContext,
    1175             :           // TODO: Change this to local context? This might imply that we need a global key which is not ideal
    1176             :           oldUrl,
    1177          12 :           newUri.toString(),
    1178             :         );
    1179             :       }
    1180             :     });
    1181             :   }
    1182             : 
    1183             :   /// This function is used in [updateUrl] when the update should be canceled
    1184             :   /// This happens and vRedirector is used to stop the navigation
    1185             :   ///
    1186             :   /// On mobile nothing happens
    1187             :   /// On the web, if the browser already navigated away, we have to navigate back to where we were
    1188             :   ///
    1189             :   /// Note that this should be called before setState, otherwise it is useless and cannot prevent a state spread
    1190             :   ///
    1191             :   /// newSerialCount should not be null if the updateUrl came from the Browser
    1192           4 :   Future<void> _abortUpdateUrl({
    1193             :     required bool fromBrowser,
    1194             :     required int serialCount,
    1195             :     required int? newSerialCount,
    1196             :   }) async {
    1197             :     // If the url change comes from the browser, chances are the url is already changed
    1198             :     // So we have to navigate back to the old url (stored in _url)
    1199             :     // Note: in future version it would be better to delete the last url of the browser
    1200             :     //        but it is not yet possible
    1201             :     if (kIsWeb &&
    1202             :         fromBrowser &&
    1203           0 :         (BrowserHelpers.getHistorySerialCount() ?? 0) != serialCount) {
    1204           0 :       _ignoreNextBrowserCalls = true;
    1205           0 :       BrowserHelpers.browserGo(serialCount - newSerialCount!);
    1206           0 :       await BrowserHelpers.onBrowserPopState.firstWhere((element) {
    1207           0 :         return BrowserHelpers.getHistorySerialCount() == serialCount;
    1208             :       });
    1209           0 :       _ignoreNextBrowserCalls = false;
    1210             :     }
    1211             :     return;
    1212             :   }
    1213             : 
    1214             :   /// Performs a systemPop cycle:
    1215             :   ///   1. Call onPop in all active [VWidgetGuards]
    1216             :   ///   2. Call onPop in all [VRouteElement]
    1217             :   ///   3. Call onPop of VRouter
    1218             :   ///   4. Update the url to the one found in [_defaultPop]
    1219           8 :   Future<void> _pop(
    1220             :     VRouteElement elementToPop, {
    1221             :     Map<String, String> pathParameters = const {},
    1222             :     Map<String, String> queryParameters = const {},
    1223             :     Map<String, String> newHistoryState = const {},
    1224             :   }) async {
    1225           8 :     assert(url != null);
    1226             : 
    1227             :     // Get information on where to pop from _defaultPop
    1228           8 :     final defaultPopResult = _defaultPop(
    1229             :       elementToPop,
    1230             :       pathParameters: pathParameters,
    1231             :       queryParameters: queryParameters,
    1232             :       newHistoryState: newHistoryState,
    1233             :     );
    1234             : 
    1235           7 :     final vRedirector = defaultPopResult.vRedirector;
    1236             : 
    1237           7 :     final poppedVRouteElements = defaultPopResult.poppedVRouteElements;
    1238             : 
    1239             :     final List<VWidgetGuardMessageRoot> poppedVWidgetGuardsMessagesRoot =
    1240           7 :         _vWidgetGuardMessagesRoot
    1241           7 :             .where((vWidgetGuardMessageRoot) =>
    1242           0 :                 poppedVRouteElements.contains(vWidgetGuardMessageRoot.associatedVRouteElement))
    1243           7 :             .toList();
    1244             : 
    1245             :     /// 1. Call onPop in all popped [VWidgetGuards]
    1246           7 :     for (var vWidgetGuardMessageRoot in poppedVWidgetGuardsMessagesRoot) {
    1247           0 :       await vWidgetGuardMessageRoot.vWidgetGuard.onPop(vRedirector);
    1248           0 :       if (!vRedirector.shouldUpdate) {
    1249           0 :         vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
    1250           0 :                 .getChildVRouteElementNode(
    1251           0 :                     vRouteElement: vWidgetGuardMessageRoot.associatedVRouteElement) ??
    1252           0 :             _vRoute.vRouteElementNode);
    1253             :         return;
    1254             :       }
    1255             :     }
    1256             : 
    1257             :     /// 2. Call onPop in all popped [VRouteElement]
    1258          14 :     for (var vRouteElement in poppedVRouteElements) {
    1259          14 :       await vRouteElement.onPop(vRedirector);
    1260           7 :       if (!vRedirector.shouldUpdate) {
    1261           1 :         vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
    1262           0 :                 .getChildVRouteElementNode(vRouteElement: vRouteElement) ??
    1263           0 :             _vRoute.vRouteElementNode);
    1264             :         return;
    1265             :       }
    1266             :     }
    1267             : 
    1268             :     /// 3. Call onPop of VRouter
    1269          21 :     await widget.onPop(vRedirector);
    1270           7 :     if (!vRedirector.shouldUpdate) {
    1271           0 :       vRedirector._redirectFunction?.call(
    1272           0 :           _vRoute.vRouteElementNode.getChildVRouteElementNode(vRouteElement: widget) ??
    1273           0 :               _vRoute.vRouteElementNode);
    1274             :       return;
    1275             :     }
    1276             : 
    1277             :     /// 4. Update the url to the one found in [_defaultPop]
    1278           7 :     if (vRedirector.newVRouterData != null) {
    1279          14 :       _updateUrl(vRedirector.to!,
    1280             :           queryParameters: queryParameters, newHistoryState: newHistoryState);
    1281           0 :     } else if (Platform.isAndroid || Platform.isIOS) {
    1282             :       // If we didn't find a url to go to, we are at the start of the stack
    1283             :       // so we close the app on mobile
    1284           0 :       MoveToBackground.moveTaskToBack();
    1285             :     }
    1286             :   }
    1287             : 
    1288             :   /// Performs a systemPop cycle:
    1289             :   /// 1. Call onSystemPop in all active [VWidgetGuards] if implemented, else onPop
    1290             :   /// 2. Call onSystemPop in all [VRouteElement] if implemented, else onPop
    1291             :   /// 3. Call onSystemPop of VRouter if implemented, else onPop
    1292             :   /// 4. Update the url to the one found in [_defaultPop]
    1293           6 :   Future<void> _systemPop(
    1294             :     VRouteElement elementToPop, {
    1295             :     Map<String, String> pathParameters = const {},
    1296             :     Map<String, String> queryParameters = const {},
    1297             :     Map<String, String> newHistoryState = const {},
    1298             :   }) async {
    1299           6 :     assert(url != null);
    1300             : 
    1301             :     // Get information on where to pop from _defaultPop
    1302           6 :     final defaultPopResult = _defaultPop(
    1303             :       elementToPop,
    1304             :       pathParameters: pathParameters,
    1305             :       queryParameters: queryParameters,
    1306             :       newHistoryState: newHistoryState,
    1307             :     );
    1308             : 
    1309           6 :     final vRedirector = defaultPopResult.vRedirector;
    1310             : 
    1311           6 :     final poppedVRouteElements = defaultPopResult.poppedVRouteElements;
    1312             : 
    1313             :     final List<VWidgetGuardMessageRoot> poppedVWidgetGuardsMessagesRoot =
    1314           6 :         _vWidgetGuardMessagesRoot
    1315           6 :             .where((vWidgetGuardMessageRoot) =>
    1316           0 :                 poppedVRouteElements.contains(vWidgetGuardMessageRoot.associatedVRouteElement))
    1317           6 :             .toList();
    1318             : 
    1319             :     /// 1. Call onSystemPop in all popping [VWidgetGuards] if implemented, else onPop
    1320           6 :     for (var vWidgetGuardMessageRoot in poppedVWidgetGuardsMessagesRoot) {
    1321           0 :       if (vWidgetGuardMessageRoot.vWidgetGuard.onSystemPop != VPopHandler._voidOnSystemPop) {
    1322           0 :         await vWidgetGuardMessageRoot.vWidgetGuard.onSystemPop(vRedirector);
    1323             :       } else {
    1324           0 :         await vWidgetGuardMessageRoot.vWidgetGuard.onPop(vRedirector);
    1325             :       }
    1326           0 :       if (!vRedirector.shouldUpdate) {
    1327           0 :         vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
    1328           0 :                 .getChildVRouteElementNode(
    1329           0 :                     vRouteElement: vWidgetGuardMessageRoot.associatedVRouteElement) ??
    1330           0 :             _vRoute.vRouteElementNode);
    1331             :         return;
    1332             :       }
    1333             :     }
    1334             : 
    1335             :     /// 2. Call onSystemPop in all popped [VRouteElement] if implemented, else onPop
    1336          12 :     for (var vRouteElement in poppedVRouteElements) {
    1337          12 :       if (vRouteElement.onSystemPop != VPopHandler._voidOnSystemPop) {
    1338          12 :         await vRouteElement.onSystemPop(vRedirector);
    1339             :       } else {
    1340           0 :         await vRouteElement.onPop(vRedirector);
    1341             :       }
    1342           6 :       if (!vRedirector.shouldUpdate) {
    1343           1 :         vRedirector._redirectFunction?.call(_vRoute.vRouteElementNode
    1344           0 :                 .getChildVRouteElementNode(vRouteElement: vRouteElement) ??
    1345           0 :             _vRoute.vRouteElementNode);
    1346             :         return;
    1347             :       }
    1348             :     }
    1349             : 
    1350             :     /// 3. Call onSystemPop of VRouter if implemented, else onPop
    1351          18 :     if (widget.onSystemPop != VPopHandler._voidOnSystemPop) {
    1352          18 :       await widget.onSystemPop(vRedirector);
    1353             :     } else {
    1354           0 :       await widget.onPop(vRedirector);
    1355             :     }
    1356           6 :     if (!vRedirector.shouldUpdate) {
    1357           0 :       vRedirector._redirectFunction?.call(
    1358           0 :           _vRoute.vRouteElementNode.getChildVRouteElementNode(vRouteElement: widget) ??
    1359           0 :               _vRoute.vRouteElementNode);
    1360             :       return;
    1361             :     }
    1362             : 
    1363             :     /// 4. Update the url to the one found in [_defaultPop]
    1364           6 :     if (vRedirector.newVRouterData != null) {
    1365          12 :       _updateUrl(vRedirector.to!,
    1366             :           queryParameters: queryParameters, newHistoryState: newHistoryState);
    1367             :     } else if (!kIsWeb) {
    1368             :       // If we didn't find a url to go to, we are at the start of the stack
    1369             :       // so we close the app on mobile
    1370           0 :       MoveToBackground.moveTaskToBack();
    1371             :     }
    1372             :   }
    1373             : 
    1374             :   /// Uses [VRouteElement.getPathFromPop] to determine the new path after popping [elementToPop]
    1375             :   ///
    1376             :   /// See:
    1377             :   ///   * [VWidgetGuard.onPop] to override this behaviour locally
    1378             :   ///   * [VRouteElement.onPop] to override this behaviour on a on a route level
    1379             :   ///   * [VRouter.onPop] to override this behaviour on a global level
    1380             :   ///   * [VWidgetGuard.onSystemPop] to override this behaviour locally
    1381             :   ///                               when the call comes from the system
    1382             :   ///   * [VRouteElement.onSystemPop] to override this behaviour on a route level
    1383             :   ///                               when the call comes from the system
    1384             :   ///   * [VRouter.onSystemPop] to override this behaviour on a global level
    1385             :   ///                               when the call comes from the system
    1386           8 :   DefaultPopResult _defaultPop(
    1387             :     VRouteElement elementToPop, {
    1388             :     Map<String, String> pathParameters = const {},
    1389             :     Map<String, String> queryParameters = const {},
    1390             :     Map<String, String> newHistoryState = const {},
    1391             :   }) {
    1392           8 :     assert(url != null);
    1393             :     // Encode the path parameters
    1394             :     pathParameters =
    1395          14 :         pathParameters.map((key, value) => MapEntry(key, Uri.encodeComponent(value)));
    1396             : 
    1397             :     // We don't use widget.getPathFromPop because widget.routes might have changed with a setState
    1398          32 :     final getPathFromPopResult = _vRoute.vRouteElementNode.vRouteElement.getPathFromPop(
    1399             :       elementToPop,
    1400             :       pathParameters: pathParameters,
    1401          16 :       parentPathResult: ValidParentPathResult(path: null, pathParameters: {}),
    1402             :     );
    1403             : 
    1404           8 :     if (getPathFromPopResult is ErrorGetPathFromPopResult) {
    1405             :       throw getPathFromPopResult;
    1406             :     }
    1407             : 
    1408           7 :     final newPath = (getPathFromPopResult as ValidPopResult).path;
    1409             : 
    1410             :     // This url will be not null if we find a route to go to
    1411             :     late final String? newUrl;
    1412             :     late final RootVRouterData? newVRouterData;
    1413             : 
    1414             :     // If newPath is empty then the app should be put in the background (for mobile)
    1415             :     if (newPath != null) {
    1416             :       // Integrate the given query parameters
    1417           7 :       newUrl = Uri.tryParse(newPath)
    1418          14 :           ?.replace(queryParameters: (queryParameters.isNotEmpty) ? queryParameters : null)
    1419           7 :           .toString();
    1420             : 
    1421           7 :       newVRouterData = RootVRouterData(
    1422           7 :         child: Container(),
    1423             :         historyState: newHistoryState,
    1424             :         pathParameters: pathParameters,
    1425             :         queryParameters: queryParameters,
    1426           0 :         url: newUrl,
    1427           7 :         previousUrl: url,
    1428             :         state: this,
    1429             :       );
    1430             :     } else {
    1431           0 :       newUrl = null;
    1432           0 :       newVRouterData = null;
    1433             :     }
    1434             : 
    1435           7 :     return DefaultPopResult(
    1436           7 :       vRedirector: VRedirector(
    1437           7 :         context: _rootVRouterContext,
    1438           7 :         from: url,
    1439           0 :         to: newUrl,
    1440           7 :         previousVRouterData: RootVRouterData(
    1441           7 :           child: Container(),
    1442           7 :           historyState: historyState,
    1443          14 :           pathParameters: _vRoute.pathParameters,
    1444             :           queryParameters: queryParameters,
    1445             :           state: this,
    1446           7 :           previousUrl: previousUrl,
    1447           7 :           url: url,
    1448             :         ),
    1449           0 :         newVRouterData: newVRouterData,
    1450             :       ),
    1451           7 :       poppedVRouteElements: getPathFromPopResult.poppedVRouteElements,
    1452             :     );
    1453             :   }
    1454             : 
    1455             :   /// This replaces the current history state of [VRouter] with given one
    1456           0 :   void replaceHistoryState(Map<String, String> newHistoryState) {
    1457           0 :     pushReplacement((url != null) ? Uri.parse(url!).path : '/', historyState: newHistoryState);
    1458             :   }
    1459             : 
    1460             :   /// WEB ONLY
    1461             :   /// Save the state if needed before the app gets unloaded
    1462             :   /// Mind that this happens when the user enter a url manually in the
    1463             :   /// browser so we can't prevent him from leaving the page
    1464           0 :   void _onBeforeUnload() async {
    1465           0 :     if (url == null) return;
    1466             : 
    1467           0 :     Map<String, String> historyStateToSave = {};
    1468           0 :     void saveHistoryState(Map<String, String> historyState) {
    1469           0 :       historyStateToSave.addAll(historyState);
    1470             :     }
    1471             : 
    1472             :     // Instantiate VRedirector
    1473           0 :     final vRedirector = VRedirector(
    1474           0 :       context: _rootVRouterContext,
    1475           0 :       from: url,
    1476             :       to: null,
    1477           0 :       previousVRouterData: RootVRouterData(
    1478           0 :         child: Container(),
    1479           0 :         historyState: historyState,
    1480           0 :         pathParameters: _vRoute.pathParameters,
    1481           0 :         queryParameters: this.queryParameters,
    1482             :         state: this,
    1483           0 :         url: url,
    1484           0 :         previousUrl: previousUrl,
    1485             :       ),
    1486             :       newVRouterData: null,
    1487             :     );
    1488             : 
    1489             :     ///   1. Call beforeLeave in all deactivated [VWidgetGuard]
    1490           0 :     for (var vWidgetGuardMessageRoot in _vWidgetGuardMessagesRoot) {
    1491           0 :       await vWidgetGuardMessageRoot.vWidgetGuard.beforeLeave(vRedirector, saveHistoryState);
    1492             :     }
    1493             : 
    1494             :     ///   2. Call beforeLeave in all deactivated [VRouteElement] and [VRouter]
    1495           0 :     for (var vRouteElement in _vRoute.vRouteElements.reversed) {
    1496           0 :       await vRouteElement.beforeLeave(vRedirector, saveHistoryState);
    1497             :     }
    1498             : 
    1499           0 :     if (historyStateToSave.isNotEmpty) {
    1500             :       ///   The historyStates got in beforeLeave are stored   ///
    1501           0 :       BrowserHelpers.replaceHistoryState(jsonEncode({
    1502           0 :         'serialCount': _serialCount,
    1503           0 :         'historyState': jsonEncode(historyStateToSave),
    1504             :       }));
    1505             :     }
    1506             :   }
    1507             : 
    1508             :   /// Starts a pop cycle
    1509             :   ///
    1510             :   /// Pop cycle:
    1511             :   ///   1. onPop is called in all [VNavigationGuard]s
    1512             :   ///   2. onPop is called in all [VRouteElement]s of the current route
    1513             :   ///   3. onPop is called in [VRouter]
    1514             :   ///
    1515             :   /// In any of the above steps, we can use [vRedirector] if you want to redirect or
    1516             :   /// stop the navigation
    1517           3 :   Future<void> pop({
    1518             :     Map<String, String> pathParameters = const {},
    1519             :     Map<String, String> queryParameters = const {},
    1520             :     Map<String, String> newHistoryState = const {},
    1521             :   }) async {
    1522           3 :     _pop(
    1523           9 :       _vRoute.vRouteElementNode.getVRouteElementToPop(),
    1524             :       pathParameters: pathParameters,
    1525             :       queryParameters: queryParameters,
    1526             :       newHistoryState: newHistoryState,
    1527             :     );
    1528             :   }
    1529             : 
    1530             :   /// Starts a systemPop cycle
    1531             :   ///
    1532             :   /// systemPop cycle:
    1533             :   ///   1. onSystemPop (or onPop if not implemented) is called in all VNavigationGuards
    1534             :   ///   2. onSystemPop (or onPop if not implemented) is called in the nested-most VRouteElement of the current route
    1535             :   ///   3. onSystemPop (or onPop if not implemented) is called in VRouter
    1536             :   ///
    1537             :   /// In any of the above steps, we can use a [VRedirector] if you want to redirect or
    1538             :   /// stop the navigation
    1539           2 :   Future<void> systemPop({
    1540             :     Map<String, String> pathParameters = const {},
    1541             :     Map<String, String> queryParameters = const {},
    1542             :     Map<String, String> newHistoryState = const {},
    1543             :   }) async {
    1544           2 :     _systemPop(
    1545           6 :       _vRoute.vRouteElementNode.getVRouteElementToPop(),
    1546             :       pathParameters: pathParameters,
    1547             :       queryParameters: queryParameters,
    1548             :       newHistoryState: newHistoryState,
    1549             :     );
    1550             :   }
    1551             : 
    1552             :   /// Pushes the new route of the given url on top of the current one
    1553             :   /// A path can be of one of two forms:
    1554             :   ///   * stating with '/', in which case we just navigate
    1555             :   ///     to the given path
    1556             :   ///   * not starting with '/', in which case we append the
    1557             :   ///     current path to the given one
    1558             :   ///
    1559             :   /// We can also specify queryParameters, either by directly
    1560             :   /// putting them is the url or by providing a Map using [queryParameters]
    1561             :   ///
    1562             :   /// We can also put a state to the next route, this state will
    1563             :   /// be a router state (this is the only kind of state that we can
    1564             :   /// push) accessible with VRouter.of(context).historyState
    1565          11 :   void push(
    1566             :     String newUrl, {
    1567             :     Map<String, String> queryParameters = const {},
    1568             :     Map<String, String> historyState = const {},
    1569             :   }) {
    1570          11 :     if (!newUrl.startsWith('/')) {
    1571           2 :       if (url == null) {
    1572           1 :         throw InvalidPushVError(url: newUrl);
    1573             :       }
    1574           3 :       final currentPath = Uri.parse(url!).path;
    1575           4 :       newUrl = currentPath + (currentPath.endsWith('/') ? '' : '/') + '$newUrl';
    1576             :     }
    1577             : 
    1578          11 :     _updateUrl(
    1579             :       newUrl,
    1580             :       queryParameters: queryParameters,
    1581             :       newHistoryState: historyState,
    1582             :     );
    1583             :   }
    1584             : 
    1585             :   /// Updates the url given a [VRouteElement] name
    1586             :   ///
    1587             :   /// We can also specify path parameters to inject into the new path
    1588             :   ///
    1589             :   /// We can also specify queryParameters, either by directly
    1590             :   /// putting them is the url or by providing a Map using [queryParameters]
    1591             :   ///
    1592             :   /// We can also put a state to the next route, this state will
    1593             :   /// be a router state (this is the only kind of state that we can
    1594             :   /// push) accessible with VRouter.of(context).historyState
    1595             :   ///
    1596             :   /// After finding the url and taking charge of the path parameters,
    1597             :   /// it updates the url
    1598             :   ///
    1599             :   /// To specify a name, see [VRouteElement.name]
    1600           6 :   void pushNamed(
    1601             :     String name, {
    1602             :     Map<String, String> pathParameters = const {},
    1603             :     Map<String, String> queryParameters = const {},
    1604             :     Map<String, String> historyState = const {},
    1605             :   }) {
    1606           6 :     _updateUrlFromName(name,
    1607             :         pathParameters: pathParameters,
    1608             :         queryParameters: queryParameters,
    1609             :         newHistoryState: historyState);
    1610             :   }
    1611             : 
    1612             :   /// Replace the current one by the new route corresponding to the given url
    1613             :   /// The difference with [push] is that this overwrites the current browser history entry
    1614             :   /// If you are on mobile, this is the same as push
    1615             :   /// Path can be of one of two forms:
    1616             :   ///   * stating with '/', in which case we just navigate
    1617             :   ///     to the given path
    1618             :   ///   * not starting with '/', in which case we append the
    1619             :   ///     current path to the given one
    1620             :   ///
    1621             :   /// We can also specify queryParameters, either by directly
    1622             :   /// putting them is the url or by providing a Map using [queryParameters]
    1623             :   ///
    1624             :   /// We can also put a state to the next route, this state will
    1625             :   /// be a router state (this is the only kind of state that we can
    1626             :   /// push) accessible with VRouter.of(context).historyState
    1627           5 :   void pushReplacement(
    1628             :     String newUrl, {
    1629             :     Map<String, String> queryParameters = const {},
    1630             :     Map<String, String> historyState = const {},
    1631             :   }) {
    1632             :     // If not on the web, this is the same as push
    1633             :     if (!kIsWeb) {
    1634           5 :       return push(newUrl, queryParameters: queryParameters, historyState: historyState);
    1635             :     }
    1636             : 
    1637           0 :     if (!newUrl.startsWith('/')) {
    1638           0 :       if (url == null) {
    1639           0 :         throw InvalidPushVError(url: newUrl);
    1640             :       }
    1641           0 :       final currentPath = Uri.parse(url!).path;
    1642           0 :       newUrl = currentPath + '/$newUrl';
    1643             :     }
    1644             : 
    1645             :     // Update the url, setting isReplacement to true
    1646           0 :     _updateUrl(
    1647             :       newUrl,
    1648             :       queryParameters: queryParameters,
    1649             :       newHistoryState: historyState,
    1650             :       isReplacement: true,
    1651             :     );
    1652             :   }
    1653             : 
    1654             :   /// Replace the url given a [VRouteElement] name
    1655             :   /// The difference with [pushNamed] is that this overwrites the current browser history entry
    1656             :   ///
    1657             :   /// We can also specify path parameters to inject into the new path
    1658             :   ///
    1659             :   /// We can also specify queryParameters, either by directly
    1660             :   /// putting them is the url or by providing a Map using [queryParameters]
    1661             :   ///
    1662             :   /// We can also put a state to the next route, this state will
    1663             :   /// be a router state (this is the only kind of state that we can
    1664             :   /// push) accessible with VRouter.of(context).historyState
    1665             :   ///
    1666             :   /// After finding the url and taking charge of the path parameters
    1667             :   /// it updates the url
    1668             :   ///
    1669             :   /// To specify a name, see [VPath.name]
    1670           0 :   void pushReplacementNamed(
    1671             :     String name, {
    1672             :     Map<String, String> pathParameters = const {},
    1673             :     Map<String, String> queryParameters = const {},
    1674             :     Map<String, String> historyState = const {},
    1675             :   }) {
    1676           0 :     _updateUrlFromName(name,
    1677             :         pathParameters: pathParameters,
    1678             :         queryParameters: queryParameters,
    1679             :         newHistoryState: historyState,
    1680             :         isReplacement: true);
    1681             :   }
    1682             : 
    1683             :   /// Goes to an url which is not in the app
    1684             :   ///
    1685             :   /// On the web, you can set [openNewTab] to true to open this url
    1686             :   /// in a new tab
    1687           0 :   void pushExternal(String newUrl, {bool openNewTab = false}) =>
    1688           0 :       _updateUrl(newUrl, isUrlExternal: true, openNewTab: openNewTab);
    1689             : }
    1690             : 
    1691             : class DefaultPopResult {
    1692             :   VRedirector vRedirector;
    1693             :   List<VRouteElement> poppedVRouteElements;
    1694             : 
    1695           7 :   DefaultPopResult({
    1696             :     required this.vRedirector,
    1697             :     required this.poppedVRouteElements,
    1698             :   });
    1699             : }

Generated by: LCOV version 1.14