From 4af88aae06526f31f23011f30bc0e5e8d90af90f Mon Sep 17 00:00:00 2001 From: Muhammad Kamel Date: Wed, 19 Aug 2026 00:15:14 +0300 Subject: [PATCH 1/3] [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 d8fa47e0d4b..1eb65723427 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 eb235c26196..5e5f592ffba 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 74627a0b025..6a574dabded 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 446a26e80e4..daa5b9c7027 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 { From aa7713e034bace69784e958adbae46c1296c5c3d Mon Sep 17 00:00:00 2001 From: Muhammad Kamel Date: Sat, 29 Aug 2026 18:01:35 +0300 Subject: [PATCH 2/3] [material_ui] Address review comments on predictive back unknown swipe edge --- .../predictive_back_unknown_swipe_edge.yaml | 3 +++ .../test/predictive_back_page_transitions_builder_test.dart | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 packages/material_ui/pending_changelogs/predictive_back_unknown_swipe_edge.yaml 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 daa5b9c7027..9948ed1298a 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 @@ -726,6 +726,7 @@ void main() { 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( @@ -800,9 +801,6 @@ void main() { 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. From b898d183c6f613de9804d4364ea714fa2e172560 Mon Sep 17 00:00:00 2001 From: Muhammad Kamel Date: Thu, 1 Oct 2026 23:48:31 +0000 Subject: [PATCH 3/3] [material_ui] Use Android-only variant for new predictive back tests Replace the early return on non-Android platforms with TargetPlatformVariant.only(TargetPlatform.android), so the tests only run where they make assertions. --- ...redictive_back_page_transitions_builder_test.dart | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) 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 9948ed1298a..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 @@ -706,10 +706,6 @@ void main() { 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', { @@ -763,7 +759,7 @@ void main() { expect(find.text('push'), findsOneWidget); expect(find.text('page b'), findsNothing); }, - variant: TargetPlatformVariant.all(), + variant: TargetPlatformVariant.only(TargetPlatform.android), ); testWidgets('button-triggered back does not start a predictive back animation', ( @@ -797,10 +793,6 @@ void main() { await tester.tap(find.text('push')); await tester.pumpAndSettle(); - if (defaultTargetPlatform != TargetPlatform.android) { - return; - } - // 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. @@ -821,7 +813,7 @@ void main() { // The predictive back transition must NOT have started. expect(_findPredictiveBackPageTransition(pageTransitionsBuilder), findsNothing); expect(_findFallbackPageTransition(pageTransitionsBuilder), findsOneWidget); - }, variant: TargetPlatformVariant.all()); + }, variant: TargetPlatformVariant.only(TargetPlatform.android)); testWidgets('PredictiveBackPageTransitionsBuilder uses fallbackColor', ( WidgetTester tester,