Jonny Borges
Committed by GitHub

Merge pull request #827 from jonataslaw/dev

Added GetPage childs and middleware
... ... @@ -9,6 +9,7 @@ export 'src/routes/custom_transition.dart';
export 'src/routes/default_route.dart';
export 'src/routes/get_route.dart';
export 'src/routes/observers/route_observer.dart';
export 'src/routes/route_middleware.dart';
export 'src/routes/transitions_type.dart';
export 'src/snackbar/snack.dart';
export 'src/snackbar/snack_route.dart';
... ...
... ... @@ -1043,15 +1043,17 @@ Since version 2.8 it is possible to access the properties
return WidgetsBinding.instance;
}
///The window to which this binding is bound.
ui.Window get window => ui.window;
//TODO: Change to ui.SingletonFlutterWindow rather dynamic
//when Flutter update stable. dynamic is used to avoid Breaking Changes
/// The window to which this binding is bound.
dynamic get window => ui.window;
Locale get deviceLocale => window.locale;
Locale get deviceLocale => ui.window.locale;
///The number of device pixels for each logical pixel.
double get pixelRatio => window.devicePixelRatio;
double get pixelRatio => ui.window.devicePixelRatio;
Size get size => window.physicalSize / pixelRatio;
Size get size => ui.window.physicalSize / pixelRatio;
///The horizontal extent of this size.
double get width => size.width;
... ... @@ -1061,14 +1063,14 @@ Since version 2.8 it is possible to access the properties
///The distance from the top edge to the first unpadded pixel,
///in physical pixels.
double get statusBarHeight => window.padding.top;
double get statusBarHeight => ui.window.padding.top;
///The distance from the bottom edge to the first unpadded pixel,
///in physical pixels.
double get bottomBarHeight => window.padding.bottom;
double get bottomBarHeight => ui.window.padding.bottom;
///The system-reported text scale.
double get textScaleFactor => window.textScaleFactor;
double get textScaleFactor => ui.window.textScaleFactor;
/// give access to TextTheme.of(context)
TextTheme get textTheme => theme?.textTheme;
... ... @@ -1080,7 +1082,8 @@ Since version 2.8 it is possible to access the properties
bool get isDarkMode => (theme.brightness == Brightness.dark);
/// Check if dark mode theme is enable on platform on android Q+
bool get isPlatformDarkMode => (window.platformBrightness == Brightness.dark);
bool get isPlatformDarkMode =>
(ui.window.platformBrightness == Brightness.dark);
/// give access to Theme.of(context).iconTheme.color
Color get iconColor => theme?.iconTheme?.color;
... ...
... ... @@ -188,44 +188,7 @@ class GetCupertinoApp extends StatelessWidget {
super(key: key);
Route<dynamic> generator(RouteSettings settings) {
final match = Get.routeTree.matchRoute(settings.name);
Get.parameters = match?.parameters;
if (match?.route == null) {
return GetPageRoute(
page: unknownRoute.page,
parameter: unknownRoute.parameter,
settings:
RouteSettings(name: settings.name, arguments: settings.arguments),
curve: unknownRoute.curve,
opaque: unknownRoute.opaque,
customTransition: unknownRoute.customTransition,
binding: unknownRoute.binding,
bindings: unknownRoute.bindings,
transitionDuration:
(unknownRoute.transitionDuration ?? Get.defaultTransitionDuration),
transition: unknownRoute.transition,
popGesture: unknownRoute.popGesture,
fullscreenDialog: unknownRoute.fullscreenDialog,
);
}
return GetPageRoute(
page: match.route.page,
parameter: match.route.parameter,
settings:
RouteSettings(name: settings.name, arguments: settings.arguments),
curve: match.route.curve,
opaque: match.route.opaque,
customTransition: match.route.customTransition,
binding: match.route.binding,
bindings: match.route.bindings,
transitionDuration:
(match.route.transitionDuration ?? Get.defaultTransitionDuration),
transition: match.route.transition,
popGesture: match.route.popGesture,
fullscreenDialog: match.route.fullscreenDialog,
);
return PageRedirect(settings, unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) {
... ...
... ... @@ -199,44 +199,7 @@ class GetMaterialApp extends StatelessWidget {
super(key: key);
Route<dynamic> generator(RouteSettings settings) {
final match = Get.routeTree.matchRoute(settings.name);
Get.parameters = match?.parameters;
if (match?.route == null) {
return GetPageRoute(
page: unknownRoute.page,
parameter: unknownRoute.parameter,
settings:
RouteSettings(name: settings.name, arguments: settings.arguments),
curve: unknownRoute.curve,
opaque: unknownRoute.opaque,
customTransition: unknownRoute.customTransition,
binding: unknownRoute.binding,
bindings: unknownRoute.bindings,
transitionDuration:
(unknownRoute.transitionDuration ?? Get.defaultTransitionDuration),
transition: unknownRoute.transition,
popGesture: unknownRoute.popGesture,
fullscreenDialog: unknownRoute.fullscreenDialog,
);
}
return GetPageRoute(
page: match.route.page,
parameter: match.route.parameter,
settings:
RouteSettings(name: settings.name, arguments: settings.arguments),
curve: match.route.curve,
opaque: match.route.opaque,
customTransition: match.route.customTransition,
binding: match.route.binding,
bindings: match.route.bindings,
transitionDuration:
(match.route.transitionDuration ?? Get.defaultTransitionDuration),
transition: match.route.transition,
popGesture: match.route.popGesture,
fullscreenDialog: match.route.fullscreenDialog,
);
return PageRedirect(settings, unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) {
... ...
import 'package:flutter/widgets.dart';
import 'package:get/get.dart';
import '../routes/get_route.dart';
class ParseRouteTree {
final List<_ParseRouteTreeNode> _nodes = <_ParseRouteTreeNode>[];
// bool _hasDefaultRoute = false;
void addRoute(GetPage route) {
var path = route.name;
... ... @@ -46,7 +46,56 @@ class ParseRouteTree {
}
parent = node;
}
// Add Page children.
for (var page in _flattenPage(route)) {
addRoute(page);
}
}
List<GetPage> _flattenPage(GetPage route) {
final result = <GetPage>[];
if (route.children == null || route.children.isEmpty) {
return result;
}
final parentPath = route.name;
for (var page in route.children) {
// Add Parent middlewares to children
final pageMiddlewares = page.middlewares ?? <GetMiddleware>[];
pageMiddlewares.addAll(route.middlewares ?? <GetMiddleware>[]);
result.add(_addChild(page, parentPath, pageMiddlewares));
final children = _flattenPage(page);
for (var child in children) {
pageMiddlewares.addAll(child.middlewares ?? <GetMiddleware>[]);
result.add(_addChild(child, parentPath, pageMiddlewares));
}
}
return result;
}
/// Change the Path for a [GetPage]
GetPage _addChild(
GetPage origin, String parentPath, List<GetMiddleware> middlewares) =>
GetPage(
name: parentPath + origin.name,
page: origin.page,
title: origin.title,
alignment: origin.alignment,
transition: origin.transition,
binding: origin.binding,
bindings: origin.bindings,
curve: origin.curve,
customTransition: origin.customTransition,
fullscreenDialog: origin.fullscreenDialog,
maintainState: origin.maintainState,
opaque: origin.opaque,
parameter: origin.parameter,
popGesture: origin.popGesture,
settings: origin.settings,
transitionDuration: origin.transitionDuration,
middlewares: middlewares,
);
_GetPageMatch matchRoute(String path) {
var usePath = path;
... ...
... ... @@ -11,8 +11,8 @@ import 'default_transitions.dart';
import 'transitions_type.dart';
class GetPageRoute<T> extends PageRoute<T> {
GetPageRoute({
RouteSettings settings,
GetPageRoute(
{RouteSettings settings,
this.transitionDuration = const Duration(milliseconds: 300),
this.opaque = true,
this.parameter,
... ... @@ -30,7 +30,8 @@ class GetPageRoute<T> extends PageRoute<T> {
this.barrierLabel,
this.maintainState = true,
bool fullscreenDialog = false,
}) : assert(opaque != null),
this.middlewares})
: assert(opaque != null),
assert(barrierDismissible != null),
assert(maintainState != null),
assert(fullscreenDialog != null),
... ... @@ -68,6 +69,8 @@ class GetPageRoute<T> extends PageRoute<T> {
final Alignment alignment;
final List<GetMiddleware> middlewares;
@override
final Color barrierColor;
... ... @@ -113,15 +116,21 @@ class GetPageRoute<T> extends PageRoute<T> {
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
// Get.reference = settings.name ?? routeName;
final middlewareRunner = MiddlewareRunner(middlewares);
final bindingsToBind = middlewareRunner.runOnBindingsStart(bindings);
binding?.dependencies();
if (bindings != null) {
for (final binding in bindings) {
if (bindingsToBind != null) {
for (final binding in bindingsToBind) {
binding.dependencies();
}
}
// final pageWidget = page();
return page();
final pageToBuild = middlewareRunner.runOnPageBuildStart(page);
return middlewareRunner.runOnPageBuilt(pageToBuild());
}
static bool isPopGestureInProgress(PageRoute<dynamic> route) {
... ... @@ -384,6 +393,10 @@ class GetPageRoute<T> extends PageRoute<T> {
WidgetsBinding.instance.addPostFrameCallback(
(_) => GetInstance().removeDependencyByRoute("$reference"));
}
final middlewareRunner = MiddlewareRunner(middlewares);
middlewareRunner.runOnPageDispose();
}
}
... ...
import 'package:flutter/widgets.dart';
import '../../../get_instance/get_instance.dart';
import '../../get_navigation.dart';
import 'custom_transition.dart';
import 'transitions_type.dart';
... ... @@ -20,6 +22,8 @@ class GetPage {
final Duration transitionDuration;
final bool fullscreenDialog;
final RouteSettings settings;
final List<GetPage> children;
final List<GetMiddleware> middlewares;
const GetPage({
@required this.name,
... ... @@ -38,8 +42,52 @@ class GetPage {
this.transition,
this.customTransition,
this.fullscreenDialog = false,
this.children,
this.middlewares,
}) : assert(page != null),
assert(name != null),
assert(maintainState != null),
assert(fullscreenDialog != null);
GetPage copyWith({
String name,
GetPageBuilder page,
bool popGesture,
Map<String, String> parameter,
String title,
Transition transition,
Curve curve,
Alignment alignment,
bool maintainState,
bool opaque,
Bindings binding,
List<Bindings> bindings,
CustomTransition customTransition,
Duration transitionDuration,
bool fullscreenDialog,
RouteSettings settings,
List<GetPage> children,
List<GetMiddleware> middlewares,
}) {
return GetPage(
name: name ?? this.name,
page: page ?? this.page,
popGesture: popGesture ?? this.popGesture,
parameter: parameter ?? this.parameter,
title: title ?? this.title,
transition: transition ?? this.transition,
curve: curve ?? this.curve,
alignment: alignment ?? this.alignment,
maintainState: maintainState ?? this.maintainState,
opaque: opaque ?? this.opaque,
binding: binding ?? this.binding,
bindings: bindings ?? this.bindings,
customTransition: customTransition ?? this.customTransition,
transitionDuration: transitionDuration ?? this.transitionDuration,
fullscreenDialog: fullscreenDialog ?? this.fullscreenDialog,
settings: settings ?? this.settings,
children: children ?? this.children,
middlewares: middlewares ?? this.middlewares,
);
}
}
... ...
import 'package:flutter/cupertino.dart';
import '../../../get.dart';
abstract class _RouteMiddleware {
/// The Order of the Middlewares to run.
///
/// {@tool snippet}
/// This Middewares will be called in this order.
/// ```dart
/// final middlewares = [
/// GetMiddleware(priority: 2),
/// GetMiddleware(priority: 5),
/// GetMiddleware(priority: 4),
/// GetMiddleware(priority: -8),
/// ];
/// ```
/// -8 => 2 => 4 => 5
/// {@end-tool}
int priority;
/// This function will be called when the page of
/// the called route is being searched for.
/// It take RouteSettings as a result an redirect to the new settings or
/// give it null and there will be no redirecting.
/// {@tool snippet}
/// ```dart
/// GetPage redirect(String route) {
/// final authService = Get.find<AuthService>();
/// return authService.authed.value ? null : RouteSettings(name: '/login');
/// }
/// ```
/// {@end-tool}
RouteSettings redirect(String route);
/// This function will be called when this Page is called
/// you can use it to change something about the page or give it new page
/// {@tool snippet}
/// ```dart
/// GetPage onPageCalled(GetPage page) {
/// final authService = Get.find<AuthService>();
/// return page.copyWith(title: 'Wellcome ${authService.UserName}');
/// }
/// ```
/// {@end-tool}
GetPage onPageCalled(GetPage page);
/// This function will be called right before the [Bindings] are initialize.
/// Here you can change [Bindings] for this page
/// {@tool snippet}
/// ```dart
/// List<Bindings> onBindingsStart(List<Bindings> bindings) {
/// final authService = Get.find<AuthService>();
/// if (authService.isAdmin) {
/// bindings.add(AdminBinding());
/// }
/// return bindings;
/// }
/// ```
/// {@end-tool}
List<Bindings> onBindingsStart(List<Bindings> bindings);
/// This function will be called right after the [Bindings] are initialize.
GetPageBuilder onPageBuildStart(GetPageBuilder page);
/// This function will be called right after the
/// GetPage.page function is called and will give you the result
/// of the function. and take the widget that will be showed.
Widget onPageBuilt(Widget page);
void onPageDispose();
}
/// The Page Middlewares.
/// The Functions will be called in this order
/// (( [redirect] -> [onPageCalled] -> [onBindingsStart] ->
/// [onPageBuildStart] -> [onPageBuilt] -> [onPageDispose] ))
class GetMiddleware implements _RouteMiddleware {
@override
int priority = 0;
GetMiddleware({this.priority});
@override
RouteSettings redirect(String route) => null;
@override
GetPage onPageCalled(GetPage page) => page;
@override
List<Bindings> onBindingsStart(List<Bindings> bindings) => bindings;
@override
GetPageBuilder onPageBuildStart(GetPageBuilder page) => page;
@override
Widget onPageBuilt(Widget page) => page;
@override
void onPageDispose() {}
}
class MiddlewareRunner {
MiddlewareRunner(this._middlewares);
final List<GetMiddleware> _middlewares;
List<GetMiddleware> _getMiddlewares() {
if (_middlewares != null) {
_middlewares.sort((a, b) => a.priority.compareTo(b.priority));
return _middlewares;
}
return <GetMiddleware>[];
}
GetPage runOnPageCalled(GetPage page) {
_getMiddlewares().forEach((element) {
page = element.onPageCalled(page);
});
return page;
}
RouteSettings runRedirect(String route) {
RouteSettings to;
_getMiddlewares().forEach((element) {
to = element.redirect(route);
});
if (to != null) {
Get.log('Redirect to $to');
}
return to;
}
List<Bindings> runOnBindingsStart(List<Bindings> bindings) {
_getMiddlewares().forEach((element) {
bindings = element.onBindingsStart(bindings);
});
return bindings;
}
GetPageBuilder runOnPageBuildStart(GetPageBuilder page) {
_getMiddlewares().forEach((element) {
page = element.onPageBuildStart(page);
});
return page;
}
Widget runOnPageBuilt(Widget page) {
_getMiddlewares().forEach((element) {
page = element.onPageBuilt(page);
});
return page;
}
void runOnPageDispose() =>
_getMiddlewares().forEach((element) => element.onPageDispose());
}
class PageRedirect {
GetPage route;
GetPage unknownRoute;
RouteSettings settings;
bool isUnknown;
PageRedirect(this.settings, this.unknownRoute,
{this.isUnknown = false, this.route});
// redirect all pages that needes redirecting
GetPageRoute page() {
while (needRecheck()) {}
return isUnknown
? GetPageRoute(
page: unknownRoute.page,
parameter: unknownRoute.parameter,
settings: RouteSettings(
name: settings.name, arguments: settings.arguments),
curve: unknownRoute.curve,
opaque: unknownRoute.opaque,
customTransition: unknownRoute.customTransition,
binding: unknownRoute.binding,
bindings: unknownRoute.bindings,
transitionDuration: (unknownRoute.transitionDuration ??
Get.defaultTransitionDuration),
transition: unknownRoute.transition,
popGesture: unknownRoute.popGesture,
fullscreenDialog: unknownRoute.fullscreenDialog,
middlewares: unknownRoute.middlewares,
)
: GetPageRoute(
page: route.page,
parameter: route.parameter,
settings: RouteSettings(
name: settings.name, arguments: settings.arguments),
curve: route.curve,
opaque: route.opaque,
customTransition: route.customTransition,
binding: route.binding,
bindings: route.bindings,
transitionDuration:
(route.transitionDuration ?? Get.defaultTransitionDuration),
transition: route.transition,
popGesture: route.popGesture,
fullscreenDialog: route.fullscreenDialog,
middlewares: route.middlewares);
}
/// check if redirect is needed
bool needRecheck() {
final match = Get.routeTree.matchRoute(settings.name);
Get.parameters = match?.parameters;
// No Match found
if (match?.route == null) {
isUnknown = true;
return false;
}
final runner = MiddlewareRunner(match.route.middlewares);
route = runner.runOnPageCalled(match.route);
// No middlewares found return match.
if (match.route.middlewares == null || match.route.middlewares.isEmpty) {
return false;
}
final newSettings = runner.runRedirect(settings.name);
if (newSettings == null) {
return false;
}
settings = newSettings;
return true;
}
}
... ...
import 'package:flutter/cupertino.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'get_main_test.dart';
class RedirectMiddleware extends GetMiddleware {
@override
RouteSettings redirect(String route) => RouteSettings(name: '/second');
}
main() {
testWidgets("Middleware redirect smoke test", (tester) async {
await tester.pumpWidget(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => Container()),
GetPage(
name: '/first',
page: () => FirstScreen(),
middlewares: [RedirectMiddleware()]),
GetPage(name: '/second', page: () => SecondScreen()),
GetPage(name: '/third', page: () => ThirdScreen()),
],
),
);
Get.toNamed('/first');
await tester.pumpAndSettle();
print(Get.routing.current);
expect(find.byType(SecondScreen), findsOneWidget);
});
}
... ...
import 'package:flutter/cupertino.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'package:get/get_navigation/src/root/parse_route.dart';
void main() {
test('Parse Page with children', () {
final tree = ParseRouteTree();
final pageTree = GetPage(name: '/city', page: () => Container(), children: [
GetPage(name: '/home', page: () => Container(), children: [
GetPage(name: '/bed-room', page: () => Container()),
GetPage(name: '/living-room', page: () => Container()),
]),
GetPage(name: '/work', page: () => Container(), children: [
GetPage(name: '/office', page: () => Container(), children: [
GetPage(name: '/pen', page: () => Container()),
GetPage(name: '/paper', page: () => Container()),
]),
GetPage(name: '/meeting-room', page: () => Container()),
]),
]);
tree.addRoute(pageTree);
final searchRoute = '/city/work/office/pen';
final match = tree.matchRoute(searchRoute);
expect(match, isNotNull);
expect(match.route.name, searchRoute);
});
test('Parse Page without children', () {
final tree = ParseRouteTree();
final pageTree = [
GetPage(name: '/city', page: () => Container()),
GetPage(name: '/city/home', page: () => Container()),
GetPage(name: '/city/home/bed-room', page: () => Container()),
GetPage(name: '/city/home/living-room', page: () => Container()),
GetPage(name: '/city/work', page: () => Container()),
GetPage(name: '/city/work/office', page: () => Container()),
GetPage(name: '/city/work/office/pen', page: () => Container()),
GetPage(name: '/city/work/office/paper', page: () => Container()),
GetPage(name: '/city/work/meeting-room', page: () => Container()),
];
for (var p in pageTree) {
tree.addRoute(p);
}
final searchRoute = '/city/work/office/pen';
final match = tree.matchRoute(searchRoute);
expect(match, isNotNull);
expect(match.route.name, searchRoute);
});
}
... ...