fluro_router.dart 8.5 KB

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