Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/material_ui/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Comment on lines 460 to +467

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since SwipeEdge.left and the wildcard _ fallback both return the exact same offset Offset(xShift, _getYShiftPosition(screenSize.height)), we can simplify the switch expression by removing the redundant SwipeEdge.left case and letting the wildcard pattern handle it.

          SwipeEdge.right => 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.
          // The wildcard pattern keeps the switch exhaustive if additional SwipeEdge
          // values are added in the Flutter SDK, and also handles SwipeEdge.left.
          _ => Offset(xShift, _getYShiftPosition(screenSize.height)),

},
end: Offset.zero,
),
Expand Down
2 changes: 1 addition & 1 deletion packages/material_ui/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <String, WidgetBuilder>{
'/': (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: <TargetPlatform, PageTransitionsBuilder>{
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', <String, dynamic>{
'touchOffset': <double>[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', <String, dynamic>{
'touchOffset': <double>[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));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For a right-edge swipe, the page is expected to shift to the left (negative dx). Using lessThan(0.0) instead of isNot(0.0) provides a more precise and robust assertion, ensuring the page shifts in the correct direction.

Suggested change
expect(pageBOffset.dx, isNot(0.0));
expect(pageBOffset.dx, lessThan(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 = <String, WidgetBuilder>{
'/': (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: <TargetPlatform, PageTransitionsBuilder>{
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', <String, dynamic>{
'touchOffset': <double>[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 {
Expand Down