new change to use intaleq_map sdk 04-16-4

This commit is contained in:
Hamza-Ayed
2026-04-16 19:45:03 +03:00
parent 0aa1f15f25
commit a54a7a4189
850 changed files with 83282 additions and 3075 deletions

View File

@@ -0,0 +1,335 @@
import 'package:flutter/material.dart';
import '../../../get.dart';
import '../router_report.dart';
class GetModalBottomSheetRoute<T> extends PopupRoute<T> {
GetModalBottomSheetRoute({
this.builder,
this.theme,
this.barrierLabel,
this.backgroundColor,
this.isPersistent,
this.elevation,
this.shape,
this.removeTop = true,
this.clipBehavior,
this.modalBarrierColor,
this.isDismissible = true,
this.enableDrag = true,
required this.isScrollControlled,
RouteSettings? settings,
this.enterBottomSheetDuration = const Duration(milliseconds: 250),
this.exitBottomSheetDuration = const Duration(milliseconds: 200),
}) : super(settings: settings) {
RouterReportManager.reportCurrentRoute(this);
}
final bool? isPersistent;
final WidgetBuilder? builder;
final ThemeData? theme;
final bool isScrollControlled;
final Color? backgroundColor;
final double? elevation;
final ShapeBorder? shape;
final Clip? clipBehavior;
final Color? modalBarrierColor;
final bool isDismissible;
final bool enableDrag;
// final String name;
final Duration enterBottomSheetDuration;
final Duration exitBottomSheetDuration;
// remove safearea from top
final bool removeTop;
@override
Duration get transitionDuration => const Duration(milliseconds: 700);
@override
bool get barrierDismissible => isDismissible;
@override
final String? barrierLabel;
@override
Color get barrierColor => modalBarrierColor ?? Colors.black54;
AnimationController? _animationController;
@override
void dispose() {
RouterReportManager.reportRouteDispose(this);
super.dispose();
}
@override
AnimationController createAnimationController() {
assert(_animationController == null);
_animationController =
BottomSheet.createAnimationController(navigator!.overlay!);
_animationController!.duration = enterBottomSheetDuration;
_animationController!.reverseDuration = exitBottomSheetDuration;
return _animationController!;
}
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
final sheetTheme =
theme?.bottomSheetTheme ?? Theme.of(context).bottomSheetTheme;
// By definition, the bottom sheet is aligned to the bottom of the page
// and isn't exposed to the top padding of the MediaQuery.
Widget bottomSheet = MediaQuery.removePadding(
context: context,
removeTop: removeTop,
child: Padding(
padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: _GetModalBottomSheet<T>(
route: this,
backgroundColor: backgroundColor ??
sheetTheme.modalBackgroundColor ??
sheetTheme.backgroundColor,
elevation:
elevation ?? sheetTheme.modalElevation ?? sheetTheme.elevation,
shape: shape,
clipBehavior: clipBehavior,
isScrollControlled: isScrollControlled,
enableDrag: enableDrag,
),
),
);
if (theme != null) bottomSheet = Theme(data: theme!, child: bottomSheet);
return bottomSheet;
}
}
class _GetModalBottomSheet<T> extends StatefulWidget {
const _GetModalBottomSheet({
Key? key,
this.route,
this.backgroundColor,
this.elevation,
this.shape,
this.clipBehavior,
this.isScrollControlled = false,
this.enableDrag = true,
this.isPersistent = false,
}) : super(key: key);
final bool isPersistent;
final GetModalBottomSheetRoute<T>? route;
final bool isScrollControlled;
final Color? backgroundColor;
final double? elevation;
final ShapeBorder? shape;
final Clip? clipBehavior;
final bool enableDrag;
@override
_GetModalBottomSheetState<T> createState() => _GetModalBottomSheetState<T>();
}
class _GetModalBottomSheetState<T> extends State<_GetModalBottomSheet<T>> {
String _getRouteLabel(MaterialLocalizations localizations) {
if ((Theme.of(context).platform == TargetPlatform.android) ||
(Theme.of(context).platform == TargetPlatform.fuchsia)) {
return localizations.dialogLabel;
} else {
return '';
}
}
@override
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
assert(debugCheckHasMaterialLocalizations(context));
final mediaQuery = MediaQuery.of(context);
final localizations = MaterialLocalizations.of(context);
final routeLabel = _getRouteLabel(localizations);
return AnimatedBuilder(
animation: widget.route!.animation!,
builder: (context, child) {
// Disable the initial animation when accessible navigation is on so
// that the semantics are added to the tree at the correct time.
final animationValue = mediaQuery.accessibleNavigation
? 1.0
: widget.route!.animation!.value;
return Semantics(
scopesRoute: true,
namesRoute: true,
label: routeLabel,
explicitChildNodes: true,
child: ClipRect(
child: CustomSingleChildLayout(
delegate: _GetModalBottomSheetLayout(
animationValue, widget.isScrollControlled),
child: widget.isPersistent == false
? BottomSheet(
animationController: widget.route!._animationController,
onClosing: () {
if (widget.route!.isCurrent) {
Navigator.pop(context);
}
},
builder: widget.route!.builder!,
backgroundColor: widget.backgroundColor,
elevation: widget.elevation,
shape: widget.shape,
clipBehavior: widget.clipBehavior,
enableDrag: widget.enableDrag,
)
: Scaffold(
bottomSheet: BottomSheet(
animationController:
widget.route!._animationController,
onClosing: () {
// if (widget.route.isCurrent) {
// Navigator.pop(context);
// }
},
builder: widget.route!.builder!,
backgroundColor: widget.backgroundColor,
elevation: widget.elevation,
shape: widget.shape,
clipBehavior: widget.clipBehavior,
enableDrag: widget.enableDrag,
),
)),
),
);
},
);
}
}
class _GetPerModalBottomSheet<T> extends StatefulWidget {
const _GetPerModalBottomSheet({
Key? key,
this.route,
this.isPersistent,
this.backgroundColor,
this.elevation,
this.shape,
this.clipBehavior,
this.isScrollControlled = false,
this.enableDrag = true,
}) : super(key: key);
final bool? isPersistent;
final GetModalBottomSheetRoute<T>? route;
final bool isScrollControlled;
final Color? backgroundColor;
final double? elevation;
final ShapeBorder? shape;
final Clip? clipBehavior;
final bool enableDrag;
@override
// ignore: lines_longer_than_80_chars
_GetPerModalBottomSheetState<T> createState() =>
_GetPerModalBottomSheetState<T>();
}
// ignore: lines_longer_than_80_chars
class _GetPerModalBottomSheetState<T>
extends State<_GetPerModalBottomSheet<T>> {
String _getRouteLabel(MaterialLocalizations localizations) {
if ((Theme.of(context).platform == TargetPlatform.android) ||
(Theme.of(context).platform == TargetPlatform.fuchsia)) {
return localizations.dialogLabel;
} else {
return '';
}
}
@override
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
assert(debugCheckHasMaterialLocalizations(context));
final mediaQuery = MediaQuery.of(context);
final localizations = MaterialLocalizations.of(context);
final routeLabel = _getRouteLabel(localizations);
return AnimatedBuilder(
animation: widget.route!.animation!,
builder: (context, child) {
// Disable the initial animation when accessible navigation is on so
// that the semantics are added to the tree at the correct time.
final animationValue = mediaQuery.accessibleNavigation
? 1.0
: widget.route!.animation!.value;
return Semantics(
scopesRoute: true,
namesRoute: true,
label: routeLabel,
explicitChildNodes: true,
child: ClipRect(
child: CustomSingleChildLayout(
delegate: _GetModalBottomSheetLayout(
animationValue, widget.isScrollControlled),
child: widget.isPersistent == false
? BottomSheet(
animationController: widget.route!._animationController,
onClosing: () {
if (widget.route!.isCurrent) {
Navigator.pop(context);
}
},
builder: widget.route!.builder!,
backgroundColor: widget.backgroundColor,
elevation: widget.elevation,
shape: widget.shape,
clipBehavior: widget.clipBehavior,
enableDrag: widget.enableDrag,
)
: Scaffold(
bottomSheet: BottomSheet(
animationController:
widget.route!._animationController,
onClosing: () {
// if (widget.route.isCurrent) {
// Navigator.pop(context);
// }
},
builder: widget.route!.builder!,
backgroundColor: widget.backgroundColor,
elevation: widget.elevation,
shape: widget.shape,
clipBehavior: widget.clipBehavior,
enableDrag: widget.enableDrag,
),
)),
),
);
},
);
}
}
class _GetModalBottomSheetLayout extends SingleChildLayoutDelegate {
_GetModalBottomSheetLayout(this.progress, this.isScrollControlled);
final double progress;
final bool isScrollControlled;
@override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
return BoxConstraints(
minWidth: constraints.maxWidth,
maxWidth: constraints.maxWidth,
minHeight: 0.0,
maxHeight: isScrollControlled
? constraints.maxHeight
: constraints.maxHeight * 9.0 / 16.0,
);
}
@override
Offset getPositionForChild(Size size, Size childSize) {
return Offset(0.0, size.height - childSize.height * progress);
}
@override
bool shouldRelayout(_GetModalBottomSheetLayout oldDelegate) {
return progress != oldDelegate.progress;
}
}

View File

@@ -0,0 +1,73 @@
import 'package:flutter/widgets.dart';
import '../router_report.dart';
class GetDialogRoute<T> extends PopupRoute<T> {
GetDialogRoute({
required RoutePageBuilder pageBuilder,
bool barrierDismissible = true,
String? barrierLabel,
Color barrierColor = const Color(0x80000000),
Duration transitionDuration = const Duration(milliseconds: 200),
RouteTransitionsBuilder? transitionBuilder,
RouteSettings? settings,
}) : widget = pageBuilder,
_barrierDismissible = barrierDismissible,
_barrierLabel = barrierLabel,
_barrierColor = barrierColor,
_transitionDuration = transitionDuration,
_transitionBuilder = transitionBuilder,
super(settings: settings) {
RouterReportManager.reportCurrentRoute(this);
}
final RoutePageBuilder widget;
@override
bool get barrierDismissible => _barrierDismissible;
final bool _barrierDismissible;
@override
void dispose() {
RouterReportManager.reportRouteDispose(this);
super.dispose();
}
@override
String? get barrierLabel => _barrierLabel;
final String? _barrierLabel;
@override
Color get barrierColor => _barrierColor;
final Color _barrierColor;
@override
Duration get transitionDuration => _transitionDuration;
final Duration _transitionDuration;
final RouteTransitionsBuilder? _transitionBuilder;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return Semantics(
scopesRoute: true,
explicitChildNodes: true,
child: widget(context, animation, secondaryAnimation),
);
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
if (_transitionBuilder == null) {
return FadeTransition(
opacity: CurvedAnimation(
parent: animation,
curve: Curves.linear,
),
child: child);
} // Some default transition
return _transitionBuilder!(context, animation, secondaryAnimation, child);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
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.treeBranch,
location: location,
state: routeInformation.state,
),
);
}
@override
RouteInformation restoreRouteInformation(GetNavConfig configuration) {
return RouteInformation(
location: configuration.location,
state: configuration.state,
);
}
}

View File

@@ -0,0 +1,60 @@
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.treeBranch.isEmpty) return null;
return GetNavConfig(
currentTreeBranch: res.treeBranch,
location: route,
state: null,
);
}
@override
String toString() => '''
======GetNavConfig=====\ncurrentTreeBranch: $currentTreeBranch\ncurrentPage: $currentPage\n======GetNavConfig=====''';
}

View File

@@ -0,0 +1,486 @@
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';
class GetDelegate extends RouterDelegate<GetNavConfig>
with ListenableMixin, ListNotifierMixin {
final List<GetNavConfig> history = <GetNavConfig>[];
final PopMode backButtonPopMode;
final PreventDuplicateHandlingMode preventDuplicateHandlingMode;
final GetPage notFoundRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionDelegate<dynamic>? transitionDelegate;
final _allCompleters = <GetPage, Completer>{};
GetDelegate({
GetPage? notFoundRoute,
this.navigatorObservers,
this.transitionDelegate,
this.backButtonPopMode = PopMode.History,
this.preventDuplicateHandlingMode =
PreventDuplicateHandlingMode.ReorderRoutes,
}) : notFoundRoute = notFoundRoute ??
GetPage(
name: '/404',
page: () => const Scaffold(
body: Text('Route not found'),
),
) {
Get.log('GetDelegate is created !');
}
@override
GetNavConfig? get currentConfiguration {
if (history.isEmpty) return null;
final route = history.last;
return route;
}
GlobalKey<NavigatorState> get navigatorKey => Get.key;
Map<String, String> get parameters {
return currentConfiguration?.currentPage?.parameters ?? {};
}
T arguments<T>() {
return currentConfiguration?.currentPage?.arguments as T;
}
/// Removes routes according to [PopMode]
/// until it reaches the specifc [fullRoute],
/// DOES NOT remove the [fullRoute]
Future<void> backUntil(
String fullRoute, {
PopMode popMode = PopMode.Page,
}) 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();
}
@override
Widget build(BuildContext context) {
final pages = getVisualPages();
if (pages.isEmpty) return const SizedBox.shrink();
final extraObservers = navigatorObservers;
return GetNavigator(
key: navigatorKey,
onPopPage: _onPopVisualRoute,
pages: pages,
observers: [
GetObserver(),
if (extraObservers != null) ...extraObservers,
],
transitionDelegate:
transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(),
);
}
// void _unsafeHistoryClear() {
// history.clear();
// }
Future<bool> canPopHistory() {
return SynchronousFuture(_canPopHistory());
}
Future<bool> canPopPage() {
return SynchronousFuture(_canPopPage());
}
/// gets the visual pages from the current history entry
///
/// visual pages must have [participatesInRootNavigator] set to true
List<GetPage> getVisualPages() {
final currentHistory = currentConfiguration;
if (currentHistory == null) return <GetPage>[];
final res = currentHistory.currentTreeBranch
.where((r) => r.participatesInRootNavigator != null);
if (res.isEmpty) {
//default behavoir, all routes participate in root navigator
return history.map((e) => e.currentPage!).toList();
} else {
//user specified at least one participatesInRootNavigator
return res
.where((element) => element.participatesInRootNavigator == true)
.toList();
}
}
// GetPageRoute getPageRoute(RouteSettings? settings) {
// return PageRedirect(settings ?? RouteSettings(name: '/404'), _notFound())
// .page();
// }
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;
}
Future<T?>? offAndToNamed<T>(
String page, {
dynamic arguments,
int? id,
dynamic result,
Map<String, String>? parameters,
PopMode popMode = PopMode.History,
}) async {
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
await popRoute(result: result);
return toNamed(page, arguments: arguments, parameters: parameters);
}
Future<T> offNamed<T>(
String page, {
dynamic arguments,
Map<String, String>? parameters,
}) async {
history.removeLast();
return toNamed<T>(page, arguments: arguments, parameters: parameters);
}
Future<GetNavConfig?> popHistory() async {
return await _popHistory();
}
// returns the popped page
@override
Future<bool> popRoute({
Object? result,
PopMode popMode = PopMode.Page,
}) 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);
refresh();
if (popped != null) {
//emulate the old pop with result
return true;
}
return false;
}
/// 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<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;
}
@override
Future<void> setNewRoutePath(GetNavConfig configuration) async {
await pushHistory(configuration);
}
Future<T> toNamed<T>(
String page, {
dynamic 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);
final completer = Completer<T>();
if (decoder.route != null) {
_allCompleters[decoder.route!] = completer;
await pushHistory(
GetNavConfig(
currentTreeBranch: decoder.treeBranch,
location: page,
state: null, //TODO: persist state?
),
);
return completer.future;
} else {
///TODO: IMPLEMENT ROUTE NOT FOUND
return Future.value();
}
}
bool _canPop(PopMode mode) {
switch (mode) {
case PopMode.History:
return _canPopHistory();
case PopMode.Page:
default:
return _canPopPage();
}
}
bool _canPopHistory() {
return history.length > 1;
}
bool _canPopPage() {
final currentTreeBranch = currentConfiguration?.currentTreeBranch;
if (currentTreeBranch == null) return false;
return currentTreeBranch.length > 1 ? true : _canPopHistory();
}
Future<GetNavConfig?> _doPopHistory() async {
return await _unsafeHistoryRemoveAt(history.length - 1);
}
// @override
// Future<void> setInitialRoutePath(GetNavConfig configuration) async {
// //no need to clear history with Reorder route strategy
// // _unsafeHistoryClear();
// // _resultCompleter.clear();
// await pushHistory(configuration);
// }
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();
}
}
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);
}
if (_allCompleters.containsKey(settings)) {
_allCompleters[settings]?.complete(route.popped);
}
}
refresh();
return true;
}
Future<GetNavConfig?> _pop(PopMode mode) async {
switch (mode) {
case PopMode.History:
return await _popHistory();
case PopMode.Page:
return await _popPage();
default:
return null;
}
}
Future<GetNavConfig?> _popHistory() async {
if (!_canPopHistory()) return null;
return await _doPopHistory();
}
Future<GetNavConfig?> _popPage() async {
if (!_canPopPage()) return null;
return await _doPopPage();
}
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<void> _removeHistoryEntry(GetNavConfig entry) async {
await _unsafeHistoryRemove(entry);
}
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);
}
}
class GetNavigator extends Navigator {
GetNavigator({
GlobalKey<NavigatorState>? key,
bool Function(Route<dynamic>, dynamic)? onPopPage,
required List<Page> pages,
List<NavigatorObserver>? observers,
bool reportsRouteUpdateToEngine = false,
TransitionDelegate? transitionDelegate,
}) : super(
//keys should be optional
key: key,
onPopPage: onPopPage ??
(route, result) {
final didPop = route.didPop(result);
if (!didPop) {
return false;
}
return true;
},
reportsRouteUpdateToEngine: reportsRouteUpdateToEngine,
pages: pages,
observers: [
// GetObserver(),
if (observers != null) ...observers,
],
transitionDelegate:
transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(),
);
}
/// 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
}

