router.dart 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /*
  2. * fluro
  3. * A Posse Production
  4. * http://goposse.com
  5. * Copyright (c) 2018 Posse Productions LLC. All rights reserved.
  6. * See LICENSE for distribution and usage details.
  7. */
  8. import 'dart:async';
  9. import 'package:fluro/fluro.dart';
  10. import 'package:fluro/src/common.dart';
  11. import 'package:flutter/material.dart';
  12. enum TransitionType {
  13. native,
  14. nativeModal,
  15. inFromLeft,
  16. inFromRight,
  17. inFromBottom,
  18. fadeIn,
  19. custom, // if using custom then you must also provide a transition
  20. }
  21. class Router {
  22. static final appRouter = new Router();
  23. /// The tree structure that stores the defined routes
  24. final RouteTree _routeTree = new RouteTree();
  25. /// Generic handler for when a route has not been defined
  26. Handler notFoundHandler;
  27. /// Creates a [PageRoute] definition for the passed [RouteHandler]. You can optionally provide a custom
  28. /// transition builder for the route.
  29. void define(String routePath, {@required Handler handler}) {
  30. _routeTree.addRoute(new AppRoute(routePath, handler));
  31. }
  32. /// Finds a defined [AppRoute] for the path value. If no [AppRoute] definition was found
  33. /// then function will return null.
  34. AppRouteMatch match(String path) {
  35. return _routeTree.matchRoute(path);
  36. }
  37. bool pop(BuildContext context) => Navigator.pop(context);
  38. ///
  39. Future navigateTo(BuildContext context, String path,
  40. {bool replace = false,
  41. bool clearStack = false,
  42. TransitionType transition = TransitionType.native,
  43. Duration transitionDuration = const Duration(milliseconds: 250),
  44. RouteTransitionsBuilder transitionBuilder}) {
  45. RouteMatch routeMatch = matchRoute(context, path,
  46. transitionType: transition,
  47. transitionsBuilder: transitionBuilder,
  48. transitionDuration: transitionDuration);
  49. Route<dynamic> route = routeMatch.route;
  50. Completer completer = new Completer();
  51. Future future = completer.future;
  52. if (routeMatch.matchType == RouteMatchType.nonVisual) {
  53. completer.complete("Non visual route type.");
  54. } else {
  55. if (route == null && notFoundHandler != null) {
  56. route = _notFoundRoute(context, path);
  57. }
  58. if (route != null) {
  59. if (clearStack) {
  60. future =
  61. Navigator.pushAndRemoveUntil(context, route, (check) => false);
  62. } else {
  63. future = replace
  64. ? Navigator.pushReplacement(context, route)
  65. : Navigator.push(context, route);
  66. }
  67. completer.complete();
  68. } else {
  69. String error = "No registered route was found to handle '$path'.";
  70. print(error);
  71. completer.completeError(error);
  72. }
  73. }
  74. return future;
  75. }
  76. ///
  77. Route<Null> _notFoundRoute(BuildContext context, String path) {
  78. RouteCreator<Null> creator =
  79. (RouteSettings routeSettings, Map<String, List<String>> parameters) {
  80. return new MaterialPageRoute<Null>(
  81. settings: routeSettings,
  82. builder: (BuildContext context) {
  83. return notFoundHandler.handlerFunc(context, parameters);
  84. });
  85. };
  86. return creator(new RouteSettings(name: path), null);
  87. }
  88. ///
  89. RouteMatch matchRoute(BuildContext buildContext, String path,
  90. {RouteSettings routeSettings,
  91. TransitionType transitionType,
  92. Duration transitionDuration = const Duration(milliseconds: 250),
  93. RouteTransitionsBuilder transitionsBuilder}) {
  94. RouteSettings settingsToUse = routeSettings;
  95. if (routeSettings == null) {
  96. settingsToUse = new RouteSettings(name: path);
  97. }
  98. AppRouteMatch match = _routeTree.matchRoute(path);
  99. AppRoute route = match?.route;
  100. Handler handler = (route != null ? route.handler : notFoundHandler);
  101. if (route == null && notFoundHandler == null) {
  102. return new RouteMatch(
  103. matchType: RouteMatchType.noMatch,
  104. errorMessage: "No matching route was found");
  105. }
  106. Map<String, List<String>> parameters =
  107. match?.parameters ?? <String, List<String>>{};
  108. if (handler.type == HandlerType.function) {
  109. handler.handlerFunc(buildContext, parameters);
  110. return new RouteMatch(matchType: RouteMatchType.nonVisual);
  111. }
  112. RouteCreator creator =
  113. (RouteSettings routeSettings, Map<String, List<String>> parameters) {
  114. bool isNativeTransition = (transitionType == TransitionType.native ||
  115. transitionType == TransitionType.nativeModal);
  116. if (isNativeTransition) {
  117. return new MaterialPageRoute<dynamic>(
  118. settings: routeSettings,
  119. fullscreenDialog: transitionType == TransitionType.nativeModal,
  120. builder: (BuildContext context) {
  121. return handler.handlerFunc(context, parameters);
  122. });
  123. } else {
  124. var routeTransitionsBuilder;
  125. if (transitionType == TransitionType.custom) {
  126. routeTransitionsBuilder = transitionsBuilder;
  127. } else {
  128. routeTransitionsBuilder = _standardTransitionsBuilder(transitionType);
  129. }
  130. return new PageRouteBuilder<dynamic>(
  131. settings: routeSettings,
  132. pageBuilder: (BuildContext context, Animation<double> animation,
  133. Animation<double> secondaryAnimation) {
  134. return handler.handlerFunc(context, parameters);
  135. },
  136. transitionDuration: transitionDuration,
  137. transitionsBuilder: routeTransitionsBuilder,
  138. );
  139. }
  140. };
  141. return new RouteMatch(
  142. matchType: RouteMatchType.visual,
  143. route: creator(settingsToUse, parameters),
  144. );
  145. }
  146. RouteTransitionsBuilder _standardTransitionsBuilder(
  147. TransitionType transitionType) {
  148. return (BuildContext context, Animation<double> animation,
  149. Animation<double> secondaryAnimation, Widget child) {
  150. if (transitionType == TransitionType.fadeIn) {
  151. return new FadeTransition(opacity: animation, child: child);
  152. } else {
  153. const Offset topLeft = const Offset(0.0, 0.0);
  154. const Offset topRight = const Offset(1.0, 0.0);
  155. const Offset bottomLeft = const Offset(0.0, 1.0);
  156. Offset startOffset = bottomLeft;
  157. Offset endOffset = topLeft;
  158. if (transitionType == TransitionType.inFromLeft) {
  159. startOffset = const Offset(-1.0, 0.0);
  160. endOffset = topLeft;
  161. } else if (transitionType == TransitionType.inFromRight) {
  162. startOffset = topRight;
  163. endOffset = topLeft;
  164. }
  165. return new SlideTransition(
  166. position: new Tween<Offset>(
  167. begin: startOffset,
  168. end: endOffset,
  169. ).animate(animation),
  170. child: child,
  171. );
  172. }
  173. };
  174. }
  175. /// Route generation method. This function can be used as a way to create routes on-the-fly
  176. /// if any defined handler is found. It can also be used with the [MaterialApp.onGenerateRoute]
  177. /// property as callback to create routes that can be used with the [Navigator] class.
  178. Route<dynamic> generator(RouteSettings routeSettings) {
  179. RouteMatch match =
  180. matchRoute(null, routeSettings.name, routeSettings: routeSettings);
  181. return match.route;
  182. }
  183. /// Prints the route tree so you can analyze it.
  184. void printTree() {
  185. _routeTree.printTree();
  186. }
  187. }