Files
intaleq/packages/get/lib/get_navigation/src/extension_navigation.dart
2026-04-16 19:45:03 +03:00

1373 lines
44 KiB
Dart

import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import '../../get_core/get_core.dart';
import '../../get_instance/src/bindings_interface.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
/// Navigator.push(context, YourRoute());
NavigatorState? get navigator => GetNavigation(Get).key.currentState;
extension ExtensionBottomSheet on GetInterface {
Future<T?> bottomSheet<T>(
Widget bottomsheet, {
Color? backgroundColor,
double? elevation,
bool persistent = true,
ShapeBorder? shape,
Clip? clipBehavior,
Color? barrierColor,
bool? ignoreSafeArea,
bool isScrollControlled = false,
bool useRootNavigator = false,
bool isDismissible = true,
bool enableDrag = true,
RouteSettings? settings,
Duration? enterBottomSheetDuration,
Duration? exitBottomSheetDuration,
}) {
return Navigator.of(overlayContext!, rootNavigator: useRootNavigator)
.push(GetModalBottomSheetRoute<T>(
builder: (_) => bottomsheet,
isPersistent: persistent,
// theme: Theme.of(key.currentContext, shadowThemeOnly: true),
theme: Theme.of(key.currentContext!),
isScrollControlled: isScrollControlled,
barrierLabel: MaterialLocalizations.of(key.currentContext!)
.modalBarrierDismissLabel,
backgroundColor: backgroundColor ?? Colors.transparent,
elevation: elevation,
shape: shape,
removeTop: ignoreSafeArea ?? true,
clipBehavior: clipBehavior,
isDismissible: isDismissible,
modalBarrierColor: barrierColor,
settings: settings,
enableDrag: enableDrag,
enterBottomSheetDuration:
enterBottomSheetDuration ?? const Duration(milliseconds: 250),
exitBottomSheetDuration:
exitBottomSheetDuration ?? const Duration(milliseconds: 200),
));
}
}
extension ExtensionDialog on GetInterface {
/// Show a dialog.
/// You can pass a [transitionDuration] and/or [transitionCurve],
/// overriding the defaults when the dialog shows up and closes.
/// When the dialog closes, uses those animations in reverse.
Future<T?> dialog<T>(
Widget widget, {
bool barrierDismissible = true,
Color? barrierColor,
bool useSafeArea = true,
GlobalKey<NavigatorState>? navigatorKey,
Object? arguments,
Duration? transitionDuration,
Curve? transitionCurve,
String? name,
RouteSettings? routeSettings,
}) {
assert(debugCheckHasMaterialLocalizations(context!));
// final theme = Theme.of(context, shadowThemeOnly: true);
final theme = Theme.of(context!);
return generalDialog<T>(
pageBuilder: (buildContext, animation, secondaryAnimation) {
final pageChild = widget;
Widget dialog = Builder(builder: (context) {
return Theme(data: theme, child: pageChild);
});
if (useSafeArea) {
dialog = SafeArea(child: dialog);
}
return dialog;
},
barrierDismissible: barrierDismissible,
barrierLabel: MaterialLocalizations.of(context!).modalBarrierDismissLabel,
barrierColor: barrierColor ?? Colors.black54,
transitionDuration: transitionDuration ?? defaultDialogTransitionDuration,
transitionBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: CurvedAnimation(
parent: animation,
curve: transitionCurve ?? defaultDialogTransitionCurve,
),
child: child,
);
},
navigatorKey: navigatorKey,
routeSettings:
routeSettings ?? RouteSettings(arguments: arguments, name: name),
);
}
/// 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,
}) {
assert(!barrierDismissible || barrierLabel != null);
final nav = navigatorKey?.currentState ??
Navigator.of(overlayContext!,
rootNavigator:
true); //overlay context will always return the root navigator
return nav.push<T>(
GetDialogRoute<T>(
pageBuilder: pageBuilder,
barrierDismissible: barrierDismissible,
barrierLabel: barrierLabel,
barrierColor: barrierColor,
transitionDuration: transitionDuration,
transitionBuilder: transitionBuilder,
settings: routeSettings,
),
);
}
/// Custom UI Dialog.
Future<T?> defaultDialog<T>({
String title = "Alert",
EdgeInsetsGeometry? titlePadding,
TextStyle? titleStyle,
Widget? content,
EdgeInsetsGeometry? contentPadding,
VoidCallback? onConfirm,
VoidCallback? onCancel,
VoidCallback? onCustom,
Color? cancelTextColor,
Color? confirmTextColor,
String? textConfirm,
String? textCancel,
String? textCustom,
Widget? confirm,
Widget? cancel,
Widget? custom,
Color? backgroundColor,
bool barrierDismissible = true,
Color? buttonColor,
String middleText = "Dialog made in 3 lines of code",
TextStyle? middleTextStyle,
double radius = 20.0,
// ThemeData themeData,
List<Widget>? actions,
// onWillPop Scope
WillPopCallback? onWillPop,
// the navigator used to push the dialog
GlobalKey<NavigatorState>? navigatorKey,
}) {
var leanCancel = onCancel != null || textCancel != null;
var leanConfirm = onConfirm != null || textConfirm != null;
actions ??= [];
if (cancel != null) {
actions.add(cancel);
} else {
if (leanCancel) {
actions.add(TextButton(
style: TextButton.styleFrom(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
shape: RoundedRectangleBorder(
side: BorderSide(
color: buttonColor ?? theme.colorScheme.secondary,
width: 2,
style: BorderStyle.solid),
borderRadius: BorderRadius.circular(100)),
),
onPressed: () {
onCancel?.call();
back();
},
child: Text(
textCancel ?? "Cancel",
style: TextStyle(
color: cancelTextColor ?? theme.colorScheme.secondary),
),
));
}
}
if (confirm != null) {
actions.add(confirm);
} else {
if (leanConfirm) {
actions.add(TextButton(
style: TextButton.styleFrom(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
backgroundColor: buttonColor ?? theme.colorScheme.secondary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100)),
),
child: Text(
textConfirm ?? "Ok",
style: TextStyle(
color: confirmTextColor ?? theme.colorScheme.onPrimary),
),
onPressed: () {
onConfirm?.call();
}));
}
}
Widget baseAlertDialog = AlertDialog(
titlePadding: titlePadding ?? const EdgeInsets.all(8),
contentPadding: contentPadding ?? const EdgeInsets.all(8),
backgroundColor: backgroundColor ?? theme.dialogBackgroundColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(radius))),
title: Text(title, textAlign: TextAlign.center, style: titleStyle),
content: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
content ??
Text(middleText,
textAlign: TextAlign.center, style: middleTextStyle),
const SizedBox(height: 16),
ButtonTheme(
minWidth: 78.0,
height: 34.0,
child: Wrap(
alignment: WrapAlignment.center,
spacing: 8,
runSpacing: 8,
children: actions,
),
)
],
),
// actions: actions, // ?? <Widget>[cancelButton, confirmButton],
buttonPadding: EdgeInsets.zero,
);
return dialog<T>(
onWillPop != null
? WillPopScope(
onWillPop: onWillPop,
child: baseAlertDialog,
)
: baseAlertDialog,
barrierDismissible: barrierDismissible,
navigatorKey: navigatorKey,
);
}
}
extension ExtensionSnackbar on GetInterface {
SnackbarController rawSnackbar({
String? title,
String? message,
Widget? titleText,
Widget? messageText,
Widget? icon,
bool instantInit = true,
bool shouldIconPulse = true,
double? maxWidth,
EdgeInsets margin = const EdgeInsets.all(0.0),
EdgeInsets padding = const EdgeInsets.all(16),
double borderRadius = 0.0,
Color? borderColor,
double borderWidth = 1.0,
Color backgroundColor = const Color(0xFF303030),
Color? leftBarIndicatorColor,
List<BoxShadow>? boxShadows,
Gradient? backgroundGradient,
Widget? mainButton,
OnTap? onTap,
Duration? duration = const Duration(seconds: 3),
bool isDismissible = true,
DismissDirection? dismissDirection,
bool showProgressIndicator = false,
AnimationController? progressIndicatorController,
Color? progressIndicatorBackgroundColor,
Animation<Color>? progressIndicatorValueColor,
SnackPosition snackPosition = SnackPosition.BOTTOM,
SnackStyle snackStyle = SnackStyle.FLOATING,
Curve forwardAnimationCurve = Curves.easeOutCirc,
Curve reverseAnimationCurve = Curves.easeOutCirc,
Duration animationDuration = const Duration(seconds: 1),
SnackbarStatusCallback? snackbarStatus,
double barBlur = 0.0,
double overlayBlur = 0.0,
Color? overlayColor,
Form? userInputForm,
}) {
final getSnackBar = GetSnackBar(
snackbarStatus: snackbarStatus,
title: title,
message: message,
titleText: titleText,
messageText: messageText,
snackPosition: snackPosition,
borderRadius: borderRadius,
margin: margin,
duration: duration,
barBlur: barBlur,
backgroundColor: backgroundColor,
icon: icon,
shouldIconPulse: shouldIconPulse,
maxWidth: maxWidth,
padding: padding,
borderColor: borderColor,
borderWidth: borderWidth,
leftBarIndicatorColor: leftBarIndicatorColor,
boxShadows: boxShadows,
backgroundGradient: backgroundGradient,
mainButton: mainButton,
onTap: onTap,
isDismissible: isDismissible,
dismissDirection: dismissDirection,
showProgressIndicator: showProgressIndicator,
progressIndicatorController: progressIndicatorController,
progressIndicatorBackgroundColor: progressIndicatorBackgroundColor,
progressIndicatorValueColor: progressIndicatorValueColor,
snackStyle: snackStyle,
forwardAnimationCurve: forwardAnimationCurve,
reverseAnimationCurve: reverseAnimationCurve,
animationDuration: animationDuration,
overlayBlur: overlayBlur,
overlayColor: overlayColor,
userInputForm: userInputForm,
);
final controller = SnackbarController(getSnackBar);
if (instantInit) {
controller.show();
} else {
ambiguate(SchedulerBinding.instance)?.addPostFrameCallback((_) {
controller.show();
});
}
return controller;
}
SnackbarController showSnackbar(GetSnackBar snackbar) {
final controller = SnackbarController(snackbar);
controller.show();
return controller;
}
SnackbarController snackbar(
String title,
String message, {
Color? colorText,
Duration? duration = const Duration(seconds: 3),
/// with instantInit = false you can put snackbar on initState
bool instantInit = true,
SnackPosition? snackPosition,
Widget? titleText,
Widget? messageText,
Widget? icon,
bool? shouldIconPulse,
double? maxWidth,
EdgeInsets? margin,
EdgeInsets? padding,
double? borderRadius,
Color? borderColor,
double? borderWidth,
Color? backgroundColor,
Color? leftBarIndicatorColor,
List<BoxShadow>? boxShadows,
Gradient? backgroundGradient,
TextButton? mainButton,
OnTap? onTap,
bool? isDismissible,
bool? showProgressIndicator,
DismissDirection? dismissDirection,
AnimationController? progressIndicatorController,
Color? progressIndicatorBackgroundColor,
Animation<Color>? progressIndicatorValueColor,
SnackStyle? snackStyle,
Curve? forwardAnimationCurve,
Curve? reverseAnimationCurve,
Duration? animationDuration,
double? barBlur,
double? overlayBlur,
SnackbarStatusCallback? snackbarStatus,
Color? overlayColor,
Form? userInputForm,
}) {
final getSnackBar = GetSnackBar(
snackbarStatus: snackbarStatus,
titleText: titleText ??
Text(
title,
style: TextStyle(
color: colorText ?? iconColor ?? Colors.black,
fontWeight: FontWeight.w800,
fontSize: 16,
),
),
messageText: messageText ??
Text(
message,
style: TextStyle(
color: colorText ?? iconColor ?? Colors.black,
fontWeight: FontWeight.w300,
fontSize: 14,
),
),
snackPosition: snackPosition ?? SnackPosition.TOP,
borderRadius: borderRadius ?? 15,
margin: margin ?? const EdgeInsets.symmetric(horizontal: 10),
duration: duration,
barBlur: barBlur ?? 7.0,
backgroundColor: backgroundColor ?? Colors.grey.withOpacity(0.2),
icon: icon,
shouldIconPulse: shouldIconPulse ?? true,
maxWidth: maxWidth,
padding: padding ?? const EdgeInsets.all(16),
borderColor: borderColor,
borderWidth: borderWidth,
leftBarIndicatorColor: leftBarIndicatorColor,
boxShadows: boxShadows,
backgroundGradient: backgroundGradient,
mainButton: mainButton,
onTap: onTap,
isDismissible: isDismissible ?? true,
dismissDirection: dismissDirection,
showProgressIndicator: showProgressIndicator ?? false,
progressIndicatorController: progressIndicatorController,
progressIndicatorBackgroundColor: progressIndicatorBackgroundColor,
progressIndicatorValueColor: progressIndicatorValueColor,
snackStyle: snackStyle ?? SnackStyle.FLOATING,
forwardAnimationCurve: forwardAnimationCurve ?? Curves.easeOutCirc,
reverseAnimationCurve: reverseAnimationCurve ?? Curves.easeOutCirc,
animationDuration: animationDuration ?? const Duration(seconds: 1),
overlayBlur: overlayBlur ?? 0.0,
overlayColor: overlayColor ?? Colors.transparent,
userInputForm: userInputForm);
final controller = SnackbarController(getSnackBar);
if (instantInit) {
controller.show();
} else {
//routing.isSnackbar = true;
ambiguate(SchedulerBinding.instance)?.addPostFrameCallback((_) {
controller.show();
});
}
return controller;
}
}
extension GetNavigation on GetInterface {
/// **Navigation.push()** shortcut.<br><br>
///
/// Pushes a new `page` to the stack
///
/// It has the advantage of not needing context,
/// so you can call from your business logic
///
/// You can set a custom [transition], and a transition [duration].
///
/// You can send any type of value to the other route in the [arguments].
///
/// Just like native routing in Flutter, you can push a route
/// as a [fullscreenDialog],
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// If you want the same behavior of ios that pops a route when the user drag,
/// you can set [popGesture] to true
///
/// If you're using the [Bindings] api, you must define it 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?>? to<T>(
dynamic page, {
bool? opaque,
Transition? transition,
Curve? curve,
Duration? duration,
int? id,
String? routeName,
bool fullscreenDialog = false,
dynamic arguments,
Bindings? binding,
bool preventDuplicates = true,
bool? popGesture,
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,
settings: RouteSettings(
name: routeName,
arguments: arguments,
),
popGesture: popGesture ?? defaultPopGesture,
transition: transition ?? defaultTransition,
curve: curve ?? defaultTransitionCurve,
fullscreenDialog: fullscreenDialog,
binding: binding,
transitionDuration: duration ?? defaultTransitionDuration,
),
);
}
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>
///
/// Pushes a new named `page` to the stack.
///
/// It has the advantage of not needing context, so you can call
/// from your business logic.
///
/// You can send any type of value to the other route in the [arguments].
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// By default, GetX will prevent you from push a route that you already in,
/// if you want to push anyway, set [preventDuplicates] to false
///
/// Note: Always put a slash on the route ('/page1'), to avoid unnexpected errors
Future<T?>? toNamed<T>(
String page, {
dynamic arguments,
int? id,
bool preventDuplicates = true,
Map<String, String>? parameters,
}) {
if (preventDuplicates && page == currentRoute) {
return null;
}
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
return global(id).currentState?.pushNamed<T>(
page,
arguments: arguments,
);
}
/// **Navigation.pushReplacementNamed()** shortcut.<br><br>
///
/// Pop the current named `page` in the stack and push a new one in its place
///
/// It has the advantage of not needing context, so you can call
/// from your business logic.
///
/// You can send any type of value to the other route in the [arguments].
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// By default, GetX will prevent you from push a route that you already in,
/// if you want to push anyway, set [preventDuplicates] to false
///
/// Note: Always put a slash on the route ('/page1'), to avoid unnexpected errors
Future<T?>? offNamed<T>(
String page, {
dynamic arguments,
int? id,
bool preventDuplicates = true,
Map<String, String>? parameters,
}) {
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,
);
}
/// **Navigation.popUntil()** shortcut.<br><br>
///
/// Calls pop several times in the stack until [predicate] returns true
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// [predicate] can be used like this:
/// `Get.until((route) => Get.currentRoute == '/home')`so when you get to home page,
///
/// 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}) {
// 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);
}
/// **Navigation.pushNamedAndRemoveUntil()** shortcut.<br><br>
///
/// Push the given named `page`, and then pop several pages in the stack
/// until [predicate] returns true
///
/// You can send any type of value to the other route in the [arguments].
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// [predicate] can be used like this:
/// `Get.offNamedUntil(page, ModalRoute.withName('/home'))`
/// to pop routes in stack until home,
/// or like this:
/// `Get.offNamedUntil((route) => !Get.isDialogOpen())`,
/// to make sure the dialog is closed
///
/// Note: Always put a slash on the route name ('/page1'), to avoid unexpected errors
Future<T?>? offNamedUntil<T>(
String page,
RoutePredicate predicate, {
int? id,
dynamic arguments,
Map<String, String>? parameters,
}) {
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
return global(id).currentState?.pushNamedAndRemoveUntil<T>(
page,
predicate,
arguments: arguments,
);
}
/// **Navigation.popAndPushNamed()** shortcut.<br><br>
///
/// Pop the current named page and pushes a new `page` to the stack
/// in its place
///
/// You can send any type of value to the other route in the [arguments].
/// It is very similar to `offNamed()` but use a different approach
///
/// The `offNamed()` pop a page, and goes to the next. The
/// `offAndToNamed()` goes to the next page, and removes the previous one.
/// The route transition animation is different.
Future<T?>? offAndToNamed<T>(
String page, {
dynamic arguments,
int? id,
dynamic result,
Map<String, String>? parameters,
}) {
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
return global(id).currentState?.popAndPushNamed(
page,
arguments: arguments,
result: result,
);
}
/// **Navigation.removeRoute()** shortcut.<br><br>
///
/// Remove a specific [route] from the stack
///
/// [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);
}
/// **Navigation.pushNamedAndRemoveUntil()** shortcut.<br><br>
///
/// Push a named `page` and pop several pages in the stack
/// until [predicate] returns true. [predicate] is optional
///
/// It has the advantage of not needing context, so you can
/// call from your business logic.
///
/// You can send any type of value to the other route in the [arguments].
///
/// [predicate] can be used like this:
/// `Get.until((route) => Get.currentRoute == '/home')`so when you get to home page,
/// or also like
/// `Get.until((route) => !Get.isDialogOpen())`, to make sure the dialog
/// is closed
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// Note: Always put a slash on the route ('/page1'), to avoid unexpected errors
Future<T?>? offAllNamed<T>(
String newRouteName, {
RoutePredicate? predicate,
dynamic arguments,
int? id,
Map<String, String>? parameters,
}) {
if (parameters != null) {
final uri = Uri(path: newRouteName, queryParameters: parameters);
newRouteName = uri.toString();
}
return global(id).currentState?.pushNamedAndRemoveUntil<T>(
newRouteName,
predicate ?? (_) => false,
arguments: arguments,
);
}
/// Returns true if a Snackbar, Dialog or BottomSheet is currently OPEN
bool get isOverlaysOpen =>
(isSnackbarOpen || isDialogOpen! || isBottomSheetOpen!);
/// Returns true if there is no Snackbar, Dialog or BottomSheet open
bool get isOverlaysClosed =>
(!isSnackbarOpen && !isDialogOpen! && !isBottomSheetOpen!);
/// **Navigation.popUntil()** shortcut.<br><br>
///
/// Pop the current page, snackbar, dialog or bottomsheet in the stack
///
/// if your set [closeOverlays] to true, Get.back() will close the
/// currently open snackbar/dialog/bottomsheet AND the current page
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// It has the advantage of not needing context, so you can call
/// from your business logic.
void back<T>({
T? result,
bool closeOverlays = false,
bool canPop = true,
int? id,
}) {
//TODO: This code brings compatibility of the new snackbar with GetX 4,
// remove this code in version 5
if (isSnackbarOpen && !closeOverlays) {
closeCurrentSnackbar();
return;
}
if (closeOverlays && isOverlaysOpen) {
//TODO: This code brings compatibility of the new snackbar with GetX 4,
// remove this code in version 5
if (isSnackbarOpen) {
closeAllSnackbars();
}
navigator?.popUntil((route) {
return (!isDialogOpen! && !isBottomSheetOpen!);
});
}
if (canPop) {
if (global(id).currentState?.canPop() == true) {
global(id).currentState?.pop<T>(result);
}
} else {
global(id).currentState?.pop<T>(result);
}
}
/// **Navigation.popUntil()** (with predicate) shortcut .<br><br>
///
/// Close as many routes as defined by [times]
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
void close(int times, [int? id]) {
if (times < 1) {
times = 1;
}
var count = 0;
var back = global(id).currentState?.popUntil((route) => count++ == times);
return back;
}
/// **Navigation.pushReplacement()** shortcut .<br><br>
///
/// Pop the current page and pushes a new `page` to the stack
///
/// It has the advantage of not needing context,
/// so you can call from your business logic
///
/// You can set a custom [transition], define a Tween [curve],
/// and a transition [duration].
///
/// You can send any type of value to the other route in the [arguments].
///
/// Just like native routing in Flutter, you can push a route
/// as a [fullscreenDialog],
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// If you want the same behavior of ios that pops a route when the user drag,
/// you can set [popGesture] to true
///
/// If you're using the [Bindings] api, you must define it 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,
Transition? transition,
Curve? curve,
bool? popGesture,
int? id,
String? routeName,
dynamic arguments,
Bindings? binding,
bool fullscreenDialog = false,
bool preventDuplicates = true,
Duration? duration,
double Function(BuildContext context)? gestureWidth,
}) {
routeName ??= "/${page.runtimeType.toString()}";
routeName = _cleanRouteName(routeName);
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));
}
///
/// Push a `page` and pop several pages in the stack
/// until [predicate] returns true. [predicate] is optional
///
/// It has the advantage of not needing context,
/// so you can call from your business logic
///
/// You can set a custom [transition], a [curve] and a transition [duration].
///
/// You can send any type of value to the other route in the [arguments].
///
/// Just like native routing in Flutter, you can push a route
/// as a [fullscreenDialog],
///
/// [predicate] can be used like this:
/// `Get.until((route) => Get.currentRoute == '/home')`so when you get to home page,
/// or also like
/// `Get.until((route) => !Get.isDialogOpen())`, to make sure the dialog
/// is closed
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// If you want the same behavior of ios that pops a route when the user drag,
/// you can set [popGesture] to true
///
/// If you're using the [Bindings] api, you must define it 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,
bool? popGesture,
int? id,
String? routeName,
dynamic arguments,
Bindings? binding,
bool fullscreenDialog = false,
Transition? transition,
Curve? curve,
Duration? duration,
double Function(BuildContext context)? gestureWidth,
}) {
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);
}
/// 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;
}
/// change default config of Get
void config(
{bool? enableLog,
LogWriterCallback? logWriterCallback,
bool? defaultPopGesture,
bool? defaultOpaqueRoute,
Duration? defaultDurationTransition,
bool? defaultGlobalState,
Transition? defaultTransition}) {
if (enableLog != null) {
Get.isLogEnable = enableLog;
}
if (logWriterCallback != null) {
Get.log = logWriterCallback;
}
if (defaultPopGesture != null) {
_getxController.defaultPopGesture = defaultPopGesture;
}
if (defaultOpaqueRoute != null) {
_getxController.defaultOpaqueRoute = defaultOpaqueRoute;
}
if (defaultTransition != null) {
_getxController.defaultTransition = defaultTransition;
}
if (defaultDurationTransition != null) {
_getxController.defaultTransitionDuration = defaultDurationTransition;
}
}
Future<void> updateLocale(Locale l) async {
Get.locale = l;
await forceAppUpdate();
}
/// As a rule, Flutter knows which widget to update,
/// so this command is rarely needed. We can mention situations
/// where you use const so that widgets are not updated with setState,
/// but you want it to be forcefully updated when an event like
/// language change happens. using context to make the widget dirty
/// for performRebuild() is a viable solution.
/// However, in situations where this is not possible, or at least,
/// is not desired by the developer, the only solution for updating
/// widgets that Flutter does not want to update is to use reassemble
/// to forcibly rebuild all widgets. Attention: calling this function will
/// reconstruct the application from the sketch, use this with caution.
/// Your entire application will be rebuilt, and touch events will not
/// work until the end of rendering.
Future<void> forceAppUpdate() async {
await engine.performReassemble();
}
void appUpdate() => _getxController.update();
void changeTheme(ThemeData theme) {
_getxController.setTheme(theme);
}
void changeThemeMode(ThemeMode themeMode) {
_getxController.setThemeMode(themeMode);
}
GlobalKey<NavigatorState>? addKey(GlobalKey<NavigatorState> newKey) {
return _getxController.addKey(newKey);
}
GlobalKey<NavigatorState>? nestedKey(dynamic key) {
keys.putIfAbsent(
key,
() => GlobalKey<NavigatorState>(
debugLabel: 'Getx nested key: ${key.toString()}',
),
);
return keys[key];
}
GlobalKey<NavigatorState> global(int? k) {
GlobalKey<NavigatorState> newKey;
if (k == null) {
newKey = key;
} else {
if (!keys.containsKey(k)) {
throw 'Route id ($k) not found';
}
newKey = keys[k]!;
}
if (newKey.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].
""";
}
return newKey;
}
/// give current arguments
dynamic get arguments => routing.args;
/// give name from current route
String get currentRoute => routing.current;
/// give name from previous route
String get previousRoute => routing.previous;
/// check if snackbar is open
bool get isSnackbarOpen =>
SnackbarController.isSnackbarBeingShown; //routing.isSnackbar;
void closeAllSnackbars() {
SnackbarController.cancelAllSnackbars();
}
Future<void> closeCurrentSnackbar() async {
await SnackbarController.closeCurrentSnackbar();
}
/// check if dialog is open
bool? get isDialogOpen => routing.isDialog;
/// check if bottomsheet is open
bool? get isBottomSheetOpen => routing.isBottomSheet;
/// check a raw current route
Route<dynamic>? get rawRoute => routing.route;
/// check if popGesture is enable
bool get isPopGestureEnable => defaultPopGesture;
/// check if default opaque route is enable
bool get isOpaqueRouteDefault => defaultOpaqueRoute;
/// give access to currentContext
BuildContext? get context => key.currentContext;
/// give access to current Overlay Context
BuildContext? get overlayContext {
BuildContext? overlay;
key.currentState?.overlay?.context.visitChildElements((element) {
overlay = element;
});
return overlay;
}
/// give access to Theme.of(context)
ThemeData get theme {
var theme = ThemeData.fallback();
if (context != null) {
theme = Theme.of(context!);
}
return theme;
}
///The current [WidgetsBinding]
WidgetsBinding get engine {
return WidgetsFlutterBinding.ensureInitialized();
}
/// The window to which this binding is bound.
ui.SingletonFlutterWindow get window => ui.window;
Locale? get deviceLocale => ui.window.locale;
///The number of device pixels for each logical pixel.
double get pixelRatio => ui.window.devicePixelRatio;
Size get size => ui.window.physicalSize / pixelRatio;
///The horizontal extent of this size.
double get width => size.width;
///The vertical extent of this size
double get height => size.height;
///The distance from the top edge to the first unpadded pixel,
///in physical pixels.
double get statusBarHeight => ui.window.padding.top;
///The distance from the bottom edge to the first unpadded pixel,
///in physical pixels.
double get bottomBarHeight => ui.window.padding.bottom;
///The system-reported text scale.
double get textScaleFactor => ui.window.textScaleFactor;
/// give access to TextTheme.of(context)
TextTheme get textTheme => theme.textTheme;
/// give access to Mediaquery.of(context)
MediaQueryData get mediaQuery => MediaQuery.of(context!);
/// Check if dark mode theme is enable
bool get isDarkMode => (theme.brightness == Brightness.dark);
/// Check if dark mode theme is enable on platform on android Q+
bool get isPlatformDarkMode =>
(ui.window.platformBrightness == Brightness.dark);
/// give access to Theme.of(context).iconTheme.color
Color? get iconColor => theme.iconTheme.color;
/// give access to FocusScope.of(context)
FocusNode? get focusScope => FocusManager.instance.primaryFocus;
// /// give access to Immutable MediaQuery.of(context).size.height
// double get height => MediaQuery.of(context).size.height;
// /// give access to Immutable MediaQuery.of(context).size.width
// double get width => MediaQuery.of(context).size.width;
GlobalKey<NavigatorState> get key => _getxController.key;
Map<dynamic, GlobalKey<NavigatorState>> get keys => _getxController.keys;
GetMaterialController get rootController => _getxController;
bool get defaultPopGesture => _getxController.defaultPopGesture;
bool get defaultOpaqueRoute => _getxController.defaultOpaqueRoute;
Transition? get defaultTransition => _getxController.defaultTransition;
Duration get defaultTransitionDuration {
return _getxController.defaultTransitionDuration;
}
Curve get defaultTransitionCurve => _getxController.defaultTransitionCurve;
Curve get defaultDialogTransitionCurve {
return _getxController.defaultDialogTransitionCurve;
}
Duration get defaultDialogTransitionDuration {
return _getxController.defaultDialogTransitionDuration;
}
Routing get routing => _getxController.routing;
Map<String, String?> get parameters => _getxController.parameters;
set parameters(Map<String, String?> newParameters) =>
_getxController.parameters = newParameters;
CustomTransition? get customTransition => _getxController.customTransition;
set customTransition(CustomTransition? newTransition) =>
_getxController.customTransition = newTransition;
bool get testMode => _getxController.testMode;
set testMode(bool isTest) => _getxController.testMode = isTest;
void resetRootNavigator() {
_getxController = GetMaterialController();
}
static GetMaterialController _getxController = GetMaterialController();
}
extension NavTwoExt on GetInterface {
void addPages(List<GetPage> getPages) {
routeTree.addRoutes(getPages);
}
void clearRouteTree() {
_routeTree.routes.clear();
}
static final _routeTree = ParseRouteTree(routes: []);
ParseRouteTree get routeTree => _routeTree;
void addPage(GetPage getPage) {
routeTree.addRoute(getPage);
}
/// Casts the stored router delegate to a desired type
TDelegate? delegate<TDelegate extends RouterDelegate<TPage>, TPage>() =>
routerDelegate as TDelegate?;
// // ignore: use_setters_to_change_properties
// void setDefaultDelegate(RouterDelegate? delegate) {
// _routerDelegate = delegate;
// }
// GetDelegate? getDelegate() => delegate<GetDelegate, GetNavConfig>();
GetInformationParser createInformationParser({String initialRoute = '/'}) {
if (routeInformationParser == null) {
return routeInformationParser = GetInformationParser(
initialRoute: initialRoute,
);
} else {
return routeInformationParser as GetInformationParser;
}
}
// static GetDelegate? _delegate;
GetDelegate get rootDelegate => createDelegate();
GetDelegate createDelegate({
GetPage<dynamic>? notFoundRoute,
List<NavigatorObserver>? navigatorObservers,
TransitionDelegate<dynamic>? transitionDelegate,
PopMode backButtonPopMode = PopMode.History,
PreventDuplicateHandlingMode preventDuplicateHandlingMode =
PreventDuplicateHandlingMode.ReorderRoutes,
}) {
if (routerDelegate == null) {
return routerDelegate = GetDelegate(
notFoundRoute: notFoundRoute,
navigatorObservers: navigatorObservers,
transitionDelegate: transitionDelegate,
backButtonPopMode: backButtonPopMode,
preventDuplicateHandlingMode: preventDuplicateHandlingMode,
);
} else {
return routerDelegate as GetDelegate;
}
}
}
extension OverlayExt on GetInterface {
Future<T> showOverlay<T>({
required Future<T> Function() asyncFunction,
Color opacityColor = Colors.black,
Widget? loadingWidget,
double opacity = .5,
}) async {
final navigatorState =
Navigator.of(Get.overlayContext!, rootNavigator: false);
final overlayState = navigatorState.overlay!;
final overlayEntryOpacity = OverlayEntry(builder: (context) {
return Opacity(
opacity: opacity,
child: Container(
color: opacityColor,
));
});
final overlayEntryLoader = OverlayEntry(builder: (context) {
return loadingWidget ??
const Center(
child: SizedBox(
height: 90,
width: 90,
child: Text('Loading...'),
));
});
overlayState.insert(overlayEntryOpacity);
overlayState.insert(overlayEntryLoader);
T data;
try {
data = await asyncFunction();
} on Exception catch (_) {
overlayEntryLoader.remove();
overlayEntryOpacity.remove();
rethrow;
}
overlayEntryLoader.remove();
overlayEntryOpacity.remove();
return data;
}
}