Unverified Commit aaaea51d authored by ipcjs's avatar ipcjs Committed by GitHub

fix a bug when android uses CupertinoPageTransitionsBuilder... (#114303)

When android uses iOS style `PageTransitionsBuilder` and iOS uses android style `PageTransitionsBuilder`, on android, swipe from the left edge of the screen doesn't work. This PR solves that problem.

#99919 introduced a breaking change, the pr re-implemented it <del>without introducing a breaking change.**</del>
parent 168d8077
...@@ -80,6 +80,8 @@ class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixi ...@@ -80,6 +80,8 @@ class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixi
/// * [CupertinoPageTransitionsBuilder], which is the default page transition /// * [CupertinoPageTransitionsBuilder], which is the default page transition
/// for iOS and macOS. /// for iOS and macOS.
mixin MaterialRouteTransitionMixin<T> on PageRoute<T> { mixin MaterialRouteTransitionMixin<T> on PageRoute<T> {
TargetPlatform? _effectiveTargetPlatform;
/// Builds the primary contents of the route. /// Builds the primary contents of the route.
@protected @protected
Widget buildContent(BuildContext context); Widget buildContent(BuildContext context);
...@@ -116,8 +118,20 @@ mixin MaterialRouteTransitionMixin<T> on PageRoute<T> { ...@@ -116,8 +118,20 @@ mixin MaterialRouteTransitionMixin<T> on PageRoute<T> {
@override @override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
final PageTransitionsTheme theme = Theme.of(context).pageTransitionsTheme; return ValueListenableBuilder<bool>(
return theme.buildTransitions<T>(this, context, animation, secondaryAnimation, child); valueListenable: navigator!.userGestureInProgressNotifier,
builder: (BuildContext context, bool useGestureInProgress, Widget? _) {
final ThemeData themeData = Theme.of(context);
if (useGestureInProgress) {
// The platform should be kept unchanged during an user gesture.
_effectiveTargetPlatform ??= themeData.platform;
} else {
_effectiveTargetPlatform = themeData.platform;
}
return themeData.pageTransitionsTheme.buildTransitions<T>(this, context, animation, secondaryAnimation, child, _effectiveTargetPlatform!);
},
);
} }
} }
......
...@@ -741,7 +741,7 @@ class PageTransitionsTheme with Diagnosticable { ...@@ -741,7 +741,7 @@ class PageTransitionsTheme with Diagnosticable {
Map<TargetPlatform, PageTransitionsBuilder> get builders => _builders; Map<TargetPlatform, PageTransitionsBuilder> get builders => _builders;
final Map<TargetPlatform, PageTransitionsBuilder> _builders; final Map<TargetPlatform, PageTransitionsBuilder> _builders;
/// Delegates to the builder for the current [ThemeData.platform]. /// Delegates to the builder for the current [platform].
/// If a builder for the current platform is not found, then the /// If a builder for the current platform is not found, then the
/// [ZoomPageTransitionsBuilder] is used. /// [ZoomPageTransitionsBuilder] is used.
/// ///
...@@ -752,13 +752,8 @@ class PageTransitionsTheme with Diagnosticable { ...@@ -752,13 +752,8 @@ class PageTransitionsTheme with Diagnosticable {
Animation<double> animation, Animation<double> animation,
Animation<double> secondaryAnimation, Animation<double> secondaryAnimation,
Widget child, Widget child,
TargetPlatform platform,
) { ) {
TargetPlatform platform = Theme.of(context).platform;
if (CupertinoRouteTransitionMixin.isPopGestureInProgress(route)) {
platform = TargetPlatform.iOS;
}
final PageTransitionsBuilder matchingBuilder = final PageTransitionsBuilder matchingBuilder =
builders[platform] ?? const ZoomPageTransitionsBuilder(); builders[platform] ?? const ZoomPageTransitionsBuilder();
return matchingBuilder.buildTransitions<T>(route, context, animation, secondaryAnimation, child); return matchingBuilder.buildTransitions<T>(route, context, animation, secondaryAnimation, child);
......
...@@ -350,4 +350,140 @@ void main() { ...@@ -350,4 +350,140 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(builtCount, 1); expect(builtCount, 1);
}, variant: TargetPlatformVariant.only(TargetPlatform.android)); }, variant: TargetPlatformVariant.only(TargetPlatform.android));
testWidgets('android can use CupertinoPageTransitionsBuilder', (WidgetTester tester) async {
int builtCount = 0;
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => Material(
child: TextButton(
child: const Text('push'),
onPressed: () { Navigator.of(context).pushNamed('/b'); },
),
),
'/b': (BuildContext context) => StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
builtCount++;
return TextButton(
child: const Text('pop'),
onPressed: () { Navigator.pop(context); },
);
},
),
};
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
pageTransitionsTheme: const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
// iOS uses different PageTransitionsBuilder
TargetPlatform.iOS: FadeUpwardsPageTransitionsBuilder(),
},
),
),
routes: routes,
),
);
// No matter push or pop was called, the child widget should built only once.
await tester.tap(find.text('push'));
await tester.pumpAndSettle();
expect(builtCount, 1);
final Size size = tester.getSize(find.byType(MaterialApp));
await tester.flingFrom(Offset(0, size.height / 2), Offset(size.width * 2 / 3, 0), 500);
await tester.pumpAndSettle();
expect(find.text('push'), findsOneWidget);
expect(builtCount, 1);
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
testWidgets('back gesture while TargetPlatform changes', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => Material(
child: TextButton(
child: const Text('PUSH'),
onPressed: () { Navigator.of(context).pushNamed('/b'); },
),
),
'/b': (BuildContext context) => const Text('HELLO'),
};
const PageTransitionsTheme pageTransitionsTheme = PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
// iOS uses different PageTransitionsBuilder
TargetPlatform.iOS: FadeUpwardsPageTransitionsBuilder(),
},
);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
platform: TargetPlatform.android,
pageTransitionsTheme: pageTransitionsTheme,
),
routes: routes,
),
);
await tester.tap(find.text('PUSH'));
expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 2);
expect(find.text('PUSH'), findsNothing);
expect(find.text('HELLO'), findsOneWidget);
final Offset helloPosition1 = tester.getCenter(find.text('HELLO'));
final TestGesture gesture = await tester.startGesture(const Offset(2.5, 300.0));
await tester.pump(const Duration(milliseconds: 20));
await gesture.moveBy(const Offset(100.0, 0.0));
expect(find.text('PUSH'), findsNothing);
expect(find.text('HELLO'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 20));
expect(find.text('PUSH'), findsOneWidget);
expect(find.text('HELLO'), findsOneWidget);
final Offset helloPosition2 = tester.getCenter(find.text('HELLO'));
expect(helloPosition1.dx, lessThan(helloPosition2.dx));
expect(helloPosition1.dy, helloPosition2.dy);
expect(Theme.of(tester.element(find.text('HELLO'))).platform, TargetPlatform.android);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
platform: TargetPlatform.iOS,
pageTransitionsTheme: pageTransitionsTheme,
),
routes: routes,
),
);
// Now, let the theme animation run through.
// This takes three frames (including the first one above):
// 1. Start the Theme animation. It's at t=0 so everything else is identical.
// 2. Start any animations that are informed by the Theme, for example, the
// DefaultTextStyle, on the first frame that the theme is not at t=0. In
// this case, it's at t=1.0 of the theme animation, so this is also the
// frame in which the theme animation ends.
// 3. End all the other animations.
expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 2);
expect(Theme.of(tester.element(find.text('HELLO'))).platform, TargetPlatform.iOS);
final Offset helloPosition3 = tester.getCenter(find.text('HELLO'));
expect(helloPosition3, helloPosition2);
expect(find.text('PUSH'), findsOneWidget);
expect(find.text('HELLO'), findsOneWidget);
await gesture.moveBy(const Offset(100.0, 0.0));
await tester.pump(const Duration(milliseconds: 20));
expect(find.text('PUSH'), findsOneWidget);
expect(find.text('HELLO'), findsOneWidget);
final Offset helloPosition4 = tester.getCenter(find.text('HELLO'));
expect(helloPosition3.dx, lessThan(helloPosition4.dx));
expect(helloPosition3.dy, helloPosition4.dy);
await gesture.moveBy(const Offset(500.0, 0.0));
await gesture.up();
expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 3);
expect(find.text('PUSH'), findsOneWidget);
expect(find.text('HELLO'), findsNothing);
await tester.tap(find.text('PUSH'));
expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 2);
expect(find.text('PUSH'), findsNothing);
expect(find.text('HELLO'), findsOneWidget);
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment