wip. initial commit
This commit is contained in:
commit
cbdda22e98
|
@ -0,0 +1,3 @@
|
|||
.idea
|
||||
.packages
|
||||
pubspec.lock
|
|
@ -0,0 +1,30 @@
|
|||
Router
|
||||
|
||||
Created by Posse in NYC
|
||||
http://goposse.com
|
||||
|
||||
Copyright (c) 2017 Posse Productions LLC.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of the Posse Productions LLC, Posse nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL POSSE PRODUCTIONS LLC (POSSE) BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,9 @@
|
|||
library router;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/src/material/page.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
part 'src/common.dart';
|
||||
part 'src/router.dart';
|
||||
part 'src/tree.dart';
|
|
@ -0,0 +1,14 @@
|
|||
part of router;
|
||||
|
||||
///
|
||||
typedef Route<Null> RouteCreator(RouteSettings route, Map<String, String> parameters);
|
||||
|
||||
///
|
||||
typedef Widget RouteHandler(Map<String, String> parameters);
|
||||
|
||||
///
|
||||
class AppRoute {
|
||||
String route;
|
||||
RouteCreator routeCreator;
|
||||
AppRoute(this.route, this.routeCreator);
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
part of router;
|
||||
|
||||
class Router {
|
||||
/// The tree structure that stores the defined routes
|
||||
RouteTree _routeTree = new RouteTree();
|
||||
|
||||
/// Generic handler for when a route has not been defined
|
||||
AppRoute notFoundRoute;
|
||||
|
||||
/// Creates a custom [Route] definition
|
||||
void defineRoute<T extends Route<Null>>(String routePath, {@required RouteCreator creator}) {
|
||||
_routeTree.addRoute(new AppRoute(routePath, creator));
|
||||
}
|
||||
|
||||
/// Creates a [MaterialPageRoute] definition
|
||||
void defineMaterialRoute(String routePath, {@required RouteHandler handler}) {
|
||||
RouteCreator creator = (RouteSettings routeSettings, Map<String, String> params) {
|
||||
return new MaterialPageRoute<Null>(settings: routeSettings, builder: (BuildContext context) {
|
||||
return handler(params);
|
||||
});
|
||||
};
|
||||
_routeTree.addRoute(new AppRoute(routePath, creator));
|
||||
}
|
||||
|
||||
void addRoute(AppRoute route) {
|
||||
_routeTree.addRoute(route);
|
||||
}
|
||||
|
||||
AppRoute match(String path) {
|
||||
AppRouteMatch match = _routeTree.matchRoute(path);
|
||||
return match?.route ?? notFoundRoute;
|
||||
}
|
||||
|
||||
/// used by the [MaterialApp.onGenerateRoute] function as callback to
|
||||
/// create a route that is able to be consumed.
|
||||
Route<Null> generator(RouteSettings routeSettings) {
|
||||
AppRouteMatch match = _routeTree.matchRoute(routeSettings.name);
|
||||
AppRoute route = match?.route ?? notFoundRoute;
|
||||
if (route == null) {
|
||||
return null;
|
||||
}
|
||||
Map<String, String> parameters = match?.parameters ?? <String, String>{};
|
||||
return route.routeCreator(routeSettings, parameters);
|
||||
}
|
||||
|
||||
/// Prints the route tree so you can analyze it.
|
||||
void printTree() {
|
||||
_routeTree.printTree();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
part of router;
|
||||
|
||||
enum RouteTreeNodeType {
|
||||
component,
|
||||
parameter,
|
||||
}
|
||||
|
||||
class AppRouteMatch {
|
||||
// constructors
|
||||
AppRouteMatch(this.route);
|
||||
|
||||
// properties
|
||||
AppRoute route;
|
||||
Map<String, String> parameters = <String, String>{};
|
||||
}
|
||||
|
||||
class RouteTreeNodeMatch {
|
||||
// constructors
|
||||
RouteTreeNodeMatch(this.node);
|
||||
|
||||
RouteTreeNodeMatch.fromMatch(RouteTreeNodeMatch match, this.node) {
|
||||
parameters = <String, String>{};
|
||||
if (match != null) {
|
||||
parameters.addAll(match.parameters);
|
||||
}
|
||||
}
|
||||
|
||||
// properties
|
||||
RouteTreeNode node;
|
||||
Map<String, String> parameters = <String, String>{};
|
||||
}
|
||||
|
||||
class RouteTreeNode {
|
||||
// constructors
|
||||
RouteTreeNode(this.part,
|
||||
this.type);
|
||||
|
||||
// properties
|
||||
String part;
|
||||
RouteTreeNodeType type;
|
||||
List<AppRoute> routes = <AppRoute>[];
|
||||
List<RouteTreeNode> nodes = <RouteTreeNode>[];
|
||||
RouteTreeNode parent;
|
||||
|
||||
bool isParameter() {
|
||||
return type == RouteTreeNodeType.parameter;
|
||||
}
|
||||
}
|
||||
|
||||
class RouteTree {
|
||||
// private
|
||||
List<RouteTreeNode> _nodes = <RouteTreeNode>[];
|
||||
bool _hasDefaultRoute = false;
|
||||
|
||||
// addRoute - add a route to the route tree
|
||||
void addRoute(AppRoute route) {
|
||||
String path = route.route;
|
||||
// is root/default route, just add it
|
||||
if (path == Navigator.defaultRouteName) {
|
||||
if (_hasDefaultRoute) {
|
||||
// throw an error because the internal consistency of the router
|
||||
// could be affected
|
||||
throw ("Default route was already defined");
|
||||
}
|
||||
var node = new RouteTreeNode(path, RouteTreeNodeType.component);
|
||||
node.routes = [route];
|
||||
_nodes.add(node);
|
||||
_hasDefaultRoute = true;
|
||||
return;
|
||||
}
|
||||
if (path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
List<String> pathComponents = path.split('/');
|
||||
RouteTreeNode parent;
|
||||
for (int i = 0; i < pathComponents.length; i++) {
|
||||
String component = pathComponents[i];
|
||||
RouteTreeNode node = _nodeForComponent(component, parent);
|
||||
if (node == null) {
|
||||
RouteTreeNodeType type = _typeForComponent(component);
|
||||
node = new RouteTreeNode(component, type);
|
||||
node.parent = parent;
|
||||
if (parent == null) {
|
||||
_nodes.add(node);
|
||||
} else {
|
||||
parent.nodes.add(node);
|
||||
}
|
||||
}
|
||||
if (i == pathComponents.length - 1) {
|
||||
if (node.routes == null) {
|
||||
node.routes = [route];
|
||||
} else {
|
||||
node.routes.add(route);
|
||||
}
|
||||
}
|
||||
parent = node;
|
||||
}
|
||||
}
|
||||
|
||||
AppRouteMatch matchRoute(String path) {
|
||||
String usePath = path;
|
||||
if (usePath.startsWith("/")) {
|
||||
usePath = path.substring(1);
|
||||
}
|
||||
List<String> components = usePath.split("/");
|
||||
if (path == Navigator.defaultRouteName) {
|
||||
components = ["/"];
|
||||
}
|
||||
|
||||
Map<RouteTreeNode, RouteTreeNodeMatch> nodeMatches = <RouteTreeNode, RouteTreeNodeMatch>{};
|
||||
List<RouteTreeNode> nodesToCheck = _nodes;
|
||||
for (String checkComponent in components) {
|
||||
Map<RouteTreeNode, RouteTreeNodeMatch> currentMatches = <RouteTreeNode, RouteTreeNodeMatch>{};
|
||||
List<RouteTreeNode> nextNodes = <RouteTreeNode>[];
|
||||
for (RouteTreeNode node in nodesToCheck) {
|
||||
bool isMatch = (node.part == checkComponent || node.isParameter());
|
||||
if (isMatch) {
|
||||
RouteTreeNodeMatch parentMatch = nodeMatches[node.parent];
|
||||
// print("pm: ${parentMatch?.node?.part}, ${parentMatch?.parameters}");
|
||||
RouteTreeNodeMatch match = new RouteTreeNodeMatch.fromMatch(parentMatch, node);
|
||||
if (node.isParameter()) {
|
||||
String paramKey = node.part.substring(1);
|
||||
match.parameters[paramKey] = checkComponent;
|
||||
}
|
||||
// print("matched: ${node.part}, isParam: ${node.isParameter()}, params: ${match.parameters}");
|
||||
currentMatches[node] = match;
|
||||
if (node.nodes != null) {
|
||||
nextNodes.addAll(node.nodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
nodeMatches = currentMatches;
|
||||
nodesToCheck = nextNodes;
|
||||
if (currentMatches.values.length == 0) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
List<RouteTreeNodeMatch> matches = nodeMatches.values.toList();
|
||||
if (matches.length > 0) {
|
||||
RouteTreeNodeMatch match = matches.first;
|
||||
RouteTreeNode nodeToUse = match.node;
|
||||
// print("using match: ${match}, ${nodeToUse?.part}, ${match?.parameters}");
|
||||
if (nodeToUse != null && nodeToUse.routes != null && nodeToUse.routes.length > 0) {
|
||||
List<AppRoute> routes = nodeToUse.routes;
|
||||
AppRouteMatch routeMatch = new AppRouteMatch(routes[0]);
|
||||
routeMatch.parameters = match.parameters;
|
||||
return routeMatch;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void printTree() {
|
||||
_printSubTree();
|
||||
}
|
||||
|
||||
void _printSubTree({RouteTreeNode parent, int level = 0}) {
|
||||
List<RouteTreeNode> nodes = parent != null ? parent.nodes : _nodes;
|
||||
for (RouteTreeNode node in nodes) {
|
||||
String indent = "";
|
||||
for (int i = 0; i < level; i++) {
|
||||
indent += " ";
|
||||
}
|
||||
print("$indent${node.part}: total routes=${node.routes.length}");
|
||||
if (node.nodes != null && node.nodes.length > 0) {
|
||||
_printSubTree(parent: node, level: level + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RouteTreeNode _nodeForComponent(String component, RouteTreeNode parent) {
|
||||
List<RouteTreeNode> nodes = _nodes;
|
||||
if (parent != null) {
|
||||
// search parent for sub-node matches
|
||||
nodes = parent.nodes;
|
||||
}
|
||||
for (RouteTreeNode node in nodes) {
|
||||
if (node.part == component) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
RouteTreeNodeType _typeForComponent(String component) {
|
||||
RouteTreeNodeType type = RouteTreeNodeType.component;
|
||||
if (_isParameterComponent(component)) {
|
||||
type = RouteTreeNodeType.parameter;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/// Is the path component a parameter
|
||||
bool _isParameterComponent(String component) {
|
||||
return component.startsWith(":");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
name: router
|
||||
description: Flexible routing for Flutter.
|
||||
version: 0.0.1
|
||||
author: Posse Productions LLC
|
||||
homepage: http://goposse.com
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
flutter:
|
||||
uses-material-design: false
|
Loading…
Reference in New Issue