| 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 | /// @docImport 'package:flutter/cupertino.dart'; |
| 6 | /// |
| 7 | /// @docImport 'form.dart'; |
| 8 | /// @docImport 'navigator_pop_handler.dart'; |
| 9 | library; |
| 10 | |
| 11 | import 'package:flutter/foundation.dart'; |
| 12 | |
| 13 | import 'framework.dart'; |
| 14 | import 'navigator.dart'; |
| 15 | import 'routes.dart'; |
| 16 | |
| 17 | /// A callback type for informing that a navigation pop has been invoked, |
| 18 | /// whether or not it was handled successfully. |
| 19 | /// |
| 20 | /// Accepts a didPop boolean indicating whether or not back navigation |
| 21 | /// succeeded. |
| 22 | @Deprecated( |
| 23 | 'Use PopInvokedWithResultCallback instead. ' |
| 24 | 'This feature was deprecated after v3.22.0-12.0.pre.' , |
| 25 | ) |
| 26 | typedef PopInvokedCallback = void Function(bool didPop); |
| 27 | |
| 28 | /// Manages back navigation gestures. |
| 29 | /// |
| 30 | /// The generic type should match or be a supertype of the generic type of the |
| 31 | /// enclosing [Route]. For example, if the enclosing Route is a |
| 32 | /// `MaterialPageRoute<int>`, you can define [PopScope] with `int` or any |
| 33 | /// supertype of `int`. |
| 34 | /// |
| 35 | /// The [canPop] parameter disables back gestures when set to `false`. |
| 36 | /// |
| 37 | /// The [onPopInvokedWithResult] parameter reports when pop navigation was attempted, and |
| 38 | /// `didPop` indicates whether or not the navigation was successful. The |
| 39 | /// `result` contains the pop result. |
| 40 | /// |
| 41 | /// Android has a system back gesture that is a swipe inward from near the edge |
| 42 | /// of the screen. It is recognized by Android before being passed to Flutter. |
| 43 | /// iOS has a similar gesture that is recognized in Flutter by |
| 44 | /// [CupertinoRouteTransitionMixin], not by iOS, and is therefore not a system |
| 45 | /// back gesture. |
| 46 | /// |
| 47 | /// If [canPop] is false, then a system back gesture will not pop the route off |
| 48 | /// of the enclosing [Navigator]. [onPopInvokedWithResult] will still be called, and |
| 49 | /// `didPop` will be `false`. On iOS when using [CupertinoRouteTransitionMixin] |
| 50 | /// with [canPop] set to false, no gesture will be detected at all, so |
| 51 | /// [onPopInvokedWithResult] will not be called. Programmatically attempting pop |
| 52 | /// navigation will also result in a call to [onPopInvokedWithResult], with `didPop` |
| 53 | /// indicating success or failure. |
| 54 | /// |
| 55 | /// If [canPop] is true, then a system back gesture will cause the enclosing |
| 56 | /// [Navigator] to receive a pop as usual. [onPopInvokedWithResult] will be called with |
| 57 | /// `didPop` as true, unless the pop failed for reasons unrelated to |
| 58 | /// [PopScope], in which case it will be false. |
| 59 | /// |
| 60 | /// {@tool dartpad} |
| 61 | /// This sample demonstrates showing a confirmation dialog before navigating |
| 62 | /// away from a page. |
| 63 | /// |
| 64 | /// ** See code in examples/api/lib/widgets/pop_scope/pop_scope.0.dart ** |
| 65 | /// {@end-tool} |
| 66 | /// |
| 67 | /// {@tool dartpad} |
| 68 | /// This sample demonstrates showing how to use PopScope to wrap widget that |
| 69 | /// may pop the page with a result. |
| 70 | /// |
| 71 | /// ** See code in examples/api/lib/widgets/pop_scope/pop_scope.1.dart ** |
| 72 | /// {@end-tool} |
| 73 | /// |
| 74 | /// See also: |
| 75 | /// |
| 76 | /// * [NavigatorPopHandler], which is a less verbose way to handle system back |
| 77 | /// gestures in simple cases of nested [Navigator]s. |
| 78 | /// * [Form.canPop] and [Form.onPopInvokedWithResult], which can be used to handle system |
| 79 | /// back gestures in the case of a form with unsaved data. |
| 80 | /// * [ModalRoute.registerPopEntry] and [ModalRoute.unregisterPopEntry], |
| 81 | /// which this widget uses to integrate with Flutter's navigation system. |
| 82 | @optionalTypeArgs |
| 83 | class PopScope<T> extends StatefulWidget { |
| 84 | /// Creates a widget that registers a callback to veto attempts by the user to |
| 85 | /// dismiss the enclosing [ModalRoute]. |
| 86 | const PopScope({ |
| 87 | super.key, |
| 88 | required this.child, |
| 89 | this.canPop = true, |
| 90 | this.onPopInvokedWithResult, |
| 91 | @Deprecated( |
| 92 | 'Use onPopInvokedWithResult instead. ' |
| 93 | 'This feature was deprecated after v3.22.0-12.0.pre.' , |
| 94 | ) |
| 95 | this.onPopInvoked, |
| 96 | }) : assert( |
| 97 | onPopInvokedWithResult == null || onPopInvoked == null, |
| 98 | 'onPopInvoked is deprecated, use onPopInvokedWithResult' , |
| 99 | ); |
| 100 | |
| 101 | /// The widget below this widget in the tree. |
| 102 | /// |
| 103 | /// {@macro flutter.widgets.ProxyWidget.child} |
| 104 | final Widget child; |
| 105 | |
| 106 | /// {@template flutter.widgets.PopScope.onPopInvokedWithResult} |
| 107 | /// Called after a route pop was handled. |
| 108 | /// {@endtemplate} |
| 109 | /// |
| 110 | /// It's not possible to prevent the pop from happening at the time that this |
| 111 | /// method is called; the pop has already happened. Use [canPop] to |
| 112 | /// disable pops in advance. |
| 113 | /// |
| 114 | /// This will still be called even when the pop is canceled. A pop is canceled |
| 115 | /// when the relevant [Route.popDisposition] returns false, such as when |
| 116 | /// [canPop] is set to false on a [PopScope]. The `didPop` parameter |
| 117 | /// indicates whether or not the back navigation actually happened |
| 118 | /// successfully. |
| 119 | /// |
| 120 | /// The `result` contains the pop result. |
| 121 | /// |
| 122 | /// See also: |
| 123 | /// |
| 124 | /// * [Route.onPopInvokedWithResult], which is similar. |
| 125 | final PopInvokedWithResultCallback<T>? onPopInvokedWithResult; |
| 126 | |
| 127 | /// Called after a route pop was handled. |
| 128 | /// |
| 129 | /// It's not possible to prevent the pop from happening at the time that this |
| 130 | /// method is called; the pop has already happened. Use [canPop] to |
| 131 | /// disable pops in advance. |
| 132 | /// |
| 133 | /// This will still be called even when the pop is canceled. A pop is canceled |
| 134 | /// when the relevant [Route.popDisposition] returns false, such as when |
| 135 | /// [canPop] is set to false on a [PopScope]. The `didPop` parameter |
| 136 | /// indicates whether or not the back navigation actually happened |
| 137 | /// successfully. |
| 138 | @Deprecated( |
| 139 | 'Use onPopInvokedWithResult instead. ' |
| 140 | 'This feature was deprecated after v3.22.0-12.0.pre.' , |
| 141 | ) |
| 142 | final PopInvokedCallback? onPopInvoked; |
| 143 | |
| 144 | void _callPopInvoked(bool didPop, T? result) { |
| 145 | if (onPopInvokedWithResult != null) { |
| 146 | onPopInvokedWithResult!(didPop, result); |
| 147 | return; |
| 148 | } |
| 149 | onPopInvoked?.call(didPop); |
| 150 | } |
| 151 | |
| 152 | /// {@template flutter.widgets.PopScope.canPop} |
| 153 | /// When false, blocks the current route from being popped. |
| 154 | /// |
| 155 | /// This includes the root route, where upon popping, the Flutter app would |
| 156 | /// exit. |
| 157 | /// |
| 158 | /// If multiple [PopScope] widgets appear in a route's widget subtree, then |
| 159 | /// each and every `canPop` must be `true` in order for the route to be |
| 160 | /// able to pop. |
| 161 | /// |
| 162 | /// [Android's predictive back](https://developer.android.com/guide/navigation/predictive-back-gesture) |
| 163 | /// feature will not animate when this boolean is false. |
| 164 | /// {@endtemplate} |
| 165 | final bool canPop; |
| 166 | |
| 167 | @override |
| 168 | State<PopScope<T>> createState() => _PopScopeState<T>(); |
| 169 | } |
| 170 | |
| 171 | class _PopScopeState<T> extends State<PopScope<T>> implements PopEntry<T> { |
| 172 | ModalRoute<dynamic>? _route; |
| 173 | |
| 174 | @override |
| 175 | void onPopInvoked(bool didPop) { |
| 176 | throw UnimplementedError(); |
| 177 | } |
| 178 | |
| 179 | @override |
| 180 | void onPopInvokedWithResult(bool didPop, T? result) { |
| 181 | widget._callPopInvoked(didPop, result); |
| 182 | } |
| 183 | |
| 184 | @override |
| 185 | late final ValueNotifier<bool> canPopNotifier; |
| 186 | |
| 187 | @override |
| 188 | void initState() { |
| 189 | super.initState(); |
| 190 | canPopNotifier = ValueNotifier<bool>(widget.canPop); |
| 191 | } |
| 192 | |
| 193 | @override |
| 194 | void didChangeDependencies() { |
| 195 | super.didChangeDependencies(); |
| 196 | final ModalRoute<dynamic>? nextRoute = ModalRoute.of(context); |
| 197 | if (nextRoute != _route) { |
| 198 | _route?.unregisterPopEntry(this); |
| 199 | _route = nextRoute; |
| 200 | _route?.registerPopEntry(this); |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | @override |
| 205 | void didUpdateWidget(PopScope<T> oldWidget) { |
| 206 | super.didUpdateWidget(oldWidget); |
| 207 | canPopNotifier.value = widget.canPop; |
| 208 | } |
| 209 | |
| 210 | @override |
| 211 | void dispose() { |
| 212 | _route?.unregisterPopEntry(this); |
| 213 | canPopNotifier.dispose(); |
| 214 | super.dispose(); |
| 215 | } |
| 216 | |
| 217 | @override |
| 218 | Widget build(BuildContext context) => widget.child; |
| 219 | } |
| 220 | |