Bladeren bron

wip. initial commit

Luke 8 jaren geleden
commit
cbdda22e98
8 gewijzigde bestanden met toevoegingen van 316 en 0 verwijderingen
  1. 3 0
      .gitignore
  2. 30 0
      LICENSE
  3. 1 0
      README.md
  4. 9 0
      lib/router.dart
  5. 14 0
      lib/src/common.dart
  6. 50 0
      lib/src/router.dart
  7. 197 0
      lib/src/tree.dart
  8. 12 0
      pubspec.yaml

+ 3 - 0
.gitignore

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

+ 30 - 0
LICENSE

@@ -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 - 0
README.md

@@ -0,0 +1 @@
+# Router

+ 9 - 0
lib/router.dart

@@ -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 - 0
lib/src/common.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);
+}

+ 50 - 0
lib/src/router.dart

@@ -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 - 0
lib/src/tree.dart

@@ -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 - 0
pubspec.yaml

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