Skip to content

Commit

Permalink
Update dependencies and refactor code
Browse files Browse the repository at this point in the history
  • Loading branch information
PlugFox committed Dec 28, 2023
1 parent d92db08 commit 2aac494
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 70 deletions.
6 changes: 0 additions & 6 deletions example/lib/src/common/router/routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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'),
Expand All @@ -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(),
Expand Down
5 changes: 5 additions & 0 deletions example/lib/src/feature/home/widget/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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),
),
],
),
),
Expand Down
2 changes: 1 addition & 1 deletion example/lib/src/feature/shop/widget/basket_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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',
);
}
Expand Down
2 changes: 1 addition & 1 deletion example/lib/src/feature/shop/widget/catalog_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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',
);
}
Expand Down
35 changes: 19 additions & 16 deletions example/lib/src/feature/shop/widget/product_image_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -27,21 +27,24 @@ class ProductImageScreen extends StatelessWidget {
BuildContext context, {
required ProductID id,
required int index,
}) =>
Navigator.of(context, rootNavigator: true).push<void>(
PageRouteBuilder<void>(
pageBuilder: (context, _, __) =>
ProductImageScreen(id: id, idx: index),
transitionsBuilder: (context, animation, secondayAnimation, child) =>
ScaleTransition(
scale: Tween<double>(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<void>(
pageBuilder: (context, _, __) => BackButtonListener(
onBackButtonPressed: navigator.maybePop,
child: ProductImageScreen._(id: id, idx: index),
),
transitionsBuilder: (context, animation, secondayAnimation, child) =>
ScaleTransition(
scale: Tween<double>(begin: 1.25, end: 1).animate(animation),
child: FadeTransition(
opacity: animation.drive(CurveTween(curve: Curves.easeIn)),
child: child,
),
);
),
);
return navigator.push<void>(route);
}

@override
Widget build(BuildContext context) {
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
121 changes: 76 additions & 45 deletions lib/src/widget/bucket_navigator.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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.
Expand All @@ -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 <NavigatorObserver>[],
this.restorationScopeId,
Expand All @@ -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<bool> Function(BuildContext context, NavigatorState navigator)?
onBackButtonPressed;

/// The delegate that decides how the route transition animation should
/// look like.
Expand All @@ -64,8 +70,11 @@ class _BucketNavigatorState extends State<BucketNavigator>
/// 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;
Expand All @@ -75,32 +84,32 @@ class _BucketNavigatorState extends State<BucketNavigator>
void initState() {
super.initState();
_router = context.octopus;
_observer = _router.observer;
_observer.addListener(_handleStateChange);
_stateObserver = _router.observer;
_stateObserver.addListener(_handleStateChange);
_handleStateChange();
}

@override
void didUpdateWidget(covariant BucketNavigator oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.bucket != oldWidget.bucket) {
_observer
_stateObserver
..removeListener(_handleStateChange)
..addListener(_handleStateChange);
}
}

@override
void dispose() {
_observer.removeListener(_handleStateChange);
_stateObserver.removeListener(_handleStateChange);
super.dispose();
}
/* #endregion */

/// 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);
}
Expand All @@ -119,6 +128,7 @@ class _BucketNavigatorState extends State<BucketNavigator>
restorationScopeId: widget.restorationScopeId,
reportsRouteUpdateToEngine: false,
observers: <NavigatorObserver>[
_navigatorObserver,
...widget.observers,
],
transitionDelegate: widget.transitionDelegate ??
Expand Down Expand Up @@ -150,54 +160,75 @@ class _BucketNavigatorState extends State<BucketNavigator>

@override
Future<bool> _onBackButtonPressed() {
if (!mounted) return Future<bool>.value(false);
final handlesBackButton = widget.handlesBackButton;
if (handlesBackButton != null && !handlesBackButton())
return Future<bool>.value(false);
final node = _node;
if (node == null) return Future<bool>.value(false);
if (node.children.length < 2) return Future<bool>.value(false);
final completer = Completer<bool>();
// 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<bool>(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<bool>(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<bool>(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<BucketNavigator> {
BackButtonDispatcher? dispatcher;

Future<bool> _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();
}
}

0 comments on commit 2aac494

Please sign in to comment.