From 5d649ed635da7778573e5f3da10d7f659d269475 Mon Sep 17 00:00:00 2001 From: Plague Fox Date: Wed, 27 Dec 2023 19:01:05 +0400 Subject: [PATCH] Update OctopusInformationProvider --- lib/src/controller/information_provider.dart | 239 +++--------------- .../controller/information_provider_js.dart | 153 +++++++++++ .../controller/information_provider_vm.dart | 153 +++++++++++ lib/src/controller/navigator/controller.dart | 2 +- .../platform/system_navigator_util_js.dart | 8 +- 5 files changed, 340 insertions(+), 215 deletions(-) create mode 100644 lib/src/controller/information_provider_js.dart create mode 100644 lib/src/controller/information_provider_vm.dart diff --git a/lib/src/controller/information_provider.dart b/lib/src/controller/information_provider.dart index 9b23aeb..05ce205 100644 --- a/lib/src/controller/information_provider.dart +++ b/lib/src/controller/information_provider.dart @@ -2,11 +2,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; -import 'package:octopus/src/state/state.dart'; -import 'package:octopus/src/state/state_codec.dart'; -import 'package:octopus/src/util/jenkins_hash.dart'; -import 'package:octopus/src/util/logs.dart'; -import 'package:octopus/src/util/system_navigator_util.dart'; +import 'package:octopus/src/controller/information_provider_js.dart'; +import 'package:octopus/src/controller/information_provider_vm.dart'; /// The route information provider that propagates /// the platform route information changes. @@ -25,35 +22,33 @@ import 'package:octopus/src/util/system_navigator_util.dart'; /// /// {@nodoc} @internal -class OctopusInformationProvider extends RouteInformationProvider +abstract base class OctopusInformationProvider extends RouteInformationProvider with WidgetsBindingObserver, ChangeNotifier { - factory OctopusInformationProvider({ - RouteInformation? initialRouteInformation, - Listenable? refreshListenable, - }) { - final valueInEngine = _initialRouteInformation(); - return OctopusInformationProvider._( - valueInEngine: valueInEngine, - value: initialRouteInformation ?? valueInEngine, - refreshListenable: refreshListenable, - ); - } - /// {@nodoc} - OctopusInformationProvider._({ - required RouteInformation valueInEngine, - required RouteInformation value, + OctopusInformationProvider({ Listenable? refreshListenable, - }) : _value = value, - _valueInEngine = valueInEngine, - _refreshListenable = refreshListenable { - /* if (kFlutterMemoryAllocationsEnabled) { + }) : _refreshListenable = refreshListenable { + if (kFlutterMemoryAllocationsEnabled) { ChangeNotifier.maybeDispatchObjectCreation(this); - } */ + } _refreshListenable?.addListener(notifyListeners); } - static RouteInformation _initialRouteInformation() { + factory OctopusInformationProvider.platform({ + RouteInformation? initialRouteInformation, + Listenable? refreshListenable, + }) => + kIsWeb + ? OctopusInformationProvider$JS( + initialRouteInformation: initialRouteInformation, + refreshListenable: refreshListenable, + ) + : OctopusInformationProvider$VM( + initialRouteInformation: initialRouteInformation, + refreshListenable: refreshListenable, + ); + + static RouteInformation initialRouteInformation() { final platformDefault = WidgetsBinding.instance.platformDispatcher.defaultRouteName; Uri? uri; @@ -62,213 +57,37 @@ class OctopusInformationProvider extends RouteInformationProvider } else { uri = Uri.tryParse(platformDefault); } - return uri == null ? _kEmptyRouteInformation : RouteInformation(uri: uri); + return uri == null ? kEmptyRouteInformation : RouteInformation(uri: uri); } - final Listenable? _refreshListenable; - - static WidgetsBinding get _binding => WidgetsBinding.instance; - static final RouteInformation _kEmptyRouteInformation = + static final RouteInformation kEmptyRouteInformation = RouteInformation(uri: Uri()); - @override - void routerReportsNewRouteInformation( - RouteInformation routeInformation, { - RouteInformationReportingType type = RouteInformationReportingType.none, - }) { - /* if (neglectIf != null && neglectIf!(routeInformation.uri.toString())) { - return; - } */ - - fine('routerReportsNewRouteInformation(${routeInformation.uri}, ' - '${routeInformation.state})'); - - if (routeInformation is OctopusRouteInformation) { - if (routeInformation.intention == OctopusStateIntention.cancel) return; - if (routeInformation.intention == OctopusStateIntention.neglect) return; - } - - // Avoid adding a new history entry if the route is the same as before. - /* final replace = type == RouteInformationReportingType.neglect || - (type == RouteInformationReportingType.none && - _valueInEngine.uri == routeInformation.uri && - _valueInEngine.state == routeInformation.state); */ - - var replace = false; - switch (routeInformation) { - case OctopusRouteInformation info - when info.intention == OctopusStateIntention.cancel || - info.intention == OctopusStateIntention.neglect: - return; - case OctopusRouteInformation info - when info.intention == OctopusStateIntention.replace: - replace = true; - case OctopusRouteInformation info - when info.intention == OctopusStateIntention.navigate: - replace = false; - } - - switch (type) { - case RouteInformationReportingType.none: - if (_valueInEngine.uri == routeInformation.uri) { - replace = true; - if (identical(_valueInEngine.state, routeInformation.state)) { - return; - } else { - final hashA = jenkinsHash(_valueInEngine.state); - final hashB = jenkinsHash(routeInformation.state); - if (hashA == hashB) return; - } - } - case RouteInformationReportingType.neglect: - replace = true; - case RouteInformationReportingType.navigate: - replace = false; - } - - // If the route is different from the current route, then update the engine. - /* if (kIsWeb && routeInformation.uri == _value.uri) { - config('Uri: ${routeInformation.uri}'); - } */ - /* SystemNavigator.selectMultiEntryHistory(); // selectSingleEntryHistory - SystemNavigator.routeInformationUpdated( - uri: routeInformation.uri, - state: routeInformation.state, - replace: replace, - ); */ - - if (replace) { - SystemNavigatorUtil.replaceState( - data: routeInformation.state, - url: routeInformation.uri, - /* title: , */ - ); - } else { - SystemNavigatorUtil.pushState( - data: routeInformation.state, - url: routeInformation.uri, - /* title: , */ - ); - } - _value = _valueInEngine = routeInformation; - } - - @override - RouteInformation get value => _value; - RouteInformation _value; - set value(RouteInformation other) { - final shouldNotify = _value.uri != other.uri || _value.state != other.state; - _value = other; - if (shouldNotify) notifyListeners(); - } - - RouteInformation _valueInEngine; - - void pushRoute(RouteInformation routeInformation) { - if (_value == routeInformation) return; - fine('pushRoute(${routeInformation.uri}, ${routeInformation.state})'); - // If the route information is an OctopusRouteInformation, - // then handle it and set it as the current route information. - if (routeInformation is OctopusRouteInformation) { - _value = _valueInEngine = routeInformation; - notifyListeners(); - return; - } - // If the route information has a state containing information about - // the children, then handle it, decode the state and set it as the - // current route information. - if (routeInformation.state case Map json) { - if (json.containsKey('children')) { - final state = OctopusState.fromJson(json); - _value = _valueInEngine = OctopusRouteInformation(state); - notifyListeners(); - return; - } - } - final uri = routeInformation.uri; - // If location does not start with a '/', - // then handle it as a pop operation. - if (!routeInformation.uri.path.startsWith('/')) - return popUri(routeInformation); - - _value = RouteInformation( - uri: uri, - state: null, - ); - _valueInEngine = _kEmptyRouteInformation; - notifyListeners(); - } - - void popUri(RouteInformation routeInformation) { - final popUri = routeInformation.uri; - fine('popFromUri($popUri)'); - var popTo = popUri.path; - popTo = popTo.startsWith('/') ? popTo : '/$popTo'; - var path = _value.uri.path; - if (path.endsWith(popTo)) { - path = path.substring(0, path.length - popTo.length); - } else { - final idx = path.lastIndexOf('$popTo/'); - if (idx == -1) { - warning('Cannot pop to "$popTo" from "$path"'); - } else { - path = path.substring(0, idx); - } - } - while (path.startsWith('//')) path = path.substring(1); - while (path.endsWith('/')) path = path.substring(0, path.length - 1); + final Listenable? _refreshListenable; - //if (path.isEmpty || path == '/') - _value = RouteInformation( - uri: _value.uri.replace(path: path), - state: null, - ); - _valueInEngine = _kEmptyRouteInformation; - notifyListeners(); - } + static WidgetsBinding get _binding => WidgetsBinding.instance; @override + @mustCallSuper void addListener(VoidCallback listener) { if (!hasListeners) _binding.addObserver(this); super.addListener(listener); } @override + @mustCallSuper void removeListener(VoidCallback listener) { super.removeListener(listener); if (!hasListeners) _binding.removeObserver(this); } @override + @mustCallSuper void dispose() { if (hasListeners) _binding.removeObserver(this); _refreshListenable?.removeListener(notifyListeners); super.dispose(); } - - @override - Future didPushRouteInformation(RouteInformation routeInformation) { - assert( - hasListeners, - 'A OctopusInformationProvider must have ' - 'at least one listener before it can be used.'); - pushRoute(routeInformation); - return SynchronousFuture(true); - } - - @override - @Deprecated('Use didPushRouteInformation instead') - Future didPushRoute(String route) { - assert( - hasListeners, - 'A OctopusInformationProvider must have ' - 'at least one listener before it can be used.'); - pushRoute(RouteInformation(uri: Uri.tryParse(route))); - return SynchronousFuture(true); - } - - @override - Future didPopRoute() => Future.value(false); } /* Useful methods for the package diff --git a/lib/src/controller/information_provider_js.dart b/lib/src/controller/information_provider_js.dart new file mode 100644 index 0000000..c55ac33 --- /dev/null +++ b/lib/src/controller/information_provider_js.dart @@ -0,0 +1,153 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:meta/meta.dart'; +import 'package:octopus/src/controller/information_provider.dart'; +import 'package:octopus/src/state/state.dart'; +import 'package:octopus/src/state/state_codec.dart'; +import 'package:octopus/src/util/jenkins_hash.dart'; +import 'package:octopus/src/util/system_navigator_util.dart'; + +/// {@nodoc} +@internal +final class OctopusInformationProvider$JS extends OctopusInformationProvider { + /// {@nodoc} + factory OctopusInformationProvider$JS({ + RouteInformation? initialRouteInformation, + Listenable? refreshListenable, + }) { + final valueInEngine = OctopusInformationProvider.initialRouteInformation(); + return OctopusInformationProvider$JS._( + valueInEngine: valueInEngine, + value: initialRouteInformation ?? valueInEngine, + refreshListenable: refreshListenable, + ); + } + + /// {@nodoc} + OctopusInformationProvider$JS._({ + required RouteInformation valueInEngine, + required RouteInformation value, + super.refreshListenable, + }) : _value = value, + _valueInEngine = valueInEngine; + + @override + void routerReportsNewRouteInformation( + RouteInformation routeInformation, { + RouteInformationReportingType type = RouteInformationReportingType.none, + }) { + /* if (neglectIf != null && neglectIf!(routeInformation.uri.toString())) { + return; + } */ + + /* fine('routerReportsNewRouteInformation(${routeInformation.uri}, ' + '${routeInformation.state})'); */ + + if (routeInformation is OctopusRouteInformation) { + if (routeInformation.intention == OctopusStateIntention.cancel) return; + if (routeInformation.intention == OctopusStateIntention.neglect) return; + } + + // Avoid adding a new history entry if the route is the same as before. + /* final replace = type == RouteInformationReportingType.neglect || + (type == RouteInformationReportingType.none && + _valueInEngine.uri == routeInformation.uri && + _valueInEngine.state == routeInformation.state); */ + + var replace = false; + switch (routeInformation) { + case OctopusRouteInformation info + when info.intention == OctopusStateIntention.cancel || + info.intention == OctopusStateIntention.neglect: + return; + case OctopusRouteInformation info + when info.intention == OctopusStateIntention.replace: + replace = true; + case OctopusRouteInformation info + when info.intention == OctopusStateIntention.navigate: + replace = false; + } + + switch (type) { + case RouteInformationReportingType.none: + if (_valueInEngine.uri == routeInformation.uri) { + replace = true; + if (identical(_valueInEngine.state, routeInformation.state)) { + return; + } else { + final hashA = jenkinsHash(_valueInEngine.state); + final hashB = jenkinsHash(routeInformation.state); + if (hashA == hashB) return; + } + } + case RouteInformationReportingType.neglect: + replace = true; + case RouteInformationReportingType.navigate: + replace = false; + } + + // If the route is different from the current route, then update the engine. + /* if (kIsWeb && routeInformation.uri == _value.uri) { + config('Uri: ${routeInformation.uri}'); + } */ + /* SystemNavigator.selectMultiEntryHistory(); // selectSingleEntryHistory + SystemNavigator.routeInformationUpdated( + uri: routeInformation.uri, + state: routeInformation.state, + replace: replace, + ); */ + + if (replace) { + SystemNavigatorUtil.replaceState( + data: routeInformation.state, url: routeInformation.uri); + } else { + SystemNavigatorUtil.pushState( + data: routeInformation.state, url: routeInformation.uri); + } + _value = _valueInEngine = routeInformation; + } + + @override + RouteInformation get value => _value; + RouteInformation _value; + set value(RouteInformation other) { + final shouldNotify = _value.uri != other.uri || _value.state != other.state; + _value = other; + if (shouldNotify) notifyListeners(); + } + + RouteInformation _valueInEngine; + + void pushRoute(RouteInformation routeInformation) { + if (_value == routeInformation) return; + if (routeInformation.uri.path.startsWith('/')) return; + final uri = routeInformation.uri; + _value = RouteInformation(uri: uri, state: null); + _valueInEngine = OctopusInformationProvider.kEmptyRouteInformation; + notifyListeners(); + } + + @override + Future didPushRouteInformation(RouteInformation routeInformation) { + assert( + hasListeners, + 'A OctopusInformationProvider must have ' + 'at least one listener before it can be used.'); + pushRoute(routeInformation); + return SynchronousFuture(true); + } + + @override + @Deprecated('Use didPushRouteInformation instead') + Future didPushRoute(String route) { + assert( + hasListeners, + 'A OctopusInformationProvider must have ' + 'at least one listener before it can be used.'); + pushRoute(RouteInformation(uri: Uri.tryParse(route))); + return SynchronousFuture(true); + } + + @override + Future didPopRoute() => Future.value(false); +} diff --git a/lib/src/controller/information_provider_vm.dart b/lib/src/controller/information_provider_vm.dart new file mode 100644 index 0000000..2ce4553 --- /dev/null +++ b/lib/src/controller/information_provider_vm.dart @@ -0,0 +1,153 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:meta/meta.dart'; +import 'package:octopus/src/controller/information_provider.dart'; +import 'package:octopus/src/state/state.dart'; +import 'package:octopus/src/state/state_codec.dart'; +import 'package:octopus/src/util/jenkins_hash.dart'; +import 'package:octopus/src/util/system_navigator_util.dart'; + +/// {@nodoc} +@internal +final class OctopusInformationProvider$VM extends OctopusInformationProvider { + /// {@nodoc} + factory OctopusInformationProvider$VM({ + RouteInformation? initialRouteInformation, + Listenable? refreshListenable, + }) { + final valueInEngine = OctopusInformationProvider.initialRouteInformation(); + return OctopusInformationProvider$VM._( + valueInEngine: valueInEngine, + value: initialRouteInformation ?? valueInEngine, + refreshListenable: refreshListenable, + ); + } + + /// {@nodoc} + OctopusInformationProvider$VM._({ + required RouteInformation valueInEngine, + required RouteInformation value, + super.refreshListenable, + }) : _value = value, + _valueInEngine = valueInEngine; + + @override + void routerReportsNewRouteInformation( + RouteInformation routeInformation, { + RouteInformationReportingType type = RouteInformationReportingType.none, + }) { + /* if (neglectIf != null && neglectIf!(routeInformation.uri.toString())) { + return; + } */ + + /* fine('routerReportsNewRouteInformation(${routeInformation.uri}, ' + '${routeInformation.state})'); */ + + if (routeInformation is OctopusRouteInformation) { + if (routeInformation.intention == OctopusStateIntention.cancel) return; + if (routeInformation.intention == OctopusStateIntention.neglect) return; + } + + // Avoid adding a new history entry if the route is the same as before. + /* final replace = type == RouteInformationReportingType.neglect || + (type == RouteInformationReportingType.none && + _valueInEngine.uri == routeInformation.uri && + _valueInEngine.state == routeInformation.state); */ + + var replace = false; + switch (routeInformation) { + case OctopusRouteInformation info + when info.intention == OctopusStateIntention.cancel || + info.intention == OctopusStateIntention.neglect: + return; + case OctopusRouteInformation info + when info.intention == OctopusStateIntention.replace: + replace = true; + case OctopusRouteInformation info + when info.intention == OctopusStateIntention.navigate: + replace = false; + } + + switch (type) { + case RouteInformationReportingType.none: + if (_valueInEngine.uri == routeInformation.uri) { + replace = true; + if (identical(_valueInEngine.state, routeInformation.state)) { + return; + } else { + final hashA = jenkinsHash(_valueInEngine.state); + final hashB = jenkinsHash(routeInformation.state); + if (hashA == hashB) return; + } + } + case RouteInformationReportingType.neglect: + replace = true; + case RouteInformationReportingType.navigate: + replace = false; + } + + // If the route is different from the current route, then update the engine. + /* if (kIsWeb && routeInformation.uri == _value.uri) { + config('Uri: ${routeInformation.uri}'); + } */ + /* SystemNavigator.selectMultiEntryHistory(); // selectSingleEntryHistory + SystemNavigator.routeInformationUpdated( + uri: routeInformation.uri, + state: routeInformation.state, + replace: replace, + ); */ + + if (replace) { + SystemNavigatorUtil.replaceState( + data: routeInformation.state, url: routeInformation.uri); + } else { + SystemNavigatorUtil.pushState( + data: routeInformation.state, url: routeInformation.uri); + } + _value = _valueInEngine = routeInformation; + } + + @override + RouteInformation get value => _value; + RouteInformation _value; + set value(RouteInformation other) { + final shouldNotify = _value.uri != other.uri || _value.state != other.state; + _value = other; + if (shouldNotify) notifyListeners(); + } + + RouteInformation _valueInEngine; + + void pushRoute(RouteInformation routeInformation) { + if (_value == routeInformation) return; + if (routeInformation.uri.path.startsWith('/')) return; + final uri = routeInformation.uri; + _value = RouteInformation(uri: uri, state: null); + _valueInEngine = OctopusInformationProvider.kEmptyRouteInformation; + notifyListeners(); + } + + @override + Future didPushRouteInformation(RouteInformation routeInformation) { + assert( + hasListeners, + 'A OctopusInformationProvider must have ' + 'at least one listener before it can be used.'); + pushRoute(routeInformation); + return SynchronousFuture(true); + } + + @override + @Deprecated('Use didPushRouteInformation instead') + Future didPushRoute(String route) { + assert( + hasListeners, + 'A OctopusInformationProvider must have ' + 'at least one listener before it can be used.'); + pushRoute(RouteInformation(uri: Uri.tryParse(route))); + return SynchronousFuture(true); + } + + @override + Future didPopRoute() => Future.value(false); +} diff --git a/lib/src/controller/navigator/controller.dart b/lib/src/controller/navigator/controller.dart index eabe431..a149634 100644 --- a/lib/src/controller/navigator/controller.dart +++ b/lib/src/controller/navigator/controller.dart @@ -58,7 +58,7 @@ final class Octopus$NavigatorImpl implements Octopus { list.map((e) => e.name).toSet().length == list.length, 'Routes list should not contain duplicate names', ); - final routeInformationProvider = OctopusInformationProvider(); + final routeInformationProvider = OctopusInformationProvider.platform(); final backButtonDispatcher = RootBackButtonDispatcher(); final routeInformationParser = OctopusInformationParser(codec: codec); final routesTable = Map.unmodifiable( diff --git a/lib/src/util/platform/system_navigator_util_js.dart b/lib/src/util/platform/system_navigator_util_js.dart index 0b47a53..d8f11bc 100644 --- a/lib/src/util/platform/system_navigator_util_js.dart +++ b/lib/src/util/platform/system_navigator_util_js.dart @@ -9,8 +9,8 @@ import 'package:octopus/src/util/logs.dart'; @internal void $pushState(Object? data, String? title, Uri? url) { fine('pushState($url)'); - //SystemNavigator.selectMultiEntryHistory().ignore(); - SystemNavigator.selectSingleEntryHistory().ignore(); + SystemNavigator.selectMultiEntryHistory().ignore(); + //SystemNavigator.selectSingleEntryHistory().ignore(); SystemNavigator.routeInformationUpdated( uri: url, state: data, @@ -31,8 +31,8 @@ void $replaceState( Uri? url, ) { fine('replaceState($url)'); - //SystemNavigator.selectMultiEntryHistory().ignore(); - SystemNavigator.selectSingleEntryHistory().ignore(); + SystemNavigator.selectMultiEntryHistory().ignore(); + //SystemNavigator.selectSingleEntryHistory().ignore(); SystemNavigator.routeInformationUpdated( uri: url, state: data,