router.dart 6.4 KB

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