View File

@@ -0,0 +1,168 @@
import 'package:flutter/material.dart';
import '../../../get.dart';
class RouterOutlet<TDelegate extends RouterDelegate<T>, T extends Object>
extends StatefulWidget {
final TDelegate routerDelegate;
final Widget Function(
BuildContext context,
TDelegate delegate,
T? currentRoute,
) builder;
//keys
RouterOutlet.builder({
Key? key,
TDelegate? delegate,
required this.builder,
}) : routerDelegate = delegate ?? Get.delegate<TDelegate, T>()!,
super(key: key);
RouterOutlet({
Key? key,
TDelegate? delegate,
required Iterable<GetPage> Function(T currentNavStack) pickPages,
required Widget Function(
BuildContext context,
TDelegate,
Iterable<GetPage>? page,
)
pageBuilder,
}) : this.builder(
builder: (context, rDelegate, currentConfig) {
var picked =
currentConfig == null ? null : pickPages(currentConfig);
if (picked?.isEmpty ?? false) {
picked = null;
}
return pageBuilder(context, rDelegate, picked);
},
delegate: delegate,
);
@override
RouterOutletState<TDelegate, T> createState() =>
RouterOutletState<TDelegate, T>();
}
class RouterOutletState<TDelegate extends RouterDelegate<T>, T extends Object>
extends State<RouterOutlet<TDelegate, T>> {
TDelegate get delegate => widget.routerDelegate;
@override
void initState() {
super.initState();
_getCurrentRoute();
delegate.addListener(onRouterDelegateChanged);
}
@override
void dispose() {
delegate.removeListener(onRouterDelegateChanged);
super.dispose();
}
T? currentRoute;
void _getCurrentRoute() {
currentRoute = delegate.currentConfiguration;
}
void onRouterDelegateChanged() {
setState(_getCurrentRoute);
}
@override
Widget build(BuildContext context) {
return widget.builder(context, delegate, currentRoute);
}
}
class GetRouterOutlet extends RouterOutlet<GetDelegate, GetNavConfig> {
GetRouterOutlet({
String? anchorRoute,
required String initialRoute,
Iterable<GetPage> Function(Iterable<GetPage> afterAnchor)? filterPages,
GlobalKey<NavigatorState>? key,
GetDelegate? delegate,
}) : this.pickPages(
pickPages: (config) {
Iterable<GetPage<dynamic>> ret;
if (anchorRoute == null) {
// jump the ancestor path
final length = Uri.parse(initialRoute).pathSegments.length;
return config.currentTreeBranch
.skip(length)
.take(length)
.toList();
}
ret = config.currentTreeBranch.pickAfterRoute(anchorRoute);
if (filterPages != null) {
ret = filterPages(ret);
}
return ret;
},
emptyPage: (delegate) =>
Get.routeTree.matchRoute(initialRoute).route ??
delegate.notFoundRoute,
key: key,
delegate: delegate,
);
GetRouterOutlet.pickPages({
Widget Function(GetDelegate delegate)? emptyWidget,
GetPage Function(GetDelegate delegate)? emptyPage,
required Iterable<GetPage> Function(GetNavConfig currentNavStack) pickPages,
bool Function(Route<dynamic>, dynamic)? onPopPage,
GlobalKey<NavigatorState>? key,
GetDelegate? delegate,
}) : super(
pageBuilder: (context, rDelegate, pages) {
final pageRes = <GetPage?>[
...?pages,
if (pages == null || pages.isEmpty) emptyPage?.call(rDelegate),
].whereType<GetPage>();
if (pageRes.isNotEmpty) {
return GetNavigator(
onPopPage: onPopPage ??
(route, result) {
final didPop = route.didPop(result);
if (!didPop) {
return false;
}
return true;
},
pages: pageRes.toList(),
key: key,
);
}
return (emptyWidget?.call(rDelegate) ?? const SizedBox.shrink());
},
pickPages: pickPages,
delegate: delegate ?? Get.rootDelegate,
);
GetRouterOutlet.builder({
required Widget Function(
BuildContext context,
GetDelegate delegate,
GetNavConfig? currentRoute,
)
builder,
GetDelegate? routerDelegate,
}) : super.builder(
builder: builder,
delegate: routerDelegate,
);
}
extension PagesListExt on List<GetPage> {
Iterable<GetPage> pickAtRoute(String route) {
return skipWhile((value) {
return value.name != route;
});
}
Iterable<GetPage> pickAfterRoute(String route) {
return pickAtRoute(route).skip(1);
}
}

View File

