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> { ...@@ -158,7 +158,7 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
/// True if an iOS-style back swipe pop gesture is currently underway for [route]. /// 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: /// See also:
/// ///
...@@ -247,6 +247,8 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> { ...@@ -247,6 +247,8 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
return _CupertinoBackGestureController<T>( return _CupertinoBackGestureController<T>(
navigator: route.navigator!, navigator: route.navigator!,
getIsCurrent: () => route.isCurrent,
getIsActive: () => route.isActive,
controller: route.controller!, // protected access controller: route.controller!, // protected access
); );
} }
...@@ -293,6 +295,8 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> { ...@@ -293,6 +295,8 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
child: _CupertinoBackGestureDetector<T>( child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route), enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route), onStartPopGesture: () => _startPopGesture<T>(route),
getIsCurrent: () => route.isCurrent,
getIsActive: () => route.isActive,
child: child, child: child,
), ),
); );
...@@ -596,6 +600,8 @@ class _CupertinoBackGestureDetector<T> extends StatefulWidget { ...@@ -596,6 +600,8 @@ class _CupertinoBackGestureDetector<T> extends StatefulWidget {
required this.enabledCallback, required this.enabledCallback,
required this.onStartPopGesture, required this.onStartPopGesture,
required this.child, required this.child,
required this.getIsActive,
required this.getIsCurrent,
}); });
final Widget child; final Widget child;
...@@ -604,6 +610,9 @@ class _CupertinoBackGestureDetector<T> extends StatefulWidget { ...@@ -604,6 +610,9 @@ class _CupertinoBackGestureDetector<T> extends StatefulWidget {
final ValueGetter<_CupertinoBackGestureController<T>> onStartPopGesture; final ValueGetter<_CupertinoBackGestureController<T>> onStartPopGesture;
final ValueGetter<bool> getIsActive;
final ValueGetter<bool> getIsCurrent;
@override @override
_CupertinoBackGestureDetectorState<T> createState() => _CupertinoBackGestureDetectorState<T>(); _CupertinoBackGestureDetectorState<T> createState() => _CupertinoBackGestureDetectorState<T>();
} }
...@@ -724,12 +733,16 @@ class _CupertinoBackGestureController<T> { ...@@ -724,12 +733,16 @@ class _CupertinoBackGestureController<T> {
_CupertinoBackGestureController({ _CupertinoBackGestureController({
required this.navigator, required this.navigator,
required this.controller, required this.controller,
required this.getIsActive,
required this.getIsCurrent,
}) { }) {
navigator.didStartUserGesture(); navigator.didStartUserGesture();
} }
final AnimationController controller; final AnimationController controller;
final NavigatorState navigator; final NavigatorState navigator;
final ValueGetter<bool> getIsActive;
final ValueGetter<bool> getIsCurrent;
/// The drag gesture has changed by [fractionalDelta]. The total range of the /// The drag gesture has changed by [fractionalDelta]. The total range of the
/// drag should be 0.0 to 1.0. /// drag should be 0.0 to 1.0.
...@@ -745,12 +758,21 @@ class _CupertinoBackGestureController<T> { ...@@ -745,12 +758,21 @@ class _CupertinoBackGestureController<T> {
// This curve has been determined through rigorously eyeballing native iOS // This curve has been determined through rigorously eyeballing native iOS
// animations. // animations.
const Curve animationCurve = Curves.fastLinearToSlowEaseIn; const Curve animationCurve = Curves.fastLinearToSlowEaseIn;
final bool isCurrent = getIsCurrent();
final bool animateForward; final bool animateForward;
// If the user releases the page before mid screen with sufficient velocity, if (!isCurrent) {
// or after mid screen, we should animate the page out. Otherwise, the page // If the page has already been navigated away from, then the animation
// should be animated back in. // direction depends on whether or not it's still in the navigation stack,
if (velocity.abs() >= _kMinFlingVelocity) { // 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.
animateForward = velocity <= 0; animateForward = velocity <= 0;
} else { } else {
animateForward = controller.value > 0.5; animateForward = controller.value > 0.5;
...@@ -766,8 +788,10 @@ class _CupertinoBackGestureController<T> { ...@@ -766,8 +788,10 @@ class _CupertinoBackGestureController<T> {
); );
controller.animateTo(1.0, duration: Duration(milliseconds: droppedPageForwardAnimationTime), curve: animationCurve); controller.animateTo(1.0, duration: Duration(milliseconds: droppedPageForwardAnimationTime), curve: animationCurve);
} else { } else {
// This route is destined to pop at this point. Reuse navigator's pop. if (isCurrent) {
navigator.pop(); // 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. // The popping may have finished inline if already at the target destination.
if (controller.isAnimating) { if (controller.isAnimating) {
......
...@@ -2919,7 +2919,7 @@ class _RouteEntry extends RouteTransitionRecord { ...@@ -2919,7 +2919,7 @@ class _RouteEntry extends RouteTransitionRecord {
initialState == _RouteLifecycle.pushReplace || initialState == _RouteLifecycle.pushReplace ||
initialState == _RouteLifecycle.replace, initialState == _RouteLifecycle.replace,
), ),
currentState = initialState { currentState = initialState {
// TODO(polina-c): stop duplicating code across disposables // TODO(polina-c): stop duplicating code across disposables
// https://github.com/flutter/flutter/issues/137435 // https://github.com/flutter/flutter/issues/137435
if (kFlutterMemoryAllocationsEnabled) { if (kFlutterMemoryAllocationsEnabled) {
......
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