mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
Distinguish canceling a route pop from bubbling the pop up to the next level (#9165)
This commit is contained in:
parent
5cbd1a33a5
commit
3000c8bb59
|
@ -75,12 +75,12 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
|
|||
return null;
|
||||
}
|
||||
|
||||
Future<bool> _warnUserAboutInvalidData() {
|
||||
Future<bool> _warnUserAboutInvalidData() async {
|
||||
final FormState form = _formKey.currentState;
|
||||
if (!_formWasEdited || form.validate())
|
||||
return new Future<bool>.value(true);
|
||||
if (form == null || !_formWasEdited || form.validate())
|
||||
return true;
|
||||
|
||||
return showDialog<bool>(
|
||||
return await showDialog<bool>(
|
||||
context: context,
|
||||
child: new AlertDialog(
|
||||
title: new Text('This form has errors'),
|
||||
|
@ -96,7 +96,7 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
|
|||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
) ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'focus_manager.dart';
|
|||
import 'focus_scope.dart';
|
||||
import 'framework.dart';
|
||||
import 'overlay.dart';
|
||||
import 'routes.dart';
|
||||
import 'ticker_provider.dart';
|
||||
|
||||
/// An abstraction for an entry managed by a [Navigator].
|
||||
|
@ -72,7 +73,9 @@ abstract class Route<T> {
|
|||
/// See also:
|
||||
///
|
||||
/// * [Form], which provides an `onWillPop` callback that uses this mechanism.
|
||||
Future<bool> willPop() async => !isFirst;
|
||||
Future<RoutePopDisposition> willPop() async {
|
||||
return isFirst ? RoutePopDisposition.bubble : RoutePopDisposition.pop;
|
||||
}
|
||||
|
||||
/// A request was made to pop this route. If the route can handle it
|
||||
/// internally (e.g. because it has its own stack of internal state) then
|
||||
|
@ -969,8 +972,10 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
|
|||
Future<bool> maybePop([dynamic result]) async {
|
||||
final Route<dynamic> route = _history.last;
|
||||
assert(route._navigator == this);
|
||||
if (await route.willPop() && mounted) {
|
||||
pop(result);
|
||||
final RoutePopDisposition disposition = await route.willPop();
|
||||
if (disposition != RoutePopDisposition.bubble && mounted) {
|
||||
if (disposition == RoutePopDisposition.pop)
|
||||
pop(result);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -317,8 +317,10 @@ abstract class LocalHistoryRoute<T> extends Route<T> {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<bool> willPop() async {
|
||||
return willHandlePopInternally || await super.willPop();
|
||||
Future<RoutePopDisposition> willPop() async {
|
||||
if (willHandlePopInternally)
|
||||
return RoutePopDisposition.pop;
|
||||
return await super.willPop();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -385,6 +387,28 @@ class _ModalScopeStatus extends InheritedWidget {
|
|||
}
|
||||
}
|
||||
|
||||
/// Indicates whether the current route should be popped.
|
||||
///
|
||||
/// Used as the return value for [Route.willPop].
|
||||
enum RoutePopDisposition {
|
||||
/// Pop the route.
|
||||
///
|
||||
/// If [Route.willPop] returns [pop] then the back button will actually pop
|
||||
/// the current route.
|
||||
pop,
|
||||
|
||||
/// Do not pop the route.
|
||||
///
|
||||
/// If [Route.willPop] returns [doNotPop] then the back button will be ignored.
|
||||
doNotPop,
|
||||
|
||||
/// Delegate this to the next level of navigation.
|
||||
///
|
||||
/// If [Route.willPop] return [bubble] then the back button will be handled
|
||||
/// by the [SystemNavigator], which will usually close the application.
|
||||
bubble,
|
||||
}
|
||||
|
||||
/// Signature for a callback that verifies that it's OK to call [Navigator.pop].
|
||||
///
|
||||
/// Used by [Form.onWillPop], [ModalRoute.addScopedWillPopCallback], and
|
||||
|
@ -664,12 +688,12 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
|||
/// * [removeScopedWillPopCallback], which removes a callback from the list
|
||||
/// this method checks.
|
||||
@override
|
||||
Future<bool> willPop() async {
|
||||
Future<RoutePopDisposition> willPop() async {
|
||||
final _ModalScopeState scope = _scopeKey.currentState;
|
||||
assert(scope != null);
|
||||
for (WillPopCallback callback in new List<WillPopCallback>.from(scope._willPopCallbacks)) {
|
||||
if (!await callback())
|
||||
return false;
|
||||
return RoutePopDisposition.doNotPop;
|
||||
}
|
||||
return await super.willPop();
|
||||
}
|
||||
|
|
|
@ -113,6 +113,12 @@ void main() {
|
|||
await tester.pump(const Duration(seconds: 1));
|
||||
expect(find.text('Sample Page'), findsOneWidget);
|
||||
|
||||
// Use didPopRoute() to simulate the system back button. Check that
|
||||
// didPopRoute() indicates that the notification was handled.
|
||||
final dynamic widgetsAppState = tester.state(find.byType(WidgetsApp));
|
||||
expect(await widgetsAppState.didPopRoute(), isTrue);
|
||||
expect(find.text('Sample Page'), findsOneWidget);
|
||||
|
||||
willPopValue = true;
|
||||
await tester.tap(find.byTooltip('Back'));
|
||||
await tester.pump();
|
||||
|
|
Loading…
Reference in a new issue