LCOV - code coverage report
Current view: top level - src/vrouter - vrouter.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 270 409 66.0 %
Date: 2021-03-18 15:42:40 Functions: 0 0 -

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

Generated by: LCOV version 1.14