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 6 : ?.call(context, navigationScheme.currentDestination) ??
54 : const _TheseusWaitingOverlay(
55 : key: Key('_TheseusWaitingOverlay_'),
56 : ),
57 : ),
58 : ],
59 0 : onPopPage: (route, result) {
60 0 : Log.d(runtimeType, 'onPopPage()');
61 0 : return route.didPop(result);
62 : },
63 : );
64 : }
65 :
66 : @override
67 2 : Future<bool> popRoute() async {
68 4 : Log.d(runtimeType, 'popRoute():');
69 4 : navigationScheme.goBack();
70 4 : if (navigationScheme.shouldClose) {
71 2 : if (Platform.isAndroid) {
72 : return false;
73 : } else {
74 8 : navigationScheme.goTo(navigationScheme.currentDestination);
75 : return true;
76 : }
77 : }
78 : return true;
79 : }
80 :
81 : @override
82 : // ignore: avoid_renaming_method_parameters
83 3 : Future<void> setNewRoutePath(destination) async {
84 9 : if (destination == navigationScheme.currentDestination) {
85 9 : Log.d(runtimeType, 'setNewRoutePath(): Ignore navigation to $destination. It is already the current destination.');
86 : return;
87 : }
88 6 : Log.d(runtimeType, 'setNewRoutePath(): destination=$destination');
89 : // The current navigation stack is reset if the new destination is not an error.
90 6 : final reset = destination != navigationScheme.errorDestination;
91 4 : return navigationScheme.goTo(
92 6 : destination.withSettings(destination.settings.copyWith(reset: reset)));
93 : }
94 :
95 3 : @override
96 6 : Destination get currentConfiguration => navigationScheme.currentDestination;
97 :
98 3 : @override
99 : void dispose() {
100 9 : navigationScheme.removeListener(_onCurrentDestinationChanged);
101 3 : super.dispose();
102 : }
103 :
104 4 : Future<void> _onCurrentDestinationChanged() async {
105 8 : final destination = navigationScheme.currentDestination;
106 4 : Log.d(
107 8 : runtimeType, 'onCurrentDestinationChanged(): destination=$destination');
108 : // Ignore closing app request here. It is processed in the 'popRoute()' method.
109 8 : if (navigationScheme.shouldClose) {
110 : return;
111 : }
112 4 : notifyListeners();
113 : }
114 : }
115 :
116 : class _TheseusPageOverlay extends Page {
117 2 : const _TheseusPageOverlay({
118 : required this.child,
119 : LocalKey? key,
120 2 : }) : super(key: key);
121 :
122 : final Widget child;
123 :
124 2 : @override
125 : Route createRoute(BuildContext context) {
126 2 : return PageRouteBuilder(
127 : settings: this,
128 : opaque: false,
129 4 : pageBuilder: (context, animation, secondaryAnimation) => child,
130 2 : transitionsBuilder: (context, animation, secondaryAnimation, child) =>
131 : child,
132 : );
133 : }
134 : }
135 :
136 : class _TheseusWaitingOverlay extends StatelessWidget {
137 11 : const _TheseusWaitingOverlay({
138 : Key? key,
139 0 : }) : super(key: key);
140 :
141 2 : @override
142 : Widget build(BuildContext context) {
143 2 : return Stack(
144 2 : children: [
145 2 : ModalBarrier(
146 2 : color: Colors.black.withAlpha(128),
147 : dismissible: false,
148 : ),
149 : const Center(
150 : child: CircularProgressIndicator(),
151 : ),
152 : ],
153 : );
154 : }
155 : }
|