| 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | import 'framework.dart'; |
| 6 | import 'navigator.dart'; |
| 7 | import 'notification_listener.dart'; |
| 8 | import 'pop_scope.dart'; |
| 9 | |
| 10 | /// Enables the handling of system back gestures. |
| 11 | /// |
| 12 | /// Typically wraps a nested [Navigator] widget and allows it to handle system |
| 13 | /// back gestures in the [onPopWithResult] callback. |
| 14 | /// |
| 15 | /// The type parameter `<T>` indicates the type of the [Route]'s return value, |
| 16 | /// which is passed to [onPopWithResult]. |
| 17 | /// |
| 18 | /// {@tool dartpad} |
| 19 | /// This sample demonstrates how to use this widget to properly handle system |
| 20 | /// back gestures when using nested [Navigator]s. |
| 21 | /// |
| 22 | /// ** See code in examples/api/lib/widgets/navigator_pop_handler/navigator_pop_handler.0.dart ** |
| 23 | /// {@end-tool} |
| 24 | /// |
| 25 | /// {@tool dartpad} |
| 26 | /// This sample demonstrates how to use this widget to properly handle system |
| 27 | /// back gestures with a bottom navigation bar whose tabs each have their own |
| 28 | /// nested [Navigator]s. |
| 29 | /// |
| 30 | /// ** See code in examples/api/lib/widgets/navigator_pop_handler/navigator_pop_handler.1.dart ** |
| 31 | /// {@end-tool} |
| 32 | /// |
| 33 | /// See also: |
| 34 | /// |
| 35 | /// * [PopScope], which allows toggling the ability of a [Navigator] to |
| 36 | /// handle pops. |
| 37 | /// * [NavigationNotification], which indicates whether a [Navigator] in a |
| 38 | /// subtree can handle pops. |
| 39 | @optionalTypeArgs |
| 40 | class NavigatorPopHandler<T> extends StatefulWidget { |
| 41 | /// Creates an instance of [NavigatorPopHandler]. |
| 42 | const NavigatorPopHandler({ |
| 43 | super.key, |
| 44 | @Deprecated( |
| 45 | 'Use onPopWithResult instead. ' |
| 46 | 'This feature was deprecated after v3.26.0-0.1.pre.' , |
| 47 | ) |
| 48 | this.onPop, |
| 49 | this.onPopWithResult, |
| 50 | this.enabled = true, |
| 51 | required this.child, |
| 52 | }) : assert(onPop == null || onPopWithResult == null); |
| 53 | |
| 54 | /// The widget to place below this in the widget tree. |
| 55 | /// |
| 56 | /// Typically this is a [Navigator] that will handle the pop when |
| 57 | /// [onPopWithResult] is called. |
| 58 | final Widget child; |
| 59 | |
| 60 | /// Whether this widget's ability to handle system back gestures is enabled or |
| 61 | /// disabled. |
| 62 | /// |
| 63 | /// When false, there will be no effect on system back gestures. If provided, |
| 64 | /// [onPop] will still be called. |
| 65 | /// |
| 66 | /// This can be used, for example, when the nested [Navigator] is no longer |
| 67 | /// active but remains in the widget tree, such as in an inactive tab. |
| 68 | /// |
| 69 | /// Defaults to true. |
| 70 | final bool enabled; |
| 71 | |
| 72 | /// Called when a handleable pop event happens. |
| 73 | /// |
| 74 | /// For example, a pop is handleable when a [Navigator] in [child] has |
| 75 | /// multiple routes on its stack. It's not handleable when it has only a |
| 76 | /// single route, and so [onPop] will not be called. |
| 77 | /// |
| 78 | /// Typically this is used to pop the [Navigator] in [child]. See the sample |
| 79 | /// code on [NavigatorPopHandler] for a full example of this. |
| 80 | @Deprecated( |
| 81 | 'Use onPopWithResult instead. ' |
| 82 | 'This feature was deprecated after v3.26.0-0.1.pre.' , |
| 83 | ) |
| 84 | final VoidCallback? onPop; |
| 85 | |
| 86 | /// Called when a handleable pop event happens. |
| 87 | /// |
| 88 | /// For example, a pop is handleable when a [Navigator] in [child] has |
| 89 | /// multiple routes on its stack. It's not handleable when it has only a |
| 90 | /// single route, and so [onPopWithResult] will not be called. |
| 91 | /// |
| 92 | /// Typically this is used to pop the [Navigator] in [child]. See the sample |
| 93 | /// code on [NavigatorPopHandler] for a full example of this. |
| 94 | /// |
| 95 | /// The passed `result` is the result of the popped [Route]. |
| 96 | final PopResultCallback<T>? onPopWithResult; |
| 97 | |
| 98 | @override |
| 99 | State<NavigatorPopHandler<T>> createState() => _NavigatorPopHandlerState<T>(); |
| 100 | } |
| 101 | |
| 102 | class _NavigatorPopHandlerState<T> extends State<NavigatorPopHandler<T>> { |
| 103 | bool _canPop = true; |
| 104 | |
| 105 | @override |
| 106 | Widget build(BuildContext context) { |
| 107 | // When the widget subtree indicates it can handle a pop, disable popping |
| 108 | // here, so that it can be manually handled in canPop. |
| 109 | return PopScope<T>( |
| 110 | canPop: !widget.enabled || _canPop, |
| 111 | onPopInvokedWithResult: (bool didPop, T? result) { |
| 112 | if (didPop) { |
| 113 | return; |
| 114 | } |
| 115 | widget.onPop?.call(); |
| 116 | widget.onPopWithResult?.call(result); |
| 117 | }, |
| 118 | // Listen to changes in the navigation stack in the widget subtree. |
| 119 | child: NotificationListener<NavigationNotification>( |
| 120 | onNotification: (NavigationNotification notification) { |
| 121 | // If this subtree cannot handle pop, then set canPop to true so |
| 122 | // that our PopScope will allow the Navigator higher in the tree to |
| 123 | // handle the pop instead. |
| 124 | final bool nextCanPop = !notification.canHandlePop; |
| 125 | if (nextCanPop != _canPop) { |
| 126 | setState(() { |
| 127 | _canPop = nextCanPop; |
| 128 | }); |
| 129 | } |
| 130 | return false; |
| 131 | }, |
| 132 | child: widget.child, |
| 133 | ), |
| 134 | ); |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | /// A signature for a function that is passed the result of a [Route]. |
| 139 | typedef PopResultCallback<T> = void Function(T? result); |
| 140 | |