@@ -0,0 +1,321 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../../get_core/get_core.dart';
import '../../../get_instance/get_instance.dart';
import '../../../get_state_manager/get_state_manager.dart';
import '../../../get_utils/get_utils.dart';
import '../../get_navigation.dart';
class GetCupertinoApp extends StatelessWidget {
final GlobalKey<NavigatorState>? navigatorKey;
final Widget? home;
final Map<String, WidgetBuilder>? routes;
final String? initialRoute;
final RouteFactory? onGenerateRoute;
final InitialRouteListFactory? onGenerateInitialRoutes;
final RouteFactory? onUnknownRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionBuilder? builder;
final String title;
final GenerateAppTitle? onGenerateTitle;
final CustomTransition? customTransition;
final Color? color;
final Map<String, Map<String, String>>? translationsKeys;
final Translations? translations;
final TextDirection? textDirection;
final Locale? locale;
final Locale? fallbackLocale;
final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
final LocaleListResolutionCallback? localeListResolutionCallback;
final LocaleResolutionCallback? localeResolutionCallback;
final Iterable<Locale> supportedLocales;
final bool showPerformanceOverlay;
final bool checkerboardRasterCacheImages;
final bool checkerboardOffscreenLayers;
final bool showSemanticsDebugger;
final bool debugShowCheckedModeBanner;
final Map<LogicalKeySet, Intent>? shortcuts;
final ThemeData? highContrastTheme;
final ThemeData? highContrastDarkTheme;
final Map<Type, Action<Intent>>? actions;
final Function(Routing?)? routingCallback;
final Transition? defaultTransition;
final bool? opaqueRoute;
final VoidCallback? onInit;
final VoidCallback? onReady;
final VoidCallback? onDispose;
final bool? enableLog;
final LogWriterCallback? logWriterCallback;
final bool? popGesture;
final SmartManagement smartManagement;
final Bindings? initialBinding;
final Duration? transitionDuration;
final bool? defaultGlobalState;
final List<GetPage>? getPages;
final GetPage? unknownRoute;
final RouteInformationProvider? routeInformationProvider;
final RouteInformationParser<Object>? routeInformationParser;
final RouterDelegate<Object>? routerDelegate;
final BackButtonDispatcher? backButtonDispatcher;
final CupertinoThemeData? theme;
final bool useInheritedMediaQuery;
const GetCupertinoApp({
Key? key,
this.theme,
this.navigatorKey,
this.home,
Map<String, Widget Function(BuildContext)> this.routes =
const <String, WidgetBuilder>{},
this.initialRoute,
this.onGenerateRoute,
this.onGenerateInitialRoutes,
this.onUnknownRoute,
List<NavigatorObserver> this.navigatorObservers =
const <NavigatorObserver>[],
this.builder,
this.translationsKeys,
this.translations,
this.textDirection,
this.title = '',
this.onGenerateTitle,
this.color,
this.customTransition,
this.onInit,
this.onDispose,
this.locale,
this.fallbackLocale,
this.localizationsDelegates,
this.localeListResolutionCallback,
this.localeResolutionCallback,
this.supportedLocales = const <Locale>[Locale('en', 'US')],
this.showPerformanceOverlay = false,
this.checkerboardRasterCacheImages = false,
this.checkerboardOffscreenLayers = false,
this.showSemanticsDebugger = false,
this.debugShowCheckedModeBanner = true,
this.shortcuts,
this.smartManagement = SmartManagement.full,
this.initialBinding,
this.useInheritedMediaQuery = false,
this.unknownRoute,
this.routingCallback,
this.defaultTransition,
this.onReady,
this.getPages,
this.opaqueRoute,
this.enableLog = kDebugMode,
this.logWriterCallback,
this.popGesture,
this.transitionDuration,
this.defaultGlobalState,
this.highContrastTheme,
this.highContrastDarkTheme,
this.actions,
}) : routeInformationProvider = null,
routeInformationParser = null,
routerDelegate = null,
backButtonDispatcher = null,
super(key: key);
GetCupertinoApp.router({
Key? key,
this.theme,
this.routeInformationProvider,
RouteInformationParser<Object>? routeInformationParser,
RouterDelegate<Object>? routerDelegate,
this.backButtonDispatcher,
this.builder,
this.title = '',
this.onGenerateTitle,
this.useInheritedMediaQuery = false,
this.color,
this.highContrastTheme,
this.highContrastDarkTheme,
this.locale,
this.localizationsDelegates,
this.localeListResolutionCallback,
this.localeResolutionCallback,
this.supportedLocales = const <Locale>[Locale('en', 'US')],
this.showPerformanceOverlay = false,
this.checkerboardRasterCacheImages = false,
this.checkerboardOffscreenLayers = false,
this.showSemanticsDebugger = false,
this.debugShowCheckedModeBanner = true,
this.shortcuts,
this.actions,
this.customTransition,
this.translationsKeys,
this.translations,
this.textDirection,
this.fallbackLocale,
this.routingCallback,
this.defaultTransition,
this.opaqueRoute,
this.onInit,
this.onReady,
this.onDispose,
this.enableLog = kDebugMode,
this.logWriterCallback,
this.popGesture,
this.smartManagement = SmartManagement.full,
this.initialBinding,
this.transitionDuration,
this.defaultGlobalState,
this.getPages,
this.unknownRoute,
}) : routerDelegate = routerDelegate ??= Get.createDelegate(
notFoundRoute: unknownRoute,
),
routeInformationParser =
routeInformationParser ??= Get.createInformationParser(
initialRoute: getPages?.first.name ?? '/',
),
navigatorObservers = null,
navigatorKey = null,
onGenerateRoute = null,
home = null,
onGenerateInitialRoutes = null,
onUnknownRoute = null,
routes = null,
initialRoute = null,
super(key: key) {
Get.routerDelegate = routerDelegate;
Get.routeInformationParser = routeInformationParser;
}
@override
Widget build(BuildContext context) => GetBuilder<GetMaterialController>(
init: Get.rootController,
dispose: (d) {
onDispose?.call();
},
initState: (i) {
Get.engine.addPostFrameCallback((timeStamp) {
onReady?.call();
});
if (locale != null) Get.locale = locale;
if (fallbackLocale != null) Get.fallbackLocale = fallbackLocale;
if (translations != null) {
Get.addTranslations(translations!.keys);
} else if (translationsKeys != null) {
Get.addTranslations(translationsKeys!);
}
Get.customTransition = customTransition;
initialBinding?.dependencies();
if (getPages != null) {
Get.addPages(getPages!);
}
Get.smartManagement = smartManagement;
onInit?.call();
Get.config(
enableLog: enableLog ?? Get.isLogEnable,
logWriterCallback: logWriterCallback,
defaultTransition: defaultTransition ?? Get.defaultTransition,
defaultOpaqueRoute: opaqueRoute ?? Get.isOpaqueRouteDefault,
defaultPopGesture: popGesture ?? Get.isPopGestureEnable,
defaultDurationTransition:
transitionDuration ?? Get.defaultTransitionDuration,
);
},
builder: (_) => routerDelegate != null
? CupertinoApp.router(
routerDelegate: routerDelegate!,
routeInformationParser: routeInformationParser!,
backButtonDispatcher: backButtonDispatcher,
routeInformationProvider: routeInformationProvider,
key: _.unikey,
theme: theme,
builder: defaultBuilder,
title: title,
onGenerateTitle: onGenerateTitle,
color: color,
locale: Get.locale ?? locale,
localizationsDelegates: localizationsDelegates,
localeListResolutionCallback: localeListResolutionCallback,
localeResolutionCallback: localeResolutionCallback,
supportedLocales: supportedLocales,
showPerformanceOverlay: showPerformanceOverlay,
checkerboardRasterCacheImages: checkerboardRasterCacheImages,
checkerboardOffscreenLayers: checkerboardOffscreenLayers,
showSemanticsDebugger: showSemanticsDebugger,
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
useInheritedMediaQuery: useInheritedMediaQuery,
)
: CupertinoApp(
key: _.unikey,
theme: theme,
navigatorKey: (navigatorKey == null
? Get.key
: Get.addKey(navigatorKey!)),
home: home,
routes: routes ?? const <String, WidgetBuilder>{},
initialRoute: initialRoute,
onGenerateRoute:
(getPages != null ? generator : onGenerateRoute),
onGenerateInitialRoutes: (getPages == null || home != null)
? onGenerateInitialRoutes
: initialRoutesGenerate,
onUnknownRoute: onUnknownRoute,
navigatorObservers: (navigatorObservers == null
? <NavigatorObserver>[
GetObserver(routingCallback, Get.routing)
]
: <NavigatorObserver>[
GetObserver(routingCallback, Get.routing)
]
..addAll(navigatorObservers!)),
builder: defaultBuilder,
title: title,
onGenerateTitle: onGenerateTitle,
color: color,
locale: Get.locale ?? locale,
localizationsDelegates: localizationsDelegates,
localeListResolutionCallback: localeListResolutionCallback,
localeResolutionCallback: localeResolutionCallback,
supportedLocales: supportedLocales,
showPerformanceOverlay: showPerformanceOverlay,
checkerboardRasterCacheImages: checkerboardRasterCacheImages,
checkerboardOffscreenLayers: checkerboardOffscreenLayers,
showSemanticsDebugger: showSemanticsDebugger,
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
useInheritedMediaQuery: useInheritedMediaQuery,
// actions: actions,
),
);
Widget defaultBuilder(BuildContext context, Widget? child) {
return Directionality(
textDirection: textDirection ??
(rtlLanguages.contains(Get.locale?.languageCode)
? TextDirection.rtl
: TextDirection.ltr),
child: builder == null
? (child ?? Material())
: builder!(context, child ?? Material()),
);
}
Route<dynamic> generator(RouteSettings settings) {
return PageRedirect(settings: settings, unknownRoute: unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) {
return [
PageRedirect(
settings: RouteSettings(name: name),
unknownRoute: unknownRoute,
).page()
];
}
}

View File

@@ -0,0 +1,351 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../../get_core/get_core.dart';
import '../../../get_instance/get_instance.dart';
import '../../../get_state_manager/get_state_manager.dart';
import '../../../get_utils/get_utils.dart';
import '../../get_navigation.dart';
class GetMaterialApp extends StatelessWidget {
final GlobalKey<NavigatorState>? navigatorKey;
final GlobalKey<ScaffoldMessengerState>? scaffoldMessengerKey;
final Widget? home;
final Map<String, WidgetBuilder>? routes;
final String? initialRoute;
final RouteFactory? onGenerateRoute;
final InitialRouteListFactory? onGenerateInitialRoutes;
final RouteFactory? onUnknownRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionBuilder? builder;
final String title;
final GenerateAppTitle? onGenerateTitle;
final ThemeData? theme;
final ThemeData? darkTheme;
final ThemeMode themeMode;
final CustomTransition? customTransition;
final Color? color;
final Map<String, Map<String, String>>? translationsKeys;
final Translations? translations;
final TextDirection? textDirection;
final Locale? locale;
final Locale? fallbackLocale;
final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
final LocaleListResolutionCallback? localeListResolutionCallback;
final LocaleResolutionCallback? localeResolutionCallback;
final Iterable<Locale> supportedLocales;
final bool showPerformanceOverlay;
final bool checkerboardRasterCacheImages;
final bool checkerboardOffscreenLayers;
final bool showSemanticsDebugger;
final bool debugShowCheckedModeBanner;
final Map<LogicalKeySet, Intent>? shortcuts;
final ScrollBehavior? scrollBehavior;
final ThemeData? highContrastTheme;
final ThemeData? highContrastDarkTheme;
final Map<Type, Action<Intent>>? actions;
final bool debugShowMaterialGrid;
final ValueChanged<Routing?>? routingCallback;
final Transition? defaultTransition;
final bool? opaqueRoute;
final VoidCallback? onInit;
final VoidCallback? onReady;
final VoidCallback? onDispose;
final bool? enableLog;
final LogWriterCallback? logWriterCallback;
final bool? popGesture;
final SmartManagement smartManagement;
final Bindings? initialBinding;
final Duration? transitionDuration;
final bool? defaultGlobalState;
final List<GetPage>? getPages;
final GetPage? unknownRoute;
final RouteInformationProvider? routeInformationProvider;
final RouteInformationParser<Object>? routeInformationParser;
final RouterDelegate<Object>? routerDelegate;
final BackButtonDispatcher? backButtonDispatcher;
final bool useInheritedMediaQuery;
const GetMaterialApp({
Key? key,
this.navigatorKey,
this.scaffoldMessengerKey,
this.home,
Map<String, Widget Function(BuildContext)> this.routes =
const <String, WidgetBuilder>{},
this.initialRoute,
this.onGenerateRoute,
this.onGenerateInitialRoutes,
this.onUnknownRoute,
this.useInheritedMediaQuery = false,
List<NavigatorObserver> this.navigatorObservers =
const <NavigatorObserver>[],
this.builder,
this.textDirection,
this.title = '',
this.onGenerateTitle,
this.color,
this.theme,
this.darkTheme,
this.themeMode = ThemeMode.system,
this.locale,
this.fallbackLocale,
this.localizationsDelegates,
this.localeListResolutionCallback,
this.localeResolutionCallback,
this.supportedLocales = const <Locale>[Locale('en', 'US')],
this.debugShowMaterialGrid = false,
this.showPerformanceOverlay = false,
this.checkerboardRasterCacheImages = false,
this.checkerboardOffscreenLayers = false,
this.showSemanticsDebugger = false,
this.debugShowCheckedModeBanner = true,
this.shortcuts,
this.scrollBehavior,
this.customTransition,
this.translationsKeys,
this.translations,
this.onInit,
this.onReady,
this.onDispose,
this.routingCallback,
this.defaultTransition,
this.getPages,
this.opaqueRoute,
this.enableLog = kDebugMode,
this.logWriterCallback,
this.popGesture,
this.transitionDuration,
this.defaultGlobalState,
this.smartManagement = SmartManagement.full,
this.initialBinding,
this.unknownRoute,
this.highContrastTheme,
this.highContrastDarkTheme,
this.actions,
}) : routeInformationProvider = null,
routeInformationParser = null,
routerDelegate = null,
backButtonDispatcher = null,
super(key: key);
GetMaterialApp.router({
Key? key,
this.routeInformationProvider,
this.scaffoldMessengerKey,
RouteInformationParser<Object>? routeInformationParser,
RouterDelegate<Object>? routerDelegate,
this.backButtonDispatcher,
this.builder,
this.title = '',
this.onGenerateTitle,
this.color,
this.theme,
this.darkTheme,
this.useInheritedMediaQuery = false,
this.highContrastTheme,
this.highContrastDarkTheme,
this.themeMode = ThemeMode.system,
this.locale,
this.localizationsDelegates,
this.localeListResolutionCallback,
this.localeResolutionCallback,
this.supportedLocales = const <Locale>[Locale('en', 'US')],
this.debugShowMaterialGrid = false,
this.showPerformanceOverlay = false,
this.checkerboardRasterCacheImages = false,
this.checkerboardOffscreenLayers = false,
this.showSemanticsDebugger = false,
this.debugShowCheckedModeBanner = true,
this.shortcuts,
this.scrollBehavior,
this.actions,
this.customTransition,
this.translationsKeys,
this.translations,
this.textDirection,
this.fallbackLocale,
this.routingCallback,
this.defaultTransition,
this.opaqueRoute,
this.onInit,
this.onReady,
this.onDispose,
this.enableLog = kDebugMode,
this.logWriterCallback,
this.popGesture,
this.smartManagement = SmartManagement.full,
this.initialBinding,
this.transitionDuration,
this.defaultGlobalState,
this.getPages,
this.navigatorObservers,
this.unknownRoute,
}) : routerDelegate = routerDelegate ??= Get.createDelegate(
notFoundRoute: unknownRoute,
),
routeInformationParser =
routeInformationParser ??= Get.createInformationParser(
initialRoute: getPages?.first.name ?? '/',
),
//navigatorObservers = null,
navigatorKey = null,
onGenerateRoute = null,
home = null,
onGenerateInitialRoutes = null,
onUnknownRoute = null,
routes = null,
initialRoute = null,
super(key: key) {
Get.routerDelegate = routerDelegate;
Get.routeInformationParser = routeInformationParser;
}
@override
Widget build(BuildContext context) => GetBuilder<GetMaterialController>(
init: Get.rootController,
dispose: (d) {
onDispose?.call();
},
initState: (i) {
Get.engine.addPostFrameCallback((timeStamp) {
onReady?.call();
});
if (locale != null) Get.locale = locale;
if (fallbackLocale != null) Get.fallbackLocale = fallbackLocale;
if (translations != null) {
Get.addTranslations(translations!.keys);
} else if (translationsKeys != null) {
Get.addTranslations(translationsKeys!);
}
Get.customTransition = customTransition;
initialBinding?.dependencies();
if (getPages != null) {
Get.addPages(getPages!);
}
//Get.setDefaultDelegate(routerDelegate);
Get.smartManagement = smartManagement;
onInit?.call();
Get.config(
enableLog: enableLog ?? Get.isLogEnable,
logWriterCallback: logWriterCallback,
defaultTransition: defaultTransition ?? Get.defaultTransition,
defaultOpaqueRoute: opaqueRoute ?? Get.isOpaqueRouteDefault,
defaultPopGesture: popGesture ?? Get.isPopGestureEnable,
defaultDurationTransition:
transitionDuration ?? Get.defaultTransitionDuration,
);
},
builder: (_) => routerDelegate != null
? MaterialApp.router(
routerDelegate: routerDelegate!,
routeInformationParser: routeInformationParser!,
backButtonDispatcher: backButtonDispatcher,
routeInformationProvider: routeInformationProvider,
key: _.unikey,
builder: defaultBuilder,
title: title,
onGenerateTitle: onGenerateTitle,
color: color,
theme: _.theme ?? theme ?? ThemeData.fallback(),
darkTheme:
_.darkTheme ?? darkTheme ?? theme ?? ThemeData.fallback(),
themeMode: _.themeMode ?? themeMode,
locale: Get.locale ?? locale,
scaffoldMessengerKey:
scaffoldMessengerKey ?? _.scaffoldMessengerKey,
localizationsDelegates: localizationsDelegates,
localeListResolutionCallback: localeListResolutionCallback,
localeResolutionCallback: localeResolutionCallback,
supportedLocales: supportedLocales,
debugShowMaterialGrid: debugShowMaterialGrid,
showPerformanceOverlay: showPerformanceOverlay,
checkerboardRasterCacheImages: checkerboardRasterCacheImages,
checkerboardOffscreenLayers: checkerboardOffscreenLayers,
showSemanticsDebugger: showSemanticsDebugger,
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
scrollBehavior: scrollBehavior,
useInheritedMediaQuery: useInheritedMediaQuery,
)
: MaterialApp(
key: _.unikey,
navigatorKey: (navigatorKey == null
? Get.key
: Get.addKey(navigatorKey!)),
scaffoldMessengerKey:
scaffoldMessengerKey ?? _.scaffoldMessengerKey,
home: home,
routes: routes ?? const <String, WidgetBuilder>{},
initialRoute: initialRoute,
onGenerateRoute:
(getPages != null ? generator : onGenerateRoute),
onGenerateInitialRoutes: (getPages == null || home != null)
? onGenerateInitialRoutes
: initialRoutesGenerate,
onUnknownRoute: onUnknownRoute,
navigatorObservers: (navigatorObservers == null
? <NavigatorObserver>[
GetObserver(routingCallback, Get.routing)
]
: <NavigatorObserver>[
GetObserver(routingCallback, Get.routing)
]
..addAll(navigatorObservers!)),
builder: defaultBuilder,
title: title,
onGenerateTitle: onGenerateTitle,
color: color,
theme: _.theme ?? theme ?? ThemeData.fallback(),
darkTheme:
_.darkTheme ?? darkTheme ?? theme ?? ThemeData.fallback(),
themeMode: _.themeMode ?? themeMode,
locale: Get.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,
useInheritedMediaQuery: useInheritedMediaQuery,
// actions: actions,
),
);
Widget defaultBuilder(BuildContext context, Widget? child) {
return Directionality(
textDirection: textDirection ??
(rtlLanguages.contains(Get.locale?.languageCode)
? TextDirection.rtl
: TextDirection.ltr),
child: builder == null
? (child ?? Material())
: builder!(context, child ?? Material()),
);
}
Route<dynamic> generator(RouteSettings settings) {
return PageRedirect(settings: settings, unknownRoute: unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) {
return [
PageRedirect(
settings: RouteSettings(name: name),
unknownRoute: unknownRoute,
).page()
];
}
}

View File

@@ -0,0 +1,11 @@
const List<String> rtlLanguages = <String>[
'ar', // Arabic
'fa', // Farsi
'he', // Hebrew
'ps', // Pashto
'ur',
];
abstract class Translations {
Map<String, Map<String, String>> get keys;
}

View File

@@ -0,0 +1,188 @@
import '../../get_navigation.dart';
class RouteDecoder {
final List<GetPage> treeBranch;
GetPage? get route => treeBranch.isEmpty ? null : treeBranch.last;
final Map<String, String> parameters;
final Object? arguments;
const RouteDecoder(
this.treeBranch,
this.parameters,
this.arguments,
);
void replaceArguments(Object? arguments) {
final _route = route;
if (_route != null) {
final index = treeBranch.indexOf(_route);
treeBranch[index] = _route.copy(arguments: arguments);
}
}
void replaceParameters(Object? arguments) {
final _route = route;
if (_route != null) {
final index = treeBranch.indexOf(_route);
treeBranch[index] = _route.copy(parameters: parameters);
}
}
}
class ParseRouteTree {
ParseRouteTree({
required this.routes,
});
final List<GetPage> routes;
RouteDecoder matchRoute(String name, {Object? arguments}) {
final uri = Uri.parse(name);
// /home/profile/123 => home,profile,123 => /,/home,/home/profile,/home/profile/123
final split = uri.path.split('/').where((element) => element.isNotEmpty);
var curPath = '/';
final cumulativePaths = <String>[
'/',
];
for (var item in split) {
if (curPath.endsWith('/')) {
curPath += '$item';
} else {
curPath += '/$item';
}
cumulativePaths.add(curPath);
}
final treeBranch = cumulativePaths
.map((e) => MapEntry(e, _findRoute(e)))
.where((element) => element.value != null)
.map((e) => MapEntry(e.key, e.value!))
.toList();
final params = Map<String, String>.from(uri.queryParameters);
if (treeBranch.isNotEmpty) {
//route is found, do further parsing to get nested query params
final lastRoute = treeBranch.last;
final parsedParams = _parseParams(name, lastRoute.value.path);
if (parsedParams.isNotEmpty) {
params.addAll(parsedParams);
}
//copy parameters to all pages.
final mappedTreeBranch = treeBranch
.map(
(e) => e.value.copy(
parameters: {
if (e.value.parameters != null) ...e.value.parameters!,
...params,
},
name: e.key,
),
)
.toList();
return RouteDecoder(
mappedTreeBranch,
params,
arguments,
);
}
//route not found
return RouteDecoder(
treeBranch.map((e) => e.value).toList(),
params,
arguments,
);
}
void addRoutes(List<GetPage> getPages) {
for (final route in getPages) {
addRoute(route);
}
}
void addRoute(GetPage route) {
routes.add(route);
// Add Page children.
for (var page in _flattenPage(route)) {
addRoute(page);
}
}
List<GetPage> _flattenPage(GetPage route) {
final result = <GetPage>[];
if (route.children.isEmpty) {
return result;
}
final parentPath = route.name;
for (var page in route.children) {
// Add Parent middlewares to children
final parentMiddlewares = [
if (page.middlewares != null) ...page.middlewares!,
if (route.middlewares != null) ...route.middlewares!
];
result.add(
_addChild(
page,
parentPath,
parentMiddlewares,
),
);
final children = _flattenPage(page);
for (var child in children) {
result.add(_addChild(
child,
parentPath,
[
...parentMiddlewares,
if (child.middlewares != null) ...child.middlewares!,
],
));
}
}
return result;
}
/// Change the Path for a [GetPage]
GetPage _addChild(
GetPage origin, String parentPath, List<GetMiddleware> middlewares) =>
origin.copy(
middlewares: middlewares,
name: (parentPath + origin.name).replaceAll(r'//', '/'),
);
GetPage? _findRoute(String name) {
return routes.firstWhereOrNull(
(route) => route.path.regex.hasMatch(name),
);
}
Map<String, String> _parseParams(String path, PathDecoded routePath) {
final params = <String, String>{};
var idx = path.indexOf('?');
if (idx > -1) {
path = path.substring(0, idx);
final uri = Uri.tryParse(path);
if (uri != null) {
params.addAll(uri.queryParameters);
}
}
var paramsMatch = routePath.regex.firstMatch(path);
for (var i = 0; i < routePath.keys.length; i++) {
var param = Uri.decodeQueryComponent(paramsMatch![i + 1]!);
params[routePath.keys[i]!] = param;
}
return params;
}
}
extension FirstWhereExt<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) {
if (test(element)) return element;
}
return null;
}
}

