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

Generated by: LCOV version 1.14