Line data Source code
1 : import 'package:flutter/foundation.dart';
2 : import 'package:flutter/material.dart';
3 :
4 : import 'destination.dart';
5 : import 'navigation_controller.dart';
6 : import 'navigation_scheme.dart';
7 : import 'utils/utils.dart';
8 :
9 : /// Implementation of [RouterDelegate].
10 : ///
11 : /// Uses [navigationScheme] to build routes.
12 : ///
13 : /// See also:
14 : /// - [NavigationScheme]
15 : /// - [NavigationController]
16 : /// - [Destination]
17 : ///
18 : class TheseusRouterDelegate extends RouterDelegate<Destination>
19 : with ChangeNotifier {
20 : /// Creates router delegate.
21 : ///
22 5 : TheseusRouterDelegate({
23 : required this.navigationScheme,
24 : }) {
25 10 : Log.d(runtimeType, 'TheseusRouterDelegate():');
26 10 : _key = GlobalKey<NavigatorState>(debugLabel: 'TheseusNavigator');
27 15 : navigationScheme.addListener(_onCurrentDestinationChanged);
28 : }
29 :
30 : /// A navigation scheme that contains destinations and navigators.
31 : ///
32 : /// This router delegate is listening the navigation scheme to identify if the
33 : /// current destination is changed, and in turn, notifies its listeners when this
34 : /// happens.
35 : ///
36 : final NavigationScheme navigationScheme;
37 :
38 : late final GlobalKey<NavigatorState> _key;
39 :
40 2 : @override
41 : Widget build(BuildContext context) {
42 10 : Log.d(runtimeType, 'build(): isResolving=${navigationScheme.isResolving}');
43 2 : return Navigator(
44 2 : key: _key,
45 2 : pages: [
46 2 : MaterialPage(
47 : key: const ValueKey('TheseusRootPage'),
48 6 : child: navigationScheme.rootNavigator.build(context),
49 : ),
50 4 : if (navigationScheme.isResolving)
51 2 : _TheseusPageOverlay(
52 4 : child: navigationScheme.waitingOverlayBuilder
53 0 : ?.call(context, navigationScheme.currentDestination) ??
54 : const _TheseusWaitingOverlay(
55 : key: Key('_TheseusWaitingOverlay_'),
56 : ),
57 : ),
58 : ],
59 0 : onPopPage: (route, result) => route.didPop(result),
60 : );
61 : }
62 :
63 : @override
64 2 : Future<bool> popRoute() async {
65 4 : Log.d(runtimeType, 'popRoute():');
66 4 : navigationScheme.goBack();
67 4 : if (navigationScheme.shouldClose) {
68 2 : if (Platform.isAndroid) {
69 : return false;
70 : } else {
71 8 : navigationScheme.goTo(navigationScheme.currentDestination);
72 : return true;
73 : }
74 : }
75 : return true;
76 : }
77 :
78 : @override
79 : // ignore: avoid_renaming_method_parameters
80 3 : Future<void> setNewRoutePath(destination) async {
81 9 : Log.d(runtimeType, 'setNewRoutePath(): destination=$destination');
82 : // The current navigation stack is reset if the new destination is not an error.
83 9 : final reset = destination != navigationScheme.errorDestination;
84 9 : return SynchronousFuture(navigationScheme.goTo(destination
85 9 : .withSettings(destination.settings.copyWith(reset: reset))));
86 : }
87 :
88 3 : @override
89 6 : Destination get currentConfiguration => navigationScheme.currentDestination;
90 :
91 3 : @override
92 : void dispose() {
93 9 : navigationScheme.removeListener(_onCurrentDestinationChanged);
94 3 : super.dispose();
95 : }
96 :
97 4 : Future<void> _onCurrentDestinationChanged() async {
98 8 : final destination = navigationScheme.currentDestination;
99 4 : Log.d(
100 8 : runtimeType, 'onCurrentDestinationChanged(): destination=$destination');
101 : // Ignore closing app request here. It is processed in the 'popRoute()' method.
102 8 : if (navigationScheme.shouldClose) {
103 : return;
104 : }
105 4 : notifyListeners();
106 : }
107 : }
108 :
109 : class _TheseusPageOverlay extends Page {
110 2 : const _TheseusPageOverlay({
111 : required this.child,
112 : LocalKey? key,
113 2 : }) : super(key: key);
114 :
115 : final Widget child;
116 :
117 2 : @override
118 : Route createRoute(BuildContext context) {
119 2 : return PageRouteBuilder(
120 : settings: this,
121 : opaque: false,
122 4 : pageBuilder: (context, animation, secondaryAnimation) => child,
123 2 : transitionsBuilder: (context, animation, secondaryAnimation, child) =>
124 : child,
125 : );
126 : }
127 : }
128 :
129 : class _TheseusWaitingOverlay extends StatelessWidget {
130 10 : const _TheseusWaitingOverlay({
131 : Key? key,
132 0 : }) : super(key: key);
133 :
134 2 : @override
135 : Widget build(BuildContext context) {
136 2 : return Stack(
137 2 : children: [
138 2 : ModalBarrier(
139 2 : color: Colors.black.withAlpha(128),
140 : dismissible: false,
141 : ),
142 : const Center(
143 : child: CircularProgressIndicator(),
144 : ),
145 : ],
146 : );
147 : }
148 : }
|