View File

@@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import '../../../get.dart';
class GetMaterialController extends SuperController {
bool testMode = false;
Key? unikey;
ThemeData? theme;
ThemeData? darkTheme;
ThemeMode? themeMode;
final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
bool defaultPopGesture = GetPlatform.isIOS;
bool defaultOpaqueRoute = true;
Transition? defaultTransition;
Duration defaultTransitionDuration = Duration(milliseconds: 300);
Curve defaultTransitionCurve = Curves.easeOutQuad;
Curve defaultDialogTransitionCurve = Curves.easeOutQuad;
Duration defaultDialogTransitionDuration = Duration(milliseconds: 300);
final routing = Routing();
Map<String, String?> parameters = {};
CustomTransition? customTransition;
var _key = GlobalKey<NavigatorState>(debugLabel: 'Key Created by default');
Map<dynamic, GlobalKey<NavigatorState>> keys = {};
GlobalKey<NavigatorState> get key => _key;
GlobalKey<NavigatorState>? addKey(GlobalKey<NavigatorState> newKey) {
_key = newKey;
return key;
}
@override
void didChangeLocales(List<Locale>? locales) {
Get.asap(() {
final locale = Get.deviceLocale;
if (locale != null) {
Get.updateLocale(locale);
}
});
}
@override
void onDetached() {}
@override
void onInactive() {}
@override
void onPaused() {}
@override
void onResumed() {}
void restartApp() {
unikey = UniqueKey();
update();
}
void setTheme(ThemeData value) {
if (darkTheme == null) {
theme = value;
} else {
if (value.brightness == Brightness.light) {
theme = value;
} else {
darkTheme = value;
}
}
update();
}
void setThemeMode(ThemeMode value) {
themeMode = value;
update();
}
}

View File

@@ -0,0 +1,113 @@
import 'dart:collection';
import 'package:flutter/widgets.dart';
import '../../get.dart';
class RouterReportManager<T> {
/// Holds a reference to `Get.reference` when the Instance was
/// created to manage the memory.
static final Map<Route?, List<String>> _routesKey = {};
/// Stores the onClose() references of instances created with `Get.create()`
/// using the `Get.reference`.
/// Experimental feature to keep the lifecycle and memory management with
/// non-singleton instances.
static final Map<Route?, HashSet<Function>> _routesByCreate = {};
void printInstanceStack() {
Get.log(_routesKey.toString());
}
static Route? _current;
// ignore: use_setters_to_change_properties
static void reportCurrentRoute(Route newRoute) {
_current = newRoute;
}
/// Links a Class instance [S] (or [tag]) to the current route.
/// Requires usage of `GetMaterialApp`.
static void reportDependencyLinkedToRoute(String depedencyKey) {
if (_current == null) return;
if (_routesKey.containsKey(_current)) {
_routesKey[_current!]!.add(depedencyKey);
} else {
_routesKey[_current] = <String>[depedencyKey];
}
}
static void clearRouteKeys() {
_routesKey.clear();
_routesByCreate.clear();
}
static void appendRouteByCreate(GetLifeCycleBase i) {
_routesByCreate[_current] ??= HashSet<Function>();
// _routesByCreate[Get.reference]!.add(i.onDelete as Function);
_routesByCreate[_current]!.add(i.onDelete);
}
static void reportRouteDispose(Route disposed) {
if (Get.smartManagement != SmartManagement.onlyBuilder) {
ambiguate(WidgetsBinding.instance)?.addPostFrameCallback((_) {
_removeDependencyByRoute(disposed);
});
}
}
static void reportRouteWillDispose(Route disposed) {
final keysToRemove = <String>[];
_routesKey[disposed]?.forEach(keysToRemove.add);
/// Removes `Get.create()` instances registered in `routeName`.
if (_routesByCreate.containsKey(disposed)) {
for (final onClose in _routesByCreate[disposed]!) {
// assure the [DisposableInterface] instance holding a reference
// to onClose() wasn't disposed.
onClose();
}
_routesByCreate[disposed]!.clear();
_routesByCreate.remove(disposed);
}
for (final element in keysToRemove) {
GetInstance().markAsDirty(key: element);
//_routesKey.remove(element);
}
keysToRemove.clear();
}
/// Clears from memory registered Instances associated with [routeName] when
/// using `Get.smartManagement` as [SmartManagement.full] or
/// [SmartManagement.keepFactory]
/// Meant for internal usage of `GetPageRoute` and `GetDialogRoute`
static void _removeDependencyByRoute(Route routeName) {
final keysToRemove = <String>[];
_routesKey[routeName]?.forEach(keysToRemove.add);
/// Removes `Get.create()` instances registered in `routeName`.
if (_routesByCreate.containsKey(routeName)) {
for (final onClose in _routesByCreate[routeName]!) {
// assure the [DisposableInterface] instance holding a reference
// to onClose() wasn't disposed.
onClose();
}
_routesByCreate[routeName]!.clear();
_routesByCreate.remove(routeName);
}
for (final element in keysToRemove) {
final value = GetInstance().delete(key: element);
if (value) {
_routesKey[routeName]?.remove(element);
}
}
keysToRemove.clear();
}
}

View File

@@ -0,0 +1,46 @@
import 'dart:math' show sqrt, max;
import 'dart:ui' show lerpDouble;
import 'package:flutter/material.dart';
class CircularRevealClipper extends CustomClipper<Path> {
final double fraction;
final Alignment? centerAlignment;
final Offset? centerOffset;
final double? minRadius;
final double? maxRadius;
CircularRevealClipper({
required this.fraction,
this.centerAlignment,
this.centerOffset,
this.minRadius,
this.maxRadius,
});
@override
Path getClip(Size size) {
final center = centerAlignment?.alongSize(size) ??
centerOffset ??
Offset(size.width / 2, size.height / 2);
final minRadius = this.minRadius ?? 0;
final maxRadius = this.maxRadius ?? calcMaxRadius(size, center);
return Path()
..addOval(
Rect.fromCircle(
center: center,
radius: lerpDouble(minRadius, maxRadius, fraction)!,
),
);
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => true;
static double calcMaxRadius(Size size, Offset center) {
final w = max(center.dx, size.width - center.dx);
final h = max(center.dy, size.height - center.dy);
return sqrt(w * w + h * h);
}
}

View File

@@ -0,0 +1,13 @@
import 'package:flutter/widgets.dart';
// ignore: one_member_abstracts
abstract class CustomTransition {
Widget buildTransition(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
);
}

View File

@@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import '../../../get.dart';
import '../router_report.dart';
import 'get_transition_mixin.dart';
mixin PageRouteReportMixin<T> on Route<T> {
@override
void install() {
super.install();
RouterReportManager.reportCurrentRoute(this);
}
@override
void dispose() {
super.dispose();
RouterReportManager.reportRouteDispose(this);
}
}
class GetPageRoute<T> extends PageRoute<T>
with GetPageRouteTransitionMixin<T>, PageRouteReportMixin {
/// Creates a page route for use in an iOS designed app.
///
/// The [builder], [maintainState], and [fullscreenDialog] arguments must not
/// be null.
GetPageRoute({
RouteSettings? settings,
this.transitionDuration = const Duration(milliseconds: 300),
this.opaque = true,
this.parameter,
this.gestureWidth,
this.curve,
this.alignment,
this.transition,
this.popGesture,
this.customTransition,
this.barrierDismissible = false,
this.barrierColor,
this.binding,
this.bindings,
this.routeName,
this.page,
this.title,
this.showCupertinoParallax = true,
this.barrierLabel,
this.maintainState = true,
bool fullscreenDialog = false,
this.middlewares,
}) : super(settings: settings, fullscreenDialog: fullscreenDialog);
@override
final Duration transitionDuration;
final GetPageBuilder? page;
final String? routeName;
//final String reference;
final CustomTransition? customTransition;
final Bindings? binding;
final Map<String, String>? parameter;
final List<Bindings>? bindings;
@override
final bool showCupertinoParallax;
@override
final bool opaque;
final bool? popGesture;
@override
final bool barrierDismissible;
final Transition? transition;
final Curve? curve;
final Alignment? alignment;
final List<GetMiddleware>? middlewares;
@override
final Color? barrierColor;
@override
final String? barrierLabel;
@override
final bool maintainState;
@override
void dispose() {
super.dispose();
final middlewareRunner = MiddlewareRunner(middlewares);
middlewareRunner.runOnPageDispose();
}
Widget? _child;
Widget _getChild() {
if (_child != null) return _child!;
final middlewareRunner = MiddlewareRunner(middlewares);
final localbindings = [
if (bindings != null) ...bindings!,
if (binding != null) ...[binding!]
];
final bindingsToBind = middlewareRunner.runOnBindingsStart(localbindings);
if (bindingsToBind != null) {
for (final binding in bindingsToBind) {
binding.dependencies();
}
}
final pageToBuild = middlewareRunner.runOnPageBuildStart(page)!;
_child = middlewareRunner.runOnPageBuilt(pageToBuild());
return _child!;
}
@override
Widget buildContent(BuildContext context) {
return _getChild();
}
@override
final String? title;
@override
String get debugLabel => '${super.debugLabel}(${settings.name})';
@override
final double Function(BuildContext context)? gestureWidth;
}

View File

@@ -0,0 +1,209 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'circular_reveal_clipper.dart';
class LeftToRightFadeTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: FadeTransition(
opacity: animation,
child: SlideTransition(
position: Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.0, 0.0),
).animate(secondaryAnimation),
child: child),
),
);
}
}
class RightToLeftFadeTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: FadeTransition(
opacity: animation,
child: SlideTransition(
position: Tween<Offset>(
begin: Offset.zero,
end: const Offset(-1.0, 0.0),
).animate(secondaryAnimation),
child: child),
),
);
}
}
class NoTransition {
Widget buildTransitions(
BuildContext context,
Curve curve,
Alignment alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return child;
}
}
class FadeInTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return FadeTransition(opacity: animation, child: child);
}
}
class SlideDownTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: Offset(0.0, 1.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
}
class SlideLeftTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: Offset(-1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
}
class SlideRightTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
}
class SlideTopTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: Offset(0.0, -1.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
}
class ZoomInTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return ScaleTransition(
scale: animation,
child: child,
);
}
}
class SizeTransitions {
Widget buildTransitions(
BuildContext context,
Curve curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return Align(
alignment: Alignment.center,
child: SizeTransition(
sizeFactor: CurvedAnimation(
parent: animation,
curve: curve,
),
child: child,
),
);
}
}
class CircularRevealTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return ClipPath(
clipper: CircularRevealClipper(
fraction: animation.value,
centerAlignment: Alignment.center,
centerOffset: Offset.zero,
minRadius: 0,
maxRadius: 800,
),
child: child,
);
}
}

View File

