Jonny Borges

getx-pub-dev

Showing 49 changed files with 1540 additions and 1455 deletions
import 'package:flutter/material.dart';
import 'package:get/route_manager.dart';
import 'package:get/get.dart';
import 'lang/translation_service.dart';
import 'routes/app_pages.dart';
... ... @@ -9,31 +9,16 @@ void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late final routerDelegate = GetNavigation.instance = GetNavigation(
pages: AppPages.routes,
navigatorObservers: [GetObserver()],
);
final routeInformationParser =
NewGetInformationParser(initialRoute: AppPages.INITIAL);
@override
Widget build(BuildContext context) {
return GetMaterialApp.router(
routeInformationParser: routeInformationParser,
routerDelegate: routerDelegate,
// title: 'Router Management Example',
return GetMaterialApp(
debugShowCheckedModeBanner: false,
enableLog: true,
logWriterCallback: Logger.write,
// initialRoute: AppPages.INITIAL,
initialRoute: AppPages.INITIAL,
getPages: AppPages.routes,
locale: TranslationService.locale,
fallbackLocale: TranslationService.fallbackLocale,
... ... @@ -42,8 +27,6 @@ class _MyAppState extends State<MyApp> {
}
}
/// Nav 2 snippet
// void main() {
// runApp(MyApp());
... ...
... ... @@ -6,8 +6,11 @@ import 'package:get/get.dart';
import '../controllers/home_controller.dart';
class CountryView extends GetView<HomeController> {
const CountryView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('BUILD CONTRY');
print(context.params);
return Container(
decoration: BoxDecoration(
image: DecorationImage(
... ... @@ -34,8 +37,8 @@ class CountryView extends GetView<HomeController> {
return ListTile(
onTap: () async {
//Get.rootDelegate.toNamed('/home/country');
final data = await GetNavigation.instance
.toNamed('/home/country/details?id=$index');
final data = await Get.toNamed(
'/home/country/details?id=$index');
print(data);
},
trailing: CircleAvatar(
... ...
... ... @@ -6,6 +6,7 @@ import 'package:get/get.dart';
import '../controllers/home_controller.dart';
class DetailsView extends GetView<HomeController> {
const DetailsView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as PageSettings;
... ... @@ -80,7 +81,7 @@ class DetailsView extends GetView<HomeController> {
),
TextButton(
onPressed: () {
GetNavigation.instance.back('djsoidjsoidj');
Get.back(result: 'djsoidjsoidj');
},
child: Text('back'))
],
... ...
... ... @@ -4,8 +4,12 @@ import 'package:get/get.dart';
import '../controllers/home_controller.dart';
class HomeView extends GetView<HomeController> {
const HomeView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('REBUILD HOME');
// print(Get.parameters);
return Container(
decoration: BoxDecoration(
color: Colors.white,
... ... @@ -76,7 +80,7 @@ class HomeView extends GetView<HomeController> {
),
onPressed: () async {
//await Navigation Get.rootDelegate.toNamed('/home/country');
GetNavigation.instance.toNamed('/home/country');
Get.toNamed('/countrdhia');
},
child: Text(
'fetch_country'.tr,
... ...
... ... @@ -2,31 +2,30 @@ import 'package:get/get.dart';
import '../pages/home/bindings/home_binding.dart';
import '../pages/home/presentation/views/country_view.dart';
import '../pages/home/presentation/views/details_view.dart';
import '../pages/home/presentation/views/home_view.dart';
part 'app_routes.dart';
// ignore: avoid_classes_with_only_static_members
class AppPages {
static const INITIAL = Routes.HOME;
static const INITIAL = '${Routes.HOME}?schineider=uuu';
static final routes = [
GetPage(
name: Routes.HOME,
page: () => HomeView(),
binding: HomeBinding(),
children: [
GetPage(
name: Routes.COUNTRY,
page: () => CountryView(),
children: [
GetPage(
name: Routes.DETAILS,
page: () => DetailsView(),
),
],
),
]),
name: '${Routes.HOME}',
page: () => const HomeView(),
binding: HomeBinding(),
children: [],
),
GetPage(
name: '${Routes.COUNTRY}/:xasa',
page: () => const CountryView(),
// children: [
// GetPage(
// name: Routes.DETAILS,
// page: () => DetailsView(),
// ),
// ],
),
];
}
... ...
... ... @@ -4,4 +4,8 @@ abstract class Routes {
static const HOME = '/home';
static const COUNTRY = '/country';
static const DETAILS = '/details';
static const DASHBOARD = '/dashboard';
static const PROFILE = '/profile';
static const PRODUCTS = '/products';
}
... ...
... ... @@ -5,14 +5,15 @@ import '../routes/app_pages.dart';
class EnsureAuthMiddleware extends GetMiddleware {
@override
Future<GetNavConfig?> redirectDelegate(GetNavConfig route) async {
Future<RouteDecoder?> redirectDelegate(RouteDecoder route) async {
// you can do whatever you want here
// but it's preferable to make this method fast
// await Future.delayed(Duration(milliseconds: 500));
if (!AuthService.to.isLoggedInValue) {
final newRoute = Routes.LOGIN_THEN(route.location!);
return GetNavConfig.fromRoute(newRoute);
final newRoute = Routes.LOGIN_THEN(route.arguments!.name);
return RouteDecoder.fromRoute(newRoute);
}
return await super.redirectDelegate(route);
}
... ... @@ -20,13 +21,13 @@ class EnsureAuthMiddleware extends GetMiddleware {
class EnsureNotAuthedMiddleware extends GetMiddleware {
@override
Future<GetNavConfig?> redirectDelegate(GetNavConfig route) async {
Future<RouteDecoder?> redirectDelegate(RouteDecoder route) async {
if (AuthService.to.isLoggedInValue) {
//NEVER navigate to auth screen, when user is already authed
return null;
//OR redirect user to another screen
//return GetNavConfig.fromRoute(Routes.PROFILE);
//return RouteDecoder.fromRoute(Routes.PROFILE);
}
return await super.redirectDelegate(route);
}
... ...
... ... @@ -10,19 +10,19 @@ class HomeView extends GetView<HomeController> {
return GetRouterOutlet.builder(
builder: (context, delegate, currentRoute) {
//This router outlet handles the appbar and the bottom navigation bar
final currentLocation = currentRoute?.location;
final currentLocation = context.location;
var currentIndex = 0;
if (currentLocation?.startsWith(Routes.PRODUCTS) == true) {
if (currentLocation.startsWith(Routes.PRODUCTS) == true) {
currentIndex = 2;
}
if (currentLocation?.startsWith(Routes.PROFILE) == true) {
if (currentLocation.startsWith(Routes.PROFILE) == true) {
currentIndex = 1;
}
return Scaffold(
body: GetRouterOutlet(
initialRoute: Routes.DASHBOARD,
// anchorRoute: Routes.HOME,
key: Get.nestedKey(Routes.HOME),
// key: Get.nestedKey(Routes.HOME),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentIndex,
... ...
... ... @@ -31,9 +31,8 @@ class LoginView extends GetView<LoginController> {
),
onPressed: () {
AuthService.to.login();
final thenTo = Get.rootDelegate.currentConfiguration!
.currentPage!.parameters?['then'];
Get.rootDelegate.offNamed(thenTo ?? Routes.HOME);
final thenTo = context.params['then'];
Get.offNamed(thenTo ?? Routes.HOME);
},
),
],
... ...
... ... @@ -31,7 +31,7 @@ class ProductsView extends GetView<ProductsController> {
final item = controller.products[index];
return ListTile(
onTap: () {
Get.rootDelegate
Get
.toNamed(Routes.PRODUCT_DETAILS(item.id));
},
title: Text(item.name),
... ...
... ... @@ -39,7 +39,8 @@ class ProfileView extends GetView<ProfileController> {
Get.defaultDialog(
title: 'Test Dialog In Home Outlet !!',
barrierDismissible: true,
navigatorKey: Get.nestedKey(Routes.HOME),
id: Routes.HOME,
// navigatorKey: Get.nestedKey(Routes.HOME),
);
},
)
... ...
... ... @@ -21,7 +21,7 @@ class DrawerWidget extends StatelessWidget {
ListTile(
title: Text('Home'),
onTap: () {
Get.rootDelegate.toNamed(Routes.HOME);
Get.toNamed(Routes.HOME);
//to close the drawer
Navigator.of(context).pop();
... ... @@ -30,7 +30,7 @@ class DrawerWidget extends StatelessWidget {
ListTile(
title: Text('Settings'),
onTap: () {
Get.rootDelegate.toNamed(Routes.SETTINGS);
Get.toNamed(Routes.SETTINGS);
//to close the drawer
Navigator.of(context).pop();
... ... @@ -46,7 +46,7 @@ class DrawerWidget extends StatelessWidget {
),
onTap: () {
AuthService.to.logout();
Get.rootDelegate.toNamed(Routes.LOGIN);
Get.toNamed(Routes.LOGIN);
//to close the drawer
Navigator.of(context).pop();
... ... @@ -61,7 +61,7 @@ class DrawerWidget extends StatelessWidget {
),
),
onTap: () {
Get.rootDelegate.toNamed(Routes.LOGIN);
Get.toNamed(Routes.LOGIN);
//to close the drawer
Navigator.of(context).pop();
... ...
... ... @@ -10,11 +10,11 @@ class RootView extends GetView<RootController> {
Widget build(BuildContext context) {
return GetRouterOutlet.builder(
builder: (context, delegate, current) {
final title = current?.location;
final title = context.location;
return Scaffold(
drawer: DrawerWidget(),
appBar: AppBar(
title: Text(title ?? ''),
title: Text(title),
centerTitle: true,
),
body: GetRouterOutlet(
... ...
... ... @@ -7,7 +7,6 @@ environment:
dependencies:
cupertino_icons: ^1.0.2
effective_dart: 1.3.1
# get: ^4.1.4
get:
path: ../
... ...
... ... @@ -2,12 +2,6 @@ library get_navigation;
export 'src/bottomsheet/bottomsheet.dart';
export 'src/extension_navigation.dart';
export 'src/nav2/get_information_parser.dart';
export 'src/nav2/get_nav_config.dart';
export 'src/nav2/get_navigator.dart';
export 'src/nav2/get_router_delegate.dart';
export 'src/nav2/router_outlet.dart';
export 'src/nav3/get_navigation/index.dart';
export 'src/root/get_cupertino_app.dart';
export 'src/root/get_material_app.dart';
export 'src/root/internacionalization.dart';
... ... @@ -15,6 +9,7 @@ export 'src/root/root_controller.dart';
export 'src/routes/custom_transition.dart';
export 'src/routes/default_route.dart';
export 'src/routes/get_route.dart';
export 'src/routes/index.dart';
export 'src/routes/observers/route_observer.dart';
export 'src/routes/route_middleware.dart';
export 'src/routes/transitions_type.dart';
... ...
... ... @@ -9,7 +9,6 @@ import '../../get_state_manager/src/simple/get_state.dart';
import '../../get_utils/get_utils.dart';
import '../get_navigation.dart';
import 'dialog/dialog_route.dart';
import 'root/parse_route.dart';
/// It replaces the Flutter Navigator, but needs no context.
/// You can to use navigator.push(YourRoute()) rather
... ... @@ -78,6 +77,7 @@ extension ExtensionDialog on GetInterface {
Curve? transitionCurve,
String? name,
RouteSettings? routeSettings,
dynamic id,
}) {
assert(debugCheckHasMaterialLocalizations(context!));
... ... @@ -110,22 +110,24 @@ extension ExtensionDialog on GetInterface {
navigatorKey: navigatorKey,
routeSettings:
routeSettings ?? RouteSettings(arguments: arguments, name: name),
id: id,
);
}
/// Api from showGeneralDialog with no context
Future<T?> generalDialog<T>({
required RoutePageBuilder pageBuilder,
bool barrierDismissible = false,
String? barrierLabel,
Color barrierColor = const Color(0x80000000),
Duration transitionDuration = const Duration(milliseconds: 200),
RouteTransitionsBuilder? transitionBuilder,
GlobalKey<NavigatorState>? navigatorKey,
RouteSettings? routeSettings,
}) {
Future<T?> generalDialog<T>(
{required RoutePageBuilder pageBuilder,
bool barrierDismissible = false,
String? barrierLabel,
Color barrierColor = const Color(0x80000000),
Duration transitionDuration = const Duration(milliseconds: 200),
RouteTransitionsBuilder? transitionBuilder,
GlobalKey<NavigatorState>? navigatorKey,
RouteSettings? routeSettings,
dynamic id}) {
assert(!barrierDismissible || barrierLabel != null);
final nav = navigatorKey?.currentState ??
final key = navigatorKey ?? Get.nestedKey(id)?.navigatorKey;
final nav = key?.currentState ??
Navigator.of(overlayContext!,
rootNavigator:
true); //overlay context will always return the root navigator
... ... @@ -148,6 +150,7 @@ extension ExtensionDialog on GetInterface {
EdgeInsetsGeometry? titlePadding,
TextStyle? titleStyle,
Widget? content,
dynamic id,
EdgeInsetsGeometry? contentPadding,
VoidCallback? onConfirm,
VoidCallback? onCancel,
... ... @@ -269,6 +272,7 @@ extension ExtensionDialog on GetInterface {
: baseAlertDialog,
barrierDismissible: barrierDismissible,
navigatorKey: navigatorKey,
id: id,
);
}
}
... ... @@ -499,66 +503,60 @@ extension GetNavigationExt on GetInterface {
///
/// By default, GetX will prevent you from push a route that you already in,
/// if you want to push anyway, set [preventDuplicates] to false
Future<T?>? to<T>(
dynamic page, {
bool? opaque,
Transition? transition,
Curve? curve,
Duration? duration,
int? id,
String? routeName,
bool fullscreenDialog = false,
dynamic arguments,
Binding? binding,
bool preventDuplicates = true,
bool? popGesture,
bool showCupertinoParallax = true,
double Function(BuildContext context)? gestureWidth,
}) {
// var routeName = "/${page.runtimeType}";
routeName ??= "/${page.runtimeType}";
routeName = _cleanRouteName(routeName);
if (preventDuplicates && routeName == currentRoute) {
return null;
}
return global(id).currentState?.push<T>(
GetPageRoute<T>(
opaque: opaque ?? true,
page: _resolvePage(page, 'to'),
routeName: routeName,
gestureWidth: gestureWidth,
showCupertinoParallax: showCupertinoParallax,
settings: RouteSettings(
name: routeName,
arguments: arguments,
),
popGesture: popGesture ?? defaultPopGesture,
transition: transition ?? defaultTransition,
curve: curve ?? defaultTransitionCurve,
fullscreenDialog: fullscreenDialog,
binding: binding,
transitionDuration: duration ?? defaultTransitionDuration,
),
);
Future<T?>? to<T>(Widget Function() page,
{bool? opaque,
Transition? transition,
Curve? curve,
Duration? duration,
int? id,
String? routeName,
bool fullscreenDialog = false,
dynamic arguments,
Binding? binding,
bool preventDuplicates = true,
bool? popGesture,
bool showCupertinoParallax = true,
double Function(BuildContext context)? gestureWidth,
bool rebuildStack = true,
PreventDuplicateHandlingMode preventDuplicateHandlingMode =
PreventDuplicateHandlingMode.ReorderRoutes}) {
searchDelegate(id).to(
page,
opaque: opaque,
transition: transition,
curve: curve,
duration: duration,
id: id,
routeName: routeName,
fullscreenDialog: fullscreenDialog,
arguments: arguments,
binding: binding,
preventDuplicates: preventDuplicates,
popGesture: popGesture,
showCupertinoParallax: showCupertinoParallax,
gestureWidth: gestureWidth,
rebuildStack: rebuildStack,
preventDuplicateHandlingMode: preventDuplicateHandlingMode,
);
}
GetPageBuilder _resolvePage(dynamic page, String method) {
if (page is GetPageBuilder) {
return page;
} else if (page is Widget) {
Get.log(
'''WARNING, consider using: "Get.$method(() => Page())" instead of "Get.$method(Page())".
Using a widget function instead of a widget fully guarantees that the widget and its controllers will be removed from memory when they are no longer used.
''');
return () => page;
} else if (page is String) {
throw '''Unexpected String,
use toNamed() instead''';
} else {
throw '''Unexpected format,
you can only use widgets and widget functions here''';
}
}
// GetPageBuilder _resolvePage(dynamic page, String method) {
// if (page is GetPageBuilder) {
// return page;
// } else if (page is Widget) {
// Get.log(
// '''WARNING, consider using: "Get.$method(() => Page())" instead of "Get.$method(Page())".
// Using a widget function instead of a widget fully guarantees that the widget and its controllers will be removed from memory when they are no longer used.
// ''');
// return () => page;
// } else if (page is String) {
// throw '''Unexpected String,
// use toNamed() instead''';
// } else {
// throw '''Unexpected format,
// you can only use widgets and widget functions here''';
// }
// }
/// **Navigation.pushNamed()** shortcut.<br><br>
///
... ... @@ -592,10 +590,13 @@ you can only use widgets and widget functions here''';
page = uri.toString();
}
return global(id).currentState?.pushNamed<T>(
page,
arguments: arguments,
);
return searchDelegate(id).toNamed(
page,
arguments: arguments,
id: id,
preventDuplicates: preventDuplicates,
parameters: parameters,
);
}
/// **Navigation.pushReplacementNamed()** shortcut.<br><br>
... ... @@ -618,21 +619,23 @@ you can only use widgets and widget functions here''';
String page, {
dynamic arguments,
int? id,
bool preventDuplicates = true,
Map<String, String>? parameters,
}) {
if (preventDuplicates && page == currentRoute) {
return null;
}
// if (preventDuplicates && page == currentRoute) {
// return null;
// }
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
return global(id).currentState?.pushReplacementNamed(
page,
arguments: arguments,
);
return searchDelegate(id).offNamed(
page,
arguments: arguments,
id: id,
// preventDuplicates: preventDuplicates,
parameters: parameters,
);
}
/// **Navigation.popUntil()** shortcut.<br><br>
... ... @@ -648,34 +651,10 @@ you can only use widgets and widget functions here''';
/// or also like this:
/// `Get.until((route) => !Get.isDialogOpen())`, to make sure the
/// dialog is closed
void until(RoutePredicate predicate, {int? id}) {
// if (key.currentState.mounted) // add this if appear problems on future with route navigate
// when widget don't mounted
return global(id).currentState?.popUntil(predicate);
}
/// **Navigation.pushAndRemoveUntil()** shortcut.<br><br>
///
/// Push the given `page`, and then pop several pages in the stack until
/// [predicate] returns true
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// Obs: unlike other get methods, this one you need to send a function
/// that returns the widget to the page argument, like this:
/// Get.offUntil(GetPageRoute(page: () => HomePage()), predicate)
///
/// [predicate] can be used like this:
/// `Get.offUntil(page, (route) => (route as GetPageRoute).routeName == '/home')`
/// to pop routes in stack until home,
/// or also like this:
/// `Get.until((route) => !Get.isDialogOpen())`, to make sure the dialog
/// is closed
Future<T?>? offUntil<T>(Route<T> page, RoutePredicate predicate, {int? id}) {
void until(bool Function(GetPage<dynamic>) predicate, {int? id}) {
// if (key.currentState.mounted) // add this if appear problems on future with route navigate
// when widget don't mounted
return global(id).currentState?.pushAndRemoveUntil<T>(page, predicate);
return searchDelegate(id).backUntil(predicate);
}
/// **Navigation.pushNamedAndRemoveUntil()** shortcut.<br><br>
... ... @@ -698,7 +677,7 @@ you can only use widgets and widget functions here''';
/// Note: Always put a slash on the route name ('/page1'), to avoid unexpected errors
Future<T?>? offNamedUntil<T>(
String page,
RoutePredicate predicate, {
bool Function(GetPage<dynamic>)? predicate, {
int? id,
dynamic arguments,
Map<String, String>? parameters,
... ... @@ -708,11 +687,13 @@ you can only use widgets and widget functions here''';
page = uri.toString();
}
return global(id).currentState?.pushNamedAndRemoveUntil<T>(
page,
predicate,
arguments: arguments,
);
return searchDelegate(id).offNamedUntil<T>(
page,
predicate: predicate,
id: id,
arguments: arguments,
parameters: parameters,
);
}
/// **Navigation.popAndPushNamed()** shortcut.<br><br>
... ... @@ -737,11 +718,11 @@ you can only use widgets and widget functions here''';
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
return global(id).currentState?.popAndPushNamed(
page,
arguments: arguments,
result: result,
);
return searchDelegate(id).backAndtoNamed(
page,
arguments: arguments,
result: result,
);
}
/// **Navigation.removeRoute()** shortcut.<br><br>
... ... @@ -750,8 +731,8 @@ you can only use widgets and widget functions here''';
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
void removeRoute(Route<dynamic> route, {int? id}) {
return global(id).currentState?.removeRoute(route);
void removeRoute(String name, {int? id}) {
return searchDelegate(id).removeRoute(name);
}
/// **Navigation.pushNamedAndRemoveUntil()** shortcut.<br><br>
... ... @@ -776,7 +757,7 @@ you can only use widgets and widget functions here''';
/// Note: Always put a slash on the route ('/page1'), to avoid unexpected errors
Future<T?>? offAllNamed<T>(
String newRouteName, {
RoutePredicate? predicate,
// bool Function(GetPage<dynamic>)? predicate,
dynamic arguments,
int? id,
Map<String, String>? parameters,
... ... @@ -786,11 +767,13 @@ you can only use widgets and widget functions here''';
newRouteName = uri.toString();
}
return global(id).currentState?.pushNamedAndRemoveUntil<T>(
newRouteName,
predicate ?? (_) => false,
arguments: arguments,
);
return searchDelegate(id).offAllNamed<T>(
newRouteName,
//predicate: predicate ?? (_) => false,
arguments: arguments,
id: id,
parameters: parameters,
);
}
/// Returns true if a Snackbar, Dialog or BottomSheet is currently OPEN
... ... @@ -832,16 +815,19 @@ you can only use widgets and widget functions here''';
if (isSnackbarOpen) {
closeAllSnackbars();
}
navigator?.popUntil((route) {
return (!isDialogOpen! && !isBottomSheetOpen!);
});
searchDelegate(id)
.backUntil((route) => (!isDialogOpen! && !isBottomSheetOpen!));
// navigator?.popUntil((route) {
// return;
// });
}
if (canPop) {
if (global(id).currentState?.canPop() == true) {
global(id).currentState?.pop<T>(result);
if (searchDelegate(id).canBack == true) {
searchDelegate(id).back<T>(result);
}
} else {
global(id).currentState?.pop<T>(result);
searchDelegate(id).back<T>(result);
}
}
... ... @@ -856,7 +842,7 @@ you can only use widgets and widget functions here''';
times = 1;
}
var count = 0;
var back = global(id).currentState?.popUntil((route) => count++ == times);
var back = searchDelegate(id).backUntil((route) => count++ == times);
return back;
}
... ... @@ -887,8 +873,8 @@ you can only use widgets and widget functions here''';
/// By default, GetX will prevent you from push a route that you already in,
/// if you want to push anyway, set [preventDuplicates] to false
Future<T?>? off<T>(
dynamic page, {
bool opaque = false,
Widget Function() page, {
bool? opaque,
Transition? transition,
Curve? curve,
bool? popGesture,
... ... @@ -906,21 +892,34 @@ you can only use widgets and widget functions here''';
if (preventDuplicates && routeName == currentRoute) {
return null;
}
return global(id).currentState?.pushReplacement(GetPageRoute(
opaque: opaque,
gestureWidth: gestureWidth,
page: _resolvePage(page, 'off'),
binding: binding,
settings: RouteSettings(
arguments: arguments,
name: routeName,
),
routeName: routeName,
fullscreenDialog: fullscreenDialog,
popGesture: popGesture ?? defaultPopGesture,
transition: transition ?? defaultTransition,
curve: curve ?? defaultTransitionCurve,
transitionDuration: duration ?? defaultTransitionDuration));
return searchDelegate(id).off(
page,
opaque: opaque ?? true,
transition: transition,
curve: curve,
popGesture: popGesture,
id: id,
routeName: routeName,
arguments: arguments,
binding: binding,
fullscreenDialog: fullscreenDialog,
preventDuplicates: preventDuplicates,
duration: duration,
gestureWidth: gestureWidth,
);
}
Future<T?> offUntil<T>(
Widget Function() page,
bool Function(GetPage) predicate, [
Object? arguments,
int? id,
]) {
return searchDelegate(id).offUntil(
page,
predicate,
arguments,
);
}
///
... ... @@ -954,9 +953,9 @@ you can only use widgets and widget functions here''';
/// By default, GetX will prevent you from push a route that you already in,
/// if you want to push anyway, set [preventDuplicates] to false
Future<T?>? offAll<T>(
dynamic page, {
RoutePredicate? predicate,
bool opaque = false,
Widget Function() page, {
bool Function(GetPage<dynamic>)? predicate,
bool? opaque,
bool? popGesture,
int? id,
String? routeName,
... ... @@ -970,24 +969,21 @@ you can only use widgets and widget functions here''';
}) {
routeName ??= "/${page.runtimeType.toString()}";
routeName = _cleanRouteName(routeName);
return global(id).currentState?.pushAndRemoveUntil<T>(
GetPageRoute<T>(
opaque: opaque,
popGesture: popGesture ?? defaultPopGesture,
page: _resolvePage(page, 'offAll'),
binding: binding,
gestureWidth: gestureWidth,
settings: RouteSettings(
name: routeName,
arguments: arguments,
),
fullscreenDialog: fullscreenDialog,
routeName: routeName,
transition: transition ?? defaultTransition,
curve: curve ?? defaultTransitionCurve,
transitionDuration: duration ?? defaultTransitionDuration,
),
predicate ?? (route) => false);
return searchDelegate(id).offAll<T>(
page,
predicate: predicate,
opaque: opaque ?? true,
popGesture: popGesture,
id: id,
// routeName routeName,
arguments: arguments,
binding: binding,
fullscreenDialog: fullscreenDialog,
transition: transition,
curve: curve,
duration: duration,
gestureWidth: gestureWidth,
);
}
/// Takes a route [name] String generated by [to], [off], [offAll]
... ... @@ -1072,20 +1068,22 @@ you can only use widgets and widget functions here''';
return _getxController.addKey(newKey);
}
GlobalKey<NavigatorState>? nestedKey(dynamic key) {
GetDelegate? nestedKey(dynamic key) {
keys.putIfAbsent(
key,
() => GlobalKey<NavigatorState>(
debugLabel: 'Getx nested key: ${key.toString()}',
() => GetDelegate(
//debugLabel: 'Getx nested key: ${key.toString()}',
pages: [],
),
);
return keys[key];
}
GlobalKey<NavigatorState> global(int? k) {
GlobalKey<NavigatorState> _key;
GetDelegate searchDelegate(int? k) {
GetDelegate _key;
if (k == null) {
_key = key;
_key = Get.rootController.rootDelegate;
print(_key.navigatorKey);
} else {
if (!keys.containsKey(k)) {
throw 'Route id ($k) not found';
... ... @@ -1093,15 +1091,15 @@ you can only use widgets and widget functions here''';
_key = keys[k]!;
}
if (_key.currentContext == null && !testMode) {
throw """You are trying to use contextless navigation without
a GetMaterialApp or Get.key.
If you are testing your app, you can use:
[Get.testMode = true], or if you are running your app on
a physical device or emulator, you must exchange your [MaterialApp]
for a [GetMaterialApp].
""";
}
// if (_key.listenersLength == 0 && !testMode) {
// throw """You are trying to use contextless navigation without
// a GetMaterialApp or Get.key.
// If you are testing your app, you can use:
// [Get.testMode = true], or if you are running your app on
// a physical device or emulator, you must exchange your [MaterialApp]
// for a [GetMaterialApp].
// """;
// }
return _key;
}
... ... @@ -1225,7 +1223,7 @@ you can only use widgets and widget functions here''';
GlobalKey<NavigatorState> get key => _getxController.key;
Map<dynamic, GlobalKey<NavigatorState>> get keys => _getxController.keys;
Map<dynamic, GetDelegate> get keys => _getxController.keys;
GetMaterialController get rootController => _getxController;
... ... @@ -1250,7 +1248,8 @@ you can only use widgets and widget functions here''';
Routing get routing => _getxController.routing;
Map<String, String?> get parameters => _getxController.parameters;
Map<String, String?> get parameters =>
_getxController.rootDelegate.parameters;
set parameters(Map<String, String?> newParameters) =>
_getxController.parameters = newParameters;
... ... @@ -1312,15 +1311,15 @@ extension NavTwoExt on GetInterface {
// static GetDelegate? _delegate;
GetDelegate get rootDelegate => createDelegate();
GetDelegate createDelegate({
GetPage<dynamic>? notFoundRoute,
List<GetPage> pages = const [],
List<NavigatorObserver>? navigatorObservers,
TransitionDelegate<dynamic>? transitionDelegate,
PopMode backButtonPopMode = PopMode.History,
PreventDuplicateHandlingMode preventDuplicateHandlingMode =
PreventDuplicateHandlingMode.ReorderRoutes,
GlobalKey<NavigatorState>? navigatorKey,
}) {
if (routerDelegate == null) {
return routerDelegate = GetDelegate(
... ... @@ -1329,6 +1328,8 @@ extension NavTwoExt on GetInterface {
transitionDelegate: transitionDelegate,
backButtonPopMode: backButtonPopMode,
preventDuplicateHandlingMode: preventDuplicateHandlingMode,
pages: pages,
navigatorKey: navigatorKey,
);
} else {
return routerDelegate as GetDelegate;
... ...
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import '../../../get.dart';
class GetInformationParser extends RouteInformationParser<GetNavConfig> {
final String initialRoute;
GetInformationParser({
this.initialRoute = '/',
}) {
Get.log('GetInformationParser is created !');
}
@override
SynchronousFuture<GetNavConfig> parseRouteInformation(
RouteInformation routeInformation,
) {
var location = routeInformation.location;
if (location == '/') {
//check if there is a corresponding page
//if not, relocate to initialRoute
if (!Get.routeTree.routes.any((element) => element.name == '/')) {
location = initialRoute;
}
}
Get.log('GetInformationParser: route location: $location');
final matchResult = Get.routeTree.matchRoute(location ?? initialRoute);
return SynchronousFuture(
GetNavConfig(
currentTreeBranch: matchResult.currentTreeBranch,
location: location,
state: routeInformation.state,
),
);
}
@override
RouteInformation restoreRouteInformation(GetNavConfig config) {
return RouteInformation(
location: config.location,
state: config.state,
);
}
}
import 'package:flutter/widgets.dart';
import '../../../get.dart';
// class GetRouterState extends GetxController {
// GetRouterState({required this.currentTreeBranch});
// final List<GetPage> currentTreeBranch;
// GetPage? get currentPage => currentTreeBranch.last;
// static GetNavConfig? fromRoute(String route) {
// final res = Get.routeTree.matchRoute(route);
// if (res.treeBranch.isEmpty) return null;
// return GetNavConfig(
// currentTreeBranch: res.treeBranch,
// location: route,
// state: null,
// );
// }
// }
/// This config enables us to navigate directly to a sub-url
class GetNavConfig extends RouteInformation {
final List<GetPage> currentTreeBranch;
GetPage? get currentPage => currentTreeBranch.last;
GetNavConfig({
required this.currentTreeBranch,
required String? location,
required Object? state,
}) : super(
location: location,
state: state,
);
GetNavConfig copyWith({
List<GetPage>? currentTreeBranch,
required String? location,
required Object? state,
}) {
return GetNavConfig(
currentTreeBranch: currentTreeBranch ?? this.currentTreeBranch,
location: location ?? this.location,
state: state ?? this.state,
);
}
static GetNavConfig? fromRoute(String route) {
final res = Get.routeTree.matchRoute(route);
if (res.currentTreeBranch.isEmpty) return null;
return GetNavConfig(
currentTreeBranch: res.currentTreeBranch,
location: route,
state: null,
);
}
@override
String toString() => '''
======GetNavConfig=====\nlocation: $location\ncurrentTreeBranch: $currentTreeBranch\n======GetNavConfig=====''';
}
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../../get.dart';
import '../../../get_state_manager/src/simple/list_notifier.dart';
/// Enables the user to customize the intended pop behavior
///
/// Goes to either the previous history entry or the previous page entry
///
/// e.g. if the user navigates to these pages
/// 1) /home
/// 2) /home/products/1234
///
/// when popping on [History] mode, it will emulate a browser back button.
///
/// so the new history stack will be:
/// 1) /home
///
/// when popping on [Page] mode, it will only remove the last part of the route
/// so the new history stack will be:
/// 1) /home
/// 2) /home/products
///
/// another pop will change the history stack to:
/// 1) /home
enum PopMode {
History,
Page,
}
/// Enables the user to customize the behavior when pushing multiple routes that
/// shouldn't be duplicates
enum PreventDuplicateHandlingMode {
/// Removes the history entries until it reaches the old route
PopUntilOriginalRoute,
/// Simply don't push the new route
DoNothing,
/// Recommended - Moves the old route entry to the front
///
/// With this mode, you guarantee there will be only one
/// route entry for each location
ReorderRoutes
}
class GetDelegate extends RouterDelegate<GetNavConfig>
with ListNotifierSingleMixin {
final List<GetNavConfig> history = <GetNavConfig>[];
final PopMode backButtonPopMode;
final PreventDuplicateHandlingMode preventDuplicateHandlingMode;
final GetPage notFoundRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionDelegate<dynamic>? transitionDelegate;
final Iterable<GetPage> Function(GetNavConfig currentNavStack)?
pickPagesForRootNavigator;
GlobalKey<NavigatorState> get navigatorKey => Get.key;
GetDelegate({
GetPage? notFoundRoute,
this.navigatorObservers,
this.transitionDelegate,
this.backButtonPopMode = PopMode.History,
this.preventDuplicateHandlingMode =
PreventDuplicateHandlingMode.ReorderRoutes,
this.pickPagesForRootNavigator,
}) : notFoundRoute = notFoundRoute ??
GetPage(
name: '/404',
page: () => Scaffold(
body: Text('Route not found'),
),
) {
Get.log('GetDelegate is created !');
}
Future<GetNavConfig?> runMiddleware(GetNavConfig config) async {
final middlewares = config.currentTreeBranch.last.middlewares;
if (middlewares == null) {
return config;
}
var iterator = config;
for (var item in middlewares) {
var redirectRes = await item.redirectDelegate(iterator);
if (redirectRes == null) return null;
iterator = redirectRes;
}
return iterator;
}
Future<void> _unsafeHistoryAdd(GetNavConfig config) async {
final res = await runMiddleware(config);
if (res == null) return;
history.add(res);
}
Future<void> _unsafeHistoryRemove(GetNavConfig config) async {
var index = history.indexOf(config);
if (index >= 0) await _unsafeHistoryRemoveAt(index);
}
Future<GetNavConfig?> _unsafeHistoryRemoveAt(int index) async {
if (index == history.length - 1 && history.length > 1) {
//removing WILL update the current route
final toCheck = history[history.length - 2];
final resMiddleware = await runMiddleware(toCheck);
if (resMiddleware == null) return null;
history[history.length - 2] = resMiddleware;
}
return history.removeAt(index);
}
T arguments<T>() {
return currentConfiguration?.currentPage?.arguments as T;
}
Map<String, String> get parameters {
return currentConfiguration?.currentPage?.parameters ?? {};
}
/// Adds a new history entry and waits for the result
Future<void> pushHistory(
GetNavConfig config, {
bool rebuildStack = true,
}) async {
//this changes the currentConfiguration
await _pushHistory(config);
if (rebuildStack) {
refresh();
}
}
Future<void> _removeHistoryEntry(GetNavConfig entry) async {
await _unsafeHistoryRemove(entry);
}
Future<void> _pushHistory(GetNavConfig config) async {
if (config.currentPage!.preventDuplicates) {
final originalEntryIndex =
history.indexWhere((element) => element.location == config.location);
if (originalEntryIndex >= 0) {
switch (preventDuplicateHandlingMode) {
case PreventDuplicateHandlingMode.PopUntilOriginalRoute:
await backUntil(config.location!, popMode: PopMode.Page);
break;
case PreventDuplicateHandlingMode.ReorderRoutes:
await _unsafeHistoryRemoveAt(originalEntryIndex);
await _unsafeHistoryAdd(config);
break;
case PreventDuplicateHandlingMode.DoNothing:
default:
break;
}
return;
}
}
await _unsafeHistoryAdd(config);
}
Future<GetNavConfig?> _popHistory() async {
if (!_canPopHistory()) return null;
return await _doPopHistory();
}
Future<GetNavConfig?> _doPopHistory() async {
return await _unsafeHistoryRemoveAt(history.length - 1);
}
Future<GetNavConfig?> _popPage() async {
if (!_canPopPage()) return null;
return await _doPopPage();
}
Future<GetNavConfig?> _pop(PopMode mode) async {
switch (mode) {
case PopMode.History:
return await _popHistory();
case PopMode.Page:
return await _popPage();
default:
return null;
}
}
// returns the popped page
Future<GetNavConfig?> _doPopPage() async {
final currentBranch = currentConfiguration?.currentTreeBranch;
if (currentBranch != null && currentBranch.length > 1) {
//remove last part only
final remaining = currentBranch.take(currentBranch.length - 1);
final prevHistoryEntry =
history.length > 1 ? history[history.length - 2] : null;
//check if current route is the same as the previous route
if (prevHistoryEntry != null) {
//if so, pop the entire history entry
final newLocation = remaining.last.name;
final prevLocation = prevHistoryEntry.location;
if (newLocation == prevLocation) {
//pop the entire history entry
return await _popHistory();
}
}
//create a new route with the remaining tree branch
final res = await _popHistory();
await _pushHistory(
GetNavConfig(
currentTreeBranch: remaining.toList(),
location: remaining.last.name,
state: null, //TOOD: persist state??
),
);
return res;
} else {
//remove entire entry
return await _popHistory();
}
}
Future<GetNavConfig?> popHistory() async {
return await _popHistory();
}
bool _canPopHistory() {
return history.length > 1;
}
Future<bool> canPopHistory() {
return SynchronousFuture(_canPopHistory());
}
bool _canPopPage() {
final currentTreeBranch = currentConfiguration?.currentTreeBranch;
if (currentTreeBranch == null) return false;
return currentTreeBranch.length > 1 ? true : _canPopHistory();
}
Future<bool> canPopPage() {
return SynchronousFuture(_canPopPage());
}
bool _canPop(PopMode mode) {
switch (mode) {
case PopMode.History:
return _canPopHistory();
case PopMode.Page:
default:
return _canPopPage();
}
}
/// gets the visual pages from the current history entry
///
/// visual pages must have [GetPage.participatesInRootNavigator] set to true
Iterable<GetPage> getVisualPages(GetNavConfig currentHistory) {
final res = currentHistory.currentTreeBranch
.where((r) => r.participatesInRootNavigator != null);
if (res.length == 0) {
//default behavoir, all routes participate in root navigator
return history.map((e) => e.currentPage!);
} else {
//user specified at least one participatesInRootNavigator
return res
.where((element) => element.participatesInRootNavigator == true);
}
}
@override
Widget build(BuildContext context) {
final currentHistory = currentConfiguration;
final pages = currentHistory == null
? <GetPage>[]
: pickPagesForRootNavigator?.call(currentHistory) ??
getVisualPages(currentHistory);
if (pages.length == 0) return SizedBox.shrink();
return GetNavigator(
key: navigatorKey,
onPopPage: _onPopVisualRoute,
pages: pages.toList(),
observers: [
GetObserver(),
...?navigatorObservers,
],
transitionDelegate:
transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(),
);
}
@override
Future<void> setNewRoutePath(GetNavConfig configuration) async {
await pushHistory(configuration);
}
@override
GetNavConfig? get currentConfiguration {
if (history.isEmpty) return null;
final route = history.last;
return route;
}
Future<void> toNamed(
String page, {
PageSettings? arguments,
Map<String, String>? parameters,
}) async {
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
final decoder = Get.routeTree.matchRoute(page, arguments: arguments);
decoder.replaceArguments(arguments);
if (decoder.route != null) {
await pushHistory(
GetNavConfig(
currentTreeBranch: decoder.currentTreeBranch,
location: page,
state: null, //TODO: persist state?
),
);
} else {
await pushHistory(
GetNavConfig(
currentTreeBranch: [notFoundRoute],
location: notFoundRoute.name,
state: null, //TODO: persist state?
),
);
}
}
//pops the previous route (if there is one) and goes to new route
Future<void> offNamed(
String page, {
PageSettings? arguments,
Map<String, String>? parameters,
PopMode popMode = PopMode.History,
}) async {
await popRoute(popMode: popMode);
return toNamed(page, arguments: arguments, parameters: parameters);
}
/// Removes routes according to [PopMode]
/// until it reaches the specifc [fullRoute],
/// DOES NOT remove the [fullRoute]
Future<void> backUntil(
String fullRoute, {
PopMode popMode = PopMode.History,
}) async {
// remove history or page entries until you meet route
var iterator = currentConfiguration;
while (_canPop(popMode) &&
iterator != null &&
iterator.location != fullRoute) {
await _pop(popMode);
// replace iterator
iterator = currentConfiguration;
}
refresh();
}
Future<bool> handlePopupRoutes({
Object? result,
}) async {
Route? currentRoute;
navigatorKey.currentState!.popUntil((route) {
currentRoute = route;
return true;
});
if (currentRoute is PopupRoute) {
return await navigatorKey.currentState!.maybePop(result);
}
return false;
}
@override
Future<bool> popRoute({
Object? result,
PopMode? popMode,
}) async {
//Returning false will cause the entire app to be popped.
final wasPopup = await handlePopupRoutes(result: result);
if (wasPopup) return true;
final _popped = await _pop(popMode ?? backButtonPopMode);
refresh();
if (_popped != null) {
//emulate the old pop with result
return true;
}
return false;
}
bool _onPopVisualRoute(Route<dynamic> route, dynamic result) {
final didPop = route.didPop(result);
if (!didPop) {
return false;
}
final settings = route.settings;
if (settings is GetPage) {
final config = history.cast<GetNavConfig?>().firstWhere(
(element) => element?.currentPage == settings,
orElse: () => null,
);
if (config != null) {
_removeHistoryEntry(config);
}
}
refresh();
return true;
}
}
import 'dart:async';
import 'package:flutter/material.dart';
import '../../../../get_state_manager/src/simple/get_state.dart';
import '../../../../get_state_manager/src/simple/list_notifier.dart';
import '../../../../get_utils/src/platform/platform.dart';
import '../../../../route_manager.dart';
import '../../root/parse_route.dart';
import '../url_strategy/url_strategy.dart';
class GetNavigation extends RouterDelegate<RouteDecoder>
with
ListNotifierSingleMixin,
PopNavigatorRouterDelegateMixin<RouteDecoder>,
IGetNavigation {
final _activePages = <RouteDecoder>[];
final GetPage _unknownPage;
final List<NavigatorObserver>? navigatorObservers;
final String? restorationScopeId;
@override
final navigatorKey = GlobalKey<NavigatorState>();
static late final GetNavigation instance;
@override
Future<void> setInitialRoutePath(RouteDecoder configuration) async {
setNewRoutePath(configuration);
}
GetNavigation({
String? initialPage,
required List<GetPage> pages,
GetPage? unknownPage,
this.navigatorObservers,
this.restorationScopeId,
bool showHashOnUrl = false,
}) : _unknownPage = unknownPage ??
GetPage(
name: '/404',
page: () =>
Scaffold(body: Center(child: Text('Route not found'))),
) {
if (!showHashOnUrl && GetPlatform.isWeb) setUrlStrategy();
Get.addPages(pages);
Get.addPage(_unknownPage);
// setNewRoutePath(_buildPageSettings(_initialPage));
}
PageSettings _buildPageSettings(String page, [Object? data]) {
var uri = Uri.parse(page);
return PageSettings(uri, data);
}
@protected
RouteDecoder? _getRouteDecoder<T>(PageSettings arguments) {
var page = arguments.uri.path;
final parameters = arguments.params;
if (parameters.isNotEmpty) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
final decoder = Get.routeTree.matchRoute(page, arguments: arguments);
final route = decoder.route;
if (route == null) return null;
return _configureRouterDecoder(decoder, arguments);
// decoder.replaceArguments(arguments);
//decoder.replaceParameters(arguments)
// return decoder;
}
@protected
RouteDecoder _configureRouterDecoder<T>(
RouteDecoder decoder, PageSettings arguments) {
// final decoder = Get.routeTree.matchRoute(page.name, arguments: arguments);
decoder.route = decoder.route?.copy(
completer: _activePages.isEmpty ? null : Completer(),
arguments: arguments,
parameters: arguments.params,
);
return decoder;
}
Future<T?> _push<T>(RouteDecoder activePage) async {
// final activePage = _configureRouterDecoder<T>(page, arguments);
final onStackPage = _activePages.firstWhereOrNull(
(element) => element.route?.key == activePage.route?.key);
/// There are no duplicate routes in the stack
if (onStackPage == null) {
_activePages.add(activePage);
} else {
/// There are duplicate routes, reorder
_activePages.remove(onStackPage);
_activePages.add(onStackPage);
}
refresh();
return activePage.route?.completer?.future as Future<T?>?;
}
Future<T?> _replace<T>(PageSettings arguments, GetPage<T> page) async {
final index = _activePages.length > 1 ? _activePages.length - 1 : 0;
Get.addPage(page);
final route = _getRouteDecoder(arguments);
final activePage = _configureRouterDecoder<T>(route!, arguments);
_activePages[index] = activePage;
refresh();
final result = await activePage.route?.completer?.future as Future<T?>;
Get.removePage(page);
return result;
}
Future<T?> _replaceNamed<T>(RouteDecoder activePage) async {
final index = _activePages.length > 1 ? _activePages.length - 1 : 0;
// final activePage = _configureRouterDecoder<T>(page, arguments);
_activePages[index] = activePage;
refresh();
final result = await activePage.route?.completer?.future as Future<T?>;
return result;
}
/// Takes a route [name] String generated by [to], [off], [offAll]
/// (and similar context navigation methods), cleans the extra chars and
/// accommodates the format.
/// TODO: check for a more "appealing" URL naming convention.
/// `() => MyHomeScreenView` becomes `/my-home-screen-view`.
String _cleanRouteName(String name) {
name = name.replaceAll('() => ', '');
/// uncommonent for URL styling.
// name = name.paramCase!;
if (!name.startsWith('/')) {
name = '/$name';
}
return Uri.tryParse(name)?.toString() ?? name;
}
@protected
void _popWithResult<T>([T? result]) {
final completer = _activePages.removeLast().route?.completer;
if (completer?.isCompleted == false) completer!.complete(result);
}
@override
Future<T?> toNamed<T>(String page, [Object? data]) async {
final arguments = _buildPageSettings(page, data);
final route = _getRouteDecoder<T>(arguments);
if (route != null) {
return _push<T>(route);
}
throw 'Route $page not registered';
}
@override
Future<T?> to<T>(
Widget Function() page, {
bool? opaque,
Transition? transition,
Curve? curve,
Duration? duration,
int? id,
String? routeName,
bool fullscreenDialog = false,
dynamic arguments,
Binding? binding,
bool preventDuplicates = true,
bool? popGesture,
bool showCupertinoParallax = true,
double Function(BuildContext context)? gestureWidth,
}) async {
routeName = _cleanRouteName("/${page.runtimeType}");
final getPage = GetPage<T>(
name: routeName,
opaque: opaque ?? true,
page: page,
gestureWidth: gestureWidth,
showCupertinoParallax: showCupertinoParallax,
popGesture: popGesture ?? Get.defaultPopGesture,
transition: transition ?? Get.defaultTransition,
curve: curve ?? Get.defaultTransitionCurve,
fullscreenDialog: fullscreenDialog,
binding: binding,
transitionDuration: duration ?? Get.defaultTransitionDuration,
);
Get.addPage(getPage);
final args = _buildPageSettings(routeName, arguments);
final route = _getRouteDecoder<T>(args);
final result = await _push<T>(route!);
Get.removePage(getPage);
return result;
}
@override
Future<T?> off<T>(
Widget Function() page, {
bool? opaque,
Transition? transition,
Curve? curve,
Duration? duration,
int? id,
String? routeName,
bool fullscreenDialog = false,
dynamic arguments,
Binding? binding,
bool preventDuplicates = true,
bool? popGesture,
bool showCupertinoParallax = true,
double Function(BuildContext context)? gestureWidth,
}) async {
routeName = _cleanRouteName("/${page.runtimeType}");
final route = GetPage<T>(
name: routeName,
opaque: opaque ?? true,
page: page,
gestureWidth: gestureWidth,
showCupertinoParallax: showCupertinoParallax,
popGesture: popGesture ?? Get.defaultPopGesture,
transition: transition ?? Get.defaultTransition,
curve: curve ?? Get.defaultTransitionCurve,
fullscreenDialog: fullscreenDialog,
binding: binding,
transitionDuration: duration ?? Get.defaultTransitionDuration,
);
final args = _buildPageSettings(routeName, arguments);
return _replace(args, route);
}
@override
Future<T?>? offAll<T>(
Widget Function() page, {
bool Function(GetPage route)? predicate,
bool opaque = true,
bool? popGesture,
int? id,
String? routeName,
dynamic arguments,
Binding? binding,
bool fullscreenDialog = false,
Transition? transition,
Curve? curve,
Duration? duration,
bool showCupertinoParallax = true,
double Function(BuildContext context)? gestureWidth,
}) async {
routeName = _cleanRouteName("/${page.runtimeType}");
final route = GetPage<T>(
name: routeName,
opaque: opaque,
page: page,
gestureWidth: gestureWidth,
showCupertinoParallax: showCupertinoParallax,
popGesture: popGesture ?? Get.defaultPopGesture,
transition: transition ?? Get.defaultTransition,
curve: curve ?? Get.defaultTransitionCurve,
fullscreenDialog: fullscreenDialog,
binding: binding,
transitionDuration: duration ?? Get.defaultTransitionDuration,
);
final args = _buildPageSettings(routeName, arguments);
final newPredicate = predicate ?? (route) => false;
while (_activePages.length > 1 && !newPredicate(_activePages.last.route!)) {
_popWithResult();
}
return _replace(args, route);
}
@override
Future<T?>? offAllNamed<T>(
String page, {
bool Function(GetPage route)? predicate,
dynamic arguments,
int? id,
Map<String, String>? parameters,
}) async {
final args = _buildPageSettings(page, arguments);
final route = _getRouteDecoder<T>(args);
if (route == null) return null;
// final newPredicate = predicate ?? (route) => false;
while (_activePages.length > 1) {
_activePages.removeLast();
}
return _replaceNamed(route);
}
@override
Future<T?> offNamed<T>(String page, [Object? data]) async {
final arguments = _buildPageSettings(page, data);
final route = _getRouteDecoder<T>(arguments);
if (route == null) return null;
_popWithResult();
return _push<T>(route);
}
@override
Future<T?> toAndOffUntil<T>(
String page,
bool Function(GetPage) predicate, [
Object? data,
]) async {
final arguments = _buildPageSettings(page, data);
final route = _getRouteDecoder<T>(arguments);
if (route == null) return null;
while (_activePages.isNotEmpty && !predicate(_activePages.last.route!)) {
_popWithResult();
}
return _push<T>(route);
}
@override
void back<T>([T? result]) {
_checkIfCanBack();
_popWithResult<T>(result);
refresh();
}
void _checkIfCanBack() {
assert(() {
if (!canBack) {
final last = _activePages.last;
final name = last.route?.name;
throw 'The page $name cannot be popped';
}
return true;
}());
}
@override
Future<R?> backAndtoNamed<T, R>(String page,
{T? result, Object? data}) async {
final arguments = _buildPageSettings(page, data);
final route = _getRouteDecoder<R>(arguments);
if (route == null) return null;
_popWithResult<T>(result);
return _push<R>(route);
}
@override
void backUntil(bool Function(GetPage) predicate) {
while (canBack && !predicate(_activePages.last.route!)) {
_popWithResult();
}
refresh();
}
@override
void goToUnknownPage([bool clearPages = false]) {
if (clearPages) _activePages.clear();
final pageSettings = _buildPageSettings(_unknownPage.name);
final routeDecoder = _getRouteDecoder(pageSettings);
_push(routeDecoder!);
}
@override
bool get canBack => _activePages.length > 1;
bool _onPopPage(Route route, result) {
if (!route.didPop(result)) return false;
_popWithResult(result);
refresh();
return true;
}
@override
Widget build(BuildContext context) {
if (_activePages.isEmpty) return SizedBox.shrink();
return GetNavigator(
key: navigatorKey,
restorationScopeId: restorationScopeId,
observers: navigatorObservers,
pages: _activePages.map((decoder) => decoder.route!).toList(),
onPopPage: _onPopPage,
);
}
@override
Future<void> setNewRoutePath(RouteDecoder configuration) async {
// if (_activePages.isEmpty) return;
final page = configuration.route;
if (page == null) {
goToUnknownPage();
return;
} else {
_push(configuration);
}
}
@override
RouteDecoder? get currentConfiguration {
if (_activePages.isEmpty) {
return null;
}
return _activePages.last;
}
}
export 'get_navigation_imp.dart';
export 'get_navigation_interface.dart';
export 'new_get_information_parser.dart';
export 'page_arguments.dart';
... ... @@ -62,7 +62,7 @@ class GetCupertinoApp extends StatelessWidget {
final BackButtonDispatcher? backButtonDispatcher;
final CupertinoThemeData? theme;
final bool useInheritedMediaQuery;
const GetCupertinoApp({
GetCupertinoApp({
Key? key,
this.theme,
this.navigatorKey,
... ... @@ -114,12 +114,37 @@ class GetCupertinoApp extends StatelessWidget {
this.highContrastTheme,
this.highContrastDarkTheme,
this.actions,
}) : routeInformationProvider = null,
routeInformationParser = null,
routerDelegate = null,
}) : routerDelegate = Get.createDelegate(
pages: getPages ??
[
GetPage(
name: _cleanRouteName("/${home.runtimeType}"),
page: () => home!,
),
],
notFoundRoute: unknownRoute,
navigatorKey: navigatorKey,
),
routeInformationParser = Get.createInformationParser(
initialRoute: initialRoute ??
getPages?.first.name ??
_cleanRouteName("/${home.runtimeType}"),
),
routeInformationProvider = null,
backButtonDispatcher = null,
super(key: key);
static String _cleanRouteName(String name) {
name = name.replaceAll('() => ', '');
/// uncommonent for URL styling.
// name = name.paramCase!;
if (!name.startsWith('/')) {
name = '/$name';
}
return Uri.tryParse(name)?.toString() ?? name;
}
GetCupertinoApp.router({
Key? key,
this.theme,
... ... @@ -167,6 +192,7 @@ class GetCupertinoApp extends StatelessWidget {
this.getPages,
this.unknownRoute,
}) : routerDelegate = routerDelegate ??= Get.createDelegate(
pages: getPages ?? [],
notFoundRoute: unknownRoute,
),
routeInformationParser =
... ...
... ... @@ -7,6 +7,64 @@ import '../../../get_state_manager/get_state_manager.dart';
import '../../../get_utils/get_utils.dart';
import '../../get_navigation.dart';
// extension GetMaterialExt on MaterialApp{
// MaterialApp get(){
// final app = MaterialApp.router(
// key: key,
// routeInformationProvider:routeInformationProvider,
// scaffoldMessengerKey:scaffoldMessengerKey,
// // RouteInformationParser<Object>? routeInformationParser,
// // RouterDelegate<Object>? routerDelegate,
// backButtonDispatcher:backButtonDispatcher,
// builder:builder,
// title:title,
// onGenerateTitle:onGenerateTitle,
// color:color,
// theme:theme,
// darkTheme:darkTheme,
// useInheritedMediaQuery:useInheritedMediaQuery,
// highContrastTheme:highContrastTheme,
// highContrastDarkTheme:highContrastDarkTheme,
// themeMode :themeMode,
// locale:locale,
// localizationsDelegates:localizationsDelegates,
// localeListResolutionCallback: localeListResolutionCallback,
// localeResolutionCallback: localeResolutionCallback,
// supportedLocales: supportedLocales,
// debugShowMaterialGrid :debugShowMaterialGrid,
// showPerformanceOverlay :showPerformanceOverlay,
// checkerboardRasterCacheImages :checkerboardRasterCacheImages,
// checkerboardOffscreenLayers :checkerboardOffscreenLayers,
// showSemanticsDebugger :showSemanticsDebugger,
// debugShowCheckedModeBanner :debugShowCheckedModeBanner,
// shortcuts: shortcuts,
// scrollBehavior:scrollBehavior,
// actions:actions,
// customTransition:customTransition,
// translationsKeys:translationsKeys,
// translations:translations,
// textDirection:textDirection,
// fallbackLocale:fallbackLocale,
// routingCallback:routingCallback,
// defaultTransition:defaultTransition,
// opaqueRoute:opaqueRoute,
// onInit:onInit,
// onReady:onReady,
// onDispose:onDispose,
// enableLog:enableLog,
// logWriterCallback:logWriterCallback,
// popGesture:popGesture,
// smartManagement:smartManagement
// initialBinding:initialBinding,
// transitionDuration:transitionDuration,
// defaultGlobalState:defaultGlobalState,
// getPages:getPages,
// navigatorObservers: navigatorObservers,
// unknownRoute:unknownRoute,
// );
// }
// }
class GetMaterialApp extends StatelessWidget {
final GlobalKey<NavigatorState>? navigatorKey;
... ... @@ -66,7 +124,7 @@ class GetMaterialApp extends StatelessWidget {
final RouterDelegate<Object>? routerDelegate;
final BackButtonDispatcher? backButtonDispatcher;
final bool useInheritedMediaQuery;
const GetMaterialApp({
GetMaterialApp({
Key? key,
this.navigatorKey,
this.scaffoldMessengerKey,
... ... @@ -123,12 +181,37 @@ class GetMaterialApp extends StatelessWidget {
this.highContrastTheme,
this.highContrastDarkTheme,
this.actions,
}) : routeInformationProvider = null,
routeInformationParser = null,
routerDelegate = null,
}) : routerDelegate = Get.createDelegate(
pages: getPages ??
[
GetPage(
name: _cleanRouteName("/${home.runtimeType}"),
page: () => home!,
),
],
notFoundRoute: unknownRoute,
navigatorKey: navigatorKey,
),
routeInformationParser = Get.createInformationParser(
initialRoute: initialRoute ??
getPages?.first.name ??
_cleanRouteName("/${home.runtimeType}"),
),
routeInformationProvider = null,
backButtonDispatcher = null,
super(key: key);
static String _cleanRouteName(String name) {
name = name.replaceAll('() => ', '');
/// uncommonent for URL styling.
// name = name.paramCase!;
if (!name.startsWith('/')) {
name = '/$name';
}
return Uri.tryParse(name)?.toString() ?? name;
}
GetMaterialApp.router({
Key? key,
this.routeInformationProvider,
... ... @@ -182,13 +265,13 @@ class GetMaterialApp extends StatelessWidget {
this.navigatorObservers,
this.unknownRoute,
}) : routerDelegate = routerDelegate ??= Get.createDelegate(
pages: getPages ?? [],
notFoundRoute: unknownRoute,
),
routeInformationParser =
routeInformationParser ??= Get.createInformationParser(
initialRoute: getPages?.first.name ?? '/',
),
//navigatorObservers = null,
navigatorKey = null,
onGenerateRoute = null,
home = null,
... ...
... ... @@ -28,14 +28,14 @@ class GetMaterialController extends SuperController {
CustomTransition? customTransition;
var _key = GlobalKey<NavigatorState>(debugLabel: 'Key Created by default');
Map<dynamic, GetDelegate> keys = {};
Map<dynamic, GlobalKey<NavigatorState>> keys = {};
GlobalKey<NavigatorState> get key => rootDelegate.navigatorKey;
GlobalKey<NavigatorState> get key => _key;
GetDelegate get rootDelegate => Get.createDelegate();
GlobalKey<NavigatorState>? addKey(GlobalKey<NavigatorState> newKey) {
_key = newKey;
rootDelegate.navigatorKey = newKey;
return key;
}
... ...
... ... @@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import '../../../get.dart';
import '../router_report.dart';
import 'get_transition_mixin.dart';
@optionalTypeArgs
mixin RouteReportMixin<T extends StatefulWidget> on State<T> {
... ... @@ -33,10 +32,8 @@ mixin PageRouteReportMixin<T> on Route<T> {
}
}
class GetPageRoute<T> extends PageRoute<T> //MaterialPageRoute<T>
with
GetPageRouteTransitionMixin<T>,
PageRouteReportMixin {
class GetPageRoute<T> extends MaterialPageRoute<T>
with GetPageRouteTransitionMixin<T>, PageRouteReportMixin {
/// Creates a page route for use in an iOS designed app.
///
/// The [builder], [maintainState], and [fullscreenDialog] arguments must not
... ... @@ -67,7 +64,7 @@ class GetPageRoute<T> extends PageRoute<T> //MaterialPageRoute<T>
}) : super(
settings: settings,
fullscreenDialog: fullscreenDialog,
// builder: (context) => Container(),
builder: (context) => Container(),
);
@override
... ...
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import '../../../../get.dart';
import '../../root/parse_route.dart';
import '../../../get.dart';
import 'parse_route.dart';
class NewGetInformationParser extends RouteInformationParser<RouteDecoder> {
class GetInformationParser extends RouteInformationParser<RouteDecoder> {
final String initialRoute;
NewGetInformationParser({
GetInformationParser({
required this.initialRoute,
}) {
Get.log('GetInformationParser is created !');
... ... @@ -29,19 +29,7 @@ class NewGetInformationParser extends RouteInformationParser<RouteDecoder> {
final routeName = location ?? initialRoute;
return SynchronousFuture(_locationToRouteDecoder(routeName));
}
RouteDecoder _locationToRouteDecoder(String location) {
var uri = Uri.parse(location);
final args = PageSettings(uri);
final decoder = Get.routeTree.matchRoute(location, arguments: args);
decoder.route = decoder.route?.copy(
completer: null,
arguments: args,
parameters: args.params,
);
return decoder;
return SynchronousFuture(RouteDecoder.fromRoute(routeName));
}
@override
... ...
import 'package:flutter/widgets.dart';
import '../../../../get_state_manager/src/simple/get_state.dart';
import '../../routes/get_route.dart';
import '../../routes/transitions_type.dart';
import '../../../get_state_manager/src/simple/get_state.dart';
import '../routes/get_route.dart';
import '../routes/transitions_type.dart';
mixin IGetNavigation {
Future<T?> to<T>(
... ... @@ -56,31 +56,54 @@ mixin IGetNavigation {
double Function(BuildContext context)? gestureWidth,
});
Future<T?> toNamed<T>(String page, [Object? data]);
Future<T?> toNamed<T>(
String page, {
dynamic arguments,
int? id,
bool preventDuplicates = true,
Map<String, String>? parameters,
});
Future<T?> offNamed<T>(String page, [Object? data]);
Future<T?> offNamed<T>(
String page, {
dynamic arguments,
int? id,
Map<String, String>? parameters,
});
Future<T?>? offAllNamed<T>(
String newRouteName, {
// bool Function(GetPage route)? predicate,
dynamic arguments,
int? id,
Map<String, String>? parameters,
});
Future<T?>? offNamedUntil<T>(
String page, {
bool Function(GetPage route)? predicate,
dynamic arguments,
int? id,
Map<String, String>? parameters,
});
Future<T?> toAndOffUntil<T>(
Future<T?> toNamedAndOffUntil<T>(
String page,
bool Function(GetPage) predicate, [
Object? data,
]);
Future<T?> offUntil<T>(
Widget Function() page,
bool Function(GetPage) predicate, [
Object? arguments,
]);
void back<T>([T? result]);
Future<R?> backAndtoNamed<T, R>(String page, {T? result, Object? data});
Future<R?> backAndtoNamed<T, R>(String page, {T? result, Object? arguments});
void backUntil(bool Function(GetPage) predicate);
void goToUnknownPage([bool clearPages = true]);
bool get canBack;
}
... ...
... ... @@ -45,6 +45,8 @@ class GetPage<T> extends Page<T> {
final GetPage? unknownRoute;
final bool showCupertinoParallax;
final PreventDuplicateHandlingMode preventDuplicateHandlingMode;
GetPage({
required this.name,
required this.page,
... ... @@ -70,6 +72,8 @@ class GetPage<T> extends Page<T> {
this.arguments,
this.showCupertinoParallax = true,
this.preventDuplicates = true,
this.preventDuplicateHandlingMode =
PreventDuplicateHandlingMode.ReorderRoutes,
this.completer,
}) : path = _nameToRegex(name),
assert(name.startsWith('/'),
... ...
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../../get_state_manager/src/simple/get_state.dart';
import '../../../get_state_manager/src/simple/list_notifier.dart';
import '../../../get_utils/src/platform/platform.dart';
import '../../../route_manager.dart';
/// Enables the user to customize the intended pop behavior
///
/// Goes to either the previous _activePages entry or the previous page entry
///
/// e.g. if the user navigates to these pages
/// 1) /home
/// 2) /home/products/1234
///
/// when popping on [History] mode, it will emulate a browser back button.
///
/// so the new _activePages stack will be:
/// 1) /home
///
/// when popping on [Page] mode, it will only remove the last part of the route
/// so the new _activePages stack will be:
/// 1) /home
/// 2) /home/products
///
/// another pop will change the _activePages stack to:
/// 1) /home
enum PopMode {
History,
Page,
}
/// Enables the user to customize the behavior when pushing multiple routes that
/// shouldn't be duplicates
enum PreventDuplicateHandlingMode {
/// Removes the _activePages entries until it reaches the old route
PopUntilOriginalRoute,
/// Simply don't push the new route
DoNothing,
/// Recommended - Moves the old route entry to the front
///
/// With this mode, you guarantee there will be only one
/// route entry for each location
ReorderRoutes,
Recreate,
}
class GetDelegate extends RouterDelegate<RouteDecoder>
with
ListNotifierSingleMixin,
PopNavigatorRouterDelegateMixin<RouteDecoder>,
IGetNavigation {
final List<RouteDecoder> _activePages = <RouteDecoder>[];
final PopMode backButtonPopMode;
final PreventDuplicateHandlingMode preventDuplicateHandlingMode;
final GetPage notFoundRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionDelegate<dynamic>? transitionDelegate;
final Iterable<GetPage> Function(RouteDecoder currentNavStack)?
pickPagesForRootNavigator;
// GlobalKey<NavigatorState> get navigatorKey => Get.key;
@override
GlobalKey<NavigatorState> navigatorKey;
final String? restorationScopeId;
GetDelegate({
GetPage? notFoundRoute,
this.navigatorObservers,
this.transitionDelegate,
this.backButtonPopMode = PopMode.History,
this.preventDuplicateHandlingMode =
PreventDuplicateHandlingMode.ReorderRoutes,
this.pickPagesForRootNavigator,
this.restorationScopeId,
bool showHashOnUrl = false,
GlobalKey<NavigatorState>? navigatorKey,
required List<GetPage> pages,
}) : navigatorKey = navigatorKey ?? GlobalKey<NavigatorState>(),
notFoundRoute = notFoundRoute ??= GetPage(
name: '/404',
page: () => Scaffold(
body: Center(child: Text('Route not found')),
),
) {
if (!showHashOnUrl && GetPlatform.isWeb) setUrlStrategy();
Get.addPages(pages);
Get.addPage(notFoundRoute);
Get.log('GetDelegate is created !');
}
Future<RouteDecoder?> runMiddleware(RouteDecoder config) async {
final middlewares = config.currentTreeBranch.last.middlewares;
if (middlewares == null) {
return config;
}
var iterator = config;
for (var item in middlewares) {
var redirectRes = await item.redirectDelegate(iterator);
if (redirectRes == null) return null;
iterator = redirectRes;
}
return iterator;
}
Future<void> _unsafeHistoryAdd(RouteDecoder config) async {
final res = await runMiddleware(config);
if (res == null) return;
_activePages.add(res);
}
Future<T?> _unsafeHistoryRemove<T>(RouteDecoder config, T result) async {
var index = _activePages.indexOf(config);
if (index >= 0) return _unsafeHistoryRemoveAt(index, result);
}
Future<T?> _unsafeHistoryRemoveAt<T>(int index, T result) async {
if (index == _activePages.length - 1 && _activePages.length > 1) {
//removing WILL update the current route
final toCheck = _activePages[_activePages.length - 2];
final resMiddleware = await runMiddleware(toCheck);
if (resMiddleware == null) return null;
_activePages[_activePages.length - 2] = resMiddleware;
}
final completer = _activePages.removeAt(index).route?.completer;
if (completer?.isCompleted == false) completer!.complete(result);
return completer?.future as T?;
}
T arguments<T>() {
return currentConfiguration?.arguments?.arguments as T;
}
Map<String, String> get parameters {
return currentConfiguration?.arguments?.params ?? {};
}
PageSettings? get pageSettings {
return currentConfiguration?.arguments;
}
Future<T?> _removeHistoryEntry<T>(RouteDecoder entry, T result) async {
return _unsafeHistoryRemove<T>(entry, result);
}
Future<void> _pushHistory(RouteDecoder config) async {
if (config.route!.preventDuplicates) {
final originalEntryIndex = _activePages.indexWhere(
(element) => element.arguments?.name == config.arguments?.name);
if (originalEntryIndex >= 0) {
switch (preventDuplicateHandlingMode) {
case PreventDuplicateHandlingMode.PopUntilOriginalRoute:
popModeUntil(config.arguments!.name, popMode: PopMode.Page);
break;
case PreventDuplicateHandlingMode.ReorderRoutes:
await _unsafeHistoryRemoveAt(originalEntryIndex, null);
await _unsafeHistoryAdd(config);
break;
case PreventDuplicateHandlingMode.DoNothing:
default:
break;
}
return;
}
}
await _unsafeHistoryAdd(config);
}
Future<T?> _popHistory<T>(T result) async {
if (!_canPopHistory()) return null;
return await _doPopHistory(result);
}
Future<T?> _doPopHistory<T>(T result) async {
return _unsafeHistoryRemoveAt<T>(_activePages.length - 1, result);
}
Future<T?> _popPage<T>(T result) async {
if (!_canPopPage()) return null;
return await _doPopPage(result);
}
// returns the popped page
Future<T?> _doPopPage<T>(T result) async {
final currentBranch = currentConfiguration?.currentTreeBranch;
if (currentBranch != null && currentBranch.length > 1) {
//remove last part only
final remaining = currentBranch.take(currentBranch.length - 1);
final prevHistoryEntry = _activePages.length > 1
? _activePages[_activePages.length - 2]
: null;
//check if current route is the same as the previous route
if (prevHistoryEntry != null) {
//if so, pop the entire _activePages entry
final newLocation = remaining.last.name;
final prevLocation = prevHistoryEntry.arguments?.name;
if (newLocation == prevLocation) {
//pop the entire _activePages entry
return await _popHistory(result);
}
}
//create a new route with the remaining tree branch
final res = await _popHistory<T>(result);
await _pushHistory(
RouteDecoder(
remaining.toList(),
null,
//TOOD: persist state??
),
);
return res;
} else {
//remove entire entry
return await _popHistory(result);
}
}
Future<T?> _pop<T>(PopMode mode, T result) async {
switch (mode) {
case PopMode.History:
return await _popHistory<T>(result);
case PopMode.Page:
return await _popPage<T>(result);
default:
return null;
}
}
Future<T?> popHistory<T>(T result) async {
return await _popHistory<T>(result);
}
bool _canPopHistory() {
return _activePages.length > 1;
}
Future<bool> canPopHistory() {
return SynchronousFuture(_canPopHistory());
}
bool _canPopPage() {
final currentTreeBranch = currentConfiguration?.currentTreeBranch;
if (currentTreeBranch == null) return false;
return currentTreeBranch.length > 1 ? true : _canPopHistory();
}
Future<bool> canPopPage() {
return SynchronousFuture(_canPopPage());
}
bool _canPop(mode) {
switch (mode) {
case PopMode.History:
return _canPopHistory();
case PopMode.Page:
default:
return _canPopPage();
}
}
/// gets the visual pages from the current _activePages entry
///
/// visual pages must have [GetPage.participatesInRootNavigator] set to true
Iterable<GetPage> getVisualPages(RouteDecoder? currentHistory) {
final res = currentHistory!.currentTreeBranch
.where((r) => r.participatesInRootNavigator != null);
if (res.length == 0) {
//default behavoir, all routes participate in root navigator
return _activePages.map((e) => e.route!);
} else {
//user specified at least one participatesInRootNavigator
return res
.where((element) => element.participatesInRootNavigator == true);
}
}
@override
Widget build(BuildContext context) {
final currentHistory = currentConfiguration;
final pages = currentHistory == null
? <GetPage>[]
: pickPagesForRootNavigator?.call(currentHistory) ??
getVisualPages(currentHistory);
if (pages.length == 0) return SizedBox.shrink();
return GetNavigator(
key: navigatorKey,
onPopPage: _onPopVisualRoute,
pages: pages.toList(),
observers: [
GetObserver(),
...?navigatorObservers,
],
transitionDelegate:
transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(),
);
}
@override
void goToUnknownPage([bool clearPages = false]) {
if (clearPages) _activePages.clear();
final pageSettings = _buildPageSettings(notFoundRoute.name);
final routeDecoder = _getRouteDecoder(pageSettings);
_push(routeDecoder!);
}
@protected
void _popWithResult<T>([T? result]) {
final completer = _activePages.removeLast().route?.completer;
if (completer?.isCompleted == false) completer!.complete(result);
}
@override
Future<T?> toNamed<T>(
String page, {
dynamic arguments,
int? id,
bool preventDuplicates = true,
Map<String, String>? parameters,
}) async {
final args = _buildPageSettings(page, arguments);
final route = _getRouteDecoder<T>(args);
if (route != null) {
return _push<T>(route);
} else {
goToUnknownPage();
}
}
@override
Future<T?> to<T>(Widget Function() page,
{bool? opaque,
Transition? transition,
Curve? curve,
Duration? duration,
int? id,
String? routeName,
bool fullscreenDialog = false,
dynamic arguments,
Binding? binding,
bool preventDuplicates = true,
bool? popGesture,
bool showCupertinoParallax = true,
double Function(BuildContext context)? gestureWidth,
bool rebuildStack = true,
PreventDuplicateHandlingMode preventDuplicateHandlingMode =
PreventDuplicateHandlingMode.ReorderRoutes}) async {
routeName = _cleanRouteName("/${page.runtimeType}");
if (preventDuplicateHandlingMode == PreventDuplicateHandlingMode.Recreate) {
routeName = routeName + page.hashCode.toString();
}
final getPage = GetPage<T>(
name: routeName,
opaque: opaque ?? true,
page: page,
gestureWidth: gestureWidth,
showCupertinoParallax: showCupertinoParallax,
popGesture: popGesture ?? Get.defaultPopGesture,
transition: transition ?? Get.defaultTransition,
curve: curve ?? Get.defaultTransitionCurve,
fullscreenDialog: fullscreenDialog,
binding: binding,
transitionDuration: duration ?? Get.defaultTransitionDuration,
preventDuplicateHandlingMode: preventDuplicateHandlingMode,
);
Get.addPage(getPage);
final args = _buildPageSettings(routeName, arguments);
final route = _getRouteDecoder<T>(args);
final result = await _push<T>(
route!,
rebuildStack: rebuildStack,
preventDuplicateHandlingMode: preventDuplicateHandlingMode,
);
Get.removePage(getPage);
return result;
}
@override
Future<T?> off<T>(
Widget Function() page, {
bool? opaque,
Transition? transition,
Curve? curve,
Duration? duration,
int? id,
String? routeName,
bool fullscreenDialog = false,
dynamic arguments,
Binding? binding,
bool preventDuplicates = true,
bool? popGesture,
bool showCupertinoParallax = true,
double Function(BuildContext context)? gestureWidth,
}) async {
routeName = _cleanRouteName("/${page.runtimeType}");
final route = GetPage<T>(
name: routeName,
opaque: opaque ?? true,
page: page,
gestureWidth: gestureWidth,
showCupertinoParallax: showCupertinoParallax,
popGesture: popGesture ?? Get.defaultPopGesture,
transition: transition ?? Get.defaultTransition,
curve: curve ?? Get.defaultTransitionCurve,
fullscreenDialog: fullscreenDialog,
binding: binding,
transitionDuration: duration ?? Get.defaultTransitionDuration,
);
final args = _buildPageSettings(routeName, arguments);
return _replace(args, route);
}
@override
Future<T?>? offAll<T>(
Widget Function() page, {
bool Function(GetPage route)? predicate,
bool opaque = true,
bool? popGesture,
int? id,
String? routeName,
dynamic arguments,
Binding? binding,
bool fullscreenDialog = false,
Transition? transition,
Curve? curve,
Duration? duration,
bool showCupertinoParallax = true,
double Function(BuildContext context)? gestureWidth,
}) async {
routeName = _cleanRouteName("/${page.runtimeType}");
final route = GetPage<T>(
name: routeName,
opaque: opaque,
page: page,
gestureWidth: gestureWidth,
showCupertinoParallax: showCupertinoParallax,
popGesture: popGesture ?? Get.defaultPopGesture,
transition: transition ?? Get.defaultTransition,
curve: curve ?? Get.defaultTransitionCurve,
fullscreenDialog: fullscreenDialog,
binding: binding,
transitionDuration: duration ?? Get.defaultTransitionDuration,
);
final args = _buildPageSettings(routeName, arguments);
final newPredicate = predicate ?? (route) => false;
while (_activePages.length > 1 && !newPredicate(_activePages.last.route!)) {
_popWithResult();
}
return _replace(args, route);
}
@override
Future<T?>? offAllNamed<T>(
String page, {
// bool Function(GetPage route)? predicate,
dynamic arguments,
int? id,
Map<String, String>? parameters,
}) async {
final args = _buildPageSettings(page, arguments);
final route = _getRouteDecoder<T>(args);
if (route == null) return null;
while (_activePages.length > 1) {
_activePages.removeLast();
}
return _replaceNamed(route);
}
@override
Future<T?>? offNamedUntil<T>(
String page, {
bool Function(GetPage route)? predicate,
dynamic arguments,
int? id,
Map<String, String>? parameters,
}) async {
final args = _buildPageSettings(page, arguments);
final route = _getRouteDecoder<T>(args);
if (route == null) return null;
final newPredicate = predicate ?? (route) => false;
while (_activePages.length > 1 && newPredicate(_activePages.last.route!)) {
_activePages.removeLast();
}
return _replaceNamed(route);
}
@override
Future<T?> offNamed<T>(
String page, {
dynamic arguments,
int? id,
Map<String, String>? parameters,
}) async {
final args = _buildPageSettings(page, arguments);
final route = _getRouteDecoder<T>(args);
if (route == null) return null;
_popWithResult();
return _push<T>(route);
}
@override
Future<T?> toNamedAndOffUntil<T>(
String page,
bool Function(GetPage) predicate, [
Object? data,
]) async {
final arguments = _buildPageSettings(page, data);
final route = _getRouteDecoder<T>(arguments);
if (route == null) return null;
while (_activePages.isNotEmpty && !predicate(_activePages.last.route!)) {
_popWithResult();
}
return _push<T>(route);
}
@override
Future<T?> offUntil<T>(
Widget Function() page,
bool Function(GetPage) predicate, [
Object? arguments,
]) async {
while (_activePages.isNotEmpty && !predicate(_activePages.last.route!)) {
_popWithResult();
}
return to<T>(page, arguments: arguments);
}
@override
void removeRoute<T>(String name) {
_activePages.remove(RouteDecoder.fromRoute(name));
}
@override
void back<T>([T? result]) {
_checkIfCanBack();
_popWithResult<T>(result);
refresh();
}
bool get canBack {
return _activePages.length > 1;
}
void _checkIfCanBack() {
assert(() {
if (!canBack) {
final last = _activePages.last;
final name = last.route?.name;
throw 'The page $name cannot be popped';
}
return true;
}());
}
@override
Future<R?> backAndtoNamed<T, R>(String page,
{T? result, Object? arguments}) async {
final args = _buildPageSettings(page, arguments);
final route = _getRouteDecoder<R>(args);
if (route == null) return null;
_popWithResult<T>(result);
return _push<R>(route);
}
/// Removes routes according to [PopMode]
/// until it reaches the specifc [fullRoute],
/// DOES NOT remove the [fullRoute]
@override
Future<void> popModeUntil(
String fullRoute, {
PopMode popMode = PopMode.History,
}) async {
// remove history or page entries until you meet route
var iterator = currentConfiguration;
while (_canPop(popMode) &&
iterator != null &&
iterator.arguments?.name != fullRoute) {
await _pop(popMode, null);
// replace iterator
iterator = currentConfiguration;
}
refresh();
}
@override
void backUntil(bool Function(GetPage) predicate) {
while (_activePages.length <= 1 && !predicate(_activePages.last.route!)) {
_popWithResult();
}
refresh();
}
Future<T?> _replace<T>(PageSettings arguments, GetPage<T> page) async {
final index = _activePages.length > 1 ? _activePages.length - 1 : 0;
Get.addPage(page);
final route = _getRouteDecoder(arguments);
final activePage = _configureRouterDecoder<T>(route!, arguments);
_activePages[index] = activePage;
refresh();
final result = await activePage.route?.completer?.future as Future<T?>?;
Get.removePage(page);
return result;
}
Future<T?> _replaceNamed<T>(RouteDecoder activePage) async {
final index = _activePages.length > 1 ? _activePages.length - 1 : 0;
// final activePage = _configureRouterDecoder<T>(page, arguments);
_activePages[index] = activePage;
refresh();
final result = await activePage.route?.completer?.future as Future<T?>?;
return result;
}
/// Takes a route [name] String generated by [to], [off], [offAll]
/// (and similar context navigation methods), cleans the extra chars and
/// accommodates the format.
/// TODO: check for a more "appealing" URL naming convention.
/// `() => MyHomeScreenView` becomes `/my-home-screen-view`.
String _cleanRouteName(String name) {
name = name.replaceAll('() => ', '');
/// uncommonent for URL styling.
// name = name.paramCase!;
if (!name.startsWith('/')) {
name = '/$name';
}
return Uri.tryParse(name)?.toString() ?? name;
}
PageSettings _buildPageSettings(String page, [Object? data]) {
var uri = Uri.parse(page);
return PageSettings(uri, data);
}
@protected
RouteDecoder? _getRouteDecoder<T>(PageSettings arguments) {
var page = arguments.uri.path;
final parameters = arguments.params;
if (parameters.isNotEmpty) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
final decoder = Get.routeTree.matchRoute(page, arguments: arguments);
final route = decoder.route;
if (route == null) return null;
return _configureRouterDecoder(decoder, arguments);
}
@protected
RouteDecoder _configureRouterDecoder<T>(
RouteDecoder decoder, PageSettings arguments) {
final parameters =
arguments.params.isEmpty ? arguments.query : arguments.params;
if (arguments.params.isEmpty) {
arguments.params.addAll(arguments.query);
}
if (decoder.parameters.isEmpty) {
decoder.parameters.addAll(parameters);
}
decoder.route = decoder.route?.copy(
completer: _activePages.isEmpty ? null : Completer(),
arguments: arguments,
parameters: parameters,
);
return decoder;
}
Future<T?> _push<T>(RouteDecoder activePage,
{bool rebuildStack = true,
PreventDuplicateHandlingMode preventDuplicateHandlingMode =
PreventDuplicateHandlingMode.ReorderRoutes}) async {
final onStackPage = _activePages.firstWhereOrNull(
(element) => element.route?.key == activePage.route?.key);
/// There are no duplicate routes in the stack
if (onStackPage == null) {
final res = await runMiddleware(activePage);
_activePages.add(res!);
} else {
/// There are duplicate routes, reorder
switch (preventDuplicateHandlingMode) {
case PreventDuplicateHandlingMode.DoNothing:
break;
case PreventDuplicateHandlingMode.ReorderRoutes:
_activePages.remove(onStackPage);
final res = await runMiddleware(onStackPage);
_activePages.add(res!);
break;
case PreventDuplicateHandlingMode.PopUntilOriginalRoute:
while (_activePages.last == onStackPage) {
_popWithResult();
}
break;
case PreventDuplicateHandlingMode.Recreate:
_activePages.remove(onStackPage);
final res = await runMiddleware(activePage);
_activePages.add(res!);
break;
default:
}
}
if (rebuildStack) {
refresh();
}
return activePage.route?.completer?.future as Future<T?>?;
}
@override
Future<void> setNewRoutePath(RouteDecoder configuration) async {
final page = configuration.route;
if (page == null) {
goToUnknownPage();
return;
} else {
_push(configuration);
}
}
@override
RouteDecoder? get currentConfiguration {
if (_activePages.isEmpty) return null;
final route = _activePages.last;
return route;
}
Future<bool> handlePopupRoutes({
Object? result,
}) async {
Route? currentRoute;
navigatorKey.currentState!.popUntil((route) {
currentRoute = route;
return true;
});
if (currentRoute is PopupRoute) {
return await navigatorKey.currentState!.maybePop(result);
}
return false;
}
@override
Future<bool> popRoute({
Object? result,
PopMode? popMode,
}) async {
//Returning false will cause the entire app to be popped.
final wasPopup = await handlePopupRoutes(result: result);
if (wasPopup) return true;
final _popped = await _pop(popMode ?? backButtonPopMode, result);
refresh();
if (_popped != null) {
//emulate the old pop with result
return true;
}
return false;
}
bool _onPopVisualRoute(Route<dynamic> route, dynamic result) {
final didPop = route.didPop(result);
if (!didPop) {
return false;
}
final settings = route.settings;
if (settings is GetPage) {
final config = _activePages.cast<RouteDecoder?>().firstWhere(
(element) => element?.route == settings,
orElse: () => null,
);
if (config != null) {
_removeHistoryEntry(config, result);
}
}
refresh();
return true;
}
}
... ...
export 'circular_reveal_clipper.dart';
export 'custom_transition.dart';
export 'default_route.dart';
export 'default_transitions.dart';
export 'get_information_parser.dart';
export 'get_navigation_interface.dart';
export 'get_navigator.dart';
export 'get_route.dart';
export 'get_router_delegate.dart';
export 'get_transition_mixin.dart';
export 'modules.dart';
export 'observers/route_observer.dart';
export 'page_settings.dart';
export 'parse_route.dart';
export 'route_middleware.dart';
export 'route_report.dart';
export 'router_outlet.dart';
export 'transitions_type.dart';
export 'url_strategy/url_strategy.dart';
... ...
... ... @@ -216,7 +216,6 @@ class Routing {
/// This is basically a util for rules about 'what a route is'
class _RouteData {
final bool isGetPageRoute;
//final bool isSnackbar;
final bool isBottomSheet;
final bool isDialog;
final String? name;
... ... @@ -224,7 +223,6 @@ class _RouteData {
_RouteData({
required this.name,
required this.isGetPageRoute,
// required this.isSnackbar,
required this.isBottomSheet,
required this.isDialog,
});
... ... @@ -233,7 +231,6 @@ class _RouteData {
return _RouteData(
name: _extractRouteName(route),
isGetPageRoute: route is GetPageRoute,
// isSnackbar: route is SnackRoute,
isDialog: route is GetDialogRoute,
isBottomSheet: route is GetModalBottomSheetRoute,
);
... ...
import 'package:flutter/widgets.dart';
import '../../../route_manager.dart';
extension PageArgExt on BuildContext {
RouteSettings? get settings {
return ModalRoute.of(this)!.settings;
... ... @@ -44,6 +46,10 @@ extension PageArgExt on BuildContext {
RouterDelegate get delegate {
return router.routerDelegate;
}
GetDelegate get navigation {
return router.routerDelegate as GetDelegate;
}
}
class PageSettings extends RouteSettings {
... ...
import '../../get_navigation.dart';
import '../../../route_manager.dart';
import 'page_settings.dart';
class RouteDecoder {
const RouteDecoder(
... ... @@ -8,6 +9,18 @@ class RouteDecoder {
final List<GetPage> currentTreeBranch;
final PageSettings? arguments;
factory RouteDecoder.fromRoute(String location) {
var uri = Uri.parse(location);
final args = PageSettings(uri);
final decoder = Get.routeTree.matchRoute(location, arguments: args);
decoder.route = decoder.route?.copy(
completer: null,
arguments: args,
parameters: args.params,
);
return decoder;
}
GetPage? get route =>
currentTreeBranch.isEmpty ? null : currentTreeBranch.last;
... ... @@ -204,7 +217,7 @@ class ParseRouteTree {
}
}
extension FirstWhereExt<T> on List<T> {
extension FirstWhereOrNullExt<T> on List<T> {
/// The first element satisfying [test], or `null` if there are none.
T? firstWhereOrNull(bool Function(T element) test) {
for (var element in this) {
... ...
... ... @@ -51,7 +51,7 @@ abstract class _RouteMiddleware {
/// }
/// ```
/// {@end-tool}
Future<GetNavConfig?> redirectDelegate(GetNavConfig route);
Future<RouteDecoder?> redirectDelegate(RouteDecoder 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
... ... @@ -120,7 +120,7 @@ class GetMiddleware implements _RouteMiddleware {
void onPageDispose() {}
@override
Future<GetNavConfig?> redirectDelegate(GetNavConfig route) =>
Future<RouteDecoder?> redirectDelegate(RouteDecoder route) =>
SynchronousFuture(route);
}
... ... @@ -285,8 +285,8 @@ class PageRedirect {
void addPageParameter(GetPage route) {
if (route.parameters == null) return;
final parameters = Get.parameters;
final parameters = Map<String, String?>.from(Get.parameters);
parameters.addEntries(route.parameters!.entries);
Get.parameters = parameters;
// Get.parameters = parameters;
}
}
... ...
... ... @@ -74,12 +74,12 @@ class _RouterOutletState<TDelegate extends RouterDelegate<T>, T extends Object>
}
}
class GetRouterOutlet extends RouterOutlet<GetDelegate, GetNavConfig> {
class GetRouterOutlet extends RouterOutlet<GetDelegate, RouteDecoder> {
GetRouterOutlet({
String? anchorRoute,
required String initialRoute,
Iterable<GetPage> Function(Iterable<GetPage> afterAnchor)? filterPages,
GlobalKey<NavigatorState>? key,
// GlobalKey<NavigatorState>? key,
GetDelegate? delegate,
}) : this.pickPages(
pickPages: (config) {
... ... @@ -102,13 +102,13 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, GetNavConfig> {
emptyPage: (delegate) =>
Get.routeTree.matchRoute(initialRoute).route ??
delegate.notFoundRoute,
key: key,
key: Get.nestedKey(anchorRoute)?.navigatorKey,
delegate: delegate,
);
GetRouterOutlet.pickPages({
Widget Function(GetDelegate delegate)? emptyWidget,
GetPage Function(GetDelegate delegate)? emptyPage,
required Iterable<GetPage> Function(GetNavConfig currentNavStack) pickPages,
required Iterable<GetPage> Function(RouteDecoder currentNavStack) pickPages,
bool Function(Route<dynamic>, dynamic)? onPopPage,
GlobalKey<NavigatorState>? key,
GetDelegate? delegate,
... ... @@ -137,14 +137,14 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, GetNavConfig> {
return (emptyWidget?.call(rDelegate) ?? SizedBox.shrink());
},
pickPages: pickPages,
delegate: delegate ?? Get.rootDelegate,
delegate: delegate ?? Get.rootController.rootDelegate,
);
GetRouterOutlet.builder({
required Widget Function(
BuildContext context,
GetDelegate delegate,
GetNavConfig? currentRoute,
RouteDecoder? currentRoute,
)
builder,
GetDelegate? routerDelegate,
... ...
... ... @@ -10,13 +10,13 @@ typedef GetControllerBuilder<T extends GetLifeCycleMixin> = Widget Function(
T controller);
extension WatchExt on BuildContext {
T listen<T extends GetxController>() {
T listen<T>() {
return Bind.of(this, rebuild: true);
}
}
extension ReadExt on BuildContext {
T get<T extends GetxController>() {
T get<T>() {
return Bind.of(this);
}
}
... ... @@ -212,7 +212,7 @@ abstract class Bind<T> extends StatelessWidget {
child: child,
);
static T of<T extends GetxController>(
static T of<T>(
BuildContext context, {
bool rebuild = false,
// Object Function(T value)? filter,
... ...
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'utils/wrapper.dart';
void main() {
... ... @@ -9,6 +10,8 @@ void main() {
Wrapper(child: Container()),
);
await tester.pump();
Get.bottomSheet(Container(
child: Wrap(
children: <Widget>[
... ... @@ -31,6 +34,8 @@ void main() {
Wrapper(child: Container()),
);
await tester.pump();
Get.bottomSheet(Container(
child: Wrap(
children: <Widget>[
... ...
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'utils/wrapper.dart';
void main() {
... ... @@ -9,6 +10,8 @@ void main() {
Wrapper(child: Container()),
);
await tester.pump();
Get.defaultDialog(
onConfirm: () => print("Ok"),
middleText: "Dialog made in 3 lines of code");
... ... @@ -23,6 +26,8 @@ void main() {
Wrapper(child: Container()),
);
await tester.pump();
Get.dialog(YourDialogWidget());
await tester.pumpAndSettle();
... ... @@ -35,10 +40,18 @@ void main() {
Wrapper(child: Container()),
);
await tester.pump();
Get.dialog(YourDialogWidget());
expect(Get.isDialogOpen, true);
await tester.pumpAndSettle();
expect(find.byType(YourDialogWidget), findsOneWidget);
// expect(Get.isDialogOpen, true);
Get.back();
expect(Get.isDialogOpen, false);
await tester.pumpAndSettle();
expect(find.byType(YourDialogWidget), findsNothing);
// expect(Get.isDialogOpen, false);
await tester.pumpAndSettle();
});
}
... ...
... ... @@ -13,7 +13,7 @@ void main() {
expect(Get.isRegistered<Controller2>(), false);
expect(Get.isRegistered<Controller>(), false);
Get.to(First());
Get.to(() => First());
await tester.pumpAndSettle();
... ... @@ -21,7 +21,7 @@ void main() {
expect(Get.isRegistered<Controller>(), true);
Get.to(Second());
Get.to(() => Second());
await tester.pumpAndSettle();
... ...
... ... @@ -8,7 +8,7 @@ void main() {
testWidgets("Get.to navigates to provided route", (tester) async {
await tester.pumpWidget(Wrapper(child: Container()));
Get.to(FirstScreen());
Get.to(() => FirstScreen());
await tester.pumpAndSettle();
... ... @@ -47,13 +47,15 @@ void main() {
await tester.pumpAndSettle();
expect(Get.currentRoute, '/404');
expect(Get.rootController.rootDelegate.currentConfiguration?.route?.name,
'/404');
});
testWidgets("Get.off navigates to provided route", (tester) async {
await tester.pumpWidget(Wrapper(child: FirstScreen()));
// await tester.pump();
Get.off(SecondScreen());
Get.off(() => SecondScreen());
await tester.pumpAndSettle();
... ... @@ -62,8 +64,9 @@ void main() {
testWidgets("Get.off removes current route", (tester) async {
await tester.pumpWidget(Wrapper(child: FirstScreen()));
await tester.pump();
Get.off(SecondScreen());
Get.off(() => SecondScreen());
Get.back();
await tester.pumpAndSettle();
... ... @@ -81,6 +84,8 @@ void main() {
],
));
await tester.pump();
Get.offNamed('/second');
await tester.pumpAndSettle();
... ... @@ -98,7 +103,10 @@ void main() {
],
));
await tester.pump();
Get.offNamed('/second');
await tester.pumpAndSettle();
Get.back();
await tester.pumpAndSettle();
... ... @@ -116,19 +124,24 @@ void main() {
],
));
await tester.pump();
Get.toNamed('/second');
await tester.pumpAndSettle();
Get.offNamed('/third');
await tester.pumpAndSettle();
Get.back();
await tester.pumpAndSettle();
print(Get.rootController.rootDelegate.currentConfiguration?.route?.name);
expect(find.byType(FirstScreen), findsOneWidget);
});
testWidgets("Get.offAll navigates to provided route", (tester) async {
await tester.pumpWidget(Wrapper(child: FirstScreen()));
await tester.pump();
Get.offAll(SecondScreen());
Get.offAll(() => SecondScreen());
await tester.pumpAndSettle();
... ... @@ -137,11 +150,13 @@ void main() {
testWidgets("Get.offAll removes all previous routes", (tester) async {
await tester.pumpWidget(Wrapper(child: FirstScreen()));
await tester.pump();
Get.to(SecondScreen());
Get.offAll(ThirdScreen());
Get.to(() => SecondScreen());
await tester.pumpAndSettle();
Get.offAll(() => ThirdScreen());
await tester.pumpAndSettle();
Get.back();
await tester.pumpAndSettle();
expect(find.byType(SecondScreen), findsNothing);
... ... @@ -164,6 +179,8 @@ void main() {
],
));
await tester.pump();
Get.toNamed('/second');
await tester.pumpAndSettle();
... ... @@ -181,10 +198,13 @@ void main() {
],
));
await tester.pump();
Get.toNamed('/second');
await tester.pumpAndSettle();
Get.offAllNamed('/third');
await tester.pumpAndSettle();
Get.back();
await tester.pumpAndSettle();
expect(find.byType(SecondScreen), findsNothing);
... ... @@ -234,10 +254,9 @@ void main() {
testWidgets("Get.offUntil navigates to provided route", (tester) async {
await tester.pumpWidget(Wrapper(child: Container()));
Get.to(FirstScreen());
Get.to(() => FirstScreen());
Get.offUntil(GetPageRoute(page: () => ThirdScreen()),
(route) => (route as GetPageRoute).routeName == '/FirstScreen');
Get.offUntil(() => ThirdScreen(), (route) => route.name == '/FirstScreen');
await tester.pumpAndSettle();
... ... @@ -249,10 +268,10 @@ void main() {
(tester) async {
await tester.pumpWidget(Wrapper(child: Container()));
Get.to(FirstScreen());
Get.to(SecondScreen());
Get.offUntil(GetPageRoute(page: () => ThirdScreen()),
(route) => (route as GetPageRoute).routeName == '/FirstScreen');
Get.to(() => FirstScreen());
Get.to(() => SecondScreen());
Get.rootController.rootDelegate
.offUntil(() => ThirdScreen(), (route) => route.name == '/FirstScreen');
Get.back();
await tester.pumpAndSettle();
... ... @@ -265,10 +284,12 @@ void main() {
(tester) async {
await tester.pumpWidget(Wrapper(child: Container()));
Get.to(FirstScreen());
Get.to(SecondScreen());
Get.offUntil(GetPageRoute(page: () => ThirdScreen()),
(route) => (route as GetPageRoute).routeName == '/FirstScreen');
Get.to(() => FirstScreen());
await tester.pumpAndSettle();
Get.to(() => SecondScreen());
await tester.pumpAndSettle();
Get.offUntil(() => ThirdScreen(), (route) => route.name == '/FirstScreen');
await tester.pumpAndSettle();
Get.back();
await tester.pumpAndSettle();
... ... @@ -286,7 +307,7 @@ void main() {
],
));
Get.offNamedUntil('/second', ModalRoute.withName('/first'));
Get.offNamedUntil('/second', (route) => route.name == '/first');
await tester.pumpAndSettle();
... ... @@ -306,7 +327,8 @@ void main() {
));
Get.toNamed('/second');
Get.offNamedUntil('/third', ModalRoute.withName('/first'));
await tester.pumpAndSettle();
Get.offNamedUntil('/third', (route) => route.name == '/first');
await tester.pumpAndSettle();
... ... @@ -326,7 +348,9 @@ void main() {
));
Get.toNamed('/second');
Get.offNamedUntil('/third', ModalRoute.withName('/first'));
await tester.pumpAndSettle();
Get.offNamedUntil('/third', (route) => route.name == '/first');
await tester.pumpAndSettle();
Get.back();
await tester.pumpAndSettle();
... ... @@ -342,7 +366,8 @@ void main() {
),
);
Get.to(SecondScreen());
Get.to(() => SecondScreen());
await tester.pumpAndSettle();
Get.back();
await tester.pumpAndSettle();
... ... @@ -355,8 +380,10 @@ void main() {
(tester) async {
await tester.pumpWidget(Wrapper(child: FirstScreen()));
Get.to(SecondScreen());
Get.to(() => SecondScreen());
await tester.pumpAndSettle();
Get.snackbar('title', "message");
await tester.pumpAndSettle();
Get.back(closeOverlays: true);
await tester.pumpAndSettle();
... ... @@ -373,7 +400,7 @@ void main() {
),
);
Get.to(FirstScreen());
Get.to(() => FirstScreen());
await tester.pumpAndSettle();
... ... @@ -386,7 +413,7 @@ void main() {
),
);
Get.to(FirstScreen());
Get.to(() => FirstScreen());
await tester.pumpAndSettle();
... ... @@ -399,7 +426,7 @@ void main() {
),
);
Get.to(FirstScreen());
Get.to(() => FirstScreen());
await tester.pumpAndSettle();
... ... @@ -412,7 +439,7 @@ void main() {
),
);
Get.to(FirstScreen());
Get.to(() => FirstScreen());
await tester.pumpAndSettle();
... ... @@ -425,7 +452,7 @@ void main() {
),
);
Get.to(FirstScreen());
Get.to(() => FirstScreen());
await tester.pumpAndSettle();
... ... @@ -438,7 +465,7 @@ void main() {
),
);
Get.to(FirstScreen());
Get.to(() => FirstScreen());
await tester.pumpAndSettle();
... ... @@ -451,7 +478,7 @@ void main() {
),
);
Get.to(FirstScreen());
Get.to(() => FirstScreen());
await tester.pumpAndSettle();
... ... @@ -464,7 +491,7 @@ void main() {
),
);
Get.to(FirstScreen());
Get.to(() => FirstScreen());
await tester.pumpAndSettle();
... ... @@ -477,7 +504,7 @@ void main() {
),
);
Get.to(FirstScreen());
Get.to(() => FirstScreen());
await tester.pumpAndSettle();
... ...
import 'package:flutter/cupertino.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
// import 'package:flutter/cupertino.dart';
// import 'package:flutter_test/flutter_test.dart';
// import 'package:get/get.dart';
import 'get_main_test.dart';
// import 'get_main_test.dart';
class RedirectMiddleware extends GetMiddleware {
@override
RouteSettings redirect(String? route) => RouteSettings(name: '/second');
}
// class RedirectMiddleware extends GetMiddleware {
// @override
// RouteSettings redirect(String? route) {
// return RouteSettings(name: '/second');
// }
// }
void 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()),
],
),
);
// void 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');
// Get.toNamed('/first');
await tester.pumpAndSettle();
print(Get.routing.current);
expect(find.byType(SecondScreen), findsOneWidget);
});
}
// await tester.pumpAndSettle();
// print(Get.rootController.rootDelegate.currentConfiguration?.route?.name);
// 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', () {
... ... @@ -146,12 +145,16 @@ void main() {
await tester.pumpAndSettle();
print(Get.rootController.rootDelegate.pageSettings?.params);
expect(Get.parameters['id'], '1234');
Get.toNamed('/third?name=jonny&job=dev');
await tester.pumpAndSettle();
print(Get.parameters);
expect(Get.parameters['name'], 'jonny');
expect(Get.parameters['job'], 'dev');
... ...
import 'package:flutter/cupertino.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
// import 'package:flutter/cupertino.dart';
// import 'package:flutter_test/flutter_test.dart';
// import 'package:get/get.dart';
void main() {
testWidgets('Back swipe dismiss interrupted by route push', (tester) async {
// final scaffoldKey = GlobalKey();
void main() {}
await tester.pumpWidget(
GetCupertinoApp(
popGesture: true,
home: CupertinoPageScaffold(
// key: scaffoldKey,
child: Center(
child: CupertinoButton(
onPressed: () {
Get.to(() => CupertinoPageScaffold(
child: Center(child: Text('route')),
));
},
child: const Text('push'),
),
),
),
),
);
// void main() {
// testWidgets('Back swipe dismiss interrupted by route push', (tester) async {
// // final scaffoldKey = GlobalKey();
// Check the basic iOS back-swipe dismiss transition. Dragging the pushed
// route halfway across the screen will trigger the iOS dismiss animation
// await tester.pumpWidget(
// GetCupertinoApp(
// popGesture: true,
// home: CupertinoPageScaffold(
// // key: scaffoldKey,
// child: Center(
// child: CupertinoButton(
// onPressed: () {
// Get.to(
// () => CupertinoPageScaffold(
// child: Center(child: Text('route')),
// ),
// preventDuplicateHandlingMode:
// PreventDuplicateHandlingMode.Recreate);
// },
// child: const Text('push'),
// ),
// ),
// ),
// ),
// );
await tester.tap(find.text('push'));
await tester.pumpAndSettle();
expect(find.text('route'), findsOneWidget);
expect(find.text('push'), findsNothing);
// await tester.pumpAndSettle();
var gesture = await tester.startGesture(const Offset(5, 300));
await gesture.moveBy(const Offset(400, 0));
await gesture.up();
await tester.pump();
expect(
// The 'route' route has been dragged to the right, halfway across
// the screen
tester.getTopLeft(find.ancestor(
of: find.text('route'),
matching: find.byType(CupertinoPageScaffold))),
const Offset(400, 0),
);
expect(
// The 'push' route is sliding in from the left.
tester
.getTopLeft(find.ancestor(
of: find.text('push'),
matching: find.byType(CupertinoPageScaffold)))
.dx,
0,
);
await tester.pumpAndSettle();
expect(find.text('push'), findsOneWidget);
expect(
tester.getTopLeft(find.ancestor(
of: find.text('push'), matching: find.byType(CupertinoPageScaffold))),
Offset.zero,
);
expect(find.text('route'), findsNothing);
// // Check the basic iOS back-swipe dismiss transition. Dragging the pushed
// // route halfway across the screen will trigger the iOS dismiss animation
// Run the dismiss animation 60%, which exposes the route "push" button,
// and then press the button.
// await tester.tap(find.text('push'));
// await tester.pumpAndSettle();
// expect(find.text('route'), findsOneWidget);
// expect(find.text('push'), findsNothing);
await tester.tap(find.text('push'));
await tester.pumpAndSettle();
expect(find.text('route'), findsOneWidget);
expect(find.text('push'), findsNothing);
// var gesture = await tester.startGesture(const Offset(5, 300));
// await gesture.moveBy(const Offset(400, 0));
// await gesture.up();
// await tester.pump();
// expect(
// // The 'route' route has been dragged to the right, halfway across
// // the screen
// tester.getTopLeft(find.ancestor(
// of: find.text('route'),
// matching: find.byType(CupertinoPageScaffold))),
// const Offset(400, 0),
// );
// expect(
// // The 'push' route is sliding in from the left.
// tester
// .getTopLeft(find.ancestor(
// of: find.text('push'),
// matching: find.byType(CupertinoPageScaffold)))
// .dx,
// 400 / 3,
// );
// await tester.pumpAndSettle();
// expect(find.text('push'), findsOneWidget);
// expect(
// tester.getTopLeft(find.ancestor(
// of: find.text('push'), matching: find.byType(CupertinoPageScaffold))),
// Offset.zero,
// );
// expect(find.text('route'), findsNothing);
gesture = await tester.startGesture(const Offset(5, 300));
await gesture.moveBy(const Offset(400, 0)); // Drag halfway.
await gesture.up();
// Trigger the snapping animation.
// Since the back swipe drag was brought to >=50% of the screen, it will
// self snap to finish the pop transition as the gesture is lifted.
//
// This drag drop animation is 400ms when dropped exactly halfway
// (800 / [pixel distance remaining], see
// _CupertinoBackGestureController.dragEnd). It follows a curve that is very
// steep initially.
await tester.pump();
expect(
tester.getTopLeft(find.ancestor(
of: find.text('route'),
matching: find.byType(CupertinoPageScaffold))),
const Offset(400, 0),
);
// Let the dismissing snapping animation go 60%.
await tester.pump(const Duration(milliseconds: 240));
expect(
tester
.getTopLeft(find.ancestor(
of: find.text('route'),
matching: find.byType(CupertinoPageScaffold)))
.dx,
moreOrLessEquals(798, epsilon: 1),
);
// // Run the dismiss animation 60%, which exposes the route "push" button,
// // and then press the button.
// Use the navigator to push a route instead of tapping the 'push' button.
// The topmost route (the one that's animating away), ignores input while
// the pop is underway because route.navigator.userGestureInProgress.
Get.to(() => const CupertinoPageScaffold(
child: Center(child: Text('route')),
));
// await tester.tap(find.text('push'));
// await tester.pumpAndSettle();
// expect(find.text('route'), findsOneWidget);
// expect(find.text('push'), findsNothing);
await tester.pumpAndSettle();
expect(find.text('route'), findsOneWidget);
expect(find.text('push'), findsNothing);
expect(
tester
.state<NavigatorState>(find.byType(Navigator))
.userGestureInProgress,
false,
);
});
}
// gesture = await tester.startGesture(const Offset(5, 300));
// await gesture.moveBy(const Offset(400, 0)); // Drag halfway.
// await gesture.up();
// // Trigger the snapping animation.
// // Since the back swipe drag was brought to >=50% of the screen, it will
// // self snap to finish the pop transition as the gesture is lifted.
// //
// // This drag drop animation is 400ms when dropped exactly halfway
// // (800 / [pixel distance remaining], see
// // _CupertinoBackGestureController.dragEnd). It follows a curve that is very
// // steep initially.
// await tester.pump();
// expect(
// tester.getTopLeft(find.ancestor(
// of: find.text('route'),
// matching: find.byType(CupertinoPageScaffold))),
// const Offset(400, 0),
// );
// // Let the dismissing snapping animation go 60%.
// await tester.pump(const Duration(milliseconds: 240));
// expect(
// tester
// .getTopLeft(find.ancestor(
// of: find.text('route'),
// matching: find.byType(CupertinoPageScaffold)))
// .dx,
// moreOrLessEquals(798, epsilon: 1),
// );
// // Use the navigator to push a route instead of tapping the 'push' button.
// // The topmost route (the one that's animating away), ignores input while
// // the pop is underway because route.navigator.userGestureInProgress.
// Get.to(() => const CupertinoPageScaffold(
// child: Center(child: Text('route')),
// ));
// await tester.pumpAndSettle();
// expect(find.text('route'), findsOneWidget);
// expect(find.text('push'), findsNothing);
// expect(
// tester
// .state<NavigatorState>(find.byType(Navigator))
// .userGestureInProgress,
// false,
// );
// });
// }
... ...
... ... @@ -23,6 +23,8 @@ void main() {
),
);
await tester.pump();
expect(Get.isSnackbarOpen, false);
await tester.tap(find.text('Open Snackbar'));
... ... @@ -57,8 +59,12 @@ void main() {
),
);
await tester.pump();
expect(Get.isSnackbarOpen, false);
await tester.tap(find.text('Open Snackbar'));
await tester.tap(
find.text('Open Snackbar'),
);
expect(Get.isSnackbarOpen, true);
await tester.pump(const Duration(seconds: 1));
... ... @@ -85,6 +91,8 @@ void main() {
),
);
await tester.pump();
expect(Get.isSnackbarOpen, false);
await tester.tap(find.text('Open Snackbar'));
expect(Get.isSnackbarOpen, true);
... ... @@ -134,6 +142,8 @@ void main() {
),
));
await tester.pump();
expect(Get.isSnackbarOpen, false);
expect(find.text('bar1'), findsNothing);
... ... @@ -218,6 +228,8 @@ void main() {
await tester.pumpWidget(GetMaterialApp(home: Scaffold()));
await tester.pump();
expect(Get.isSnackbarOpen, false);
expect(find.text('bar1'), findsNothing);
... ...
... ... @@ -7,6 +7,8 @@ import '../../navigation/utils/wrapper.dart';
void main() {
testWidgets("Get.defaultDialog smoke test", (tester) async {
await tester.pumpWidget(Wrapper(child: Container()));
await tester.pump();
final BuildContext context = tester.element(find.byType(Container));
var mediaQuery = MediaQuery.of(context);
... ...