wip. initial commit

This commit is contained in:
Luke 2017-04-25 00:24:14 -07:00
commit cbdda22e98
8 changed files with 316 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.idea
.packages
pubspec.lock

30
LICENSE Normal file
View File

@ -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.

1
README.md Normal file
View File

@ -0,0 +1 @@
# Router

9
lib/router.dart Normal file
View File

@ -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';

14
lib/src/common.dart Normal file
View File

@ -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);
}

50
lib/src/router.dart Normal file
View File

@ -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();
}
}

197
lib/src/tree.dart Normal file
View File

@ -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(":");
}
}

12
pubspec.yaml Normal file
View File

@ -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