blob: 3df1a690ae66e05e5f87c1972b384ec0e5d682be [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
Finder findPredictiveBackPageTransition() {
return find.descendant(
of: find.byType(MaterialApp),
matching: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_PredictiveBackPageTransition'),
);
}
Finder findFallbackPageTransition() {
return find.descendant(
of: find.byType(MaterialApp),
matching: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_ZoomPageTransition'),
);
}
testWidgets('PredictiveBackPageTransitionsBuilder supports predictive back on Android', (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('page b'),
};
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
pageTransitionsTheme: PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
for (final TargetPlatform platform in TargetPlatform.values)
platform: const PredictiveBackPageTransitionsBuilder(),
},
),
),
routes: routes,
),
);
expect(find.text('push'), findsOneWidget);
expect(find.text('page b'), findsNothing);
expect(findPredictiveBackPageTransition(), findsNothing);
expect(findFallbackPageTransition(), findsOneWidget);
await tester.tap(find.text('push'));
await tester.pumpAndSettle();
expect(find.text('push'), findsNothing);
expect(find.text('page b'), findsOneWidget);
expect(findPredictiveBackPageTransition(), findsNothing);
expect(findFallbackPageTransition(), findsOneWidget);
// Only Android supports backGesture channel methods. Other platforms will
// do nothing.
if (defaultTargetPlatform != TargetPlatform.android) {
return;
}
// Start a system pop gesture, which will switch to using
// _PredictiveBackPageTransition for the page transition.
final ByteData startMessage = const StandardMethodCodec().encodeMethodCall(
const MethodCall(
'startBackGesture',
<String, dynamic>{
'touchOffset': <double>[5.0, 300.0],
'progress': 0.0,
'swipeEdge': 0, // left
},
),
);
await binding.defaultBinaryMessenger.handlePlatformMessage(
'flutter/backgesture',
startMessage,
(ByteData? _) {},
);
await tester.pump();
expect(findPredictiveBackPageTransition(), findsOneWidget);
expect(findFallbackPageTransition(), findsNothing);
final Offset startPageBOffset = tester.getTopLeft(find.text('page b'));
expect(startPageBOffset.dx, 0.0);
// Drag the system back gesture far enough to commit.
final ByteData updateMessage = const StandardMethodCodec().encodeMethodCall(
const MethodCall(
'updateBackGestureProgress',
<String, dynamic>{
'x': 100.0,
'y': 300.0,
'progress': 0.35,
'swipeEdge': 0, // left
},
),
);
await binding.defaultBinaryMessenger.handlePlatformMessage(
'flutter/backgesture',
updateMessage,
(ByteData? _) {},
);
await tester.pumpAndSettle();
expect(findPredictiveBackPageTransition(), findsNWidgets(2));
expect(findFallbackPageTransition(), findsNothing);
final Offset updatePageBOffset = tester.getTopLeft(find.text('page b'));
expect(updatePageBOffset.dx, greaterThan(startPageBOffset.dx));
// Commit the system back gesture.
final ByteData commitMessage = const StandardMethodCodec().encodeMethodCall(
const MethodCall(
'commitBackGesture',
),
);
await binding.defaultBinaryMessenger.handlePlatformMessage(
'flutter/backgesture',
commitMessage,
(ByteData? _) {},
);
await tester.pumpAndSettle();
expect(findPredictiveBackPageTransition(), findsNothing);
expect(findFallbackPageTransition(), findsOneWidget);
expect(find.text('push'), findsOneWidget);
expect(find.text('page b'), findsNothing);
}, variant: TargetPlatformVariant.all());
testWidgets('PredictiveBackPageTransitionsBuilder supports canceling a predictive back gesture', (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('page b'),
};
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
pageTransitionsTheme: PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
for (final TargetPlatform platform in TargetPlatform.values)
platform: const PredictiveBackPageTransitionsBuilder(),
},
),
),
routes: routes,
),
);
expect(find.text('push'), findsOneWidget);
expect(find.text('page b'), findsNothing);
expect(findPredictiveBackPageTransition(), findsNothing);
expect(findFallbackPageTransition(), findsOneWidget);
await tester.tap(find.text('push'));
await tester.pumpAndSettle();
expect(find.text('push'), findsNothing);
expect(find.text('page b'), findsOneWidget);
expect(findPredictiveBackPageTransition(), findsNothing);
expect(findFallbackPageTransition(), findsOneWidget);
// Only Android supports backGesture channel methods. Other platforms will
// do nothing.
if (defaultTargetPlatform != TargetPlatform.android) {
return;
}
// Start a system pop gesture, which will switch to using
// _PredictiveBackPageTransition for the page transition.
final ByteData startMessage = const StandardMethodCodec().encodeMethodCall(
const MethodCall(
'startBackGesture',
<String, dynamic>{
'touchOffset': <double>[5.0, 300.0],
'progress': 0.0,
'swipeEdge': 0, // left
},
),
);
await binding.defaultBinaryMessenger.handlePlatformMessage(
'flutter/backgesture',
startMessage,
(ByteData? _) {},
);
await tester.pump();
expect(findPredictiveBackPageTransition(), findsOneWidget);
expect(findFallbackPageTransition(), findsNothing);
final Offset startPageBOffset = tester.getTopLeft(find.text('page b'));
expect(startPageBOffset.dx, 0.0);
// Drag the system back gesture.
final ByteData updateMessage = const StandardMethodCodec().encodeMethodCall(
const MethodCall(
'updateBackGestureProgress',
<String, dynamic>{
'touchOffset': <double>[100.0, 300.0],
'progress': 0.35,
'swipeEdge': 0, // left
},
),
);
await binding.defaultBinaryMessenger.handlePlatformMessage(
'flutter/backgesture',
updateMessage,
(ByteData? _) {},
);
await tester.pumpAndSettle();
expect(findPredictiveBackPageTransition(), findsNWidgets(2));
expect(findFallbackPageTransition(), findsNothing);
final Offset updatePageBOffset = tester.getTopLeft(find.text('page b'));
expect(updatePageBOffset.dx, greaterThan(startPageBOffset.dx));
// Cancel the system back gesture.
final ByteData commitMessage = const StandardMethodCodec().encodeMethodCall(
const MethodCall(
'cancelBackGesture',
),
);
await binding.defaultBinaryMessenger.handlePlatformMessage(
'flutter/backgesture',
commitMessage,
(ByteData? _) {},
);
await tester.pumpAndSettle();
expect(find.text('push'), findsNothing);
expect(find.text('page b'), findsOneWidget);
expect(findPredictiveBackPageTransition(), findsNothing);
expect(findFallbackPageTransition(), findsOneWidget);
}, variant: TargetPlatformVariant.all());
testWidgets('if multiple PredictiveBackPageTransitionBuilder observers, only one gets called for a given back gesture', (WidgetTester tester) async {
bool includingNestedNavigator = false;
late StateSetter setState;
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) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text('page b'),
StatefulBuilder(
builder: (BuildContext context, StateSetter localSetState) {
setState = localSetState;
if (!includingNestedNavigator) {
return const SizedBox.shrink();
}
return Navigator(
initialRoute: 'b/nested',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch (settings.name) {
case 'b/nested':
builder = (BuildContext context) => Material(
child: Theme(
data: ThemeData(
pageTransitionsTheme: PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
for (final TargetPlatform platform in TargetPlatform.values)
platform: const PredictiveBackPageTransitionsBuilder(),
},
),
),
child: const Column(
children: <Widget>[
Text('Nested route inside of page b'),
],
),
),
);
default:
throw Exception('Invalid route: ${settings.name}');
}
return MaterialPageRoute<void>(builder: builder, settings: settings);
},
);
},
),
],
),
};
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
pageTransitionsTheme: PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
for (final TargetPlatform platform in TargetPlatform.values)
platform: const PredictiveBackPageTransitionsBuilder(),
},
),
),
routes: routes,
),
);
expect(find.text('push'), findsOneWidget);
expect(find.text('page b'), findsNothing);
expect(find.text('Nested route inside of page b'), findsNothing);
expect(findPredictiveBackPageTransition(), findsNothing);
expect(findFallbackPageTransition(), findsOneWidget);
await tester.tap(find.text('push'));
await tester.pumpAndSettle();
expect(find.text('push'), findsNothing);
expect(find.text('page b'), findsOneWidget);
expect(find.text('Nested route inside of page b'), findsNothing);
expect(findPredictiveBackPageTransition(), findsNothing);
expect(findFallbackPageTransition(), findsOneWidget);
// Only Android supports backGesture channel methods. Other platforms will
// do nothing.
if (defaultTargetPlatform != TargetPlatform.android) {
return;
}
// Start a system pop gesture, which will switch to using
// _PredictiveBackPageTransition for the page transition.
final ByteData startMessage = const StandardMethodCodec().encodeMethodCall(
const MethodCall(
'startBackGesture',
<String, dynamic>{
'touchOffset': <double>[5.0, 300.0],
'progress': 0.0,
'swipeEdge': 0, // left
},
),
);
await binding.defaultBinaryMessenger.handlePlatformMessage(
'flutter/backgesture',
startMessage,
(ByteData? _) {},
);
await tester.pump();
expect(findPredictiveBackPageTransition(), findsOneWidget);
expect(findFallbackPageTransition(), findsNothing);
final Offset startPageBOffset = tester.getTopLeft(find.text('page b'));
expect(startPageBOffset.dx, 0.0);
// Drag the system back gesture.
final ByteData updateMessage = const StandardMethodCodec().encodeMethodCall(
const MethodCall(
'updateBackGestureProgress',
<String, dynamic>{
'touchOffset': <double>[100.0, 300.0],
'progress': 0.3,
'swipeEdge': 0, // left
},
),
);
await binding.defaultBinaryMessenger.handlePlatformMessage(
'flutter/backgesture',
updateMessage,
(ByteData? _) {},
);
await tester.pumpAndSettle();
expect(findPredictiveBackPageTransition(), findsNWidgets(2));
expect(findFallbackPageTransition(), findsNothing);
final Offset updatePageBOffset = tester.getTopLeft(find.text('page b'));
expect(updatePageBOffset.dx, greaterThan(startPageBOffset.dx));
// In the middle of the system back gesture here, add a nested Navigator
// that includes a new predictive back gesture observer.
setState(() {
includingNestedNavigator = true;
});
await tester.pumpAndSettle();
expect(find.text('push'), findsOneWidget);
expect(find.text('page b'), findsOneWidget);
expect(find.text('Nested route inside of page b'), findsOneWidget);
// Send another drag gesture, and ensure that the original observer still
// gets it.
final ByteData updateMessage2 = const StandardMethodCodec().encodeMethodCall(
const MethodCall(
'updateBackGestureProgress',
<String, dynamic>{
'touchOffset': <double>[110.0, 300.0],
'progress': 0.35,
'swipeEdge': 0, // left
},
),
);
await binding.defaultBinaryMessenger.handlePlatformMessage(
'flutter/backgesture',
updateMessage2,
(ByteData? _) {},
);
await tester.pumpAndSettle();
expect(findPredictiveBackPageTransition(), findsNWidgets(2));
// Despite using a PredictiveBackPageTransitions, the new route has not
// received a start event, so it is still using the fallback transition.
expect(findFallbackPageTransition(), findsOneWidget);
final Offset update2PageBOffset = tester.getTopLeft(find.text('page b'));
expect(update2PageBOffset.dx, greaterThan(updatePageBOffset.dx));
// Commit the system back gesture, and the original observer is able to
// handle the back without interference.
final ByteData commitMessage = const StandardMethodCodec().encodeMethodCall(
const MethodCall(
'commitBackGesture',
),
);
await binding.defaultBinaryMessenger.handlePlatformMessage(
'flutter/backgesture',
commitMessage,
(ByteData? _) {},
);
await tester.pumpAndSettle();
expect(findPredictiveBackPageTransition(), findsNothing);
expect(findFallbackPageTransition(), findsOneWidget);
expect(find.text('push'), findsOneWidget);
expect(find.text('page b'), findsNothing);
expect(find.text('Nested route inside of page b'), findsNothing);
}, variant: TargetPlatformVariant.all());
}