From 4af88aae06526f31f23011f30bc0e5e8d90af90f Mon Sep 17 00:00:00 2001 From: Muhammad Kamel Date: Wed, 19 Aug 2026 00:15:14 +0300 Subject: [PATCH] [material_ui] Handle unknown swipe edges in predictive back transitions Treat unknown or button-triggered swipe edges as a no-direction fallback so the switch stays exhaustive when the Flutter SDK adds SwipeEdge values. --- packages/material_ui/CHANGELOG.md | 5 + ...dictive_back_page_transitions_builder.dart | 7 +- packages/material_ui/pubspec.yaml | 2 +- ...ve_back_page_transitions_builder_test.dart | 150 ++++++++++++++++++ 4 files changed, 162 insertions(+), 2 deletions(-) diff --git a/packages/material_ui/CHANGELOG.md b/packages/material_ui/CHANGELOG.md index d8fa47e0d4be..1eb657234277 100644 --- a/packages/material_ui/CHANGELOG.md +++ b/packages/material_ui/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.0.1 + +- Fixes `PredictiveBackPageTransitionsBuilder` to treat unknown or + button-triggered swipe edges as a no-direction fallback. + ## 1.0.0 - README updated for the full release of material_ui. 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 eb235c261963..5e5f592ffba0 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 @@ -459,7 +459,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/pubspec.yaml b/packages/material_ui/pubspec.yaml index 74627a0b025a..6a574dabded0 100644 --- a/packages/material_ui/pubspec.yaml +++ b/packages/material_ui/pubspec.yaml @@ -1,6 +1,6 @@ name: material_ui description: The official Flutter Material UI Library, implementing Google's Material Design design system. -version: 1.0.0 +version: 1.0.1 repository: https://github.com/flutter/packages/tree/main/packages/material_ui issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A%20material%20design%22 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 446a26e80e49..daa5b9c70273 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,156 @@ 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(); + + if (defaultTargetPlatform != TargetPlatform.android) { + return; + } + + // 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); + + // 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.all(), + ); + + 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(); + + if (defaultTargetPlatform != TargetPlatform.android) { + return; + } + + expect(_findPredictiveBackPageTransition(pageTransitionsBuilder), findsNothing); + expect(_findFallbackPageTransition(pageTransitionsBuilder), findsOneWidget); + + // 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.all()); + testWidgets('PredictiveBackPageTransitionsBuilder uses fallbackColor', ( WidgetTester tester, ) async {