From 4bfa7ec0bc4232a432107a7e9549683b2580363e Mon Sep 17 00:00:00 2001 From: fabianbrandscheid <38261693+fabianbrandscheid@users.noreply.github.com> Date: Tue, 16 Jul 2024 09:16:54 +0200 Subject: [PATCH 1/8] Add files via upload Update Browser Title --- package/lib/src/beamer_delegate.dart | 395 +++++++++++++-------------- 1 file changed, 191 insertions(+), 204 deletions(-) diff --git a/package/lib/src/beamer_delegate.dart b/package/lib/src/beamer_delegate.dart index c6a4140..d61b7c3 100644 --- a/package/lib/src/beamer_delegate.dart +++ b/package/lib/src/beamer_delegate.dart @@ -1,7 +1,7 @@ import 'package:beamer/beamer.dart'; import 'package:beamer/src/browser_tab_title_util_non_web.dart' - if (dart.library.html) 'package:beamer/src/browser_tab_title_util_web.dart' - as browser_tab_title_util; +if (dart.library.html) 'package:beamer/src/browser_tab_title_util_web.dart' +as browser_tab_title_util; import 'package:beamer/src/utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -14,18 +14,17 @@ class BeamerDelegate extends RouterDelegate with ChangeNotifier, PopNavigatorRouterDelegateMixin { /// Creates a [BeamerDelegate] with specified properties. /// - /// [stackBuilder] is required to process the incoming navigation request. + /// [locationBuilder] is required to process the incoming navigation request. BeamerDelegate({ - required this.stackBuilder, + required this.locationBuilder, this.initialPath = '/', this.routeListener, this.buildListener, this.updateListenable, @Deprecated( 'No longer used by this package, please remove any references to it. ' - 'This feature was deprecated after v1.0.0.', - ) - this.preferUpdate = true, + 'This feature was deprecated after v1.0.0.', + ) this.preferUpdate = true, this.removeDuplicateHistory = true, this.notFoundPage = BeamPage.notFound, this.notFoundRedirect, @@ -51,7 +50,7 @@ class BeamerDelegate extends RouterDelegate } /// A state of this delegate. This is the `routeInformation` that goes into - /// [stackBuilder] to build an appropriate [BeamStack]. + /// [locationBuilder] to build an appropriate [BeamLocation]. /// /// A way to modify this state is via [update]. late RouteInformation configuration; @@ -109,44 +108,44 @@ class BeamerDelegate extends RouterDelegate return root; } - /// A builder for [BeamStack]s. + /// A builder for [BeamLocation]s. /// - /// There are 3 ways of building an appropriate [BeamStack] which will in + /// There are 3 ways of building an appropriate [BeamLocation] which will in /// turn build a stack of pages that should go into [Navigator.pages]. /// /// 1. Custom closure /// ```dart - /// stackBuilder: (state) { + /// locationBuilder: (state) { /// if (state.uri.pathSegments.contains('l1')) { - /// return Stack1(state); + /// return Location1(state); /// } /// if (state.uri.pathSegments.contains('l2')) { - /// return Stack2(state); + /// return Location2(state); /// } /// return NotFound(path: state.uri.toString()); /// }, /// ``` /// - /// 2. [BeamerStackBuilder]; chooses appropriate [BeamStack] itself + /// 2. [BeamerLocationBuilder]; chooses appropriate [BeamLocation] itself /// ```dart - /// stackBuilder: BeamerStackBuilder( - /// beamStacks: [ - /// Stack1(), - /// Stack2(), + /// locationBuilder: BeamerLocationBuilder( + /// beamLocations: [ + /// Location1(), + /// Location2(), /// ], /// ), /// ``` /// - /// 3. [RoutesStackBuilder]; a Map of routes + /// 3. [RoutesLocationBuilder]; a Map of routes /// ```dart - /// stackBuilder: RoutesStackBuilder( + /// locationBuilder: RoutesLocationBuilder( /// routes: { /// '/': (context, state) => HomeScreen(), /// '/another': (context, state) => AnotherScreen(), /// }, /// ), /// ``` - final StackBuilder stackBuilder; + final LocationBuilder locationBuilder; /// The path to replace `/` as default initial route path upon load. /// @@ -154,26 +153,10 @@ class BeamerDelegate extends RouterDelegate /// you will not be able to navigate to `/` by manually typing /// it in the URL bar, because it will always be transformed to `initialPath`, /// but you will be able to get to `/` by popping pages with back button, - /// if there are pages in [BeamStack.buildPages] that will build + /// if there are pages in [BeamLocation.buildPages] that will build /// when there are no path segments. final String initialPath; - /// An initial deep-link that should be stored until used. - /// - /// This can be used in [BeamGuard.beamToNamed] to redirect properly after - /// some auth flows that need to be executed, during which the [initialPath] - /// information is lost. - String? _deepLink; - - /// Sets the deep-link route path that - /// - will be used as [initialPath] - /// - can be used in [BeamGuard.beamToNamed] to redirect properly - /// after some auth flows that need to be executed, - /// during which the [initialPath] information is lost. - /// - /// Once a [BeamGuard] uses the deep-link, it will be reset to null. - void setDeepLink(String? deepLink) => _deepLink = deepLink; - /// The routeListener will be called on every navigation event /// and will receive the [configuration] and a reference to this delegate. final void Function(RouteInformation, BeamerDelegate)? routeListener; @@ -188,31 +171,31 @@ class BeamerDelegate extends RouterDelegate @Deprecated( 'No longer used by this package, please remove any references to it. ' - 'This feature was deprecated after v1.0.0.', + 'This feature was deprecated after v1.0.0.', ) // ignore: public_member_api_docs final bool preferUpdate; - /// Whether to remove [BeamStack]s from [beamingHistory] - /// if they are the same type as the stack being beamed to. + /// Whether to remove [BeamLocation]s from [beamingHistory] + /// if they are the same type as the location being beamed to. /// /// See how this is used at [_addToBeamingHistory] implementation. final bool removeDuplicateHistory; - /// Page to show when no [BeamStack] supports the incoming URI. + /// Page to show when no [BeamLocation] supports the incoming URI. late BeamPage notFoundPage; - /// [BeamStack] to redirect to when no [BeamStack] supports the incoming URI. - final BeamStack? notFoundRedirect; + /// [BeamLocation] to redirect to when no [BeamLocation] supports the incoming URI. + final BeamLocation? notFoundRedirect; - /// URI string to redirect to when no [BeamStack] supports the incoming URI. + /// URI string to redirect to when no [BeamLocation] supports the incoming URI. final String? notFoundRedirectNamed; - /// Guards that will be executing [BeamGuard.check] on [currentBeamStack] + /// Guards that will be executing [BeamGuard.check] on [currentBeamLocation] /// candidate. /// /// Checks will be executed in order; chain of responsibility pattern. - /// When some guard returns `false`, stack candidate will not be accepted + /// When some guard returns `false`, location candidate will not be accepted /// and stack of pages will be updated as is configured in [BeamGuard]. final List guards; @@ -221,7 +204,7 @@ class BeamerDelegate extends RouterDelegate /// A transition delegate to be used by [Navigator]. /// - /// This transition delegate will be overridden by the one in [BeamStack], + /// This transition delegate will be overridden by the one in [BeamLocation], /// if any is set. /// /// See [Navigator.transitionDelegate]. @@ -241,7 +224,7 @@ class BeamerDelegate extends RouterDelegate /// /// See [build] for details on how beamer handles [Navigator.onPopPage]. bool Function(BuildContext context, Route route, dynamic result)? - onPopPage; + onPopPage; /// Whether the title attribute of [BeamPage] should /// be used to set and update the browser tab title. @@ -264,7 +247,7 @@ class BeamerDelegate extends RouterDelegate final bool updateParent; /// Whether to remove all entries from [beamingHistory] (and their nested - /// [BeamStack.history]) when a route belonging to this set is reached, + /// [BeamLocation.history]) when a route belonging to this set is reached, /// regardless of how it was reached. /// /// Note that [popToNamed] will also try to clear as much [beamingHistory] @@ -274,41 +257,41 @@ class BeamerDelegate extends RouterDelegate final GlobalKey _navigatorKey = GlobalKey(); /// {@template beamingHistory} - /// The history of [BeamStack]s, each holding its [BeamStack.history]. + /// The history of [BeamLocation]s, each holding its [BeamLocation.history]. /// /// See [_addToBeamingHistory]. /// {@endtemplate} - final List beamingHistory = []; + final List beamingHistory = []; /// Returns the complete length of beaming history, that is the sum of all - /// history lengths for each [BeamStack] in [beamingHistory]. + /// history lengths for each [BeamLocation] in [beamingHistory]. int get beamingHistoryCompleteLength { var length = 0; - for (var stack in beamingHistory) { - length += stack.history.length; + for (var location in beamingHistory) { + length += location.history.length; } return length; } - /// A candidate to become the next [currentBeamStack]. - BeamStack _beamStackCandidate = EmptyBeamStack(); + /// A candidate to become the next [currentBeamLocation]. + BeamLocation _beamLocationCandidate = EmptyBeamLocation(); - /// {@template currentBeamStack} - /// A [BeamStack] that is currently responsible for providing a page stack - /// via [BeamStack.buildPages] and holds the current [BeamState]. + /// {@template currentBeamLocation} + /// A [BeamLocation] that is currently responsible for providing a page stack + /// via [BeamLocation.buildPages] and holds the current [BeamState]. /// /// Usually obtained via /// ```dart - /// Beamer.of(context).currentBeamStack + /// Beamer.of(context).currentBeamLocation /// ``` /// {@endtemplate} - BeamStack get currentBeamStack => - beamingHistory.isEmpty ? EmptyBeamStack() : beamingHistory.last; + BeamLocation get currentBeamLocation => + beamingHistory.isEmpty ? EmptyBeamLocation() : beamingHistory.last; List _currentPages = []; /// {@template currentPages} - /// [currentBeamStack]'s "effective" pages, the ones that were built. + /// [currentBeamLocation]'s "effective" pages, the ones that were built. /// {@endtemplate} List get currentPages => _currentPages; @@ -353,14 +336,14 @@ class BeamerDelegate extends RouterDelegate bool _initialConfigurationReady = false; /// Main method to update the [configuration] of this delegate and its - /// [currentBeamStack]. + /// [currentBeamLocation]. /// /// This "top-level" [update] is generally used for navigation - /// _between_ [BeamStack]s and not _within_ a specific [BeamStack]. - /// For latter purpose, see [BeamStack.update]. - /// Nevertheless, [update] **will** work for navigation within [BeamStack]. + /// _between_ [BeamLocation]s and not _within_ a specific [BeamLocation]. + /// For latter purpose, see [BeamLocation.update]. + /// Nevertheless, [update] **will** work for navigation within [BeamLocation]. /// - /// Calling [update] will run the [stackBuilder]. + /// Calling [update] will run the [locationBuilder]. /// /// ```dart /// Beamer.of(context).update( @@ -374,11 +357,11 @@ class BeamerDelegate extends RouterDelegate /// other that will be used for this update **and** stored in history. /// /// [data] can be used to hold arbitrary [Object] throughout navigation. - /// See [BeamStack.data] for more information. + /// See [BeamLocation.data] for more information. /// - /// [buildBeamStack] determines whether a [BeamStack] should be created - /// from [configuration], using the [stackBuilder]. For example, when using - /// [beamTo] and passing an already created [BeamStack], this can be false. + /// [buildBeamLocation] determines whether a [BeamLocation] should be created + /// from [configuration], using the [locationBuilder]. For example, when using + /// [beamTo] and passing an already created [BeamLocation], this can be false. /// /// [rebuild] determines whether to call [notifyListeners]. This can be false /// if we already have built the UI and just want to notify the platform, @@ -395,7 +378,7 @@ class BeamerDelegate extends RouterDelegate RouteInformation? configuration, BeamParameters? beamParameters, Object? data, - bool buildBeamStack = true, + bool buildBeamLocation = true, bool rebuild = true, bool updateParent = true, bool updateRouteInformation = true, @@ -416,23 +399,28 @@ class BeamerDelegate extends RouterDelegate this.configuration = configuration != null ? Utils.createNewConfiguration(this.configuration, configuration) - : currentBeamStack.state.routeInformation.copyWith(); + : currentBeamLocation.state.routeInformation.copyWith(); // update beam parameters _currentBeamParameters = beamParameters ?? _currentBeamParameters; - if (buildBeamStack) { - // build a BeamStack from configuration - _beamStackCandidate = stackBuilder( + if (buildBeamLocation) { + // build a BeamLocation from configuration + _beamLocationCandidate = locationBuilder( this.configuration.copyWith(), _currentBeamParameters, ); } - // run guards on _beamStackCandidate + // run guards on _beamLocationCandidate final context = _context; if (context != null) { - final didApply = _runGuards(context, _beamStackCandidate); + if (configuration != null) { + _setBrowserTitle(context, + fixTitle: configuration.uri.pathSegments.last); + } + + final didApply = _runGuards(context, _beamLocationCandidate); _didRunGuards = true; if (didApply) { return; @@ -442,10 +430,10 @@ class BeamerDelegate extends RouterDelegate } // adds the candidate to history - // it will become currentBeamStack after this step - _updateBeamingHistory(_beamStackCandidate); + // it will become currentBeamLocation after this step + _updateBeamingHistory(_beamLocationCandidate); if (data != null) { - currentBeamStack.data = data; + currentBeamLocation.data = data; } routeListener?.call(this.configuration, this); @@ -474,12 +462,12 @@ class BeamerDelegate extends RouterDelegate } /// {@template beamTo} - /// Beams to a specific, manually configured [BeamStack]. + /// Beams to a specific, manually configured [BeamLocation]. /// /// For example /// ```dart /// Beamer.of(context).beamTo( - /// Stack2( + /// Location2( /// BeamState( /// pathBlueprintSegments = ['user',':userId','transactions'], /// pathParameters = {'userId': '1'}, @@ -492,59 +480,57 @@ class BeamerDelegate extends RouterDelegate /// /// See [update] for more details. /// {@endtemplate} - void beamTo( - BeamStack stack, { + void beamTo(BeamLocation location, { Object? data, - BeamStack? popTo, + BeamLocation? popTo, TransitionDelegate? transitionDelegate, bool beamBackOnPop = false, - bool popBeamStackOnPop = false, + bool popBeamLocationOnPop = false, bool stacked = true, bool replaceRouteInformation = false, }) { - _beamStackCandidate = stack; + _beamLocationCandidate = location; update( - configuration: stack.state.routeInformation, + configuration: location.state.routeInformation, beamParameters: _currentBeamParameters.copyWith( popConfiguration: popTo?.state.routeInformation, transitionDelegate: transitionDelegate ?? this.transitionDelegate, beamBackOnPop: beamBackOnPop, - popBeamStackOnPop: popBeamStackOnPop, + popBeamLocationOnPop: popBeamLocationOnPop, stacked: stacked, ), data: data, - buildBeamStack: false, + buildBeamLocation: false, replaceRouteInformation: replaceRouteInformation, ); } /// The same as [beamTo], but replaces the last state in history, /// i.e. removes it from the `beamingHistory.last.history` and then does [beamTo]. - void beamToReplacement( - BeamStack stack, { + void beamToReplacement(BeamLocation location, { Object? data, - BeamStack? popTo, + BeamLocation? popTo, TransitionDelegate? transitionDelegate, bool beamBackOnPop = false, - bool popBeamStackOnPop = false, + bool popBeamLocationOnPop = false, bool stacked = true, }) { removeLastHistoryElement(); beamTo( - stack, + location, data: data, popTo: popTo, transitionDelegate: transitionDelegate, beamBackOnPop: beamBackOnPop, - popBeamStackOnPop: popBeamStackOnPop, + popBeamLocationOnPop: popBeamLocationOnPop, stacked: stacked, replaceRouteInformation: true, ); } /// {@template beamToNamed} - /// Configures and beams to a [BeamStack] that supports uri within its - /// [BeamStack.pathPatterns]. + /// Configures and beams to a [BeamLocation] that supports uri within its + /// [BeamLocation.pathPatterns]. /// /// For example /// ```dart @@ -556,14 +542,13 @@ class BeamerDelegate extends RouterDelegate /// /// See [update] for more details. /// {@endtemplate} - void beamToNamed( - String uri, { + void beamToNamed(String uri, { Object? routeState, Object? data, String? popToNamed, TransitionDelegate? transitionDelegate, bool beamBackOnPop = false, - bool popBeamStackOnPop = false, + bool popBeamLocationOnPop = false, bool stacked = true, bool replaceRouteInformation = false, }) { @@ -575,7 +560,7 @@ class BeamerDelegate extends RouterDelegate : null, transitionDelegate: transitionDelegate ?? this.transitionDelegate, beamBackOnPop: beamBackOnPop, - popBeamStackOnPop: popBeamStackOnPop, + popBeamLocationOnPop: popBeamLocationOnPop, stacked: stacked, ), data: data, @@ -585,14 +570,13 @@ class BeamerDelegate extends RouterDelegate /// The same as [beamToNamed], but replaces the last state in history, /// i.e. removes it from the `beamingHistory.last.history` and then does [beamToNamed]. - void beamToReplacementNamed( - String uri, { + void beamToReplacementNamed(String uri, { Object? routeState, Object? data, String? popToNamed, TransitionDelegate? transitionDelegate, bool beamBackOnPop = false, - bool popBeamStackOnPop = false, + bool popBeamLocationOnPop = false, bool stacked = true, }) { removeLastHistoryElement(); @@ -603,7 +587,7 @@ class BeamerDelegate extends RouterDelegate popToNamed: popToNamed, transitionDelegate: transitionDelegate, beamBackOnPop: beamBackOnPop, - popBeamStackOnPop: popBeamStackOnPop, + popBeamLocationOnPop: popBeamLocationOnPop, stacked: stacked, replaceRouteInformation: true, ); @@ -616,22 +600,21 @@ class BeamerDelegate extends RouterDelegate /// /// See [beamToNamed] for more details. /// {@endtemplate} - void popToNamed( - String uri, { + void popToNamed(String uri, { Object? routeState, Object? data, String? popToNamed, bool beamBackOnPop = false, - bool popBeamStackOnPop = false, + bool popBeamLocationOnPop = false, bool stacked = true, bool replaceRouteInformation = false, }) { while (beamingHistory.isNotEmpty) { final index = beamingHistory.last.history.lastIndexWhere( - (element) => element.routeInformation.uri == Uri.parse(uri), + (element) => element.routeInformation.uri == Uri.parse(uri), ); if (index == -1) { - _disposeBeamStack(beamingHistory.last); + _disposeBeamLocation(beamingHistory.last); beamingHistory.removeLast(); continue; } else { @@ -647,7 +630,7 @@ class BeamerDelegate extends RouterDelegate popToNamed: popToNamed, transitionDelegate: const ReverseTransitionDelegate(), beamBackOnPop: beamBackOnPop, - popBeamStackOnPop: popBeamStackOnPop, + popBeamLocationOnPop: popBeamLocationOnPop, stacked: stacked, replaceRouteInformation: replaceRouteInformation, ); @@ -674,22 +657,22 @@ class BeamerDelegate extends RouterDelegate if (!canBeamBack) { return false; } - // first we try to beam back within last BeamStack + // first we try to beam back within last BeamLocation // i.e. we just pop its last history element id possible if (beamingHistory.last.history.length > 1) { beamingHistory.last.history.removeLast(); } else { // here we know that beamingHistory.length > 1 (because of canBeamBack) // and that beamingHistory.last.history.length == 1 - // so this last (only) entry is removed along with BeamStack - _disposeBeamStack(beamingHistory.last); + // so this last (only) entry is removed along with BeamLocation + _disposeBeamLocation(beamingHistory.last); beamingHistory.removeLast(); - _initBeamStack(beamingHistory.last); + _initBeamLocation(beamingHistory.last); } - _beamStackCandidate = beamingHistory.last; - _beamStackCandidate.update(); - final targetHistoryElement = _beamStackCandidate.history.last; + _beamLocationCandidate = beamingHistory.last; + _beamLocationCandidate.update(); + final targetHistoryElement = _beamLocationCandidate.history.last; update( configuration: targetHistoryElement.routeInformation.copyWith(), @@ -697,43 +680,43 @@ class BeamerDelegate extends RouterDelegate transitionDelegate: beamBackTransitionDelegate, ), data: data, - buildBeamStack: false, + buildBeamLocation: false, replaceRouteInformation: replaceRouteInformation, ); return true; } - /// {@template canPopBeamStack} - /// Whether it is possible to [popBeamStack], - /// i.e. there is more than 1 stack in [beamingHistory]. + /// {@template canPopBeamLocation} + /// Whether it is possible to [popBeamLocation], + /// i.e. there is more than 1 location in [beamingHistory]. /// {@endtemplate} - bool get canPopBeamStack => beamingHistory.length > 1; + bool get canPopBeamLocation => beamingHistory.length > 1; - /// {@template popBeamStack} - /// Beams to previous stack in [beamingHistory] - /// and **removes** the last stack from history. + /// {@template popBeamLocation} + /// Beams to previous location in [beamingHistory] + /// and **removes** the last location from history. /// - /// If there is no previous stack, does nothing. + /// If there is no previous location, does nothing. /// - /// Returns the success, whether the [currentBeamStack] was changed. + /// Returns the success, whether the [currentBeamLocation] was changed. /// {@endtemplate} - bool popBeamStack({ + bool popBeamLocation({ Object? data, bool replaceRouteInformation = false, }) { - if (!canPopBeamStack) { + if (!canPopBeamLocation) { return false; } - _disposeBeamStack(currentBeamStack); + _disposeBeamLocation(currentBeamLocation); beamingHistory.removeLast(); - _beamStackCandidate = beamingHistory.last; + _beamLocationCandidate = beamingHistory.last; update( - beamParameters: currentBeamStack.history.last.parameters.copyWith( + beamParameters: currentBeamLocation.history.last.parameters.copyWith( transitionDelegate: beamBackTransitionDelegate, ), data: data, - buildBeamStack: false, + buildBeamLocation: false, replaceRouteInformation: replaceRouteInformation, ); return true; @@ -742,7 +725,7 @@ class BeamerDelegate extends RouterDelegate @override RouteInformation? get currentConfiguration { final response = - _parent == null && _initialConfigurationReady ? configuration : null; + _parent == null && _initialConfigurationReady ? configuration : null; if (response != null) { _lastReportedRouteInformation = response.copyWith(); } @@ -758,20 +741,20 @@ class BeamerDelegate extends RouterDelegate _context = context; if (!_didRunGuards) { - _runGuards(_context!, _beamStackCandidate); - _addToBeamingHistory(_beamStackCandidate); + _runGuards(_context!, _beamLocationCandidate); + _addToBeamingHistory(_beamLocationCandidate); } if (!_initialConfigurationReady && active && parent != null) { updateRouteInformation(configuration); } _initialConfigurationReady = true; - if (currentBeamStack is NotFound) { + if (currentBeamLocation is NotFound) { _handleNotFoundRedirect(); } - if (!currentBeamStack.mounted) { - currentBeamStack.buildInit(context); + if (!currentBeamLocation.mounted) { + currentBeamLocation.buildInit(context); } final navigator = Builder( @@ -783,7 +766,7 @@ class BeamerDelegate extends RouterDelegate return Navigator( key: navigatorKey, observers: navigatorObservers, - transitionDelegate: currentBeamStack.transitionDelegate ?? + transitionDelegate: currentBeamLocation.transitionDelegate ?? _currentBeamParameters.transitionDelegate, pages: _currentPages, onPopPage: (route, result) => _onPopPage(context, route, result), @@ -792,15 +775,15 @@ class BeamerDelegate extends RouterDelegate ); _buildInProgress = false; - return currentBeamStack.builder(context, navigator); + return currentBeamLocation.builder(context, navigator); } @override SynchronousFuture setInitialRoutePath(RouteInformation configuration) { _initialConfigurationReady = false; final uri = configuration.uri; - if (currentBeamStack is! EmptyBeamStack) { - configuration = currentBeamStack.state.routeInformation; + if (currentBeamLocation is! EmptyBeamLocation) { + configuration = currentBeamLocation.state.routeInformation; } else if (uri.path == '/') { configuration = RouteInformation( uri: uri.replace( @@ -809,7 +792,6 @@ class BeamerDelegate extends RouterDelegate ), ); } - _deepLink ??= configuration.uri.toString(); return setNewRoutePath(configuration); } @@ -847,17 +829,17 @@ class BeamerDelegate extends RouterDelegate } } - bool _runGuards(BuildContext context, BeamStack targetBeamStack) { - final allGuards = (parent?.guards ?? []) + guards + targetBeamStack.guards; + bool _runGuards(BuildContext context, BeamLocation targetBeamLocation) { + final allGuards = + (parent?.guards ?? []) + guards + targetBeamLocation.guards; for (final guard in allGuards) { - if (guard.shouldGuard(targetBeamStack)) { + if (guard.shouldGuard(targetBeamLocation)) { final wasApplied = guard.apply( context, this, - currentBeamStack, + currentBeamLocation, _currentPages, - targetBeamStack, - _deepLink, + targetBeamLocation, ); // Return true on the first guard that was fully applied @@ -869,35 +851,35 @@ class BeamerDelegate extends RouterDelegate return false; } - void _initBeamStack(BeamStack beamStack) { - beamStack.initState(); - beamStack.onUpdate(); - beamStack.addListener(_updateFromCurrentBeamStack); + void _initBeamLocation(BeamLocation beamLocation) { + beamLocation.initState(); + beamLocation.onUpdate(); + beamLocation.addListener(_updateFromCurrentBeamLocation); } - void _disposeBeamStack(BeamStack beamStack) { - beamStack.removeListener(_updateFromCurrentBeamStack); - beamStack.disposeState(); + void _disposeBeamLocation(BeamLocation beamLocation) { + beamLocation.removeListener(_updateFromCurrentBeamLocation); + beamLocation.disposeState(); } - void _addToBeamingHistory(BeamStack beamStack) { - _disposeBeamStack(currentBeamStack); + void _addToBeamingHistory(BeamLocation beamLocation) { + _disposeBeamLocation(currentBeamLocation); if (removeDuplicateHistory) { - final index = beamingHistory.indexWhere( - (historyStack) => historyStack.runtimeType == beamStack.runtimeType); + final index = beamingHistory.indexWhere((historyLocation) => + historyLocation.runtimeType == beamLocation.runtimeType); if (index != -1) { - _disposeBeamStack(beamingHistory[index]); + _disposeBeamLocation(beamingHistory[index]); beamingHistory.removeAt(index); } } - _initBeamStack(beamStack); - beamingHistory.add(beamStack); + _initBeamLocation(beamLocation); + beamingHistory.add(beamLocation); } - void _updateBeamingHistory(BeamStack beamStack) { + void _updateBeamingHistory(BeamLocation beamLocation) { if (beamingHistory.isEmpty || - beamStack.runtimeType != beamingHistory.last.runtimeType) { - _addToBeamingHistory(beamStack); + beamLocation.runtimeType != beamingHistory.last.runtimeType) { + _addToBeamingHistory(beamLocation); } else { beamingHistory.last.update( null, @@ -929,10 +911,10 @@ class BeamerDelegate extends RouterDelegate if (updateParent) { _parent?.removeFirstHistoryElement(); } - final firstBeamStack = beamingHistory.first; - final firstHistoryElement = firstBeamStack.removeFirstFromHistory(); - if (firstBeamStack.history.isEmpty) { - _disposeBeamStack(firstBeamStack); + final firstBeamLocation = beamingHistory.first; + final firstHistoryElement = firstBeamLocation.removeFirstFromHistory(); + if (firstBeamLocation.history.isEmpty) { + _disposeBeamLocation(firstBeamLocation); beamingHistory.removeAt(0); } @@ -951,7 +933,7 @@ class BeamerDelegate extends RouterDelegate } final lastHistoryElement = beamingHistory.last.removeLastFromHistory(); if (beamingHistory.last.history.isEmpty) { - _disposeBeamStack(beamingHistory.last); + _disposeBeamLocation(beamingHistory.last); beamingHistory.removeLast(); } else { beamingHistory.last.update(null, null, null, false); @@ -966,30 +948,34 @@ class BeamerDelegate extends RouterDelegate return; } if (notFoundRedirect != null) { - _beamStackCandidate = notFoundRedirect!; + _beamLocationCandidate = notFoundRedirect!; } else if (notFoundRedirectNamed != null) { - _beamStackCandidate = stackBuilder( + _beamLocationCandidate = locationBuilder( RouteInformation(uri: Uri.parse(notFoundRedirectNamed!)), _currentBeamParameters.copyWith(), ); } - _updateFromBeamStackCandidate(); + _updateFromBeamLocationCandidate(); } void _setCurrentPages(BuildContext context) { - if (currentBeamStack is NotFound) { + if (currentBeamLocation is NotFound) { _currentPages = [notFoundPage]; } else { _currentPages = _currentBeamParameters.stacked - ? currentBeamStack.buildPages(context, currentBeamStack.state) - : [currentBeamStack.buildPages(context, currentBeamStack.state).last]; + ? currentBeamLocation.buildPages(context, currentBeamLocation.state) + : [ + currentBeamLocation + .buildPages(context, currentBeamLocation.state) + .last + ]; } } - void _setBrowserTitle(BuildContext context) { + void _setBrowserTitle(BuildContext context, {String? fixTitle}) { if (active && setBrowserTabTitle) { - final String title = _currentPages.last.title ?? - currentBeamStack.state.routeInformation.uri.path; + final String title = fixTitle ?? _currentPages.last.title ?? + currentBeamLocation.state.routeInformation.uri.path; browser_tab_title_util.setTabTitle(title); } } @@ -1010,9 +996,9 @@ class BeamerDelegate extends RouterDelegate ), // replaceCurrent: true, ); - } else if (_currentBeamParameters.popBeamStackOnPop) { - final didPopBeamStack = popBeamStack(); - if (!didPopBeamStack) { + } else if (_currentBeamParameters.popBeamLocationOnPop) { + final didPopBeamLocation = popBeamLocation(); + if (!didPopBeamLocation) { return false; } } else if (_currentBeamParameters.beamBackOnPop) { @@ -1028,7 +1014,7 @@ class BeamerDelegate extends RouterDelegate final shouldPop = lastPage.onPopPage( context, this, - currentBeamStack.state, + currentBeamLocation.state, lastPage, ); if (!shouldPop) { @@ -1046,14 +1032,14 @@ class BeamerDelegate extends RouterDelegate void _initializeChild() { final parentConfiguration = _parent!.configuration.copyWith(); if (initializeFromParent) { - _beamStackCandidate = - stackBuilder(parentConfiguration, _currentBeamParameters); + _beamLocationCandidate = + locationBuilder(parentConfiguration, _currentBeamParameters); } // If this couldn't handle parents configuration, // it will update itself to initialPath and declare itself inactive. - if (_beamStackCandidate is EmptyBeamStack || - _beamStackCandidate is NotFound) { + if (_beamLocationCandidate is EmptyBeamLocation || + _beamLocationCandidate is NotFound) { update( configuration: RouteInformation(uri: Uri.parse(initialPath)), rebuild: false, @@ -1077,9 +1063,10 @@ class BeamerDelegate extends RouterDelegate // Updates only if it can handle the configuration void _updateFromParent({bool rebuild = true}) { final parentConfiguration = _parent!.configuration.copyWith(); - final beamStack = stackBuilder(parentConfiguration, _currentBeamParameters); + final beamLocation = + locationBuilder(parentConfiguration, _currentBeamParameters); - if (beamStack is! NotFound) { + if (beamLocation is! NotFound) { update( configuration: parentConfiguration, rebuild: rebuild, @@ -1089,18 +1076,18 @@ class BeamerDelegate extends RouterDelegate } } - void _updateFromCurrentBeamStack({bool rebuild = true}) { + void _updateFromCurrentBeamLocation({bool rebuild = true}) { update( - configuration: currentBeamStack.state.routeInformation, - buildBeamStack: false, + configuration: currentBeamLocation.state.routeInformation, + buildBeamLocation: false, rebuild: rebuild, ); } - void _updateFromBeamStackCandidate({bool rebuild = false}) { + void _updateFromBeamLocationCandidate({bool rebuild = false}) { update( - configuration: _beamStackCandidate.state.routeInformation, - buildBeamStack: false, + configuration: _beamLocationCandidate.state.routeInformation, + buildBeamLocation: false, rebuild: rebuild, ); } @@ -1130,8 +1117,8 @@ class BeamerDelegate extends RouterDelegate void dispose() { _children.clear(); _parent?.removeListener(_updateFromParent); - _disposeBeamStack(currentBeamStack); - currentBeamStack.dispose(); + _disposeBeamLocation(currentBeamLocation); + currentBeamLocation.dispose(); updateListenable?.removeListener(_update); super.dispose(); } From c6d1823f7fdcc82ff25b1a99d2aec4243506ea8c Mon Sep 17 00:00:00 2001 From: fabianbrandscheid <38261693+fabianbrandscheid@users.noreply.github.com> Date: Tue, 16 Jul 2024 09:21:21 +0200 Subject: [PATCH 2/8] Add files via upload Fix Update Title web --- package/lib/src/beamer_delegate.dart | 441 ++++++++++++++------------- 1 file changed, 230 insertions(+), 211 deletions(-) diff --git a/package/lib/src/beamer_delegate.dart b/package/lib/src/beamer_delegate.dart index d61b7c3..0a36af3 100644 --- a/package/lib/src/beamer_delegate.dart +++ b/package/lib/src/beamer_delegate.dart @@ -14,9 +14,9 @@ class BeamerDelegate extends RouterDelegate with ChangeNotifier, PopNavigatorRouterDelegateMixin { /// Creates a [BeamerDelegate] with specified properties. /// - /// [locationBuilder] is required to process the incoming navigation request. + /// [stackBuilder] is required to process the incoming navigation request. BeamerDelegate({ - required this.locationBuilder, + required this.stackBuilder, this.initialPath = '/', this.routeListener, this.buildListener, @@ -24,7 +24,8 @@ class BeamerDelegate extends RouterDelegate @Deprecated( 'No longer used by this package, please remove any references to it. ' 'This feature was deprecated after v1.0.0.', - ) this.preferUpdate = true, + ) + this.preferUpdate = true, this.removeDuplicateHistory = true, this.notFoundPage = BeamPage.notFound, this.notFoundRedirect, @@ -50,7 +51,7 @@ class BeamerDelegate extends RouterDelegate } /// A state of this delegate. This is the `routeInformation` that goes into - /// [locationBuilder] to build an appropriate [BeamLocation]. + /// [stackBuilder] to build an appropriate [BeamStack]. /// /// A way to modify this state is via [update]. late RouteInformation configuration; @@ -108,44 +109,44 @@ class BeamerDelegate extends RouterDelegate return root; } - /// A builder for [BeamLocation]s. + /// A builder for [BeamStack]s. /// - /// There are 3 ways of building an appropriate [BeamLocation] which will in + /// There are 3 ways of building an appropriate [BeamStack] which will in /// turn build a stack of pages that should go into [Navigator.pages]. /// /// 1. Custom closure /// ```dart - /// locationBuilder: (state) { + /// stackBuilder: (state) { /// if (state.uri.pathSegments.contains('l1')) { - /// return Location1(state); + /// return Stack1(state); /// } /// if (state.uri.pathSegments.contains('l2')) { - /// return Location2(state); + /// return Stack2(state); /// } /// return NotFound(path: state.uri.toString()); /// }, /// ``` /// - /// 2. [BeamerLocationBuilder]; chooses appropriate [BeamLocation] itself + /// 2. [BeamerStackBuilder]; chooses appropriate [BeamStack] itself /// ```dart - /// locationBuilder: BeamerLocationBuilder( - /// beamLocations: [ - /// Location1(), - /// Location2(), + /// stackBuilder: BeamerStackBuilder( + /// beamStacks: [ + /// Stack1(), + /// Stack2(), /// ], /// ), /// ``` /// - /// 3. [RoutesLocationBuilder]; a Map of routes + /// 3. [RoutesStackBuilder]; a Map of routes /// ```dart - /// locationBuilder: RoutesLocationBuilder( + /// stackBuilder: RoutesStackBuilder( /// routes: { /// '/': (context, state) => HomeScreen(), /// '/another': (context, state) => AnotherScreen(), /// }, /// ), /// ``` - final LocationBuilder locationBuilder; + final StackBuilder stackBuilder; /// The path to replace `/` as default initial route path upon load. /// @@ -153,10 +154,26 @@ class BeamerDelegate extends RouterDelegate /// you will not be able to navigate to `/` by manually typing /// it in the URL bar, because it will always be transformed to `initialPath`, /// but you will be able to get to `/` by popping pages with back button, - /// if there are pages in [BeamLocation.buildPages] that will build + /// if there are pages in [BeamStack.buildPages] that will build /// when there are no path segments. final String initialPath; + /// An initial deep-link that should be stored until used. + /// + /// This can be used in [BeamGuard.beamToNamed] to redirect properly after + /// some auth flows that need to be executed, during which the [initialPath] + /// information is lost. + String? _deepLink; + + /// Sets the deep-link route path that + /// - will be used as [initialPath] + /// - can be used in [BeamGuard.beamToNamed] to redirect properly + /// after some auth flows that need to be executed, + /// during which the [initialPath] information is lost. + /// + /// Once a [BeamGuard] uses the deep-link, it will be reset to null. + void setDeepLink(String? deepLink) => _deepLink = deepLink; + /// The routeListener will be called on every navigation event /// and will receive the [configuration] and a reference to this delegate. final void Function(RouteInformation, BeamerDelegate)? routeListener; @@ -176,26 +193,26 @@ class BeamerDelegate extends RouterDelegate // ignore: public_member_api_docs final bool preferUpdate; - /// Whether to remove [BeamLocation]s from [beamingHistory] - /// if they are the same type as the location being beamed to. + /// Whether to remove [BeamStack]s from [beamingHistory] + /// if they are the same type as the stack being beamed to. /// /// See how this is used at [_addToBeamingHistory] implementation. final bool removeDuplicateHistory; - /// Page to show when no [BeamLocation] supports the incoming URI. + /// Page to show when no [BeamStack] supports the incoming URI. late BeamPage notFoundPage; - /// [BeamLocation] to redirect to when no [BeamLocation] supports the incoming URI. - final BeamLocation? notFoundRedirect; + /// [BeamStack] to redirect to when no [BeamStack] supports the incoming URI. + final BeamStack? notFoundRedirect; - /// URI string to redirect to when no [BeamLocation] supports the incoming URI. + /// URI string to redirect to when no [BeamStack] supports the incoming URI. final String? notFoundRedirectNamed; - /// Guards that will be executing [BeamGuard.check] on [currentBeamLocation] + /// Guards that will be executing [BeamGuard.check] on [currentBeamStack] /// candidate. /// /// Checks will be executed in order; chain of responsibility pattern. - /// When some guard returns `false`, location candidate will not be accepted + /// When some guard returns `false`, stack candidate will not be accepted /// and stack of pages will be updated as is configured in [BeamGuard]. final List guards; @@ -204,7 +221,7 @@ class BeamerDelegate extends RouterDelegate /// A transition delegate to be used by [Navigator]. /// - /// This transition delegate will be overridden by the one in [BeamLocation], + /// This transition delegate will be overridden by the one in [BeamStack], /// if any is set. /// /// See [Navigator.transitionDelegate]. @@ -247,7 +264,7 @@ class BeamerDelegate extends RouterDelegate final bool updateParent; /// Whether to remove all entries from [beamingHistory] (and their nested - /// [BeamLocation.history]) when a route belonging to this set is reached, + /// [BeamStack.history]) when a route belonging to this set is reached, /// regardless of how it was reached. /// /// Note that [popToNamed] will also try to clear as much [beamingHistory] @@ -257,41 +274,41 @@ class BeamerDelegate extends RouterDelegate final GlobalKey _navigatorKey = GlobalKey(); /// {@template beamingHistory} - /// The history of [BeamLocation]s, each holding its [BeamLocation.history]. + /// The history of [BeamStack]s, each holding its [BeamStack.history]. /// /// See [_addToBeamingHistory]. /// {@endtemplate} - final List beamingHistory = []; + final List beamingHistory = []; /// Returns the complete length of beaming history, that is the sum of all - /// history lengths for each [BeamLocation] in [beamingHistory]. + /// history lengths for each [BeamStack] in [beamingHistory]. int get beamingHistoryCompleteLength { var length = 0; - for (var location in beamingHistory) { - length += location.history.length; + for (var stack in beamingHistory) { + length += stack.history.length; } return length; } - /// A candidate to become the next [currentBeamLocation]. - BeamLocation _beamLocationCandidate = EmptyBeamLocation(); + /// A candidate to become the next [currentBeamStack]. + BeamStack _beamStackCandidate = EmptyBeamStack(); - /// {@template currentBeamLocation} - /// A [BeamLocation] that is currently responsible for providing a page stack - /// via [BeamLocation.buildPages] and holds the current [BeamState]. + /// {@template currentBeamStack} + /// A [BeamStack] that is currently responsible for providing a page stack + /// via [BeamStack.buildPages] and holds the current [BeamState]. /// /// Usually obtained via /// ```dart - /// Beamer.of(context).currentBeamLocation + /// Beamer.of(context).currentBeamStack /// ``` /// {@endtemplate} - BeamLocation get currentBeamLocation => - beamingHistory.isEmpty ? EmptyBeamLocation() : beamingHistory.last; + BeamStack get currentBeamStack => + beamingHistory.isEmpty ? EmptyBeamStack() : beamingHistory.last; List _currentPages = []; /// {@template currentPages} - /// [currentBeamLocation]'s "effective" pages, the ones that were built. + /// [currentBeamStack]'s "effective" pages, the ones that were built. /// {@endtemplate} List get currentPages => _currentPages; @@ -336,14 +353,14 @@ class BeamerDelegate extends RouterDelegate bool _initialConfigurationReady = false; /// Main method to update the [configuration] of this delegate and its - /// [currentBeamLocation]. + /// [currentBeamStack]. /// /// This "top-level" [update] is generally used for navigation - /// _between_ [BeamLocation]s and not _within_ a specific [BeamLocation]. - /// For latter purpose, see [BeamLocation.update]. - /// Nevertheless, [update] **will** work for navigation within [BeamLocation]. + /// _between_ [BeamStack]s and not _within_ a specific [BeamStack]. + /// For latter purpose, see [BeamStack.update]. + /// Nevertheless, [update] **will** work for navigation within [BeamStack]. /// - /// Calling [update] will run the [locationBuilder]. + /// Calling [update] will run the [stackBuilder]. /// /// ```dart /// Beamer.of(context).update( @@ -357,11 +374,11 @@ class BeamerDelegate extends RouterDelegate /// other that will be used for this update **and** stored in history. /// /// [data] can be used to hold arbitrary [Object] throughout navigation. - /// See [BeamLocation.data] for more information. + /// See [BeamStack.data] for more information. /// - /// [buildBeamLocation] determines whether a [BeamLocation] should be created - /// from [configuration], using the [locationBuilder]. For example, when using - /// [beamTo] and passing an already created [BeamLocation], this can be false. + /// [buildBeamStack] determines whether a [BeamStack] should be created + /// from [configuration], using the [stackBuilder]. For example, when using + /// [beamTo] and passing an already created [BeamStack], this can be false. /// /// [rebuild] determines whether to call [notifyListeners]. This can be false /// if we already have built the UI and just want to notify the platform, @@ -378,7 +395,7 @@ class BeamerDelegate extends RouterDelegate RouteInformation? configuration, BeamParameters? beamParameters, Object? data, - bool buildBeamLocation = true, + bool buildBeamStack = true, bool rebuild = true, bool updateParent = true, bool updateRouteInformation = true, @@ -399,28 +416,27 @@ class BeamerDelegate extends RouterDelegate this.configuration = configuration != null ? Utils.createNewConfiguration(this.configuration, configuration) - : currentBeamLocation.state.routeInformation.copyWith(); + : currentBeamStack.state.routeInformation.copyWith(); // update beam parameters _currentBeamParameters = beamParameters ?? _currentBeamParameters; - if (buildBeamLocation) { - // build a BeamLocation from configuration - _beamLocationCandidate = locationBuilder( + if (buildBeamStack) { + // build a BeamStack from configuration + _beamStackCandidate = stackBuilder( this.configuration.copyWith(), _currentBeamParameters, ); } - // run guards on _beamLocationCandidate + // run guards on _beamStackCandidate final context = _context; if (context != null) { if (configuration != null) { _setBrowserTitle(context, fixTitle: configuration.uri.pathSegments.last); } - - final didApply = _runGuards(context, _beamLocationCandidate); + final didApply = _runGuards(context, _beamStackCandidate); _didRunGuards = true; if (didApply) { return; @@ -430,10 +446,10 @@ class BeamerDelegate extends RouterDelegate } // adds the candidate to history - // it will become currentBeamLocation after this step - _updateBeamingHistory(_beamLocationCandidate); + // it will become currentBeamStack after this step + _updateBeamingHistory(_beamStackCandidate); if (data != null) { - currentBeamLocation.data = data; + currentBeamStack.data = data; } routeListener?.call(this.configuration, this); @@ -462,12 +478,12 @@ class BeamerDelegate extends RouterDelegate } /// {@template beamTo} - /// Beams to a specific, manually configured [BeamLocation]. + /// Beams to a specific, manually configured [BeamStack]. /// /// For example /// ```dart /// Beamer.of(context).beamTo( - /// Location2( + /// Stack2( /// BeamState( /// pathBlueprintSegments = ['user',':userId','transactions'], /// pathParameters = {'userId': '1'}, @@ -480,57 +496,59 @@ class BeamerDelegate extends RouterDelegate /// /// See [update] for more details. /// {@endtemplate} - void beamTo(BeamLocation location, { - Object? data, - BeamLocation? popTo, - TransitionDelegate? transitionDelegate, - bool beamBackOnPop = false, - bool popBeamLocationOnPop = false, - bool stacked = true, - bool replaceRouteInformation = false, - }) { - _beamLocationCandidate = location; + void beamTo( + BeamStack stack, { + Object? data, + BeamStack? popTo, + TransitionDelegate? transitionDelegate, + bool beamBackOnPop = false, + bool popBeamStackOnPop = false, + bool stacked = true, + bool replaceRouteInformation = false, + }) { + _beamStackCandidate = stack; update( - configuration: location.state.routeInformation, + configuration: stack.state.routeInformation, beamParameters: _currentBeamParameters.copyWith( popConfiguration: popTo?.state.routeInformation, transitionDelegate: transitionDelegate ?? this.transitionDelegate, beamBackOnPop: beamBackOnPop, - popBeamLocationOnPop: popBeamLocationOnPop, + popBeamStackOnPop: popBeamStackOnPop, stacked: stacked, ), data: data, - buildBeamLocation: false, + buildBeamStack: false, replaceRouteInformation: replaceRouteInformation, ); } /// The same as [beamTo], but replaces the last state in history, /// i.e. removes it from the `beamingHistory.last.history` and then does [beamTo]. - void beamToReplacement(BeamLocation location, { - Object? data, - BeamLocation? popTo, - TransitionDelegate? transitionDelegate, - bool beamBackOnPop = false, - bool popBeamLocationOnPop = false, - bool stacked = true, - }) { + void beamToReplacement( + BeamStack stack, { + Object? data, + BeamStack? popTo, + TransitionDelegate? transitionDelegate, + bool beamBackOnPop = false, + bool popBeamStackOnPop = false, + bool stacked = true, + }) { removeLastHistoryElement(); beamTo( - location, + stack, data: data, popTo: popTo, transitionDelegate: transitionDelegate, beamBackOnPop: beamBackOnPop, - popBeamLocationOnPop: popBeamLocationOnPop, + popBeamStackOnPop: popBeamStackOnPop, stacked: stacked, replaceRouteInformation: true, ); } /// {@template beamToNamed} - /// Configures and beams to a [BeamLocation] that supports uri within its - /// [BeamLocation.pathPatterns]. + /// Configures and beams to a [BeamStack] that supports uri within its + /// [BeamStack.pathPatterns]. /// /// For example /// ```dart @@ -542,16 +560,17 @@ class BeamerDelegate extends RouterDelegate /// /// See [update] for more details. /// {@endtemplate} - void beamToNamed(String uri, { - Object? routeState, - Object? data, - String? popToNamed, - TransitionDelegate? transitionDelegate, - bool beamBackOnPop = false, - bool popBeamLocationOnPop = false, - bool stacked = true, - bool replaceRouteInformation = false, - }) { + void beamToNamed( + String uri, { + Object? routeState, + Object? data, + String? popToNamed, + TransitionDelegate? transitionDelegate, + bool beamBackOnPop = false, + bool popBeamStackOnPop = false, + bool stacked = true, + bool replaceRouteInformation = false, + }) { update( configuration: RouteInformation(uri: Uri.parse(uri), state: routeState), beamParameters: _currentBeamParameters.copyWith( @@ -560,7 +579,7 @@ class BeamerDelegate extends RouterDelegate : null, transitionDelegate: transitionDelegate ?? this.transitionDelegate, beamBackOnPop: beamBackOnPop, - popBeamLocationOnPop: popBeamLocationOnPop, + popBeamStackOnPop: popBeamStackOnPop, stacked: stacked, ), data: data, @@ -570,15 +589,16 @@ class BeamerDelegate extends RouterDelegate /// The same as [beamToNamed], but replaces the last state in history, /// i.e. removes it from the `beamingHistory.last.history` and then does [beamToNamed]. - void beamToReplacementNamed(String uri, { - Object? routeState, - Object? data, - String? popToNamed, - TransitionDelegate? transitionDelegate, - bool beamBackOnPop = false, - bool popBeamLocationOnPop = false, - bool stacked = true, - }) { + void beamToReplacementNamed( + String uri, { + Object? routeState, + Object? data, + String? popToNamed, + TransitionDelegate? transitionDelegate, + bool beamBackOnPop = false, + bool popBeamStackOnPop = false, + bool stacked = true, + }) { removeLastHistoryElement(); beamToNamed( uri, @@ -587,7 +607,7 @@ class BeamerDelegate extends RouterDelegate popToNamed: popToNamed, transitionDelegate: transitionDelegate, beamBackOnPop: beamBackOnPop, - popBeamLocationOnPop: popBeamLocationOnPop, + popBeamStackOnPop: popBeamStackOnPop, stacked: stacked, replaceRouteInformation: true, ); @@ -600,21 +620,22 @@ class BeamerDelegate extends RouterDelegate /// /// See [beamToNamed] for more details. /// {@endtemplate} - void popToNamed(String uri, { - Object? routeState, - Object? data, - String? popToNamed, - bool beamBackOnPop = false, - bool popBeamLocationOnPop = false, - bool stacked = true, - bool replaceRouteInformation = false, - }) { + void popToNamed( + String uri, { + Object? routeState, + Object? data, + String? popToNamed, + bool beamBackOnPop = false, + bool popBeamStackOnPop = false, + bool stacked = true, + bool replaceRouteInformation = false, + }) { while (beamingHistory.isNotEmpty) { final index = beamingHistory.last.history.lastIndexWhere( (element) => element.routeInformation.uri == Uri.parse(uri), ); if (index == -1) { - _disposeBeamLocation(beamingHistory.last); + _disposeBeamStack(beamingHistory.last); beamingHistory.removeLast(); continue; } else { @@ -630,7 +651,7 @@ class BeamerDelegate extends RouterDelegate popToNamed: popToNamed, transitionDelegate: const ReverseTransitionDelegate(), beamBackOnPop: beamBackOnPop, - popBeamLocationOnPop: popBeamLocationOnPop, + popBeamStackOnPop: popBeamStackOnPop, stacked: stacked, replaceRouteInformation: replaceRouteInformation, ); @@ -657,22 +678,22 @@ class BeamerDelegate extends RouterDelegate if (!canBeamBack) { return false; } - // first we try to beam back within last BeamLocation + // first we try to beam back within last BeamStack // i.e. we just pop its last history element id possible if (beamingHistory.last.history.length > 1) { beamingHistory.last.history.removeLast(); } else { // here we know that beamingHistory.length > 1 (because of canBeamBack) // and that beamingHistory.last.history.length == 1 - // so this last (only) entry is removed along with BeamLocation - _disposeBeamLocation(beamingHistory.last); + // so this last (only) entry is removed along with BeamStack + _disposeBeamStack(beamingHistory.last); beamingHistory.removeLast(); - _initBeamLocation(beamingHistory.last); + _initBeamStack(beamingHistory.last); } - _beamLocationCandidate = beamingHistory.last; - _beamLocationCandidate.update(); - final targetHistoryElement = _beamLocationCandidate.history.last; + _beamStackCandidate = beamingHistory.last; + _beamStackCandidate.update(); + final targetHistoryElement = _beamStackCandidate.history.last; update( configuration: targetHistoryElement.routeInformation.copyWith(), @@ -680,43 +701,43 @@ class BeamerDelegate extends RouterDelegate transitionDelegate: beamBackTransitionDelegate, ), data: data, - buildBeamLocation: false, + buildBeamStack: false, replaceRouteInformation: replaceRouteInformation, ); return true; } - /// {@template canPopBeamLocation} - /// Whether it is possible to [popBeamLocation], - /// i.e. there is more than 1 location in [beamingHistory]. + /// {@template canPopBeamStack} + /// Whether it is possible to [popBeamStack], + /// i.e. there is more than 1 stack in [beamingHistory]. /// {@endtemplate} - bool get canPopBeamLocation => beamingHistory.length > 1; + bool get canPopBeamStack => beamingHistory.length > 1; - /// {@template popBeamLocation} - /// Beams to previous location in [beamingHistory] - /// and **removes** the last location from history. + /// {@template popBeamStack} + /// Beams to previous stack in [beamingHistory] + /// and **removes** the last stack from history. /// - /// If there is no previous location, does nothing. + /// If there is no previous stack, does nothing. /// - /// Returns the success, whether the [currentBeamLocation] was changed. + /// Returns the success, whether the [currentBeamStack] was changed. /// {@endtemplate} - bool popBeamLocation({ + bool popBeamStack({ Object? data, bool replaceRouteInformation = false, }) { - if (!canPopBeamLocation) { + if (!canPopBeamStack) { return false; } - _disposeBeamLocation(currentBeamLocation); + _disposeBeamStack(currentBeamStack); beamingHistory.removeLast(); - _beamLocationCandidate = beamingHistory.last; + _beamStackCandidate = beamingHistory.last; update( - beamParameters: currentBeamLocation.history.last.parameters.copyWith( + beamParameters: currentBeamStack.history.last.parameters.copyWith( transitionDelegate: beamBackTransitionDelegate, ), data: data, - buildBeamLocation: false, + buildBeamStack: false, replaceRouteInformation: replaceRouteInformation, ); return true; @@ -741,20 +762,20 @@ class BeamerDelegate extends RouterDelegate _context = context; if (!_didRunGuards) { - _runGuards(_context!, _beamLocationCandidate); - _addToBeamingHistory(_beamLocationCandidate); + _runGuards(_context!, _beamStackCandidate); + _addToBeamingHistory(_beamStackCandidate); } if (!_initialConfigurationReady && active && parent != null) { updateRouteInformation(configuration); } _initialConfigurationReady = true; - if (currentBeamLocation is NotFound) { + if (currentBeamStack is NotFound) { _handleNotFoundRedirect(); } - if (!currentBeamLocation.mounted) { - currentBeamLocation.buildInit(context); + if (!currentBeamStack.mounted) { + currentBeamStack.buildInit(context); } final navigator = Builder( @@ -766,7 +787,7 @@ class BeamerDelegate extends RouterDelegate return Navigator( key: navigatorKey, observers: navigatorObservers, - transitionDelegate: currentBeamLocation.transitionDelegate ?? + transitionDelegate: currentBeamStack.transitionDelegate ?? _currentBeamParameters.transitionDelegate, pages: _currentPages, onPopPage: (route, result) => _onPopPage(context, route, result), @@ -775,15 +796,15 @@ class BeamerDelegate extends RouterDelegate ); _buildInProgress = false; - return currentBeamLocation.builder(context, navigator); + return currentBeamStack.builder(context, navigator); } @override SynchronousFuture setInitialRoutePath(RouteInformation configuration) { _initialConfigurationReady = false; final uri = configuration.uri; - if (currentBeamLocation is! EmptyBeamLocation) { - configuration = currentBeamLocation.state.routeInformation; + if (currentBeamStack is! EmptyBeamStack) { + configuration = currentBeamStack.state.routeInformation; } else if (uri.path == '/') { configuration = RouteInformation( uri: uri.replace( @@ -792,6 +813,7 @@ class BeamerDelegate extends RouterDelegate ), ); } + _deepLink ??= configuration.uri.toString(); return setNewRoutePath(configuration); } @@ -829,17 +851,17 @@ class BeamerDelegate extends RouterDelegate } } - bool _runGuards(BuildContext context, BeamLocation targetBeamLocation) { - final allGuards = - (parent?.guards ?? []) + guards + targetBeamLocation.guards; + bool _runGuards(BuildContext context, BeamStack targetBeamStack) { + final allGuards = (parent?.guards ?? []) + guards + targetBeamStack.guards; for (final guard in allGuards) { - if (guard.shouldGuard(targetBeamLocation)) { + if (guard.shouldGuard(targetBeamStack)) { final wasApplied = guard.apply( context, this, - currentBeamLocation, + currentBeamStack, _currentPages, - targetBeamLocation, + targetBeamStack, + _deepLink, ); // Return true on the first guard that was fully applied @@ -851,35 +873,35 @@ class BeamerDelegate extends RouterDelegate return false; } - void _initBeamLocation(BeamLocation beamLocation) { - beamLocation.initState(); - beamLocation.onUpdate(); - beamLocation.addListener(_updateFromCurrentBeamLocation); + void _initBeamStack(BeamStack beamStack) { + beamStack.initState(); + beamStack.onUpdate(); + beamStack.addListener(_updateFromCurrentBeamStack); } - void _disposeBeamLocation(BeamLocation beamLocation) { - beamLocation.removeListener(_updateFromCurrentBeamLocation); - beamLocation.disposeState(); + void _disposeBeamStack(BeamStack beamStack) { + beamStack.removeListener(_updateFromCurrentBeamStack); + beamStack.disposeState(); } - void _addToBeamingHistory(BeamLocation beamLocation) { - _disposeBeamLocation(currentBeamLocation); + void _addToBeamingHistory(BeamStack beamStack) { + _disposeBeamStack(currentBeamStack); if (removeDuplicateHistory) { - final index = beamingHistory.indexWhere((historyLocation) => - historyLocation.runtimeType == beamLocation.runtimeType); + final index = beamingHistory.indexWhere( + (historyStack) => historyStack.runtimeType == beamStack.runtimeType); if (index != -1) { - _disposeBeamLocation(beamingHistory[index]); + _disposeBeamStack(beamingHistory[index]); beamingHistory.removeAt(index); } } - _initBeamLocation(beamLocation); - beamingHistory.add(beamLocation); + _initBeamStack(beamStack); + beamingHistory.add(beamStack); } - void _updateBeamingHistory(BeamLocation beamLocation) { + void _updateBeamingHistory(BeamStack beamStack) { if (beamingHistory.isEmpty || - beamLocation.runtimeType != beamingHistory.last.runtimeType) { - _addToBeamingHistory(beamLocation); + beamStack.runtimeType != beamingHistory.last.runtimeType) { + _addToBeamingHistory(beamStack); } else { beamingHistory.last.update( null, @@ -911,10 +933,10 @@ class BeamerDelegate extends RouterDelegate if (updateParent) { _parent?.removeFirstHistoryElement(); } - final firstBeamLocation = beamingHistory.first; - final firstHistoryElement = firstBeamLocation.removeFirstFromHistory(); - if (firstBeamLocation.history.isEmpty) { - _disposeBeamLocation(firstBeamLocation); + final firstBeamStack = beamingHistory.first; + final firstHistoryElement = firstBeamStack.removeFirstFromHistory(); + if (firstBeamStack.history.isEmpty) { + _disposeBeamStack(firstBeamStack); beamingHistory.removeAt(0); } @@ -933,7 +955,7 @@ class BeamerDelegate extends RouterDelegate } final lastHistoryElement = beamingHistory.last.removeLastFromHistory(); if (beamingHistory.last.history.isEmpty) { - _disposeBeamLocation(beamingHistory.last); + _disposeBeamStack(beamingHistory.last); beamingHistory.removeLast(); } else { beamingHistory.last.update(null, null, null, false); @@ -948,32 +970,30 @@ class BeamerDelegate extends RouterDelegate return; } if (notFoundRedirect != null) { - _beamLocationCandidate = notFoundRedirect!; + _beamStackCandidate = notFoundRedirect!; } else if (notFoundRedirectNamed != null) { - _beamLocationCandidate = locationBuilder( + _beamStackCandidate = stackBuilder( RouteInformation(uri: Uri.parse(notFoundRedirectNamed!)), _currentBeamParameters.copyWith(), ); } - _updateFromBeamLocationCandidate(); + _updateFromBeamStackCandidate(); } void _setCurrentPages(BuildContext context) { - if (currentBeamLocation is NotFound) { + if (currentBeamStack is NotFound) { _currentPages = [notFoundPage]; } else { _currentPages = _currentBeamParameters.stacked - ? currentBeamLocation.buildPages(context, currentBeamLocation.state) - : [ - currentBeamLocation - .buildPages(context, currentBeamLocation.state) - .last - ]; + ? currentBeamStack.buildPages(context, currentBeamStack.state) + : [currentBeamStack.buildPages(context, currentBeamStack.state).last]; } } void _setBrowserTitle(BuildContext context, {String? fixTitle}) { if (active && setBrowserTabTitle) { + final String title = _currentPages.last.title ?? + currentBeamStack.state.routeInformation.uri.path; final String title = fixTitle ?? _currentPages.last.title ?? currentBeamLocation.state.routeInformation.uri.path; browser_tab_title_util.setTabTitle(title); @@ -996,9 +1016,9 @@ class BeamerDelegate extends RouterDelegate ), // replaceCurrent: true, ); - } else if (_currentBeamParameters.popBeamLocationOnPop) { - final didPopBeamLocation = popBeamLocation(); - if (!didPopBeamLocation) { + } else if (_currentBeamParameters.popBeamStackOnPop) { + final didPopBeamStack = popBeamStack(); + if (!didPopBeamStack) { return false; } } else if (_currentBeamParameters.beamBackOnPop) { @@ -1014,7 +1034,7 @@ class BeamerDelegate extends RouterDelegate final shouldPop = lastPage.onPopPage( context, this, - currentBeamLocation.state, + currentBeamStack.state, lastPage, ); if (!shouldPop) { @@ -1032,14 +1052,14 @@ class BeamerDelegate extends RouterDelegate void _initializeChild() { final parentConfiguration = _parent!.configuration.copyWith(); if (initializeFromParent) { - _beamLocationCandidate = - locationBuilder(parentConfiguration, _currentBeamParameters); + _beamStackCandidate = + stackBuilder(parentConfiguration, _currentBeamParameters); } // If this couldn't handle parents configuration, // it will update itself to initialPath and declare itself inactive. - if (_beamLocationCandidate is EmptyBeamLocation || - _beamLocationCandidate is NotFound) { + if (_beamStackCandidate is EmptyBeamStack || + _beamStackCandidate is NotFound) { update( configuration: RouteInformation(uri: Uri.parse(initialPath)), rebuild: false, @@ -1063,10 +1083,9 @@ class BeamerDelegate extends RouterDelegate // Updates only if it can handle the configuration void _updateFromParent({bool rebuild = true}) { final parentConfiguration = _parent!.configuration.copyWith(); - final beamLocation = - locationBuilder(parentConfiguration, _currentBeamParameters); + final beamStack = stackBuilder(parentConfiguration, _currentBeamParameters); - if (beamLocation is! NotFound) { + if (beamStack is! NotFound) { update( configuration: parentConfiguration, rebuild: rebuild, @@ -1076,18 +1095,18 @@ class BeamerDelegate extends RouterDelegate } } - void _updateFromCurrentBeamLocation({bool rebuild = true}) { + void _updateFromCurrentBeamStack({bool rebuild = true}) { update( - configuration: currentBeamLocation.state.routeInformation, - buildBeamLocation: false, + configuration: currentBeamStack.state.routeInformation, + buildBeamStack: false, rebuild: rebuild, ); } - void _updateFromBeamLocationCandidate({bool rebuild = false}) { + void _updateFromBeamStackCandidate({bool rebuild = false}) { update( - configuration: _beamLocationCandidate.state.routeInformation, - buildBeamLocation: false, + configuration: _beamStackCandidate.state.routeInformation, + buildBeamStack: false, rebuild: rebuild, ); } @@ -1117,9 +1136,9 @@ class BeamerDelegate extends RouterDelegate void dispose() { _children.clear(); _parent?.removeListener(_updateFromParent); - _disposeBeamLocation(currentBeamLocation); - currentBeamLocation.dispose(); + _disposeBeamStack(currentBeamStack); + currentBeamStack.dispose(); updateListenable?.removeListener(_update); super.dispose(); } -} +} \ No newline at end of file From f8f769d7099e7d6c2b5ef81777dffd4d9080200f Mon Sep 17 00:00:00 2001 From: fabianbrandscheid <38261693+fabianbrandscheid@users.noreply.github.com> Date: Tue, 16 Jul 2024 09:25:28 +0200 Subject: [PATCH 3/8] Add files via upload format --- package/lib/src/beamer_delegate.dart | 116 +++++++++++++-------------- 1 file changed, 57 insertions(+), 59 deletions(-) diff --git a/package/lib/src/beamer_delegate.dart b/package/lib/src/beamer_delegate.dart index 0a36af3..174026a 100644 --- a/package/lib/src/beamer_delegate.dart +++ b/package/lib/src/beamer_delegate.dart @@ -1,7 +1,7 @@ import 'package:beamer/beamer.dart'; import 'package:beamer/src/browser_tab_title_util_non_web.dart' -if (dart.library.html) 'package:beamer/src/browser_tab_title_util_web.dart' -as browser_tab_title_util; + if (dart.library.html) 'package:beamer/src/browser_tab_title_util_web.dart' + as browser_tab_title_util; import 'package:beamer/src/utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -23,9 +23,8 @@ class BeamerDelegate extends RouterDelegate this.updateListenable, @Deprecated( 'No longer used by this package, please remove any references to it. ' - 'This feature was deprecated after v1.0.0.', - ) - this.preferUpdate = true, + 'This feature was deprecated after v1.0.0.', + ) this.preferUpdate = true, this.removeDuplicateHistory = true, this.notFoundPage = BeamPage.notFound, this.notFoundRedirect, @@ -188,7 +187,7 @@ class BeamerDelegate extends RouterDelegate @Deprecated( 'No longer used by this package, please remove any references to it. ' - 'This feature was deprecated after v1.0.0.', + 'This feature was deprecated after v1.0.0.', ) // ignore: public_member_api_docs final bool preferUpdate; @@ -241,7 +240,7 @@ class BeamerDelegate extends RouterDelegate /// /// See [build] for details on how beamer handles [Navigator.onPopPage]. bool Function(BuildContext context, Route route, dynamic result)? - onPopPage; + onPopPage; /// Whether the title attribute of [BeamPage] should /// be used to set and update the browser tab title. @@ -496,16 +495,15 @@ class BeamerDelegate extends RouterDelegate /// /// See [update] for more details. /// {@endtemplate} - void beamTo( - BeamStack stack, { - Object? data, - BeamStack? popTo, - TransitionDelegate? transitionDelegate, - bool beamBackOnPop = false, - bool popBeamStackOnPop = false, - bool stacked = true, - bool replaceRouteInformation = false, - }) { + void beamTo(BeamStack stack, { + Object? data, + BeamStack? popTo, + TransitionDelegate? transitionDelegate, + bool beamBackOnPop = false, + bool popBeamStackOnPop = false, + bool stacked = true, + bool replaceRouteInformation = false, + }) { _beamStackCandidate = stack; update( configuration: stack.state.routeInformation, @@ -524,15 +522,14 @@ class BeamerDelegate extends RouterDelegate /// The same as [beamTo], but replaces the last state in history, /// i.e. removes it from the `beamingHistory.last.history` and then does [beamTo]. - void beamToReplacement( - BeamStack stack, { - Object? data, - BeamStack? popTo, - TransitionDelegate? transitionDelegate, - bool beamBackOnPop = false, - bool popBeamStackOnPop = false, - bool stacked = true, - }) { + void beamToReplacement(BeamStack stack, { + Object? data, + BeamStack? popTo, + TransitionDelegate? transitionDelegate, + bool beamBackOnPop = false, + bool popBeamStackOnPop = false, + bool stacked = true, + }) { removeLastHistoryElement(); beamTo( stack, @@ -560,17 +557,16 @@ class BeamerDelegate extends RouterDelegate /// /// See [update] for more details. /// {@endtemplate} - void beamToNamed( - String uri, { - Object? routeState, - Object? data, - String? popToNamed, - TransitionDelegate? transitionDelegate, - bool beamBackOnPop = false, - bool popBeamStackOnPop = false, - bool stacked = true, - bool replaceRouteInformation = false, - }) { + void beamToNamed(String uri, { + Object? routeState, + Object? data, + String? popToNamed, + TransitionDelegate? transitionDelegate, + bool beamBackOnPop = false, + bool popBeamStackOnPop = false, + bool stacked = true, + bool replaceRouteInformation = false, + }) { update( configuration: RouteInformation(uri: Uri.parse(uri), state: routeState), beamParameters: _currentBeamParameters.copyWith( @@ -589,16 +585,15 @@ class BeamerDelegate extends RouterDelegate /// The same as [beamToNamed], but replaces the last state in history, /// i.e. removes it from the `beamingHistory.last.history` and then does [beamToNamed]. - void beamToReplacementNamed( - String uri, { - Object? routeState, - Object? data, - String? popToNamed, - TransitionDelegate? transitionDelegate, - bool beamBackOnPop = false, - bool popBeamStackOnPop = false, - bool stacked = true, - }) { + void beamToReplacementNamed(String uri, { + Object? routeState, + Object? data, + String? popToNamed, + TransitionDelegate? transitionDelegate, + bool beamBackOnPop = false, + bool popBeamStackOnPop = false, + bool stacked = true, + }) { removeLastHistoryElement(); beamToNamed( uri, @@ -620,16 +615,15 @@ class BeamerDelegate extends RouterDelegate /// /// See [beamToNamed] for more details. /// {@endtemplate} - void popToNamed( - String uri, { - Object? routeState, - Object? data, - String? popToNamed, - bool beamBackOnPop = false, - bool popBeamStackOnPop = false, - bool stacked = true, - bool replaceRouteInformation = false, - }) { + void popToNamed(String uri, { + Object? routeState, + Object? data, + String? popToNamed, + bool beamBackOnPop = false, + bool popBeamStackOnPop = false, + bool stacked = true, + bool replaceRouteInformation = false, + }) { while (beamingHistory.isNotEmpty) { final index = beamingHistory.last.history.lastIndexWhere( (element) => element.routeInformation.uri == Uri.parse(uri), @@ -888,7 +882,8 @@ class BeamerDelegate extends RouterDelegate _disposeBeamStack(currentBeamStack); if (removeDuplicateHistory) { final index = beamingHistory.indexWhere( - (historyStack) => historyStack.runtimeType == beamStack.runtimeType); + (historyStack) => + historyStack.runtimeType == beamStack.runtimeType); if (index != -1) { _disposeBeamStack(beamingHistory[index]); beamingHistory.removeAt(index); @@ -986,7 +981,10 @@ class BeamerDelegate extends RouterDelegate } else { _currentPages = _currentBeamParameters.stacked ? currentBeamStack.buildPages(context, currentBeamStack.state) - : [currentBeamStack.buildPages(context, currentBeamStack.state).last]; + : [currentBeamStack + .buildPages(context, currentBeamStack.state) + .last + ]; } } From 34c68799b4779ca43d7307a3fe13b015c36359de Mon Sep 17 00:00:00 2001 From: fabianbrandscheid <38261693+fabianbrandscheid@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:18:34 +0200 Subject: [PATCH 4/8] Add files via upload format --- package/lib/src/beamer_delegate.dart | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/package/lib/src/beamer_delegate.dart b/package/lib/src/beamer_delegate.dart index 174026a..63834a9 100644 --- a/package/lib/src/beamer_delegate.dart +++ b/package/lib/src/beamer_delegate.dart @@ -1,7 +1,7 @@ import 'package:beamer/beamer.dart'; import 'package:beamer/src/browser_tab_title_util_non_web.dart' - if (dart.library.html) 'package:beamer/src/browser_tab_title_util_web.dart' - as browser_tab_title_util; + if (dart.library.html) 'package:beamer/src/browser_tab_title_util_web.dart' + as browser_tab_title_util; import 'package:beamer/src/utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -24,7 +24,8 @@ class BeamerDelegate extends RouterDelegate @Deprecated( 'No longer used by this package, please remove any references to it. ' 'This feature was deprecated after v1.0.0.', - ) this.preferUpdate = true, + ) + this.preferUpdate = true, this.removeDuplicateHistory = true, this.notFoundPage = BeamPage.notFound, this.notFoundRedirect, @@ -240,7 +241,7 @@ class BeamerDelegate extends RouterDelegate /// /// See [build] for details on how beamer handles [Navigator.onPopPage]. bool Function(BuildContext context, Route route, dynamic result)? - onPopPage; + onPopPage; /// Whether the title attribute of [BeamPage] should /// be used to set and update the browser tab title. @@ -495,7 +496,8 @@ class BeamerDelegate extends RouterDelegate /// /// See [update] for more details. /// {@endtemplate} - void beamTo(BeamStack stack, { + void beamTo( + BeamStack stack, { Object? data, BeamStack? popTo, TransitionDelegate? transitionDelegate, @@ -557,7 +559,8 @@ class BeamerDelegate extends RouterDelegate /// /// See [update] for more details. /// {@endtemplate} - void beamToNamed(String uri, { + void beamToNamed( + String uri, { Object? routeState, Object? data, String? popToNamed, @@ -585,7 +588,8 @@ class BeamerDelegate extends RouterDelegate /// The same as [beamToNamed], but replaces the last state in history, /// i.e. removes it from the `beamingHistory.last.history` and then does [beamToNamed]. - void beamToReplacementNamed(String uri, { + void beamToReplacementNamed( + String uri, { Object? routeState, Object? data, String? popToNamed, @@ -615,7 +619,8 @@ class BeamerDelegate extends RouterDelegate /// /// See [beamToNamed] for more details. /// {@endtemplate} - void popToNamed(String uri, { + void popToNamed( + String uri, { Object? routeState, Object? data, String? popToNamed, @@ -740,7 +745,7 @@ class BeamerDelegate extends RouterDelegate @override RouteInformation? get currentConfiguration { final response = - _parent == null && _initialConfigurationReady ? configuration : null; + _parent == null && _initialConfigurationReady ? configuration : null; if (response != null) { _lastReportedRouteInformation = response.copyWith(); } @@ -981,9 +986,7 @@ class BeamerDelegate extends RouterDelegate } else { _currentPages = _currentBeamParameters.stacked ? currentBeamStack.buildPages(context, currentBeamStack.state) - : [currentBeamStack - .buildPages(context, currentBeamStack.state) - .last + : [currentBeamStack.buildPages(context, currentBeamStack.state).last ]; } } From 0941ad24073a4a8dcc825e5fabda9b0f07938391 Mon Sep 17 00:00:00 2001 From: fabianbrandscheid <38261693+fabianbrandscheid@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:21:16 +0200 Subject: [PATCH 5/8] Add files via upload format --- package/lib/beamer_delegate.dart | 1144 ++++++++++++++++++++++++++++++ 1 file changed, 1144 insertions(+) create mode 100644 package/lib/beamer_delegate.dart diff --git a/package/lib/beamer_delegate.dart b/package/lib/beamer_delegate.dart new file mode 100644 index 0000000..0b48d2b --- /dev/null +++ b/package/lib/beamer_delegate.dart @@ -0,0 +1,1144 @@ +import 'package:beamer/beamer.dart'; +import 'package:beamer/src/browser_tab_title_util_non_web.dart' + if (dart.library.html) 'package:beamer/src/browser_tab_title_util_web.dart' + as browser_tab_title_util; +import 'package:beamer/src/utils.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +/// A delegate that is used by the [Router] to build the [Navigator]. +/// +/// This is "the beamer", the one that does the actual beaming. +class BeamerDelegate extends RouterDelegate + with ChangeNotifier, PopNavigatorRouterDelegateMixin { + /// Creates a [BeamerDelegate] with specified properties. + /// + /// [stackBuilder] is required to process the incoming navigation request. + BeamerDelegate({ + required this.stackBuilder, + this.initialPath = '/', + this.routeListener, + this.buildListener, + this.updateListenable, + @Deprecated( + 'No longer used by this package, please remove any references to it. ' + 'This feature was deprecated after v1.0.0.', + ) + this.preferUpdate = true, + this.removeDuplicateHistory = true, + this.notFoundPage = BeamPage.notFound, + this.notFoundRedirect, + this.notFoundRedirectNamed, + this.guards = const [], + this.navigatorObservers = const [], + this.transitionDelegate = const DefaultTransitionDelegate(), + this.beamBackTransitionDelegate = const ReverseTransitionDelegate(), + this.onPopPage, + this.setBrowserTabTitle = true, + this.initializeFromParent = true, + this.updateFromParent = true, + this.updateParent = true, + this.clearBeamingHistoryOn = const {}, + }) { + _currentBeamParameters = BeamParameters( + transitionDelegate: transitionDelegate, + ); + + configuration = RouteInformation(uri: Uri.parse(initialPath)); + + updateListenable?.addListener(_update); + } + + /// A state of this delegate. This is the `routeInformation` that goes into + /// [stackBuilder] to build an appropriate [BeamStack]. + /// + /// A way to modify this state is via [update]. + late RouteInformation configuration; + + BeamerDelegate? _parent; + + final Set _children = {}; + + /// Takes priority over all other siblings, + /// i.e. sets itself as active and all other siblings as inactive. + void takePriority() { + if (_parent?._children.isNotEmpty ?? false) { + for (var child in _parent!._children) { + child.active = false; + } + } + active = true; + } + + /// A delegate of a parent of the [Beamer] that has this delegate. + /// + /// This is not null only if multiple [Beamer]s are used; + /// `*App.router` and at least one more [Beamer] in the Widget tree. + BeamerDelegate? get parent => _parent; + + set parent(BeamerDelegate? parent) { + if (parent == null && _parent != null) { + _parent!.removeListener(_updateFromParent); + _parent!._children.remove(this); + _parent = null; + return; + } + if (_parent == parent) { + return; + } + _parent = parent; + _parent!._children.add(this); + _initializeChild(); + if (updateFromParent) { + _parent!.addListener(_updateFromParent); + } + } + + /// The top-most [BeamerDelegate], a parent of all. + /// + /// It will return root even when called on root. + BeamerDelegate get root { + if (_parent == null) { + return this; + } + var root = _parent!; + while (root._parent != null) { + root = root._parent!; + } + return root; + } + + /// A builder for [BeamStack]s. + /// + /// There are 3 ways of building an appropriate [BeamStack] which will in + /// turn build a stack of pages that should go into [Navigator.pages]. + /// + /// 1. Custom closure + /// ```dart + /// stackBuilder: (state) { + /// if (state.uri.pathSegments.contains('l1')) { + /// return Stack1(state); + /// } + /// if (state.uri.pathSegments.contains('l2')) { + /// return Stack2(state); + /// } + /// return NotFound(path: state.uri.toString()); + /// }, + /// ``` + /// + /// 2. [BeamerStackBuilder]; chooses appropriate [BeamStack] itself + /// ```dart + /// stackBuilder: BeamerStackBuilder( + /// beamStacks: [ + /// Stack1(), + /// Stack2(), + /// ], + /// ), + /// ``` + /// + /// 3. [RoutesStackBuilder]; a Map of routes + /// ```dart + /// stackBuilder: RoutesStackBuilder( + /// routes: { + /// '/': (context, state) => HomeScreen(), + /// '/another': (context, state) => AnotherScreen(), + /// }, + /// ), + /// ``` + final StackBuilder stackBuilder; + + /// The path to replace `/` as default initial route path upon load. + /// + /// Note that (if set to anything other than `/` (default)), + /// you will not be able to navigate to `/` by manually typing + /// it in the URL bar, because it will always be transformed to `initialPath`, + /// but you will be able to get to `/` by popping pages with back button, + /// if there are pages in [BeamStack.buildPages] that will build + /// when there are no path segments. + final String initialPath; + + /// An initial deep-link that should be stored until used. + /// + /// This can be used in [BeamGuard.beamToNamed] to redirect properly after + /// some auth flows that need to be executed, during which the [initialPath] + /// information is lost. + String? _deepLink; + + /// Sets the deep-link route path that + /// - will be used as [initialPath] + /// - can be used in [BeamGuard.beamToNamed] to redirect properly + /// after some auth flows that need to be executed, + /// during which the [initialPath] information is lost. + /// + /// Once a [BeamGuard] uses the deep-link, it will be reset to null. + void setDeepLink(String? deepLink) => _deepLink = deepLink; + + /// The routeListener will be called on every navigation event + /// and will receive the [configuration] and a reference to this delegate. + final void Function(RouteInformation, BeamerDelegate)? routeListener; + + /// The buildListener will be called every time after the [currentPages] + /// are updated. it receives a reference to this delegate. + final void Function(BuildContext, BeamerDelegate)? buildListener; + + /// A Listenable to which an update listener will be added, i.e. + /// [update] will be called when listeners are notified. + final Listenable? updateListenable; + + @Deprecated( + 'No longer used by this package, please remove any references to it. ' + 'This feature was deprecated after v1.0.0.', + ) + // ignore: public_member_api_docs + final bool preferUpdate; + + /// Whether to remove [BeamStack]s from [beamingHistory] + /// if they are the same type as the stack being beamed to. + /// + /// See how this is used at [_addToBeamingHistory] implementation. + final bool removeDuplicateHistory; + + /// Page to show when no [BeamStack] supports the incoming URI. + late BeamPage notFoundPage; + + /// [BeamStack] to redirect to when no [BeamStack] supports the incoming URI. + final BeamStack? notFoundRedirect; + + /// URI string to redirect to when no [BeamStack] supports the incoming URI. + final String? notFoundRedirectNamed; + + /// Guards that will be executing [BeamGuard.check] on [currentBeamStack] + /// candidate. + /// + /// Checks will be executed in order; chain of responsibility pattern. + /// When some guard returns `false`, stack candidate will not be accepted + /// and stack of pages will be updated as is configured in [BeamGuard]. + final List guards; + + /// The list of observers for the [Navigator]. + final List navigatorObservers; + + /// A transition delegate to be used by [Navigator]. + /// + /// This transition delegate will be overridden by the one in [BeamStack], + /// if any is set. + /// + /// See [Navigator.transitionDelegate]. + final TransitionDelegate transitionDelegate; + + /// A transition delegate to be used by [Navigator] when beaming back. + /// + /// When calling [beamBack], it's useful to animate routes in reverse order; + /// adding the new ones behind and then popping the current ones, + /// therefore, the default is [ReverseTransitionDelegate]. + final TransitionDelegate beamBackTransitionDelegate; + + /// Callback when `pop` is requested. + /// + /// Return `true` if pop will be handled entirely by this function. + /// Return `false` if beamer should finish handling the pop. + /// + /// See [build] for details on how beamer handles [Navigator.onPopPage]. + bool Function(BuildContext context, Route route, dynamic result)? + onPopPage; + + /// Whether the title attribute of [BeamPage] should + /// be used to set and update the browser tab title. + final bool setBrowserTabTitle; + + /// Whether to take [configuration] from parent when this is created. + /// + /// If false, the [initialPath] will be used. + final bool initializeFromParent; + + /// Whether to call [update] when parent notifies listeners. + /// + /// This means that navigation can be done either on parent or on this + final bool updateFromParent; + + /// Whether to call [update] on [parent] when this instance's [configuration] + /// is updated. + /// + /// This means that parent's [beamingHistory] will be in sync. + final bool updateParent; + + /// Whether to remove all entries from [beamingHistory] (and their nested + /// [BeamStack.history]) when a route belonging to this set is reached, + /// regardless of how it was reached. + /// + /// Note that [popToNamed] will also try to clear as much [beamingHistory] + /// as possible, even when this is empty. + final Set clearBeamingHistoryOn; + + final GlobalKey _navigatorKey = GlobalKey(); + + /// {@template beamingHistory} + /// The history of [BeamStack]s, each holding its [BeamStack.history]. + /// + /// See [_addToBeamingHistory]. + /// {@endtemplate} + final List beamingHistory = []; + + /// Returns the complete length of beaming history, that is the sum of all + /// history lengths for each [BeamStack] in [beamingHistory]. + int get beamingHistoryCompleteLength { + var length = 0; + for (var stack in beamingHistory) { + length += stack.history.length; + } + return length; + } + + /// A candidate to become the next [currentBeamStack]. + BeamStack _beamStackCandidate = EmptyBeamStack(); + + /// {@template currentBeamStack} + /// A [BeamStack] that is currently responsible for providing a page stack + /// via [BeamStack.buildPages] and holds the current [BeamState]. + /// + /// Usually obtained via + /// ```dart + /// Beamer.of(context).currentBeamStack + /// ``` + /// {@endtemplate} + BeamStack get currentBeamStack => + beamingHistory.isEmpty ? EmptyBeamStack() : beamingHistory.last; + + List _currentPages = []; + + /// {@template currentPages} + /// [currentBeamStack]'s "effective" pages, the ones that were built. + /// {@endtemplate} + List get currentPages => _currentPages; + + /// Describes the current parameters for beaming, such as + /// pop configuration, beam back on pop, etc. + late BeamParameters _currentBeamParameters; + + /// If `false`, does not report the route until next [update]. + /// + /// Useful when having sibling beamers that are both build at the same time. + /// Becomes active on next [update]. + bool active = true; + + /// The [Navigator] that belongs to this [BeamerDelegate]. + /// + /// Useful for popping dialogs without accessing [BuildContext]: + /// + /// ```dart + /// beamerDelegate.navigator.pop(); + /// ``` + NavigatorState get navigator => _navigatorKey.currentState!; + + /// A context for guards + BuildContext? _context; + + /// Whether guards already ran. + /// + /// This is used to decide whether guards need to be run in the [build]. + /// They will run just in [update] everytime, except on first build, + /// when the `Navigator` does not exist so we don't have `context`. + bool _didRunGuards = false; + + /// Is [build] in progress. + /// Used to determine not to [notifyListeners]. + bool _buildInProgress = false; + + /// Whether the [configuration] is ready to be consumed by platform, + /// most importantly - the browser URL bar. + /// + /// This is important for first build, i.e. [setInitialRoutePath], + /// to avoid setting URL when the guards have not been run yet. + bool _initialConfigurationReady = false; + + /// Main method to update the [configuration] of this delegate and its + /// [currentBeamStack]. + /// + /// This "top-level" [update] is generally used for navigation + /// _between_ [BeamStack]s and not _within_ a specific [BeamStack]. + /// For latter purpose, see [BeamStack.update]. + /// Nevertheless, [update] **will** work for navigation within [BeamStack]. + /// + /// Calling [update] will run the [stackBuilder]. + /// + /// ```dart + /// Beamer.of(context).update( + /// state: BeamState.fromUriString('/xx'), + /// ); + /// ``` + /// + /// **all of the beaming functions call [update] to really do the update.** + /// + /// [beamParameters] hold various parameters such as `transitionDelegate` and + /// other that will be used for this update **and** stored in history. + /// + /// [data] can be used to hold arbitrary [Object] throughout navigation. + /// See [BeamStack.data] for more information. + /// + /// [buildBeamStack] determines whether a [BeamStack] should be created + /// from [configuration], using the [stackBuilder]. For example, when using + /// [beamTo] and passing an already created [BeamStack], this can be false. + /// + /// [rebuild] determines whether to call [notifyListeners]. This can be false + /// if we already have built the UI and just want to notify the platform, + /// e.g. browser, of the new [RouteInformation]. + /// + /// [updateParent] is used in nested navigation to call [update] on [parent]. + /// + /// [updateRouteInformation] determines whether to update the browser's URL. + /// This is usually done through `notifyListeners`, but in specific cases, + /// e.g. when [rebuild] is false, browser would not be updated. + /// This is mainly used to not update the route information from parent, + /// when it has already been done by this delegate. + void update({ + RouteInformation? configuration, + BeamParameters? beamParameters, + Object? data, + bool buildBeamStack = true, + bool rebuild = true, + bool updateParent = true, + bool updateRouteInformation = true, + bool replaceRouteInformation = false, + bool takePriority = true, + }) { + if (takePriority) { + this.takePriority(); + } + + if (_buildInProgress) { + rebuild = false; + } + + replaceRouteInformation + ? SystemNavigator.selectSingleEntryHistory() + : SystemNavigator.selectMultiEntryHistory(); + + this.configuration = configuration != null + ? Utils.createNewConfiguration(this.configuration, configuration) + : currentBeamStack.state.routeInformation.copyWith(); + + // update beam parameters + _currentBeamParameters = beamParameters ?? _currentBeamParameters; + + if (buildBeamStack) { + // build a BeamStack from configuration + _beamStackCandidate = stackBuilder( + this.configuration.copyWith(), + _currentBeamParameters, + ); + } + + // run guards on _beamStackCandidate + final context = _context; + if (context != null) { + if (configuration != null) { + _setBrowserTitle(context, + fixTitle: configuration.uri.pathSegments.last); + } + final didApply = _runGuards(context, _beamStackCandidate); + _didRunGuards = true; + if (didApply) { + return; + } else { + // TODO revert configuration if guard just blocked navigation + } + } + + // adds the candidate to history + // it will become currentBeamStack after this step + _updateBeamingHistory(_beamStackCandidate); + if (data != null) { + currentBeamStack.data = data; + } + + routeListener?.call(this.configuration, this); + + // update parent if necessary + if (this.updateParent && updateParent) { + _parent?.update( + configuration: this.configuration.copyWith(), + rebuild: false, + updateRouteInformation: false, + ); + } + + // update browser history + // if this is from nested Beamer + // or + // if rebuild was false (browser will not be notified implicitly) + if (updateRouteInformation && active && (_parent != null || !rebuild)) { + this.updateRouteInformation(this.configuration); + } + + // initiate build + if (rebuild) { + notifyListeners(); + } + } + + /// {@template beamTo} + /// Beams to a specific, manually configured [BeamStack]. + /// + /// For example + /// ```dart + /// Beamer.of(context).beamTo( + /// Stack2( + /// BeamState( + /// pathBlueprintSegments = ['user',':userId','transactions'], + /// pathParameters = {'userId': '1'}, + /// queryParameters = {'perPage': '10'}, + /// data = {'favoriteUser': true}, + /// ), + /// ), + /// ); + /// ``` + /// + /// See [update] for more details. + /// {@endtemplate} + void beamTo( + BeamStack stack, { + Object? data, + BeamStack? popTo, + TransitionDelegate? transitionDelegate, + bool beamBackOnPop = false, + bool popBeamStackOnPop = false, + bool stacked = true, + bool replaceRouteInformation = false, + }) { + _beamStackCandidate = stack; + update( + configuration: stack.state.routeInformation, + beamParameters: _currentBeamParameters.copyWith( + popConfiguration: popTo?.state.routeInformation, + transitionDelegate: transitionDelegate ?? this.transitionDelegate, + beamBackOnPop: beamBackOnPop, + popBeamStackOnPop: popBeamStackOnPop, + stacked: stacked, + ), + data: data, + buildBeamStack: false, + replaceRouteInformation: replaceRouteInformation, + ); + } + + /// The same as [beamTo], but replaces the last state in history, + /// i.e. removes it from the `beamingHistory.last.history` and then does [beamTo]. + void beamToReplacement( + BeamStack stack, { + Object? data, + BeamStack? popTo, + TransitionDelegate? transitionDelegate, + bool beamBackOnPop = false, + bool popBeamStackOnPop = false, + bool stacked = true, + }) { + removeLastHistoryElement(); + beamTo( + stack, + data: data, + popTo: popTo, + transitionDelegate: transitionDelegate, + beamBackOnPop: beamBackOnPop, + popBeamStackOnPop: popBeamStackOnPop, + stacked: stacked, + replaceRouteInformation: true, + ); + } + + /// {@template beamToNamed} + /// Configures and beams to a [BeamStack] that supports uri within its + /// [BeamStack.pathPatterns]. + /// + /// For example + /// ```dart + /// Beamer.of(context).beamToNamed( + /// '/user/1/transactions?perPage=10', + /// data: {'favoriteUser': true},, + /// ); + /// ``` + /// + /// See [update] for more details. + /// {@endtemplate} + void beamToNamed( + String uri, { + Object? routeState, + Object? data, + String? popToNamed, + TransitionDelegate? transitionDelegate, + bool beamBackOnPop = false, + bool popBeamStackOnPop = false, + bool stacked = true, + bool replaceRouteInformation = false, + }) { + update( + configuration: RouteInformation(uri: Uri.parse(uri), state: routeState), + beamParameters: _currentBeamParameters.copyWith( + popConfiguration: popToNamed != null + ? RouteInformation(uri: Uri.parse(popToNamed)) + : null, + transitionDelegate: transitionDelegate ?? this.transitionDelegate, + beamBackOnPop: beamBackOnPop, + popBeamStackOnPop: popBeamStackOnPop, + stacked: stacked, + ), + data: data, + replaceRouteInformation: replaceRouteInformation, + ); + } + + /// The same as [beamToNamed], but replaces the last state in history, + /// i.e. removes it from the `beamingHistory.last.history` and then does [beamToNamed]. + void beamToReplacementNamed( + String uri, { + Object? routeState, + Object? data, + String? popToNamed, + TransitionDelegate? transitionDelegate, + bool beamBackOnPop = false, + bool popBeamStackOnPop = false, + bool stacked = true, + }) { + removeLastHistoryElement(); + beamToNamed( + uri, + routeState: routeState, + data: data, + popToNamed: popToNamed, + transitionDelegate: transitionDelegate, + beamBackOnPop: beamBackOnPop, + popBeamStackOnPop: popBeamStackOnPop, + stacked: stacked, + replaceRouteInformation: true, + ); + } + + /// {@template popToNamed} + /// Calls [beamToNamed] with a [ReverseTransitionDelegate] and tries to + /// remove everything from history after entry corresponding to `uri`, as + /// if doing a pop way back to that state, if it exists in history. + /// + /// See [beamToNamed] for more details. + /// {@endtemplate} + void popToNamed( + String uri, { + Object? routeState, + Object? data, + String? popToNamed, + bool beamBackOnPop = false, + bool popBeamStackOnPop = false, + bool stacked = true, + bool replaceRouteInformation = false, + }) { + while (beamingHistory.isNotEmpty) { + final index = beamingHistory.last.history.lastIndexWhere( + (element) => element.routeInformation.uri == Uri.parse(uri), + ); + if (index == -1) { + _disposeBeamStack(beamingHistory.last); + beamingHistory.removeLast(); + continue; + } else { + beamingHistory.last.history + .removeRange(index, beamingHistory.last.history.length); + break; + } + } + beamToNamed( + uri, + routeState: routeState, + data: data, + popToNamed: popToNamed, + transitionDelegate: const ReverseTransitionDelegate(), + beamBackOnPop: beamBackOnPop, + popBeamStackOnPop: popBeamStackOnPop, + stacked: stacked, + replaceRouteInformation: replaceRouteInformation, + ); + } + + /// {@template canBeamBack} + /// Whether it is possible to [beamBack], + /// i.e. there is more than 1 entry in [beamingHistoryCompleteLength]. + /// {@endtemplate} + bool get canBeamBack => beamingHistoryCompleteLength > 1; + + /// {@template beamBack} + /// Beams to previous entry in [beamingHistory]. + /// and **removes** the last entry from history. + /// + /// If there is no previous entry, does nothing. + /// + /// Returns the success, whether [update] was executed. + /// {@endtemplate} + bool beamBack({ + Object? data, + bool replaceRouteInformation = false, + }) { + if (!canBeamBack) { + return false; + } + // first we try to beam back within last BeamStack + // i.e. we just pop its last history element id possible + if (beamingHistory.last.history.length > 1) { + beamingHistory.last.history.removeLast(); + } else { + // here we know that beamingHistory.length > 1 (because of canBeamBack) + // and that beamingHistory.last.history.length == 1 + // so this last (only) entry is removed along with BeamStack + _disposeBeamStack(beamingHistory.last); + beamingHistory.removeLast(); + _initBeamStack(beamingHistory.last); + } + + _beamStackCandidate = beamingHistory.last; + _beamStackCandidate.update(); + final targetHistoryElement = _beamStackCandidate.history.last; + + update( + configuration: targetHistoryElement.routeInformation.copyWith(), + beamParameters: targetHistoryElement.parameters.copyWith( + transitionDelegate: beamBackTransitionDelegate, + ), + data: data, + buildBeamStack: false, + replaceRouteInformation: replaceRouteInformation, + ); + + return true; + } + + /// {@template canPopBeamStack} + /// Whether it is possible to [popBeamStack], + /// i.e. there is more than 1 stack in [beamingHistory]. + /// {@endtemplate} + bool get canPopBeamStack => beamingHistory.length > 1; + + /// {@template popBeamStack} + /// Beams to previous stack in [beamingHistory] + /// and **removes** the last stack from history. + /// + /// If there is no previous stack, does nothing. + /// + /// Returns the success, whether the [currentBeamStack] was changed. + /// {@endtemplate} + bool popBeamStack({ + Object? data, + bool replaceRouteInformation = false, + }) { + if (!canPopBeamStack) { + return false; + } + _disposeBeamStack(currentBeamStack); + beamingHistory.removeLast(); + _beamStackCandidate = beamingHistory.last; + update( + beamParameters: currentBeamStack.history.last.parameters.copyWith( + transitionDelegate: beamBackTransitionDelegate, + ), + data: data, + buildBeamStack: false, + replaceRouteInformation: replaceRouteInformation, + ); + return true; + } + + @override + RouteInformation? get currentConfiguration { + final response = + _parent == null && _initialConfigurationReady ? configuration : null; + if (response != null) { + _lastReportedRouteInformation = response.copyWith(); + } + return response; + } + + @override + GlobalKey get navigatorKey => _navigatorKey; + + @override + Widget build(BuildContext context) { + _buildInProgress = true; + _context = context; + + if (!_didRunGuards) { + _runGuards(_context!, _beamStackCandidate); + _addToBeamingHistory(_beamStackCandidate); + } + if (!_initialConfigurationReady && active && parent != null) { + updateRouteInformation(configuration); + } + _initialConfigurationReady = true; + + if (currentBeamStack is NotFound) { + _handleNotFoundRedirect(); + } + + if (!currentBeamStack.mounted) { + currentBeamStack.buildInit(context); + } + + final navigator = Builder( + builder: (context) { + _setCurrentPages(context); + _setBrowserTitle(context); + + buildListener?.call(context, this); + return Navigator( + key: navigatorKey, + observers: navigatorObservers, + transitionDelegate: currentBeamStack.transitionDelegate ?? + _currentBeamParameters.transitionDelegate, + pages: _currentPages, + onPopPage: (route, result) => _onPopPage(context, route, result), + ); + }, + ); + + _buildInProgress = false; + return currentBeamStack.builder(context, navigator); + } + + @override + SynchronousFuture setInitialRoutePath(RouteInformation configuration) { + _initialConfigurationReady = false; + final uri = configuration.uri; + if (currentBeamStack is! EmptyBeamStack) { + configuration = currentBeamStack.state.routeInformation; + } else if (uri.path == '/') { + configuration = RouteInformation( + uri: uri.replace( + path: initialPath, + query: uri.hasQuery ? uri.query : null, + ), + ); + } + _deepLink ??= configuration.uri.toString(); + return setNewRoutePath(configuration); + } + + @override + SynchronousFuture setNewRoutePath(RouteInformation configuration) { + if (configuration.uri.path == '/' && initialPath != '/') { + configuration = configuration.copyWith(uri: Uri.parse(initialPath)); + } + update(configuration: configuration); + return SynchronousFuture(null); + } + + RouteInformation? _lastReportedRouteInformation; + + /// Pass this call to [root] which notifies the platform for a [configuration] + /// change. + /// + /// On Web, creates a new browser history entry and updates URL. + /// + /// See [SystemNavigator.routeInformationUpdated]. + void updateRouteInformation(RouteInformation routeInformation) { + if (_parent == null) { + if (!routeInformation.isEqualTo(_lastReportedRouteInformation)) { + SystemNavigator.routeInformationUpdated( + uri: routeInformation.uri, + state: routeInformation.state, + ); + _lastReportedRouteInformation = routeInformation.copyWith(); + } + } else { + if (updateParent) { + _parent!.configuration = routeInformation.copyWith(); + } + _parent!.updateRouteInformation(routeInformation); + } + } + + bool _runGuards(BuildContext context, BeamStack targetBeamStack) { + final allGuards = (parent?.guards ?? []) + guards + targetBeamStack.guards; + for (final guard in allGuards) { + if (guard.shouldGuard(targetBeamStack)) { + final wasApplied = guard.apply( + context, + this, + currentBeamStack, + _currentPages, + targetBeamStack, + _deepLink, + ); + + // Return true on the first guard that was fully applied + if (wasApplied) { + return true; + } + } + } + return false; + } + + void _initBeamStack(BeamStack beamStack) { + beamStack.initState(); + beamStack.onUpdate(); + beamStack.addListener(_updateFromCurrentBeamStack); + } + + void _disposeBeamStack(BeamStack beamStack) { + beamStack.removeListener(_updateFromCurrentBeamStack); + beamStack.disposeState(); + } + + void _addToBeamingHistory(BeamStack beamStack) { + _disposeBeamStack(currentBeamStack); + if (removeDuplicateHistory) { + final index = beamingHistory.indexWhere( + (historyStack) => historyStack.runtimeType == beamStack.runtimeType); + if (index != -1) { + _disposeBeamStack(beamingHistory[index]); + beamingHistory.removeAt(index); + } + } + _initBeamStack(beamStack); + beamingHistory.add(beamStack); + } + + void _updateBeamingHistory(BeamStack beamStack) { + if (beamingHistory.isEmpty || + beamStack.runtimeType != beamingHistory.last.runtimeType) { + _addToBeamingHistory(beamStack); + } else { + beamingHistory.last.update( + null, + configuration.copyWith(), + _currentBeamParameters, + false, + false, + ); + } + + if (clearBeamingHistoryOn.contains(configuration.uri.path)) { + _clearBeamingHistory(); + } + } + + void _clearBeamingHistory() { + while (beamingHistoryCompleteLength > 1) { + removeFirstHistoryElement(); + } + } + + /// Removes the first element from [beamingHistory] and returns it. + /// + /// If there is none, returns `null`. + HistoryElement? removeFirstHistoryElement() { + if (beamingHistoryCompleteLength == 0) { + return null; + } + if (updateParent) { + _parent?.removeFirstHistoryElement(); + } + final firstBeamStack = beamingHistory.first; + final firstHistoryElement = firstBeamStack.removeFirstFromHistory(); + if (firstBeamStack.history.isEmpty) { + _disposeBeamStack(firstBeamStack); + beamingHistory.removeAt(0); + } + + return firstHistoryElement; + } + + /// Removes the last element from [beamingHistory] and returns it. + /// + /// If there is none, returns `null`. + HistoryElement? removeLastHistoryElement() { + if (beamingHistoryCompleteLength == 0) { + return null; + } + if (updateParent) { + _parent?.removeLastHistoryElement(); + } + final lastHistoryElement = beamingHistory.last.removeLastFromHistory(); + if (beamingHistory.last.history.isEmpty) { + _disposeBeamStack(beamingHistory.last); + beamingHistory.removeLast(); + } else { + beamingHistory.last.update(null, null, null, false); + } + + return lastHistoryElement; + } + + void _handleNotFoundRedirect() { + if (notFoundRedirect == null && notFoundRedirectNamed == null) { + // do nothing, pass on NotFound + return; + } + if (notFoundRedirect != null) { + _beamStackCandidate = notFoundRedirect!; + } else if (notFoundRedirectNamed != null) { + _beamStackCandidate = stackBuilder( + RouteInformation(uri: Uri.parse(notFoundRedirectNamed!)), + _currentBeamParameters.copyWith(), + ); + } + _updateFromBeamStackCandidate(); + } + + void _setCurrentPages(BuildContext context) { + if (currentBeamStack is NotFound) { + _currentPages = [notFoundPage]; + } else { + _currentPages = _currentBeamParameters.stacked + ? currentBeamStack.buildPages(context, currentBeamStack.state) + : [currentBeamStack.buildPages(context, currentBeamStack.state).last]; + } + } + + void _setBrowserTitle(BuildContext context, {String? fixTitle}) { + if (active && setBrowserTabTitle) { + final String title = _currentPages.last.title ?? + currentBeamStack.state.routeInformation.uri.path; + final String title = fixTitle ?? _currentPages.last.title ?? + currentBeamLocation.state.routeInformation.uri.path; + browser_tab_title_util.setTabTitle(title); + } + } + + bool _onPopPage(BuildContext context, Route route, dynamic result) { + if (route.willHandlePopInternally) { + if (!route.didPop(result)) { + return false; + } + } + + if (_currentBeamParameters.popConfiguration != null) { + update( + configuration: _currentBeamParameters.popConfiguration, + beamParameters: _currentBeamParameters.copyWith( + transitionDelegate: beamBackTransitionDelegate, + resetPopConfiguration: true, + ), + // replaceCurrent: true, + ); + } else if (_currentBeamParameters.popBeamStackOnPop) { + final didPopBeamStack = popBeamStack(); + if (!didPopBeamStack) { + return false; + } + } else if (_currentBeamParameters.beamBackOnPop) { + final didBeamBack = beamBack(); + if (!didBeamBack) { + return false; + } + } else { + final lastPage = _currentPages.last; + if (lastPage.popToNamed != null) { + popToNamed(lastPage.popToNamed!); + } else { + final shouldPop = lastPage.onPopPage( + context, + this, + currentBeamStack.state, + lastPage, + ); + if (!shouldPop) { + return false; + } + } + } + + return route.didPop(result); + } + + // When a nested Beamer gets into a Widget tree, it must initialize. + // It will try to take the configuration from parent, + // but act differently depending on whether it can handle that configuration. + void _initializeChild() { + final parentConfiguration = _parent!.configuration.copyWith(); + if (initializeFromParent) { + _beamStackCandidate = + stackBuilder(parentConfiguration, _currentBeamParameters); + } + + // If this couldn't handle parents configuration, + // it will update itself to initialPath and declare itself inactive. + if (_beamStackCandidate is EmptyBeamStack || + _beamStackCandidate is NotFound) { + update( + configuration: RouteInformation(uri: Uri.parse(initialPath)), + rebuild: false, + updateParent: false, + updateRouteInformation: false, + takePriority: false, + ); + active = false; + } else { + update( + configuration: parentConfiguration, + rebuild: false, + updateParent: false, + updateRouteInformation: false, + ); + } + } + + void _update() => update(); + + // Updates only if it can handle the configuration + void _updateFromParent({bool rebuild = true}) { + final parentConfiguration = _parent!.configuration.copyWith(); + final beamStack = stackBuilder(parentConfiguration, _currentBeamParameters); + + if (beamStack is! NotFound) { + update( + configuration: parentConfiguration, + rebuild: rebuild, + updateParent: false, + updateRouteInformation: false, + ); + } + } + + void _updateFromCurrentBeamStack({bool rebuild = true}) { + update( + configuration: currentBeamStack.state.routeInformation, + buildBeamStack: false, + rebuild: rebuild, + ); + } + + void _updateFromBeamStackCandidate({bool rebuild = false}) { + update( + configuration: _beamStackCandidate.state.routeInformation, + buildBeamStack: false, + rebuild: rebuild, + ); + } + + // This is a temporary implementation + // as there is a ?bug? when navigating with browser buttons + // that keeps creating new delegates **but** persisting the children List. + // The children list then grows unnecessary. + // + // Also tried with navigatorKey, but this is also newly created + // Using initialPath is not perfect, + // but should be good until I investigate further. + // + // These overrides are for the inserting into a Set of children + @override + bool operator ==(other) { + if (other is! BeamerDelegate) { + return false; + } + return initialPath == other.initialPath; + } + + @override + int get hashCode => initialPath.hashCode; + + @override + void dispose() { + _children.clear(); + _parent?.removeListener(_updateFromParent); + _disposeBeamStack(currentBeamStack); + currentBeamStack.dispose(); + updateListenable?.removeListener(_update); + super.dispose(); + } +} \ No newline at end of file From 5725030ee6df2b74c1d055d3ce7ce95298371296 Mon Sep 17 00:00:00 2001 From: fabianbrandscheid <38261693+fabianbrandscheid@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:24:58 +0200 Subject: [PATCH 6/8] Delete package/lib/beamer_delegate.dart --- package/lib/beamer_delegate.dart | 1144 ------------------------------ 1 file changed, 1144 deletions(-) delete mode 100644 package/lib/beamer_delegate.dart diff --git a/package/lib/beamer_delegate.dart b/package/lib/beamer_delegate.dart deleted file mode 100644 index 0b48d2b..0000000 --- a/package/lib/beamer_delegate.dart +++ /dev/null @@ -1,1144 +0,0 @@ -import 'package:beamer/beamer.dart'; -import 'package:beamer/src/browser_tab_title_util_non_web.dart' - if (dart.library.html) 'package:beamer/src/browser_tab_title_util_web.dart' - as browser_tab_title_util; -import 'package:beamer/src/utils.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -/// A delegate that is used by the [Router] to build the [Navigator]. -/// -/// This is "the beamer", the one that does the actual beaming. -class BeamerDelegate extends RouterDelegate - with ChangeNotifier, PopNavigatorRouterDelegateMixin { - /// Creates a [BeamerDelegate] with specified properties. - /// - /// [stackBuilder] is required to process the incoming navigation request. - BeamerDelegate({ - required this.stackBuilder, - this.initialPath = '/', - this.routeListener, - this.buildListener, - this.updateListenable, - @Deprecated( - 'No longer used by this package, please remove any references to it. ' - 'This feature was deprecated after v1.0.0.', - ) - this.preferUpdate = true, - this.removeDuplicateHistory = true, - this.notFoundPage = BeamPage.notFound, - this.notFoundRedirect, - this.notFoundRedirectNamed, - this.guards = const [], - this.navigatorObservers = const [], - this.transitionDelegate = const DefaultTransitionDelegate(), - this.beamBackTransitionDelegate = const ReverseTransitionDelegate(), - this.onPopPage, - this.setBrowserTabTitle = true, - this.initializeFromParent = true, - this.updateFromParent = true, - this.updateParent = true, - this.clearBeamingHistoryOn = const {}, - }) { - _currentBeamParameters = BeamParameters( - transitionDelegate: transitionDelegate, - ); - - configuration = RouteInformation(uri: Uri.parse(initialPath)); - - updateListenable?.addListener(_update); - } - - /// A state of this delegate. This is the `routeInformation` that goes into - /// [stackBuilder] to build an appropriate [BeamStack]. - /// - /// A way to modify this state is via [update]. - late RouteInformation configuration; - - BeamerDelegate? _parent; - - final Set _children = {}; - - /// Takes priority over all other siblings, - /// i.e. sets itself as active and all other siblings as inactive. - void takePriority() { - if (_parent?._children.isNotEmpty ?? false) { - for (var child in _parent!._children) { - child.active = false; - } - } - active = true; - } - - /// A delegate of a parent of the [Beamer] that has this delegate. - /// - /// This is not null only if multiple [Beamer]s are used; - /// `*App.router` and at least one more [Beamer] in the Widget tree. - BeamerDelegate? get parent => _parent; - - set parent(BeamerDelegate? parent) { - if (parent == null && _parent != null) { - _parent!.removeListener(_updateFromParent); - _parent!._children.remove(this); - _parent = null; - return; - } - if (_parent == parent) { - return; - } - _parent = parent; - _parent!._children.add(this); - _initializeChild(); - if (updateFromParent) { - _parent!.addListener(_updateFromParent); - } - } - - /// The top-most [BeamerDelegate], a parent of all. - /// - /// It will return root even when called on root. - BeamerDelegate get root { - if (_parent == null) { - return this; - } - var root = _parent!; - while (root._parent != null) { - root = root._parent!; - } - return root; - } - - /// A builder for [BeamStack]s. - /// - /// There are 3 ways of building an appropriate [BeamStack] which will in - /// turn build a stack of pages that should go into [Navigator.pages]. - /// - /// 1. Custom closure - /// ```dart - /// stackBuilder: (state) { - /// if (state.uri.pathSegments.contains('l1')) { - /// return Stack1(state); - /// } - /// if (state.uri.pathSegments.contains('l2')) { - /// return Stack2(state); - /// } - /// return NotFound(path: state.uri.toString()); - /// }, - /// ``` - /// - /// 2. [BeamerStackBuilder]; chooses appropriate [BeamStack] itself - /// ```dart - /// stackBuilder: BeamerStackBuilder( - /// beamStacks: [ - /// Stack1(), - /// Stack2(), - /// ], - /// ), - /// ``` - /// - /// 3. [RoutesStackBuilder]; a Map of routes - /// ```dart - /// stackBuilder: RoutesStackBuilder( - /// routes: { - /// '/': (context, state) => HomeScreen(), - /// '/another': (context, state) => AnotherScreen(), - /// }, - /// ), - /// ``` - final StackBuilder stackBuilder; - - /// The path to replace `/` as default initial route path upon load. - /// - /// Note that (if set to anything other than `/` (default)), - /// you will not be able to navigate to `/` by manually typing - /// it in the URL bar, because it will always be transformed to `initialPath`, - /// but you will be able to get to `/` by popping pages with back button, - /// if there are pages in [BeamStack.buildPages] that will build - /// when there are no path segments. - final String initialPath; - - /// An initial deep-link that should be stored until used. - /// - /// This can be used in [BeamGuard.beamToNamed] to redirect properly after - /// some auth flows that need to be executed, during which the [initialPath] - /// information is lost. - String? _deepLink; - - /// Sets the deep-link route path that - /// - will be used as [initialPath] - /// - can be used in [BeamGuard.beamToNamed] to redirect properly - /// after some auth flows that need to be executed, - /// during which the [initialPath] information is lost. - /// - /// Once a [BeamGuard] uses the deep-link, it will be reset to null. - void setDeepLink(String? deepLink) => _deepLink = deepLink; - - /// The routeListener will be called on every navigation event - /// and will receive the [configuration] and a reference to this delegate. - final void Function(RouteInformation, BeamerDelegate)? routeListener; - - /// The buildListener will be called every time after the [currentPages] - /// are updated. it receives a reference to this delegate. - final void Function(BuildContext, BeamerDelegate)? buildListener; - - /// A Listenable to which an update listener will be added, i.e. - /// [update] will be called when listeners are notified. - final Listenable? updateListenable; - - @Deprecated( - 'No longer used by this package, please remove any references to it. ' - 'This feature was deprecated after v1.0.0.', - ) - // ignore: public_member_api_docs - final bool preferUpdate; - - /// Whether to remove [BeamStack]s from [beamingHistory] - /// if they are the same type as the stack being beamed to. - /// - /// See how this is used at [_addToBeamingHistory] implementation. - final bool removeDuplicateHistory; - - /// Page to show when no [BeamStack] supports the incoming URI. - late BeamPage notFoundPage; - - /// [BeamStack] to redirect to when no [BeamStack] supports the incoming URI. - final BeamStack? notFoundRedirect; - - /// URI string to redirect to when no [BeamStack] supports the incoming URI. - final String? notFoundRedirectNamed; - - /// Guards that will be executing [BeamGuard.check] on [currentBeamStack] - /// candidate. - /// - /// Checks will be executed in order; chain of responsibility pattern. - /// When some guard returns `false`, stack candidate will not be accepted - /// and stack of pages will be updated as is configured in [BeamGuard]. - final List guards; - - /// The list of observers for the [Navigator]. - final List navigatorObservers; - - /// A transition delegate to be used by [Navigator]. - /// - /// This transition delegate will be overridden by the one in [BeamStack], - /// if any is set. - /// - /// See [Navigator.transitionDelegate]. - final TransitionDelegate transitionDelegate; - - /// A transition delegate to be used by [Navigator] when beaming back. - /// - /// When calling [beamBack], it's useful to animate routes in reverse order; - /// adding the new ones behind and then popping the current ones, - /// therefore, the default is [ReverseTransitionDelegate]. - final TransitionDelegate beamBackTransitionDelegate; - - /// Callback when `pop` is requested. - /// - /// Return `true` if pop will be handled entirely by this function. - /// Return `false` if beamer should finish handling the pop. - /// - /// See [build] for details on how beamer handles [Navigator.onPopPage]. - bool Function(BuildContext context, Route route, dynamic result)? - onPopPage; - - /// Whether the title attribute of [BeamPage] should - /// be used to set and update the browser tab title. - final bool setBrowserTabTitle; - - /// Whether to take [configuration] from parent when this is created. - /// - /// If false, the [initialPath] will be used. - final bool initializeFromParent; - - /// Whether to call [update] when parent notifies listeners. - /// - /// This means that navigation can be done either on parent or on this - final bool updateFromParent; - - /// Whether to call [update] on [parent] when this instance's [configuration] - /// is updated. - /// - /// This means that parent's [beamingHistory] will be in sync. - final bool updateParent; - - /// Whether to remove all entries from [beamingHistory] (and their nested - /// [BeamStack.history]) when a route belonging to this set is reached, - /// regardless of how it was reached. - /// - /// Note that [popToNamed] will also try to clear as much [beamingHistory] - /// as possible, even when this is empty. - final Set clearBeamingHistoryOn; - - final GlobalKey _navigatorKey = GlobalKey(); - - /// {@template beamingHistory} - /// The history of [BeamStack]s, each holding its [BeamStack.history]. - /// - /// See [_addToBeamingHistory]. - /// {@endtemplate} - final List beamingHistory = []; - - /// Returns the complete length of beaming history, that is the sum of all - /// history lengths for each [BeamStack] in [beamingHistory]. - int get beamingHistoryCompleteLength { - var length = 0; - for (var stack in beamingHistory) { - length += stack.history.length; - } - return length; - } - - /// A candidate to become the next [currentBeamStack]. - BeamStack _beamStackCandidate = EmptyBeamStack(); - - /// {@template currentBeamStack} - /// A [BeamStack] that is currently responsible for providing a page stack - /// via [BeamStack.buildPages] and holds the current [BeamState]. - /// - /// Usually obtained via - /// ```dart - /// Beamer.of(context).currentBeamStack - /// ``` - /// {@endtemplate} - BeamStack get currentBeamStack => - beamingHistory.isEmpty ? EmptyBeamStack() : beamingHistory.last; - - List _currentPages = []; - - /// {@template currentPages} - /// [currentBeamStack]'s "effective" pages, the ones that were built. - /// {@endtemplate} - List get currentPages => _currentPages; - - /// Describes the current parameters for beaming, such as - /// pop configuration, beam back on pop, etc. - late BeamParameters _currentBeamParameters; - - /// If `false`, does not report the route until next [update]. - /// - /// Useful when having sibling beamers that are both build at the same time. - /// Becomes active on next [update]. - bool active = true; - - /// The [Navigator] that belongs to this [BeamerDelegate]. - /// - /// Useful for popping dialogs without accessing [BuildContext]: - /// - /// ```dart - /// beamerDelegate.navigator.pop(); - /// ``` - NavigatorState get navigator => _navigatorKey.currentState!; - - /// A context for guards - BuildContext? _context; - - /// Whether guards already ran. - /// - /// This is used to decide whether guards need to be run in the [build]. - /// They will run just in [update] everytime, except on first build, - /// when the `Navigator` does not exist so we don't have `context`. - bool _didRunGuards = false; - - /// Is [build] in progress. - /// Used to determine not to [notifyListeners]. - bool _buildInProgress = false; - - /// Whether the [configuration] is ready to be consumed by platform, - /// most importantly - the browser URL bar. - /// - /// This is important for first build, i.e. [setInitialRoutePath], - /// to avoid setting URL when the guards have not been run yet. - bool _initialConfigurationReady = false; - - /// Main method to update the [configuration] of this delegate and its - /// [currentBeamStack]. - /// - /// This "top-level" [update] is generally used for navigation - /// _between_ [BeamStack]s and not _within_ a specific [BeamStack]. - /// For latter purpose, see [BeamStack.update]. - /// Nevertheless, [update] **will** work for navigation within [BeamStack]. - /// - /// Calling [update] will run the [stackBuilder]. - /// - /// ```dart - /// Beamer.of(context).update( - /// state: BeamState.fromUriString('/xx'), - /// ); - /// ``` - /// - /// **all of the beaming functions call [update] to really do the update.** - /// - /// [beamParameters] hold various parameters such as `transitionDelegate` and - /// other that will be used for this update **and** stored in history. - /// - /// [data] can be used to hold arbitrary [Object] throughout navigation. - /// See [BeamStack.data] for more information. - /// - /// [buildBeamStack] determines whether a [BeamStack] should be created - /// from [configuration], using the [stackBuilder]. For example, when using - /// [beamTo] and passing an already created [BeamStack], this can be false. - /// - /// [rebuild] determines whether to call [notifyListeners]. This can be false - /// if we already have built the UI and just want to notify the platform, - /// e.g. browser, of the new [RouteInformation]. - /// - /// [updateParent] is used in nested navigation to call [update] on [parent]. - /// - /// [updateRouteInformation] determines whether to update the browser's URL. - /// This is usually done through `notifyListeners`, but in specific cases, - /// e.g. when [rebuild] is false, browser would not be updated. - /// This is mainly used to not update the route information from parent, - /// when it has already been done by this delegate. - void update({ - RouteInformation? configuration, - BeamParameters? beamParameters, - Object? data, - bool buildBeamStack = true, - bool rebuild = true, - bool updateParent = true, - bool updateRouteInformation = true, - bool replaceRouteInformation = false, - bool takePriority = true, - }) { - if (takePriority) { - this.takePriority(); - } - - if (_buildInProgress) { - rebuild = false; - } - - replaceRouteInformation - ? SystemNavigator.selectSingleEntryHistory() - : SystemNavigator.selectMultiEntryHistory(); - - this.configuration = configuration != null - ? Utils.createNewConfiguration(this.configuration, configuration) - : currentBeamStack.state.routeInformation.copyWith(); - - // update beam parameters - _currentBeamParameters = beamParameters ?? _currentBeamParameters; - - if (buildBeamStack) { - // build a BeamStack from configuration - _beamStackCandidate = stackBuilder( - this.configuration.copyWith(), - _currentBeamParameters, - ); - } - - // run guards on _beamStackCandidate - final context = _context; - if (context != null) { - if (configuration != null) { - _setBrowserTitle(context, - fixTitle: configuration.uri.pathSegments.last); - } - final didApply = _runGuards(context, _beamStackCandidate); - _didRunGuards = true; - if (didApply) { - return; - } else { - // TODO revert configuration if guard just blocked navigation - } - } - - // adds the candidate to history - // it will become currentBeamStack after this step - _updateBeamingHistory(_beamStackCandidate); - if (data != null) { - currentBeamStack.data = data; - } - - routeListener?.call(this.configuration, this); - - // update parent if necessary - if (this.updateParent && updateParent) { - _parent?.update( - configuration: this.configuration.copyWith(), - rebuild: false, - updateRouteInformation: false, - ); - } - - // update browser history - // if this is from nested Beamer - // or - // if rebuild was false (browser will not be notified implicitly) - if (updateRouteInformation && active && (_parent != null || !rebuild)) { - this.updateRouteInformation(this.configuration); - } - - // initiate build - if (rebuild) { - notifyListeners(); - } - } - - /// {@template beamTo} - /// Beams to a specific, manually configured [BeamStack]. - /// - /// For example - /// ```dart - /// Beamer.of(context).beamTo( - /// Stack2( - /// BeamState( - /// pathBlueprintSegments = ['user',':userId','transactions'], - /// pathParameters = {'userId': '1'}, - /// queryParameters = {'perPage': '10'}, - /// data = {'favoriteUser': true}, - /// ), - /// ), - /// ); - /// ``` - /// - /// See [update] for more details. - /// {@endtemplate} - void beamTo( - BeamStack stack, { - Object? data, - BeamStack? popTo, - TransitionDelegate? transitionDelegate, - bool beamBackOnPop = false, - bool popBeamStackOnPop = false, - bool stacked = true, - bool replaceRouteInformation = false, - }) { - _beamStackCandidate = stack; - update( - configuration: stack.state.routeInformation, - beamParameters: _currentBeamParameters.copyWith( - popConfiguration: popTo?.state.routeInformation, - transitionDelegate: transitionDelegate ?? this.transitionDelegate, - beamBackOnPop: beamBackOnPop, - popBeamStackOnPop: popBeamStackOnPop, - stacked: stacked, - ), - data: data, - buildBeamStack: false, - replaceRouteInformation: replaceRouteInformation, - ); - } - - /// The same as [beamTo], but replaces the last state in history, - /// i.e. removes it from the `beamingHistory.last.history` and then does [beamTo]. - void beamToReplacement( - BeamStack stack, { - Object? data, - BeamStack? popTo, - TransitionDelegate? transitionDelegate, - bool beamBackOnPop = false, - bool popBeamStackOnPop = false, - bool stacked = true, - }) { - removeLastHistoryElement(); - beamTo( - stack, - data: data, - popTo: popTo, - transitionDelegate: transitionDelegate, - beamBackOnPop: beamBackOnPop, - popBeamStackOnPop: popBeamStackOnPop, - stacked: stacked, - replaceRouteInformation: true, - ); - } - - /// {@template beamToNamed} - /// Configures and beams to a [BeamStack] that supports uri within its - /// [BeamStack.pathPatterns]. - /// - /// For example - /// ```dart - /// Beamer.of(context).beamToNamed( - /// '/user/1/transactions?perPage=10', - /// data: {'favoriteUser': true},, - /// ); - /// ``` - /// - /// See [update] for more details. - /// {@endtemplate} - void beamToNamed( - String uri, { - Object? routeState, - Object? data, - String? popToNamed, - TransitionDelegate? transitionDelegate, - bool beamBackOnPop = false, - bool popBeamStackOnPop = false, - bool stacked = true, - bool replaceRouteInformation = false, - }) { - update( - configuration: RouteInformation(uri: Uri.parse(uri), state: routeState), - beamParameters: _currentBeamParameters.copyWith( - popConfiguration: popToNamed != null - ? RouteInformation(uri: Uri.parse(popToNamed)) - : null, - transitionDelegate: transitionDelegate ?? this.transitionDelegate, - beamBackOnPop: beamBackOnPop, - popBeamStackOnPop: popBeamStackOnPop, - stacked: stacked, - ), - data: data, - replaceRouteInformation: replaceRouteInformation, - ); - } - - /// The same as [beamToNamed], but replaces the last state in history, - /// i.e. removes it from the `beamingHistory.last.history` and then does [beamToNamed]. - void beamToReplacementNamed( - String uri, { - Object? routeState, - Object? data, - String? popToNamed, - TransitionDelegate? transitionDelegate, - bool beamBackOnPop = false, - bool popBeamStackOnPop = false, - bool stacked = true, - }) { - removeLastHistoryElement(); - beamToNamed( - uri, - routeState: routeState, - data: data, - popToNamed: popToNamed, - transitionDelegate: transitionDelegate, - beamBackOnPop: beamBackOnPop, - popBeamStackOnPop: popBeamStackOnPop, - stacked: stacked, - replaceRouteInformation: true, - ); - } - - /// {@template popToNamed} - /// Calls [beamToNamed] with a [ReverseTransitionDelegate] and tries to - /// remove everything from history after entry corresponding to `uri`, as - /// if doing a pop way back to that state, if it exists in history. - /// - /// See [beamToNamed] for more details. - /// {@endtemplate} - void popToNamed( - String uri, { - Object? routeState, - Object? data, - String? popToNamed, - bool beamBackOnPop = false, - bool popBeamStackOnPop = false, - bool stacked = true, - bool replaceRouteInformation = false, - }) { - while (beamingHistory.isNotEmpty) { - final index = beamingHistory.last.history.lastIndexWhere( - (element) => element.routeInformation.uri == Uri.parse(uri), - ); - if (index == -1) { - _disposeBeamStack(beamingHistory.last); - beamingHistory.removeLast(); - continue; - } else { - beamingHistory.last.history - .removeRange(index, beamingHistory.last.history.length); - break; - } - } - beamToNamed( - uri, - routeState: routeState, - data: data, - popToNamed: popToNamed, - transitionDelegate: const ReverseTransitionDelegate(), - beamBackOnPop: beamBackOnPop, - popBeamStackOnPop: popBeamStackOnPop, - stacked: stacked, - replaceRouteInformation: replaceRouteInformation, - ); - } - - /// {@template canBeamBack} - /// Whether it is possible to [beamBack], - /// i.e. there is more than 1 entry in [beamingHistoryCompleteLength]. - /// {@endtemplate} - bool get canBeamBack => beamingHistoryCompleteLength > 1; - - /// {@template beamBack} - /// Beams to previous entry in [beamingHistory]. - /// and **removes** the last entry from history. - /// - /// If there is no previous entry, does nothing. - /// - /// Returns the success, whether [update] was executed. - /// {@endtemplate} - bool beamBack({ - Object? data, - bool replaceRouteInformation = false, - }) { - if (!canBeamBack) { - return false; - } - // first we try to beam back within last BeamStack - // i.e. we just pop its last history element id possible - if (beamingHistory.last.history.length > 1) { - beamingHistory.last.history.removeLast(); - } else { - // here we know that beamingHistory.length > 1 (because of canBeamBack) - // and that beamingHistory.last.history.length == 1 - // so this last (only) entry is removed along with BeamStack - _disposeBeamStack(beamingHistory.last); - beamingHistory.removeLast(); - _initBeamStack(beamingHistory.last); - } - - _beamStackCandidate = beamingHistory.last; - _beamStackCandidate.update(); - final targetHistoryElement = _beamStackCandidate.history.last; - - update( - configuration: targetHistoryElement.routeInformation.copyWith(), - beamParameters: targetHistoryElement.parameters.copyWith( - transitionDelegate: beamBackTransitionDelegate, - ), - data: data, - buildBeamStack: false, - replaceRouteInformation: replaceRouteInformation, - ); - - return true; - } - - /// {@template canPopBeamStack} - /// Whether it is possible to [popBeamStack], - /// i.e. there is more than 1 stack in [beamingHistory]. - /// {@endtemplate} - bool get canPopBeamStack => beamingHistory.length > 1; - - /// {@template popBeamStack} - /// Beams to previous stack in [beamingHistory] - /// and **removes** the last stack from history. - /// - /// If there is no previous stack, does nothing. - /// - /// Returns the success, whether the [currentBeamStack] was changed. - /// {@endtemplate} - bool popBeamStack({ - Object? data, - bool replaceRouteInformation = false, - }) { - if (!canPopBeamStack) { - return false; - } - _disposeBeamStack(currentBeamStack); - beamingHistory.removeLast(); - _beamStackCandidate = beamingHistory.last; - update( - beamParameters: currentBeamStack.history.last.parameters.copyWith( - transitionDelegate: beamBackTransitionDelegate, - ), - data: data, - buildBeamStack: false, - replaceRouteInformation: replaceRouteInformation, - ); - return true; - } - - @override - RouteInformation? get currentConfiguration { - final response = - _parent == null && _initialConfigurationReady ? configuration : null; - if (response != null) { - _lastReportedRouteInformation = response.copyWith(); - } - return response; - } - - @override - GlobalKey get navigatorKey => _navigatorKey; - - @override - Widget build(BuildContext context) { - _buildInProgress = true; - _context = context; - - if (!_didRunGuards) { - _runGuards(_context!, _beamStackCandidate); - _addToBeamingHistory(_beamStackCandidate); - } - if (!_initialConfigurationReady && active && parent != null) { - updateRouteInformation(configuration); - } - _initialConfigurationReady = true; - - if (currentBeamStack is NotFound) { - _handleNotFoundRedirect(); - } - - if (!currentBeamStack.mounted) { - currentBeamStack.buildInit(context); - } - - final navigator = Builder( - builder: (context) { - _setCurrentPages(context); - _setBrowserTitle(context); - - buildListener?.call(context, this); - return Navigator( - key: navigatorKey, - observers: navigatorObservers, - transitionDelegate: currentBeamStack.transitionDelegate ?? - _currentBeamParameters.transitionDelegate, - pages: _currentPages, - onPopPage: (route, result) => _onPopPage(context, route, result), - ); - }, - ); - - _buildInProgress = false; - return currentBeamStack.builder(context, navigator); - } - - @override - SynchronousFuture setInitialRoutePath(RouteInformation configuration) { - _initialConfigurationReady = false; - final uri = configuration.uri; - if (currentBeamStack is! EmptyBeamStack) { - configuration = currentBeamStack.state.routeInformation; - } else if (uri.path == '/') { - configuration = RouteInformation( - uri: uri.replace( - path: initialPath, - query: uri.hasQuery ? uri.query : null, - ), - ); - } - _deepLink ??= configuration.uri.toString(); - return setNewRoutePath(configuration); - } - - @override - SynchronousFuture setNewRoutePath(RouteInformation configuration) { - if (configuration.uri.path == '/' && initialPath != '/') { - configuration = configuration.copyWith(uri: Uri.parse(initialPath)); - } - update(configuration: configuration); - return SynchronousFuture(null); - } - - RouteInformation? _lastReportedRouteInformation; - - /// Pass this call to [root] which notifies the platform for a [configuration] - /// change. - /// - /// On Web, creates a new browser history entry and updates URL. - /// - /// See [SystemNavigator.routeInformationUpdated]. - void updateRouteInformation(RouteInformation routeInformation) { - if (_parent == null) { - if (!routeInformation.isEqualTo(_lastReportedRouteInformation)) { - SystemNavigator.routeInformationUpdated( - uri: routeInformation.uri, - state: routeInformation.state, - ); - _lastReportedRouteInformation = routeInformation.copyWith(); - } - } else { - if (updateParent) { - _parent!.configuration = routeInformation.copyWith(); - } - _parent!.updateRouteInformation(routeInformation); - } - } - - bool _runGuards(BuildContext context, BeamStack targetBeamStack) { - final allGuards = (parent?.guards ?? []) + guards + targetBeamStack.guards; - for (final guard in allGuards) { - if (guard.shouldGuard(targetBeamStack)) { - final wasApplied = guard.apply( - context, - this, - currentBeamStack, - _currentPages, - targetBeamStack, - _deepLink, - ); - - // Return true on the first guard that was fully applied - if (wasApplied) { - return true; - } - } - } - return false; - } - - void _initBeamStack(BeamStack beamStack) { - beamStack.initState(); - beamStack.onUpdate(); - beamStack.addListener(_updateFromCurrentBeamStack); - } - - void _disposeBeamStack(BeamStack beamStack) { - beamStack.removeListener(_updateFromCurrentBeamStack); - beamStack.disposeState(); - } - - void _addToBeamingHistory(BeamStack beamStack) { - _disposeBeamStack(currentBeamStack); - if (removeDuplicateHistory) { - final index = beamingHistory.indexWhere( - (historyStack) => historyStack.runtimeType == beamStack.runtimeType); - if (index != -1) { - _disposeBeamStack(beamingHistory[index]); - beamingHistory.removeAt(index); - } - } - _initBeamStack(beamStack); - beamingHistory.add(beamStack); - } - - void _updateBeamingHistory(BeamStack beamStack) { - if (beamingHistory.isEmpty || - beamStack.runtimeType != beamingHistory.last.runtimeType) { - _addToBeamingHistory(beamStack); - } else { - beamingHistory.last.update( - null, - configuration.copyWith(), - _currentBeamParameters, - false, - false, - ); - } - - if (clearBeamingHistoryOn.contains(configuration.uri.path)) { - _clearBeamingHistory(); - } - } - - void _clearBeamingHistory() { - while (beamingHistoryCompleteLength > 1) { - removeFirstHistoryElement(); - } - } - - /// Removes the first element from [beamingHistory] and returns it. - /// - /// If there is none, returns `null`. - HistoryElement? removeFirstHistoryElement() { - if (beamingHistoryCompleteLength == 0) { - return null; - } - if (updateParent) { - _parent?.removeFirstHistoryElement(); - } - final firstBeamStack = beamingHistory.first; - final firstHistoryElement = firstBeamStack.removeFirstFromHistory(); - if (firstBeamStack.history.isEmpty) { - _disposeBeamStack(firstBeamStack); - beamingHistory.removeAt(0); - } - - return firstHistoryElement; - } - - /// Removes the last element from [beamingHistory] and returns it. - /// - /// If there is none, returns `null`. - HistoryElement? removeLastHistoryElement() { - if (beamingHistoryCompleteLength == 0) { - return null; - } - if (updateParent) { - _parent?.removeLastHistoryElement(); - } - final lastHistoryElement = beamingHistory.last.removeLastFromHistory(); - if (beamingHistory.last.history.isEmpty) { - _disposeBeamStack(beamingHistory.last); - beamingHistory.removeLast(); - } else { - beamingHistory.last.update(null, null, null, false); - } - - return lastHistoryElement; - } - - void _handleNotFoundRedirect() { - if (notFoundRedirect == null && notFoundRedirectNamed == null) { - // do nothing, pass on NotFound - return; - } - if (notFoundRedirect != null) { - _beamStackCandidate = notFoundRedirect!; - } else if (notFoundRedirectNamed != null) { - _beamStackCandidate = stackBuilder( - RouteInformation(uri: Uri.parse(notFoundRedirectNamed!)), - _currentBeamParameters.copyWith(), - ); - } - _updateFromBeamStackCandidate(); - } - - void _setCurrentPages(BuildContext context) { - if (currentBeamStack is NotFound) { - _currentPages = [notFoundPage]; - } else { - _currentPages = _currentBeamParameters.stacked - ? currentBeamStack.buildPages(context, currentBeamStack.state) - : [currentBeamStack.buildPages(context, currentBeamStack.state).last]; - } - } - - void _setBrowserTitle(BuildContext context, {String? fixTitle}) { - if (active && setBrowserTabTitle) { - final String title = _currentPages.last.title ?? - currentBeamStack.state.routeInformation.uri.path; - final String title = fixTitle ?? _currentPages.last.title ?? - currentBeamLocation.state.routeInformation.uri.path; - browser_tab_title_util.setTabTitle(title); - } - } - - bool _onPopPage(BuildContext context, Route route, dynamic result) { - if (route.willHandlePopInternally) { - if (!route.didPop(result)) { - return false; - } - } - - if (_currentBeamParameters.popConfiguration != null) { - update( - configuration: _currentBeamParameters.popConfiguration, - beamParameters: _currentBeamParameters.copyWith( - transitionDelegate: beamBackTransitionDelegate, - resetPopConfiguration: true, - ), - // replaceCurrent: true, - ); - } else if (_currentBeamParameters.popBeamStackOnPop) { - final didPopBeamStack = popBeamStack(); - if (!didPopBeamStack) { - return false; - } - } else if (_currentBeamParameters.beamBackOnPop) { - final didBeamBack = beamBack(); - if (!didBeamBack) { - return false; - } - } else { - final lastPage = _currentPages.last; - if (lastPage.popToNamed != null) { - popToNamed(lastPage.popToNamed!); - } else { - final shouldPop = lastPage.onPopPage( - context, - this, - currentBeamStack.state, - lastPage, - ); - if (!shouldPop) { - return false; - } - } - } - - return route.didPop(result); - } - - // When a nested Beamer gets into a Widget tree, it must initialize. - // It will try to take the configuration from parent, - // but act differently depending on whether it can handle that configuration. - void _initializeChild() { - final parentConfiguration = _parent!.configuration.copyWith(); - if (initializeFromParent) { - _beamStackCandidate = - stackBuilder(parentConfiguration, _currentBeamParameters); - } - - // If this couldn't handle parents configuration, - // it will update itself to initialPath and declare itself inactive. - if (_beamStackCandidate is EmptyBeamStack || - _beamStackCandidate is NotFound) { - update( - configuration: RouteInformation(uri: Uri.parse(initialPath)), - rebuild: false, - updateParent: false, - updateRouteInformation: false, - takePriority: false, - ); - active = false; - } else { - update( - configuration: parentConfiguration, - rebuild: false, - updateParent: false, - updateRouteInformation: false, - ); - } - } - - void _update() => update(); - - // Updates only if it can handle the configuration - void _updateFromParent({bool rebuild = true}) { - final parentConfiguration = _parent!.configuration.copyWith(); - final beamStack = stackBuilder(parentConfiguration, _currentBeamParameters); - - if (beamStack is! NotFound) { - update( - configuration: parentConfiguration, - rebuild: rebuild, - updateParent: false, - updateRouteInformation: false, - ); - } - } - - void _updateFromCurrentBeamStack({bool rebuild = true}) { - update( - configuration: currentBeamStack.state.routeInformation, - buildBeamStack: false, - rebuild: rebuild, - ); - } - - void _updateFromBeamStackCandidate({bool rebuild = false}) { - update( - configuration: _beamStackCandidate.state.routeInformation, - buildBeamStack: false, - rebuild: rebuild, - ); - } - - // This is a temporary implementation - // as there is a ?bug? when navigating with browser buttons - // that keeps creating new delegates **but** persisting the children List. - // The children list then grows unnecessary. - // - // Also tried with navigatorKey, but this is also newly created - // Using initialPath is not perfect, - // but should be good until I investigate further. - // - // These overrides are for the inserting into a Set of children - @override - bool operator ==(other) { - if (other is! BeamerDelegate) { - return false; - } - return initialPath == other.initialPath; - } - - @override - int get hashCode => initialPath.hashCode; - - @override - void dispose() { - _children.clear(); - _parent?.removeListener(_updateFromParent); - _disposeBeamStack(currentBeamStack); - currentBeamStack.dispose(); - updateListenable?.removeListener(_update); - super.dispose(); - } -} \ No newline at end of file From 6394a920484222f1eee53021ea2f9498447f269f Mon Sep 17 00:00:00 2001 From: fabianbrandscheid <38261693+fabianbrandscheid@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:26:26 +0200 Subject: [PATCH 7/8] Add files via upload format --- package/lib/src/beamer_delegate.dart | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/package/lib/src/beamer_delegate.dart b/package/lib/src/beamer_delegate.dart index 63834a9..d98808e 100644 --- a/package/lib/src/beamer_delegate.dart +++ b/package/lib/src/beamer_delegate.dart @@ -497,7 +497,7 @@ class BeamerDelegate extends RouterDelegate /// See [update] for more details. /// {@endtemplate} void beamTo( - BeamStack stack, { + BeamStack stack, { Object? data, BeamStack? popTo, TransitionDelegate? transitionDelegate, @@ -524,7 +524,8 @@ class BeamerDelegate extends RouterDelegate /// The same as [beamTo], but replaces the last state in history, /// i.e. removes it from the `beamingHistory.last.history` and then does [beamTo]. - void beamToReplacement(BeamStack stack, { + void beamToReplacement( + BeamStack stack, { Object? data, BeamStack? popTo, TransitionDelegate? transitionDelegate, @@ -560,7 +561,7 @@ class BeamerDelegate extends RouterDelegate /// See [update] for more details. /// {@endtemplate} void beamToNamed( - String uri, { + String uri, { Object? routeState, Object? data, String? popToNamed, @@ -589,7 +590,7 @@ class BeamerDelegate extends RouterDelegate /// The same as [beamToNamed], but replaces the last state in history, /// i.e. removes it from the `beamingHistory.last.history` and then does [beamToNamed]. void beamToReplacementNamed( - String uri, { + String uri, { Object? routeState, Object? data, String? popToNamed, @@ -620,7 +621,7 @@ class BeamerDelegate extends RouterDelegate /// See [beamToNamed] for more details. /// {@endtemplate} void popToNamed( - String uri, { + String uri, { Object? routeState, Object? data, String? popToNamed, @@ -631,7 +632,7 @@ class BeamerDelegate extends RouterDelegate }) { while (beamingHistory.isNotEmpty) { final index = beamingHistory.last.history.lastIndexWhere( - (element) => element.routeInformation.uri == Uri.parse(uri), + (element) => element.routeInformation.uri == Uri.parse(uri), ); if (index == -1) { _disposeBeamStack(beamingHistory.last); @@ -887,8 +888,7 @@ class BeamerDelegate extends RouterDelegate _disposeBeamStack(currentBeamStack); if (removeDuplicateHistory) { final index = beamingHistory.indexWhere( - (historyStack) => - historyStack.runtimeType == beamStack.runtimeType); + (historyStack) => historyStack.runtimeType == beamStack.runtimeType); if (index != -1) { _disposeBeamStack(beamingHistory[index]); beamingHistory.removeAt(index); @@ -986,8 +986,7 @@ class BeamerDelegate extends RouterDelegate } else { _currentPages = _currentBeamParameters.stacked ? currentBeamStack.buildPages(context, currentBeamStack.state) - : [currentBeamStack.buildPages(context, currentBeamStack.state).last - ]; + : [currentBeamStack.buildPages(context, currentBeamStack.state).last]; } } From 7732113f5f1baefa3da1916349945ccb2ed3f43f Mon Sep 17 00:00:00 2001 From: fabianbrandscheid <38261693+fabianbrandscheid@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:27:35 +0200 Subject: [PATCH 8/8] Add files via upload format --- package/lib/src/beamer_delegate.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/lib/src/beamer_delegate.dart b/package/lib/src/beamer_delegate.dart index d98808e..dc10a12 100644 --- a/package/lib/src/beamer_delegate.dart +++ b/package/lib/src/beamer_delegate.dart @@ -888,7 +888,7 @@ class BeamerDelegate extends RouterDelegate _disposeBeamStack(currentBeamStack); if (removeDuplicateHistory) { final index = beamingHistory.indexWhere( - (historyStack) => historyStack.runtimeType == beamStack.runtimeType); + (historyStack) => historyStack.runtimeType == beamStack.runtimeType); if (index != -1) { _disposeBeamStack(beamingHistory[index]); beamingHistory.removeAt(index);