Skip to content

Instantly share code, notes, and snippets.

@ndugger
Created August 29, 2022 01:22
Show Gist options
  • Save ndugger/c1959c7aa7475320917d2b53f0cb54ae to your computer and use it in GitHub Desktop.
Save ndugger/c1959c7aa7475320917d2b53f0cb54ae to your computer and use it in GitHub Desktop.
import 'package:flutter/widgets.dart';
import '../utilities/navigation.dart';
typedef PathMiddleware = Future<bool> Function(BuildContext);
class Path {
final String name;
final WidgetBuilder builder;
final List<PathMiddleware> middleware;
Path({
required this.name,
required this.builder,
this.middleware = const []
});
}
class MissingPath extends Path {
MissingPath({
required super.builder
}): super(
name: '/',
);
}
class RestrictedPath extends Path {
RestrictedPath({
required super.builder
}): super(
name: '/',
);
}
class ActivePath {
final Path definition;
final Map<String, String> arguments;
ActivePath({
required this.definition,
required this.arguments
});
}
class PathRouter {
final registeredRoutes = <Path>[];
var initialPathName = '/';
var argumentMatcher = RegExp(r'\{(.*)\}');
var pathSeparator = '/';
bool containsPath(String path) {
final pathSegments = Uri.parse(path).normalizePath().toString().split(pathSeparator);
return registeredRoutes.any((routeDefinition) {
final definitionPathSegments = routeDefinition.name.split(pathSeparator);
if (pathSegments.length == definitionPathSegments.length) {
var segmentIndex = -1;
return pathSegments.every((segment) {
++segmentIndex;
if (argumentMatcher.hasMatch(segment) && argumentMatcher.hasMatch(definitionPathSegments.elementAt(segmentIndex))) {
return true;
}
else if (segment == definitionPathSegments.elementAt(segmentIndex)) {
return true;
}
return false;
});
}
return false;
});
}
void register(Path route) {
if (containsPath(route.name)) {
throw Exception('Path has already been registered: ${route.name}');
}
registeredRoutes.add(route);
}
ActivePath? matchPath(String path) {
final normalizedPath = Uri.parse(path).normalizePath().toString();
if (registeredRoutes.any((routeDefinition) => routeDefinition.name == normalizedPath)) {
return ActivePath(
definition: registeredRoutes.firstWhere((routeDefinition) => routeDefinition.name == normalizedPath),
arguments: {}
);
}
try {
final Map<String, String> routeArguments = {};
final registeredRoute = registeredRoutes.firstWhere((registeredPath) {
final pathSegments = normalizedPath.split(pathSeparator);
final registeredPathSegments = registeredPath.name.split(pathSeparator);
if (pathSegments.length != registeredPathSegments.length) {
return false;
}
var segmentIndex = -1;
return registeredPathSegments.every((segment) {
++segmentIndex;
if (argumentMatcher.hasMatch(segment)) {
final argument = argumentMatcher.firstMatch(segment)!.group(1)!;
routeArguments[argument] = pathSegments.elementAt(segmentIndex);
return true;
}
else if (segment == pathSegments.elementAt(segmentIndex)) {
return true;
}
routeArguments.clear();
return false;
});
}, orElse: () => MissingPath(
builder: (context) {
return SizedBox();
}
));
return ActivePath(
definition: registeredRoute,
arguments: routeArguments
);
} catch (_) {
return null;
}
}
}
class InheritedPathfinder extends InheritedNotifier<NavigationController> {
final PathRouter router;
static InheritedPathfinder of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<InheritedPathfinder>()!;
}
InheritedPathfinder({
required this.router,
super.key,
}) : super(
notifier: globalNavigationController,
child: Builder(
builder: (BuildContext context) {
final pathfinder = Pathfinder.of(context);
final activePath = pathfinder.activePath;
if (activePath == null) {
return SizedBox();
} else {
return activePath.definition.builder(context);
}
}
)
);
ActivePath? get activePath {
return router.matchPath(notifier!.pathName);
}
void navigate(String pathName, { bool replace = false }) {
notifier!.navigate(pathName, replace: replace);
}
}
class Pathfinder extends StatefulWidget {
final List<Path> paths;
final String initialPathName;
final String pattern;
final String delimiter;
const Pathfinder({
required this.paths,
this.initialPathName = '/',
this.pattern = r'\{(.*)\}',
this.delimiter = '/',
Key? key
}) : super(key: key);
static InheritedPathfinder of(BuildContext context) {
return InheritedPathfinder.of(context);
}
@override
PathfinderState createState() {
return PathfinderState();
}
}
class PathfinderState extends State<Pathfinder> {
final router = PathRouter();
@override
void initState() {
super.initState();
for (final route in widget.paths) {
router.register(route);
}
router.initialPathName = widget.initialPathName;
router.pathSeparator = widget.delimiter;
router.argumentMatcher = RegExp(widget.pattern);
}
@override
Widget build(BuildContext context) {
return InheritedPathfinder(router: router);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment