Skip to content

Commit

Permalink
Switch to new Flutter 3.24.0 Navigator API: onDidRemovePage
Browse files Browse the repository at this point in the history
  • Loading branch information
tp committed Aug 19, 2024
1 parent 7974dc8 commit d6e2a83
Show file tree
Hide file tree
Showing 18 changed files with 455 additions and 63 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion example/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Flutter
import UIKit

@UIApplicationMain
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
Expand Down
92 changes: 88 additions & 4 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class ExampleSelectionNavigator
ExampleSelectionPage(
examples: {
'List Detail': () => ListDetailNavigator(),
'Dynamic back behavior': () => DynamicPopNavigator(),
},
// TODO(tp): Dispose previous if needed
onExampleSelect: (exampleFactory) {
Expand All @@ -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)
Expand Down Expand Up @@ -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<DeclarativeNavigatable> 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<bool> 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<int?> {
ListDetailNavigator() : super(initialState: null);

Expand All @@ -135,14 +219,14 @@ class ListDetailNavigator extends MappedNavigatableSource<int?> {
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),
];
}
}
Expand Down
28 changes: 14 additions & 14 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.0.1"
version: "0.0.2"
flutter:
dependency: "direct main"
description: flutter
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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"
15 changes: 1 addition & 14 deletions example/test/widget_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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?
});
}
1 change: 1 addition & 0 deletions lib/fdr.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
library fdr;

export './src/declarative_navigator.dart';
export './src/pages/fdr_page.dart';
80 changes: 53 additions & 27 deletions lib/src/declarative_navigator.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -69,17 +71,8 @@ class _DeclarativeNavigatorState extends State<DeclarativeNavigator> {

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');
},
);
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
),
Expand All @@ -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,
);
}
}

Expand All @@ -208,6 +233,7 @@ class _CupertinoModalPopupPage extends Page {
super.restorationId,
required this.child,
});

final Widget child;

@override
Expand Down
Loading

0 comments on commit d6e2a83

Please sign in to comment.