Skip to content

Instantly share code, notes, and snippets.

@esDotDev
Last active April 12, 2021 06:15
Show Gist options
  • Save esDotDev/09b0cb9fe2604c44b1d5a642d5a9ac29 to your computer and use it in GitHub Desktop.
Save esDotDev/09b0cb9fe2604c44b1d5a642d5a9ac29 to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';
import 'package:path_to_regexp/path_to_regexp.dart';
class PathStackEntry {
PathStackEntry({required this.path, required this.builder, this.maintainState = true, this.aliases});
final String path;
// This wants the query string values
final WidgetBuilder builder;
final List<String>? aliases;
final bool maintainState;
// Paths ending with a / are assumed to allow prefixes on the end
bool get allowSuffix => path.endsWith("/");
}
class PathStack extends StatefulWidget {
static const kDefaultDuration = Duration(milliseconds: 250);
final String path;
final String parentPath;
final List<PathStackEntry> entries;
final bool caseSensitive;
final Widget Function(BuildContext context, Widget child)? childBuilder;
final Widget Function(BuildContext context)? unknownPathBuilder;
final Widget Function(BuildContext context, Widget child, AnimationController animation)? transitionBuilder;
final Duration transitionDuration;
const PathStack({
Key? key,
required this.path,
required this.entries,
this.parentPath = "",
this.caseSensitive = false,
this.childBuilder,
this.transitionBuilder,
this.transitionDuration = kDefaultDuration,
this.unknownPathBuilder,
}) : super(key: key);
@override
PathStackState createState() => PathStackState();
}
class PathStackState extends State<PathStack> with SingleTickerProviderStateMixin {
Map<String, Widget> _knownRoutes = {};
String? _previousPath;
late AnimationController transitionInAnim = AnimationController(vsync: this, duration: widget.transitionDuration)
..forward();
@override
Widget build(BuildContext context) {
// Try and find a known route for the current path
PathStackEntry? matchingRoute =
List<PathStackEntry?>.from(widget.entries).firstWhere(checkEntryMatchesPath, orElse: () => null);
// Fall back to first page in the route stack, TODO: Add a emptyRouteBuilder?
if (matchingRoute == null) {
print("WARNING: Unable to find a route for ${widget.path}");
matchingRoute = PathStackEntry(
path: "404",
builder: (c) {
return widget.unknownPathBuilder?.call(c) ?? Center(child: Text("Page Not Found"));
});
}
// Use a UniqueKey if maintainState=false
Key pageKey = matchingRoute.maintainState ? ValueKey(matchingRoute.path) : UniqueKey();
// Add the new route to our list of known routes
_knownRoutes[matchingRoute.path] = _KeyedWidget(key: pageKey, child: matchingRoute.builder.call(context));
// Get all known children which we'll pass to the indexedStack
List<Widget> children = _knownRoutes.values.toList();
// Finds the index of the matching route, which we'll also pass to indexedStack
int index = _knownRoutes.keys.toList().indexWhere((r) => r == matchingRoute!.path);
// Remove non-persistent pages from the stack so they are not retained on next build
if (matchingRoute.maintainState == false) {
_knownRoutes.remove(matchingRoute.path);
}
Widget content = IndexedStack(index: index, children: children);
if (_previousPath != matchingRoute.path) {
transitionInAnim.forward(from: 0);
}
_previousPath = matchingRoute.path;
content = widget.transitionBuilder?.call(context, content, transitionInAnim) ?? content;
content = widget.childBuilder?.call(context, content) ?? content;
return content;
}
void clearState() => _knownRoutes.clear();
@override
void dispose() {
clearState();
super.dispose();
}
bool checkEntryMatchesPath(PathStackEntry? entry) {
List<String> allPaths = List.from([entry!.path, ...(entry.aliases ?? [])]);
for (var i = 0; i < allPaths.length; i++) {
final regExp = pathToRegExp('${widget.parentPath}${allPaths[i]}', prefix: entry.allowSuffix);
if (regExp.hasMatch("${widget.path}")) return true;
}
return false;
}
}
class _KeyedWidget extends StatelessWidget {
const _KeyedWidget({Key? key, required this.child}) : super(key: key);
final Widget child;
@override
Widget build(BuildContext context) => child;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment