From 2aac494f1f0228ec30bc868ac843aa224ddd94ce Mon Sep 17 00:00:00 2001 From: Plague Fox Date: Thu, 28 Dec 2023 15:55:12 +0400 Subject: [PATCH 1/4] Update dependencies and refactor code --- example/lib/src/common/router/routes.dart | 6 - .../src/feature/home/widget/home_screen.dart | 5 + .../feature/shop/widget/basket_screen.dart | 2 +- .../feature/shop/widget/catalog_screen.dart | 2 +- .../shop/widget/product_image_screen.dart | 35 ++--- example/pubspec.lock | 2 +- lib/src/widget/bucket_navigator.dart | 121 +++++++++++------- 7 files changed, 103 insertions(+), 70 deletions(-) diff --git a/example/lib/src/common/router/routes.dart b/example/lib/src/common/router/routes.dart index 215aa61..136074a 100644 --- a/example/lib/src/common/router/routes.dart +++ b/example/lib/src/common/router/routes.dart @@ -11,7 +11,6 @@ import 'package:example/src/feature/shop/widget/catalog_screen.dart'; import 'package:example/src/feature/shop/widget/category_screen.dart'; import 'package:example/src/feature/shop/widget/checkout_screen.dart'; import 'package:example/src/feature/shop/widget/favorites_screen.dart'; -import 'package:example/src/feature/shop/widget/product_image_screen.dart'; import 'package:example/src/feature/shop/widget/product_screen.dart'; import 'package:example/src/feature/shop/widget/shop_screen.dart'; import 'package:flutter/material.dart'; @@ -25,7 +24,6 @@ enum Routes with OctopusRoute { catalog('catalog', title: 'Catalog'), category('category', title: 'Category'), product('product', title: 'Product'), - productImage('product-img-dialog', title: 'Product Image'), basket('basket', title: 'Basket'), checkout('checkout', title: 'Checkout'), favorites('favorites', title: 'Favorites'), @@ -52,10 +50,6 @@ enum Routes with OctopusRoute { Routes.catalog => const CatalogScreen(), Routes.category => CategoryScreen(id: node.arguments['id']), Routes.product => ProductScreen(id: node.arguments['id']), - Routes.productImage => ProductImageScreen( - id: node.arguments['id'], - idx: node.arguments['idx'], - ), Routes.basket => const BasketScreen(), Routes.checkout => const CheckoutScreen(), Routes.favorites => const FavoritesScreen(), diff --git a/example/lib/src/feature/home/widget/home_screen.dart b/example/lib/src/feature/home/widget/home_screen.dart index 1d219c0..1b5587b 100644 --- a/example/lib/src/feature/home/widget/home_screen.dart +++ b/example/lib/src/feature/home/widget/home_screen.dart @@ -31,6 +31,11 @@ class HomeScreen extends StatelessWidget { subtitle: const Text('Gallery description'), onTap: () => context.octopus.push(Routes.gallery), ), + ListTile( + title: const Text('Profile'), + subtitle: const Text('Profile description'), + onTap: () => context.octopus.push(Routes.profile), + ), ], ), ), diff --git a/example/lib/src/feature/shop/widget/basket_screen.dart b/example/lib/src/feature/shop/widget/basket_screen.dart index 22a5174..9e7dbb4 100644 --- a/example/lib/src/feature/shop/widget/basket_screen.dart +++ b/example/lib/src/feature/shop/widget/basket_screen.dart @@ -18,7 +18,7 @@ class BasketTab extends StatelessWidget { Widget build(BuildContext context) => BucketNavigator( bucket: '${ShopTabsEnum.basket}-tab', // Handles back button only if the current route is the basket screen - handlesBackButton: () => + shouldHandleBackButton: (_) => Octopus.instance.state.arguments['shop'] == 'basket', ); } diff --git a/example/lib/src/feature/shop/widget/catalog_screen.dart b/example/lib/src/feature/shop/widget/catalog_screen.dart index a00e058..662a34c 100644 --- a/example/lib/src/feature/shop/widget/catalog_screen.dart +++ b/example/lib/src/feature/shop/widget/catalog_screen.dart @@ -25,7 +25,7 @@ class CatalogTab extends StatelessWidget { Widget build(BuildContext context) => BucketNavigator( bucket: '${ShopTabsEnum.catalog}-tab', // Handles back button only if the current route is the catalog screen - handlesBackButton: () => + shouldHandleBackButton: (_) => Octopus.instance.state.arguments['shop'] == 'catalog', ); } diff --git a/example/lib/src/feature/shop/widget/product_image_screen.dart b/example/lib/src/feature/shop/widget/product_image_screen.dart index 44cd1d3..934e6ab 100644 --- a/example/lib/src/feature/shop/widget/product_image_screen.dart +++ b/example/lib/src/feature/shop/widget/product_image_screen.dart @@ -10,10 +10,10 @@ import 'package:photo_view/photo_view.dart'; /// {@endtemplate} class ProductImageScreen extends StatelessWidget { /// {@macro photo_image_screen} - const ProductImageScreen({ + const ProductImageScreen._({ required this.id, required this.idx, - super.key, + super.key, // ignore: unused_element }); /// Product id @@ -27,21 +27,24 @@ class ProductImageScreen extends StatelessWidget { BuildContext context, { required ProductID id, required int index, - }) => - Navigator.of(context, rootNavigator: true).push( - PageRouteBuilder( - pageBuilder: (context, _, __) => - ProductImageScreen(id: id, idx: index), - transitionsBuilder: (context, animation, secondayAnimation, child) => - ScaleTransition( - scale: Tween(begin: 1.25, end: 1).animate(animation), - child: FadeTransition( - opacity: animation.drive(CurveTween(curve: Curves.easeIn)), - child: child, - ), - ), + }) { + final navigator = Navigator.of(context, rootNavigator: true); + final route = PageRouteBuilder( + pageBuilder: (context, _, __) => BackButtonListener( + onBackButtonPressed: navigator.maybePop, + child: ProductImageScreen._(id: id, idx: index), + ), + transitionsBuilder: (context, animation, secondayAnimation, child) => + ScaleTransition( + scale: Tween(begin: 1.25, end: 1).animate(animation), + child: FadeTransition( + opacity: animation.drive(CurveTween(curve: Curves.easeIn)), + child: child, ), - ); + ), + ); + return navigator.push(route); + } @override Widget build(BuildContext context) { diff --git a/example/pubspec.lock b/example/pubspec.lock index 9d907dd..468f2b6 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -498,7 +498,7 @@ packages: path: ".." relative: true source: path - version: "0.0.1-pre.0" + version: "0.0.1-pre.1" package_config: dependency: transitive description: diff --git a/lib/src/widget/bucket_navigator.dart b/lib/src/widget/bucket_navigator.dart index 3e2964f..55e157c 100644 --- a/lib/src/widget/bucket_navigator.dart +++ b/lib/src/widget/bucket_navigator.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:octopus/src/controller/controller.dart'; import 'package:octopus/src/controller/observer.dart'; @@ -15,7 +16,7 @@ import 'package:octopus/src/widget/route_context.dart'; /// /// The [bucket] unique identifier is used to identify the navigator /// within the all application. -/// The [handlesBackButton] parameter is used to decide whether this navigator +/// The [shouldHandleBackButton] parameter is used to decide whether this navigator /// should handle back button presses. /// The [transitionDelegate] parameter is used to customize the transition /// animation. @@ -27,7 +28,8 @@ class BucketNavigator extends StatefulWidget { /// {@macro bucket_navigator} const BucketNavigator({ required this.bucket, - this.handlesBackButton, + this.shouldHandleBackButton, + this.onBackButtonPressed, this.transitionDelegate, this.observers = const [], this.restorationScopeId, @@ -37,12 +39,16 @@ class BucketNavigator extends StatefulWidget { /// The unique identifier of the navigator. final String bucket; - /// The [handlesBackButton] parameter is used to decide whether this navigator - /// should handle back button presses. + /// The [shouldHandleBackButton] parameter is used to decide + /// whether this navigator should handle back button presses. /// Usefull when you want to handle back button only when the current screen /// is in focus now. /// By default, the value is `true` if the navigator has more than one page. - final bool Function()? handlesBackButton; + final bool Function(BuildContext context)? shouldHandleBackButton; + + /// Override the default back button behavior logic. + final Future Function(BuildContext context, NavigatorState navigator)? + onBackButtonPressed; /// The delegate that decides how the route transition animation should /// look like. @@ -64,8 +70,11 @@ class _BucketNavigatorState extends State /// Octopus router. late final Octopus _router; + /// Navigator observer + final NavigatorObserver _navigatorObserver = NavigatorObserver(); + /// State observer. - late final OctopusStateObserver _observer; + late final OctopusStateObserver _stateObserver; /// Current bucket node. OctopusNode$Immutable? _node; @@ -75,8 +84,8 @@ class _BucketNavigatorState extends State void initState() { super.initState(); _router = context.octopus; - _observer = _router.observer; - _observer.addListener(_handleStateChange); + _stateObserver = _router.observer; + _stateObserver.addListener(_handleStateChange); _handleStateChange(); } @@ -84,7 +93,7 @@ class _BucketNavigatorState extends State void didUpdateWidget(covariant BucketNavigator oldWidget) { super.didUpdateWidget(oldWidget); if (widget.bucket != oldWidget.bucket) { - _observer + _stateObserver ..removeListener(_handleStateChange) ..addListener(_handleStateChange); } @@ -92,7 +101,7 @@ class _BucketNavigatorState extends State @override void dispose() { - _observer.removeListener(_handleStateChange); + _stateObserver.removeListener(_handleStateChange); super.dispose(); } /* #endregion */ @@ -100,7 +109,7 @@ class _BucketNavigatorState extends State /// Callback for router state changes. void _handleStateChange() { if (!mounted) return; - final newNode = _observer.value.findByName(widget.bucket); + final newNode = _stateObserver.value.findByName(widget.bucket); if (newNode == _node) return; setState(() => _node = newNode); } @@ -119,6 +128,7 @@ class _BucketNavigatorState extends State restorationScopeId: widget.restorationScopeId, reportsRouteUpdateToEngine: false, observers: [ + _navigatorObserver, ...widget.observers, ], transitionDelegate: widget.transitionDelegate ?? @@ -150,54 +160,75 @@ class _BucketNavigatorState extends State @override Future _onBackButtonPressed() { - if (!mounted) return Future.value(false); - final handlesBackButton = widget.handlesBackButton; - if (handlesBackButton != null && !handlesBackButton()) - return Future.value(false); - final node = _node; - if (node == null) return Future.value(false); - if (node.children.length < 2) return Future.value(false); - final completer = Completer(); - // ignore: avoid_positional_boolean_parameters - void complete(bool value) { - if (completer.isCompleted) return; - completer.complete(value); - } - - _router.setState( - (state) { - final node = state.findByName(widget.bucket); - if (node == null || node.children.length < 2) { - complete(false); - return state..intention = OctopusStateIntention.cancel; - } - node.removeLast(); - return state; - }, - ).whenComplete(() => complete(true)); - return completer.future; + // Do not handle back button if the navigator is not in focus. + if (!mounted) return SynchronousFuture(false); + + // Check if the navigator should handle back button. + // e.g. if the navigator is not in focus. + final handlesBackButton = widget.shouldHandleBackButton; + if (handlesBackButton != null && !handlesBackButton(context)) + return SynchronousFuture(false); + + // Get the navigator from the observer. + final nav = _navigatorObserver.navigator; + assert(nav != null, 'Navigator is not attached to the OctopusDelegate'); + if (nav == null) return SynchronousFuture(false); + + // Check if the navigator has custom back button behavior. + final onBackButtonPressed = widget.onBackButtonPressed; + if (onBackButtonPressed != null) return onBackButtonPressed(context, nav); + + // Handle back button by default with the current navigator. + return nav.maybePop(); } } /// {@nodoc} mixin _BackButtonBucketNavigatorStateMixin on State { - BackButtonDispatcher? dispatcher; - Future _onBackButtonPressed(); + late final Octopus _bbRouter; + late final BackButtonDispatcher _bbDispatcher; + bool _bbHasPriority = false; + @override void initState() { - dispatcher?.removeCallback(_onBackButtonPressed); - final rootBackDispatcher = context.octopus.config.backButtonDispatcher; - dispatcher = rootBackDispatcher.createChildBackButtonDispatcher() - ..addCallback(_onBackButtonPressed) - ..takePriority(); super.initState(); + _bbRouter = context.octopus; + _bbDispatcher = + _bbRouter.config.backButtonDispatcher.createChildBackButtonDispatcher(); + _bbRouter.observer.addListener(_checkPriority); + _checkPriority(); + } + + void _checkPriority() { + final bucket = widget.bucket; + var children = _bbRouter.observer.value.children; + var priority = false; + while (true) { + if (children.isEmpty) break; + if (children.any((node) => node.name == bucket)) { + priority = true; + break; + } + children = children.last.children; + } + + if (priority == _bbHasPriority) return; + _bbHasPriority = priority; + if (priority) { + _bbDispatcher + ..addCallback(_onBackButtonPressed) + ..takePriority(); + } else { + _bbDispatcher.removeCallback(_onBackButtonPressed); + } } @override void dispose() { - dispatcher?.removeCallback(_onBackButtonPressed); + _bbDispatcher.removeCallback(_onBackButtonPressed); + _bbRouter.observer.removeListener(_checkPriority); super.dispose(); } } From 379feff0c7cf7a7280812a161a3df1b52de8c7c5 Mon Sep 17 00:00:00 2001 From: Plague Fox Date: Thu, 28 Dec 2023 17:37:52 +0400 Subject: [PATCH 2/4] Update dialog functionality and add showDialog method --- .../account/widget/settings_dialog.dart | 2 +- .../src/feature/home/widget/home_screen.dart | 105 +++++++++++++++++- lib/src/controller/controller.dart | 11 ++ lib/src/controller/navigator/controller.dart | 26 +++-- lib/src/controller/navigator/delegate.dart | 89 ++++++++++++++- 5 files changed, 216 insertions(+), 17 deletions(-) diff --git a/example/lib/src/feature/account/widget/settings_dialog.dart b/example/lib/src/feature/account/widget/settings_dialog.dart index 4325667..0735f35 100644 --- a/example/lib/src/feature/account/widget/settings_dialog.dart +++ b/example/lib/src/feature/account/widget/settings_dialog.dart @@ -8,7 +8,7 @@ class SettingsDialog extends StatelessWidget { const SettingsDialog({super.key}); @override - Widget build(BuildContext context) => AlertDialog( + Widget build(BuildContext context) => AlertDialog.adaptive( title: const Text('Settings'), content: const Text('Coming soon...'), actions: [ diff --git a/example/lib/src/feature/home/widget/home_screen.dart b/example/lib/src/feature/home/widget/home_screen.dart index 1b5587b..8ab9818 100644 --- a/example/lib/src/feature/home/widget/home_screen.dart +++ b/example/lib/src/feature/home/widget/home_screen.dart @@ -21,21 +21,114 @@ class HomeScreen extends StatelessWidget { child: ListView( padding: const EdgeInsets.all(16), children: [ + ListTile( + title: const Text('Gallery'), + subtitle: const Text('Simple navigation between screens'), + onTap: () => context.octopus.push(Routes.gallery), + ), ListTile( title: const Text('Shop'), subtitle: const Text('Explore nested navigation'), onTap: () => context.octopus.push(Routes.shop), ), - ListTile( - title: const Text('Gallery'), - subtitle: const Text('Gallery description'), - onTap: () => context.octopus.push(Routes.gallery), - ), ListTile( title: const Text('Profile'), - subtitle: const Text('Profile description'), + subtitle: const Text('Profile with dialogs & anonymous routes'), onTap: () => context.octopus.push(Routes.profile), ), + const _ShowDialogExample(), + ], + ), + ), + ); +} + +class _ShowDialogExample extends StatefulWidget { + const _ShowDialogExample({ + super.key, // ignore: unused_element + }); + + @override + State<_ShowDialogExample> createState() => _ShowDialogExampleState(); +} + +class _ShowDialogExampleState extends State<_ShowDialogExample> { + String? lastResult; + + @override + Widget build(BuildContext context) => ListTile( + title: const Text('Show dialog'), + subtitle: Text(switch (lastResult) { + String text when text.isNotEmpty => 'Last result: $text', + _ => 'Show dialog and receive result', + }), + onTap: () => context.octopus + .showDialog((context) => const _DialogExample()) + .then((value) => setState(() => lastResult = value)), + ); +} + +class _DialogExample extends StatefulWidget { + const _DialogExample({ + super.key, // ignore: unused_element + }); + + @override + State<_DialogExample> createState() => _DialogExampleState(); +} + +class _DialogExampleState extends State<_DialogExample> { + final TextEditingController _controller = TextEditingController(); + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => AlertDialog.adaptive( + title: const Text('Dialog example'), + alignment: Alignment.center, + scrollable: false, + content: SingleChildScrollView( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 200, + height: 64, + child: TextField( + controller: _controller, + decoration: + const InputDecoration(hintText: 'Enter some text'), + ), + ), + const SizedBox(width: 16), + IconButton( + icon: ValueListenableBuilder( + valueListenable: _controller, + builder: (context, value, _) => AnimatedSwitcher( + duration: const Duration(milliseconds: 250), + transitionBuilder: (child, animation) => ScaleTransition( + scale: animation, + child: FadeTransition( + opacity: animation, + child: child, + ), + ), + child: value.text.isEmpty + ? const Icon(Icons.close, key: Key('close')) + : const Icon(Icons.send, key: Key('send')), + ), + ), + iconSize: 32, + onPressed: () => Navigator.maybePop(context, _controller.text), + ), ], ), ), diff --git a/lib/src/controller/controller.dart b/lib/src/controller/controller.dart index af11a6b..4012f4f 100644 --- a/lib/src/controller/controller.dart +++ b/lib/src/controller/controller.dart @@ -1,7 +1,9 @@ import 'dart:async'; import 'dart:convert'; +import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:meta/meta.dart'; import 'package:octopus/src/controller/config.dart'; import 'package:octopus/src/controller/guard.dart'; import 'package:octopus/src/controller/navigator/controller.dart'; @@ -168,4 +170,13 @@ abstract interface class Octopus { /// Update state arguments Future setArguments(void Function(Map args) change); + + /// Show a dialog as a declarative page. + /// The dialog will be added to the navigation stack. + /// [arguments] - dialog arguments + @experimental + Future showDialog( + WidgetBuilder builder, { + Map? arguments, + }); } diff --git a/lib/src/controller/navigator/controller.dart b/lib/src/controller/navigator/controller.dart index a149634..dcba4d3 100644 --- a/lib/src/controller/navigator/controller.dart +++ b/lib/src/controller/navigator/controller.dart @@ -3,8 +3,8 @@ import 'dart:collection'; import 'dart:convert'; import 'dart:math' as math; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; import 'package:octopus/src/controller/config.dart'; import 'package:octopus/src/controller/controller.dart'; @@ -105,20 +105,23 @@ final class Octopus$NavigatorImpl implements Octopus { required OctopusInformationParser routeInformationParser, required BackButtonDispatcher backButtonDispatcher, required OctopusStateObserver observer, - }) : config = OctopusConfig( + }) : config = OctopusConfig( routes: routes, routeInformationProvider: routeInformationProvider, routeInformationParser: routeInformationParser, routerDelegate: routerDelegate, backButtonDispatcher: backButtonDispatcher, observer: observer, - ) { + ), + _routerDelegate = routerDelegate { $octopusSingletonInstance = this; } @override final OctopusConfig config; + final OctopusDelegate$NavigatorImpl _routerDelegate; + @override OctopusStateObserver get stateObserver => observer; @@ -135,23 +138,23 @@ final class Octopus$NavigatorImpl implements Octopus { bool get isIdle => !isProcessing; @override - bool get isProcessing => config.routerDelegate.isProcessing; + bool get isProcessing => _routerDelegate.isProcessing; @override - Future get processingCompleted => - config.routerDelegate.processingCompleted; + Future get processingCompleted => _routerDelegate.processingCompleted; + @override OctopusRoute? getRouteByName(String name) => config.routes[name]; @override Future setState( OctopusState Function(OctopusState$Mutable state) change) => - config.routerDelegate.setNewRoutePath( + _routerDelegate.setNewRoutePath( change(state.mutate()..intention = OctopusStateIntention.auto)); @override Future navigate(String location) => - config.routerDelegate.setNewRoutePath(StateUtil.decodeLocation(location)); + _routerDelegate.setNewRoutePath(StateUtil.decodeLocation(location)); @override Future pop() { @@ -309,4 +312,11 @@ final class Octopus$NavigatorImpl implements Octopus { _txnQueue.add((change, priority)); return completer.future; } + + @override + Future showDialog( + WidgetBuilder builder, { + Map? arguments, + }) => + _routerDelegate.showDialog(builder, arguments: arguments); } diff --git a/lib/src/controller/navigator/delegate.dart b/lib/src/controller/navigator/delegate.dart index a190e68..868c22d 100644 --- a/lib/src/controller/navigator/delegate.dart +++ b/lib/src/controller/navigator/delegate.dart @@ -1,7 +1,7 @@ import 'dart:async'; -import 'dart:collection'; import 'dart:developer' as developer; +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -17,10 +17,14 @@ import 'package:octopus/src/state/node_extra_storage.dart'; import 'package:octopus/src/state/state.dart'; import 'package:octopus/src/util/logs.dart'; import 'package:octopus/src/util/state_util.dart'; +import 'package:octopus/src/widget/dialog_page.dart'; import 'package:octopus/src/widget/inherited_octopus.dart'; import 'package:octopus/src/widget/navigator.dart'; import 'package:octopus/src/widget/no_animation.dart'; +/// {@nodoc} +const String _kDialogNodeName = 'd'; + /// Octopus delegate. /// {@nodoc} @internal @@ -155,7 +159,17 @@ final class OctopusDelegate$NavigatorImpl extends OctopusDelegate { final state = _observer.value.mutate(); if (state.children.isEmpty) return false; - state.children.removeLast(); + final node = state.children.removeLast(); + + // If the node is a dialog, then save the result + if (node.name == _kDialogNodeName) { + final key = node.arguments['k']; + if (key != null) { + _dialogResults[key] = result; + } + } + + // Update the state setNewRoutePath(state); } return true; @@ -175,6 +189,16 @@ final class OctopusDelegate$NavigatorImpl extends OctopusDelegate // Build pages for (final node in nodes) { try { + // If the node is a dialog, then build the dialog page + if (node.name == _kDialogNodeName) { + final key = node.arguments['k']; + if (key == null) continue; + final page = _dialogBuilders[key]; + if (page == null) continue; + pages.add(page); + continue; + } + // Build the page final Page page; final route = routes[node.name]; if (route == null) { @@ -412,6 +436,67 @@ final class OctopusDelegate$NavigatorImpl extends OctopusDelegate ..close(); super.dispose(); } + + final Map _dialogBuilders = + {}; + final Map _dialogResults = {}; + + /// Show a dialog as a declarative page. + /// {@nodoc} + @internal + Future showDialog( + WidgetBuilder builder, { + Map? arguments, + }) async { + final key = shortHash(UniqueKey()); + final completer = Completer(); + void onStateChanged() { + if (completer.isCompleted) return; + final node = _observer.value.children.firstWhereOrNull((node) => + node.name == _kDialogNodeName && node.arguments['k'] == key); + if (node != null) return; + final result = _dialogResults.remove(key); + completer.complete(result is T ? result : null); + } + + try { + _dialogBuilders[key] = OctopusDialogPage( + name: _kDialogNodeName, + builder: builder, + arguments: { + 'k': key, + ...?arguments, + }, + restorationId: null, + ); + await setNewRoutePath( + currentConfiguration.mutate() + ..intention = OctopusStateIntention.navigate + ..children.add( + OctopusNode.mutable( + _kDialogNodeName, + arguments: { + 'k': key, + ...?arguments, + }, + ), + ), + ); + await Future.delayed(Duration.zero); + _observer.addListener(onStateChanged); + onStateChanged(); + final result = await completer.future; + return result; + } on Object { + return null; // ignore errors + } finally { + // Clean up + _observer.removeListener(onStateChanged); + _dialogBuilders.remove(key); + _dialogResults.remove(key); + if (!completer.isCompleted) completer.complete(); + } + } } mixin _TitleMixin { From 63c7f9860c698f4f71387cc6a9868ccfdcb588c3 Mon Sep 17 00:00:00 2001 From: Plague Fox Date: Thu, 28 Dec 2023 17:38:45 +0400 Subject: [PATCH 3/4] Add dialogs and pop buttons logic, update version to 0.0.1-pre.2 --- CHANGELOG.md | 4 ++++ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f28221..48c5faf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.1-pre.2 + +- Dialogs and pop buttons logic + ## 0.0.1-pre.1 - Refactoring diff --git a/pubspec.yaml b/pubspec.yaml index d54a899..496f760 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: octopus description: "A cross-platform declarative router for Flutter with a focus on state and nested navigation. Made with ❤️ by PlugFox." -version: 0.0.1-pre.1 +version: 0.0.1-pre.2 homepage: https://github.com/PlugFox/octopus From 9746809d4cb1da133a2caeb0aecb75abb5cc6aa4 Mon Sep 17 00:00:00 2001 From: Plague Fox Date: Thu, 28 Dec 2023 17:39:29 +0400 Subject: [PATCH 4/4] Update comment --- lib/src/widget/bucket_navigator.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/widget/bucket_navigator.dart b/lib/src/widget/bucket_navigator.dart index 55e157c..63e6b42 100644 --- a/lib/src/widget/bucket_navigator.dart +++ b/lib/src/widget/bucket_navigator.dart @@ -16,8 +16,8 @@ import 'package:octopus/src/widget/route_context.dart'; /// /// The [bucket] unique identifier is used to identify the navigator /// within the all application. -/// The [shouldHandleBackButton] parameter is used to decide whether this navigator -/// should handle back button presses. +/// The [shouldHandleBackButton] parameter is used to decide +/// whether this navigator should handle back button presses. /// The [transitionDelegate] parameter is used to customize the transition /// animation. /// The [observers] parameter is used to observe the navigation events.