Skip to content

Commit

Permalink
Add _buildPages method
Browse files Browse the repository at this point in the history
  • Loading branch information
PlugFox committed Nov 19, 2023
1 parent 06623c8 commit 52e09e4
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 37 deletions.
55 changes: 52 additions & 3 deletions lib/src/controller/delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ final class OctopusDelegate extends RouterDelegate<OctopusState>
/// {@nodoc}
OctopusDelegate({
required OctopusState initialState,
required List<OctopusRoute> routes,
String? restorationScopeId = 'octopus',
List<NavigatorObserver>? observers,
TransitionDelegate<Object?>? transitionDelegate,
RouteFactory? notFound,
void Function(Object error, StackTrace stackTrace)? onError,
}) : _restorationScopeId = restorationScopeId,
}) : _routes = <String, OctopusRoute>{
for (final route in routes) route.name: route,
},
_restorationScopeId = restorationScopeId,
_observers = observers,
_transitionDelegate =
transitionDelegate ?? const DefaultTransitionDelegate<Object?>(),
Expand All @@ -46,6 +50,9 @@ final class OctopusDelegate extends RouterDelegate<OctopusState>
/// Current octopus instance.
late Octopus _controller;

/// Routes hash table.
final Map<String, OctopusRoute> _routes;

@internal
set $controller(Octopus controller) => _controller = controller;

Expand Down Expand Up @@ -92,14 +99,56 @@ final class OctopusDelegate extends RouterDelegate<OctopusState>
bool _onPopPage(Route<Object?> route, Object? result) => _handleErrors(
() {
if (!route.didPop(result)) return false;
final popped = value.maybePop();
final state = value.copy();
final popped = state.maybePop();
if (popped == null) return false;
setNewRoutePath(popped);
setNewRoutePath(state);
return true;
},
(_, __) => false,
);

List<Page<Object?>> _buildPages(BuildContext context) => _handleErrors(() {
final pages = <Page<Object?>>[];
for (final node in value.children) {
final route = _routes[node.name];
assert(route != null, 'Route ${node.name} not found');
if (route == null) continue;
final page = route.pageBuilder(context, node);
pages.add(page);
}
if (pages.isNotEmpty) return pages;
throw FlutterError('The Navigator.pages must not be empty to use the '
'Navigator.pages API');
}, (error, stackTrace) {
final flutterError = switch (error) {
FlutterError error => error,
String message => FlutterError(message),
_ => FlutterError.fromParts(
<DiagnosticsNode>[
ErrorSummary('Failed to build pages'),
ErrorDescription(Error.safeToString(error)),
],
),
};
return <Page<Object?>>[
MaterialPage(
child: Scaffold(
body: SafeArea(
child: ErrorWidget.withDetails(
message: 'Failed to build pages',
error: flutterError,
),
),
),
arguments: <String, Object?>{
'error': Error.safeToString(error),
'stack': stackTrace.toString(),
},
),
];
});

@override
Future<bool> popRoute() => _handleErrors(() {
final nav = navigator;
Expand Down
5 changes: 5 additions & 0 deletions lib/src/controller/octopus.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ final class _OctopusImpl extends Octopus
onError?.call(error, StackTrace.current);
throw error;
}
assert(
list.map<String>((e) => e.name).toSet().length == list.length,
'Routes list should not contain duplicate names',
);
final routeInformationProvider = OctopusInformationProvider();
final backButtonDispatcher = RootBackButtonDispatcher();
final routeInformationParser = OctopusInformationParser();
Expand All @@ -72,6 +76,7 @@ final class _OctopusImpl extends Octopus
children: <OctopusNode>[defaultRoute.node()],
arguments: <String, String>{},
),
routes: list,
restorationScopeId: restorationScopeId,
observers: observers,
transitionDelegate: transitionDelegate,
Expand Down
34 changes: 26 additions & 8 deletions lib/src/state/state.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'dart:collection';

import 'package:flutter/material.dart' show MaterialPage;
import 'package:flutter/widgets.dart';
import 'package:octopus/src/utils/state_util.dart';
import 'package:octopus/src/widget/route_context.dart';

