Skip to content

Commit

Permalink
Add mutable and immutable state
Browse files Browse the repository at this point in the history
  • Loading branch information
PlugFox committed Nov 24, 2023
1 parent c143006 commit 81b2fdc
Show file tree
Hide file tree
Showing 6 changed files with 475 additions and 69 deletions.
58 changes: 32 additions & 26 deletions lib/src/controller/delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,28 @@ import 'package:octopus/src/widget/octopus_navigator.dart';
/// {@nodoc}
@internal
final class OctopusDelegate extends RouterDelegate<OctopusState>
with ChangeNotifier, _OctopusStateObserver {
with ChangeNotifier {
/// Octopus delegate.
/// {@nodoc}
OctopusDelegate({
required OctopusState initialState,
required List<OctopusRoute> routes,
required Map<String, OctopusRoute> routes,
String? restorationScopeId = 'octopus',
List<NavigatorObserver>? observers,
TransitionDelegate<Object?>? transitionDelegate,
RouteFactory? notFound,
void Function(Object error, StackTrace stackTrace)? onError,
}) : _routes = <String, OctopusRoute>{
for (final route in routes) route.name: route,
},
}) : _stateObserver = _OctopusStateObserver(initialState),
_routes = routes,
_restorationScopeId = restorationScopeId,
_observers = observers,
_transitionDelegate =
transitionDelegate ?? const DefaultTransitionDelegate<Object?>(),
_notFound = notFound,
_onError = onError {
_value = initialState;
}
_onError = onError;

final _OctopusStateObserver _stateObserver;
ValueListenable<OctopusState> get stateObserver => _stateObserver;

/// The restoration scope id for the navigator.
final String? _restorationScopeId;
Expand Down Expand Up @@ -77,9 +77,7 @@ final class OctopusDelegate extends RouterDelegate<OctopusState>
@override
OctopusState get currentConfiguration =>
// ignore: prefer_expression_function_bodies
_handleErrors(() {
return value.copy();
});
_handleErrors(_stateObserver.value.copy);

@override
Widget build(BuildContext context) => OctopusNavigator(
Expand All @@ -91,26 +89,31 @@ final class OctopusDelegate extends RouterDelegate<OctopusState>
...?_observers,
],
transitionDelegate: _transitionDelegate,
pages: _buildPages(context),
pages: buildPagesFromNodes(context, _stateObserver.value.children),
onPopPage: _onPopPage,
onUnknownRoute: _onUnknownRoute,
);

bool _onPopPage(Route<Object?> route, Object? result) => _handleErrors(
() {
if (!route.didPop(result)) return false;
final state = value.copy();
final popped = state.maybePop();
if (popped == null) return false;
setNewRoutePath(state);
// TODO(plugfox): make effective pop on immutable state
{
final state = _stateObserver.value.copy();
final popped = state.maybePop();
if (popped == null) return false;
setNewRoutePath(state);
}
return true;
},
(_, __) => false,
);

