LCOV - code coverage report
Current view: top level - src - beamer_delegate.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 240 258 93.0 %
Date: 2021-12-03 10:03:44 Functions: 0 0 -

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

Generated by: LCOV version 1.14