@@ -0,0 +1,184 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../../get_core/src/get_main.dart';
import '../../../get_instance/get_instance.dart';
import '../../get_navigation.dart';
class GetPage<T> extends Page<T> {
final GetPageBuilder page;
final bool? popGesture;
final Map<String, String>? parameters;
final String? title;
final Transition? transition;
final Curve curve;
final bool? participatesInRootNavigator;
final Alignment? alignment;
final bool maintainState;
final bool opaque;
final double Function(BuildContext context)? gestureWidth;
final Bindings? binding;
final List<Bindings> bindings;
final CustomTransition? customTransition;
final Duration? transitionDuration;
final bool fullscreenDialog;
final bool preventDuplicates;
// @override
// final LocalKey? key;
// @override
// RouteSettings get settings => this;
@override
final Object? arguments;
@override
final String name;
final List<GetPage> children;
final List<GetMiddleware>? middlewares;
final PathDecoded path;
final GetPage? unknownRoute;
final bool showCupertinoParallax;
GetPage({
required this.name,
required this.page,
this.title,
this.participatesInRootNavigator,
this.gestureWidth,
// RouteSettings settings,
this.maintainState = true,
this.curve = Curves.linear,
this.alignment,
this.parameters,
this.opaque = true,
this.transitionDuration,
this.popGesture,
this.binding,
this.bindings = const [],
this.transition,
this.customTransition,
this.fullscreenDialog = false,
this.children = const <GetPage>[],
this.middlewares,
this.unknownRoute,
this.arguments,
this.showCupertinoParallax = true,
this.preventDuplicates = true,
}) : path = _nameToRegex(name),
assert(name.startsWith('/'),
'It is necessary to start route name [$name] with a slash: /$name'),
super(
key: ValueKey(name),
name: name,
arguments: Get.arguments,
);
// settings = RouteSettings(name: name, arguments: Get.arguments);
GetPage<T> copy({
String? name,
GetPageBuilder? page,
bool? popGesture,
Map<String, String>? parameters,
String? title,
Transition? transition,
Curve? curve,
Alignment? alignment,
bool? maintainState,
bool? opaque,
Bindings? binding,
List<Bindings>? bindings,
CustomTransition? customTransition,
Duration? transitionDuration,
bool? fullscreenDialog,
RouteSettings? settings,
List<GetPage>? children,
GetPage? unknownRoute,
List<GetMiddleware>? middlewares,
bool? preventDuplicates,
final double Function(BuildContext context)? gestureWidth,
bool? participatesInRootNavigator,
Object? arguments,
bool? showCupertinoParallax,
}) {
return GetPage(
participatesInRootNavigator:
participatesInRootNavigator ?? this.participatesInRootNavigator,
preventDuplicates: preventDuplicates ?? this.preventDuplicates,
name: name ?? this.name,
page: page ?? this.page,
popGesture: popGesture ?? this.popGesture,
parameters: parameters ?? this.parameters,
title: title ?? this.title,
transition: transition ?? this.transition,
curve: curve ?? this.curve,
alignment: alignment ?? this.alignment,
maintainState: maintainState ?? this.maintainState,
opaque: opaque ?? this.opaque,
binding: binding ?? this.binding,
bindings: bindings ?? this.bindings,
customTransition: customTransition ?? this.customTransition,
transitionDuration: transitionDuration ?? this.transitionDuration,
fullscreenDialog: fullscreenDialog ?? this.fullscreenDialog,
children: children ?? this.children,
unknownRoute: unknownRoute ?? this.unknownRoute,
middlewares: middlewares ?? this.middlewares,
gestureWidth: gestureWidth ?? this.gestureWidth,
arguments: arguments ?? this.arguments,
showCupertinoParallax:
showCupertinoParallax ?? this.showCupertinoParallax,
);
}
@override
Route<T> createRoute(BuildContext context) {
// return GetPageRoute<T>(settings: this, page: page);
final _page = PageRedirect(
route: this,
settings: this,
unknownRoute: unknownRoute,
).getPageToRoute<T>(this, unknownRoute);
return _page;
}
static PathDecoded _nameToRegex(String path) {
var keys = <String?>[];
String _replace(Match pattern) {
var buffer = StringBuffer('(?:');
if (pattern[1] != null) buffer.write('\.');
buffer.write('([\\w%+-._~!\$&\'()*,;=:@]+))');
if (pattern[3] != null) buffer.write('?');
keys.add(pattern[2]);
return "$buffer";
}
var stringPath = '$path/?'
.replaceAllMapped(RegExp(r'(\.)?:(\w+)(\?)?'), _replace)
.replaceAll('//', '/');
return PathDecoded(RegExp('^$stringPath\$'), keys);
}
}
@immutable
class PathDecoded {
final RegExp regex;
final List<String?> keys;
const PathDecoded(this.regex, this.keys);
@override
int get hashCode => regex.hashCode;
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is PathDecoded &&
other.regex == regex; // && listEquals(other.keys, keys);
}
}

View File

