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