router.dart 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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 'dart:io';
  11. import 'package:fluro/fluro.dart';
  12. import 'package:fluro/src/common.dart';
  13. import 'package:flutter/cupertino.dart';
  14. import 'package:flutter/material.dart';
  15. class Router {
  16. static final appRouter = Router();
  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(BuildContext context) => Navigator.pop(context);
  34. ///
  35. Future navigateTo(BuildContext context, String path,
  36. {bool replace = false,
  37. bool clearStack = false,
  38. TransitionType transition,
  39. Duration transitionDuration = const Duration(milliseconds: 250),
  40. RouteTransitionsBuilder transitionBuilder}) {
  41. RouteMatch routeMatch = matchRoute(context, path,
  42. transitionType: transition,
  43. transitionsBuilder: transitionBuilder,
  44. transitionDuration: transitionDuration);
  45. Route<dynamic> route = routeMatch.route;
  46. Completer completer = Completer();
  47. Future future = completer.future;
  48. if (routeMatch.matchType == RouteMatchType.nonVisual) {
  49. completer.complete("Non visual route type.");
  50. } else {
  51. if (route == null && notFoundHandler != null) {
  52. route = _notFoundRoute(context, path);
  53. }
  54. if (route != null) {
  55. if (clearStack) {
  56. future =
  57. Navigator.pushAndRemoveUntil(context, route, (check) => false);
  58. } else {
  59. future = replace
  60. ? Navigator.pushReplacement(context, route)
  61. : Navigator.push(context, route);
  62. }
  63. completer.complete();
  64. } else {
  65. String error = "No registered route was found to handle '$path'.";
  66. print(error);
  67. completer.completeError(RouteNotFoundException(error, path));
  68. }
  69. }
  70. return future;
  71. }
  72. ///
  73. Route<Null> _notFoundRoute(BuildContext context, String path) {
  74. RouteCreator<Null> creator =
  75. (RouteSettings routeSettings, Map<String, List<String>> parameters) {
  76. return MaterialPageRoute<Null>(
  77. settings: routeSettings,
  78. builder: (BuildContext context) {
  79. return notFoundHandler.handlerFunc(context, parameters);
  80. });
  81. };
  82. return creator(RouteSettings(name: path), null);
  83. }
  84. ///
  85. RouteMatch matchRoute(BuildContext buildContext, String path,
  86. {RouteSettings routeSettings,
  87. TransitionType transitionType,
  88. Duration transitionDuration = const Duration(milliseconds: 250),
  89. RouteTransitionsBuilder transitionsBuilder}) {
  90. RouteSettings settingsToUse = routeSettings;
  91. if (routeSettings == null) {
  92. settingsToUse = RouteSettings(name: path);
  93. }
  94. AppRouteMatch match = _routeTree.matchRoute(path);
  95. AppRoute route = match?.route;
  96. Handler handler = (route != null ? route.handler : notFoundHandler);
  97. var transition = transitionType;
  98. if (transitionType == null) {
  99. transition = route != null ? route.transitionType : TransitionType.native;
  100. }
  101. if (route == null && notFoundHandler == null) {
  102. return 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 RouteMatch(matchType: RouteMatchType.nonVisual);
  111. }
  112. RouteCreator creator =
  113. (RouteSettings routeSettings, Map<String, List<String>> parameters) {
  114. bool isNativeTransition = (transition == TransitionType.native ||
  115. transition == TransitionType.nativeModal);
  116. if (isNativeTransition) {
  117. if (Platform.isIOS) {
  118. return CupertinoPageRoute<dynamic>(
  119. settings: routeSettings,
  120. fullscreenDialog: transition == TransitionType.nativeModal,
  121. builder: (BuildContext context) {
  122. return handler.handlerFunc(context, parameters);
  123. });
  124. } else {
  125. return MaterialPageRoute<dynamic>(
  126. settings: routeSettings,
  127. fullscreenDialog: transition == TransitionType.nativeModal,
  128. builder: (BuildContext context) {
  129. return handler.handlerFunc(context, parameters);
  130. });
  131. }
  132. } else if (transition == TransitionType.material ||
  133. transition == TransitionType.materialFullScreenDialog) {
  134. return MaterialPageRoute<dynamic>(
  135. settings: routeSettings,
  136. fullscreenDialog:
  137. transition == TransitionType.materialFullScreenDialog,
  138. builder: (BuildContext context) {
  139. return handler.handlerFunc(context, parameters);
  140. });
  141. } else if (transition == TransitionType.cupertino ||
  142. transition == TransitionType.cupertinoFullScreenDialog) {
  143. return CupertinoPageRoute<dynamic>(
  144. settings: routeSettings,
  145. fullscreenDialog:
  146. transition == TransitionType.cupertinoFullScreenDialog,
  147. builder: (BuildContext context) {
  148. return handler.handlerFunc(context, parameters);
  149. });
  150. } else {
  151. var routeTransitionsBuilder;
  152. if (transition == TransitionType.custom) {
  153. routeTransitionsBuilder = transitionsBuilder;
  154. } else {
  155. routeTransitionsBuilder = _standardTransitionsBuilder(transition);
  156. }
  157. return PageRouteBuilder<dynamic>(
  158. settings: routeSettings,
  159. pageBuilder: (BuildContext context, Animation<double> animation,
  160. Animation<double> secondaryAnimation) {
  161. return handler.handlerFunc(context, parameters);
  162. },
  163. transitionDuration: transitionDuration,
  164. transitionsBuilder: routeTransitionsBuilder,
  165. );
  166. }
  167. };
  168. return RouteMatch(
  169. matchType: RouteMatchType.visual,
  170. route: creator(settingsToUse, parameters),
  171. );
  172. }
  173. RouteTransitionsBuilder _standardTransitionsBuilder(
  174. TransitionType transitionType) {
  175. return (BuildContext context, Animation<double> animation,
  176. Animation<double> secondaryAnimation, Widget child) {
  177. if (transitionType == TransitionType.fadeIn) {
  178. return FadeTransition(opacity: animation, child: child);
  179. } else {
  180. const Offset topLeft = const Offset(0.0, 0.0);
  181. const Offset topRight = const Offset(1.0, 0.0);
  182. const Offset bottomLeft = const Offset(0.0, 1.0);
  183. Offset startOffset = bottomLeft;
  184. Offset endOffset = topLeft;
  185. if (transitionType == TransitionType.inFromLeft) {
  186. startOffset = const Offset(-1.0, 0.0);
  187. endOffset = topLeft;
  188. } else if (transitionType == TransitionType.inFromRight) {
  189. startOffset = topRight;
  190. endOffset = topLeft;
  191. }
  192. return SlideTransition(
  193. position: Tween<Offset>(
  194. begin: startOffset,
  195. end: endOffset,
  196. ).animate(animation),
  197. child: child,
  198. );
  199. }
  200. };
  201. }
  202. /// Route generation method. This function can be used as a way to create routes on-the-fly
  203. /// if any defined handler is found. It can also be used with the [MaterialApp.onGenerateRoute]
  204. /// property as callback to create routes that can be used with the [Navigator] class.
  205. Route<dynamic> generator(RouteSettings routeSettings) {
  206. RouteMatch match =
  207. matchRoute(null, routeSettings.name, routeSettings: routeSettings);
  208. return match.route;
  209. }
  210. /// Prints the route tree so you can analyze it.
  211. void printTree() {
  212. _routeTree.printTree();
  213. }
  214. }