diff --git a/packages/flutter/lib/src/cupertino/route.dart b/packages/flutter/lib/src/cupertino/route.dart index 6cfd781352..566e4b62d8 100644 --- a/packages/flutter/lib/src/cupertino/route.dart +++ b/packages/flutter/lib/src/cupertino/route.dart @@ -634,18 +634,22 @@ class _CupertinoBackGestureController { controller.animateBack(0.0, duration: Duration(milliseconds: droppedPageBackAnimationTime), curve: animationCurve); } - assert(controller.isAnimating); - assert(controller.status != AnimationStatus.completed); - assert(controller.status != AnimationStatus.dismissed); + if (controller.isAnimating) { + // Don't end the gesture until the transition completes. + _animating = true; + controller.addStatusListener(_handleStatusChanged); + } else { + // Animate calls could return inline if already at the target destination + // value. + return _handleStatusChanged(controller.status); + } - // Don't end the gesture until the transition completes. - _animating = true; - controller.addStatusListener(_handleStatusChanged); } void _handleStatusChanged(AnimationStatus status) { - assert(_animating); - controller.removeStatusListener(_handleStatusChanged); + if (_animating) { + controller.removeStatusListener(_handleStatusChanged); + } _animating = false; if (status == AnimationStatus.dismissed) navigator.pop(); // this will cause the route to get disposed, which will dispose us diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart index 8479791cf9..cf00273a30 100644 --- a/packages/flutter/lib/src/widgets/routes.dart +++ b/packages/flutter/lib/src/widgets/routes.dart @@ -155,7 +155,6 @@ abstract class TransitionRoute extends OverlayRoute { overlayEntries.first.opaque = false; break; case AnimationStatus.dismissed: - assert(!overlayEntries.first.opaque); // We might still be the current route if a subclass is controlling the // the transition and hits the dismissed status. For example, the iOS // back gesture drives this animation to the dismissed status before diff --git a/packages/flutter/test/cupertino/page_test.dart b/packages/flutter/test/cupertino/page_test.dart index e0bc7a715a..a0120427ba 100644 --- a/packages/flutter/test/cupertino/page_test.dart +++ b/packages/flutter/test/cupertino/page_test.dart @@ -427,6 +427,74 @@ void main() { expect(find.text('Page 1'), isOnstage); expect(find.text('Page 2'), isOnstage); }); + + testWidgets('test edge swipe then drop back at starting point works', (WidgetTester tester) async { + await tester.pumpWidget( + CupertinoApp( + onGenerateRoute: (RouteSettings settings) { + return CupertinoPageRoute( + settings: settings, + builder: (BuildContext context) { + final String pageNumber = settings.name == '/' ? '1' : '2'; + return Center(child: Text('Page $pageNumber')); + } + ); + }, + ), + ); + + tester.state(find.byType(Navigator)).pushNamed('/next'); + + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + expect(find.text('Page 1'), findsNothing); + expect(find.text('Page 2'), isOnstage); + + final TestGesture gesture = await tester.startGesture(const Offset(5, 200)); + await gesture.moveBy(const Offset(300, 0)); + await tester.pump(); + // Bring it exactly back such that there's nothing to animate when releasing. + await gesture.moveBy(const Offset(-300, 0)); + await gesture.up(); + await tester.pump(); + + expect(find.text('Page 1'), findsNothing); + expect(find.text('Page 2'), isOnstage); + }); + + testWidgets('test edge swipe then drop back at ending point works', (WidgetTester tester) async { + await tester.pumpWidget( + CupertinoApp( + onGenerateRoute: (RouteSettings settings) { + return CupertinoPageRoute( + settings: settings, + builder: (BuildContext context) { + final String pageNumber = settings.name == '/' ? '1' : '2'; + return Center(child: Text('Page $pageNumber')); + } + ); + }, + ), + ); + + tester.state(find.byType(Navigator)).pushNamed('/next'); + + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + expect(find.text('Page 1'), findsNothing); + expect(find.text('Page 2'), isOnstage); + + final TestGesture gesture = await tester.startGesture(const Offset(5, 200)); + // The width of the page. + await gesture.moveBy(const Offset(800, 0)); + await gesture.up(); + await tester.pump(); + + expect(find.text('Page 1'), isOnstage); + expect(find.text('Page 2'), findsNothing); + }); } class RtlOverrideWidgetsDelegate extends LocalizationsDelegate { diff --git a/packages/flutter/test/material/page_test.dart b/packages/flutter/test/material/page_test.dart index 9b039bfd44..172bab300c 100644 --- a/packages/flutter/test/material/page_test.dart +++ b/packages/flutter/test/material/page_test.dart @@ -470,4 +470,74 @@ void main() { // An exception should've been thrown because the `builder` returned null. expect(tester.takeException(), isInstanceOf()); }); + + testWidgets('test iOS edge swipe then drop back at starting point works', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(platform: TargetPlatform.iOS), + onGenerateRoute: (RouteSettings settings) { + return MaterialPageRoute( + settings: settings, + builder: (BuildContext context) { + final String pageNumber = settings.name == '/' ? '1' : '2'; + return Center(child: Text('Page $pageNumber')); + } + ); + }, + ), + ); + + tester.state(find.byType(Navigator)).pushNamed('/next'); + + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + expect(find.text('Page 1'), findsNothing); + expect(find.text('Page 2'), isOnstage); + + final TestGesture gesture = await tester.startGesture(const Offset(5, 200)); + await gesture.moveBy(const Offset(300, 0)); + await tester.pump(); + // Bring it exactly back such that there's nothing to animate when releasing. + await gesture.moveBy(const Offset(-300, 0)); + await gesture.up(); + await tester.pump(); + + expect(find.text('Page 1'), findsNothing); + expect(find.text('Page 2'), isOnstage); + }); + + testWidgets('test iOS edge swipe then drop back at ending point works', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(platform: TargetPlatform.iOS), + onGenerateRoute: (RouteSettings settings) { + return MaterialPageRoute( + settings: settings, + builder: (BuildContext context) { + final String pageNumber = settings.name == '/' ? '1' : '2'; + return Center(child: Text('Page $pageNumber')); + } + ); + }, + ), + ); + + tester.state(find.byType(Navigator)).pushNamed('/next'); + + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + expect(find.text('Page 1'), findsNothing); + expect(find.text('Page 2'), isOnstage); + + final TestGesture gesture = await tester.startGesture(const Offset(5, 200)); + // The width of the page. + await gesture.moveBy(const Offset(800, 0)); + await gesture.up(); + await tester.pump(); + + expect(find.text('Page 1'), isOnstage); + expect(find.text('Page 2'), findsNothing); + }); }