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

Generated by: LCOV version 1.14