@@ -0,0 +1,714 @@
import 'dart:math';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import '../../../get.dart';
import 'default_transitions.dart';
const double _kBackGestureWidth = 20.0;
const int _kMaxDroppedSwipePageForwardAnimationTime =
800; // Screen widths per second.
// An eyeballed value for the maximum time it takes
//for a page to animate forward
// if the user releases a page mid swipe.
const int _kMaxPageBackAnimationTime = 300; // Milliseconds.
// The maximum time for a page to get reset to it's original position if the
// user releases a page mid swipe.
const double _kMinFlingVelocity = 1.0; // Milliseconds.
class CupertinoBackGestureController<T> {
final AnimationController controller;
final NavigatorState navigator;
/// Creates a controller for an iOS-style back gesture.
///
/// The [navigator] and [controller] arguments must not be null.
CupertinoBackGestureController({
required this.navigator,
required this.controller,
}) {
navigator.didStartUserGesture();
}
/// The drag gesture has ended with a horizontal motion of
/// [fractionalVelocity] as a fraction of screen width per second.
void dragEnd(double velocity) {
// Fling in the appropriate direction.
// AnimationController.fling is guaranteed to
// take at least one frame.
//
// This curve has been determined through rigorously eyeballing native iOS
// animations.
const Curve animationCurve = Curves.fastLinearToSlowEaseIn;
final bool animateForward;
// If the user releases the page before mid screen with sufficient velocity,
// or after mid screen, we should animate the page out. Otherwise, the page
// should be animated back in.
if (velocity.abs() >= _kMinFlingVelocity) {
animateForward = velocity <= 0;
} else {
animateForward = controller.value > 0.5;
}
if (animateForward) {
// The closer the panel is to dismissing, the shorter the animation is.
// We want to cap the animation time, but we want to use a linear curve
// to determine it.
final droppedPageForwardAnimationTime = min(
lerpDouble(
_kMaxDroppedSwipePageForwardAnimationTime, 0, controller.value)!
.floor(),
_kMaxPageBackAnimationTime,
);
controller.animateTo(1.0,
duration: Duration(milliseconds: droppedPageForwardAnimationTime),
curve: animationCurve);
} else {
// This route is destined to pop at this point. Reuse navigator's pop.
navigator.pop();
// The popping may have finished inline if already at the
// target destination.
if (controller.isAnimating) {
// Otherwise, use a custom popping animation duration and curve.
final droppedPageBackAnimationTime = lerpDouble(
0, _kMaxDroppedSwipePageForwardAnimationTime, controller.value)!
.floor();
controller.animateBack(0.0,
duration: Duration(milliseconds: droppedPageBackAnimationTime),
curve: animationCurve);
}
}
if (controller.isAnimating) {
// Keep the userGestureInProgress in true state so we don't change the
// curve of the page transition mid-flight since CupertinoPageTransition
// depends on userGestureInProgress.
late AnimationStatusListener animationStatusCallback;
animationStatusCallback = (status) {
navigator.didStopUserGesture();
controller.removeStatusListener(animationStatusCallback);
};
controller.addStatusListener(animationStatusCallback);
} else {
navigator.didStopUserGesture();
}
}
/// The drag gesture has changed by [fractionalDelta]. The total range of the
/// drag should be 0.0 to 1.0.
void dragUpdate(double delta) {
controller.value -= delta;
}
}
class CupertinoBackGestureDetector<T> extends StatefulWidget {
final Widget child;
final double gestureWidth;
final ValueGetter<bool> enabledCallback;
final ValueGetter<CupertinoBackGestureController<T>> onStartPopGesture;
const CupertinoBackGestureDetector({
Key? key,
required this.enabledCallback,
required this.onStartPopGesture,
required this.child,
required this.gestureWidth,
}) : super(key: key);
@override
CupertinoBackGestureDetectorState<T> createState() =>
CupertinoBackGestureDetectorState<T>();
}
class CupertinoBackGestureDetectorState<T>
extends State<CupertinoBackGestureDetector<T>> {
CupertinoBackGestureController<T>? _backGestureController;
late HorizontalDragGestureRecognizer _recognizer;
@override
Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context));
// For devices with notches, the drag area needs to be larger on the side
// that has the notch.
var dragAreaWidth = Directionality.of(context) == TextDirection.ltr
? MediaQuery.of(context).padding.left
: MediaQuery.of(context).padding.right;
dragAreaWidth = max(dragAreaWidth, widget.gestureWidth);
return Stack(
fit: StackFit.passthrough,
children: <Widget>[
widget.child,
PositionedDirectional(
start: 0.0,
width: dragAreaWidth,
top: 0.0,
bottom: 0.0,
child: Listener(
onPointerDown: _handlePointerDown,
behavior: HitTestBehavior.translucent,
),
),
],
);
}
@override
void dispose() {
_recognizer.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
_recognizer = HorizontalDragGestureRecognizer(debugOwner: this)
..onStart = _handleDragStart
..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd
..onCancel = _handleDragCancel;
}
double _convertToLogical(double value) {
switch (Directionality.of(context)) {
case TextDirection.rtl:
return -value;
case TextDirection.ltr:
return value;
}
}
void _handleDragCancel() {
assert(mounted);
// This can be called even if start is not called, paired with
// the "down" event
// that we don't consider here.
_backGestureController?.dragEnd(0.0);
_backGestureController = null;
}
void _handleDragEnd(DragEndDetails details) {
assert(mounted);
assert(_backGestureController != null);
_backGestureController!.dragEnd(_convertToLogical(
details.velocity.pixelsPerSecond.dx / context.size!.width));
_backGestureController = null;
}
void _handleDragStart(DragStartDetails details) {
assert(mounted);
assert(_backGestureController == null);
_backGestureController = widget.onStartPopGesture();
}
void _handleDragUpdate(DragUpdateDetails details) {
assert(mounted);
assert(_backGestureController != null);
_backGestureController!.dragUpdate(
_convertToLogical(details.primaryDelta! / context.size!.width));
}
void _handlePointerDown(PointerDownEvent event) {
if (widget.enabledCallback()) _recognizer.addPointer(event);
}
}
mixin GetPageRouteTransitionMixin<T> on PageRoute<T> {
ValueNotifier<String?>? _previousTitle;
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
double Function(BuildContext context)? get gestureWidth;
/// Whether a pop gesture can be started by the user.
///
/// Returns true if the user can edge-swipe to a previous route.
///
/// Returns false once [isPopGestureInProgress] is true, but
/// [isPopGestureInProgress] can only become true if [popGestureEnabled] was
/// true first.
///
/// This should only be used between frames, not during build.
bool get popGestureEnabled => _isPopGestureEnabled(this);
/// True if an iOS-style back swipe pop gesture is currently
/// underway for this route.
///
/// See also:
///
/// * [isPopGestureInProgress], which returns true if a Cupertino pop gesture
/// is currently underway for specific route.
/// * [popGestureEnabled], which returns true if a user-triggered pop gesture
/// would be allowed.
bool get popGestureInProgress => isPopGestureInProgress(this);
/// The title string of the previous [CupertinoPageRoute].
///
/// The [ValueListenable]'s value is readable after the route is installed
/// onto a [Navigator]. The [ValueListenable] will also notify its listeners
/// if the value changes (such as by replacing the previous route).
///
/// The [ValueListenable] itself will be null before the route is installed.
/// Its content value will be null if the previous route has no title or
/// is not a [CupertinoPageRoute].
///
/// See also:
///
/// * [ValueListenableBuilder], which can be used to listen and rebuild
/// widgets based on a ValueListenable.
ValueListenable<String?> get previousTitle {
assert(
_previousTitle != null,
'''
Cannot read the previousTitle for a route that has not yet been installed''',
);
return _previousTitle!;
}
bool get showCupertinoParallax;
/// {@template flutter.cupertino.CupertinoRouteTransitionMixin.title}
/// A title string for this route.
///
/// Used to auto-populate [CupertinoNavigationBar] and
/// [CupertinoSliverNavigationBar]'s `middle`/`largeTitle` widgets when
/// one is not manually supplied.
/// {@endtemplate}
String? get title;
@override
// A relatively rigorous eyeball estimation.
Duration get transitionDuration => const Duration(milliseconds: 400);
/// Builds the primary contents of the route.
@protected
Widget buildContent(BuildContext context);
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
final child = buildContent(context);
final Widget result = Semantics(
scopesRoute: true,
explicitChildNodes: true,
child: child,
);
return result;
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return buildPageTransitions<T>(
this, context, animation, secondaryAnimation, child);
}
@override
bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
// Don't perform outgoing animation if the next route is a
// fullscreen dialog.
return (nextRoute is GetPageRouteTransitionMixin &&
!nextRoute.fullscreenDialog &&
nextRoute.showCupertinoParallax) ||
(nextRoute is CupertinoRouteTransitionMixin &&
!nextRoute.fullscreenDialog);
}
@override
void didChangePrevious(Route<dynamic>? previousRoute) {
final previousTitleString = previousRoute is CupertinoRouteTransitionMixin
? previousRoute.title
: null;
if (_previousTitle == null) {
_previousTitle = ValueNotifier<String?>(previousTitleString);
} else {
_previousTitle!.value = previousTitleString;
}
super.didChangePrevious(previousRoute);
}
/// Returns a [CupertinoFullscreenDialogTransition] if [route] is a full
/// screen dialog, otherwise a [CupertinoPageTransition] is returned.
///
/// Used by [CupertinoPageRoute.buildTransitions].
///
/// This method can be applied to any [PageRoute], not just
/// [CupertinoPageRoute]. It's typically used to provide a Cupertino style
/// horizontal transition for material widgets when the target platform
/// is [TargetPlatform.iOS].
///
/// See also:
///
/// * [CupertinoPageTransitionsBuilder], which uses this method to define a
/// [PageTransitionsBuilder] for the [PageTransitionsTheme].
static Widget buildPageTransitions<T>(
PageRoute<T> rawRoute,
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
// Check if the route has an animation that's currently participating
// in a back swipe gesture.
//
// In the middle of a back gesture drag, let the transition be linear to
// match finger motions.
final route = rawRoute as GetPageRoute<T>;
final linearTransition = isPopGestureInProgress(route);
final finalCurve = route.curve ?? Get.defaultTransitionCurve;
final hasCurve = route.curve != null;
if (route.fullscreenDialog && route.transition == null) {
return CupertinoFullscreenDialogTransition(
primaryRouteAnimation: hasCurve
? CurvedAnimation(parent: animation, curve: finalCurve)
: animation,
secondaryRouteAnimation: secondaryAnimation,
child: child,
linearTransition: linearTransition,
);
} else {
if (route.customTransition != null) {
return route.customTransition!.buildTransition(
context,
finalCurve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth:
route.gestureWidth?.call(context) ?? _kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child,
);
}
/// Apply the curve by default...
final iosAnimation = animation;
animation = CurvedAnimation(parent: animation, curve: finalCurve);
switch (route.transition ?? Get.defaultTransition) {
case Transition.leftToRight:
return SlideLeftTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.downToUp:
return SlideDownTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.upToDown:
return SlideTopTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.noTransition:
return route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth:
route.gestureWidth?.call(context) ?? _kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child;
case Transition.rightToLeft:
return SlideRightTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.zoom:
return ZoomInTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.fadeIn:
return FadeInTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.rightToLeftWithFade:
return RightToLeftFadeTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.leftToRightWithFade:
return LeftToRightFadeTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.cupertino:
return CupertinoPageTransition(
primaryRouteAnimation: animation,
secondaryRouteAnimation: secondaryAnimation,
linearTransition: linearTransition,
child: CupertinoBackGestureDetector<T>(
gestureWidth:
route.gestureWidth?.call(context) ?? _kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
);
case Transition.size:
return SizeTransitions().buildTransitions(
context,
route.curve!,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.fade:
return FadeUpwardsPageTransitionsBuilder().buildTransitions(
route,
context,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.topLevel:
return ZoomPageTransitionsBuilder().buildTransitions(
route,
context,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.native:
return PageTransitionsTheme().buildTransitions(
route,
context,
iosAnimation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.circularReveal:
return CircularRevealTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
default:
if (Get.customTransition != null) {
return Get.customTransition!.buildTransition(context, route.curve,
route.alignment, animation, secondaryAnimation, child);
}
return PageTransitionsTheme().buildTransitions(
route,
context,
iosAnimation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
}
}
}
// Called by CupertinoBackGestureDetector when a pop ("back") drag start
// gesture is detected. The returned controller handles all of the subsequent
// drag events.
/// True if an iOS-style back swipe pop gesture is currently
/// underway for [route].
///
/// This just check the route's [NavigatorState.userGestureInProgress].
///
/// See also:
///
/// * [popGestureEnabled], which returns true if a user-triggered pop gesture
/// would be allowed.
static bool isPopGestureInProgress(PageRoute<dynamic> route) {
return route.navigator!.userGestureInProgress;
}
static bool _isPopGestureEnabled<T>(PageRoute<T> route) {
// If there's nothing to go back to, then obviously we don't support
// the back gesture.
if (route.isFirst) return false;
// If the route wouldn't actually pop if we popped it, then the gesture
// would be really confusing (or would skip internal routes),
//so disallow it.
if (route.willHandlePopInternally) return false;
// If attempts to dismiss this route might be vetoed such as in a page
// with forms, then do not allow the user to dismiss the route with a swipe.
if (route.hasScopedWillPopCallback) return false;
// Fullscreen dialogs aren't dismissible by back swipe.
if (route.fullscreenDialog) return false;
// If we're in an animation already, we cannot be manually swiped.
if (route.animation!.status != AnimationStatus.completed) return false;
// If we're being popped into, we also cannot be swiped until the pop above
// it completes. This translates to our secondary animation being
// dismissed.
if (route.secondaryAnimation!.status != AnimationStatus.dismissed) {
return false;
}
// If we're in a gesture already, we cannot start another.
if (isPopGestureInProgress(route)) return false;
// Looks like a back gesture would be welcome!
return true;
}
static CupertinoBackGestureController<T> _startPopGesture<T>(
PageRoute<T> route) {
assert(_isPopGestureEnabled(route));
return CupertinoBackGestureController<T>(
navigator: route.navigator!,
controller: route.controller!, // protected access
);
}
}

View File

@@ -0,0 +1,241 @@
import 'package:flutter/widgets.dart';
import '../../../../get_core/get_core.dart';
import '../../../../instance_manager.dart';
import '../../../get_navigation.dart';
import '../../dialog/dialog_route.dart';
import '../../router_report.dart';
/// Extracts the name of a route based on it's instance type
/// or null if not possible.
String? _extractRouteName(Route? route) {
if (route?.settings.name != null) {
return route!.settings.name;
}
if (route is GetPageRoute) {
return route.routeName;
}
if (route is GetDialogRoute) {
return 'DIALOG ${route.hashCode}';
}
if (route is GetModalBottomSheetRoute) {
return 'BOTTOMSHEET ${route.hashCode}';
}
return null;
}
class GetObserver extends NavigatorObserver {
final Function(Routing?)? routing;
final Routing? _routeSend;
GetObserver([this.routing, this._routeSend]);
@override
void didPop(Route route, Route? previousRoute) {
super.didPop(route, previousRoute);
final currentRoute = _RouteData.ofRoute(route);
final newRoute = _RouteData.ofRoute(previousRoute);
// if (currentRoute.isSnackbar) {
// // Get.log("CLOSE SNACKBAR ${currentRoute.name}");
// Get.log("CLOSE SNACKBAR");
// } else
if (currentRoute.isBottomSheet || currentRoute.isDialog) {
Get.log("CLOSE ${currentRoute.name}");
} else if (currentRoute.isGetPageRoute) {
Get.log("CLOSE TO ROUTE ${currentRoute.name}");
}
if (previousRoute != null) {
RouterReportManager.reportCurrentRoute(previousRoute);
}
// Here we use a 'inverse didPush set', meaning that we use
// previous route instead of 'route' because this is
// a 'inverse push'
_routeSend?.update((value) {
// Only PageRoute is allowed to change current value
if (previousRoute is PageRoute) {
value.current = _extractRouteName(previousRoute) ?? '';
value.previous = newRoute.name ?? '';
} else if (value.previous.isNotEmpty) {
value.current = value.previous;
}
value.args = previousRoute?.settings.arguments;
value.route = previousRoute;
value.isBack = true;
value.removed = '';
// value.isSnackbar = newRoute.isSnackbar;
value.isBottomSheet = newRoute.isBottomSheet;
value.isDialog = newRoute.isDialog;
});
// print('currentRoute.isDialog ${currentRoute.isDialog}');
routing?.call(_routeSend);
}
@override
void didPush(Route route, Route? previousRoute) {
super.didPush(route, previousRoute);
final newRoute = _RouteData.ofRoute(route);
// if (newRoute.isSnackbar) {
// // Get.log("OPEN SNACKBAR ${newRoute.name}");
// Get.log("OPEN SNACKBAR");
// } else
if (newRoute.isBottomSheet || newRoute.isDialog) {
Get.log("OPEN ${newRoute.name}");
} else if (newRoute.isGetPageRoute) {
Get.log("GOING TO ROUTE ${newRoute.name}");
}
RouterReportManager.reportCurrentRoute(route);
_routeSend?.update((value) {
// Only PageRoute is allowed to change current value
if (route is PageRoute) {
value.current = newRoute.name ?? '';
}
final previousRouteName = _extractRouteName(previousRoute);
if (previousRouteName != null) {
value.previous = previousRouteName;
}
value.args = route.settings.arguments;
value.route = route;
value.isBack = false;
value.removed = '';
value.isBottomSheet =
newRoute.isBottomSheet ? true : value.isBottomSheet ?? false;
value.isDialog = newRoute.isDialog ? true : value.isDialog ?? false;
});
if (routing != null) {
routing!(_routeSend);
}
}
@override
void didRemove(Route route, Route? previousRoute) {
super.didRemove(route, previousRoute);
final routeName = _extractRouteName(route);
final currentRoute = _RouteData.ofRoute(route);
Get.log("REMOVING ROUTE $routeName");
_routeSend?.update((value) {
value.route = previousRoute;
value.isBack = false;
value.removed = routeName ?? '';
value.previous = routeName ?? '';
// value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar;
value.isBottomSheet =
currentRoute.isBottomSheet ? false : value.isBottomSheet;
value.isDialog = currentRoute.isDialog ? false : value.isDialog;
});
if (route is GetPageRoute) {
RouterReportManager.reportRouteWillDispose(route);
}
routing?.call(_routeSend);
}
@override
void didReplace({Route? newRoute, Route? oldRoute}) {
super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
final newName = _extractRouteName(newRoute);
final oldName = _extractRouteName(oldRoute);
final currentRoute = _RouteData.ofRoute(oldRoute);
Get.log("REPLACE ROUTE $oldName");
Get.log("NEW ROUTE $newName");
if (newRoute != null) {
RouterReportManager.reportCurrentRoute(newRoute);
}
_routeSend?.update((value) {
// Only PageRoute is allowed to change current value
if (newRoute is PageRoute) {
value.current = newName ?? '';
}
value.args = newRoute?.settings.arguments;
value.route = newRoute;
value.isBack = false;
value.removed = '';
value.previous = '$oldName';
// value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar;
value.isBottomSheet =
currentRoute.isBottomSheet ? false : value.isBottomSheet;
value.isDialog = currentRoute.isDialog ? false : value.isDialog;
});
if (oldRoute is GetPageRoute) {
RouterReportManager.reportRouteWillDispose(oldRoute);
}
routing?.call(_routeSend);
}
}
class Routing {
String current;
String previous;
dynamic args;
String removed;
Route<dynamic>? route;
bool? isBack;
// bool? isSnackbar;
bool? isBottomSheet;
bool? isDialog;
Routing({
this.current = '',
this.previous = '',
this.args,
this.removed = '',
this.route,
this.isBack,
// this.isSnackbar,
this.isBottomSheet,
this.isDialog,
});
void update(void fn(Routing value)) {
fn(this);
}
}
/// 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;
_RouteData({
required this.name,
required this.isGetPageRoute,
// required this.isSnackbar,
required this.isBottomSheet,
required this.isDialog,
});
factory _RouteData.ofRoute(Route? route) {
return _RouteData(
name: _extractRouteName(route),
isGetPageRoute: route is GetPageRoute,
// isSnackbar: route is SnackRoute,
isDialog: route is GetDialogRoute,
isBottomSheet: route is GetModalBottomSheetRoute,
);
}
}

View File

@@ -0,0 +1,291 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import '../../../get.dart';
abstract class _RouteMiddleware {
/// The Order of the Middlewares to run.
///
/// {@tool snippet}
/// This Middewares will be called in this order.
/// ```dart
/// final middlewares = [
/// GetMiddleware(priority: 2),
/// GetMiddleware(priority: 5),
/// GetMiddleware(priority: 4),
/// GetMiddleware(priority: -8),
/// ];
/// ```
/// -8 => 2 => 4 => 5
/// {@end-tool}
int? priority;
/// This function will be called when the page of
/// the called route is being searched for.
/// It take RouteSettings as a result an redirect to the new settings or
/// give it null and there will be no redirecting.
/// {@tool snippet}
/// ```dart
/// GetPage redirect(String route) {
/// final authService = Get.find<AuthService>();
/// return authService.authed.value ? null : RouteSettings(name: '/login');
/// }
/// ```
/// {@end-tool}
RouteSettings? redirect(String route);
/// Similar to [redirect],
/// This function will be called when the router delegate changes the
/// current route.
///
/// The default implmentation is to navigate to
/// the input route, with no redirection.
///
/// if this returns null, the navigation is stopped,
/// and no new routes are pushed.
/// {@tool snippet}
/// ```dart
/// GetNavConfig? redirect(GetNavConfig route) {
/// final authService = Get.find<AuthService>();
/// return authService.authed.value ? null : RouteSettings(name: '/login');
/// }
/// ```
/// {@end-tool}
Future<GetNavConfig?> redirectDelegate(GetNavConfig route);
/// This function will be called when this Page is called
/// you can use it to change something about the page or give it new page
/// {@tool snippet}
/// ```dart
/// GetPage onPageCalled(GetPage page) {
/// final authService = Get.find<AuthService>();
/// return page.copyWith(title: 'Welcome ${authService.UserName}');
/// }
/// ```
/// {@end-tool}
GetPage? onPageCalled(GetPage page);
/// This function will be called right before the [Bindings] are initialize.
/// Here you can change [Bindings] for this page
/// {@tool snippet}
/// ```dart
/// List<Bindings> onBindingsStart(List<Bindings> bindings) {
/// final authService = Get.find<AuthService>();
/// if (authService.isAdmin) {
/// bindings.add(AdminBinding());
/// }
/// return bindings;
/// }
/// ```
/// {@end-tool}
List<Bindings>? onBindingsStart(List<Bindings> bindings);
/// This function will be called right after the [Bindings] are initialize.
GetPageBuilder? onPageBuildStart(GetPageBuilder page);
/// This function will be called right after the
/// GetPage.page function is called and will give you the result
/// of the function. and take the widget that will be showed.
Widget onPageBuilt(Widget page);
void onPageDispose();
}
/// The Page Middlewares.
/// The Functions will be called in this order
/// (( [redirect] -> [onPageCalled] -> [onBindingsStart] ->
/// [onPageBuildStart] -> [onPageBuilt] -> [onPageDispose] ))
class GetMiddleware implements _RouteMiddleware {
@override
int? priority = 0;
GetMiddleware({this.priority});
@override
RouteSettings? redirect(String? route) => null;
@override
GetPage? onPageCalled(GetPage? page) => page;
@override
List<Bindings>? onBindingsStart(List<Bindings>? bindings) => bindings;
@override
GetPageBuilder? onPageBuildStart(GetPageBuilder? page) => page;
@override
Widget onPageBuilt(Widget page) => page;
@override
void onPageDispose() {}
@override
Future<GetNavConfig?> redirectDelegate(GetNavConfig route) =>
SynchronousFuture(route);
}
class MiddlewareRunner {
MiddlewareRunner(this._middlewares);
final List<GetMiddleware>? _middlewares;
List<GetMiddleware> _getMiddlewares() {
final _m = _middlewares ?? <GetMiddleware>[];
return _m
..sort(
(a, b) => (a.priority ?? 0).compareTo(b.priority ?? 0),
);
}
GetPage? runOnPageCalled(GetPage? page) {
_getMiddlewares().forEach((element) {
page = element.onPageCalled(page);
});
return page;
}
RouteSettings? runRedirect(String? route) {
RouteSettings? to;
for (final element in _getMiddlewares()) {
to = element.redirect(route);
if (to != null) {
break;
}
}
Get.log('Redirect to $to');
return to;
}
List<Bindings>? runOnBindingsStart(List<Bindings>? bindings) {
_getMiddlewares().forEach((element) {
bindings = element.onBindingsStart(bindings);
});
return bindings;
}
GetPageBuilder? runOnPageBuildStart(GetPageBuilder? page) {
_getMiddlewares().forEach((element) {
page = element.onPageBuildStart(page);
});
return page;
}
Widget runOnPageBuilt(Widget page) {
_getMiddlewares().forEach((element) {
page = element.onPageBuilt(page);
});
return page;
}
void runOnPageDispose() =>
_getMiddlewares().forEach((element) => element.onPageDispose());
}
class PageRedirect {
GetPage? route;
GetPage? unknownRoute;
RouteSettings? settings;
bool isUnknown;
PageRedirect({
this.route,
this.unknownRoute,
this.isUnknown = false,
this.settings,
});
// redirect all pages that needes redirecting
GetPageRoute<T> page<T>() {
while (needRecheck()) {}
final _r = (isUnknown ? unknownRoute : route)!;
return GetPageRoute<T>(
page: _r.page,
parameter: _r.parameters,
settings: isUnknown
? RouteSettings(
name: _r.name,
arguments: settings!.arguments,
)
: settings,
curve: _r.curve,
opaque: _r.opaque,
showCupertinoParallax: _r.showCupertinoParallax,
gestureWidth: _r.gestureWidth,
customTransition: _r.customTransition,
binding: _r.binding,
bindings: _r.bindings,
transitionDuration:
_r.transitionDuration ?? Get.defaultTransitionDuration,
transition: _r.transition,
popGesture: _r.popGesture,
fullscreenDialog: _r.fullscreenDialog,
middlewares: _r.middlewares,
);
}
// redirect all pages that needes redirecting
GetPageRoute<T> getPageToRoute<T>(GetPage rou, GetPage? unk) {
while (needRecheck()) {}
final _r = (isUnknown ? unk : rou)!;
return GetPageRoute<T>(
page: _r.page,
parameter: _r.parameters,
alignment: _r.alignment,
title: _r.title,
maintainState: _r.maintainState,
routeName: _r.name,
settings: _r,
curve: _r.curve,
showCupertinoParallax: _r.showCupertinoParallax,
gestureWidth: _r.gestureWidth,
opaque: _r.opaque,
customTransition: _r.customTransition,
binding: _r.binding,
bindings: _r.bindings,
transitionDuration:
_r.transitionDuration ?? Get.defaultTransitionDuration,
transition: _r.transition,
popGesture: _r.popGesture,
fullscreenDialog: _r.fullscreenDialog,
middlewares: _r.middlewares,
);
}
/// check if redirect is needed
bool needRecheck() {
if (settings == null && route != null) {
settings = route;
}
final match = Get.routeTree.matchRoute(settings!.name!);
Get.parameters = match.parameters;
// No Match found
if (match.route == null) {
isUnknown = true;
return false;
}
final runner = MiddlewareRunner(match.route!.middlewares);
route = runner.runOnPageCalled(match.route);
addPageParameter(route!);
// No middlewares found return match.
if (match.route!.middlewares == null || match.route!.middlewares!.isEmpty) {
return false;
}
final newSettings = runner.runRedirect(settings!.name);
if (newSettings == null) {
return false;
}
settings = newSettings;
return true;
}
void addPageParameter(GetPage route) {
if (route.parameters == null) return;
final parameters = Get.parameters;
parameters.addEntries(route.parameters!.entries);
Get.parameters = parameters;
}
}

View File

@@ -0,0 +1,22 @@
import 'package:flutter/widgets.dart';
enum Transition {
fade,
fadeIn,
rightToLeft,
leftToRight,
upToDown,
downToUp,
rightToLeftWithFade,
leftToRightWithFade,
zoom,
topLevel,
noTransition,
cupertino,
cupertinoDialog,
size,
circularReveal,
native,
}
typedef GetPageBuilder = Widget Function();

View File

@@ -0,0 +1,660 @@
import 'dart:async';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import '../../../get_core/get_core.dart';
import '../../get_navigation.dart';
typedef OnTap = void Function(GetSnackBar snack);
typedef SnackbarStatusCallback = void Function(SnackbarStatus? status);
@Deprecated('use GetSnackBar')
class GetBar extends GetSnackBar {
GetBar({
Key? key,
String? title,
String? message,
Widget? titleText,
Widget? messageText,
Widget? icon,
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,
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),
double barBlur = 0.0,
double overlayBlur = 0.0,
Color overlayColor = Colors.transparent,
Form? userInputForm,
SnackbarStatusCallback? snackbarStatus,
}) : super(
key: key,
title: title,
message: message,
titleText: titleText,
messageText: messageText,
icon: icon,
shouldIconPulse: shouldIconPulse,
maxWidth: maxWidth,
margin: margin,
padding: padding,
borderRadius: borderRadius,
borderColor: borderColor,
borderWidth: borderWidth,
backgroundColor: backgroundColor,
leftBarIndicatorColor: leftBarIndicatorColor,
boxShadows: boxShadows,
backgroundGradient: backgroundGradient,
mainButton: mainButton,
onTap: onTap,
duration: duration,
isDismissible: isDismissible,
dismissDirection: dismissDirection,
showProgressIndicator: showProgressIndicator,
progressIndicatorController: progressIndicatorController,
progressIndicatorBackgroundColor: progressIndicatorBackgroundColor,
progressIndicatorValueColor: progressIndicatorValueColor,
snackPosition: snackPosition,
snackStyle: snackStyle,
forwardAnimationCurve: forwardAnimationCurve,
reverseAnimationCurve: reverseAnimationCurve,
animationDuration: animationDuration,
barBlur: barBlur,
overlayBlur: overlayBlur,
overlayColor: overlayColor,
userInputForm: userInputForm,
snackbarStatus: snackbarStatus,
);
}
class GetSnackBar extends StatefulWidget {
/// A callback for you to listen to the different Snack status
final SnackbarStatusCallback? snackbarStatus;
/// The title displayed to the user
final String? title;
/// The direction in which the SnackBar can be dismissed.
///
/// Default is [DismissDirection.down] when
/// [snackPosition] == [SnackPosition.BOTTOM] and [DismissDirection.up]
/// when [snackPosition] == [SnackPosition.TOP]
final DismissDirection? dismissDirection;
/// The message displayed to the user.
final String? message;
/// Replaces [title]. Although this accepts a [Widget], it is meant
/// to receive [Text] or [RichText]
final Widget? titleText;
/// Replaces [message]. Although this accepts a [Widget], it is meant
/// to receive [Text] or [RichText]
final Widget? messageText;
/// Will be ignored if [backgroundGradient] is not null
final Color backgroundColor;
/// If not null, shows a left vertical colored bar on notification.
/// It is not possible to use it with a [Form] and I do not recommend
/// using it with [LinearProgressIndicator]
final Color? leftBarIndicatorColor;
/// [boxShadows] The shadows generated by Snack. Leave it null
/// if you don't want a shadow.
/// You can use more than one if you feel the need.
/// Check (this example)[https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/shadows.dart]
final List<BoxShadow>? boxShadows;
/// Give to GetSnackbar a gradient background.
/// It Makes [backgroundColor] be ignored.
final Gradient? backgroundGradient;
/// You can use any widget here, but I recommend [Icon] or [Image] as
/// indication of what kind
/// of message you are displaying. Other widgets may break the layout
final Widget? icon;
/// An option to animate the icon (if present). Defaults to true.
final bool shouldIconPulse;
/// (optional) An action that the user can take based on the snack bar.
///
/// For example, the snack bar might let the user undo the operation that
/// prompted the snackbar.
final Widget? mainButton;
/// A callback that registers the user's click anywhere.
/// An alternative to [mainButton]
final OnTap? onTap;
/// How long until Snack will hide itself (be dismissed).
/// To make it indefinite, leave it null.
final Duration? duration;
/// True if you want to show a [LinearProgressIndicator].
final bool showProgressIndicator;
/// An optional [AnimationController] when you want to control the
/// progress of your [LinearProgressIndicator].
final AnimationController? progressIndicatorController;
/// A [LinearProgressIndicator] configuration parameter.
final Color? progressIndicatorBackgroundColor;
/// A [LinearProgressIndicator] configuration parameter.
final Animation<Color>? progressIndicatorValueColor;
/// Determines if the user can swipe or click the overlay
/// (if [overlayBlur] > 0) to dismiss.
/// It is recommended that you set [duration] != null if this is false.
/// If the user swipes to dismiss or clicks the overlay, no value
/// will be returned.
final bool isDismissible;
/// Used to limit Snack width (usually on large screens)
final double? maxWidth;
/// Adds a custom margin to Snack
final EdgeInsets margin;
/// Adds a custom padding to Snack
/// The default follows material design guide line
final EdgeInsets padding;
/// Adds a radius to all corners of Snack. Best combined with [margin].
/// I do not recommend using it with [showProgressIndicator]
/// or [leftBarIndicatorColor].
final double borderRadius;
/// Adds a border to every side of Snack
/// I do not recommend using it with [showProgressIndicator]
/// or [leftBarIndicatorColor].
final Color? borderColor;
/// Changes the width of the border if [borderColor] is specified
final double? borderWidth;
/// Snack can be based on [SnackPosition.TOP] or on [SnackPosition.BOTTOM]
/// of your screen.
/// [SnackPosition.BOTTOM] is the default.
final SnackPosition snackPosition;
/// Snack can be floating or be grounded to the edge of the screen.
/// If grounded, I do not recommend using [margin] or [borderRadius].
/// [SnackStyle.FLOATING] is the default
/// If grounded, I do not recommend using a [backgroundColor] with
/// transparency or [barBlur]
final SnackStyle snackStyle;
/// The [Curve] animation used when show() is called.
/// [Curves.easeOut] is default
final Curve forwardAnimationCurve;
/// The [Curve] animation used when dismiss() is called.
/// [Curves.fastOutSlowIn] is default
final Curve reverseAnimationCurve;
/// Use it to speed up or slow down the animation duration
final Duration animationDuration;
/// Default is 0.0. If different than 0.0, blurs only Snack's background.
/// To take effect, make sure your [backgroundColor] has some opacity.
/// The greater the value, the greater the blur.
final double barBlur;
/// Default is 0.0. If different than 0.0, creates a blurred
/// overlay that prevents the user from interacting with the screen.
/// The greater the value, the greater the blur.
final double overlayBlur;
/// Default is [Colors.transparent]. Only takes effect if [overlayBlur] > 0.0.
/// Make sure you use a color with transparency here e.g.
/// Colors.grey[600].withOpacity(0.2).
final Color? overlayColor;
/// A [TextFormField] in case you want a simple user input.
/// Every other widget is ignored if this is not null.
final Form? userInputForm;
const GetSnackBar({
Key? key,
this.title,
this.message,
this.titleText,
this.messageText,
this.icon,
this.shouldIconPulse = true,
this.maxWidth,
this.margin = const EdgeInsets.all(0.0),
this.padding = const EdgeInsets.all(16),
this.borderRadius = 0.0,
this.borderColor,
this.borderWidth = 1.0,
this.backgroundColor = const Color(0xFF303030),
this.leftBarIndicatorColor,
this.boxShadows,
this.backgroundGradient,
this.mainButton,
this.onTap,
this.duration,
this.isDismissible = true,
this.dismissDirection,
this.showProgressIndicator = false,
this.progressIndicatorController,
this.progressIndicatorBackgroundColor,
this.progressIndicatorValueColor,
this.snackPosition = SnackPosition.BOTTOM,
this.snackStyle = SnackStyle.FLOATING,
this.forwardAnimationCurve = Curves.easeOutCirc,
this.reverseAnimationCurve = Curves.easeOutCirc,
this.animationDuration = const Duration(seconds: 1),
this.barBlur = 0.0,
this.overlayBlur = 0.0,
this.overlayColor = Colors.transparent,
this.userInputForm,
this.snackbarStatus,
}) : super(key: key);
@override
State createState() => GetSnackBarState();
/// Show the snack. It's call [SnackbarStatus.OPENING] state
/// followed by [SnackbarStatus.OPEN]
SnackbarController show() {
return Get.showSnackbar(this);
}
}
class GetSnackBarState extends State<GetSnackBar>
with TickerProviderStateMixin {
AnimationController? _fadeController;
late Animation<double> _fadeAnimation;
final Widget _emptyWidget = SizedBox(width: 0.0, height: 0.0);
final double _initialOpacity = 1.0;
final double _finalOpacity = 0.4;
final Duration _pulseAnimationDuration = Duration(seconds: 1);
late bool _isTitlePresent;
late double _messageTopMargin;
FocusScopeNode? _focusNode;
late FocusAttachment _focusAttachment;
final Completer<Size> _boxHeightCompleter = Completer<Size>();
late CurvedAnimation _progressAnimation;
final _backgroundBoxKey = GlobalKey();
double get buttonPadding {
if (widget.padding.right - 12 < 0) {
return 4;
} else {
return widget.padding.right - 12;
}
}
RowStyle get _rowStyle {
if (widget.mainButton != null && widget.icon == null) {
return RowStyle.action;
} else if (widget.mainButton == null && widget.icon != null) {
return RowStyle.icon;
} else if (widget.mainButton != null && widget.icon != null) {
return RowStyle.all;
} else {
return RowStyle.none;
}
}
@override
Widget build(BuildContext context) {
return Align(
heightFactor: 1.0,
child: Material(
color: widget.snackStyle == SnackStyle.FLOATING
? Colors.transparent
: widget.backgroundColor,
child: SafeArea(
minimum: widget.snackPosition == SnackPosition.BOTTOM
? EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom)
: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
bottom: widget.snackPosition == SnackPosition.BOTTOM,
top: widget.snackPosition == SnackPosition.TOP,
left: false,
right: false,
child: Stack(
children: [
FutureBuilder<Size>(
future: _boxHeightCompleter.future,
builder: (context, snapshot) {
if (snapshot.hasData) {
if (widget.barBlur == 0) {
return _emptyWidget;
}
return ClipRRect(
borderRadius: BorderRadius.circular(widget.borderRadius),
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: widget.barBlur, sigmaY: widget.barBlur),
child: Container(
height: snapshot.data!.height,
width: snapshot.data!.width,
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius:
BorderRadius.circular(widget.borderRadius),
),
),
),
);
} else {
return _emptyWidget;
}
},
),
if (widget.userInputForm != null)
_containerWithForm()
else
_containerWithoutForm()
],
),
),
),
);
}
@override
void dispose() {
_fadeController?.dispose();
widget.progressIndicatorController?.removeListener(_updateProgress);
widget.progressIndicatorController?.dispose();
_focusAttachment.detach();
_focusNode!.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
assert(
widget.userInputForm != null ||
((widget.message != null && widget.message!.isNotEmpty) ||
widget.messageText != null),
'''
You need to either use message[String], or messageText[Widget] or define a userInputForm[Form] in GetSnackbar''');
_isTitlePresent = (widget.title != null || widget.titleText != null);
_messageTopMargin = _isTitlePresent ? 6.0 : widget.padding.top;
_configureLeftBarFuture();
_configureProgressIndicatorAnimation();
if (widget.icon != null && widget.shouldIconPulse) {
_configurePulseAnimation();
_fadeController?.forward();
}
_focusNode = FocusScopeNode();
_focusAttachment = _focusNode!.attach(context);
}
Widget _buildLeftBarIndicator() {
if (widget.leftBarIndicatorColor != null) {
return FutureBuilder<Size>(
future: _boxHeightCompleter.future,
builder: (buildContext, snapshot) {
if (snapshot.hasData) {
return Container(
color: widget.leftBarIndicatorColor,
width: 5.0,
height: snapshot.data!.height,
);
} else {
return _emptyWidget;
}
},
);
} else {
return _emptyWidget;
}
}
void _configureLeftBarFuture() {
ambiguate(SchedulerBinding.instance)?.addPostFrameCallback(
(_) {
final keyContext = _backgroundBoxKey.currentContext;
if (keyContext != null) {
final box = keyContext.findRenderObject() as RenderBox;
_boxHeightCompleter.complete(box.size);
}
},
);
}
void _configureProgressIndicatorAnimation() {
if (widget.showProgressIndicator &&
widget.progressIndicatorController != null) {
widget.progressIndicatorController!.addListener(_updateProgress);
_progressAnimation = CurvedAnimation(
curve: Curves.linear, parent: widget.progressIndicatorController!);
}
}
void _configurePulseAnimation() {
_fadeController =
AnimationController(vsync: this, duration: _pulseAnimationDuration);
_fadeAnimation = Tween(begin: _initialOpacity, end: _finalOpacity).animate(
CurvedAnimation(
parent: _fadeController!,
curve: Curves.linear,
),
);
_fadeController!.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_fadeController!.reverse();
}
if (status == AnimationStatus.dismissed) {
_fadeController!.forward();
}
});
_fadeController!.forward();
}
Widget _containerWithForm() {
return Container(
key: _backgroundBoxKey,
constraints: widget.maxWidth != null
? BoxConstraints(maxWidth: widget.maxWidth!)
: null,
decoration: BoxDecoration(
color: widget.backgroundColor,
gradient: widget.backgroundGradient,
boxShadow: widget.boxShadows,
borderRadius: BorderRadius.circular(widget.borderRadius),
border: widget.borderColor != null
? Border.all(
color: widget.borderColor!,
width: widget.borderWidth!,
)
: null,
),
child: Padding(
padding: const EdgeInsets.only(
left: 8.0, right: 8.0, bottom: 8.0, top: 16.0),
child: FocusScope(
child: widget.userInputForm!,
node: _focusNode,
autofocus: true,
),
),
);
}
Widget _containerWithoutForm() {
final iconPadding = widget.padding.left > 16.0 ? widget.padding.left : 0.0;
final left = _rowStyle == RowStyle.icon || _rowStyle == RowStyle.all
? 4.0
: widget.padding.left;
final right = _rowStyle == RowStyle.action || _rowStyle == RowStyle.all
? 8.0
: widget.padding.right;
return Container(
key: _backgroundBoxKey,
constraints: widget.maxWidth != null
? BoxConstraints(maxWidth: widget.maxWidth!)
: null,
decoration: BoxDecoration(
color: widget.backgroundColor,
gradient: widget.backgroundGradient,
boxShadow: widget.boxShadows,
borderRadius: BorderRadius.circular(widget.borderRadius),
border: widget.borderColor != null
? Border.all(color: widget.borderColor!, width: widget.borderWidth!)
: null,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
widget.showProgressIndicator
? LinearProgressIndicator(
value: widget.progressIndicatorController != null
? _progressAnimation.value
: null,
backgroundColor: widget.progressIndicatorBackgroundColor,
valueColor: widget.progressIndicatorValueColor,
)
: _emptyWidget,
Row(
mainAxisSize: MainAxisSize.max,
children: [
_buildLeftBarIndicator(),
if (_rowStyle == RowStyle.icon || _rowStyle == RowStyle.all)
ConstrainedBox(
constraints:
BoxConstraints.tightFor(width: 42.0 + iconPadding),
child: _getIcon(),
),
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
if (_isTitlePresent)
Padding(
padding: EdgeInsets.only(
top: widget.padding.top,
left: left,
right: right,
),
child: widget.titleText ??
Text(
widget.title ?? "",
style: TextStyle(
fontSize: 16.0,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
)
else
_emptyWidget,
Padding(
padding: EdgeInsets.only(
top: _messageTopMargin,
left: left,
right: right,
bottom: widget.padding.bottom,
),
child: widget.messageText ??
Text(
widget.message ?? "",
style:
TextStyle(fontSize: 14.0, color: Colors.white),
),
),
],
),
),
if (_rowStyle == RowStyle.action || _rowStyle == RowStyle.all)
Padding(
padding: EdgeInsets.only(right: buttonPadding),
child: widget.mainButton,
),
],
),
],
),
);
}
Widget? _getIcon() {
if (widget.icon != null && widget.icon is Icon && widget.shouldIconPulse) {
return FadeTransition(
opacity: _fadeAnimation,
child: widget.icon,
);
} else if (widget.icon != null) {
return widget.icon;
} else {
return _emptyWidget;
}
}
void _updateProgress() => setState(() {});
}
enum RowStyle {
icon,
action,
all,
none,
}
/// Indicates Status of snackbar
/// [SnackbarStatus.OPEN] Snack is fully open, [SnackbarStatus.CLOSED] Snackbar
/// has closed,
/// [SnackbarStatus.OPENING] Starts with the opening animation and ends
/// with the full
/// snackbar display, [SnackbarStatus.CLOSING] Starts with the closing animation
/// and ends
/// with the full snackbar dispose
enum SnackbarStatus { OPEN, CLOSED, OPENING, CLOSING }
/// Indicates if snack is going to start at the [TOP] or at the [BOTTOM]
enum SnackPosition { TOP, BOTTOM }
/// Indicates if snack will be attached to the edge of the screen or not
enum SnackStyle { FLOATING, GROUNDED }

