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
5import 'framework.dart';
6import 'navigator.dart';
7import 'notification_listener.dart';
8import '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
40class 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
102class _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].
139typedef PopResultCallback<T> = void Function(T? result);
140