From d6e2a83cb4713d8d1e02fcbc13d2628dbf63a178 Mon Sep 17 00:00:00 2001 From: Timm Preetz Date: Fri, 16 Aug 2024 16:30:25 +0200 Subject: [PATCH] Switch to new Flutter 3.24.0 Navigator API: `onDidRemovePage` --- CHANGELOG.md | 8 +- example/ios/Runner/AppDelegate.swift | 2 +- example/lib/main.dart | 92 ++++++++- example/pubspec.lock | 28 +-- example/test/widget_test.dart | 15 +- lib/fdr.dart | 1 + lib/src/declarative_navigator.dart | 80 +++++--- lib/src/pages/cupertino_page.dart | 51 +++++ lib/src/pages/fdr_page.dart | 3 + lib/src/pages/material_page.dart | 50 +++++ pubspec.yaml | 6 +- test/dynamic_back_button_test.dart | 182 ++++++++++++++++++ .../dynamic_back_button/android/01_off.png | Bin 0 -> 7143 bytes .../dynamic_back_button/android/02_on.png | Bin 0 -> 6985 bytes .../android/03_transitioning_out.png | Bin 0 -> 9626 bytes .../dynamic_back_button/ios/01_off.png | Bin 0 -> 3748 bytes .../goldens/dynamic_back_button/ios/02_on.png | Bin 0 -> 4352 bytes .../ios/03_transitioning_out.png | Bin 0 -> 4495 bytes 18 files changed, 455 insertions(+), 63 deletions(-) create mode 100644 lib/src/pages/cupertino_page.dart create mode 100644 lib/src/pages/fdr_page.dart create mode 100644 lib/src/pages/material_page.dart create mode 100644 test/dynamic_back_button_test.dart create mode 100644 test/goldens/dynamic_back_button/android/01_off.png create mode 100644 test/goldens/dynamic_back_button/android/02_on.png create mode 100644 test/goldens/dynamic_back_button/android/03_transitioning_out.png create mode 100644 test/goldens/dynamic_back_button/ios/01_off.png create mode 100644 test/goldens/dynamic_back_button/ios/02_on.png create mode 100644 test/goldens/dynamic_back_button/ios/03_transitioning_out.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 41cc7d8..eec4671 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.0.2 + +* Switch to new Flutter 3.24.0 Navigator API: `onDidRemovePage` + * Make use of `Page.canPop` and `Page.onPopInvoke` to tell the framework which pages can be popped. + This can be configured / changed during the page's display, e.g. to prevent back swipes once a form contains changes. + ## 0.0.1 -* TODO: Describe initial release. +* Initial release with basic page and navigtable source ("bloc for routing") support diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 9074fee..6266644 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Flutter import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/example/lib/main.dart b/example/lib/main.dart index ba0b241..d91d251 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -51,6 +51,7 @@ class ExampleSelectionNavigator ExampleSelectionPage( examples: { 'List Detail': () => ListDetailNavigator(), + 'Dynamic back behavior': () => DynamicPopNavigator(), }, // TODO(tp): Dispose previous if needed onExampleSelect: (exampleFactory) { @@ -62,7 +63,7 @@ class ExampleSelectionNavigator this.state = exampleFactory(); }, - ).page, + ).page(onPop: null), if (state != null) if (state is NavigatableSource) // Use pipe type trick for type preservation? (not extension, but rather on class) @@ -127,6 +128,89 @@ class ExampleSelectionPage extends StatelessWidget { } } +class DynamicPopNavigator extends MappedNavigatableSource< + ( + bool /* shows child */, + bool /* can pop */, + )> { + DynamicPopNavigator() : super(initialState: (true, false)); + + @override + List build((bool, bool) state) { + final canPop = state.$2; + + return [ + const Placeholder().page(onPop: null), + if (state.$1) + PopToggle( + value: canPop, + onChange: (v) => this.state = (true, v), + ).page(onPop: canPop ? () => this.state = (false, false) : null), + ]; + } +} + +class PopToggle extends StatelessWidget { + const PopToggle({ + super.key, + required this.value, + required this.onChange, + }); + + final bool value; + + final ValueSetter onChange; + + @override + Widget build(BuildContext context) { + if (Theme.of(context).platform == TargetPlatform.android) { + return Scaffold( + appBar: AppBar( + title: const Text('Dynamic pop'), + ), + body: Switch( + value: value, + onChanged: onChange, + ), + ); + } + + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar( + middle: Text('Dynamic Pop'), + ), + child: Container( + color: CupertinoColors.systemGroupedBackground, + child: SafeArea( + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 20.0), + child: CupertinoFormSection.insetGrouped( + children: [ + CupertinoListTile.notched( + trailing: CupertinoSwitch( + value: value, + onChanged: onChange, + ), + title: const Text('Can pop'), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} + class ListDetailNavigator extends MappedNavigatableSource { ListDetailNavigator() : super(initialState: null); @@ -135,14 +219,14 @@ class ListDetailNavigator extends MappedNavigatableSource { return [ NumberSelectionPage( onNumberSelect: (number) => this.state = number, - ).page, + ).page(onPop: null), if (state != null) if (state == 7) LuckyNumberSevenPage( onTap: () => this.state = null, - ).popup + ).popup(onPop: () => this.state = null) else - NumberDetailPage(number: state).page..onPop = () => this.state = null, + NumberDetailPage(number: state).page(onPop: () => this.state = null), ]; } } diff --git a/example/pubspec.lock b/example/pubspec.lock index 85319b0..2d55253 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -63,7 +63,7 @@ packages: path: ".." relative: true source: path - version: "0.0.1" + version: "0.0.2" flutter: dependency: "direct main" description: flutter @@ -86,18 +86,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -126,18 +126,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" path: dependency: transitive description: @@ -195,10 +195,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" vector_math: dependency: transitive description: @@ -211,10 +211,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.4" sdks: dart: ">=3.4.4 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.24.0" diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index 6af93f2..a1dbe5e 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -2,19 +2,6 @@ import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('TODO', (WidgetTester tester) async { - // // Build our app and trigger a frame. - // await tester.pumpWidget(const MyApp()); - - // // Verify that our counter starts at 0. - // expect(find.text('0'), findsOneWidget); - // expect(find.text('1'), findsNothing); - - // // Tap the '+' icon and trigger a frame. - // await tester.tap(find.byIcon(Icons.add)); - // await tester.pump(); - - // // Verify that our counter has incremented. - // expect(find.text('0'), findsNothing); - // expect(find.text('1'), findsOneWidget); + // TODO(tp): Move goldens in here so implementation can be shared with demo gallery? }); } diff --git a/lib/fdr.dart b/lib/fdr.dart index 2802ca0..37d07f4 100644 --- a/lib/fdr.dart +++ b/lib/fdr.dart @@ -2,3 +2,4 @@ library fdr; export './src/declarative_navigator.dart'; +export './src/pages/fdr_page.dart'; diff --git a/lib/src/declarative_navigator.dart b/lib/src/declarative_navigator.dart index ce43b24..fb895c2 100644 --- a/lib/src/declarative_navigator.dart +++ b/lib/src/declarative_navigator.dart @@ -1,5 +1,7 @@ // ignore_for_file: unused_element +import 'package:fdr/src/pages/cupertino_page.dart'; +import 'package:fdr/src/pages/material_page.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -69,17 +71,8 @@ class _DeclarativeNavigatorState extends State { return Navigator( pages: pageNavigatables.map((n) => n.page).toList(), - onPopPage: (route, result) { - debugPrint('onPopPage: $route $result'); - - if (pageNavigatables.last.onPop != null) { - pageNavigatables.last.onPop!(); - - // pop will not yet be completed on edge-swipe (as the animation still runs), so we can not yet return `true` - return false; - } - - return false; + onDidRemovePage: (page) { + debugPrint('onPopPage: $page'); }, ); } @@ -130,16 +123,20 @@ class __ManagingDeclarativeNavigatorState sealed class DeclarativeNavigatable {} +typedef PageBuilder = Page Function(VoidCallback? onPop); + class DeclarativeNavigatablePage implements DeclarativeNavigatable { DeclarativeNavigatablePage({ - required this.page, - this.onPop, - }); + required PageBuilder builder, + required this.onPop, + }) : _builder = builder, + page = builder(onPop); + + final PageBuilder _builder; final Page page; - // TODO(tp): Make final, and then provide better creators - VoidCallback? onPop; + final VoidCallback? onPop; } extension Poppable on NavigatableSource { @@ -170,7 +167,7 @@ class _PoppableNavigatableSourceWrapper implements NavigatableSource { _pages.value = [ DeclarativeNavigatablePage( - page: pages.first.page, + builder: pages.first._builder, // TODO(tp): What about the underlying onpop, if any? (though unlikely makes sense) onPop: onPop, ), @@ -184,19 +181,47 @@ class _PoppableNavigatableSourceWrapper implements NavigatableSource { } } -extension DeclarativeNavigatableFromPage on Page { - DeclarativeNavigatablePage get navigatable { - return DeclarativeNavigatablePage(page: this); - } -} - extension DeclarativeNavigatableFromWidget on Widget { - DeclarativeNavigatablePage get page { - return CupertinoPage(child: this).navigatable; + DeclarativeNavigatablePage page({ + /// Provide `null` if the page should not be poppable + required VoidCallback? onPop, + }) { + return DeclarativeNavigatablePage( + builder: (onPop) => defaultTargetPlatform == TargetPlatform.android + ? FDRMaterialPage( + child: this, + canPop: onPop != null, + onPopInvoked: (didPop, _) { + if (didPop) { + // When the route was popped, it had to be pop-able (`canPop = true`), + // such that there is a callback to update the state to reflect its absence + onPop!(); + } + }, + ) + : FDRCupertinoPage( + child: this, + canPop: onPop != null, + onPopInvoked: (didPop, _) { + if (didPop) { + // When the route was popped, it had to be pop-able (`canPop = true`), + // such that there is a callback to update the state to reflect its absence + onPop!(); + } + }, + ), + onPop: onPop, + ); } - DeclarativeNavigatablePage get popup { - return _CupertinoModalPopupPage(child: this).navigatable; + DeclarativeNavigatablePage popup({ + /// Provide `null` if the model should not be poppable + required VoidCallback? onPop, + }) { + return DeclarativeNavigatablePage( + builder: (onPop) => _CupertinoModalPopupPage(child: this), + onPop: onPop, + ); } } @@ -208,6 +233,7 @@ class _CupertinoModalPopupPage extends Page { super.restorationId, required this.child, }); + final Widget child; @override diff --git a/lib/src/pages/cupertino_page.dart b/lib/src/pages/cupertino_page.dart new file mode 100644 index 0000000..0e50923 --- /dev/null +++ b/lib/src/pages/cupertino_page.dart @@ -0,0 +1,51 @@ +import 'package:fdr/src/pages/fdr_page.dart'; +import 'package:flutter/cupertino.dart'; + +/// Fork of Flutter's default `CupertinoPage` with a fix applied to the `PageRoute` it creates, +/// which now forwards the `Page`'s `canPop` property, also as `impliesAppBarDismissal` (such that the back button can be dynamically updated). +class FDRCupertinoPage extends CupertinoPage implements FDRPage { + const FDRCupertinoPage({ + required super.child, + super.canPop, + super.onPopInvoked, + }); + + @override + Route createRoute(BuildContext context) { + return _PageBasedCupertinoPageRoute( + page: this, allowSnapshotting: allowSnapshotting); + } +} + +class _PageBasedCupertinoPageRoute extends PageRoute + with CupertinoRouteTransitionMixin { + _PageBasedCupertinoPageRoute({ + required CupertinoPage page, + super.allowSnapshotting = true, + }) : super(settings: page) { + assert(opaque); + } + + CupertinoPage get _page => settings as CupertinoPage; + + @override + Widget buildContent(BuildContext context) => _page.child; + + @override + String? get title => _page.title; + + @override + bool get maintainState => _page.maintainState; + + @override + bool get fullscreenDialog => _page.fullscreenDialog; + + @override + String get debugLabel => '${super.debugLabel}(${_page.name})'; + + @override + bool get canPop => _page.canPop; + + @override + bool get impliesAppBarDismissal => _page.canPop; +} diff --git a/lib/src/pages/fdr_page.dart b/lib/src/pages/fdr_page.dart new file mode 100644 index 0000000..99ed1ec --- /dev/null +++ b/lib/src/pages/fdr_page.dart @@ -0,0 +1,3 @@ +abstract class FDRPage { + bool get canPop; +} diff --git a/lib/src/pages/material_page.dart b/lib/src/pages/material_page.dart new file mode 100644 index 0000000..8b560c5 --- /dev/null +++ b/lib/src/pages/material_page.dart @@ -0,0 +1,50 @@ +import 'package:fdr/src/pages/fdr_page.dart'; +import 'package:flutter/material.dart'; + +/// Fork of Flutter's default `MaterialPage` with a fix applied to the `PageRoute` it creates, +/// which now forwards the `Page`'s `canPop` property, also as `impliesAppBarDismissal` (such that the back button can be dynamically updated). +class FDRMaterialPage extends MaterialPage implements FDRPage { + const FDRMaterialPage({ + required super.child, + super.canPop, + super.onPopInvoked, + }); + + @override + Route createRoute(BuildContext context) { + return _PageBasedMaterialPageRoute( + page: this, allowSnapshotting: allowSnapshotting); + } +} + +class _PageBasedMaterialPageRoute extends PageRoute + with MaterialRouteTransitionMixin { + _PageBasedMaterialPageRoute({ + required MaterialPage page, + super.allowSnapshotting, + }) : super(settings: page) { + assert(opaque); + } + + MaterialPage get _page => settings as MaterialPage; + + @override + Widget buildContent(BuildContext context) { + return _page.child; + } + + @override + bool get maintainState => _page.maintainState; + + @override + bool get fullscreenDialog => _page.fullscreenDialog; + + @override + String get debugLabel => '${super.debugLabel}(${_page.name})'; + + @override + bool get canPop => _page.canPop; + + @override + bool get impliesAppBarDismissal => _page.canPop; +} diff --git a/pubspec.yaml b/pubspec.yaml index 63015fc..c2288d4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,12 +1,12 @@ name: fdr description: "Fully declarative routing for Flutter applications." -version: 0.0.1 +version: 0.0.2 homepage: "https://github.com/relentless-works/fdr" repository: "https://github.com/relentless-works/fdr" environment: sdk: ">=3.4.4 <4.0.0" - flutter: ">=1.22.0" + flutter: ">=3.24.0" dependencies: flutter: @@ -18,4 +18,6 @@ dev_dependencies: flutter_lints: ^3.0.0 + golden_toolkit: ^0.15.0 + flutter: diff --git a/test/dynamic_back_button_test.dart b/test/dynamic_back_button_test.dart new file mode 100644 index 0000000..c3ebbee --- /dev/null +++ b/test/dynamic_back_button_test.dart @@ -0,0 +1,182 @@ +// ignore_for_file: unused_element + +import 'package:fdr/fdr.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:golden_toolkit/golden_toolkit.dart'; + +void main() { + const size = Size(375, 600); + + setUpAll(() { + loadAppFonts(); + + WidgetController.hitTestWarningShouldBeFatal = true; + EditableText.debugDeterministicCursor = true; + }); + + group('Dynamic back button', () { + testGoldens('Android', (tester) async { + // TODO: Would be better to use `TestVariant` once https://github.com/eBay/flutter_glove_box/issues/179 is fixed + debugDefaultTargetPlatformOverride = TargetPlatform.android; + + await tester.pumpWidgetBuilder( + DeclarativeNavigator.managing( + navigatorFactory: () => _Navigator(), + ), + surfaceSize: size, + ); + + final backButtonFinder = find.byType(BackButtonIcon); + + await screenMatchesGolden(tester, 'dynamic_back_button/android/01_off'); + + expect(backButtonFinder, findsNothing); + + await tester.tap(find.byType(Switch)); + + await screenMatchesGolden(tester, 'dynamic_back_button/android/02_on'); + + expect(backButtonFinder, findsOneWidget); + + await tester.tap(backButtonFinder); + + await screenMatchesGolden( + tester, 'dynamic_back_button/android/03_transitioning_out', + customPump: (tester) async { + await tester.pump(); + await tester.pump(const Duration(milliseconds: 50)); + }); + + await tester.pumpAndSettle(); + + debugDefaultTargetPlatformOverride = null; + }); + + testGoldens('iOS', (tester) async { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + await tester.pumpWidgetBuilder( + DeclarativeNavigator.managing( + navigatorFactory: () => _Navigator(), + ), + surfaceSize: size, + wrapper: (child) => CupertinoApp( + home: child, + debugShowCheckedModeBanner: false, + ), + ); + + final backButtonFinder = + find.text(String.fromCharCode(CupertinoIcons.back.codePoint)); + + await screenMatchesGolden(tester, 'dynamic_back_button/ios/01_off'); + + expect(backButtonFinder, findsNothing); + + await tester.tap(find.byType(CupertinoSwitch)); + + await screenMatchesGolden(tester, 'dynamic_back_button/ios/02_on'); + + expect(backButtonFinder, findsOneWidget); + + await tester.tap(backButtonFinder); + + await screenMatchesGolden( + tester, 'dynamic_back_button/ios/03_transitioning_out', + customPump: (tester) async { + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + }); + + await tester.pumpAndSettle(); + + debugDefaultTargetPlatformOverride = null; + }); + }); +} + +class _PlatformScaffold extends StatelessWidget { + const _PlatformScaffold({ + super.key, + required this.title, + required this.child, + }); + + final Widget title; + + final Widget child; + + @override + Widget build(BuildContext context) { + if (defaultTargetPlatform == TargetPlatform.android) { + return Scaffold( + appBar: AppBar( + title: title, + ), + body: child, + ); + } + + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: title, + ), + child: SafeArea(child: child), + ); + } +} + +class _Navigator extends MappedNavigatableSource< + ( + bool /* show child */, + bool + /* can pop */ + )> { + _Navigator() : super(initialState: (true, false)); + + @override + List build((bool, bool) state) { + return [ + const _PlatformScaffold( + title: Text('Home'), + child: ColoredBox(color: Colors.blue), + ).page(onPop: null), + if (state.$1) + _SwitchPage( + canPop: state.$2, + onCanPopChanged: (v) => this.state = (true, v), + ).page(onPop: state.$2 ? () => this.state = (false, false) : null), + ]; + } +} + +class _SwitchPage extends StatelessWidget { + const _SwitchPage({ + super.key, + required this.canPop, + required this.onCanPopChanged, + }); + + final bool canPop; + final ValueSetter onCanPopChanged; + + @override + Widget build(BuildContext context) { + return _PlatformScaffold( + title: const Text('Can pop test'), + child: defaultTargetPlatform == TargetPlatform.android + ? Switch.adaptive( + // as this crashed in pure Cupertino setup + value: canPop, + onChanged: onCanPopChanged, + ) + : CupertinoSwitch( + value: canPop, + onChanged: onCanPopChanged, + ), + ); + } +} diff --git a/test/goldens/dynamic_back_button/android/01_off.png b/test/goldens/dynamic_back_button/android/01_off.png new file mode 100644 index 0000000000000000000000000000000000000000..2322f7fb1127218dfac1cdd2eef400f8a44dd62d GIT binary patch literal 7143 zcmeHM`CF3d{?>8QGd1O@PSdzIljby~j+&Vp)-+`tw;CHwQOQ)CTvJq31ZgebqbaXzs^f!Ir4t}^Q2l|LFC-PVIoH5NLb~{~mnt zZ@`uPx2RurbW9$Ae>in9gCL&Dd_0y$Q%J{jws+***Zbwp@6&Ip4i$$i_@@6D>RJ<` zpum}~3DqdlNV;D&d;qoP_f&WocZ@t@+k7!AwD;ntr#_zy2>6bj_T@$W0gKP+C-tL@ zba^22vCgzj*qo}A6?0pZHGlx|qKk{jq)WSX{wb_$wAMMP*o(-y@z(5L2a*lGIX9`h zeb0Z>%xeG09U6L|-0?+4c{%?V<4#3HC7AXJ8Y-!(ldZwM0vNwrWN#!l)sLol5NT^1*_cyWqX>j(q3Dp=4rgT;IOJ zLvz3#dA}noeyHv`bIc~0=$sS|TK`~Z1qQ7>D42s2HSy`$>^`g|Eo-gc?VjgXMAb@6 zp=AtO@S=fCMkq6j?4V`UWm&Q1UE!lc51OpJkE`Ow$CEh|6XDDD$47@Nz3b!kSjk64 z^WzmH-Hhb&qy%bN7UP{0=(w~O>>_@XY#T) z_2X0P+2w;>0y3G4^~Y%HjS$P%jpWZz4w7j-Ii7_{EZtq^5*OH}QSiEVd$obe6(%J^ zs(dDrr^QheZOGPyyq|2Fmv5M&->!EJcjvXjX10a{KBT%ZFE1}!%Hl9#jXP4W7{6dt zr!dbP;cyAiFr+wy2=BbRPpN!+BxS{zVFc3r z%|oV$)LouevS9-RC6x!c%o((alki;S9Az~|DeyTF`~3ij{~|5XS@`UTynn<$M!TtC z)pn%&VzW0*FgSRd0-B!?2(9K_?YTz0 zty#>`Gau5>*({H>eim9?xRM~@Wbif>JfbItQ)1%P+L3P5)Fn8f%By$y@cU;aPF)Kc z(d5!xUj0BQpKvBFE)E;jYlCqOrfn!ljg3y;INOANX&h7&>*t|-*wv(!5_zGK!7nN+ zE9(+~-e`N_g1frA!(|h-6vme~G&EHB6G2K1MG(_JZ!c08=OlQMO)35*$8Er``UXyq zwNBM}Srn3~N}K$bTFsMOY0qOdbaL22s+xF@iQ6kb0vMf(CNCqu(BA7(G16mwW_?J`7KNSJ zN6P3tD<)rVR};_^t@0p?_49si%6BW0DK`(~TK`uD7Q-jDm(@*RS>M{&*eJO!yuQan zk;CO)1$o9!-!7fdn1QiK2`%O(Lt8@1w%L2H_`bM_V@u0g?d`EHBOuC_2P}Ko+O+h@ zR#8$js9w+y_V)H3Sf3*s)4^L|#M8`?hXH(QYHA`N+La~lT~+G?E=LqwdlF0sSv<59 zvg>0&EA58In~iliX-{OfKa z?atGuUL$7{=-TINEF68kGJB(t*b5*89Eil(IUbi_lGtKzg9@?@a(~|mrjMFItKNHa|I7c=x9M&JYo5$^FBv3OV_9H4*I^EH-V2Qu*4d%F5_`v1&zFSjbEJ z5gWBpI^vv}m*05p!<(*GtPi1WF6jiNrV;saCOVxjC9Bt~J=rf@Ruaib9A$T6(6c#Oxcxh@7fWy32Nh}pKyHe|a#k!{h zlVK@z|F@^0e`$thkpZpZ+B+?|&c3zmF$vekWXempgz|#oX^q}D6_AghY{itkqy$sw zWr0eiahNSCNowlRzQNL5*y|kK%r$n590!a68z_EMdi(R9<q}Vd>_@=uK736@k+z zBqU&jh*r&CYLYqUSa}C()&`%CZP3G5pw@`7n+T__t`POi$1YKap<8?@N8BhV%OA^a zTN_lf>}r{;LmKy>@U))}@jxzg>ROO;wvpa@-N5v5eOuaDeM569SgzY-Gqp!Cl^&Kk zqkJx^Wde2_srDx}HU+=Dc4`aiurrhJwvsUm=|=Vv748TheX&SklqrP;=;2Fu!B_)~ zb~^BmieI3ulV(*9R1!TDlSd5Q*Du3Z_^DaY^CGWs)b#T$e>%nE!i-5Q7y07Owwo)n zc9={qd_tJH6`DQP#C|8b`V2@a^oPmBh449c%xoW2J$#61Fy55fSFB2AnBr1v5!z*j z=R!)!a}uRWx-oromtUY;aEl@wS+jf1QrTgl88i!kkZMO;t;3uS3#rGh{q!9p&$!f* zvMB45E!MMY>o#BAyBC_SoCOP9Zuy$=m4GWCeDPHJ=E$C*is!YVajMxqoIvUc;%2A} zaG`{9UWN}JR&Ocf(J@@jDA~ytH#v-G|HO^}Wc?ASvNwAgwd9Y@{@@MmFTiXR9)-41 zrP8f*Aa1@9U@fgRbcB=NqSzsIc1Qrw_VL8~S0S$~|DcO@t0Q)~1m~nggioC}RI8%L zg$fHTowaLo1ZC-?F(F;`&a)DWgELI|Z{7qCl<@dw3Xo z;h@*-keBv88y?8rJP|w7tzMT|INb|eqAhW$ALh%AiF}HoF=SSu0Nt;us_OFf0qCj7 zgw%@8Pk>E29c0ilXPMp#9txUOBM=s{Xzf4KQ3_wIPf43B7AN4kKeEJWm+A7SEOd)K ztFD|8)wLvv?N|I;Zr2}?Yu*wZnHnt|u4E$@fA3PH7VJKlqU^*~B9^$p4L@Jtt`|8w zbs4$A;y2b35^tf$Z97u7Q!8eTKwtwS}LdV{-#R7LeSxUAT6F1y)J33uBovpOd=8hLaPAS z6uQ1BkQZt-QschABPDe)wwvu%M%>KCo@eLV#5PGxjJl5~sUWx^W`~dk=0~bI?;qJe zCss_Jw@`I^Z6PddVnvugZ;l?1P_hfqErF^287isrLnST-kfL}%($&(XxBE++?Y6i; zPDe_NBY5xZTZVxQ&0gRWeOz(0DqkYE*&GF62_^R9BbU{gfMLK>MhIqi4SA{vxTw-` zzKq|8dHm#b5cksP^B5KZKFU^(dx46#0Ee0>H;%9XXt)i--4=Jt1I(366vx8-X_2vI z+1H8rda1-j)XdUCS+=al9iR)NV?UoyACJftOra%{luX&vYKK%oY5i0>hxYe?*h^^_ zQC%3c8^a79U({gN16ax-s`@KYw(0AI2@c0mAZ66|4L%W3Z@}P;jM_85eGv_evNa~o z^B;jPE)A{k6jBsAsnH+a{DSwyXyh6#AXj777TH1|wEuuC&mXSakT}{)jBf$&_-i$r z&KasxH}%spJ6J`70E^u=v}kMRI6FJXDAGdZ+%P~7+A}e_B-Zh-M0F9o0eckB9 zwP|_c*yJQNSgzl+3v+tkA7#b-Hb@b>2&FE!=mWHXV~c!ab-(CK9g*xt>G^G!MRiB@ z&fjm=`-i1OXWJdOlczrJ`kypcCkUn0zH~=N*yJPYxdC4!5($`UWN2szRqs9gJcxA6 z*}0lR@z*yrepnaDh~lBvgroL4&#s1W+pctWEe?)0S<=^MNhyqmt6O{mZN}FyfTDyL zBql@exyp84O_bFCG^g{@hBMtvx>Wb|81GtSVzac$eRE|5jD2!C7d95r@oL1+tul!> zw;~#|1Jugm=x6DvsWVTj%X+a^V!Vf{P7SC#*?f6{6?g-nYAIjqzsqx_@oH@B!f3K? zc>nQlB!IYNDtYwp3_aE7vR39F*q3_=rf(Yl6AqUb;b-9ujW#_sWNtB!Nx%ar8$+H- z?*lJw{I(+Ge`9{YWRJoVWbZ5>VorTw8}e{;)C|68&xit?1xdUussqG;Pm8P<$OTV5 zgI)bks|kemn<3=22A!ss)h=310=O-$3m;6)NIskQWm)+>cL!Ql!l=u5A~~M@DipQq zlsVmQ;CMOLO!mYOeZa;B^~a;n(!#g1fw~aRq96Eezuf>JDvBZtTwox zi8O+Z8V|FanrD5drPjpkZ|NE)6R=co9ivwJD{6qiaD>nG_d{PEkPBG;*u`f4J$5n! z7yYjLf|0|_KD)`qpb^0DrXCUB+itXXDp!*d2jX_QoB&iKBq}~9!50@N!D$tSa7KfW z9Yd_koLZNLNydSV@VF^)Req(#`$U(Tl7I=Df!gD%TvLL%M^iuZP*+CH@E0^n$^Lk( zw_U3{TreQ!;z=FAf`H<3wYNbyLZ0vq)PZn9lC@JWf`jX>oH?5}evpP#NK&ei5{mPV zFOE)5pPCyeEdg2xIjrS+aY@M*^roh!M$Jtjk{;OWHaW$0Zc2g`mfRr&O+52Rm} zPif1Lo-f`t?Kd+TsaW-lv~9W)R@2PNR7)%56+Z{Traa&C9V#NxZN5y76(NcU8}-U28?QtOTj;dx7lMx#`Npnr2egFdv&{ zy1L8w*w2Lq90JO@OKxMMdyyPC88r2t-F9zvp}l1HJd+K~)YxeC9QFUp={^!(W|^rj zz$~WZ4wZW)3|q{M12N|S>!0_<^Up$@6gI2#dN~QqkYpa5fl+!)b6hgVgRf!abV=HofJ%ZNoa~Mj0`VB>H~q_jX}l5#j^3p0w?oN*FyPSVj`e_ zla>$MG}zgTR!Lej9j8tL&ghP;kExDfm9bmp2K!!n1CuRHwIaIinHBCk5;#)?NfM`2 z1N2R}rslw3Hz$i|4wX-z9_DxD_kCtN;d|XSO>h*O!_-kE$3MZiu&UB)8OBr-jsaN~y+te#1Cz2_D0AV*4B7f<X4`()(7(@IO-NwYF-%LZ>lq|cjA|Kge%axeJp2eCFSPzH)B6Gb zpsVGzA9a6U$mEw(;(F9=C;Ko;b)0^{`LKs- zu|D`!6u9WI3~>AHL%+&kM)F<=}Ruyv3#ZJLbAUgGL_V_QS4KK z76htHDmh7XRCpt=4S((31K~(DD-f^ux+%V#-_{~T40Ho2?wG_?158{^qoGO*wsS86 znGfJPytqVFk;WKZY+VAe5yZMFSN)StCHoLMO}--PKlbG8xBD}Pb^q1L-d~;n^r7fq yru)luf9^Q_ugLi;a{h{(Kk**wf0#HM&=B!*=`->?8hBZ!13u&TL+$sMZ~Orw%0NVie(%kEi+>Y3YZzt z0cj!B00}6PL@7bUkWizE)IcD#03pc>-+SNR@4fZ@^{#c-U1#63&OK|N{VRLjhnL-5 z)PBSsRjbvPhgHrBw36=p+Ge=#XN2m73qzZB?fM^w z&#n5>OG-*YzZL(HRaIf1$lL$iGP>`G@Q0OCQJ;K?;|}Px#g+34v_8~JJX9H8=t?ll zsxrF++20hVvTK*5_G(a&;cVA4EjF7?T)e6h6nPfG%7IBUC+8v2)5p)AX zzu{WZw3OvcPxmtiv+dV%jvPI@3LaP8SvRCVHWmPX7-w!n+PSBYLvZQp!C@F8+oSdMe1fo97pk%wuu7^eJ!md@-sb>o(u ztkXi6F!VrM8yNBai!xFPdjIl(Xz4`i^0bYV8fR?G9~=+aNeG^&txShIXlZJ?xnAOP z3-J$K%~ylL;34FKTj}twz-CnP@jy!DIjDJQMRvMVM&Ih`Q}ILT>FEXdp36_3JRz68 zVA?$O@*%O~?i_EU83peJRm?4LAg9Y&VcF|bKHEWfOmch{PYn})ArCoqu_wGfzfe+P zOV4gve7N0Q?oM4`*;+nLodQ6dS~kf9R5%f!b?6tI7R3H&<7{QnG$n49izYQA#)p&W z=_z>)Dg4)m|21<}Hw5QuCJml$_Tf#?gzjW%Fj~+=2x=VI2FbAjVs6h^0>3C`0Jfq} zhZBchK~RDKUgkv-B&r5DlElnU0&je-1Q24VsVQLKGccRiT$GXM<37scmyB4SaF;SB z$&SR}9(39ae{O%vE-ZHHwZAZs9O`6u5_#DG#r@eNq~5FhYZU+mYUi2aakw66aJF(C zrU%n72sv~v&B#EubrJug)*;k%^mhG>0>jH)H-<_mfcP)xdg@{Z&k*`i)m;LHFc$a9 z_33Au-`5{SWAy4rmeX2ilx4%5?ynZWH5_)nBLa-@YDWIP;q(G0;L^GqafyX4yj z2}3;(%7;yxV)lCSUg+ULt6)3%H!Vbmnl=5Ls!@$Q6vh2iQc~=D)dMPQ4E2~Xor%WE zt6PPfX0)`ptSl1Qhoaq2Wy1+AByvERSE;v$hlgeoFBM|9+$IpW z{mwM@F2kieX?ytj`T54m?``QRaLVv)4qep?@!{rhZj5o)cGppa!Vv$$ue71t_Gcc4kz zHqckB>tM5elGQYOjEa#ATbyvFoiGmGYk7!eA3bVT1&~jaAmOY$94-fh#r!-So|l;N z3o;CqX=r&!w6>71St_3xN|X{q>w}UqJj^q3Z#C48Wm>FHVmF`-o;6Iho~V7G_Kd=L z_t=`3iNkeS^Qh228`a zKqczu(*l0#P)kkm?L1K}KO!)yZJudddzg25Bdow&*DtZ6DU& zF0`58$g5|d8La&1p?Ya-lS|o|kAA%S>4s9?=Q^ij%XmHjYly^t{mUAq#~lyWFtknFV3hR!>Tv57@&=e% zRuwH-szxcd^2a{bL<#~8mc9x$jm|!0dI!X6?{~>c_tk#i2&?Y0($o|Wyc|&V7TP*H zX6b{P=N66@anL($jYFqu>i2uc^zc#yUE-{2R%VYmK3mKEG|<>sovUc!o1|e6syod6 z#f|TE)3?*$`aVpxhc$IsiHGLKL)R83qXT34r6lKsrK*X6SJ5>wgH}o{d9QIpaq4xm)DXlfLa06 zi;~}4eL$4XG_92q>Sucj=N247xQFGdZJy$}FE0F^za61%?={8WJhAx0#I@AL;X?RO z{IFUN8Op4Et^tFk>qrRB)ZK8>K*^fT=;$c@xpN^mrSwU_3#@|asU(KI%Ht`n?aXV4 z>D{^a0Z+Hqt;<({7g!lBQ~|N(&UhFXnU@w!yl9usR;WMC@HvB zSmc7A5o8_p40T@3JOfyei~i_dk`4JQ^(i1x=)JPqrw@Pl>C>ka`&eb(Gc4R~?Kkl3 zTY2r=2JMOSUCq)p_q()Z)er6V3C7UzbYJ>`%dER<;bt&wB;cazN4yZ5*H)YyN<`@SjG7Fj0bV#qMsepz-4F3=VaHG zMYT}7P3Q=4-+ln0mxH(S_iVlh>}tQn?hUi22304om}j(nJRVZ zTanF=c0k9Qy;+ z@%GtTqdIPA#)ex^lBk44u`@zWy!CBfzaLjX#4JBs8IhQ0;c!}b(BHSNkpehR0*Lp2 z+hG>C&yyVC5tt-cLP%&PkhDDeRPUZK*`sKzzfJF9grX}Nc zN*=DDuBDM-{p}yklm;OhljwH61N{cGxi2M?J^A^yXIshkVh2eY`RR!G0p< zyP9ib>XinEGSQ^Vd=avOQbN4_8k~b}$~l1u-Jx^}c8l-dJ-`xQxe|2L_h46i&oVU( zw^huN|AU)zYJiYHwSYQL56^DTBA4uucgEGSbXH%Q`d02^DOmM_*Vr=Z$ttB1^^}H_ zavae9oxZgJuYI~-h~LPtCa`k%6!aB?TXT0GBv7%NdR=IzC$l0xy}Y=g>3OMdlgVNA zrh;eK9i`+>idew^NGhWuBj-y=F1?nhwHr!@u>D*%jvF?fzu!2tllvsRDPHvXHG}S{ zsVPw`VFmozpes4yM8i}j1&cTDlvIFtd!?f4?lV~)g!cA!?%xnnYjk6fM1)Y-OPm(Q zx}YP}WCT|;O_wp*+XShB`T1{?JsqygOqJjEiJuv;RC+6>b@NhJYYRlWJX0vG8|+&t zocj!gI7>vs+v_2AOD~aQ{o(t)X(p>*H7bf1hJS=*>c!M|>g3xp14xj_GIq=@<(K(* zYo1>H`q#caNW~i@wEeLjQ9XHVFK@%XHZbHXMuSVZyGcBKLJ-D}3JkmS^J5nyF?)hH zoADsXr}}1BdbXi0<4|rHYQGof84%!b!7qg8pty%z4pEed#ci_XVQb7nMhGWe1avVp z9E8yl*1x3rY#3iC*8<{F)_6>^B53yBKOJrB9OPdUp{W~!^jM#&Z^>3_@E(gq*jLn| z(i1U0zQr@lF-sW~1xXRW?P4p{Y|lq*3uUdn^rxawE7PM71K^DZF<99w^b2}3)Hyl% z>yk+60#J%ivz%?I;I_RXpu(M!4c6S^B!?OJ67^y_WW6`v-rE9<8|$3-__t$CWbYW1 zK(vhZKJH67(U>@SWpOqQjV)dKjTZt)Gn#+pTZ8m?+GGB7Roce2r*{LyBzWyvtGQyNgj|Wq2Zg%Eq zmIu^lE3;R@26%9Hp4Pr?s`SlHNRp#w=m)h zMO_`Ni{VgS@m^khv~{K=no|)}6U`Ap2_~kdK}E^$erw<4^8Af>*YtmDu_`+5Gqdw__7jPE7XNf*J=p%Vt#ylQK~!Xdpv%nI zDrV%AKfw~;7uHqiNvI<;M*`@qrliQvK$WVK!#dnxGlhKff{#B{2#$GBcdCT{A${DG~iqy(^#o zuys;wuE+o5Hgo+4WC)EP+(0=NySCITo?4k-vMAj;@CwecS(-KnXMDQ3(ac$G1L#YV zc+UXV@)TdHl(k*}`KKnVnj1!nB@d1_ITe}36wHLXDzPm}%_u|-Q1{5z2Z77|*1G0c&!CtGRvy44k3z-7`7KWQ;M_!0{+B{2xMg#F#?7uv zeH56NSAyLF_vM~7ju?oI4a?%ij5N3fqS$wP8r>EOSh~tp1}Hv0=6GJ93`_+^dV3tM z^yHZ#)*eW~8Bf&)UtMV^J|16KOC-^^xciO$YhXIO#u_YYGRL9k1e0*n?sUX?fxFJN{hr}Nx~!00 zjsU%o&vzy3d@BSOVawhY=V%$Z3$Y?@Y8#Ozkr0ZHX$P`6D;_hbz=_4FmR`lULPg3jp1`j}OuyNL7$BZR74?&owV-^mMz;(jYoZ&X~j zZ98;OSouM&dGKIv8zy9+nSp*sd=WVAXQ@KmTFaLYv~)oGt45vlE8UuO$P43*R%k4<*=P#sDj#R_YP=!EB|9&*FFpJPv@%K}xb zuGaD$X39@H8CJqu$~l9L8UGH#Mf?JFZcJ`>S$%@8v_S<{97RXVsr;>CLB-ja&i{^G z31awpu_?rZ8kOV{ZHZNkt1!6gk4|%b#h&T0l(#9zd6zx%x_G>h03iaB zc>SDabO01tUU0l zHb=2Y1TMlYTdv;7H8SK^7Q$*b6Jw*-?@gbcHBAo*7W;N4!+WN5B6kxfKc376#dULE z!wD&-igw&2Legc<$n&!tXOlhsP!bnaC*jGE-_=l!Cy6R5^bLX z*Rfsfx2qdzc8njQCe%dpP8u7JHP;`x`P4kq`fyjaPsmaHMS;RoNG|Vq&P>9_Uyh%? z>b;o!18l;D*+x3=-}f@8qqQH-Q`L+6+4$3 zlB{x|F}eR4zKXgDcKoyZ4_|g+g=y literal 0 HcmV?d00001 diff --git a/test/goldens/dynamic_back_button/android/03_transitioning_out.png b/test/goldens/dynamic_back_button/android/03_transitioning_out.png new file mode 100644 index 0000000000000000000000000000000000000000..a4858b7a9a3ad3be2607728f1eca6ade4cd78b7b GIT binary patch literal 9626 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 VbN!w#_+h6GqgiCHA9C;<#4uB;-DO$e{>$V1n$ zND;Fmi&QYsQEM;(0!R`tKBXJdI@6hL|1bBSJKs5X z&iTII`JLbSZrPV;q{sVS?*jnffjV^}4gg%p0N|Rqbqi>@_v-Og@a2*lhdd564|~so z3%A_ksB>GvhqCqSn*aboqfUH&uJHc6CYs=*U=A!go9yGdnnzjvr+%S2;z@Sv+Z9l^ zcVRbt;ctVBUR{vDUhJulJvb9Ck``Hd@9wfZ{}Xqg&rq$s-+$QdAN8y9M=O$<;E~9N zv!&I~t8et3jc1NrY5xuI09uW30h9qo;M#Ma{oi%G=jKVi8aWC1;FQ|MwJa=hKgoTo z|G%C3lx4R&F$XGZ(5y5s-J3TJcdM(#a0X38e|!Xk!3^iECGi%MS{NOP7}tulIp3;qnBbozI*ATpV{6!doSYem?02}T)sdchV~B+qYviV zmQ_UYQZHv{Xs8%WiM_Y|egJ7k{SA?*DD|Tij!GKXY(~WT%ISfU*S}1-@lK|s+z8}R zg1VvhD6wvJWi$(er#tegOCv~`foa5A7AE?X8cmZxOS4PO4Y~;bx~bPsDUkdwf~cnl zXD}F8u$~HW+iejmZE==WFAu1vzC z&>-;hh1fW0w@N_7FM9)5lG0ie3;8=D_YQvcQyF2F%Hgy<}OT9D)EFBk^{sp4ccP zZ@W$G)XwIeXD(g%v_U4T(p&9Kx3_;c*{KWnWf;cCTl#gRz};pq2%b|_H3h~N!!L){ z$|fex%$rOJEJ-@zpAMP`laP?{>E3qO;of*_GRd!^pC=Sz&9<)J_kHD2$_b)8a5x-F zm5NA3{aG?ARVvR125y7*raC_}y1Tpc1Xf9vb$WVV+8@l6aIW1-Z?sXyR^!*Qk2Qij z!VI`|u+g{dSD?-AJAI|EUd5#d`6EgceQj~TixJR-e?iP@#6EC2mVWRbd9WspMx)Pz z@mgEQ^eAqM5ECOYR+Yrd9wLPyOf=SS&p5m2vYJe`W`x%(f@rOytDZl^M#F1E>IK65 zoYq9C(TL-ux+b5DtpGE!Uw$38Z=Yd?&+1CAQNB*(^8LOEW}-nL1+W9X@n&g`$>euV z0_C;0=QRi5ak%hm(oP6u_)_v!&SY6f?Aw0gby0f0qG#pjPbD{x1yrIo!v8w5Czh{a(^XueRRx1b$2Ytz~?w-i<(KD62> zI0wz%ia>D{>p^$;Zi+khWuRCK%EuIcr_@pF5J)kS^-u4Dwz`yz9+G@21<#{5J$R@^bjlGgLTj=XdKI44);^G;VO2vD&I!P>mp+s6yOKl4PewgydF%V&}2gyeD?-Z5V z&Ro{+@{@)HBdf?`f8-rjf4A18uoy(mrXC@ckZCF{EwyOKf=p_+X3w=9Tdu#Q?zU_h zJU9BKiDp%SWX(6o>HHpeaP5#@-h_?kHGm2s@1M_uS(KT5^8WWa*mWOSaohy@n$Ax@ zKK>^@pMM@PE0<$@yuIbq)0a8oJUkx%R1V3GQyW%NSQ}Cya?H~WV){BGSgVGDJrYY_ zwXWK=COHUzK|!ZJUR)o|KDLyTbv2dwBDb+q-Ce>3>?iGdN5=ni1Z_J1+mPNwXcM9T;V!pHlTDgz(&YbR=q%BhKxh{<`q)A6PZL0$ MM4xCre&O4H0sTHNvj6}9 literal 0 HcmV?d00001 diff --git a/test/goldens/dynamic_back_button/ios/02_on.png b/test/goldens/dynamic_back_button/ios/02_on.png new file mode 100644 index 0000000000000000000000000000000000000000..ba5ce6d7ec7c56e7c2bfe60fc1f4e0767d01f963 GIT binary patch literal 4352 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 literal 0 HcmV?d00001