Unverified Commit 3280be93 authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

Support navigation during a Cupertino back gesture (#142248)

Fixes a bug where programmatically navigating during an iOS back gesture caused the app to enter an unstable state.
parent ac7879e2
......@@ -158,7 +158,7 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
/// True if an iOS-style back swipe pop gesture is currently underway for [route].
///
/// This just check the route's [NavigatorState.userGestureInProgress].
/// This just checks the route's [NavigatorState.userGestureInProgress].
///
/// See also:
///
......@@ -247,6 +247,8 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
return _CupertinoBackGestureController<T>(
navigator: route.navigator!,
getIsCurrent: () => route.isCurrent,
getIsActive: () => route.isActive,
controller: route.controller!, // protected access
);
}
......@@ -293,6 +295,8 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
getIsCurrent: () => route.isCurrent,
getIsActive: () => route.isActive,
child: child,
),
);
......@@ -596,6 +600,8 @@ class _CupertinoBackGestureDetector<T> extends StatefulWidget {
required this.enabledCallback,
required this.onStartPopGesture,
required this.child,
required this.getIsActive,
required this.getIsCurrent,
});
final Widget child;
......@@ -604,6 +610,9 @@ class _CupertinoBackGestureDetector<T> extends StatefulWidget {
final ValueGetter<_CupertinoBackGestureController<T>> onStartPopGesture;
final ValueGetter<bool> getIsActive;
final ValueGetter<bool> getIsCurrent;
@override
_CupertinoBackGestureDetectorState<T> createState() => _CupertinoBackGestureDetectorState<T>();
}
......@@ -724,12 +733,16 @@ class _CupertinoBackGestureController<T> {
_CupertinoBackGestureController({
required this.navigator,
required this.controller,
required this.getIsActive,
required this.getIsCurrent,
}) {
navigator.didStartUserGesture();
}
final AnimationController controller;
final NavigatorState navigator;
final ValueGetter<bool> getIsActive;
final ValueGetter<bool> getIsCurrent;
/// The drag gesture has changed by [fractionalDelta]. The total range of the
/// drag should be 0.0 to 1.0.
......@@ -745,12 +758,21 @@ class _CupertinoBackGestureController<T> {
// This curve has been determined through rigorously eyeballing native iOS
// animations.
const Curve animationCurve = Curves.fastLinearToSlowEaseIn;
final bool isCurrent = getIsCurrent();
final bool animateForward;
if (!isCurrent) {
// If the page has already been navigated away from, then the animation
// direction depends on whether or not it's still in the navigation stack,
// regardless of velocity or drag position. For example, if a route is
// being slowly dragged back by just a few pixels, but then a programmatic
// pop occurs, the route should still be animated off the screen.
// See https://github.com/flutter/flutter/issues/141268.
animateForward = getIsActive();
} else if (velocity.abs() >= _kMinFlingVelocity) {
// If the user releases the page before mid screen with sufficient velocity,
// or after mid screen, we should animate the page out. Otherwise, the page
// should be animated back in.
if (velocity.abs() >= _kMinFlingVelocity) {
animateForward = velocity <= 0;
} else {
animateForward = controller.value > 0.5;
......@@ -766,8 +788,10 @@ class _CupertinoBackGestureController<T> {
);
controller.animateTo(1.0, duration: Duration(milliseconds: droppedPageForwardAnimationTime), curve: animationCurve);
} else {
if (isCurrent) {
// This route is destined to pop at this point. Reuse navigator's pop.
navigator.pop();
}
// The popping may have finished inline if already at the target destination.
if (controller.isAnimating) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment