1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/foundation.dart';
6import 'package:flutter/material.dart';
7import 'package:flutter/services.dart';
8import 'package:flutter_test/flutter_test.dart';
9
10void main() {
11 final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
12
13 Finder findPredictiveBackPageTransition() {
14 return find.descendant(
15 of: find.byType(MaterialApp),
16 matching: find.byWidgetPredicate(
17 (Widget w) => '${w.runtimeType}' == '_PredictiveBackPageTransition',
18 ),
19 );
20 }
21
22 Finder findFallbackPageTransition() {
23 return find.descendant(
24 of: find.byType(MaterialApp),
25 matching: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_ZoomPageTransition'),
26 );
27 }
28
29 testWidgets(
30 'PredictiveBackPageTransitionsBuilder supports predictive back on Android',
31 (WidgetTester tester) async {
32 final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
33 '/':
34 (BuildContext context) => Material(
35 child: TextButton(
36 child: const Text('push'),
37 onPressed: () {
38 Navigator.of(context).pushNamed('/b');
39 },
40 ),
41 ),
42 '/b': (BuildContext context) => const Text('page b'),
43 };
44
45 await tester.pumpWidget(
46 MaterialApp(
47 theme: ThemeData(
48 pageTransitionsTheme: PageTransitionsTheme(
49 builders: <TargetPlatform, PageTransitionsBuilder>{
50 for (final TargetPlatform platform in TargetPlatform.values)
51 platform: const PredictiveBackPageTransitionsBuilder(),
52 },
53 ),
54 ),
55 routes: routes,
56 ),
57 );
58
59 expect(find.text('push'), findsOneWidget);
60 expect(find.text('page b'), findsNothing);
61 expect(findPredictiveBackPageTransition(), findsNothing);
62 expect(findFallbackPageTransition(), findsOneWidget);
63
64 await tester.tap(find.text('push'));
65 await tester.pumpAndSettle();
66
67 expect(find.text('push'), findsNothing);
68 expect(find.text('page b'), findsOneWidget);
69 expect(findPredictiveBackPageTransition(), findsNothing);
70 expect(findFallbackPageTransition(), findsOneWidget);
71
72 // Only Android supports backGesture channel methods. Other platforms will
73 // do nothing.
74 if (defaultTargetPlatform != TargetPlatform.android) {
75 return;
76 }
77
78 // Start a system pop gesture, which will switch to using
79 // _PredictiveBackPageTransition for the page transition.
80 final ByteData startMessage = const StandardMethodCodec().encodeMethodCall(
81 const MethodCall('startBackGesture', <String, dynamic>{
82 'touchOffset': <double>[5.0, 300.0],
83 'progress': 0.0,
84 'swipeEdge': 0, // left
85 }),
86 );
87 await binding.defaultBinaryMessenger.handlePlatformMessage(
88 'flutter/backgesture',
89 startMessage,
90 (ByteData? _) {},
91 );
92 await tester.pump();
93
94 expect(findPredictiveBackPageTransition(), findsOneWidget);
95 expect(findFallbackPageTransition(), findsNothing);
96 final Offset startPageBOffset = tester.getTopLeft(find.text('page b'));
97 expect(startPageBOffset.dx, 0.0);
98
99 // Drag the system back gesture far enough to commit.
100 final ByteData updateMessage = const StandardMethodCodec().encodeMethodCall(
101 const MethodCall('updateBackGestureProgress', <String, dynamic>{
102 'x': 100.0,
103 'y': 300.0,
104 'progress': 0.35,
105 'swipeEdge': 0, // left
106 }),
107 );
108 await binding.defaultBinaryMessenger.handlePlatformMessage(
109 'flutter/backgesture',
110 updateMessage,
111 (ByteData? _) {},
112 );
113 await tester.pumpAndSettle();
114
115 expect(findPredictiveBackPageTransition(), findsNWidgets(2));
116 expect(findFallbackPageTransition(), findsNothing);
117
118 final Offset updatePageBOffset = tester.getTopLeft(find.text('page b'));
119 expect(updatePageBOffset.dx, greaterThan(startPageBOffset.dx));
120
121 // Commit the system back gesture.
122 final ByteData commitMessage = const StandardMethodCodec().encodeMethodCall(
123 const MethodCall('commitBackGesture'),
124 );
125 await binding.defaultBinaryMessenger.handlePlatformMessage(
126 'flutter/backgesture',
127 commitMessage,
128 (ByteData? _) {},
129 );
130 await tester.pumpAndSettle();
131
132 expect(findPredictiveBackPageTransition(), findsNothing);
133 expect(findFallbackPageTransition(), findsOneWidget);
134 expect(find.text('push'), findsOneWidget);
135 expect(find.text('page b'), findsNothing);
136 },
137 variant: TargetPlatformVariant.all(),
138 );
139
140 testWidgets(
141 'PredictiveBackPageTransitionsBuilder supports canceling a predictive back gesture',
142 (WidgetTester tester) async {
143 final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
144 '/':
145 (BuildContext context) => Material(
146 child: TextButton(
147 child: const Text('push'),
148 onPressed: () {
149 Navigator.of(context).pushNamed('/b');
150 },
151 ),
152 ),
153 '/b': (BuildContext context) => const Text('page b'),
154 };
155
156 await tester.pumpWidget(
157 MaterialApp(
158 theme: ThemeData(
159 pageTransitionsTheme: PageTransitionsTheme(
160 builders: <TargetPlatform, PageTransitionsBuilder>{
161 for (final TargetPlatform platform in TargetPlatform.values)
162 platform: const PredictiveBackPageTransitionsBuilder(),
163 },
164 ),
165 ),
166 routes: routes,
167 ),
168 );
169
170 expect(find.text('push'), findsOneWidget);
171 expect(find.text('page b'), findsNothing);
172 expect(findPredictiveBackPageTransition(), findsNothing);
173 expect(findFallbackPageTransition(), findsOneWidget);
174
175 await tester.tap(find.text('push'));
176 await tester.pumpAndSettle();
177
178 expect(find.text('push'), findsNothing);
179 expect(find.text('page b'), findsOneWidget);
180 expect(findPredictiveBackPageTransition(), findsNothing);
181 expect(findFallbackPageTransition(), findsOneWidget);
182
183 // Only Android supports backGesture channel methods. Other platforms will
184 // do nothing.
185 if (defaultTargetPlatform != TargetPlatform.android) {
186 return;
187 }
188
189 // Start a system pop gesture, which will switch to using
190 // _PredictiveBackPageTransition for the page transition.
191 final ByteData startMessage = const StandardMethodCodec().encodeMethodCall(
192 const MethodCall('startBackGesture', <String, dynamic>{
193 'touchOffset': <double>[5.0, 300.0],
194 'progress': 0.0,
195 'swipeEdge': 0, // left
196 }),
197 );
198 await binding.defaultBinaryMessenger.handlePlatformMessage(
199 'flutter/backgesture',
200 startMessage,
201 (ByteData? _) {},
202 );
203 await tester.pump();
204
205 expect(findPredictiveBackPageTransition(), findsOneWidget);
206 expect(findFallbackPageTransition(), findsNothing);
207 final Offset startPageBOffset = tester.getTopLeft(find.text('page b'));
208 expect(startPageBOffset.dx, 0.0);
209
210 // Drag the system back gesture.
211 final ByteData updateMessage = const StandardMethodCodec().encodeMethodCall(
212 const MethodCall('updateBackGestureProgress', <String, dynamic>{
213 'touchOffset': <double>[100.0, 300.0],
214 'progress': 0.35,
215 'swipeEdge': 0, // left
216 }),
217 );
218 await binding.defaultBinaryMessenger.handlePlatformMessage(
219 'flutter/backgesture',
220 updateMessage,
221 (ByteData? _) {},
222 );
223 await tester.pumpAndSettle();
224
225 expect(findPredictiveBackPageTransition(), findsNWidgets(2));
226 expect(findFallbackPageTransition(), findsNothing);
227
228 final Offset updatePageBOffset = tester.getTopLeft(find.text('page b'));
229 expect(updatePageBOffset.dx, greaterThan(startPageBOffset.dx));
230
231 // Cancel the system back gesture.
232 final ByteData commitMessage = const StandardMethodCodec().encodeMethodCall(
233 const MethodCall('cancelBackGesture'),
234 );
235 await binding.defaultBinaryMessenger.handlePlatformMessage(
236 'flutter/backgesture',
237 commitMessage,
238 (ByteData? _) {},
239 );
240 await tester.pumpAndSettle();
241
242 expect(find.text('push'), findsNothing);
243 expect(find.text('page b'), findsOneWidget);
244 expect(findPredictiveBackPageTransition(), findsNothing);
245 expect(findFallbackPageTransition(), findsOneWidget);
246 },
247 variant: TargetPlatformVariant.all(),
248 );
249
250 testWidgets(
251 'if multiple PredictiveBackPageTransitionBuilder observers, only one gets called for a given back gesture',
252 (WidgetTester tester) async {
253 bool includingNestedNavigator = false;
254 late StateSetter setState;
255 final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
256 '/':
257 (BuildContext context) => Material(
258 child: TextButton(
259 child: const Text('push'),
260 onPressed: () {
261 Navigator.of(context).pushNamed('/b');
262 },
263 ),
264 ),
265 '/b':
266 (BuildContext context) => Column(
267 crossAxisAlignment: CrossAxisAlignment.start,
268 children: <Widget>[
269 const Text('page b'),
270 StatefulBuilder(
271 builder: (BuildContext context, StateSetter localSetState) {
272 setState = localSetState;
273 if (!includingNestedNavigator) {
274 return const SizedBox.shrink();
275 }
276 return Navigator(
277 initialRoute: 'b/nested',
278 onGenerateRoute: (RouteSettings settings) {
279 WidgetBuilder builder;
280 switch (settings.name) {
281 case 'b/nested':
282 builder =
283 (BuildContext context) => Material(
284 child: Theme(
285 data: ThemeData(
286 pageTransitionsTheme: PageTransitionsTheme(
287 builders: <TargetPlatform, PageTransitionsBuilder>{
288 for (final TargetPlatform platform
289 in TargetPlatform.values)
290 platform: const PredictiveBackPageTransitionsBuilder(),
291 },
292 ),
293 ),
294 child: const Column(
295 children: <Widget>[Text('Nested route inside of page b')],
296 ),
297 ),
298 );
299 default:
300 throw Exception('Invalid route: ${settings.name}');
301 }
302 return MaterialPageRoute<void>(builder: builder, settings: settings);
303 },
304 );
305 },
306 ),
307 ],
308 ),
309 };
310
311 await tester.pumpWidget(
312 MaterialApp(
313 theme: ThemeData(
314 pageTransitionsTheme: PageTransitionsTheme(
315 builders: <TargetPlatform, PageTransitionsBuilder>{
316 for (final TargetPlatform platform in TargetPlatform.values)
317 platform: const PredictiveBackPageTransitionsBuilder(),
318 },
319 ),
320 ),
321 routes: routes,
322 ),
323 );
324
325 expect(find.text('push'), findsOneWidget);
326 expect(find.text('page b'), findsNothing);
327 expect(find.text('Nested route inside of page b'), findsNothing);
328 expect(findPredictiveBackPageTransition(), findsNothing);
329 expect(findFallbackPageTransition(), findsOneWidget);
330
331 await tester.tap(find.text('push'));
332 await tester.pumpAndSettle();
333
334 expect(find.text('push'), findsNothing);
335 expect(find.text('page b'), findsOneWidget);
336 expect(find.text('Nested route inside of page b'), findsNothing);
337 expect(findPredictiveBackPageTransition(), findsNothing);
338 expect(findFallbackPageTransition(), findsOneWidget);
339
340 // Only Android supports backGesture channel methods. Other platforms will
341 // do nothing.
342 if (defaultTargetPlatform != TargetPlatform.android) {
343 return;
344 }
345
346 // Start a system pop gesture, which will switch to using
347 // _PredictiveBackPageTransition for the page transition.
348 final ByteData startMessage = const StandardMethodCodec().encodeMethodCall(
349 const MethodCall('startBackGesture', <String, dynamic>{
350 'touchOffset': <double>[5.0, 300.0],
351 'progress': 0.0,
352 'swipeEdge': 0, // left
353 }),
354 );
355 await binding.defaultBinaryMessenger.handlePlatformMessage(
356 'flutter/backgesture',
357 startMessage,
358 (ByteData? _) {},
359 );
360 await tester.pump();
361
362 expect(findPredictiveBackPageTransition(), findsOneWidget);
363 expect(findFallbackPageTransition(), findsNothing);
364 final Offset startPageBOffset = tester.getTopLeft(find.text('page b'));
365 expect(startPageBOffset.dx, 0.0);
366
367 // Drag the system back gesture.
368 final ByteData updateMessage = const StandardMethodCodec().encodeMethodCall(
369 const MethodCall('updateBackGestureProgress', <String, dynamic>{
370 'touchOffset': <double>[100.0, 300.0],
371 'progress': 0.3,
372 'swipeEdge': 0, // left
373 }),
374 );
375 await binding.defaultBinaryMessenger.handlePlatformMessage(
376 'flutter/backgesture',
377 updateMessage,
378 (ByteData? _) {},
379 );
380 await tester.pumpAndSettle();
381
382 expect(findPredictiveBackPageTransition(), findsNWidgets(2));
383 expect(findFallbackPageTransition(), findsNothing);
384
385 final Offset updatePageBOffset = tester.getTopLeft(find.text('page b'));
386 expect(updatePageBOffset.dx, greaterThan(startPageBOffset.dx));
387
388 // In the middle of the system back gesture here, add a nested Navigator
389 // that includes a new predictive back gesture observer.
390 setState(() {
391 includingNestedNavigator = true;
392 });
393 await tester.pumpAndSettle();
394 expect(find.text('push'), findsOneWidget);
395 expect(find.text('page b'), findsOneWidget);
396 expect(find.text('Nested route inside of page b'), findsOneWidget);
397
398 // Send another drag gesture, and ensure that the original observer still
399 // gets it.
400 final ByteData updateMessage2 = const StandardMethodCodec().encodeMethodCall(
401 const MethodCall('updateBackGestureProgress', <String, dynamic>{
402 'touchOffset': <double>[110.0, 300.0],
403 'progress': 0.35,
404 'swipeEdge': 0, // left
405 }),
406 );
407 await binding.defaultBinaryMessenger.handlePlatformMessage(
408 'flutter/backgesture',
409 updateMessage2,
410 (ByteData? _) {},
411 );
412 await tester.pumpAndSettle();
413
414 expect(findPredictiveBackPageTransition(), findsNWidgets(2));
415 // Despite using a PredictiveBackPageTransitions, the new route has not
416 // received a start event, so it is still using the fallback transition.
417 expect(findFallbackPageTransition(), findsOneWidget);
418
419 final Offset update2PageBOffset = tester.getTopLeft(find.text('page b'));
420 expect(update2PageBOffset.dx, greaterThan(updatePageBOffset.dx));
421
422 // Commit the system back gesture, and the original observer is able to
423 // handle the back without interference.
424 final ByteData commitMessage = const StandardMethodCodec().encodeMethodCall(
425 const MethodCall('commitBackGesture'),
426 );
427 await binding.defaultBinaryMessenger.handlePlatformMessage(
428 'flutter/backgesture',
429 commitMessage,
430 (ByteData? _) {},
431 );
432 await tester.pumpAndSettle();
433
434 expect(findPredictiveBackPageTransition(), findsNothing);
435 expect(findFallbackPageTransition(), findsOneWidget);
436 expect(find.text('push'), findsOneWidget);
437 expect(find.text('page b'), findsNothing);
438 expect(find.text('Nested route inside of page b'), findsNothing);
439 },
440 variant: TargetPlatformVariant.all(),
441 );
442}
443

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com