LCOV - code coverage report
Current view: top level - src - beamer_delegate.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 204 219 93.2 %
Date: 2021-07-18 19:09:57 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:convert';
       2             : 
       3             : import 'package:beamer/beamer.dart';
       4             : import 'package:beamer/src/transition_delegates.dart';
       5             : import 'package:flutter/foundation.dart';
       6             : import 'package:flutter/material.dart';
       7             : import 'package:flutter/services.dart';
       8             : import 'package:flutter/widgets.dart';
       9             : 
      10             : import 'utils.dart';
      11             : 
      12             : /// A delegate that is used by the [Router] to build the [Navigator].
      13             : ///
      14             : /// This is "the beamer", the one that does the actual beaming.
      15             : class BeamerDelegate extends RouterDelegate<RouteInformation>
      16             :     with ChangeNotifier, PopNavigatorRouterDelegateMixin<RouteInformation> {
      17           6 :   BeamerDelegate({
      18             :     required this.locationBuilder,
      19             :     this.initialPath = '/',
      20             :     this.listener,
      21             :     this.preferUpdate = true,
      22             :     this.removeDuplicateHistory = true,
      23             :     this.notFoundPage,
      24             :     this.notFoundRedirect,
      25             :     this.notFoundRedirectNamed,
      26             :     this.guards = const <BeamGuard>[],
      27             :     this.navigatorObservers = const <NavigatorObserver>[],
      28             :     this.transitionDelegate = const DefaultTransitionDelegate(),
      29             :     this.beamBackTransitionDelegate = const ReverseTransitionDelegate(),
      30             :     this.onPopPage,
      31             :     this.setBrowserTabTitle = true,
      32             :     this.updateFromParent = true,
      33             :     this.updateParent = true,
      34             :   }) {
      35           6 :     notFoundPage ??= const BeamPage(
      36             :       title: 'Not found',
      37             :       child: Scaffold(body: Center(child: Text('Not found'))),
      38             :     );
      39             : 
      40          12 :     _currentTransitionDelegate = transitionDelegate;
      41             : 
      42          18 :     configuration = RouteInformation(location: initialPath);
      43          12 :     _currentBeamLocation = EmptyBeamLocation();
      44             :   }
      45             : 
      46             :   /// A state of this delegate. This is the `routeInformation` that goes into
      47             :   /// [locationBuilder] to build an appropriate [BeamLocation].
      48             :   ///
      49             :   /// A way to modify this state is via [update].
      50             :   late RouteInformation configuration;
      51             : 
      52             :   BeamerDelegate? _parent;
      53             : 
      54             :   /// A delegate of a parent of the [Beamer] that has this delegate.
      55             :   ///
      56             :   /// This is not null only if multiple [Beamer]s are used;
      57             :   /// `*App.router` and at least one more [Beamer] in the Widget tree.
      58           6 :   BeamerDelegate? get parent => _parent;
      59           3 :   set parent(BeamerDelegate? parent) {
      60           3 :     _parent = parent!;
      61           3 :     _initializeFromParent();
      62           3 :     if (updateFromParent) {
      63           9 :       _parent!.addListener(_updateFromParent);
      64             :     }
      65             :   }
      66             : 
      67             :   /// The top-most [BeamerDelegate], a parent of all.
      68             :   ///
      69             :   /// It will return root even when called on root.
      70           1 :   BeamerDelegate get root {
      71           1 :     if (_parent == null) {
      72             :       return this;
      73             :     }
      74           1 :     var root = _parent!;
      75           1 :     while (root._parent != null) {
      76           1 :       root = root._parent!;
      77             :     }
      78             :     return root;
      79             :   }
      80             : 
      81             :   /// A builder for [BeamLocation]s.
      82             :   ///
      83             :   /// There are 3 ways of building an appropriate [BeamLocation] which will in
      84             :   /// turn build a stack of pages that should go into [Navigator.pages].
      85             :   ///
      86             :   ///   1. Custom closure
      87             :   /// ```dart
      88             :   /// locationBuilder: (state) {
      89             :   ///   if (state.uri.pathSegments.contains('l1')) {
      90             :   ///     return Location1(state);
      91             :   ///   }
      92             :   ///   if (state.uri.pathSegments.contains('l2')) {
      93             :   ///     return Location2(state);
      94             :   ///   }
      95             :   ///   return NotFound(path: state.uri.toString());
      96             :   /// },
      97             :   /// ```
      98             :   ///
      99             :   ///   2. [BeamerLocationBuilder]; chooses appropriate [BeamLocation] itself
     100             :   /// ```dart
     101             :   /// locationBuilder: BeamerLocationBuilder(
     102             :   ///   beamLocations: [
     103             :   ///     Location1(),
     104             :   ///     Location2(),
     105             :   ///   ],
     106             :   /// ),
     107             :   /// ```
     108             :   ///
     109             :   ///   3. [SimpleLocationBuilder]; a Map of routes
     110             :   /// ```dart
     111             :   /// locationBuilder: SimpleLocationBuilder(
     112             :   ///   routes: {
     113             :   ///     '/': (context) => HomeScreen(),
     114             :   ///     '/another': (context) => AnotherScreen(),
     115             :   ///   },
     116             :   /// ),
     117             :   /// ```
     118             :   final LocationBuilder locationBuilder;
     119             : 
     120             :   /// The path to replace `/` as default initial route path upon load.
     121             :   ///
     122             :   /// Note that (if set to anything other than `/` (default)),
     123             :   /// you will not be able to navigate to `/` by manually typing
     124             :   /// it in the URL bar, because it will always be transformed to `initialPath`,
     125             :   /// but you will be able to get to `/` by popping pages with back button,
     126             :   /// if there are pages in [BeamLocation.buildPages] that will build
     127             :   /// when there are no path segments.
     128             :   final String initialPath;
     129             : 
     130             :   /// The listener for this, that will be called on every navigation event
     131             :   /// and will recieve the [state] and [currentBeamLocation].
     132             :   final void Function(RouteInformation, BeamLocation)? listener;
     133             : 
     134             :   /// Whether to prefer updating [currentBeamLocation] if it's of the same type
     135             :   /// as the [BeamLocation] being beamed to,
     136             :   /// instead of adding it to [beamLocationHistory].
     137             :   ///
     138             :   /// See how this is used at [_pushHistory] implementation.
     139             :   final bool preferUpdate;
     140             : 
     141             :   /// Whether to remove [BeamLocation]s from [beamLocationHistory]
     142             :   /// if they are the same type as the location being beamed to.
     143             :   ///
     144             :   /// See how this is used at [_pushHistory] implementation.
     145             :   final bool removeDuplicateHistory;
     146             : 
     147             :   /// Page to show when no [BeamLocation] supports the incoming URI.
     148             :   BeamPage? notFoundPage;
     149             : 
     150             :   /// [BeamLocation] to redirect to when no [BeamLocation] supports the incoming URI.
     151             :   final BeamLocation? notFoundRedirect;
     152             : 
     153             :   /// URI string to redirect to when no [BeamLocation] supports the incoming URI.
     154             :   final String? notFoundRedirectNamed;
     155             : 
     156             :   /// Guards that will be executing [check] on [currentBeamLocation] candidate.
     157             :   ///
     158             :   /// Checks will be executed in order; chain of responsibility pattern.
     159             :   /// When some guard returns `false`, location candidate will not be accepted
     160             :   /// and stack of pages will be updated as is configured in [BeamGuard].
     161             :   final List<BeamGuard> guards;
     162             : 
     163             :   /// The list of observers for the [Navigator].
     164             :   final List<NavigatorObserver> navigatorObservers;
     165             : 
     166             :   /// A transition delegate to be used by [Navigator].
     167             :   ///
     168             :   /// This transition delegate will be overridden by the one in [BeamLocation],
     169             :   /// if any is set.
     170             :   ///
     171             :   /// See [Navigator.transitionDelegate].
     172             :   final TransitionDelegate transitionDelegate;
     173             : 
     174             :   /// A transition delegate to be used by [Navigator] when beaming back.
     175             :   ///
     176             :   /// When calling [beamBack], it's useful to animate routes in reverse order;
     177             :   /// adding the new ones behind and then popping the current ones,
     178             :   /// therefore, the default is [ReverseTransitionDelegate].
     179             :   final TransitionDelegate beamBackTransitionDelegate;
     180             : 
     181             :   /// Callback when `pop` is requested.
     182             :   ///
     183             :   /// Return `true` if pop will be handled entirely by this function.
     184             :   /// Return `false` if beamer should finish handling the pop.
     185             :   ///
     186             :   /// See [build] for details on how beamer handles [Navigator.onPopPage].
     187             :   bool Function(BuildContext context, Route<dynamic> route, dynamic result)?
     188             :       onPopPage;
     189             : 
     190             :   /// Whether the title attribute of [BeamPage] should
     191             :   /// be used to set and update the browser tab title.
     192             :   final bool setBrowserTabTitle;
     193             : 
     194             :   /// Whether to call [update] when parent notifies listeners.
     195             :   ///
     196             :   /// This means that navigation can be done either on parent or on this
     197             :   final bool updateFromParent;
     198             : 
     199             :   /// Whether to call [update] on [parent] when [state] is updated.
     200             :   ///
     201             :   /// This means that parent's [beamStateHistory] will be in sync.
     202             :   final bool updateParent;
     203             : 
     204             :   final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
     205             : 
     206             :   /// {@template routeHistory}
     207             :   /// The history of beaming states.
     208             :   ///
     209             :   /// [BeamState] is inserted on every beaming event, if it differs from last.
     210             :   ///
     211             :   /// See [_pushHistory].
     212             :   /// {@endtemplate}
     213             :   final List<RouteInformation> routeHistory = [];
     214             : 
     215             :   /// {@template beamLocationHistory}
     216             :   /// The history of [BeamLocation]s.
     217             :   ///
     218             :   /// [BeamLocation] is inserted differently depending on configuration of
     219             :   /// [preferUpdate], [replaceCurrent], [removeDuplicateHistory].
     220             :   ///
     221             :   /// See [_pushHistory].
     222             :   /// {@endtemplate}
     223             :   final List<BeamLocation> beamLocationHistory = [];
     224             : 
     225             :   late BeamLocation _currentBeamLocation;
     226             : 
     227             :   /// {@template currentBeamLocation}
     228             :   /// A [BeamLocation] that is currently responsible for providing a page stack
     229             :   /// via [BeamLocation.buildPages] and holds the current [BeamState].
     230             :   ///
     231             :   /// Usually obtained via
     232             :   /// ```dart
     233             :   /// Beamer.of(context).currentBeamLocation
     234             :   /// ```
     235             :   /// {@endtemplate}
     236          12 :   BeamLocation get currentBeamLocation => _currentBeamLocation;
     237             : 
     238             :   List<BeamPage> _currentPages = [];
     239             : 
     240             :   /// {@template currentPages}
     241             :   /// [currentBeamLocation]'s "effective" pages, the ones that were built.
     242             :   /// {@endtemplate}
     243           8 :   List<BeamPage> get currentPages => _currentPages;
     244             : 
     245             :   /// Whether to implicitly [beamBack] instead of default pop.
     246             :   bool _beamBackOnPop = false;
     247             : 
     248             :   /// Whether to implicitly [popBeamLocation] instead of default pop.
     249             :   bool _popBeamLocationOnPop = false;
     250             : 
     251             :   /// Which transition delegate to use in the next build.
     252             :   late TransitionDelegate _currentTransitionDelegate;
     253             : 
     254             :   /// Which location to pop to, instead of default pop.
     255             :   ///
     256             :   /// This is more general than [_beamBackOnPop].
     257             :   RouteInformation? _popConfiguration;
     258             : 
     259             :   /// Whether all the pages from [currentBeamLocation] are stacked.
     260             :   /// If not (`false`), just the last page is taken.
     261             :   bool _stacked = true;
     262             : 
     263             :   /// If `false`, does not report the route until next [update].
     264             :   ///
     265             :   /// Useful when having sibling beamers that are both build at the same time.
     266             :   /// Becomes active on next [update].
     267             :   bool active = true;
     268             : 
     269             :   /// The [Navigator] that belongs to this [BeamerDelegate].
     270             :   ///
     271             :   /// Useful for popping dialogs without accessing [BuildContext]:
     272             :   ///
     273             :   /// ```dart
     274             :   /// beamerDelegate.navigator.pop();
     275             :   /// ```
     276           3 :   NavigatorState get navigator => _navigatorKey.currentState!;
     277             : 
     278             :   /// Main method to update the [state] of this; `Beamer.of(context)`,
     279             :   ///
     280             :   /// This "top-level" [update] is generally used for navigation
     281             :   /// _between_ [BeamLocation]s and not _within_ a specific [BeamLocation].
     282             :   /// For latter purpose, see [BeamLocation.update].
     283             :   /// Nevertheless, [update] **will** work for navigation within [BeamLocation].
     284             :   /// Calling [update] will run the [locationBuilder].
     285             :   ///
     286             :   /// ```dart
     287             :   /// Beamer.of(context).update(
     288             :   ///   state: BeamState.fromUriString('/xx'),
     289             :   /// );
     290             :   /// ```
     291             :   ///
     292             :   /// **[beamTo] and [beamToNamed] call [update] to really do the update.**
     293             :   ///
     294             :   /// [transitionDelegate] determines how a new stack of pages replaces current.
     295             :   /// See [Navigator.transitionDelegate].
     296             :   ///
     297             :   /// If [beamBackOnPop] is set to `true`,
     298             :   /// default pop action will triger [beamBack] instead.
     299             :   ///
     300             :   /// [popState] is more general than [beamBackOnPop],
     301             :   /// and can beam you anywhere; whatever it resolves to during build.
     302             :   ///
     303             :   /// If [stacked] is set to `false`,
     304             :   /// only the location's last page will be shown.
     305             :   ///
     306             :   /// If [replaceCurrent] is set to `true`,
     307             :   /// new location will replace the last one in the stack.
     308             :   ///
     309             :   /// If [rebuild] is set to `false`,
     310             :   /// [build] will not occur, but [state] and browser URL will be updated.
     311           6 :   void update({
     312             :     RouteInformation? configuration,
     313             :     RouteInformation? popConfiguration,
     314             :     TransitionDelegate? transitionDelegate,
     315             :     bool beamBackOnPop = false,
     316             :     bool popBeamLocationOnPop = false,
     317             :     bool stacked = true,
     318             :     bool replaceCurrent = false,
     319             :     bool buildBeamLocation = true,
     320             :     bool rebuild = true,
     321             :     bool updateParent = true,
     322             :   }) {
     323           6 :     configuration = configuration?.copyWith(
     324          12 :       location: Utils.trimmed(configuration.location),
     325             :     );
     326           1 :     popConfiguration = popConfiguration?.copyWith(
     327           2 :       location: Utils.trimmed(popConfiguration.location),
     328             :     );
     329             : 
     330           6 :     active = true;
     331          12 :     _popConfiguration = popConfiguration ?? _popConfiguration;
     332          12 :     _currentTransitionDelegate = transitionDelegate ?? this.transitionDelegate;
     333           6 :     _beamBackOnPop = beamBackOnPop;
     334           6 :     _popBeamLocationOnPop = popBeamLocationOnPop;
     335           6 :     _stacked = stacked;
     336             : 
     337             :     if (configuration != null) {
     338           6 :       this.configuration = configuration;
     339             :       if (buildBeamLocation) {
     340          12 :         final location = locationBuilder(this.configuration);
     341           6 :         _pushHistory(location, replaceCurrent: replaceCurrent);
     342             :       }
     343           6 :       listener?.call(this.configuration, _currentBeamLocation);
     344             :     }
     345             : 
     346           6 :     if (this.updateParent &&
     347             :         updateParent &&
     348          13 :         configuration != _parent?.configuration) {
     349           7 :       _parent?.update(
     350           2 :         configuration: this.configuration.copyWith(),
     351             :         rebuild: false,
     352             :       );
     353             :     }
     354             : 
     355             :     if (!rebuild || !updateParent) {
     356           8 :       updateRouteInformation(this.configuration);
     357             :     }
     358             : 
     359             :     if (rebuild) {
     360           6 :       notifyListeners();
     361             :     }
     362             :   }
     363             : 
     364             :   /// {@template beamTo}
     365             :   /// Beams to a specific, manually configured [BeamLocation].
     366             :   ///
     367             :   /// For example
     368             :   /// ```dart
     369             :   /// Beamer.of(context).beamTo(
     370             :   ///   Location2(
     371             :   ///     BeamState(
     372             :   ///       pathBlueprintSegments = ['user',':userId','transactions'],
     373             :   ///       pathParameters = {'userId': '1'},
     374             :   ///       queryParameters = {'perPage': '10'},
     375             :   ///       data = {'favoriteUser': true},
     376             :   ///     ),
     377             :   ///   ),
     378             :   /// );
     379             :   /// ```
     380             :   ///
     381             :   /// See [update] for more details.
     382             :   /// {@endtemplate}
     383           2 :   void beamTo(
     384             :     BeamLocation location, {
     385             :     BeamLocation? popTo,
     386             :     TransitionDelegate? transitionDelegate,
     387             :     bool beamBackOnPop = false,
     388             :     bool popBeamLocationOnPop = false,
     389             :     bool stacked = true,
     390             :     bool replaceCurrent = false,
     391             :   }) {
     392           2 :     _pushHistory(location, replaceCurrent: replaceCurrent);
     393           2 :     update(
     394           4 :       configuration: location.state.routeInformation,
     395           0 :       popConfiguration: popTo?.state.routeInformation,
     396             :       transitionDelegate: transitionDelegate,
     397             :       beamBackOnPop: beamBackOnPop,
     398             :       popBeamLocationOnPop: popBeamLocationOnPop,
     399             :       stacked: stacked,
     400             :       buildBeamLocation: false,
     401             :     );
     402             :   }
     403             : 
     404             :   /// {@template beamToNamed}
     405             :   /// Beams to [BeamLocation] that has [uri] contained within its
     406             :   /// [BeamLocation.pathBlueprintSegments].
     407             :   ///
     408             :   /// For example
     409             :   /// ```dart
     410             :   /// Beamer.of(context).beamToNamed(
     411             :   ///   '/user/1/transactions?perPage=10',
     412             :   ///   data: {'favoriteUser': true},,
     413             :   /// );
     414             :   /// ```
     415             :   ///
     416             :   /// See [update] for more details.
     417             :   /// {@endtemplate}
     418           6 :   void beamToNamed(
     419             :     String uri, {
     420             :     Map<String, dynamic>? data,
     421             :     String? popToNamed,
     422             :     TransitionDelegate? transitionDelegate,
     423             :     bool beamBackOnPop = false,
     424             :     bool popBeamLocationOnPop = false,
     425             :     bool stacked = true,
     426             :     bool replaceCurrent = false,
     427             :   }) {
     428          24 :     final beamData = data ?? _currentBeamLocation.state.routeInformation.state;
     429           6 :     update(
     430           6 :       configuration: RouteInformation(location: uri, state: beamData),
     431             :       popConfiguration: popToNamed != null
     432           1 :           ? RouteInformation(location: popToNamed, state: beamData)
     433             :           : null,
     434             :       transitionDelegate: transitionDelegate,
     435             :       beamBackOnPop: beamBackOnPop,
     436             :       popBeamLocationOnPop: popBeamLocationOnPop,
     437             :       stacked: stacked,
     438             :       replaceCurrent: replaceCurrent,
     439             :     );
     440             :   }
     441             : 
     442             :   /// {@template popToNamed}
     443             :   /// Calls [beamToNamed] with a [ReverseTransitionDelegate].
     444             :   ///
     445             :   /// See [beamToNamed] for more details.
     446             :   /// {@endtemplate}
     447           3 :   void popToNamed(
     448             :     String uri, {
     449             :     Map<String, dynamic>? data,
     450             :     String? popToNamed,
     451             :     bool beamBackOnPop = false,
     452             :     bool popBeamLocationOnPop = false,
     453             :     bool stacked = true,
     454             :     bool replaceCurrent = false,
     455             :   }) {
     456           6 :     final index = routeHistory.lastIndexWhere(
     457           9 :       (element) => element.location == uri,
     458             :     );
     459           6 :     if (index != -1) {
     460           4 :       routeHistory.removeRange(index, routeHistory.length);
     461             :     }
     462           3 :     beamToNamed(
     463             :       uri,
     464             :       data: data,
     465             :       popToNamed: popToNamed,
     466             :       transitionDelegate: const ReverseTransitionDelegate(),
     467             :       beamBackOnPop: beamBackOnPop,
     468             :       popBeamLocationOnPop: popBeamLocationOnPop,
     469             :       stacked: stacked,
     470             :       replaceCurrent: replaceCurrent,
     471             :     );
     472             :   }
     473             : 
     474             :   /// {@template canBeamBack}
     475             :   /// Whether it is possible to [beamBack],
     476             :   /// i.e. there is more than 1 state in [routeHistory].
     477             :   /// {@endtemplate}
     478          16 :   bool get canBeamBack => routeHistory.length > 1;
     479             : 
     480             :   /// {@template beamBack}
     481             :   /// Beams to previous state in [routeHistory].
     482             :   /// and **removes** the last state from history.
     483             :   ///
     484             :   /// If there is no previous state, does nothing.
     485             :   ///
     486             :   /// Returns the success, whether the [state] updated.
     487             :   /// {@endtemplate}
     488           4 :   bool beamBack({Map<String, dynamic>? data}) {
     489           4 :     if (!canBeamBack) {
     490             :       return false;
     491             :     }
     492           4 :     removeLastRouteInformation();
     493             :     // has to exist because canbeamBack
     494           4 :     final lastConfiguration = removeLastRouteInformation()!;
     495           4 :     update(
     496           4 :       configuration: lastConfiguration.copyWith(state: data),
     497           4 :       transitionDelegate: beamBackTransitionDelegate,
     498             :     );
     499             :     return true;
     500             :   }
     501             : 
     502             :   /// Remove everything except last from [routeHistory].
     503           1 :   void clearRouteHistory() =>
     504           5 :       routeHistory.removeRange(0, routeHistory.length - 1);
     505             : 
     506             :   /// {@template canPopBeamLocation}
     507             :   /// Whether it is possible to [popBeamLocation],
     508             :   /// i.e. there is more than 1 location in [beamLocationHistory].
     509             :   /// {@endtemplate}
     510          12 :   bool get canPopBeamLocation => beamLocationHistory.length > 1;
     511             : 
     512             :   /// {@template popBeamLocation}
     513             :   /// Beams to previous location in [beamLocationHistory]
     514             :   /// and **removes** the last location from history.
     515             :   ///
     516             :   /// If there is no previous location, does nothing.
     517             :   ///
     518             :   /// Returns the success, whether the [currentBeamLocation] was changed.
     519             :   /// {@endtemplate}
     520           3 :   bool popBeamLocation() {
     521           3 :     if (!canPopBeamLocation) {
     522             :       return false;
     523             :     }
     524           6 :     _currentBeamLocation.removeListener(_updateFromLocation);
     525           4 :     beamLocationHistory.removeLast();
     526           6 :     _currentBeamLocation = beamLocationHistory.last;
     527          12 :     routeHistory.add(_currentBeamLocation.state.routeInformation.copyWith());
     528           6 :     _currentBeamLocation.addListener(_updateFromLocation);
     529           2 :     update(
     530           2 :       transitionDelegate: beamBackTransitionDelegate,
     531             :     );
     532             :     return true;
     533             :   }
     534             : 
     535             :   /// Remove everything except last from [beamLocationHistory].
     536           1 :   void clearBeamLocationHistory() =>
     537           5 :       beamLocationHistory.removeRange(0, beamLocationHistory.length - 1);
     538             : 
     539           6 :   @override
     540             :   RouteInformation? get currentConfiguration =>
     541          24 :       _parent == null ? _currentBeamLocation.state.routeInformation : null;
     542             : 
     543           6 :   @override
     544           6 :   GlobalKey<NavigatorState> get navigatorKey => _navigatorKey;
     545             : 
     546           6 :   @override
     547             :   Widget build(BuildContext context) {
     548          18 :     BeamGuard? guard = _checkGuards(guards, context, _currentBeamLocation);
     549             :     if (guard != null) {
     550           1 :       _applyGuard(guard, context);
     551             :     }
     552          12 :     if (_currentBeamLocation is NotFound) {
     553           8 :       if (notFoundRedirect == null && notFoundRedirectNamed == null) {
     554             :         // do nothing, pass on NotFound
     555             :       } else {
     556             :         late BeamLocation redirectBeamLocation;
     557           1 :         if (notFoundRedirect != null) {
     558           1 :           redirectBeamLocation = notFoundRedirect!;
     559           1 :         } else if (notFoundRedirectNamed != null) {
     560           1 :           redirectBeamLocation = locationBuilder(
     561           2 :             RouteInformation(location: notFoundRedirectNamed),
     562             :           );
     563             :         }
     564           3 :         _currentBeamLocation.removeListener(_updateFromLocation);
     565           1 :         _pushHistory(redirectBeamLocation);
     566           1 :         _updateFromLocation(rebuild: false);
     567             :       }
     568             :     }
     569             : 
     570           6 :     final navigator = Builder(
     571           6 :       builder: (context) {
     572          12 :         if (_currentBeamLocation is NotFound ||
     573          12 :             _currentBeamLocation is EmptyBeamLocation) {
     574          12 :           _currentPages = [notFoundPage!];
     575             :         } else {
     576          12 :           _currentPages = _stacked
     577          12 :               ? _currentBeamLocation.buildPages(
     578          12 :                   context, _currentBeamLocation.state)
     579           1 :               : [
     580           1 :                   _currentBeamLocation
     581           3 :                       .buildPages(context, _currentBeamLocation.state)
     582           1 :                       .last
     583             :                 ];
     584             :         }
     585           6 :         if (active && kIsWeb && setBrowserTabTitle) {
     586           0 :           SystemChrome.setApplicationSwitcherDescription(
     587           0 :               ApplicationSwitcherDescription(
     588           0 :             label: _currentPages.last.title ??
     589           0 :                 _currentBeamLocation.state.routeInformation.location,
     590           0 :             primaryColor: Theme.of(context).primaryColor.value,
     591             :           ));
     592             :         }
     593           6 :         return Navigator(
     594           6 :           key: navigatorKey,
     595           6 :           observers: navigatorObservers,
     596          12 :           transitionDelegate: _currentBeamLocation.transitionDelegate ??
     597           6 :               _currentTransitionDelegate,
     598           1 :           pages: guard != null && guard.showPage != null
     599           0 :               ? guard.replaceCurrentStack
     600           0 :                   ? [guard.showPage!]
     601           0 :                   : _currentPages + [guard.showPage!]
     602           6 :               : _currentPages,
     603           3 :           onPopPage: (route, result) {
     604           3 :             if (route.willHandlePopInternally) {
     605           1 :               if (!route.didPop(result)) {
     606             :                 return false;
     607             :               }
     608             :             }
     609             : 
     610           3 :             if (_popConfiguration != null) {
     611           1 :               update(
     612           1 :                 configuration: _popConfiguration,
     613           1 :                 transitionDelegate: beamBackTransitionDelegate,
     614             :                 replaceCurrent: true,
     615             :               );
     616           3 :             } else if (_popBeamLocationOnPop) {
     617           1 :               final didPopBeamLocation = popBeamLocation();
     618             :               if (!didPopBeamLocation) {
     619             :                 return false;
     620             :               }
     621           3 :             } else if (_beamBackOnPop) {
     622           1 :               final didBeamBack = beamBack();
     623             :               if (!didBeamBack) {
     624             :                 return false;
     625             :               }
     626             :             } else {
     627           6 :               final lastPage = _currentPages.last;
     628           3 :               if (lastPage is BeamPage) {
     629           3 :                 if (lastPage.popToNamed != null) {
     630           2 :                   popToNamed(lastPage.popToNamed!);
     631             :                 } else {
     632           3 :                   final shouldPop = lastPage.onPopPage(context, this, lastPage);
     633             :                   if (!shouldPop) {
     634             :                     return false;
     635             :                   }
     636             :                 }
     637             :               }
     638             :             }
     639             : 
     640           3 :             return route.didPop(result);
     641             :           },
     642             :         );
     643             :       },
     644             :     );
     645          12 :     return _currentBeamLocation.builder(context, navigator);
     646             :   }
     647             : 
     648           6 :   @override
     649             :   SynchronousFuture<void> setInitialRoutePath(RouteInformation configuration) {
     650          12 :     final uri = Uri.parse(configuration.location ?? '/');
     651          12 :     if (_currentBeamLocation is! EmptyBeamLocation) {
     652          12 :       configuration = _currentBeamLocation.state.routeInformation;
     653          12 :     } else if (uri.path == '/') {
     654           6 :       configuration = RouteInformation(
     655          24 :         location: initialPath + (uri.query.isNotEmpty ? '?${uri.query}' : ''),
     656             :       );
     657             :     }
     658           6 :     return setNewRoutePath(configuration);
     659             :   }
     660             : 
     661           6 :   @override
     662             :   SynchronousFuture<void> setNewRoutePath(RouteInformation configuration) {
     663           6 :     update(configuration: configuration);
     664           6 :     return SynchronousFuture(null);
     665             :   }
     666             : 
     667             :   /// Pass this call to [root] which notifies the platform for a [state] change.
     668             :   ///
     669             :   /// On Web, creates a new browser history entry and update URL
     670             :   ///
     671             :   /// See [SystemNavigator.routeInformationUpdated].
     672           4 :   void updateRouteInformation(RouteInformation routeInformation) {
     673           4 :     if (_parent == null) {
     674           4 :       SystemNavigator.routeInformationUpdated(
     675           8 :         location: configuration.location ?? '/',
     676           8 :         state: configuration.state,
     677             :       );
     678             :     } else {
     679           6 :       _parent!.updateRouteInformation(routeInformation);
     680             :     }
     681             :   }
     682             : 
     683           6 :   BeamGuard? _checkGuards(
     684             :     List<BeamGuard> guards,
     685             :     BuildContext context,
     686             :     BeamLocation location,
     687             :   ) {
     688          13 :     for (final guard in guards + location.guards) {
     689           2 :       if (guard.shouldGuard(location) && !guard.check(context, location)) {
     690           1 :         guard.onCheckFailed?.call(context, location);
     691             :         return guard;
     692             :       }
     693             :     }
     694             :     return null;
     695             :   }
     696             : 
     697           1 :   void _applyGuard(BeamGuard guard, BuildContext context) {
     698           1 :     if (guard.showPage != null) {
     699             :       return;
     700             :     }
     701             : 
     702             :     late BeamLocation redirectLocation;
     703             : 
     704           2 :     if (guard.beamTo == null && guard.beamToNamed == null) {
     705           1 :       final lastState = removeLastRouteInformation();
     706           2 :       configuration = lastState!.copyWith();
     707           2 :       redirectLocation = locationBuilder(configuration);
     708           1 :     } else if (guard.beamTo != null) {
     709           1 :       redirectLocation = guard.beamTo!(context);
     710           1 :     } else if (guard.beamToNamed != null) {
     711           3 :       configuration = RouteInformation(location: guard.beamToNamed!);
     712           2 :       redirectLocation = locationBuilder(configuration);
     713             :     }
     714             : 
     715           2 :     final anotherGuard = _checkGuards(guards, context, redirectLocation);
     716             :     if (anotherGuard != null) {
     717           1 :       return _applyGuard(anotherGuard, context);
     718             :     }
     719             : 
     720           3 :     _currentBeamLocation.removeListener(_updateFromLocation);
     721           3 :     if (guard.replaceCurrentStack && beamLocationHistory.isNotEmpty) {
     722           1 :       removeLastRouteInformation();
     723           2 :       beamLocationHistory.removeLast();
     724             :     }
     725           1 :     _pushHistory(redirectLocation);
     726           1 :     _updateFromLocation(rebuild: false);
     727             :   }
     728             : 
     729           6 :   void _pushHistory(BeamLocation location, {bool replaceCurrent = false}) {
     730          12 :     if (routeHistory.isEmpty ||
     731          24 :         routeHistory.last.location !=
     732          18 :             location.state.routeInformation.location) {
     733          30 :       routeHistory.add(location.state.routeInformation.copyWith());
     734             :     }
     735             : 
     736          18 :     _currentBeamLocation.removeListener(_updateFromLocation);
     737           6 :     if ((preferUpdate &&
     738          24 :                 location.runtimeType == _currentBeamLocation.runtimeType ||
     739             :             replaceCurrent) &&
     740          12 :         beamLocationHistory.isNotEmpty) {
     741          12 :       beamLocationHistory.removeLast();
     742             :     }
     743           6 :     if (removeDuplicateHistory) {
     744           6 :       beamLocationHistory
     745          26 :           .removeWhere((l) => l.runtimeType == location.runtimeType);
     746             :     }
     747             : 
     748          12 :     beamLocationHistory.add(location);
     749          18 :     _currentBeamLocation = beamLocationHistory.last;
     750          18 :     _currentBeamLocation.addListener(_updateFromLocation);
     751             :   }
     752             : 
     753           5 :   RouteInformation? removeLastRouteInformation() {
     754          10 :     if (routeHistory.isEmpty) {
     755             :       return null;
     756             :     }
     757           5 :     if (updateParent) {
     758           6 :       _parent?.removeLastRouteInformation();
     759             :     }
     760          10 :     return routeHistory.removeLast();
     761             :   }
     762             : 
     763           3 :   void _initializeFromParent() {
     764          12 :     configuration = _parent!.configuration.copyWith();
     765           6 :     var location = locationBuilder(configuration);
     766           3 :     if (location is NotFound) {
     767           0 :       configuration = RouteInformation(location: initialPath);
     768           0 :       location = locationBuilder(configuration);
     769             :     }
     770           3 :     _pushHistory(location);
     771             :   }
     772             : 
     773           3 :   void _updateFromParent({bool rebuild = true}) {
     774           3 :     update(
     775           9 :       configuration: _parent!.configuration.copyWith(),
     776             :       rebuild: rebuild,
     777             :       updateParent: false,
     778             :     );
     779             :   }
     780             : 
     781           3 :   void _updateFromLocation({bool rebuild = true}) {
     782           3 :     update(
     783           9 :       configuration: _currentBeamLocation.state.routeInformation,
     784             :       buildBeamLocation: false,
     785             :       rebuild: rebuild,
     786             :     );
     787             :   }
     788             : 
     789           0 :   @override
     790             :   void dispose() {
     791           0 :     _parent?.removeListener(_updateFromParent);
     792           0 :     _currentBeamLocation.removeListener(_updateFromLocation);
     793           0 :     super.dispose();
     794             :   }
     795             : }

Generated by: LCOV version 1.14