Jonny Borges

add transition mixin

... ... @@ -494,6 +494,7 @@ extension GetNavigation on GetInterface {
Bindings? binding,
bool preventDuplicates = true,
bool? popGesture,
double gestureWidth = 20,
}) {
var routeName = "/${page.runtimeType.toString()}";
if (preventDuplicates && routeName == currentRoute) {
... ... @@ -504,6 +505,7 @@ extension GetNavigation on GetInterface {
opaque: opaque ?? true,
page: _resolve(page, 'to'),
routeName: routeName,
gestureWidth: gestureWidth,
settings: RouteSettings(
// name: forceRouteName ? '${a.runtimeType}' : '',
arguments: arguments,
... ... @@ -862,6 +864,7 @@ you can only use widgets and widget functions here''';
bool fullscreenDialog = false,
bool preventDuplicates = true,
Duration? duration,
double gestureWidth = 20,
}) {
var routeName = "/${page.runtimeType.toString()}";
if (preventDuplicates && routeName == currentRoute) {
... ... @@ -869,6 +872,7 @@ you can only use widgets and widget functions here''';
}
return global(id).currentState?.pushReplacement(GetPageRoute(
opaque: opaque,
gestureWidth: gestureWidth,
page: _resolve(page, 'off'),
binding: binding,
settings: RouteSettings(arguments: arguments),
... ... @@ -923,6 +927,7 @@ you can only use widgets and widget functions here''';
Transition? transition,
Curve? curve,
Duration? duration,
double gestureWidth = 20,
}) {
var routeName = "/${page.runtimeType.toString()}";
... ... @@ -932,6 +937,7 @@ you can only use widgets and widget functions here''';
popGesture: popGesture ?? defaultPopGesture,
page: _resolve(page, 'offAll'),
binding: binding,
gestureWidth: gestureWidth,
settings: RouteSettings(arguments: arguments),
fullscreenDialog: fullscreenDialog,
routeName: routeName,
... ... @@ -1054,8 +1060,8 @@ you can only use widgets and widget functions here''';
return _key;
}
/// Casts the stored router delegate to a desired type
/// Casts the stored router delegate to a desired type
TDelegate? delegate<TDelegate extends RouterDelegate<TPage>, TPage>() =>
_routerDelegate as TDelegate?;
... ...
... ... @@ -174,11 +174,17 @@ class GetCupertinoApp extends StatelessWidget {
super(key: key);
Route<dynamic> generator(RouteSettings settings) {
return PageRedirect(settings, unknownRoute).page();
return PageRedirect(settings: settings, unknownRoute: unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) =>
[PageRedirect(RouteSettings(name: name), unknownRoute).page()];
List<Route<dynamic>> initialRoutesGenerate(String name) {
return [
PageRedirect(
settings: RouteSettings(name: name),
unknownRoute: unknownRoute,
).page()
];
}
@override
Widget build(BuildContext context) => GetBuilder<GetMaterialController>(
... ...
... ... @@ -183,11 +183,18 @@ class GetMaterialApp extends StatelessWidget {
initialRoute = null,
super(key: key);
Route<dynamic> generator(RouteSettings settings) =>
PageRedirect(settings, unknownRoute).page();
Route<dynamic> generator(RouteSettings settings) {
return PageRedirect(settings: settings, unknownRoute: unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) =>
[PageRedirect(RouteSettings(name: name), unknownRoute).page()];
List<Route<dynamic>> initialRoutesGenerate(String name) {
return [
PageRedirect(
settings: RouteSettings(name: name),
unknownRoute: unknownRoute,
).page()
];
}
@override
Widget build(BuildContext context) => GetBuilder<GetMaterialController>(
... ...
import 'dart:math';
import 'dart:ui' show lerpDouble;
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import '../../../get_core/get_core.dart';
import '../../../get_instance/get_instance.dart';
import '../../get_navigation.dart';
import '../../../get.dart';
import 'custom_transition.dart';
import 'default_transitions.dart';
import 'get_transition_mixin.dart';
import 'route_middleware.dart';
import 'transitions_type.dart';
class GetPageRoute<T> extends PageRoute<T> {
class GetPageRoute<T> extends PageRoute<T> with GetPageRouteTransitionMixin<T> {
/// 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 = 20.0,
this.curve,
this.alignment,
this.transition,
... ... @@ -27,6 +28,7 @@ class GetPageRoute<T> extends PageRoute<T> {
this.bindings,
this.routeName,
this.page,
this.title,
this.barrierLabel,
this.maintainState = true,
bool fullscreenDialog = false,
... ... @@ -36,35 +38,23 @@ class GetPageRoute<T> extends PageRoute<T> {
@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 opaque;
final bool? popGesture;
@override
final bool barrierDismissible;
final Transition? transition;
final Curve? curve;
final Alignment? alignment;
final List<GetMiddleware>? middlewares;
@override
... ... @@ -77,308 +67,6 @@ class GetPageRoute<T> extends PageRoute<T> {
final bool maintainState;
@override
bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
// Don't perform outgoing animation if the next route is a
// fullscreen dialog.
return nextRoute is PageRoute && !nextRoute.fullscreenDialog;
}
static bool _isPopGestureEnabled<T>(PageRoute<T> route) {
// ignore: lines_longer_than_80_chars
if (route.isFirst ||
route.willHandlePopInternally ||
route.hasScopedWillPopCallback ||
route.fullscreenDialog ||
route.animation!.status != AnimationStatus.completed ||
route.secondaryAnimation!.status != AnimationStatus.dismissed ||
isPopGestureInProgress(route)) return false;
return true;
}
static _CupertinoBackGestureController<T> _startPopGesture<T>(
PageRoute<T> route) {
assert(_isPopGestureEnabled(route));
return _CupertinoBackGestureController<T>(
navigator: route.navigator!,
controller: route.controller!,
);
}
@override
Widget buildPage(
BuildContext? context,
Animation<double>? animation,
Animation<double>? secondaryAnimation,
) {
// Get.reference = settings.name ?? routeName;
Get.reference = reference;
final middlewareRunner = MiddlewareRunner(middlewares);
final bindingsToBind = middlewareRunner.runOnBindingsStart(bindings);
binding?.dependencies();
if (bindingsToBind != null) {
for (final binding in bindingsToBind) {
binding.dependencies();
}
}
final pageToBuild = middlewareRunner.runOnPageBuildStart(page)!;
return middlewareRunner.runOnPageBuilt(pageToBuild());
}
static bool isPopGestureInProgress(PageRoute<dynamic> route) {
return route.navigator!.userGestureInProgress;
}
bool get popGestureInProgress => isPopGestureInProgress(this);
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
final finalCurve = curve ?? Get.defaultTransitionCurve;
final hasCurve = curve != null;
if (fullscreenDialog && transition == null) {
/// by default, if no curve is defined, use Cupertino transition in the
/// default way (no linearTransition)... otherwise take the curve passed.
return CupertinoFullscreenDialogTransition(
primaryRouteAnimation: hasCurve
? CurvedAnimation(parent: animation, curve: finalCurve)
: animation,
secondaryRouteAnimation: secondaryAnimation,
child: child,
linearTransition: hasCurve);
}
if (customTransition != null) {
return customTransition!.buildTransition(
context,
finalCurve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child,
);
}
/// Apply the curve by default...
final iosAnimation = animation;
animation = CurvedAnimation(parent: animation, curve: finalCurve);
switch (transition ?? Get.defaultTransition) {
case Transition.leftToRight:
return SlideLeftTransition().buildTransitions(
context,
curve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.downToUp:
return SlideDownTransition().buildTransitions(
context,
curve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.upToDown:
return SlideTopTransition().buildTransitions(
context,
curve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.noTransition:
return popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child;
case Transition.rightToLeft:
return SlideRightTransition().buildTransitions(
context,
curve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.zoom:
return ZoomInTransition().buildTransitions(
context,
curve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.fadeIn:
return FadeInTransition().buildTransitions(
context,
curve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.rightToLeftWithFade:
return RightToLeftFadeTransition().buildTransitions(
context,
curve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.leftToRightWithFade:
return LeftToRightFadeTransition().buildTransitions(
context,
curve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.cupertino:
return CupertinoPageTransitionsBuilder().buildTransitions(
this,
context,
iosAnimation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.size:
return SizeTransitions().buildTransitions(
context,
curve!,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.fade:
return FadeUpwardsPageTransitionsBuilder().buildTransitions(
this,
context,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.topLevel:
return ZoomPageTransitionsBuilder().buildTransitions(
this,
context,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.native:
return PageTransitionsTheme().buildTransitions(
this,
context,
iosAnimation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
default:
if (Get.customTransition != null) {
return Get.customTransition!.buildTransition(
context, curve, alignment, animation, secondaryAnimation, child);
}
return PageTransitionsTheme().buildTransitions(
this,
context,
iosAnimation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
}
}
@override
void dispose() {
super.dispose();
if (Get.smartManagement != SmartManagement.onlyBuilder) {
... ... @@ -396,220 +84,30 @@ class GetPageRoute<T> extends PageRoute<T> {
final middlewareRunner = MiddlewareRunner(middlewares);
middlewareRunner.runOnPageDispose();
}
}
const double _kBackGestureWidth = 20.0;
const double _kMinFlingVelocity = 1.0;
const int _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds.
// The maximum time for a page to get reset to it's original position if the
// user releases a page mid swipe.
const int _kMaxPageBackAnimationTime = 300;
class _CupertinoBackGestureDetector<T> extends StatefulWidget {
const _CupertinoBackGestureDetector({
Key? key,
required this.enabledCallback,
required this.onStartPopGesture,
required this.child,
}) : super(key: key);
final Widget child;
final ValueGetter<bool> enabledCallback;
final ValueGetter<_CupertinoBackGestureController<T>> onStartPopGesture;
@override
_CupertinoBackGestureDetectorState<T> createState() =>
_CupertinoBackGestureDetectorState<T>();
}
class _CupertinoBackGestureDetectorState<T>
extends State<_CupertinoBackGestureDetector<T>> {
_CupertinoBackGestureController<T>? _backGestureController;
Widget buildContent(BuildContext context) {
Get.reference = reference;
final middlewareRunner = MiddlewareRunner(middlewares);
final bindingsToBind = middlewareRunner.runOnBindingsStart(bindings);
late HorizontalDragGestureRecognizer _recognizer;
binding?.dependencies();
if (bindingsToBind != null) {
for (final binding in bindingsToBind) {
binding.dependencies();
}
}
@override
void initState() {
super.initState();
_recognizer = HorizontalDragGestureRecognizer(debugOwner: this)
..onStart = _handleDragStart
..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd
..onCancel = _handleDragCancel;
final pageToBuild = middlewareRunner.runOnPageBuildStart(page)!;
return middlewareRunner.runOnPageBuilt(pageToBuild());
}
@override
void dispose() {
_recognizer.dispose();
super.dispose();
}
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 _handleDragEnd(DragEndDetails details) {
assert(mounted);
assert(_backGestureController != null);
_backGestureController!.dragEnd(_convertToLogical(
details.velocity.pixelsPerSecond.dx / context.size!.width)!);
_backGestureController = null;
}
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 _handlePointerDown(PointerDownEvent event) {
if (widget.enabledCallback()) _recognizer.addPointer(event);
}
double? _convertToLogical(double value) {
switch (Directionality.of(context)) {
case TextDirection.rtl:
return -value;
case TextDirection.ltr:
return value;
default:
return value;
}
}
final String? title;
@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, _kBackGestureWidth);
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,
),
),
],
);
}
}
class _CupertinoBackGestureController<T> {
/// 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();
}
final AnimationController controller;
final NavigatorState navigator;
/// 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;
}
/// 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;
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;
}
String get debugLabel => '${super.debugLabel}(${settings.name})';
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();
}
}
@override
final double gestureWidth;
}
... ...
// import 'package:flutter/material.dart';
// import '../../../get.dart';
// import 'custom_transition.dart';
// import 'get_transition_mixin.dart';
// import 'route_middleware.dart';
// import 'transitions_type.dart';
// class GetPageRoute<T> extends PageRoute<T>
// with GetPageRouteTransitionMixin<T> {
// /// 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.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.barrierLabel,
// this.maintainState = true,
// bool fullscreenDialog = false,
// this.middlewares,
// }) : reference = "$routeName: ${settings?.hashCode ?? page.hashCode}",
// 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 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();
// if (Get.smartManagement != SmartManagement.onlyBuilder) {
// WidgetsBinding.instance!.addPostFrameCallback((_) {
// if (Get.reference != reference) {
// GetInstance().removeDependencyByRoute("$reference");
// }
// });
// }
// // if (Get.smartManagement != SmartManagement.onlyBuilder) {
// // GetInstance().removeDependencyByRoute("$reference");
// // }
// final middlewareRunner = MiddlewareRunner(middlewares);
// middlewareRunner.runOnPageDispose();
// }
// @override
// Widget buildContent(BuildContext context) {
// Get.reference = reference;
// final middlewareRunner = MiddlewareRunner(middlewares);
// final bindingsToBind = middlewareRunner.runOnBindingsStart(bindings);
// binding?.dependencies();
// if (bindingsToBind != null) {
// for (final binding in bindingsToBind) {
// binding.dependencies();
// }
// }
// final pageToBuild = middlewareRunner.runOnPageBuildStart(page)!;
// return middlewareRunner.runOnPageBuilt(pageToBuild());
// }
// @override
// final String? title;
// @override
// String get debugLabel => '${super.debugLabel}(${settings.name})';
// }
... ...
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
... ... @@ -36,6 +37,7 @@ class GetPage<T> extends Page<T> {
final Alignment? alignment;
final bool maintainState;
final bool opaque;
final double gestureWidth;
final Bindings? binding;
final List<Bindings> bindings;
final CustomTransition? customTransition;
... ... @@ -63,6 +65,7 @@ class GetPage<T> extends Page<T> {
required this.name,
required this.page,
this.title,
this.gestureWidth = 20,
// RouteSettings settings,
this.maintainState = true,
this.curve = Curves.linear,
... ... @@ -109,7 +112,7 @@ class GetPage<T> extends Page<T> {
return PathDecoded(RegExp('^$stringPath\$'), keys);
}
GetPage copy({
GetPage<T> copy({
String? name,
GetPageBuilder? page,
bool? popGesture,
... ... @@ -130,6 +133,7 @@ class GetPage<T> extends Page<T> {
GetPage? unknownRoute,
List<GetMiddleware>? middlewares,
bool? preventDuplicates,
double? gestureWidth,
}) {
return GetPage(
preventDuplicates: preventDuplicates ?? this.preventDuplicates,
... ... @@ -151,14 +155,17 @@ class GetPage<T> extends Page<T> {
children: children ?? this.children,
unknownRoute: unknownRoute ?? this.unknownRoute,
middlewares: middlewares ?? this.middlewares,
gestureWidth: gestureWidth ?? this.gestureWidth,
);
}
@override
Route<T> createRoute(BuildContext context) {
// return GetPageRoute<T>(settings: this, page: page);
return PageRedirect(
this,
unknownRoute,
).page<T>();
route: this,
settings: this,
unknownRoute: unknownRoute,
).getPageToRoute<T>(this, unknownRoute);
}
}
... ...
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';
import 'transitions_type.dart';
//const double _kBackGestureWidth = 20.0;
const double _kMinFlingVelocity = 1.0; // 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 _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds.
// The maximum time for a page to get reset to it's original position if the
// user releases a page mid swipe.
const int _kMaxPageBackAnimationTime = 300; // Milliseconds.
mixin GetPageRouteTransitionMixin<T> on PageRoute<T> {
/// Builds the primary contents of the route.
@protected
Widget buildContent(BuildContext context);
/// {@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;
double get gestureWidth;
ValueNotifier<String?>? _previousTitle;
/// 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!;
}
@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);
}
@override
// A relatively rigorous eyeball estimation.
Duration get transitionDuration => const Duration(milliseconds: 400);
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
// Don't perform outgoing animation if the next route is a
// fullscreen dialog.
return nextRoute is CupertinoRouteTransitionMixin &&
!nextRoute.fullscreenDialog;
}
/// 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;
}
/// 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);
/// 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);
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;
}
@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;
}
// Called by CupertinoBackGestureDetector when a pop ("back") drag start
// gesture is detected. The returned controller handles all of the subsequent
// drag events.
static CupertinoBackGestureController<T> _startPopGesture<T>(
PageRoute<T> route) {
assert(_isPopGestureEnabled(route));
return CupertinoBackGestureController<T>(
navigator: route.navigator!,
controller: route.controller!, // protected access
);
}
/// 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,
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,
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,
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,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.noTransition:
return route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
}
}
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return buildPageTransitions<T>(
this, context, animation, secondaryAnimation, child);
}
}
class CupertinoBackGestureDetector<T> extends StatefulWidget {
const CupertinoBackGestureDetector({
Key? key,
required this.enabledCallback,
required this.onStartPopGesture,
required this.child,
required this.gestureWidth,
}) : super(key: key);
final Widget child;
final double gestureWidth;
final ValueGetter<bool> enabledCallback;
final ValueGetter<CupertinoBackGestureController<T>> onStartPopGesture;
@override
CupertinoBackGestureDetectorState<T> createState() =>
CupertinoBackGestureDetectorState<T>();
}
class CupertinoBackGestureDetectorState<T>
extends State<CupertinoBackGestureDetector<T>> {
CupertinoBackGestureController<T>? _backGestureController;
late HorizontalDragGestureRecognizer _recognizer;
@override
void initState() {
super.initState();
_recognizer = HorizontalDragGestureRecognizer(debugOwner: this)
..onStart = _handleDragStart
..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd
..onCancel = _handleDragCancel;
}
@override
void dispose() {
_recognizer.dispose();
super.dispose();
}
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 _handleDragEnd(DragEndDetails details) {
assert(mounted);
assert(_backGestureController != null);
_backGestureController!.dragEnd(_convertToLogical(
details.velocity.pixelsPerSecond.dx / context.size!.width));
_backGestureController = null;
}
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 _handlePointerDown(PointerDownEvent event) {
if (widget.enabledCallback()) _recognizer.addPointer(event);
}
double _convertToLogical(double value) {
switch (Directionality.of(context)) {
case TextDirection.rtl:
return -value;
case TextDirection.ltr:
return value;
}
}
@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,
),
),
],
);
}
}
class CupertinoBackGestureController<T> {
/// 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();
}
final AnimationController controller;
final NavigatorState navigator;
/// 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;
}
/// 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();
}
}
}
... ...
... ... @@ -158,14 +158,14 @@ class MiddlewareRunner {
class PageRedirect {
GetPage? route;
GetPage? unknownRoute;
RouteSettings settings;
RouteSettings? settings;
bool isUnknown;
PageRedirect(
this.settings,
this.unknownRoute, {
this.isUnknown = false,
PageRedirect({
this.route,
this.unknownRoute,
this.isUnknown = false,
this.settings,
});
// redirect all pages that needes redirecting
... ... @@ -178,11 +178,36 @@ class PageRedirect {
settings: isUnknown
? RouteSettings(
name: _r.name,
arguments: settings.arguments,
arguments: settings!.arguments,
)
: settings,
curve: _r.curve,
opaque: _r.opaque,
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.parameter,
settings: _r,
curve: _r.curve,
gestureWidth: _r.gestureWidth,
opaque: _r.opaque,
customTransition: _r.customTransition,
binding: _r.binding,
bindings: _r.bindings,
... ... @@ -197,7 +222,10 @@ class PageRedirect {
/// check if redirect is needed
bool needRecheck() {
final match = Get.routeTree.matchRoute(settings.name!);
if (settings == null && route != null) {
settings = route;
}
final match = Get.routeTree.matchRoute(settings!.name!);
Get.parameters = match.parameters;
// No Match found
... ... @@ -214,7 +242,7 @@ class PageRedirect {
if (match.route!.middlewares == null || match.route!.middlewares!.isEmpty) {
return false;
}
final newSettings = runner.runRedirect(settings.name);
final newSettings = runner.runRedirect(settings!.name);
if (newSettings == null) {
return false;
}
... ...