new change to use intaleq_map sdk 04-16-4
This commit is contained in:
335
packages/get/lib/get_navigation/src/bottomsheet/bottomsheet.dart
Normal file
335
packages/get/lib/get_navigation/src/bottomsheet/bottomsheet.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
73
packages/get/lib/get_navigation/src/dialog/dialog_route.dart
Normal file
73
packages/get/lib/get_navigation/src/dialog/dialog_route.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
1372
packages/get/lib/get_navigation/src/extension_navigation.dart
Normal file
1372
packages/get/lib/get_navigation/src/extension_navigation.dart
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
60
packages/get/lib/get_navigation/src/nav2/get_nav_config.dart
Normal file
60
packages/get/lib/get_navigation/src/nav2/get_nav_config.dart
Normal 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=====''';
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
168
packages/get/lib/get_navigation/src/nav2/router_outlet.dart
Normal file
168
packages/get/lib/get_navigation/src/nav2/router_outlet.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
321
packages/get/lib/get_navigation/src/root/get_cupertino_app.dart
Normal file
321
packages/get/lib/get_navigation/src/root/get_cupertino_app.dart
Normal 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()
|
||||
];
|
||||
}
|
||||
}
|
||||
351
packages/get/lib/get_navigation/src/root/get_material_app.dart
Normal file
351
packages/get/lib/get_navigation/src/root/get_material_app.dart
Normal 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()
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
188
packages/get/lib/get_navigation/src/root/parse_route.dart
Normal file
188
packages/get/lib/get_navigation/src/root/parse_route.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
113
packages/get/lib/get_navigation/src/router_report.dart
Normal file
113
packages/get/lib/get_navigation/src/router_report.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
127
packages/get/lib/get_navigation/src/routes/default_route.dart
Normal file
127
packages/get/lib/get_navigation/src/routes/default_route.dart
Normal 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;
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
184
packages/get/lib/get_navigation/src/routes/get_route.dart
Normal file
184
packages/get/lib/get_navigation/src/routes/get_route.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
291
packages/get/lib/get_navigation/src/routes/route_middleware.dart
Normal file
291
packages/get/lib/get_navigation/src/routes/route_middleware.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
660
packages/get/lib/get_navigation/src/snackbar/snackbar.dart
Normal file
660
packages/get/lib/get_navigation/src/snackbar/snackbar.dart
Normal 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 }
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user