fluro_router.dart 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  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,
  24. TransitionType transitionType,
  25. Duration transitionDuration = const Duration(milliseconds: 250),
  26. RouteTransitionsBuilder transitionBuilder}) {
  27. _routeTree.addRoute(
  28. AppRoute(routePath, handler,
  29. transitionType: transitionType,
  30. transitionDuration: transitionDuration,
  31. transitionBuilder: transitionBuilder),
  32. );
  33. }
  34. /// Finds a defined [AppRoute] for the path value. If no [AppRoute] definition was found
  35. /// then function will return null.
  36. AppRouteMatch match(String path) {
  37. return _routeTree.matchRoute(path);
  38. }
  39. void pop<T>(BuildContext context, [T result]) =>
  40. Navigator.of(context).pop(result);
  41. ///
  42. Future navigateTo(BuildContext context, String path,
  43. {bool replace = false,
  44. bool clearStack = false,
  45. bool maintainState = true,
  46. bool rootNavigator = false,
  47. bool nullOk = false,
  48. TransitionType transition,
  49. Duration transitionDuration,
  50. RouteTransitionsBuilder transitionBuilder,
  51. RouteSettings routeSettings}) {
  52. RouteMatch routeMatch = matchRoute(context, path,
  53. transitionType: transition,
  54. transitionsBuilder: transitionBuilder,
  55. transitionDuration: transitionDuration,
  56. maintainState: maintainState,
  57. routeSettings: routeSettings);
  58. Route<dynamic> route = routeMatch.route;
  59. Completer completer = Completer();
  60. Future future = completer.future;
  61. if (routeMatch.matchType == RouteMatchType.nonVisual) {
  62. completer.complete("Non visual route type.");
  63. } else {
  64. if (route == null && notFoundHandler != null) {
  65. route = _notFoundRoute(context, path, maintainState: maintainState);
  66. }
  67. if (route != null) {
  68. final navigator =
  69. Navigator.of(context, rootNavigator: rootNavigator, nullOk: nullOk);
  70. if (clearStack) {
  71. future = navigator.pushAndRemoveUntil(route, (check) => false);
  72. } else {
  73. future = replace
  74. ? navigator.pushReplacement(route)
  75. : navigator.push(route);
  76. }
  77. completer.complete();
  78. } else {
  79. String error = "No registered route was found to handle '$path'.";
  80. print(error);
  81. completer.completeError(RouteNotFoundException(error, path));
  82. }
  83. }
  84. return future;
  85. }
  86. ///
  87. Route<Null> _notFoundRoute(BuildContext context, String path,
  88. {bool maintainState}) {
  89. RouteCreator<Null> creator =
  90. (RouteSettings routeSettings, Map<String, List<String>> parameters) {
  91. return MaterialPageRoute<Null>(
  92. settings: routeSettings,
  93. maintainState: maintainState,
  94. builder: (BuildContext context) {
  95. return notFoundHandler.handlerFunc(context, parameters);
  96. });
  97. };
  98. return creator(RouteSettings(name: path), null);
  99. }
  100. ///
  101. RouteMatch matchRoute(BuildContext buildContext, String path,
  102. {RouteSettings routeSettings,
  103. TransitionType transitionType,
  104. Duration transitionDuration,
  105. RouteTransitionsBuilder transitionsBuilder,
  106. bool maintainState = true}) {
  107. RouteSettings settingsToUse = routeSettings;
  108. if (routeSettings == null) {
  109. settingsToUse = RouteSettings(name: path);
  110. }
  111. if (settingsToUse.name == null) {
  112. settingsToUse = settingsToUse.copyWith(name: path);
  113. }
  114. AppRouteMatch match = _routeTree.matchRoute(path);
  115. AppRoute route = match?.route;
  116. if (route.transitionDuration != null) {
  117. transitionDuration = route.transitionDuration;
  118. }
  119. Handler handler = (route != null ? route.handler : notFoundHandler);
  120. var transition = transitionType;
  121. if (transitionType == null) {
  122. transition = route != null ? route.transitionType : TransitionType.native;
  123. }
  124. if (route == null && notFoundHandler == null) {
  125. return RouteMatch(
  126. matchType: RouteMatchType.noMatch,
  127. errorMessage: "No matching route was found");
  128. }
  129. Map<String, List<String>> parameters =
  130. match?.parameters ?? <String, List<String>>{};
  131. if (handler.type == HandlerType.function) {
  132. handler.handlerFunc(buildContext, parameters);
  133. return RouteMatch(matchType: RouteMatchType.nonVisual);
  134. }
  135. RouteCreator creator =
  136. (RouteSettings routeSettings, Map<String, List<String>> parameters) {
  137. bool isNativeTransition = (transition == TransitionType.native ||
  138. transition == TransitionType.nativeModal);
  139. if (isNativeTransition) {
  140. if (Theme.of(buildContext).platform == TargetPlatform.iOS) {
  141. return CupertinoPageRoute<dynamic>(
  142. settings: routeSettings,
  143. fullscreenDialog: transition == TransitionType.nativeModal,
  144. maintainState: maintainState,
  145. builder: (BuildContext context) {
  146. return handler.handlerFunc(context, parameters);
  147. });
  148. } else {
  149. return MaterialPageRoute<dynamic>(
  150. settings: routeSettings,
  151. fullscreenDialog: transition == TransitionType.nativeModal,
  152. maintainState: maintainState,
  153. builder: (BuildContext context) {
  154. return handler.handlerFunc(context, parameters);
  155. });
  156. }
  157. } else if (transition == TransitionType.material ||
  158. transition == TransitionType.materialFullScreenDialog) {
  159. return MaterialPageRoute<dynamic>(
  160. settings: routeSettings,
  161. fullscreenDialog:
  162. transition == TransitionType.materialFullScreenDialog,
  163. maintainState: maintainState,
  164. builder: (BuildContext context) {
  165. return handler.handlerFunc(context, parameters);
  166. });
  167. } else if (transition == TransitionType.cupertino ||
  168. transition == TransitionType.cupertinoFullScreenDialog) {
  169. return CupertinoPageRoute<dynamic>(
  170. settings: routeSettings,
  171. fullscreenDialog:
  172. transition == TransitionType.cupertinoFullScreenDialog,
  173. maintainState: maintainState,
  174. builder: (BuildContext context) {
  175. return handler.handlerFunc(context, parameters);
  176. });
  177. } else {
  178. var routeTransitionsBuilder;
  179. if (transition == TransitionType.custom) {
  180. routeTransitionsBuilder =
  181. transitionsBuilder ?? route.transitionBuilder;
  182. } else {
  183. routeTransitionsBuilder = _standardTransitionsBuilder(transition);
  184. }
  185. return PageRouteBuilder<dynamic>(
  186. settings: routeSettings,
  187. maintainState: maintainState,
  188. pageBuilder: (BuildContext context, Animation<double> animation,
  189. Animation<double> secondaryAnimation) {
  190. return handler.handlerFunc(context, parameters);
  191. },
  192. transitionDuration: transition == TransitionType.none
  193. ? Duration.zero
  194. : transitionDuration ?? route.transitionDuration,
  195. reverseTransitionDuration: transition == TransitionType.none
  196. ? Duration.zero
  197. : transitionDuration ?? route.transitionDuration,
  198. transitionsBuilder: transition == TransitionType.none
  199. ? (_, __, ___, child) => child
  200. : routeTransitionsBuilder,
  201. );
  202. }
  203. };
  204. return RouteMatch(
  205. matchType: RouteMatchType.visual,
  206. route: creator(settingsToUse, parameters),
  207. );
  208. }
  209. RouteTransitionsBuilder _standardTransitionsBuilder(
  210. TransitionType transitionType) {
  211. return (BuildContext context, Animation<double> animation,
  212. Animation<double> secondaryAnimation, Widget child) {
  213. if (transitionType == TransitionType.fadeIn) {
  214. return FadeTransition(opacity: animation, child: child);
  215. } else {
  216. const Offset topLeft = const Offset(0.0, 0.0);
  217. const Offset topRight = const Offset(1.0, 0.0);
  218. const Offset bottomLeft = const Offset(0.0, 1.0);
  219. Offset startOffset = bottomLeft;
  220. Offset endOffset = topLeft;
  221. if (transitionType == TransitionType.inFromLeft) {
  222. startOffset = const Offset(-1.0, 0.0);
  223. endOffset = topLeft;
  224. } else if (transitionType == TransitionType.inFromRight) {
  225. startOffset = topRight;
  226. endOffset = topLeft;
  227. } else if (transitionType == TransitionType.inFromBottom) {
  228. startOffset = bottomLeft;
  229. endOffset = topLeft;
  230. } else if (transitionType == TransitionType.inFromTop) {
  231. startOffset = Offset(0.0, -1.0);
  232. endOffset = topLeft;
  233. }
  234. return SlideTransition(
  235. position: Tween<Offset>(
  236. begin: startOffset,
  237. end: endOffset,
  238. ).animate(animation),
  239. child: child,
  240. );
  241. }
  242. };
  243. }
  244. /// Route generation method. This function can be used as a way to create routes on-the-fly
  245. /// if any defined handler is found. It can also be used with the [MaterialApp.onGenerateRoute]
  246. /// property as callback to create routes that can be used with the [Navigator] class.
  247. Route<dynamic> generator(RouteSettings routeSettings) {
  248. RouteMatch match =
  249. matchRoute(null, routeSettings.name, routeSettings: routeSettings);
  250. return match.route;
  251. }
  252. /// Prints the route tree so you can analyze it.
  253. void printTree() {
  254. _routeTree.printTree();
  255. }
  256. }