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

Generated by: LCOV version 1.14