LCOV - code coverage report
Current view: top level - src - beamer_router_delegate.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 174 201 86.6 %
Date: 2021-05-15 21:06:48 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             : /// A delegate that is used by the [Router] to build the [Navigator].
      11             : class BeamerRouterDelegate<T extends BeamState>
      12             :     extends RouterDelegate<BeamState>
      13             :     with ChangeNotifier, PopNavigatorRouterDelegateMixin<BeamState> {
      14           6 :   BeamerRouterDelegate({
      15             :     required this.locationBuilder,
      16             :     this.initialPath = '/',
      17             :     this.listener,
      18             :     this.createState,
      19             :     this.preferUpdate = true,
      20             :     this.removeDuplicateHistory = true,
      21             :     this.notFoundPage,
      22             :     this.notFoundRedirect,
      23             :     this.guards = const <BeamGuard>[],
      24             :     this.navigatorObservers = const <NavigatorObserver>[],
      25             :     this.transitionDelegate = const DefaultTransitionDelegate(),
      26             :     this.beamBackTransitionDelegate = const ReverseTransitionDelegate(),
      27             :     this.onPopPage,
      28             :     this.setBrowserTabTitle = true,
      29             :   }) {
      30          12 :     notFoundPage ??= BeamPage(
      31             :       title: 'Not found',
      32          18 :       child: Container(child: Center(child: Text('Not found'))),
      33             :     );
      34             : 
      35          18 :     createState ??= (BeamState state) => BeamState.fromUri(
      36           6 :           state.uri,
      37           6 :           data: state.data,
      38             :         ) as T;
      39             : 
      40          12 :     _currentTransitionDelegate = transitionDelegate;
      41             : 
      42          36 :     state = createState!(BeamState.fromUri(Uri.parse(initialPath)));
      43          12 :     _currentLocation = EmptyBeamLocation();
      44             :   }
      45             : 
      46             :   late T _state;
      47             : 
      48             :   /// A state of this router delegate. This is the `state` that goes into
      49             :   /// [locationBuilder] to build an appropriate [BeamLocation].
      50             :   ///
      51             :   /// Most common way to modify this state is via [update].
      52          18 :   T get state => _state.copyWith() as T;
      53          18 :   set state(T state) => _state = state..configure();
      54             : 
      55             :   BeamerRouterDelegate? _parent;
      56             : 
      57             :   /// A router delegate of a parent of the [Beamer] that has this router delegate.
      58             :   ///
      59             :   /// This is not null only if multiple [Beamer]s are used;
      60             :   /// `*App.router` and at least one more [Beamer] in the Widget tree.
      61           4 :   BeamerRouterDelegate? get parent => _parent;
      62           1 :   set parent(BeamerRouterDelegate? parent) {
      63           1 :     _parent = parent!;
      64           1 :     _initializeFromParent();
      65             :   }
      66             : 
      67             :   /// The top-most [BeamerRouterDelegate], a parent of all.
      68             :   ///
      69             :   /// It will return root even when called on root.
      70           1 :   BeamerRouterDelegate get root {
      71           1 :     if (_parent == null) {
      72             :       return this;
      73             :     }
      74           1 :     var root = _parent!;
      75           1 :     while (root._parent != null) {
      76           0 :       root = root._parent!;
      77             :     }
      78             :     return root;
      79             :   }
      80             : 
      81             :   /// A builder for [BeamLocation]s.
      82             :   final LocationBuilder locationBuilder;
      83             : 
      84             :   /// The path to replace `/` as default initial route path upon load.
      85             :   ///
      86             :   /// Note that (if set to anything other than `/` (default)),
      87             :   /// you will not be able to navigate to `/` by manually typing
      88             :   /// it in the URL bar, because it will always be transformed to `initialPath`,
      89             :   /// but you will be able to get to `/` by popping pages with back button,
      90             :   /// if there are pages in [BeamLocation.pagesBuilder] that will build
      91             :   /// when there are no path segments.
      92             :   final String initialPath;
      93             : 
      94             :   /// The listener for this, that will be called on every navigation event
      95             :   /// and will recieve the [state] and [currentLocation].
      96             :   final void Function(BeamState, BeamLocation)? listener;
      97             : 
      98             :   /// Whether to prefer updating [currentLocation] if it's of the same type
      99             :   /// as the location being beamed to, instead of adding it to [beamHistory].
     100             :   ///
     101             :   /// See how this is used at [beamTo] implementation.
     102             :   final bool preferUpdate;
     103             : 
     104             :   /// Whether to remove locations from history if they are the same type
     105             :   /// as the location beaing beamed to.
     106             :   ///
     107             :   /// See how this is used at [beamTo] implementation.
     108             :   final bool removeDuplicateHistory;
     109             : 
     110             :   /// Page to show when no [BeamLocation] supports the incoming URI.
     111             :   BeamPage? notFoundPage;
     112             : 
     113             :   /// [BeamLocation] to redirect to when no [BeamLocation] supports the incoming URI.
     114             :   final BeamLocation? notFoundRedirect;
     115             : 
     116             :   /// Guards that will be executing [check] on [currentLocation] candidate.
     117             :   ///
     118             :   /// Checks will be executed in order; chain of responsibility pattern.
     119             :   /// When some guard returns `false`, location candidate will not be accepted
     120             :   /// and stack of pages will be updated as is configured in [BeamGuard].
     121             :   final List<BeamGuard> guards;
     122             : 
     123             :   /// The list of observers for the [Navigator] created for this app.
     124             :   final List<NavigatorObserver> navigatorObservers;
     125             : 
     126             :   /// A transition delegate to be used by [Navigator].
     127             :   ///
     128             :   /// This transition delegate will be overridden by the one in [BeamLocation],
     129             :   /// if any is set.
     130             :   final TransitionDelegate transitionDelegate;
     131             : 
     132             :   /// A transition delegate to be used by [Navigator] when beaming back.
     133             :   ///
     134             :   /// When calling [beamBack], it's useful to animate routes in reverse order;
     135             :   /// adding the new ones behind and then popping the current ones,
     136             :   /// therefore, the default is [ReverseTransitionDelegate].
     137             :   final TransitionDelegate beamBackTransitionDelegate;
     138             : 
     139             :   /// Callback when `pop` is requested.
     140             :   ///
     141             :   /// Return `true` if pop will be handled entirely by this function.
     142             :   /// Return `false` if beamer should finish handling the pop.
     143             :   ///
     144             :   /// See [build] for details on how beamer handles [Navigator.onPopPage].
     145             :   bool Function(BuildContext context, Route<dynamic> route, dynamic result)?
     146             :       onPopPage;
     147             : 
     148             :   /// Whether the title attribute of [BeamPage] should
     149             :   /// be used to set and update the browser tab title.
     150             :   final bool setBrowserTabTitle;
     151             : 
     152             :   final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
     153             : 
     154             :   final List<BeamState> _beamStateHistory = [];
     155             : 
     156             :   /// The history of beaming.
     157          10 :   List<BeamState> get beamStateHistory => _beamStateHistory;
     158             : 
     159             :   final List<BeamLocation> _beamLocationHistory = [];
     160             : 
     161             :   /// The history of visited [BeamLocation]s.
     162           6 :   List<BeamLocation> get beamLocationHistory => _beamLocationHistory;
     163             : 
     164             :   late BeamLocation _currentLocation;
     165             : 
     166             :   /// {@template currentLocation}
     167             :   /// Access the current [BeamLocation].
     168             :   ///
     169             :   /// Can be useful in:
     170             :   ///
     171             :   /// * extracting location properties when building Widgets:
     172             :   ///
     173             :   /// ```dart
     174             :   /// final queryParameters = Beamer.of(context).currentLocation.queryParameters;
     175             :   /// ```
     176             :   ///
     177             :   /// * to check which navigation button should be highlighted:
     178             :   ///
     179             :   /// ```dart
     180             :   /// highlighted: Beamer.of(context).currentLocation is MyLocation,
     181             :   /// ```
     182             :   /// {@endtemplate}
     183          12 :   BeamLocation get currentLocation => _currentLocation;
     184             : 
     185             :   List<BeamPage> _currentPages = [];
     186             : 
     187             :   /// {@template currentPages}
     188             :   /// Current location's effective pages.
     189             :   /// {@endtemplate}
     190           8 :   List<BeamPage> get currentPages => _currentPages;
     191             : 
     192             :   /// Whether to implicitly [beamBack] instead of default pop.
     193             :   bool _beamBackOnPop = false;
     194             : 
     195             :   /// Whether to implicitly [popBeamLocation] instead of default pop.
     196             :   bool _popBeamLocationOnPop = false;
     197             : 
     198             :   /// Which transition delegate to use in the next rebuild.
     199             :   late TransitionDelegate _currentTransitionDelegate;
     200             : 
     201             :   /// Which location to pop to, instead of default pop.
     202             :   ///
     203             :   /// This is more general than `beamBackOnPop`.
     204             :   T? _popState;
     205             : 
     206             :   /// Whether all the pages from location are stacked.
     207             :   /// If not (`false`), just the last page is taken.
     208             :   bool _stacked = true;
     209             : 
     210             :   /// How to create a [state] for this router delegate.
     211             :   ///
     212             :   /// If not set, default `BeamState.copyWith() as T` is used.
     213             :   T Function(BeamState state)? createState;
     214             : 
     215             :   /// If `false`, does not report the route until next `update`.
     216             :   bool _active = true;
     217             : 
     218             :   /// When not active, does not report the route.
     219             :   ///
     220             :   /// Useful when having sibling beamers that are both build at the same time.
     221             :   /// Upon `update` becomes active.
     222           0 :   void active([bool? value]) => _active = value ?? true;
     223             : 
     224             :   /// Main method to update the state of this; `Beamer.of(context)`,
     225             :   ///
     226             :   /// This "top-level" [update] is generally used for navigation
     227             :   /// _between_ [BeamLocation]s and not _within_ a specific [BeamLocation].
     228             :   /// For latter purpose, see [BeamLocation.update].
     229             :   /// Nevertheless, [update] **will** work for navigation within [BeamLocation].
     230             :   ///
     231             :   /// _Imperative_ [beamTo] or [beamToNamed] and [popToNamed]
     232             :   /// all call [update] to really do the update.
     233             :   ///
     234             :   /// If `beamBackOnPop` is set to `true`,
     235             :   /// default pop action will triger `beamBack` instead.
     236             :   ///
     237             :   /// `popState` is more general than `beamBackOnPop`,
     238             :   /// and can beam you anywhere; whatever it resolves to during build.
     239             :   ///
     240             :   /// If `stacked` is set to `false`,
     241             :   /// only the location's last page will be shown.
     242             :   ///
     243             :   /// If `replaceCurrent` is set to `true`,
     244             :   /// new location will replace the last one in the stack.
     245             :   ///
     246             :   /// If `rebuild` is set to `false`,
     247             :   /// `build` will not occur, but state and browser URL will be updated.
     248           6 :   void update({
     249             :     T? state,
     250             :     T? popState,
     251             :     TransitionDelegate? transitionDelegate,
     252             :     bool? beamBackOnPop,
     253             :     bool? popBeamLocationOnPop,
     254             :     bool? stacked,
     255             :     bool replaceCurrent = false,
     256             :     bool buildBeamLocation = true,
     257             :     bool rebuild = true,
     258             :   }) {
     259           6 :     _active = true;
     260          12 :     _popState = popState ?? _popState;
     261           6 :     _currentTransitionDelegate =
     262           6 :         transitionDelegate ?? _currentTransitionDelegate;
     263          12 :     _beamBackOnPop = beamBackOnPop ?? _beamBackOnPop;
     264          12 :     _popBeamLocationOnPop = popBeamLocationOnPop ?? _popBeamLocationOnPop;
     265          12 :     _stacked = stacked ?? _stacked;
     266             : 
     267             :     if (state != null) {
     268           6 :       this.state = state;
     269             :       if (buildBeamLocation) {
     270          18 :         final location = locationBuilder(this.state);
     271           6 :         _pushHistory(location, replaceCurrent: replaceCurrent);
     272             :       }
     273           6 :       listener?.call(this.state, _currentLocation);
     274             :     }
     275             : 
     276          12 :     if (state != _parent?.state) {
     277           6 :       _parent?.update(
     278           0 :         state: this.state.copyWith(),
     279             :         rebuild: false,
     280             :       );
     281           6 :       _parent?.updateRouteInformation(this.state.copyWith());
     282             :     }
     283             : 
     284             :     if (rebuild) {
     285           6 :       notifyListeners();
     286             :     }
     287             :   }
     288             : 
     289             :   /// {@template beamTo}
     290             :   /// Beams to a specific, manually configured [BeamLocation].
     291             :   ///
     292             :   /// For example
     293             :   ///
     294             :   /// ```dart
     295             :   /// Beamer.of(context).beamTo(
     296             :   ///   Location2(
     297             :   ///     BeamState(
     298             :   ///       pathBlueprintSegments = ['user',':userId','transactions'],
     299             :   ///       pathParameters = {'userId': '1'},
     300             :   ///       queryParameters = {'perPage': '10'},
     301             :   ///       data = {'favoriteUser': true},
     302             :   ///     ),
     303             :   ///   ),
     304             :   /// );
     305             :   /// ```
     306             :   ///
     307             :   /// See [update] for more details.
     308             :   /// {@endtemplate}
     309           2 :   void beamTo(
     310             :     BeamLocation location, {
     311             :     BeamLocation? popTo,
     312             :     TransitionDelegate? transitionDelegate,
     313             :     bool beamBackOnPop = false,
     314             :     bool popBeamLocationOnPop = false,
     315             :     bool stacked = true,
     316             :     bool replaceCurrent = false,
     317             :   }) {
     318           2 :     _pushHistory(location, replaceCurrent: replaceCurrent);
     319           2 :     update(
     320           6 :       state: createState!(location.state),
     321           0 :       popState: popTo != null ? createState!(popTo.state) : null,
     322             :       transitionDelegate: transitionDelegate,
     323             :       beamBackOnPop: beamBackOnPop,
     324             :       popBeamLocationOnPop: popBeamLocationOnPop,
     325             :       stacked: stacked,
     326             :       buildBeamLocation: false,
     327             :     );
     328             :   }
     329             : 
     330             :   /// {@template beamToNamed}
     331             :   /// Beams to [BeamLocation] that has `uri` contained within its
     332             :   /// [BeamLocation.pathBlueprintSegments].
     333             :   ///
     334             :   /// For example
     335             :   ///
     336             :   /// ```dart
     337             :   /// Beamer.of(context).beamToNamed(
     338             :   ///   '/user/1/transactions?perPage=10',
     339             :   ///   data: {'favoriteUser': true},,
     340             :   /// );
     341             :   /// ```
     342             :   ///
     343             :   /// See [update] for more details.
     344             :   /// {@endtemplate}
     345           6 :   void beamToNamed(
     346             :     String uri, {
     347             :     Map<String, dynamic> data = const <String, dynamic>{},
     348             :     String? popToNamed,
     349             :     TransitionDelegate? transitionDelegate,
     350             :     bool beamBackOnPop = false,
     351             :     bool popBeamLocationOnPop = false,
     352             :     bool stacked = true,
     353             :     bool replaceCurrent = false,
     354             :   }) {
     355           6 :     update(
     356          24 :       state: createState!(BeamState.fromUri(Uri.parse(uri), data: data)),
     357             :       popState: popToNamed != null
     358           4 :           ? createState!(BeamState.fromUri(Uri.parse(popToNamed), data: data))
     359             :           : null,
     360             :       transitionDelegate: transitionDelegate,
     361             :       beamBackOnPop: beamBackOnPop,
     362             :       popBeamLocationOnPop: popBeamLocationOnPop,
     363             :       stacked: stacked,
     364             :       replaceCurrent: replaceCurrent,
     365             :     );
     366             :   }
     367             : 
     368             :   /// {@template popToNamed}
     369             :   /// Calls [beamToNamed] with a [ReverseTransitionDelegate].
     370             :   ///
     371             :   /// See [beamToNamed] for more details.
     372             :   /// {@endtemplate}
     373           2 :   void popToNamed(
     374             :     String uri, {
     375             :     Map<String, dynamic> data = const <String, dynamic>{},
     376             :     String? popToNamed,
     377             :     bool beamBackOnPop = false,
     378             :     bool popBeamLocationOnPop = false,
     379             :     bool stacked = true,
     380             :     bool replaceCurrent = false,
     381             :   }) {
     382           2 :     beamToNamed(
     383             :       uri,
     384             :       data: data,
     385             :       popToNamed: popToNamed,
     386             :       transitionDelegate: const ReverseTransitionDelegate(),
     387             :       beamBackOnPop: beamBackOnPop,
     388             :       popBeamLocationOnPop: popBeamLocationOnPop,
     389             :       stacked: stacked,
     390             :       replaceCurrent: replaceCurrent,
     391             :     );
     392             :   }
     393             : 
     394             :   /// {@template canBeamBack}
     395             :   /// Whether it is possible to [beamBack],
     396             :   /// i.e. there is more than 1 state in [beamStateHistory].
     397             :   /// {@endtemplate}
     398          12 :   bool get canBeamBack => _beamStateHistory.length > 1;
     399             : 
     400             :   /// {@template beamBack}
     401             :   /// Beams to previous state in [beamStateHistory].
     402             :   /// and **removes** the last state from history.
     403             :   ///
     404             :   /// If there is no previous state, does nothing.
     405             :   ///
     406             :   /// Returns the success, whether the [currentLocation] was changed.
     407             :   /// {@endtemplate}
     408           3 :   bool beamBack() {
     409           3 :     if (!canBeamBack) {
     410             :       return false;
     411             :     }
     412           6 :     _beamStateHistory.removeLast();
     413           6 :     final state = _beamStateHistory.removeLast();
     414           3 :     update(
     415           6 :       state: createState!(state),
     416           3 :       transitionDelegate: beamBackTransitionDelegate,
     417             :     );
     418             :     return true;
     419             :   }
     420             : 
     421             :   /// Remove everything except last from [_beamStateHistory].
     422           1 :   void clearBeamStateHistory() =>
     423           5 :       _beamStateHistory.removeRange(0, _beamStateHistory.length - 1);
     424             : 
     425             :   /// {@template canPopBeamLocation}
     426             :   /// Whether it is possible to [popBeamLocation],
     427             :   /// i.e. there is more than 1 location in [beamLocationHistory].
     428             :   /// {@endtemplate}
     429           8 :   bool get canPopBeamLocation => _beamLocationHistory.length > 1;
     430             : 
     431             :   /// {@template popBeamLocation}
     432             :   /// Beams to previous location in [beamLocationHistory]
     433             :   /// and **removes** the last location from history.
     434             :   ///
     435             :   /// If there is no previous location, does nothing.
     436             :   ///
     437             :   /// Returns the success, whether the [currentLocation] was changed.
     438             :   /// {@endtemplate}
     439           2 :   bool popBeamLocation() {
     440           2 :     if (!canPopBeamLocation) {
     441             :       return false;
     442             :     }
     443           6 :     _currentLocation.removeListener(_updateFromLocation);
     444           4 :     _beamLocationHistory.removeLast();
     445           6 :     _currentLocation = _beamLocationHistory.last;
     446          10 :     _beamStateHistory.add(_currentLocation.state.copyWith());
     447           6 :     _currentLocation.addListener(_updateFromLocation);
     448           2 :     update(
     449           2 :       transitionDelegate: beamBackTransitionDelegate,
     450             :     );
     451             :     return true;
     452             :   }
     453             : 
     454             :   /// Remove everything except last from [beamLocationHistory].
     455           1 :   void clearBeamLocationHistory() =>
     456           5 :       _beamLocationHistory.removeRange(0, _beamLocationHistory.length - 1);
     457             : 
     458           6 :   @override
     459             :   BeamState? get currentConfiguration =>
     460          18 :       _parent == null ? _currentLocation.state : null;
     461             : 
     462           6 :   @override
     463           6 :   GlobalKey<NavigatorState> get navigatorKey => _navigatorKey;
     464             : 
     465           6 :   @override
     466             :   Widget build(BuildContext context) {
     467          18 :     BeamGuard? guard = _checkGuards(guards, context, _currentLocation);
     468             :     if (guard != null) {
     469           1 :       _applyGuard(guard, context);
     470             :     }
     471          14 :     if ((_currentLocation is NotFound) && notFoundRedirect != null) {
     472           0 :       _currentLocation.removeListener(_updateFromLocation);
     473           0 :       _pushHistory(notFoundRedirect!);
     474             :     }
     475             : 
     476           6 :     final navigator = Builder(
     477           6 :       builder: (context) {
     478          12 :         if (_currentLocation is NotFound ||
     479          12 :             _currentLocation is EmptyBeamLocation) {
     480           6 :           _currentPages = [notFoundPage!];
     481             :         } else {
     482          12 :           _currentPages = _stacked
     483          24 :               ? _currentLocation.pagesBuilder(context, _currentLocation.state)
     484           1 :               : [
     485           1 :                   _currentLocation
     486           3 :                       .pagesBuilder(context, _currentLocation.state)
     487           1 :                       .last
     488             :                 ];
     489             :         }
     490           6 :         if (_active && kIsWeb && setBrowserTabTitle) {
     491           0 :           SystemChrome.setApplicationSwitcherDescription(
     492           0 :               ApplicationSwitcherDescription(
     493           0 :             label: _currentPages.last.title ??
     494           0 :                 _currentLocation.state.uri.toString(),
     495           0 :             primaryColor: Theme.of(context).primaryColor.value,
     496             :           ));
     497             :         }
     498           6 :         return Navigator(
     499           6 :           key: navigatorKey,
     500           6 :           observers: navigatorObservers,
     501             :           transitionDelegate:
     502          18 :               _currentLocation.transitionDelegate ?? _currentTransitionDelegate,
     503           1 :           pages: guard != null && guard.showPage != null
     504           0 :               ? guard.replaceCurrentStack
     505           0 :                   ? [guard.showPage!]
     506           0 :                   : _currentPages + [guard.showPage!]
     507           6 :               : _currentPages,
     508           3 :           onPopPage: (route, result) {
     509           3 :             if (_popState != null) {
     510           1 :               update(
     511           1 :                 state: _popState,
     512           1 :                 transitionDelegate: beamBackTransitionDelegate,
     513             :                 replaceCurrent: true,
     514             :               );
     515           1 :               return route.didPop(result);
     516           2 :             } else if (_popBeamLocationOnPop) {
     517           0 :               final didPopBeamLocation = popBeamLocation();
     518             :               if (!didPopBeamLocation) {
     519             :                 return false;
     520             :               }
     521           0 :               return route.didPop(result);
     522           2 :             } else if (_beamBackOnPop) {
     523           0 :               beamBack();
     524           0 :               final didBeamBack = beamBack();
     525             :               if (!didBeamBack) {
     526             :                 return false;
     527             :               }
     528           0 :               return route.didPop(result);
     529             :             }
     530             : 
     531           4 :             final lastPage = _currentPages.last;
     532           2 :             if (lastPage is BeamPage) {
     533           2 :               if (lastPage.popToNamed != null) {
     534           1 :                 beamToNamed(
     535           1 :                   lastPage.popToNamed!,
     536           1 :                   transitionDelegate: beamBackTransitionDelegate,
     537             :                 );
     538             :               } else {
     539           4 :                 final shouldPop = lastPage.onPopPage(context, this, lastPage);
     540             :                 if (!shouldPop) {
     541             :                   return false;
     542             :                 }
     543             :               }
     544             :             }
     545             : 
     546           2 :             return route.didPop(result);
     547             :           },
     548             :         );
     549             :       },
     550             :     );
     551          12 :     return _currentLocation.builder(context, navigator);
     552             :   }
     553             : 
     554           6 :   @override
     555             :   SynchronousFuture<void> setInitialRoutePath(BeamState beamState) {
     556          12 :     if (_currentLocation is! EmptyBeamLocation) {
     557           8 :       beamState = _currentLocation.state;
     558          12 :     } else if (beamState.uri.path == '/') {
     559          12 :       beamState = BeamState.fromUri(Uri.parse(initialPath));
     560             :     }
     561           6 :     return setNewRoutePath(beamState);
     562             :   }
     563             : 
     564           6 :   @override
     565             :   SynchronousFuture<void> setNewRoutePath(BeamState beamState) {
     566          18 :     update(state: createState!(beamState));
     567           6 :     return SynchronousFuture(null);
     568             :   }
     569             : 
     570           1 :   void updateRouteInformation(BeamState state) {
     571           1 :     if (parent == null) {
     572           1 :       SystemNavigator.routeInformationUpdated(
     573           2 :         location: state.uri.toString(),
     574           2 :         state: json.encode(state.data),
     575             :       );
     576             :     } else {
     577           0 :       parent!.updateRouteInformation(state);
     578             :     }
     579             :   }
     580             : 
     581           6 :   BeamGuard? _checkGuards(
     582             :     List<BeamGuard> guards,
     583             :     BuildContext context,
     584             :     BeamLocation location,
     585             :   ) {
     586           7 :     for (var guard in guards) {
     587           3 :       if (guard.shouldGuard(location) && !guard.check(context, location)) {
     588           1 :         guard.onCheckFailed?.call(context, location);
     589             :         return guard;
     590             :       }
     591             :     }
     592           6 :     for (var guard in location.guards) {
     593           0 :       if (guard.shouldGuard(location) && !guard.check(context, location)) {
     594           0 :         guard.onCheckFailed?.call(context, location);
     595             :         return guard;
     596             :       }
     597             :     }
     598             :     return null;
     599             :   }
     600             : 
     601           1 :   void _applyGuard(BeamGuard guard, BuildContext context) {
     602           1 :     if (guard.showPage != null) {
     603             :       return;
     604             :     }
     605             : 
     606             :     var redirectLocation;
     607             : 
     608           2 :     if (guard.beamTo == null && guard.beamToNamed == null) {
     609           2 :       _beamStateHistory.removeLast();
     610           5 :       state = createState!(_beamStateHistory.last);
     611           3 :       redirectLocation = locationBuilder(_state);
     612           1 :     } else if (guard.beamTo != null) {
     613           2 :       redirectLocation = guard.beamTo!(context);
     614           1 :     } else if (guard.beamToNamed != null) {
     615           6 :       state = createState!(BeamState.fromUri(Uri.parse(guard.beamToNamed!)));
     616           3 :       redirectLocation = locationBuilder(_state);
     617             :     }
     618             : 
     619           2 :     final anotherGuard = _checkGuards(guards, context, redirectLocation);
     620             :     if (anotherGuard != null) {
     621           1 :       return _applyGuard(anotherGuard, context);
     622             :     }
     623             : 
     624           3 :     _currentLocation.removeListener(_updateFromLocation);
     625           3 :     if (guard.replaceCurrentStack && _beamLocationHistory.isNotEmpty) {
     626           2 :       _beamStateHistory.removeLast();
     627           2 :       _beamLocationHistory.removeLast();
     628             :     }
     629           1 :     _pushHistory(redirectLocation);
     630           3 :     updateRouteInformation(_currentLocation.state);
     631             :   }
     632             : 
     633           6 :   void _pushHistory(BeamLocation location, {bool replaceCurrent = false}) {
     634          12 :     if (_beamStateHistory.isEmpty ||
     635          36 :         _beamStateHistory.last.uri != location.state.uri) {
     636          24 :       _beamStateHistory.add(location.state.copyWith());
     637             :     }
     638             : 
     639          18 :     _currentLocation.removeListener(_updateFromLocation);
     640          30 :     if ((preferUpdate && location.runtimeType == _currentLocation.runtimeType ||
     641             :             replaceCurrent) &&
     642          12 :         _beamLocationHistory.isNotEmpty) {
     643          12 :       _beamLocationHistory.removeLast();
     644             :     }
     645           6 :     if (removeDuplicateHistory) {
     646           6 :       _beamLocationHistory
     647          22 :           .removeWhere((l) => l.runtimeType == location.runtimeType);
     648             :     }
     649             : 
     650          12 :     _beamLocationHistory.add(location);
     651          18 :     _currentLocation = _beamLocationHistory.last;
     652          18 :     _currentLocation.addListener(_updateFromLocation);
     653             :   }
     654             : 
     655           1 :   void _initializeFromParent() {
     656           5 :     state = createState!(_parent!.state);
     657           3 :     var location = locationBuilder(state);
     658           1 :     if (location is NotFound) {
     659           0 :       state = createState!(BeamState.fromUri(Uri.parse(initialPath)));
     660           0 :       location = locationBuilder(state);
     661             :     }
     662           1 :     _pushHistory(location);
     663             :   }
     664             : 
     665           3 :   void _updateFromLocation() {
     666           3 :     update(
     667          12 :       state: createState!(_currentLocation.state),
     668             :       buildBeamLocation: false,
     669             :     );
     670             :   }
     671             : 
     672           0 :   @override
     673             :   void dispose() {
     674           0 :     _currentLocation.removeListener(_updateFromLocation);
     675           0 :     super.dispose();
     676             :   }
     677             : }

Generated by: LCOV version 1.14