Last active
April 12, 2021 06:15
-
-
Save esDotDev/09b0cb9fe2604c44b1d5a642d5a9ac29 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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