fluro/lib/src/router.dart

232 lines
8.3 KiB
Dart

/*
* fluro
* Created by Yakka
* https://theyakka.com
*
* Copyright (c) 2019 Yakka, LLC. All rights reserved.
* See LICENSE for distribution and usage details.
*/
import 'dart:async';
import 'package:fluro/fluro.dart';
import 'package:fluro/src/common.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class Router {
static final appRouter = Router();
/// The tree structure that stores the defined routes
final RouteTree _routeTree = RouteTree();
/// Generic handler for when a route has not been defined
Handler notFoundHandler;
/// Creates a [PageRoute] definition for the passed [RouteHandler]. You can optionally provide a default transition type.
void define(String routePath,
{@required Handler handler, TransitionType transitionType}) {
_routeTree.addRoute(
AppRoute(routePath, handler, transitionType: transitionType),
);
}
/// Finds a defined [AppRoute] for the path value. If no [AppRoute] definition was found
/// then function will return null.
AppRouteMatch match(String path) {
return _routeTree.matchRoute(path);
}
void pop(GlobalKey<NavigatorState> key) => key.currentState.pop();
///
Future navigateTo(GlobalKey<NavigatorState> key, String path,
{bool replace = false,
bool clearStack = false,
TransitionType transition,
Duration transitionDuration = const Duration(milliseconds: 250),
RouteTransitionsBuilder transitionBuilder}) {
RouteMatch routeMatch = matchRoute(key.currentContext, path,
transitionType: transition,
transitionsBuilder: transitionBuilder,
transitionDuration: transitionDuration);
Route<dynamic> route = routeMatch.route;
Completer completer = Completer();
Future future = completer.future;
if (routeMatch.matchType == RouteMatchType.nonVisual) {
completer.complete("Non visual route type.");
} else {
if (route == null && notFoundHandler != null) {
route = _notFoundRoute(path);
}
if (route != null) {
if (clearStack) {
future =
key.currentState.pushAndRemoveUntil(route, (check) => false);
} else {
future = replace
? key.currentState.pushReplacement(route)
: key.currentState.push(route);
}
completer.complete();
} else {
String error = "No registered route was found to handle '$path'.";
print(error);
completer.completeError(RouteNotFoundException(error, path));
}
}
return future;
}
///
Route<Null> _notFoundRoute(String path) {
RouteCreator<Null> creator =
(RouteSettings routeSettings, Map<String, List<String>> parameters) {
return MaterialPageRoute<Null>(
settings: routeSettings,
builder: (BuildContext context) {
return notFoundHandler.handlerFunc(context, parameters);
});
};
return creator(RouteSettings(name: path), null);
}
///
RouteMatch matchRoute(BuildContext buildContext, String path,
{RouteSettings routeSettings,
TransitionType transitionType,
Duration transitionDuration = const Duration(milliseconds: 250),
RouteTransitionsBuilder transitionsBuilder}) {
RouteSettings settingsToUse = routeSettings;
if (routeSettings == null) {
settingsToUse = RouteSettings(name: path);
}
AppRouteMatch match = _routeTree.matchRoute(path);
AppRoute route = match?.route;
Handler handler = (route != null ? route.handler : notFoundHandler);
var transition = transitionType;
if (transitionType == null) {
transition = route != null ? route.transitionType : TransitionType.native;
}
if (route == null && notFoundHandler == null) {
return RouteMatch(
matchType: RouteMatchType.noMatch,
errorMessage: "No matching route was found");
}
Map<String, List<String>> parameters =
match?.parameters ?? <String, List<String>>{};
if (handler.type == HandlerType.function) {
handler.handlerFunc(buildContext, parameters);
return RouteMatch(matchType: RouteMatchType.nonVisual);
}
RouteCreator creator =
(RouteSettings routeSettings, Map<String, List<String>> parameters) {
bool isNativeTransition = (transition == TransitionType.native ||
transition == TransitionType.nativeModal);
if (isNativeTransition) {
if (Theme.of(buildContext).platform == TargetPlatform.iOS) {
return CupertinoPageRoute<dynamic>(
settings: routeSettings,
fullscreenDialog: transition == TransitionType.nativeModal,
builder: (BuildContext context) {
return handler.handlerFunc(context, parameters);
});
} else {
return MaterialPageRoute<dynamic>(
settings: routeSettings,
fullscreenDialog: transition == TransitionType.nativeModal,
builder: (BuildContext context) {
return handler.handlerFunc(context, parameters);
});
}
} else if (transition == TransitionType.material ||
transition == TransitionType.materialFullScreenDialog) {
return MaterialPageRoute<dynamic>(
settings: routeSettings,
fullscreenDialog:
transition == TransitionType.materialFullScreenDialog,
builder: (BuildContext context) {
return handler.handlerFunc(context, parameters);
});
} else if (transition == TransitionType.cupertino ||
transition == TransitionType.cupertinoFullScreenDialog) {
return CupertinoPageRoute<dynamic>(
settings: routeSettings,
fullscreenDialog:
transition == TransitionType.cupertinoFullScreenDialog,
builder: (BuildContext context) {
return handler.handlerFunc(context, parameters);
});
} else {
var routeTransitionsBuilder;
if (transition == TransitionType.custom) {
routeTransitionsBuilder = transitionsBuilder;
} else {
routeTransitionsBuilder = _standardTransitionsBuilder(transition);
}
return PageRouteBuilder<dynamic>(
settings: routeSettings,
pageBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return handler.handlerFunc(context, parameters);
},
transitionDuration: transitionDuration,
transitionsBuilder: routeTransitionsBuilder,
);
}
};
return RouteMatch(
matchType: RouteMatchType.visual,
route: creator(settingsToUse, parameters),
);
}
RouteTransitionsBuilder _standardTransitionsBuilder(
TransitionType transitionType) {
return (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
if (transitionType == TransitionType.fadeIn) {
return FadeTransition(opacity: animation, child: child);
} else {
const Offset topLeft = const Offset(0.0, 0.0);
const Offset topRight = const Offset(1.0, 0.0);
const Offset bottomLeft = const Offset(0.0, 1.0);
Offset startOffset = bottomLeft;
Offset endOffset = topLeft;
if (transitionType == TransitionType.inFromLeft) {
startOffset = const Offset(-1.0, 0.0);
endOffset = topLeft;
} else if (transitionType == TransitionType.inFromRight) {
startOffset = topRight;
endOffset = topLeft;
}
return SlideTransition(
position: Tween<Offset>(
begin: startOffset,
end: endOffset,
).animate(animation),
child: child,
);
}
};
}
/// Route generation method. This function can be used as a way to create routes on-the-fly
/// if any defined handler is found. It can also be used with the [MaterialApp.onGenerateRoute]
/// property as callback to create routes that can be used with the [Navigator] class.
Route<dynamic> generator(RouteSettings routeSettings) {
RouteMatch match =
matchRoute(null, routeSettings.name, routeSettings: routeSettings);
return match.route;
}
/// Prints the route tree so you can analyze it.
void printTree() {
_routeTree.printTree();
}
}