List<Page<Object?>> _buildPages(BuildContext context) => _handleErrors(() {
List<Page<Object?>> buildPagesFromNodes(
BuildContext context, List<OctopusNode> nodes) =>
_handleErrors(() {
final pages = <Page<Object?>>[];
for (final node in value.children) {
for (final node in nodes) {
final route = _routes[node.name];
assert(route != null, 'Route ${node.name} not found');
if (route == null) continue;
Expand Down Expand Up @@ -205,7 +208,7 @@ final class OctopusDelegate extends RouterDelegate<OctopusState>
// return SynchronousFuture<void>(null);
//}

changeState(newConfiguration);
_stateObserver.changeState(newConfiguration);
notifyListeners();
}, (_, __) {});

Expand All @@ -227,22 +230,25 @@ final class OctopusDelegate extends RouterDelegate<OctopusState>
}
}

mixin _OctopusStateObserver
on RouterDelegate<OctopusState>, ChangeNotifier
implements ValueListenable<OctopusState> {
final class _OctopusStateObserver
with ChangeNotifier
implements ValueListenable<OctopusState$Immutable> {
_OctopusStateObserver(OctopusState initialState)
: _value = OctopusState$Immutable.from(initialState);

@protected
@nonVirtual
late OctopusState _value;
OctopusState$Immutable _value;

@override
OctopusState get value => _value;
OctopusState$Immutable get value => _value;

@protected
@internal
@nonVirtual
void changeState(OctopusState? state) {
if (state == null) return;
if (state.children.isEmpty) return;
_value = state;
_value = OctopusState$Immutable.from(state);
notifyListeners();
}
}
84 changes: 62 additions & 22 deletions lib/src/controller/octopus.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:collection';

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:octopus/src/controller/delegate.dart';
Expand Down Expand Up @@ -26,17 +28,14 @@ abstract base class Octopus {
Octopus._({required this.config});

/// Receives the [Octopus] instance from the elements tree.
static Octopus of(BuildContext context) =>
context.findAncestorWidgetOfExactType<OctopusNavigator>()?.controller ??
_notFound();
static Octopus? maybeOf(BuildContext context) =>
OctopusNavigator.maybeOf(context);

static Never _notFound() => throw ArgumentError(
'Out of scope, not found a OctopusNavigator widget',
'out_of_scope',
);
/// Receives the [Octopus] instance from the elements tree.
static Octopus of(BuildContext context) => OctopusNavigator.of(context);

/// A convenient bundle to configure a [Router] widget.
final RouterConfig<OctopusState> config;
final OctopusConfig config;

/// Current state.
OctopusState get state;
Expand Down Expand Up @@ -82,19 +81,25 @@ final class _OctopusImpl extends Octopus
final routeInformationProvider = OctopusInformationProvider();
final backButtonDispatcher = RootBackButtonDispatcher();
final routeInformationParser = OctopusInformationParser();
final routesTable = UnmodifiableMapView<String, OctopusRoute>(
<String, OctopusRoute>{
for (final route in routes) route.name: route,
},
);
final routerDelegate = OctopusDelegate(
initialState: OctopusState(
initialState: OctopusState$Immutable(
children: <OctopusNode>[defaultRoute.node()],
arguments: <String, String>{},
arguments: const <String, String>{},
),
routes: list,
routes: routesTable,
restorationScopeId: restorationScopeId,
observers: observers,
transitionDelegate: transitionDelegate,
notFound: notFound,
onError: onError,
);
final controller = _OctopusImpl._(
routes: routesTable,
routeInformationProvider: routeInformationProvider,
routeInformationParser: routeInformationParser,
routerDelegate: routerDelegate,
Expand All @@ -105,13 +110,14 @@ final class _OctopusImpl extends Octopus
}

_OctopusImpl._({
required Map<String, OctopusRoute> routes,
required OctopusDelegate routerDelegate,
required RouteInformationProvider routeInformationProvider,
required RouteInformationParser<OctopusState> routeInformationParser,
required OctopusInformationProvider routeInformationProvider,
required OctopusInformationParser routeInformationParser,
required BackButtonDispatcher backButtonDispatcher,
}) : stateObserver = routerDelegate,
super._(
config: RouterConfig<OctopusState>(
}) : super._(
config: OctopusConfig(
routes: routes,
routeInformationProvider: routeInformationProvider,
routeInformationParser: routeInformationParser,
routerDelegate: routerDelegate,
Expand All @@ -120,23 +126,57 @@ final class _OctopusImpl extends Octopus
);

@override
OctopusState get state => stateObserver.currentConfiguration;
OctopusState get state => config.routerDelegate.currentConfiguration;

@override
final OctopusDelegate stateObserver;
ValueListenable<OctopusState> get stateObserver =>
config.routerDelegate.stateObserver;
}

base mixin _OctopusDelegateOwner on Octopus {
@override
abstract final OctopusDelegate stateObserver;
abstract final ValueListenable<OctopusState> stateObserver;
}

base mixin _OctopusNavigationMixin on _OctopusDelegateOwner, Octopus {
base mixin _OctopusNavigationMixin on Octopus {
@override
void setState(OctopusState Function(OctopusState state) change) =>
stateObserver.setNewRoutePath(change(state));
config.routerDelegate.setNewRoutePath(change(state));

@override
void navigate(String location) =>
stateObserver.setNewRoutePath(StateUtil.decodeLocation(location));
config.routerDelegate.setNewRoutePath(StateUtil.decodeLocation(location));
}

/// {@template octopus_config}
/// Creates a [OctopusConfig] as a [RouterConfig].
/// {@endtemplate}
class OctopusConfig implements RouterConfig<OctopusState> {
/// {@macro octopus_config}
OctopusConfig({
required this.routes,
required this.routeInformationProvider,
required this.routeInformationParser,
required this.routerDelegate,
required this.backButtonDispatcher,
});

/// The [OctopusRoute]s that are used to configure the [Router].
final Map<String, OctopusRoute> routes;

/// The [RouteInformationProvider] that is used to configure the [Router].
@override
final OctopusInformationProvider routeInformationProvider;

/// The [RouteInformationParser] that is used to configure the [Router].
@override
final OctopusInformationParser routeInformationParser;

/// The [RouterDelegate] that is used to configure the [Router].
@override
final OctopusDelegate routerDelegate;

/// The [BackButtonDispatcher] that is used to configure the [Router].
@override
final BackButtonDispatcher backButtonDispatcher;
}
Loading

0 comments on commit 81b2fdc

Please sign in to comment.