Line data Source code
1 : // ignore_for_file: public_member_api_docs
2 :
3 : import 'package:flutter/cupertino.dart';
4 : import 'package:flutter/material.dart';
5 :
6 : import '../navigator/navigator.dart';
7 : import '../navigator_observer.dart';
8 : import '../transitions/transition.dart';
9 : import 'meedu_page_route.dart';
10 :
11 24 : ContextlessNavigator get router => ContextlessNavigator.i;
12 :
13 : ///this class has the navigator state and the transition configuration
14 : class ContextlessNavigator {
15 12 : ContextlessNavigator._();
16 :
17 : /// expose the MeeduNavigator soingleton
18 36 : static final ContextlessNavigator i = ContextlessNavigator._();
19 :
20 : GlobalKey<NavigatorState>? _navigatorKey;
21 : GlobalKey? _appKey;
22 :
23 : RouteSettings? _currentRouteSettings;
24 :
25 : /// store the current route settings
26 2 : RouteSettings? get routeSettings => _currentRouteSettings;
27 :
28 : /// the observer to listen the changes in the stack route
29 10 : MeeduNavigatorObserver get observer => MeeduNavigatorObserver();
30 :
31 : /// a GlobalKey<NavigatorState> to navigate without BuildContext
32 : /// ```dart
33 : ///import 'package:flutter_meedu/router.dart';
34 : ///class MyApp extends StatelessWidget {
35 : /// @override
36 : /// Widget build(BuildContext context) {
37 : /// return MaterialApp(
38 : /// navigatorKey: router.navigatorKey, // add the navigator key
39 : /// home: HomePage(),
40 : /// routes: {YOUR_ROUTES},
41 : /// );
42 : /// }
43 : ///}
44 : ///```
45 10 : GlobalKey<NavigatorState> get navigatorKey {
46 20 : _navigatorKey ??= GlobalKey<NavigatorState>();
47 10 : return _navigatorKey!;
48 : }
49 :
50 : /// GlobalKey to detenrminate if a MaterialApp or CupertinoApp
51 : /// is using the `onGenerateRoute` paremeter or for custom transitions
52 : /// using named routes we need to now the value of `routes` parameter
53 : ///
54 : ///```dart
55 : ///import 'package:flutter_meedu/router.dart';
56 : ///class MyApp extends StatelessWidget {
57 : /// @override
58 : /// Widget build(BuildContext context) {
59 : /// return MaterialApp(
60 : /// key: router.appKey,
61 : /// navigatorKey: router.navigatorKey, // add the navigator key
62 : /// home: HomePage(),
63 : /// routes: {YOUR_ROUTES},
64 : /// );
65 : /// }
66 : ///}
67 : ///```
68 7 : GlobalKey get appKey {
69 14 : _appKey ??= GlobalKey();
70 7 : return _appKey!;
71 : }
72 :
73 : /// use material transition as default transition
74 : Transition _transition = Transition.material;
75 8 : Transition get transition => _transition;
76 :
77 : /// default transition duration
78 : Duration _transitionDuration = const Duration(milliseconds: 300);
79 10 : Duration get transitionDuration => _transitionDuration;
80 :
81 : /// current state of navigatorKey to be used to navigate without BuildContext
82 30 : NavigatorState? get _state => navigatorKey.currentState;
83 :
84 : /// create a navigator 1.0 to use push, pushReplacement, pushAndRemoveUntil and all pop methods
85 32 : Navigator1 get _navigator => Navigator1(_state!.context);
86 :
87 : /// set the current route settings data
88 5 : void setRouteSettings(
89 : RouteSettings settings, {
90 : bool isOverrride = false,
91 : }) {
92 5 : _currentRouteSettings = settings;
93 : }
94 :
95 : /// validate if the navigator key was assigned to one MaterialApp or CupertinoApp
96 10 : void _validateRouterState() {
97 : assert(
98 10 : _state != null,
99 : 'Invalid navigator state, make sure that you has been set the navigator key in your MaterialApp',
100 : );
101 : }
102 :
103 : /// use this method to destroy the current navigatorKey attached to the app
104 : ///
105 : /// useful for widget testing
106 8 : void dispose() {
107 8 : _appKey = null;
108 9 : _navigatorKey?.currentState?.dispose();
109 8 : _navigatorKey = null;
110 : }
111 :
112 : /// set the default transition for all pages
113 : ///
114 : /// [duration] set the transition duration for all pages
115 : /// by default duration is 300 milliseconds
116 4 : void setDefaultTransition(Transition transition, {Duration? duration}) {
117 4 : _transition = transition;
118 : if (duration != null) {
119 2 : _transitionDuration = duration;
120 : }
121 : }
122 :
123 : /// return null if default transition must be used
124 : ///
125 : /// [transitionDuration] is ignored when transition is equals to Transition.material or Transition.cupertino
126 : ///
127 : /// [backGestureEnabled] not works on Android if transition is Transition.material
128 6 : MeeduPageRoute<T>? _buildNamedRoute<T>({
129 : required String routeName,
130 : required Object? arguments,
131 : required bool backGestureEnabled,
132 : required Transition? transition,
133 : required Duration? transitionDuration,
134 : }) {
135 6 : _validateRouterState();
136 12 : if (appKey.currentWidget == null) {
137 : return null;
138 : }
139 6 : final app = appKey.currentWidget;
140 : Route<dynamic>? Function(RouteSettings)? onGenerateRoute;
141 3 : if (app is MaterialApp) {
142 2 : onGenerateRoute = app.onGenerateRoute;
143 1 : } else if (app is CupertinoApp) {
144 1 : onGenerateRoute = app.onGenerateRoute;
145 : }
146 :
147 : // if the MaterialApp or CupertinoApp is using onGenerateRoute custom
148 : // transitions are not allowed
149 : if (onGenerateRoute != null) {
150 : return null;
151 : }
152 :
153 3 : final mtransition = transition ?? _transition;
154 3 : if (mtransition == Transition.material ||
155 2 : mtransition == Transition.cupertino) {
156 : return null;
157 : }
158 2 : final mtransitionDuration = transitionDuration ?? _transitionDuration;
159 :
160 : // create a custom route with a custom transition
161 2 : return MeeduPageRoute<T>(
162 : routeName: routeName,
163 2 : settings: RouteSettings(
164 : name: routeName,
165 : arguments: arguments,
166 : ),
167 : maintainState: true,
168 : transitionDuration:
169 2 : mtransition == Transition.none ? Duration.zero : mtransitionDuration,
170 : fullscreenDialog: false,
171 : transition: mtransition,
172 : backGestureEnabled: backGestureEnabled,
173 2 : )..build();
174 : }
175 :
176 : /// Push the given [page] onto the navigator.
177 : ///
178 : /// [transitionDuration] will be [Duration.zero] when [transition] is equals to [Transition.none]
179 : ///
180 : /// [backGestureEnabled] ignored when [transition] is equals to [Transition.material] or [Transition.cupertino]
181 5 : Future<T?> push<T>(
182 : Widget page, {
183 : Object? arguments,
184 : bool maintainState = true,
185 : bool fullscreenDialog = false,
186 : Transition? transition,
187 : Duration? transitionDuration,
188 : bool backGestureEnabled = false,
189 : }) {
190 5 : _validateRouterState();
191 10 : return _navigator.push<T>(
192 : page,
193 : arguments: arguments,
194 : maintainState: maintainState,
195 : fullscreenDialog: fullscreenDialog,
196 : transition: transition,
197 : transitionDuration: transitionDuration,
198 : backGestureEnabled: backGestureEnabled,
199 : );
200 : }
201 :
202 : /// Replace the current [page] of the navigator by pushing the given [page] and then
203 : /// disposing the previous route once the new route has finished animating in.
204 : ///
205 : /// [transitionDuration] will be [Duration.zero] when [transition] is equals to [Transition.none]
206 : ///
207 : /// [backGestureEnabled] ignored when [transition] is [Transition.material] or [Transition.cupertino]
208 1 : Future<T?> pushReplacement<T extends Object?, TO extends Object?>(
209 : Widget page, {
210 : Object? arguments,
211 : bool maintainState = true,
212 : bool fullscreenDialog = false,
213 : Transition? transition,
214 : Duration transitionDuration = const Duration(milliseconds: 300),
215 : bool backGestureEnabled = false,
216 : TO? result,
217 : }) {
218 1 : _validateRouterState();
219 2 : return _navigator.pushReplacement<T, TO>(
220 : page,
221 : arguments: arguments,
222 : maintainState: maintainState,
223 : fullscreenDialog: fullscreenDialog,
224 : transition: transition,
225 : transitionDuration: transitionDuration,
226 : backGestureEnabled: backGestureEnabled,
227 : result: result,
228 : );
229 : }
230 :
231 : /// navigates to a new pages and remove until
232 : ///
233 : /// [transitionDuration] is ignored when transition is equals to Transition.material or Transition.cupertino
234 : ///
235 : /// [backGestureEnabled] not works on Android if transition is Transition.material
236 1 : Future<T?> pushAndRemoveUntil<T>(
237 : Widget page, {
238 : bool Function(Route<dynamic>)? predicate,
239 : Object? arguments,
240 : bool backGestureEnabled = true,
241 : bool maintainState = true,
242 : bool fullscreenDialog = false,
243 : Transition? transition,
244 : Duration? transitionDuration,
245 : }) {
246 1 : _validateRouterState();
247 2 : return _navigator.pushAndRemoveUntil<T>(
248 : page,
249 : arguments: arguments,
250 : maintainState: maintainState,
251 : fullscreenDialog: fullscreenDialog,
252 : transition: transition,
253 : transitionDuration: transitionDuration,
254 : backGestureEnabled: backGestureEnabled,
255 1 : predicate: predicate ?? (_) => false,
256 : );
257 : }
258 :
259 : /// Push a named route onto the navigator.
260 : ///
261 : /// [transitionDuration] is ignored when transition is equals to Transition.material or Transition.cupertino
262 : ///
263 : /// [backGestureEnabled] not works on Android if transition is Transition.material
264 3 : Future<T?> pushNamed<T>(
265 : String routeName, {
266 : Object? arguments,
267 : bool backGestureEnabled = true,
268 : Transition? transition,
269 : Duration? transitionDuration,
270 : }) {
271 3 : final route = _buildNamedRoute<T>(
272 : routeName: routeName,
273 : arguments: arguments,
274 : backGestureEnabled: backGestureEnabled,
275 : transition: transition,
276 : transitionDuration: transitionDuration,
277 : );
278 : if (route == null) {
279 4 : return _state!.pushNamed<T>(routeName, arguments: arguments);
280 : }
281 2 : return _state!.push<T>(route);
282 : }
283 :
284 : /// Pop the current route off the navigator and push a named route in its place
285 : ///
286 : /// [transitionDuration] is ignored when transition is equals to Transition.material or Transition.cupertino
287 : ///
288 : /// [backGestureEnabled] not works on Android if transition is Transition.material
289 2 : Future<T?> popAndPushNamed<T extends Object?, TO extends Object?>(
290 : String routeName, {
291 : Object? arguments,
292 : bool backGestureEnabled = true,
293 : Transition? transition,
294 : Duration? transitionDuration,
295 : TO? result,
296 : }) {
297 2 : final route = _buildNamedRoute<T>(
298 : routeName: routeName,
299 : arguments: arguments,
300 : backGestureEnabled: backGestureEnabled,
301 : transition: transition,
302 : transitionDuration: transitionDuration,
303 : );
304 : if (route == null) {
305 2 : return _state!.popAndPushNamed<T, TO>(
306 : routeName,
307 : arguments: arguments,
308 : result: result,
309 : );
310 : }
311 2 : _state!.pop<TO>(result);
312 2 : return _state!.push<T>(route);
313 : }
314 :
315 : /// replace the current page with a new route name
316 : ///
317 : /// [transitionDuration] is ignored when transition is equals to Transition.material or Transition.cupertino
318 : ///
319 : /// [backGestureEnabled] not works on Android if transition is Transition.material
320 3 : Future<T?> pushReplacementNamed<T extends Object?, TO extends Object?>(
321 : String routeName, {
322 : Object? arguments,
323 : bool backGestureEnabled = true,
324 : Transition? transition,
325 : Duration? transitionDuration,
326 : TO? result,
327 : }) {
328 3 : final route = _buildNamedRoute<T>(
329 : routeName: routeName,
330 : arguments: arguments,
331 : backGestureEnabled: backGestureEnabled,
332 : transition: transition,
333 : transitionDuration: transitionDuration,
334 : );
335 : if (route == null) {
336 4 : return _state!.pushReplacementNamed<T, TO>(
337 : routeName,
338 : arguments: arguments,
339 : result: result,
340 : );
341 : }
342 2 : return _state!.pushReplacement<T, TO>(route);
343 : }
344 :
345 : /// navigates to a new pages and remove until
346 : ///
347 : /// [transitionDuration] is ignored when transition is equals to Transition.material or Transition.cupertino
348 : ///
349 : /// [backGestureEnabled] not works on Android if transition is Transition.material
350 5 : Future<T?> pushNamedAndRemoveUntil<T>(
351 : String routeName, {
352 : bool Function(Route<dynamic>)? predicate,
353 : Object? arguments,
354 : bool backGestureEnabled = true,
355 : Transition? transition,
356 : Duration? transitionDuration,
357 : }) {
358 5 : final route = _buildNamedRoute<T>(
359 : routeName: routeName,
360 : arguments: arguments,
361 : backGestureEnabled: backGestureEnabled,
362 : transition: transition,
363 : transitionDuration: transitionDuration,
364 : );
365 : if (route == null) {
366 8 : return _state!.pushNamedAndRemoveUntil<T>(
367 : routeName,
368 4 : predicate ?? (_) => false,
369 : arguments: arguments,
370 : );
371 : }
372 2 : return _state!.pushAndRemoveUntil<T>(
373 : route,
374 1 : predicate ?? (_) => false,
375 : );
376 : }
377 :
378 : /// Consults the current route's [Route.willPop] method,
379 : /// and acts accordingly, potentially popping the route as a result;
380 : ///
381 : /// returns whether the pop request should be considered handled.
382 1 : Future<bool> maybePop<T>([T? result]) {
383 1 : _validateRouterState();
384 2 : return _navigator.maybePop(result);
385 : }
386 :
387 : /// remove the current page or dialog from the stack until `predicate`
388 3 : void pop<T>([T? result]) {
389 3 : _validateRouterState();
390 6 : _navigator.pop(result);
391 : }
392 :
393 : /// remove all pages in the stack until [predicate]
394 1 : void popUntil([bool Function(Route<dynamic>)? predicate]) {
395 1 : _validateRouterState();
396 2 : _navigator.popUntil(predicate ?? (_) => false);
397 : }
398 :
399 : /// return true if we can do pop
400 3 : bool canPop() {
401 3 : _validateRouterState();
402 6 : return _navigator.canPop();
403 : }
404 :
405 : /// return the arguments of the current page
406 1 : Object? get arguments {
407 2 : return settings.arguments;
408 : }
409 :
410 : /// return the current route settings
411 1 : RouteSettings get settings {
412 : assert(
413 3 : ContextlessNavigator.i.routeSettings != null,
414 : '''
415 : you need to define the navigator observer to allow
416 : flutter_meedu store the settings of the current route
417 :
418 : MaterialApp(
419 : key: router.appKey,
420 : initialRoute: '/',
421 : navigatorKey: router.navigatorKey,
422 : navigatorObservers: [
423 : router.observer, //<-- ADD THIS LINE
424 : ],
425 : routes: YOUR_ROUTES,
426 : ),
427 :
428 : ''',
429 : );
430 2 : return ContextlessNavigator.i.routeSettings!;
431 : }
432 :
433 : /// return the current context linked to the global navigatorKey
434 1 : BuildContext? get context {
435 2 : return navigatorKey.currentContext;
436 : }
437 : }
|