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 | |
5 | import 'package:flutter/cupertino.dart'; |
6 | import 'package:flutter/material.dart'; |
7 | import 'package:flutter/services.dart'; |
8 | import 'package:flutter_localizations/flutter_localizations.dart'; |
9 | import 'package:flutter_test/flutter_test.dart'; |
10 | |
11 | import '../widgets/navigator_utils.dart'; |
12 | |
13 | // Matches _kTopGapRatio in cupertino/sheet.dart. |
14 | const double _kTopGapRatio = 0.08; |
15 | |
16 | void main() { |
17 | testWidgets('Sheet route does not cover the whole screen' , (WidgetTester tester) async { |
18 | final GlobalKey scaffoldKey = GlobalKey(); |
19 | |
20 | await tester.pumpWidget( |
21 | CupertinoApp( |
22 | home: CupertinoPageScaffold( |
23 | key: scaffoldKey, |
24 | child: Center( |
25 | child: Column( |
26 | children: <Widget>[ |
27 | const Text('Page 1' ), |
28 | CupertinoButton( |
29 | onPressed: () { |
30 | Navigator.push<void>( |
31 | scaffoldKey.currentContext!, |
32 | CupertinoSheetRoute<void>( |
33 | builder: (BuildContext context) { |
34 | return const CupertinoPageScaffold(child: Text('Page 2' )); |
35 | }, |
36 | ), |
37 | ); |
38 | }, |
39 | child: const Text('Push Page 2' ), |
40 | ), |
41 | ], |
42 | ), |
43 | ), |
44 | ), |
45 | ), |
46 | ); |
47 | |
48 | expect(find.text('Page 1' ), findsOneWidget); |
49 | expect(find.text('Page 2' ), findsNothing); |
50 | |
51 | await tester.tap(find.text('Push Page 2' )); |
52 | await tester.pumpAndSettle(); |
53 | |
54 | expect(find.text('Page 2' ), findsOneWidget); |
55 | expect( |
56 | tester |
57 | .getTopLeft( |
58 | find.ancestor(of: find.text('Page 2' ), matching: find.byType(CupertinoPageScaffold)), |
59 | ) |
60 | .dy, |
61 | greaterThan(0.0), |
62 | ); |
63 | }); |
64 | |
65 | testWidgets('Previous route moves slight downward when sheet route is pushed' , ( |
66 | WidgetTester tester, |
67 | ) async { |
68 | final GlobalKey scaffoldKey = GlobalKey(); |
69 | |
70 | await tester.pumpWidget( |
71 | CupertinoApp( |
72 | home: CupertinoPageScaffold( |
73 | key: scaffoldKey, |
74 | child: Column( |
75 | children: <Widget>[ |
76 | const Text('Page 1' ), |
77 | CupertinoButton( |
78 | onPressed: () { |
79 | Navigator.push<void>( |
80 | scaffoldKey.currentContext!, |
81 | CupertinoSheetRoute<void>( |
82 | builder: (BuildContext context) { |
83 | return const CupertinoPageScaffold(child: Text('Page 2' )); |
84 | }, |
85 | ), |
86 | ); |
87 | }, |
88 | child: const Text('Push Page 2' ), |
89 | ), |
90 | ], |
91 | ), |
92 | ), |
93 | ), |
94 | ); |
95 | |
96 | expect(find.text('Page 1' ), findsOneWidget); |
97 | expect( |
98 | tester |
99 | .getTopLeft( |
100 | find.ancestor(of: find.text('Page 1' ), matching: find.byType(CupertinoPageScaffold)), |
101 | ) |
102 | .dy, |
103 | equals(0.0), |
104 | ); |
105 | expect(find.text('Page 2' ), findsNothing); |
106 | |
107 | await tester.tap(find.text('Push Page 2' )); |
108 | await tester.pumpAndSettle(); |
109 | |
110 | // Previous page is still visible behind the new sheet. |
111 | expect(find.text('Page 1' ), findsOneWidget); |
112 | final Offset pageOneOffset = tester.getTopLeft( |
113 | find.ancestor(of: find.text('Page 1' ), matching: find.byType(CupertinoPageScaffold)), |
114 | ); |
115 | expect(pageOneOffset.dy, greaterThan(0.0)); |
116 | expect(pageOneOffset.dx, greaterThan(0.0)); |
117 | expect(find.text('Page 2' ), findsOneWidget); |
118 | final double pageTwoYOffset = |
119 | tester |
120 | .getTopLeft( |
121 | find.ancestor(of: find.text('Page 2' ), matching: find.byType(CupertinoPageScaffold)), |
122 | ) |
123 | .dy; |
124 | expect(pageTwoYOffset, greaterThan(pageOneOffset.dy)); |
125 | }); |
126 | |
127 | testWidgets('If a sheet covers another sheet, then the previous sheet moves slightly upwards' , ( |
128 | WidgetTester tester, |
129 | ) async { |
130 | final GlobalKey scaffoldKey = GlobalKey(); |
131 | |
132 | await tester.pumpWidget( |
133 | CupertinoApp( |
134 | home: CupertinoPageScaffold( |
135 | key: scaffoldKey, |
136 | child: Column( |
137 | children: <Widget>[ |
138 | const Text('Page 1' ), |
139 | CupertinoButton( |
140 | onPressed: () { |
141 | Navigator.push<void>( |
142 | scaffoldKey.currentContext!, |
143 | CupertinoSheetRoute<void>( |
144 | builder: (BuildContext context) { |
145 | return CupertinoPageScaffold( |
146 | child: Column( |
147 | children: <Widget>[ |
148 | const Text('Page 2' ), |
149 | CupertinoButton( |
150 | onPressed: () { |
151 | Navigator.push<void>( |
152 | scaffoldKey.currentContext!, |
153 | CupertinoSheetRoute<void>( |
154 | builder: (BuildContext context) { |
155 | return const CupertinoPageScaffold(child: Text('Page 3' )); |
156 | }, |
157 | ), |
158 | ); |
159 | }, |
160 | child: const Text('Push Page 3' ), |
161 | ), |
162 | ], |
163 | ), |
164 | ); |
165 | }, |
166 | ), |
167 | ); |
168 | }, |
169 | child: const Text('Push Page 2' ), |
170 | ), |
171 | ], |
172 | ), |
173 | ), |
174 | ), |
175 | ); |
176 | |
177 | expect(find.text('Page 1' ), findsOneWidget); |
178 | expect( |
179 | tester |
180 | .getTopLeft( |
181 | find.ancestor(of: find.text('Page 1' ), matching: find.byType(CupertinoPageScaffold)), |
182 | ) |
183 | .dy, |
184 | equals(0.0), |
185 | ); |
186 | expect(find.text('Page 2' ), findsNothing); |
187 | expect(find.text('Page 3' ), findsNothing); |
188 | |
189 | await tester.tap(find.text('Push Page 2' )); |
190 | await tester.pumpAndSettle(); |
191 | |
192 | expect(find.text('Page 2' ), findsOneWidget); |
193 | expect(find.text('Page 3' ), findsNothing); |
194 | final double previousPageTwoDY = |
195 | tester |
196 | .getTopLeft( |
197 | find.ancestor(of: find.text('Page 2' ), matching: find.byType(CupertinoPageScaffold)), |
198 | ) |
199 | .dy; |
200 | |
201 | await tester.tap(find.text('Push Page 3' )); |
202 | await tester.pumpAndSettle(); |
203 | |
204 | expect(find.text('Page 3' ), findsOneWidget); |
205 | expect(previousPageTwoDY, greaterThan(0.0)); |
206 | expect( |
207 | previousPageTwoDY, |
208 | greaterThan( |
209 | tester |
210 | .getTopLeft( |
211 | find.ancestor(of: find.text('Page 2' ), matching: find.byType(CupertinoPageScaffold)), |
212 | ) |
213 | .dy, |
214 | ), |
215 | ); |
216 | }); |
217 | |
218 | testWidgets('by default showCupertinoSheet does not enable nested navigation' , ( |
219 | WidgetTester tester, |
220 | ) async { |
221 | final GlobalKey scaffoldKey = GlobalKey(); |
222 | |
223 | Widget sheetScaffoldContent(BuildContext context) { |
224 | return Column( |
225 | children: <Widget>[ |
226 | const Text('Page 2' ), |
227 | CupertinoButton( |
228 | onPressed: () { |
229 | Navigator.push<void>( |
230 | context, |
231 | CupertinoPageRoute<void>( |
232 | builder: (BuildContext context) { |
233 | return CupertinoPageScaffold( |
234 | child: Column( |
235 | children: <Widget>[ |
236 | const Text('Page 3' ), |
237 | CupertinoButton(onPressed: () {}, child: const Text('Pop Page 3' )), |
238 | ], |
239 | ), |
240 | ); |
241 | }, |
242 | ), |
243 | ); |
244 | }, |
245 | child: const Text('Push Page 3' ), |
246 | ), |
247 | ], |
248 | ); |
249 | } |
250 | |
251 | await tester.pumpWidget( |
252 | CupertinoApp( |
253 | home: CupertinoPageScaffold( |
254 | key: scaffoldKey, |
255 | child: Center( |
256 | child: Column( |
257 | children: <Widget>[ |
258 | const Text('Page 1' ), |
259 | CupertinoButton( |
260 | onPressed: () { |
261 | showCupertinoSheet<void>( |
262 | context: scaffoldKey.currentContext!, |
263 | pageBuilder: (BuildContext context) { |
264 | return CupertinoPageScaffold(child: sheetScaffoldContent(context)); |
265 | }, |
266 | ); |
267 | }, |
268 | child: const Text('Push Page 2' ), |
269 | ), |
270 | ], |
271 | ), |
272 | ), |
273 | ), |
274 | ), |
275 | ); |
276 | |
277 | expect(find.text('Page 1' ), findsOneWidget); |
278 | |
279 | await tester.tap(find.text('Push Page 2' )); |
280 | await tester.pumpAndSettle(); |
281 | |
282 | expect(find.text('Page 2' ), findsOneWidget); |
283 | expect(find.text('Page 3' ), findsNothing); |
284 | |
285 | expect( |
286 | tester |
287 | .getTopLeft( |
288 | find.ancestor(of: find.text('Page 2' ), matching: find.byType(CupertinoPageScaffold)), |
289 | ) |
290 | .dy, |
291 | greaterThan(0.0), |
292 | ); |
293 | |
294 | await tester.tap(find.text('Push Page 3' )); |
295 | await tester.pumpAndSettle(); |
296 | |
297 | expect(find.text('Page 2' ), findsNothing); |
298 | expect(find.text('Page 3' ), findsOneWidget); |
299 | // New route should be at the top of the screen. |
300 | expect( |
301 | tester |
302 | .getTopLeft( |
303 | find.ancestor(of: find.text('Page 3' ), matching: find.byType(CupertinoPageScaffold)), |
304 | ) |
305 | .dy, |
306 | equals(0.0), |
307 | ); |
308 | }); |
309 | |
310 | testWidgets('useNestedNavigation set to true enables nested navigation' , ( |
311 | WidgetTester tester, |
312 | ) async { |
313 | final GlobalKey scaffoldKey = GlobalKey(); |
314 | |
315 | Widget sheetScaffoldContent(BuildContext context) { |
316 | return Column( |
317 | children: <Widget>[ |
318 | const Text('Page 2' ), |
319 | CupertinoButton( |
320 | onPressed: () { |
321 | Navigator.push<void>( |
322 | context, |
323 | CupertinoPageRoute<void>( |
324 | builder: (BuildContext context) { |
325 | return CupertinoPageScaffold( |
326 | child: Column( |
327 | children: <Widget>[ |
328 | const Text('Page 3' ), |
329 | CupertinoButton(onPressed: () {}, child: const Text('Pop Page 3' )), |
330 | ], |
331 | ), |
332 | ); |
333 | }, |
334 | ), |
335 | ); |
336 | }, |
337 | child: const Text('Push Page 3' ), |
338 | ), |
339 | ], |
340 | ); |
341 | } |
342 | |
343 | await tester.pumpWidget( |
344 | CupertinoApp( |
345 | home: CupertinoPageScaffold( |
346 | key: scaffoldKey, |
347 | child: Center( |
348 | child: Column( |
349 | children: <Widget>[ |
350 | const Text('Page 1' ), |
351 | CupertinoButton( |
352 | onPressed: () { |
353 | showCupertinoSheet<void>( |
354 | context: scaffoldKey.currentContext!, |
355 | useNestedNavigation: true, |
356 | pageBuilder: (BuildContext context) { |
357 | return CupertinoPageScaffold(child: sheetScaffoldContent(context)); |
358 | }, |
359 | ); |
360 | }, |
361 | child: const Text('Push Page 2' ), |
362 | ), |
363 | ], |
364 | ), |
365 | ), |
366 | ), |
367 | ), |
368 | ); |
369 | |
370 | expect(find.text('Page 1' ), findsOneWidget); |
371 | |
372 | await tester.tap(find.text('Push Page 2' )); |
373 | await tester.pumpAndSettle(); |
374 | |
375 | expect(find.text('Page 2' ), findsOneWidget); |
376 | expect(find.text('Page 3' ), findsNothing); |
377 | |
378 | final double pageTwoDY = |
379 | tester |
380 | .getTopLeft( |
381 | find.ancestor(of: find.text('Page 2' ), matching: find.byType(CupertinoPageScaffold)), |
382 | ) |
383 | .dy; |
384 | expect(pageTwoDY, greaterThan(0.0)); |
385 | |
386 | await tester.tap(find.text('Push Page 3' )); |
387 | await tester.pumpAndSettle(); |
388 | |
389 | expect(find.text('Page 2' ), findsNothing); |
390 | expect(find.text('Page 3' ), findsOneWidget); |
391 | |
392 | // New route should be at the same height as the previous route. |
393 | final double pageThreeDY = |
394 | tester |
395 | .getTopLeft( |
396 | find.ancestor(of: find.text('Page 3' ), matching: find.byType(CupertinoPageScaffold)), |
397 | ) |
398 | .dy; |
399 | expect(pageThreeDY, greaterThan(0.0)); |
400 | expect(pageThreeDY, equals(pageTwoDY)); |
401 | }); |
402 | |
403 | testWidgets('useNestedNavigation handles programmatic pops' , (WidgetTester tester) async { |
404 | final GlobalKey scaffoldKey = GlobalKey(); |
405 | |
406 | Widget sheetScaffoldContent(BuildContext context) { |
407 | return Column( |
408 | children: <Widget>[ |
409 | const Text('Page 2' ), |
410 | CupertinoButton( |
411 | onPressed: () => Navigator.of(context).maybePop(), |
412 | child: const Text('Go Back' ), |
413 | ), |
414 | ], |
415 | ); |
416 | } |
417 | |
418 | await tester.pumpWidget( |
419 | CupertinoApp( |
420 | home: CupertinoPageScaffold( |
421 | key: scaffoldKey, |
422 | child: Center( |
423 | child: Column( |
424 | children: <Widget>[ |
425 | const Text('Page 1' ), |
426 | CupertinoButton( |
427 | onPressed: () { |
428 | showCupertinoSheet<void>( |
429 | context: scaffoldKey.currentContext!, |
430 | useNestedNavigation: true, |
431 | pageBuilder: (BuildContext context) { |
432 | return CupertinoPageScaffold(child: sheetScaffoldContent(context)); |
433 | }, |
434 | ); |
435 | }, |
436 | child: const Text('Push Page 2' ), |
437 | ), |
438 | ], |
439 | ), |
440 | ), |
441 | ), |
442 | ), |
443 | ); |
444 | |
445 | expect(find.text('Page 1' ), findsOneWidget); |
446 | // The first page is at the top of the screen. |
447 | expect( |
448 | tester |
449 | .getTopLeft( |
450 | find.ancestor(of: find.text('Page 1' ), matching: find.byType(CupertinoPageScaffold)), |
451 | ) |
452 | .dy, |
453 | equals(0.0), |
454 | ); |
455 | expect(find.text('Page 2' ), findsNothing); |
456 | |
457 | await tester.tap(find.text('Push Page 2' )); |
458 | await tester.pumpAndSettle(); |
459 | |
460 | expect(find.text('Page 2' ), findsOneWidget); |
461 | |
462 | // The first page, which is behind the top sheet but still partially visibile, is moved downwards. |
463 | expect( |
464 | tester |
465 | .getTopLeft( |
466 | find.ancestor(of: find.text('Page 1' ), matching: find.byType(CupertinoPageScaffold)), |
467 | ) |
468 | .dy, |
469 | greaterThan(0.0), |
470 | ); |
471 | |
472 | await tester.tap(find.text('Go Back' )); |
473 | await tester.pumpAndSettle(); |
474 | |
475 | // The first page would correctly transition back and sit at the top of the screen. |
476 | expect(find.text('Page 1' ), findsOneWidget); |
477 | expect( |
478 | tester |
479 | .getTopLeft( |
480 | find.ancestor(of: find.text('Page 1' ), matching: find.byType(CupertinoPageScaffold)), |
481 | ) |
482 | .dy, |
483 | equals(0.0), |
484 | ); |
485 | expect(find.text('Page 2' ), findsNothing); |
486 | }); |
487 | |
488 | testWidgets('useNestedNavigation handles system pop gestures' , (WidgetTester tester) async { |
489 | final GlobalKey scaffoldKey = GlobalKey(); |
490 | |
491 | Widget sheetScaffoldContent(BuildContext context) { |
492 | return Column( |
493 | children: <Widget>[ |
494 | const Text('Page 2' ), |
495 | CupertinoButton( |
496 | onPressed: () { |
497 | Navigator.of(context).push( |
498 | CupertinoPageRoute<void>( |
499 | builder: (BuildContext context) { |
500 | return CupertinoPageScaffold( |
501 | child: Column( |
502 | children: <Widget>[ |
503 | const Text('Page 3' ), |
504 | CupertinoButton( |
505 | onPressed: () { |
506 | Navigator.of(context).pop(); |
507 | }, |
508 | child: const Text('Go back' ), |
509 | ), |
510 | ], |
511 | ), |
512 | ); |
513 | }, |
514 | ), |
515 | ); |
516 | }, |
517 | child: const Text('Push Page 3' ), |
518 | ), |
519 | ], |
520 | ); |
521 | } |
522 | |
523 | await tester.pumpWidget( |
524 | CupertinoApp( |
525 | home: CupertinoPageScaffold( |
526 | key: scaffoldKey, |
527 | child: Center( |
528 | child: Column( |
529 | children: <Widget>[ |
530 | const Text('Page 1' ), |
531 | CupertinoButton( |
532 | onPressed: () { |
533 | showCupertinoSheet<void>( |
534 | context: scaffoldKey.currentContext!, |
535 | useNestedNavigation: true, |
536 | pageBuilder: (BuildContext context) { |
537 | return CupertinoPageScaffold(child: sheetScaffoldContent(context)); |
538 | }, |
539 | ); |
540 | }, |
541 | child: const Text('Push Page 2' ), |
542 | ), |
543 | ], |
544 | ), |
545 | ), |
546 | ), |
547 | ), |
548 | ); |
549 | |
550 | expect(find.text('Page 1' ), findsOneWidget); |
551 | // The first page is at the top of the screen. |
552 | expect( |
553 | tester |
554 | .getTopLeft( |
555 | find.ancestor(of: find.text('Page 1' ), matching: find.byType(CupertinoPageScaffold)), |
556 | ) |
557 | .dy, |
558 | equals(0.0), |
559 | ); |
560 | expect(find.text('Page 2' ), findsNothing); |
561 | expect(find.text('Page 3' ), findsNothing); |
562 | |
563 | await tester.tap(find.text('Push Page 2' )); |
564 | await tester.pumpAndSettle(); |
565 | |
566 | expect(find.text('Page 2' ), findsOneWidget); |
567 | expect(find.text('Page 3' ), findsNothing); |
568 | |
569 | // The first page, which is behind the top sheet but still partially visibile, is moved downwards. |
570 | expect( |
571 | tester |
572 | .getTopLeft( |
573 | find.ancestor(of: find.text('Page 1' ), matching: find.byType(CupertinoPageScaffold)), |
574 | ) |
575 | .dy, |
576 | greaterThan(0.0), |
577 | ); |
578 | |
579 | await tester.tap(find.text('Push Page 3' )); |
580 | await tester.pumpAndSettle(); |
581 | |
582 | expect(find.text('Page 3' ), findsOneWidget); |
583 | |
584 | // Simulate a system back gesture. |
585 | await simulateSystemBack(); |
586 | await tester.pumpAndSettle(); |
587 | |
588 | // Go back to the first page within the sheet. |
589 | expect(find.text('Page 2' ), findsOneWidget); |
590 | expect(find.text('Page 3' ), findsNothing); |
591 | |
592 | // The first page is still stacked behind the sheet. |
593 | expect( |
594 | tester |
595 | .getTopLeft( |
596 | find.ancestor(of: find.text('Page 1' ), matching: find.byType(CupertinoPageScaffold)), |
597 | ) |
598 | .dy, |
599 | greaterThan(0.0), |
600 | ); |
601 | |
602 | await simulateSystemBack(); |
603 | await tester.pumpAndSettle(); |
604 | |
605 | // The first page would correctly transition back and sit at the top of the screen. |
606 | expect(find.text('Page 1' ), findsOneWidget); |
607 | expect( |
608 | tester |
609 | .getTopLeft( |
610 | find.ancestor(of: find.text('Page 1' ), matching: find.byType(CupertinoPageScaffold)), |
611 | ) |
612 | .dy, |
613 | equals(0.0), |
614 | ); |
615 | expect(find.text('Page 2' ), findsNothing); |
616 | }); |
617 | |
618 | testWidgets('sheet has route settings' , (WidgetTester tester) async { |
619 | await tester.pumpWidget( |
620 | CupertinoApp( |
621 | initialRoute: '/' , |
622 | onGenerateRoute: (RouteSettings settings) { |
623 | if (settings.name == '/' ) { |
624 | return PageRouteBuilder<void>( |
625 | pageBuilder: ( |
626 | BuildContext context, |
627 | Animation<double> animation, |
628 | Animation<double> secondaryAnimation, |
629 | ) { |
630 | return CupertinoPageScaffold( |
631 | navigationBar: const CupertinoNavigationBar(middle: Text('Page 1' )), |
632 | child: Container(), |
633 | ); |
634 | }, |
635 | ); |
636 | } |
637 | return CupertinoSheetRoute<void>( |
638 | builder: (BuildContext context) { |
639 | return CupertinoPageScaffold( |
640 | navigationBar: CupertinoNavigationBar(middle: Text('Page: ${settings.name}' )), |
641 | child: Container(), |
642 | ); |
643 | }, |
644 | ); |
645 | }, |
646 | ), |
647 | ); |
648 | |
649 | expect(find.text('Page 1' ), findsOneWidget); |
650 | expect(find.text('Page 2' ), findsNothing); |
651 | |
652 | tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next' ); |
653 | await tester.pumpAndSettle(); |
654 | |
655 | expect(find.text('Page: /next' ), findsOneWidget); |
656 | }); |
657 | |
658 | testWidgets('content does not go below the bottom of the screen' , (WidgetTester tester) async { |
659 | final GlobalKey scaffoldKey = GlobalKey(); |
660 | |
661 | await tester.pumpWidget( |
662 | CupertinoApp( |
663 | home: CupertinoPageScaffold( |
664 | key: scaffoldKey, |
665 | child: Center( |
666 | child: Column( |
667 | children: <Widget>[ |
668 | const Text('Page 1' ), |
669 | CupertinoButton( |
670 | onPressed: () { |
671 | Navigator.push<void>( |
672 | scaffoldKey.currentContext!, |
673 | CupertinoSheetRoute<void>( |
674 | builder: (BuildContext context) { |
675 | return CupertinoPageScaffold(child: Container()); |
676 | }, |
677 | ), |
678 | ); |
679 | }, |
680 | child: const Text('Push Page 2' ), |
681 | ), |
682 | ], |
683 | ), |
684 | ), |
685 | ), |
686 | ), |
687 | ); |
688 | |
689 | await tester.tap(find.text('Push Page 2' )); |
690 | await tester.pumpAndSettle(); |
691 | |
692 | expect(tester.getSize(find.byType(Container)).height, 600.0 - (600.0 * _kTopGapRatio)); |
693 | }); |
694 | |
695 | testWidgets('nested navbars remove MediaQuery top padding' , (WidgetTester tester) async { |
696 | final GlobalKey scaffoldKey = GlobalKey(); |
697 | final GlobalKey appBarKey = GlobalKey(); |
698 | final GlobalKey sheetBarKey = GlobalKey(); |
699 | |
700 | await tester.pumpWidget( |
701 | CupertinoApp( |
702 | home: MediaQuery( |
703 | data: const MediaQueryData(padding: EdgeInsets.fromLTRB(0, 20, 0, 0)), |
704 | child: CupertinoPageScaffold( |
705 | key: scaffoldKey, |
706 | navigationBar: CupertinoNavigationBar( |
707 | key: appBarKey, |
708 | middle: const Text('Navbar' ), |
709 | backgroundColor: const Color(0xFFF8F8F8), |
710 | ), |
711 | child: Center( |
712 | child: Column( |
713 | children: <Widget>[ |
714 | const Text('Page 1' ), |
715 | CupertinoButton( |
716 | onPressed: () { |
717 | Navigator.push<void>( |
718 | scaffoldKey.currentContext!, |
719 | CupertinoSheetRoute<void>( |
720 | builder: (BuildContext context) { |
721 | return CupertinoPageScaffold( |
722 | navigationBar: CupertinoNavigationBar( |
723 | key: sheetBarKey, |
724 | middle: const Text('Navbar' ), |
725 | ), |
726 | child: Container(), |
727 | ); |
728 | }, |
729 | ), |
730 | ); |
731 | }, |
732 | child: const Text('Push Page 2' ), |
733 | ), |
734 | ], |
735 | ), |
736 | ), |
737 | ), |
738 | ), |
739 | ), |
740 | ); |
741 | final double homeNavBardHeight = tester.getSize(find.byKey(appBarKey)).height; |
742 | |
743 | await tester.tap(find.text('Push Page 2' )); |
744 | await tester.pumpAndSettle(); |
745 | |
746 | final double sheetNavBarHeight = tester.getSize(find.byKey(sheetBarKey)).height; |
747 | |
748 | expect(sheetNavBarHeight, lessThan(homeNavBardHeight)); |
749 | }); |
750 | |
751 | testWidgets('Previous route corner radius goes to same when sheet route is popped' , ( |
752 | WidgetTester tester, |
753 | ) async { |
754 | final GlobalKey scaffoldKey = GlobalKey(); |
755 | |
756 | await tester.pumpWidget( |
757 | CupertinoApp( |
758 | home: CupertinoPageScaffold( |
759 | key: scaffoldKey, |
760 | child: Column( |
761 | children: <Widget>[ |
762 | const Text('Page 1' ), |
763 | CupertinoButton( |
764 | onPressed: () { |
765 | Navigator.push<void>( |
766 | scaffoldKey.currentContext!, |
767 | CupertinoSheetRoute<void>( |
768 | builder: (BuildContext context) { |
769 | return CupertinoPageScaffold( |
770 | child: GestureDetector( |
771 | onTap: () => Navigator.pop(context), |
772 | child: const Icon(Icons.arrow_back_ios), |
773 | ), |
774 | ); |
775 | }, |
776 | ), |
777 | ); |
778 | }, |
779 | child: const Text('Push Page 2' ), |
780 | ), |
781 | ], |
782 | ), |
783 | ), |
784 | ), |
785 | ); |
786 | |
787 | expect(find.text('Page 1' ), findsOneWidget); |
788 | expect( |
789 | tester |
790 | .getTopLeft( |
791 | find.ancestor(of: find.text('Page 1' ), matching: find.byType(CupertinoPageScaffold)), |
792 | ) |
793 | .dy, |
794 | equals(0.0), |
795 | ); |
796 | expect(find.byType(Icon), findsNothing); |
797 | |
798 | await tester.tap(find.text('Push Page 2' )); |
799 | await tester.pumpAndSettle(); |
800 | |
801 | // Previous page is still visible behind the new sheet. |
802 | expect(find.text('Page 1' ), findsOneWidget); |
803 | final Offset pageOneOffset = tester.getTopLeft( |
804 | find.ancestor(of: find.text('Page 1' ), matching: find.byType(CupertinoPageScaffold)), |
805 | ); |
806 | expect(pageOneOffset.dy, greaterThan(0.0)); |
807 | expect(pageOneOffset.dx, greaterThan(0.0)); |
808 | expect(find.byType(Icon), findsOneWidget); |
809 | |
810 | // Pop Sheet Route |
811 | await tester.tap(find.byType(Icon)); |
812 | await tester.pumpAndSettle(); |
813 | |
814 | final Finder clipRRectFinder = find.byType(ClipRRect); |
815 | expect(clipRRectFinder, findsNothing); |
816 | }); |
817 | |
818 | testWidgets('Sheet transition does not interfere after popping' , (WidgetTester tester) async { |
819 | final GlobalKey homeKey = GlobalKey(); |
820 | final GlobalKey sheetKey = GlobalKey(); |
821 | final GlobalKey popupMenuButtonKey = GlobalKey(); |
822 | |
823 | await tester.pumpWidget( |
824 | CupertinoApp( |
825 | localizationsDelegates: const <LocalizationsDelegate<dynamic>>[ |
826 | GlobalMaterialLocalizations.delegate, |
827 | GlobalWidgetsLocalizations.delegate, |
828 | GlobalCupertinoLocalizations.delegate, |
829 | ], |
830 | home: CupertinoPageScaffold( |
831 | key: homeKey, |
832 | child: CupertinoListTile( |
833 | onTap: () { |
834 | showCupertinoSheet<void>( |
835 | context: homeKey.currentContext!, |
836 | pageBuilder: (BuildContext context) { |
837 | return CupertinoPageScaffold( |
838 | key: sheetKey, |
839 | child: const Center(child: Text('Page 2' )), |
840 | ); |
841 | }, |
842 | ); |
843 | }, |
844 | title: const Text('ListItem 0' ), |
845 | trailing: Material( |
846 | type: MaterialType.transparency, |
847 | child: PopupMenuButton<int>( |
848 | key: popupMenuButtonKey, |
849 | itemBuilder: (BuildContext context) { |
850 | return <PopupMenuEntry<int>>[ |
851 | const PopupMenuItem<int>(child: Text('Item 0' )), |
852 | const PopupMenuItem<int>(child: Text('Item 1' )), |
853 | ]; |
854 | }, |
855 | ), |
856 | ), |
857 | ), |
858 | ), |
859 | ), |
860 | ); |
861 | |
862 | await tester.tap(find.text('ListItem 0' )); |
863 | await tester.pumpAndSettle(); |
864 | |
865 | expect(find.text('Page 2' ), findsOneWidget); |
866 | |
867 | final TestGesture gesture = await tester.startGesture(const Offset(100, 200)); |
868 | await gesture.moveBy(const Offset(0, 350)); |
869 | await tester.pump(); |
870 | |
871 | await gesture.up(); |
872 | await tester.pumpAndSettle(); |
873 | |
874 | expect(find.text('Page 2' ), findsNothing); |
875 | expect(find.text('ListItem 0' ), findsOneWidget); |
876 | |
877 | await tester.tap(find.byKey(popupMenuButtonKey)); |
878 | await tester.pumpAndSettle(); |
879 | |
880 | expect(find.text('Item 0' ), findsOneWidget); |
881 | expect(tester.takeException(), isNull); |
882 | }); |
883 | |
884 | group('drag dismiss gesture' , () { |
885 | Widget dragGestureApp(GlobalKey homeScaffoldKey, GlobalKey sheetScaffoldKey) { |
886 | return CupertinoApp( |
887 | home: CupertinoPageScaffold( |
888 | key: homeScaffoldKey, |
889 | child: Center( |
890 | child: Column( |
891 | children: <Widget>[ |
892 | const Text('Page 1' ), |
893 | CupertinoButton( |
894 | onPressed: () { |
895 | showCupertinoSheet<void>( |
896 | context: homeScaffoldKey.currentContext!, |
897 | pageBuilder: (BuildContext context) { |
898 | return CupertinoPageScaffold( |
899 | key: sheetScaffoldKey, |
900 | child: const Center(child: Text('Page 2' )), |
901 | ); |
902 | }, |
903 | ); |
904 | }, |
905 | child: const Text('Push Page 2' ), |
906 | ), |
907 | ], |
908 | ), |
909 | ), |
910 | ), |
911 | ); |
912 | } |
913 | |
914 | testWidgets('partial drag and drop does not pop the sheet' , (WidgetTester tester) async { |
915 | final GlobalKey homeKey = GlobalKey(); |
916 | final GlobalKey sheetKey = GlobalKey(); |
917 | |
918 | await tester.pumpWidget(dragGestureApp(homeKey, sheetKey)); |
919 | |
920 | await tester.tap(find.text('Push Page 2' )); |
921 | await tester.pumpAndSettle(); |
922 | |
923 | expect(find.text('Page 2' ), findsOneWidget); |
924 | |
925 | RenderBox box = tester.renderObject(find.byKey(sheetKey)) as RenderBox; |
926 | final double initialPosition = box.localToGlobal(Offset.zero).dy; |
927 | |
928 | final TestGesture gesture = await tester.startGesture(const Offset(100, 200)); |
929 | // Partial drag down |
930 | await gesture.moveBy(const Offset(0, 200)); |
931 | await tester.pump(); |
932 | |
933 | box = tester.renderObject(find.byKey(sheetKey)) as RenderBox; |
934 | final double middlePosition = box.localToGlobal(Offset.zero).dy; |
935 | expect(middlePosition, greaterThan(initialPosition)); |
936 | |
937 | // Release gesture. Sheet should not pop and slide back up. |
938 | await gesture.up(); |
939 | await tester.pumpAndSettle(); |
940 | |
941 | expect(find.text('Page 2' ), findsOneWidget); |
942 | |
943 | box = tester.renderObject(find.byKey(sheetKey)) as RenderBox; |
944 | final double finalPosition = box.localToGlobal(Offset.zero).dy; |
945 | |
946 | expect(finalPosition, lessThan(middlePosition)); |
947 | expect(finalPosition, equals(initialPosition)); |
948 | }); |
949 | |
950 | testWidgets('dropping the drag further down the page pops the sheet' , ( |
951 | WidgetTester tester, |
952 | ) async { |
953 | final GlobalKey homeKey = GlobalKey(); |
954 | final GlobalKey sheetKey = GlobalKey(); |
955 | |
956 | await tester.pumpWidget(dragGestureApp(homeKey, sheetKey)); |
957 | |
958 | await tester.tap(find.text('Push Page 2' )); |
959 | await tester.pumpAndSettle(); |
960 | |
961 | expect(find.text('Page 2' ), findsOneWidget); |
962 | |
963 | final TestGesture gesture = await tester.startGesture(const Offset(100, 200)); |
964 | await gesture.moveBy(const Offset(0, 350)); |
965 | await tester.pump(); |
966 | |
967 | await gesture.up(); |
968 | await tester.pumpAndSettle(); |
969 | |
970 | expect(find.text('Page 2' ), findsNothing); |
971 | }); |
972 | |
973 | testWidgets('dismissing with a drag pops all nested routes' , (WidgetTester tester) async { |
974 | final GlobalKey homeKey = GlobalKey(); |
975 | final GlobalKey sheetKey = GlobalKey(); |
976 | |
977 | Widget sheetScaffoldContent(BuildContext context) { |
978 | return Column( |
979 | children: <Widget>[ |
980 | const Text('Page 2' ), |
981 | CupertinoButton( |
982 | onPressed: () { |
983 | Navigator.of(context).push( |
984 | CupertinoPageRoute<void>( |
985 | builder: (BuildContext context) { |
986 | return const CupertinoPageScaffold(child: Center(child: Text('Page 3' ))); |
987 | }, |
988 | ), |
989 | ); |
990 | }, |
991 | child: const Text('Push Page 3' ), |
992 | ), |
993 | ], |
994 | ); |
995 | } |
996 | |
997 | await tester.pumpWidget( |
998 | CupertinoApp( |
999 | home: CupertinoPageScaffold( |
1000 | key: homeKey, |
1001 | child: Center( |
1002 | child: Column( |
1003 | children: <Widget>[ |
1004 | const Text('Page 1' ), |
1005 | CupertinoButton( |
1006 | onPressed: () { |
1007 | showCupertinoSheet<void>( |
1008 | context: homeKey.currentContext!, |
1009 | useNestedNavigation: true, |
1010 | pageBuilder: (BuildContext context) { |
1011 | return CupertinoPageScaffold( |
1012 | key: sheetKey, |
1013 | child: sheetScaffoldContent(context), |
1014 | ); |
1015 | }, |
1016 | ); |
1017 | }, |
1018 | child: const Text('Push Page 2' ), |
1019 | ), |
1020 | ], |
1021 | ), |
1022 | ), |
1023 | ), |
1024 | ), |
1025 | ); |
1026 | |
1027 | await tester.tap(find.text('Push Page 2' )); |
1028 | await tester.pumpAndSettle(); |
1029 | |
1030 | expect(find.text('Page 2' ), findsOneWidget); |
1031 | |
1032 | await tester.tap(find.text('Push Page 3' )); |
1033 | await tester.pumpAndSettle(); |
1034 | |
1035 | expect(find.text('Page 3' ), findsOneWidget); |
1036 | |
1037 | final TestGesture gesture = await tester.startGesture(const Offset(100, 200)); |
1038 | await gesture.moveBy(const Offset(0, 350)); |
1039 | await tester.pump(); |
1040 | |
1041 | await gesture.up(); |
1042 | await tester.pumpAndSettle(); |
1043 | |
1044 | expect(find.text('Page 2' ), findsNothing); |
1045 | expect(find.text('Page 3' ), findsNothing); |
1046 | }); |
1047 | |
1048 | testWidgets('Popping the sheet during drag should not crash' , (WidgetTester tester) async { |
1049 | final GlobalKey homeKey = GlobalKey(); |
1050 | final GlobalKey sheetKey = GlobalKey(); |
1051 | |
1052 | await tester.pumpWidget(dragGestureApp(homeKey, sheetKey)); |
1053 | |
1054 | await tester.tap(find.text('Push Page 2' )); |
1055 | await tester.pumpAndSettle(); |
1056 | |
1057 | final TestGesture gesture = await tester.createGesture(); |
1058 | |
1059 | await gesture.down(const Offset(100, 200)); |
1060 | |
1061 | // Need 2 events to form a valid drag |
1062 | await tester.pump(const Duration(milliseconds: 100)); |
1063 | await gesture.moveTo(const Offset(100, 300), timeStamp: const Duration(milliseconds: 100)); |
1064 | await tester.pump(const Duration(milliseconds: 200)); |
1065 | await gesture.moveTo(const Offset(100, 500), timeStamp: const Duration(milliseconds: 200)); |
1066 | |
1067 | Navigator.of(homeKey.currentContext!).pop(); |
1068 | |
1069 | await tester.pumpAndSettle(); |
1070 | |
1071 | expect(find.text('Page 1' ), findsOneWidget); |
1072 | expect(find.text('Page 2' ), findsNothing); |
1073 | |
1074 | await gesture.up(); |
1075 | await tester.pumpAndSettle(); |
1076 | |
1077 | expect(find.text('Page 1' ), findsOneWidget); |
1078 | }); |
1079 | |
1080 | testWidgets('Sheet should not block nested scroll' , (WidgetTester tester) async { |
1081 | final GlobalKey homeKey = GlobalKey(); |
1082 | |
1083 | Widget sheetScaffoldContent(BuildContext context) { |
1084 | return ListView( |
1085 | children: const <Widget>[ |
1086 | Text('Top of Scroll' ), |
1087 | SizedBox(width: double.infinity, height: 100), |
1088 | Text('Middle of Scroll' ), |
1089 | SizedBox(width: double.infinity, height: 100), |
1090 | ], |
1091 | ); |
1092 | } |
1093 | |
1094 | await tester.pumpWidget( |
1095 | CupertinoApp( |
1096 | home: CupertinoPageScaffold( |
1097 | key: homeKey, |
1098 | child: Center( |
1099 | child: Column( |
1100 | children: <Widget>[ |
1101 | const Text('Page 1' ), |
1102 | CupertinoButton( |
1103 | onPressed: () { |
1104 | showCupertinoSheet<void>( |
1105 | context: homeKey.currentContext!, |
1106 | pageBuilder: (BuildContext context) { |
1107 | return CupertinoPageScaffold(child: sheetScaffoldContent(context)); |
1108 | }, |
1109 | ); |
1110 | }, |
1111 | child: const Text('Push Page 2' ), |
1112 | ), |
1113 | ], |
1114 | ), |
1115 | ), |
1116 | ), |
1117 | ), |
1118 | ); |
1119 | |
1120 | await tester.tap(find.text('Push Page 2' )); |
1121 | await tester.pumpAndSettle(); |
1122 | |
1123 | expect(find.text('Top of Scroll' ), findsOneWidget); |
1124 | final double startPosition = tester.getTopLeft(find.text('Middle of Scroll' )).dy; |
1125 | |
1126 | final TestGesture gesture = await tester.createGesture(); |
1127 | |
1128 | await gesture.down(const Offset(100, 100)); |
1129 | |
1130 | // Need 2 events to form a valid drag. |
1131 | await tester.pump(const Duration(milliseconds: 100)); |
1132 | await gesture.moveTo(const Offset(100, 80), timeStamp: const Duration(milliseconds: 100)); |
1133 | await tester.pump(const Duration(milliseconds: 200)); |
1134 | await gesture.moveTo(const Offset(100, 50), timeStamp: const Duration(milliseconds: 200)); |
1135 | |
1136 | await tester.pumpAndSettle(); |
1137 | |
1138 | final double endPosition = tester.getTopLeft(find.text('Middle of Scroll' )).dy; |
1139 | |
1140 | // Final position should be higher. |
1141 | expect(endPosition, lessThan(startPosition)); |
1142 | |
1143 | await gesture.up(); |
1144 | await tester.pumpAndSettle(); |
1145 | }); |
1146 | |
1147 | testWidgets('dragging does not move the sheet when enableDrag is false' , ( |
1148 | WidgetTester tester, |
1149 | ) async { |
1150 | Widget nonDragGestureApp(GlobalKey homeScaffoldKey, GlobalKey sheetScaffoldKey) { |
1151 | return CupertinoApp( |
1152 | home: CupertinoPageScaffold( |
1153 | key: homeScaffoldKey, |
1154 | child: Center( |
1155 | child: Column( |
1156 | children: <Widget>[ |
1157 | const Text('Page 1' ), |
1158 | CupertinoButton( |
1159 | onPressed: () { |
1160 | showCupertinoSheet<void>( |
1161 | context: homeScaffoldKey.currentContext!, |
1162 | pageBuilder: (BuildContext context) { |
1163 | return CupertinoPageScaffold( |
1164 | key: sheetScaffoldKey, |
1165 | child: const Center(child: Text('Page 2' )), |
1166 | ); |
1167 | }, |
1168 | enableDrag: false, |
1169 | ); |
1170 | }, |
1171 | child: const Text('Push Page 2' ), |
1172 | ), |
1173 | ], |
1174 | ), |
1175 | ), |
1176 | ), |
1177 | ); |
1178 | } |
1179 | |
1180 | final GlobalKey homeKey = GlobalKey(); |
1181 | final GlobalKey sheetKey = GlobalKey(); |
1182 | |
1183 | await tester.pumpWidget(nonDragGestureApp(homeKey, sheetKey)); |
1184 | |
1185 | await tester.tap(find.text('Push Page 2' )); |
1186 | await tester.pumpAndSettle(); |
1187 | |
1188 | expect(find.text('Page 2' ), findsOneWidget); |
1189 | |
1190 | RenderBox box = tester.renderObject(find.byKey(sheetKey)) as RenderBox; |
1191 | final double initialPosition = box.localToGlobal(Offset.zero).dy; |
1192 | |
1193 | final TestGesture gesture = await tester.startGesture(const Offset(100, 200)); |
1194 | // Partial drag down |
1195 | await gesture.moveBy(const Offset(0, 200)); |
1196 | await tester.pump(); |
1197 | |
1198 | // Release gesture. Sheet should not move. |
1199 | box = tester.renderObject(find.byKey(sheetKey)) as RenderBox; |
1200 | final double middlePosition = box.localToGlobal(Offset.zero).dy; |
1201 | |
1202 | expect(middlePosition, equals(initialPosition)); |
1203 | |
1204 | await gesture.up(); |
1205 | await tester.pumpAndSettle(); |
1206 | |
1207 | expect(find.text('Page 2' ), findsOneWidget); |
1208 | |
1209 | box = tester.renderObject(find.byKey(sheetKey)) as RenderBox; |
1210 | final double finalPosition = box.localToGlobal(Offset.zero).dy; |
1211 | |
1212 | expect(finalPosition, equals(middlePosition)); |
1213 | expect(finalPosition, equals(initialPosition)); |
1214 | }); |
1215 | }); |
1216 | |
1217 | testWidgets('CupertinoSheetTransition handles SystemUiOverlayStyle changes' , ( |
1218 | WidgetTester tester, |
1219 | ) async { |
1220 | final AnimationController controller = AnimationController( |
1221 | duration: const Duration(milliseconds: 100), |
1222 | vsync: const TestVSync(), |
1223 | ); |
1224 | addTearDown(controller.dispose); |
1225 | |
1226 | final Animation<double> secondaryAnimation = Tween<double>( |
1227 | begin: 1, |
1228 | end: 0, |
1229 | ).animate(controller); |
1230 | |
1231 | await tester.pumpWidget( |
1232 | CupertinoApp( |
1233 | home: AnimatedBuilder( |
1234 | animation: controller, |
1235 | builder: (BuildContext context, Widget? child) { |
1236 | return CupertinoSheetTransition( |
1237 | primaryRouteAnimation: controller, |
1238 | secondaryRouteAnimation: secondaryAnimation, |
1239 | linearTransition: false, |
1240 | child: const SizedBox(), |
1241 | ); |
1242 | }, |
1243 | ), |
1244 | ), |
1245 | ); |
1246 | |
1247 | expect(SystemChrome.latestStyle!.statusBarBrightness, Brightness.dark); |
1248 | expect(SystemChrome.latestStyle!.statusBarIconBrightness, Brightness.light); |
1249 | }); |
1250 | } |
1251 | |