/// Signature for the callback to [OctopusNode.visitChildNodes].
///
Expand Down Expand Up @@ -117,18 +117,36 @@ abstract class OctopusRoute {
/// e.g. my-page
abstract final String name;

/// Build [Widget] for this route using [OctopusRouteContext].
/// Use [OctopusRouteContext] to access current route information,
/// Build [Widget] for this route using [BuildContext] and [OctopusNode].
///
/// Use [OctopusNode] to access current route information,
/// arguments and its children.
///
/// e.g.
/// ```dart
/// context.node;
/// context.name;
/// context.arguments;
/// context.children;
/// final OctopusNode(:name, :arguments, :children) = node;
/// ```
Page<Object?> builder(OctopusRouteContext context);
Widget builder(BuildContext context, OctopusNode node);

/// Build [Page] for this route using [BuildContext] and [OctopusNode].
/// [BuildContext] - Navigator context.
/// [OctopusNode] - Current node of the router state tree.
Page<Object?> pageBuilder(BuildContext context, OctopusNode node) {
final OctopusNode(:name, arguments: args) = node;
final key = ValueKey<String>(
args.isEmpty
? name
: '$name'
'#'
'${args.entries.map((e) => '${e.key}=${e.value}').join(';')}',
);
return MaterialPage<Object?>(
key: key,
child: builder(context, node),
name: name,
arguments: args,
);
}

/// Construct [OctopusNode] for this route.
OctopusNode node({
Expand Down
58 changes: 32 additions & 26 deletions lib/src/utils/state_util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,17 @@ abstract final class StateUtil {
final segments = <String>[];
void encodeNode(OctopusNode node, int depth) {
final prefix = '.' * depth;
final args = node.arguments.entries
.map<String>((e) =>
'${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}')
.join('&');
final name = args.isEmpty ? node.name : '${node.name}~$args';
final String name;
if (node.arguments.isEmpty) {
name = node.name;
} else {
final args = (node.arguments.entries.toList(growable: false)
..sort((a, b) => a.key.compareTo(b.key)))
.map<String>((e) =>
'${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}')
.join('&');
name = args.isEmpty ? node.name : '${node.name}~$args';
}

segments.add('$prefix$name');

Expand All @@ -76,25 +82,15 @@ abstract final class StateUtil {
/// Convert location string to tree components.
/// {@nodoc}
@internal
static OctopusState decodeLocation(String location) {
final arguments = <String, String>{};
final segments =
location.replaceAll('\n', '').replaceAll(r'\', '/').trim().split('/');
if (segments.isEmpty) {
return OctopusState(
children: <OctopusNode>[],
arguments: arguments,
);
}

return OctopusState(
children: _parseSegments(segments, 0).toList(),
arguments: arguments,
);
}
static OctopusState decodeLocation(String location) =>
stateFromUri(Uri.parse(location));

static OctopusState stateFromUri(Uri uri) {
final arguments = uri.queryParameters;
final queryParameters = uri.queryParameters.entries.toList(growable: false)
..sort((a, b) => a.key.compareTo(b.key));
final arguments = <String, String>{
for (final entry in queryParameters) entry.key: entry.value
};
final segments = uri.pathSegments;
if (segments.isEmpty) {
return OctopusState(
Expand Down Expand Up @@ -170,13 +166,23 @@ abstract final class StateUtil {
segment = segment.substring(currentDepth);
final delimiter = segment.indexOf('~');
final name = delimiter == -1 ? segment : segment.substring(0, delimiter);
final args = delimiter == -1
? <String, String>{}
: Uri.splitQueryString(segment.substring(delimiter + 1));
final Map<String, String> arguments;
if (delimiter == -1) {
arguments = <String, String>{};
} else {
final queryParameters =
Uri.splitQueryString(segment.substring(delimiter + 1))
.entries
.toList(growable: false)
..sort((a, b) => a.key.compareTo(b.key));
arguments = <String, String>{
for (final entry in queryParameters) entry.key: entry.value
};
}
var children = currentDepth < segment.length - 1
? _parseSegments(segments, currentDepth + 1).toList()
: <OctopusNode>[];
yield OctopusNode(name: name, arguments: args, children: children);
yield OctopusNode(name: name, arguments: arguments, children: children);
}
}
}

0 comments on commit 52e09e4

Please sign in to comment.