diff --git a/packages/flutter/lib/src/widgets/heroes.dart b/packages/flutter/lib/src/widgets/heroes.dart index 7253e7bbf99..ab64df09525 100644 --- a/packages/flutter/lib/src/widgets/heroes.dart +++ b/packages/flutter/lib/src/widgets/heroes.dart @@ -533,6 +533,14 @@ class _HeroFlight { } void _handleAnimationUpdate(AnimationStatus status) { + // The animation will not finish until the user lifts their finger, so we + // should ignore the status update if the gesture is in progress. + // + // This also relies on the animation to update its status at the end of the + // gesture. See the _CupertinoBackGestureController.dragEnd for how + // cupertino page route achieves that. + if (manifest.fromRoute?.navigator?.userGestureInProgress == true) + return; if (status == AnimationStatus.completed || status == AnimationStatus.dismissed) { _proxyAnimation.parent = null; diff --git a/packages/flutter/test/cupertino/route_test.dart b/packages/flutter/test/cupertino/route_test.dart index 2215f70d55c..bd083fa3be4 100644 --- a/packages/flutter/test/cupertino/route_test.dart +++ b/packages/flutter/test/cupertino/route_test.dart @@ -1133,6 +1133,76 @@ void main() { expect(nestedObserver.popupCount, 0); }); + testWidgets('back swipe to screen edges does not dismiss the hero animation', (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + final UniqueKey container = UniqueKey(); + await tester.pumpWidget(CupertinoApp( + navigatorKey: navigator, + routes: { + '/': (BuildContext context) { + return CupertinoPageScaffold( + child: Center( + child: Hero( + tag: 'tag', + transitionOnUserGestures: true, + child: Container(key: container, height: 150.0, width: 150.0) + ), + ) + ); + }, + '/page2': (BuildContext context) { + return CupertinoPageScaffold( + child: Center( + child: Padding( + padding: const EdgeInsets.fromLTRB(100.0, 0.0, 0.0, 0.0), + child: Hero( + tag: 'tag', + transitionOnUserGestures: true, + child: Container(key: container, height: 150.0, width: 150.0) + ) + ), + ) + ); + } + }, + )); + + RenderBox box = tester.renderObject(find.byKey(container)) as RenderBox; + final double initialPosition = box.localToGlobal(Offset.zero).dx; + + navigator.currentState.pushNamed('/page2'); + await tester.pumpAndSettle(); + box = tester.renderObject(find.byKey(container)) as RenderBox; + final double finalPosition = box.localToGlobal(Offset.zero).dx; + + final TestGesture gesture = await tester.startGesture(const Offset(5, 300)); + await gesture.moveBy(const Offset(200, 0)); + await tester.pump(); + box = tester.renderObject(find.byKey(container)) as RenderBox; + final double firstPosition = box.localToGlobal(Offset.zero).dx; + // Checks the hero is in-transit. + expect(finalPosition, greaterThan(firstPosition)); + expect(firstPosition, greaterThan(initialPosition)); + + // Goes back to final position. + await gesture.moveBy(const Offset(-200, 0)); + await tester.pump(); + box = tester.renderObject(find.byKey(container)) as RenderBox; + final double secondPosition = box.localToGlobal(Offset.zero).dx; + // There will be a small difference. + expect(finalPosition - secondPosition, lessThan(0.001)); + + await gesture.moveBy(const Offset(400, 0)); + await tester.pump(); + box = tester.renderObject(find.byKey(container)) as RenderBox; + final double thirdPosition = box.localToGlobal(Offset.zero).dx; + // Checks the hero is still in-transit and moves further away from the first + // position. + expect(finalPosition, greaterThan(thirdPosition)); + expect(thirdPosition, greaterThan(initialPosition)); + expect(firstPosition, greaterThan(thirdPosition)); + }); + testWidgets('showCupertinoModalPopup uses nested navigator if useRootNavigator is false', (WidgetTester tester) async { final PopupObserver rootObserver = PopupObserver(); final PopupObserver nestedObserver = PopupObserver();