fix(driver): ضمّ حزم get و get_storage داخل المستودع
apps/driver كان يشير إلى `../../Intaleq/packages/get` خارج المستودع — وهو مسار يُحل من apps/driver إلى Tripz/Intaleq/ غير الموجود، فلا يمرّ `flutter pub get` إطلاقاً. (فجوة docs/30 §2.6.) ضُمّت نسخة **انطلق** تحديداً (App/Intaleq/packages) لا فرع apps/rider: الفرعان يختلفان فعلاً — snackbar_controller.dart و get_queue.dart وغيرهما مختلفة بين النسختين، فاستعارة فرع rider كانت ستغيّر سلوك السائق صامتة. apps/driver/packages/get (3.5 م.ب، 202 ملف dart مع get_storage) apps/driver/packages/get_storage pubspec: ../../Intaleq/packages/* → ./packages/* (3 مواضع، منها dependency_overrides سطر 142) المستودع صار مكتفياً ذاتياً: لا تبعية مسار خارج شجرته. لم يُمسّ App/Intaleq. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4d8414c96b
commit
ecbe245b77
@@ -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,
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user