View File

@@ -0,0 +1,372 @@
import 'dart:async';
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
import '../../../get.dart';
class SnackbarController {
static final _snackBarQueue = _SnackBarQueue();
static bool get isSnackbarBeingShown => _snackBarQueue._isJobInProgress;
final key = GlobalKey<GetSnackBarState>();
late Animation<double> _filterBlurAnimation;
late Animation<Color?> _filterColorAnimation;
final GetSnackBar snackbar;
final _transitionCompleter = Completer();
late SnackbarStatusCallback? _snackbarStatus;
late final Alignment? _initialAlignment;
late final Alignment? _endAlignment;
bool _wasDismissedBySwipe = false;
bool _onTappedDismiss = false;
Timer? _timer;
/// The animation that drives the route's transition and the previous route's
/// forward transition.
late final Animation<Alignment> _animation;
/// The animation controller that the route uses to drive the transitions.
///
/// The animation itself is exposed by the [animation] property.
late final AnimationController _controller;
SnackbarStatus? _currentStatus;
final _overlayEntries = <OverlayEntry>[];
OverlayState? _overlayState;
SnackbarController(this.snackbar);
Future<void> get future => _transitionCompleter.future;
/// Close the snackbar with animation
Future<void> close({bool withAnimations = true}) async {
if (!withAnimations) {
_removeOverlay();
return;
}
_removeEntry();
await future;
}
/// Adds GetSnackbar to a view queue.
/// Only one GetSnackbar will be displayed at a time, and this method returns
/// a future to when the snackbar disappears.
Future<void> show() {
return _snackBarQueue._addJob(this);
}
void _cancelTimer() {
if (_timer != null && _timer!.isActive) {
_timer!.cancel();
}
}
// ignore: avoid_returning_this
void _configureAlignment(SnackPosition snackPosition) {
switch (snackbar.snackPosition) {
case SnackPosition.TOP:
{
_initialAlignment = const Alignment(-1.0, -2.0);
_endAlignment = const Alignment(-1.0, -1.0);
break;
}
case SnackPosition.BOTTOM:
{
_initialAlignment = const Alignment(-1.0, 2.0);
_endAlignment = const Alignment(-1.0, 1.0);
break;
}
}
}
void _configureOverlay() {
_overlayState = Overlay.of(Get.overlayContext!);
_overlayEntries.clear();
_overlayEntries.addAll(_createOverlayEntries(_getBodyWidget()));
_overlayState!.insertAll(_overlayEntries);
_configureSnackBarDisplay();
}
void _configureSnackBarDisplay() {
assert(!_transitionCompleter.isCompleted,
'Cannot configure a snackbar after disposing it.');
_controller = _createAnimationController();
_configureAlignment(snackbar.snackPosition);
_snackbarStatus = snackbar.snackbarStatus;
_filterBlurAnimation = _createBlurFilterAnimation();
_filterColorAnimation = _createColorOverlayColor();
_animation = _createAnimation();
_animation.addStatusListener(_handleStatusChanged);
_configureTimer();
_controller.forward();
}
void _configureTimer() {
if (snackbar.duration != null) {
if (_timer != null && _timer!.isActive) {
_timer!.cancel();
}
_timer = Timer(snackbar.duration!, _removeEntry);
} else {
if (_timer != null) {
_timer!.cancel();
}
}
}
/// Called to create the animation that exposes the current progress of
/// the transition controlled by the animation controller created by
/// `createAnimationController()`.
Animation<Alignment> _createAnimation() {
assert(!_transitionCompleter.isCompleted,
'Cannot create a animation from a disposed snackbar');
return AlignmentTween(begin: _initialAlignment, end: _endAlignment).animate(
CurvedAnimation(
parent: _controller,
curve: snackbar.forwardAnimationCurve,
reverseCurve: snackbar.reverseAnimationCurve,
),
);
}
/// Called to create the animation controller that will drive the transitions
/// to this route from the previous one, and back to the previous route
/// from this one.
AnimationController _createAnimationController() {
assert(!_transitionCompleter.isCompleted,
'Cannot create a animationController from a disposed snackbar');
assert(snackbar.animationDuration >= Duration.zero);
return AnimationController(
duration: snackbar.animationDuration,
debugLabel: '$runtimeType',
vsync: _overlayState!,
);
}
Animation<double> _createBlurFilterAnimation() {
return Tween(begin: 0.0, end: snackbar.overlayBlur).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(
0.0,
0.35,
curve: Curves.easeInOutCirc,
),
),
);
}
Animation<Color?> _createColorOverlayColor() {
return ColorTween(
begin: const Color(0x00000000), end: snackbar.overlayColor)
.animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(
0.0,
0.35,
curve: Curves.easeInOutCirc,
),
),
);
}
Iterable<OverlayEntry> _createOverlayEntries(Widget child) {
return <OverlayEntry>[
if (snackbar.overlayBlur > 0.0) ...[
OverlayEntry(
builder: (context) => GestureDetector(
onTap: () {
if (snackbar.isDismissible && !_onTappedDismiss) {
_onTappedDismiss = true;
close();
}
},
child: AnimatedBuilder(
animation: _filterBlurAnimation,
builder: (context, child) {
return BackdropFilter(
filter: ImageFilter.blur(
sigmaX: max(0.001, _filterBlurAnimation.value),
sigmaY: max(0.001, _filterBlurAnimation.value),
),
child: Container(
constraints: const BoxConstraints.expand(),
color: _filterColorAnimation.value,
),
);
},
),
),
maintainState: false,
opaque: false,
),
],
OverlayEntry(
builder: (context) => Semantics(
child: AlignTransition(
alignment: _animation,
child: snackbar.isDismissible
? _getDismissibleSnack(child)
: _getSnackbarContainer(child),
),
focused: false,
container: true,
explicitChildNodes: true,
),
maintainState: false,
opaque: false,
),
];
}
Widget _getBodyWidget() {
return Builder(builder: (_) {
return GestureDetector(
child: snackbar,
onTap: snackbar.onTap != null
? () => snackbar.onTap?.call(snackbar)
: null,
);
});
}
DismissDirection _getDefaultDismissDirection() {
if (snackbar.snackPosition == SnackPosition.TOP) {
return DismissDirection.up;
}
return DismissDirection.down;
}
Widget _getDismissibleSnack(Widget child) {
return Dismissible(
direction: snackbar.dismissDirection ?? _getDefaultDismissDirection(),
resizeDuration: null,
confirmDismiss: (_) {
if (_currentStatus == SnackbarStatus.OPENING ||
_currentStatus == SnackbarStatus.CLOSING) {
return Future.value(false);
}
return Future.value(true);
},
key: const Key('dismissible'),
onDismissed: (_) {
_wasDismissedBySwipe = true;
_removeEntry();
},
child: _getSnackbarContainer(child),
);
}
Widget _getSnackbarContainer(Widget child) {
return Container(
margin: snackbar.margin,
child: child,
);
}
void _handleStatusChanged(AnimationStatus status) {
switch (status) {
case AnimationStatus.completed:
_currentStatus = SnackbarStatus.OPEN;
_snackbarStatus?.call(_currentStatus);
if (_overlayEntries.isNotEmpty) _overlayEntries.first.opaque = false;
break;
case AnimationStatus.forward:
_currentStatus = SnackbarStatus.OPENING;
_snackbarStatus?.call(_currentStatus);
break;
case AnimationStatus.reverse:
_currentStatus = SnackbarStatus.CLOSING;
_snackbarStatus?.call(_currentStatus);
if (_overlayEntries.isNotEmpty) _overlayEntries.first.opaque = false;
break;
case AnimationStatus.dismissed:
assert(!_overlayEntries.first.opaque);
_currentStatus = SnackbarStatus.CLOSED;
_snackbarStatus?.call(_currentStatus);
_removeOverlay();
break;
}
}
void _removeEntry() {
assert(
!_transitionCompleter.isCompleted,
'Cannot remove entry from a disposed snackbar',
);
_cancelTimer();
if (_wasDismissedBySwipe) {
Timer(const Duration(milliseconds: 200), _controller.reset);
_wasDismissedBySwipe = false;
} else {
_controller.reverse();
}
}
void _removeOverlay() {
for (var element in _overlayEntries) {
element.remove();
}
assert(!_transitionCompleter.isCompleted,
'Cannot remove overlay from a disposed snackbar');
_controller.dispose();
_overlayEntries.clear();
_transitionCompleter.complete();
}
Future<void> _show() {
_configureOverlay();
return future;
}
static void cancelAllSnackbars() {
_snackBarQueue._cancelAllJobs();
}
static Future<void> closeCurrentSnackbar() async {
await _snackBarQueue._closeCurrentJob();
}
}
class _SnackBarQueue {
final _queue = GetQueue();
final _snackbarList = <SnackbarController>[];
SnackbarController? get _currentSnackbar {
if (_snackbarList.isEmpty) return null;
return _snackbarList.first;
}
bool get _isJobInProgress => _snackbarList.isNotEmpty;
Future<void> _addJob(SnackbarController job) async {
_snackbarList.add(job);
final data = await _queue.add(job._show);
_snackbarList.remove(job);
return data;
}
Future<void> _cancelAllJobs() async {
await _currentSnackbar?.close();
_queue.cancelAllJobs();
_snackbarList.clear();
}
Future<void> _closeCurrentJob() async {
if (_currentSnackbar == null) return;
await _currentSnackbar!.close();
}
}