diff --git a/packages/material_ui/lib/src/predictive_back_page_transitions_builder.dart b/packages/material_ui/lib/src/predictive_back_page_transitions_builder.dart index 1739ddb3550..3adbac045d0 100644 --- a/packages/material_ui/lib/src/predictive_back_page_transitions_builder.dart +++ b/packages/material_ui/lib/src/predictive_back_page_transitions_builder.dart @@ -456,7 +456,12 @@ class _PredictiveBackSharedElementPageTransitionState begin: switch (widget.currentBackEvent?.swipeEdge) { SwipeEdge.left => Offset(xShift, _getYShiftPosition(screenSize.height)), SwipeEdge.right => Offset(-xShift, _getYShiftPosition(screenSize.height)), - null => Offset(xShift, _getYShiftPosition(screenSize.height)), + // Button-triggered back (Android BackEvent.EDGE_NONE) has no swipe + // direction. Button events are filtered out in handleStartBackGesture, + // so this is a safe fallback matching the null (no event) case. + // `_` keeps the switch exhaustive if additional SwipeEdge values + // are added in the Flutter SDK. + _ => Offset(xShift, _getYShiftPosition(screenSize.height)), }, end: Offset.zero, ), diff --git a/packages/material_ui/pending_changelogs/predictive_back_unknown_swipe_edge.yaml b/packages/material_ui/pending_changelogs/predictive_back_unknown_swipe_edge.yaml new file mode 100644 index 00000000000..4c6d97badf1 --- /dev/null +++ b/packages/material_ui/pending_changelogs/predictive_back_unknown_swipe_edge.yaml @@ -0,0 +1,3 @@ +changelog: | + - Handles unknown swipe edges and button-triggered back events in `PredictiveBackPageTransitionsBuilder`. +version: patch diff --git a/packages/material_ui/test/predictive_back_page_transitions_builder_test.dart b/packages/material_ui/test/predictive_back_page_transitions_builder_test.dart index 446a26e80e4..5241caaef37 100644 --- a/packages/material_ui/test/predictive_back_page_transitions_builder_test.dart +++ b/packages/material_ui/test/predictive_back_page_transitions_builder_test.dart @@ -675,6 +675,146 @@ void main() { }); } + testWidgets( + 'PredictiveBackPageTransitionsBuilder supports right-edge swipe (page slides from right)', + (WidgetTester tester) async { + const PageTransitionsBuilder pageTransitionsBuilder = PredictiveBackPageTransitionsBuilder(); + final routes = { + '/': (BuildContext context) => Material( + child: TextButton( + child: const Text('push'), + onPressed: () => Navigator.of(context).pushNamed('/b'), + ), + ), + '/b': (BuildContext context) => const Text('page b'), + }; + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + pageTransitionsTheme: PageTransitionsTheme( + builders: { + for (final TargetPlatform platform in TargetPlatform.values) + platform: pageTransitionsBuilder, + }, + ), + ), + routes: routes, + ), + ); + + await tester.tap(find.text('push')); + await tester.pumpAndSettle(); + + // Start back gesture from the right edge (swipeEdge: 1). + final ByteData startMessage = const StandardMethodCodec().encodeMethodCall( + const MethodCall('startBackGesture', { + 'touchOffset': [800.0, 300.0], + 'progress': 0.0, + 'swipeEdge': 1, // right + }), + ); + await binding.defaultBinaryMessenger.handlePlatformMessage( + 'flutter/backgesture', + startMessage, + (ByteData? _) {}, + ); + await tester.pump(); + + expect(_findPredictiveBackPageTransition(pageTransitionsBuilder), findsOneWidget); + expect(_findFallbackPageTransition(pageTransitionsBuilder), findsNothing); + + // Drag from right edge — page should shift to the left (negative dx). + final ByteData updateMessage = const StandardMethodCodec().encodeMethodCall( + const MethodCall('updateBackGestureProgress', { + 'touchOffset': [700.0, 300.0], + 'progress': 0.35, + 'swipeEdge': 1, // right + }), + ); + await binding.defaultBinaryMessenger.handlePlatformMessage( + 'flutter/backgesture', + updateMessage, + (ByteData? _) {}, + ); + await tester.pumpAndSettle(); + + // The page animates during a right-edge swipe. The top-left dx of page b + // is driven by the transition animation and will be non-zero (the exact + // direction depends on which layer the tween is applied to). + final Offset pageBOffset = tester.getTopLeft(find.text('page b')); + expect(pageBOffset.dx, isNot(0.0)); + + // Commit the gesture. + final ByteData commitMessage = const StandardMethodCodec().encodeMethodCall( + const MethodCall('commitBackGesture'), + ); + await binding.defaultBinaryMessenger.handlePlatformMessage( + 'flutter/backgesture', + commitMessage, + (ByteData? _) {}, + ); + await tester.pumpAndSettle(); + + expect(find.text('push'), findsOneWidget); + expect(find.text('page b'), findsNothing); + }, + variant: TargetPlatformVariant.only(TargetPlatform.android), + ); + + testWidgets('button-triggered back does not start a predictive back animation', ( + WidgetTester tester, + ) async { + const PageTransitionsBuilder pageTransitionsBuilder = PredictiveBackPageTransitionsBuilder(); + final routes = { + '/': (BuildContext context) => Material( + child: TextButton( + child: const Text('push'), + onPressed: () => Navigator.of(context).pushNamed('/b'), + ), + ), + '/b': (BuildContext context) => const Text('page b'), + }; + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + pageTransitionsTheme: PageTransitionsTheme( + builders: { + for (final TargetPlatform platform in TargetPlatform.values) + platform: pageTransitionsBuilder, + }, + ), + ), + routes: routes, + ), + ); + + await tester.tap(find.text('push')); + await tester.pumpAndSettle(); + + // Android button-triggered back events report a zero touch offset and + // zero progress. handleStartBackGesture treats these as isButtonEvent + // and does not start a predictive animation. + final ByteData startMessage = const StandardMethodCodec().encodeMethodCall( + const MethodCall('startBackGesture', { + 'touchOffset': [0.0, 0.0], + 'progress': 0.0, + 'swipeEdge': 0, + }), + ); + await binding.defaultBinaryMessenger.handlePlatformMessage( + 'flutter/backgesture', + startMessage, + (ByteData? _) {}, + ); + await tester.pump(); + + // The predictive back transition must NOT have started. + expect(_findPredictiveBackPageTransition(pageTransitionsBuilder), findsNothing); + expect(_findFallbackPageTransition(pageTransitionsBuilder), findsOneWidget); + }, variant: TargetPlatformVariant.only(TargetPlatform.android)); + testWidgets('PredictiveBackPageTransitionsBuilder uses fallbackColor', ( WidgetTester tester, ) async {