jonataslaw

Refactor GetRoute to add back gesture

... ... @@ -172,6 +172,12 @@
## [1.13.1-dev]
- Fix back function
## [1.14.0-dev]
- Added compatibility with Flutter 1.17.1
- Added back popGesture to iOS (default) and Android (optional)
- Improve performance
- Decrease lib size to 94.9kb (25.4k after compiled on release)
... ...
... ... @@ -11,7 +11,7 @@ increasing your productivity, and eliminating all the bugs present in Flutter's
##### If you use MODULAR, add on your MaterialApp this: navigatorKey: Get.addKey(Modular.navigatorKey)
##### If you use master/dev branch of Flutter, use the version 1.12.1-dev.
##### If you use master/dev branch of Flutter, use the version 1.14.0-dev.
## How to use?
... ... @@ -19,7 +19,7 @@ Add this to your package's pubspec.yaml file:
```
dependencies:
get: ^1.11.6 // get: ^1.13.1-dev on dev/master
get: ^1.11.6 // ^1.14.0-dev on dev/master
```
And import it:
... ... @@ -268,7 +268,8 @@ class Router {
return GetRoute(
settings: settings,
page: Third(),
transition: Transition.rightToLeft);
popGesture: true,
transition: Transition.cupertino);
default:
return GetRoute(
settings: settings,
... ...
... ... @@ -3,37 +3,37 @@
"packages": [
{
"name": "archive",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/archive-2.0.11",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/archive-2.0.13",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "args",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/args-1.5.2",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/args-1.6.0",
"packageUri": "lib/",
"languageVersion": "2.0"
"languageVersion": "2.3"
},
{
"name": "async",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/async-2.4.0",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/async-2.4.1",
"packageUri": "lib/",
"languageVersion": "2.0"
"languageVersion": "2.2"
},
{
"name": "boolean_selector",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.5",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.0.0",
"packageUri": "lib/",
"languageVersion": "2.0"
"languageVersion": "2.4"
},
{
"name": "charcode",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/charcode-1.1.2",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/charcode-1.1.3",
"packageUri": "lib/",
"languageVersion": "1.0"
"languageVersion": "2.0"
},
{
"name": "collection",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/collection-1.14.11",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/collection-1.14.12",
"packageUri": "lib/",
"languageVersion": "2.0"
},
... ... @@ -45,7 +45,7 @@
},
{
"name": "crypto",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/crypto-2.1.3",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/crypto-2.1.4",
"packageUri": "lib/",
"languageVersion": "2.1"
},
... ... @@ -75,7 +75,7 @@
},
{
"name": "image",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/image-2.1.4",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/image-2.1.12",
"packageUri": "lib/",
"languageVersion": "2.0"
},
... ... @@ -105,7 +105,7 @@
},
{
"name": "quiver",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/quiver-2.0.5",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/quiver-2.1.3",
"packageUri": "lib/",
"languageVersion": "2.0"
},
... ... @@ -117,9 +117,9 @@
},
{
"name": "source_span",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.5.5",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.7.0",
"packageUri": "lib/",
"languageVersion": "1.8"
"languageVersion": "2.6"
},
{
"name": "stack_trace",
... ... @@ -165,9 +165,9 @@
},
{
"name": "xml",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/xml-3.5.0",
"rootUri": "file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/xml-3.6.1",
"packageUri": "lib/",
"languageVersion": "2.3"
"languageVersion": "2.6"
},
{
"name": "profileweb",
... ... @@ -176,7 +176,7 @@
"languageVersion": "2.1"
}
],
"generated": "2020-03-30T23:54:30.607116Z",
"generated": "2020-04-02T04:26:33.309754Z",
"generator": "pub",
"generatorVersion": "2.8.0-dev.17.0.flutter-1402e8e1a4"
"generatorVersion": "2.8.0-dev.18.0.flutter-e8c4aed700"
}
... ...
# Generated by pub on 2020-03-30 20:54:30.579731.
archive:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/archive-2.0.11/lib/
args:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/args-1.5.2/lib/
async:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/async-2.4.0/lib/
boolean_selector:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.5/lib/
charcode:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/charcode-1.1.2/lib/
collection:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/collection-1.14.11/lib/
# Generated by pub on 2020-04-02 01:26:33.285366.
archive:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/archive-2.0.13/lib/
args:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/args-1.6.0/lib/
async:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/async-2.4.1/lib/
boolean_selector:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.0.0/lib/
charcode:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/charcode-1.1.3/lib/
collection:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/collection-1.14.12/lib/
convert:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/convert-2.1.1/lib/
crypto:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/crypto-2.1.3/lib/
crypto:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/crypto-2.1.4/lib/
cupertino_icons:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/cupertino_icons-0.1.3/lib/
flutter:file:///opt/flutter/packages/flutter/lib/
flutter_test:file:///opt/flutter/packages/flutter_test/lib/
get:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/get-1.11.6/lib/
image:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/image-2.1.4/lib/
image:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/image-2.1.12/lib/
matcher:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.6/lib/
meta:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/meta-1.1.8/lib/
path:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/path-1.6.4/lib/
petitparser:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/petitparser-2.4.0/lib/
quiver:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/quiver-2.0.5/lib/
quiver:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/quiver-2.1.3/lib/
sky_engine:file:///opt/flutter/bin/cache/pkg/sky_engine/lib/
source_span:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.5.5/lib/
source_span:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.7.0/lib/
stack_trace:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.9.3/lib/
stream_channel:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.0.0/lib/
string_scanner:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.0.5/lib/
... ... @@ -26,5 +26,5 @@ term_glyph:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.1
test_api:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/test_api-0.2.15/lib/
typed_data:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/typed_data-1.1.6/lib/
vector_math:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/vector_math-2.0.8/lib/
xml:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/xml-3.5.0/lib/
xml:file:///opt/flutter/.pub-cache/hosted/pub.dartlang.org/xml-3.6.1/lib/
profileweb:lib/
... ...
... ... @@ -7,42 +7,42 @@ packages:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.11"
version: "2.0.13"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.2"
version: "1.6.0"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0"
version: "2.4.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
version: "2.0.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
version: "1.1.3"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.14.11"
version: "1.14.12"
convert:
dependency: transitive
description:
... ... @@ -56,7 +56,7 @@ packages:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
version: "2.1.4"
cupertino_icons:
dependency: "direct main"
description:
... ... @@ -87,7 +87,7 @@ packages:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
version: "2.1.12"
matcher:
dependency: transitive
description:
... ... @@ -122,7 +122,7 @@ packages:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
version: "2.1.3"
sky_engine:
dependency: transitive
description: flutter
... ... @@ -134,7 +134,7 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.5"
version: "1.7.0"
stack_trace:
dependency: transitive
description:
... ... @@ -190,6 +190,6 @@ packages:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "3.5.0"
version: "3.6.1"
sdks:
dart: ">=2.4.0 <3.0.0"
dart: ">=2.6.0 <3.0.0"
... ...
... ... @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:get/src/dialog/dialog.dart';
import 'package:get/get.dart';
import 'platform/platform.dart';
import 'routes/blur/backdrop_blur.dart';
import 'routes/blur/transparent_route.dart';
import 'routes/default_route.dart';
... ... @@ -43,21 +44,27 @@ class Get {
/// It replaces Navigator.push, but needs no context, and it doesn't have the Navigator.push
/// routes rebuild bug present in Flutter. If for some strange reason you want the default behavior
/// of rebuilding every app after a route, use rebuildRoutes = true as the parameter.
/// of rebuilding every app after a route, use opaque = true as the parameter.
static Future<T> to<T>(Widget page,
{bool rebuildRoutes, Transition transition, Duration duration}) {
{bool opaque,
Transition transition,
Duration duration,
bool popGesture}) {
// if (key.currentState.mounted) // add this if appear problems on future with route navigate
// when widget don't mounted
return key.currentState.push(GetRoute(
rebuildRoutes: rebuildRoutes,
opaque: opaque ?? true,
page: page,
transition: transition ?? Transition.cupertino,
popGesture: popGesture,
transition: transition ?? GetPlatform.isIOS
? Transition.cupertino
: Transition.fade,
duration: duration ?? const Duration(milliseconds: 400)));
}
/// It replaces Navigator.pushNamed, but needs no context, and it doesn't have the Navigator.pushNamed
/// routes rebuild bug present in Flutter. If for some strange reason you want the default behavior
/// of rebuilding every app after a route, use rebuildRoutes = true as the parameter.
/// of rebuilding every app after a route, use opaque = true as the parameter.
static Future<T> toNamed<T>(String page, {arguments}) {
// if (key.currentState.mounted) // add this if appear problems on future with route navigate
// when widget don't mounted
... ... @@ -128,27 +135,38 @@ class Get {
/// It replaces Navigator.pushReplacement, but needs no context, and it doesn't have the Navigator.pushReplacement
/// routes rebuild bug present in Flutter. If for some strange reason you want the default behavior
/// of rebuilding every app after a route, use rebuildRoutes = true as the parameter.
/// of rebuilding every app after a route, use opaque = true as the parameter.
static Future<T> off<T>(Widget page,
{bool rebuildRoutes = false,
{bool opaque = false,
Transition transition,
bool popGesture,
Duration duration = const Duration(milliseconds: 400)}) {
return key.currentState.pushReplacement(GetRoute(
rebuildRoutes: rebuildRoutes,
opaque: opaque ?? true,
page: page,
transition: transition,
popGesture: popGesture,
transition: transition ?? GetPlatform.isIOS
? Transition.cupertino
: Transition.fade,
duration: duration));
}
/// It replaces Navigator.pushAndRemoveUntil, but needs no context
static Future<T> offAll<T>(Widget page,
{RoutePredicate predicate,
bool rebuildRoutes = false,
bool opaque = false,
bool popGesture,
Transition transition}) {
var route = (Route<dynamic> rota) => false;
return key.currentState.pushAndRemoveUntil(
GetRoute(
rebuildRoutes: rebuildRoutes, page: page, transition: transition),
opaque: opaque ?? true,
popGesture: popGesture,
page: page,
transition: transition ?? GetPlatform.isIOS
? Transition.cupertino
: Transition.fade,
),
predicate ?? route);
}
... ...
import 'dart:math';
import 'dart:ui' show lerpDouble;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'getroute_cupertino.dart';
import 'getroute_material.dart';
import '../platform/platform.dart';
import 'transitions_type.dart';
import 'package:get/get.dart';
// ignore: non_constant_identifier_names
PageRoute<T> GetRoute<T>({
Key key,
RouteSettings settings,
String title,
bool rebuildRoutes,
bool maintainState = true,
@required Widget page,
Transition transition,
Curve curve = Curves.linear,
Alignment alignment,
Duration duration = const Duration(milliseconds: 400),
bool fullscreenDialog = false,
}) {
return GetPlatform.isIOS
? GetCupertino(
settings: settings,
title: title,
opaque: rebuildRoutes ?? true,
maintainState: maintainState,
page: page,
transition: transition ?? Transition.cupertino,
curve: curve,
alignment: alignment,
duration: duration,
fullscreenDialog: fullscreenDialog)
: GetMaterial(
settings: settings,
opaque: rebuildRoutes ?? false,
maintainState: maintainState,
page: page,
transition: transition ?? Transition.fade,
curve: curve,
alignment: alignment,
duration: duration,
fullscreenDialog: fullscreenDialog);
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 GetRoute<T> extends PageRoute<T> {
/// Creates a page route for use in an iOS designed app.
///
/// The [builder], [maintainState], and [fullscreenDialog] arguments must not
/// be null.
GetRoute({
@required this.page,
this.title,
RouteSettings settings,
this.maintainState = true,
this.curve = Curves.linear,
this.alignment,
this.opaque = true,
this.popGesture,
this.transition = Transition.cupertino,
this.duration = const Duration(milliseconds: 400),
bool fullscreenDialog = false,
}) : assert(page != null),
assert(maintainState != null),
assert(fullscreenDialog != null),
// assert(opaque),
super(settings: settings, fullscreenDialog: fullscreenDialog);
/// Builds the primary contents of the route.
final Widget page;
final bool popGesture;
final Duration duration;
final String title;
final Transition transition;
final Curve curve;
final Alignment alignment;
ValueNotifier<String> _previousTitle;
/// The title string of the previous [GetRoute].
///
/// 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 [GetRoute].
///
/// 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 String previousTitleString =
previousRoute is GetRoute ? previousRoute.title : null;
if (_previousTitle == null) {
_previousTitle = ValueNotifier<String>(previousTitleString);
} else {
_previousTitle.value = previousTitleString;
}
super.didChangePrevious(previousRoute);
}
@override
final bool maintainState;
/// Allows you to set opaque to false to prevent route reconstruction.
@override
final bool opaque;
@override
// A relatively rigorous eyeball estimation.
Duration get transitionDuration => const Duration(milliseconds: 400);
@override
Color get barrierColor => null; //Color(0x00FFFFFF);
@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 GetRoute && !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 Widget child = page;
final Widget result = Semantics(
scopesRoute: true,
explicitChildNodes: true,
child: child,
);
assert(() {
if (child == null) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary(
'The builder for route "${settings.name}" returned null.'),
ErrorDescription('Route builders must never return null.'),
]);
}
return true;
}());
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 [GetRoute.buildTransitions].
///
/// This method can be applied to any [PageRoute], not just
/// [GetRoute]. 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> route,
BuildContext context,
bool popGesture,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
Transition transition,
Curve curve,
Alignment alignment,
) {
if (route.fullscreenDialog) {
final bool linearTransition = isPopGestureInProgress(route);
return CupertinoFullscreenDialogTransition(
primaryRouteAnimation: animation,
secondaryRouteAnimation: secondaryAnimation,
child: child,
linearTransition: linearTransition,
);
} else {
switch (transition) {
case Transition.fade:
final PageTransitionsBuilder matchingBuilder =
FadeUpwardsPageTransitionsBuilder();
return matchingBuilder.buildTransitions<T>(
route,
context,
animation,
secondaryAnimation,
popGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
break;
case Transition.rightToLeft:
return SlideTransition(
transformHitTests: false,
position: new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(-1.0, 0.0),
).animate(secondaryAnimation),
child: popGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child),
);
break;
case Transition.leftToRight:
return SlideTransition(
transformHitTests: false,
position: Tween<Offset>(
begin: const Offset(-1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.0, 0.0),
).animate(secondaryAnimation),
child: popGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child),
);
break;
case Transition.upToDown:
return SlideTransition(
transformHitTests: false,
position: Tween<Offset>(
begin: const Offset(0.0, -1.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(0.0, 1.0),
).animate(secondaryAnimation),
child: popGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child),
);
break;
case Transition.downToUp:
return SlideTransition(
transformHitTests: false,
position: Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(0.0, -1.0),
).animate(secondaryAnimation),
child: popGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child),
);
break;
case Transition.scale:
return ScaleTransition(
alignment: alignment,
scale: CurvedAnimation(
parent: animation,
curve: Interval(
0.00,
0.50,
curve: curve,
),
),
child: popGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
break;
case Transition.rotate:
return RotationTransition(
alignment: alignment,
turns: animation,
child: ScaleTransition(
alignment: alignment,
scale: animation,
child: FadeTransition(
opacity: animation,
child: popGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child),
),
);
break;
case Transition.rightToLeftWithFade:
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: popGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child),
),
);
break;
case Transition.leftToRightWithFade:
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: popGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child),
),
);
break;
case Transition.cupertino:
return CupertinoPageTransition(
primaryRouteAnimation: animation,
secondaryRouteAnimation: secondaryAnimation,
// 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.
linearTransition: isPopGestureInProgress(route),
child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
);
break;
default:
return CupertinoPageTransition(
primaryRouteAnimation: animation,
secondaryRouteAnimation: secondaryAnimation,
// 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.
linearTransition: isPopGestureInProgress(route),
child: popGesture
? _CupertinoBackGestureDetector<T>(
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,
popGesture ?? GetPlatform.isIOS,
animation,
secondaryAnimation,
child,
transition,
curve,
alignment);
}
@override
String get debugLabel => '${super.debugLabel}(${settings.name})';
}
class _CupertinoBackGestureDetector<T> extends StatefulWidget {
const _CupertinoBackGestureDetector({
Key key,
@required this.enabledCallback,
@required this.onStartPopGesture,
@required this.child,
}) : assert(enabledCallback != null),
assert(onStartPopGesture != null),
assert(child != null),
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;
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;
}
return null;
}
@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.
double 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,
}) : assert(navigator != null),
assert(controller != null) {
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;
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 int 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 int 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.
AnimationStatusListener animationStatusCallback;
animationStatusCallback = (AnimationStatus status) {
navigator.didStopUserGesture();
controller.removeStatusListener(animationStatusCallback);
};
controller.addStatusListener(animationStatusCallback);
} else {
navigator.didStopUserGesture();
}
}
}
... ...
import 'dart:math';
import 'dart:ui' show lerpDouble;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'transitions_type.dart';
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 GetCupertino<T> extends PageRoute<T> {
/// Creates a page route for use in an iOS designed app.
///
/// The [builder], [maintainState], and [fullscreenDialog] arguments must not
/// be null.
GetCupertino({
@required this.page,
this.title,
RouteSettings settings,
this.maintainState = true,
this.curve = Curves.linear,
this.alignment,
this.opaque = false,
this.transition = Transition.cupertino,
this.duration = const Duration(milliseconds: 400),
bool fullscreenDialog = false,
}) : assert(page != null),
assert(maintainState != null),
assert(fullscreenDialog != null),
// assert(opaque),
super(settings: settings, fullscreenDialog: fullscreenDialog);
/// Builds the primary contents of the route.
final Widget page;
final Duration duration;
final String title;
final Transition transition;
final Curve curve;
final Alignment alignment;
ValueNotifier<String> _previousTitle;
/// The title string of the previous [GetCupertino].
///
/// 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 [GetCupertino].
///
/// 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 String previousTitleString =
previousRoute is GetCupertino ? previousRoute.title : null;
if (_previousTitle == null) {
_previousTitle = ValueNotifier<String>(previousTitleString);
} else {
_previousTitle.value = previousTitleString;
}
super.didChangePrevious(previousRoute);
}
@override
final bool maintainState;
/// Allows you to set opaque to false to prevent route reconstruction.
@override
final bool opaque;
@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 GetCupertino && !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 Widget child = page;
final Widget result = Semantics(
scopesRoute: true,
explicitChildNodes: true,
child: child,
);
assert(() {
if (child == null) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary(
'The builder for route "${settings.name}" returned null.'),
ErrorDescription('Route builders must never return null.'),
]);
}
return true;
}());
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 [GetCupertino.buildTransitions].
///
/// This method can be applied to any [PageRoute], not just
/// [GetCupertino]. 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> route,
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
Transition transition,
Curve curve,
Alignment alignment,
) {
if (route.fullscreenDialog) {
return CupertinoFullscreenDialogTransition(
animation: animation,
child: child,
);
} else {
switch (transition) {
case Transition.fade:
return FadeTransition(
opacity: animation,
child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
);
break;
case Transition.rightToLeft:
return SlideTransition(
transformHitTests: false,
position: new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(-1.0, 0.0),
).animate(secondaryAnimation),
child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
),
);
break;
case Transition.leftToRight:
return SlideTransition(
transformHitTests: false,
position: Tween<Offset>(
begin: const Offset(-1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.0, 0.0),
).animate(secondaryAnimation),
child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
),
);
break;
case Transition.upToDown:
return SlideTransition(
transformHitTests: false,
position: Tween<Offset>(
begin: const Offset(0.0, -1.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(0.0, 1.0),
).animate(secondaryAnimation),
child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
),
);
break;
case Transition.downToUp:
return SlideTransition(
transformHitTests: false,
position: Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(0.0, -1.0),
).animate(secondaryAnimation),
child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
),
);
break;
case Transition.scale:
return ScaleTransition(
alignment: alignment,
scale: CurvedAnimation(
parent: animation,
curve: Interval(
0.00,
0.50,
curve: curve,
),
),
child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
);
break;
case Transition.rotate:
return RotationTransition(
alignment: alignment,
turns: animation,
child: ScaleTransition(
alignment: alignment,
scale: animation,
child: FadeTransition(
opacity: animation,
child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
),
),
);
break;
case Transition.size:
return Align(
alignment: alignment,
child: SizeTransition(
sizeFactor: CurvedAnimation(
parent: animation,
curve: curve,
),
child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
),
);
break;
case Transition.rightToLeftWithFade:
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: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
),
),
);
break;
case Transition.leftToRightWithFade:
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: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
),
),
);
break;
case Transition.cupertino:
return CupertinoPageTransition(
primaryRouteAnimation: animation,
secondaryRouteAnimation: secondaryAnimation,
// 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.
linearTransition: isPopGestureInProgress(route),
child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
);
break;
default:
return CupertinoPageTransition(
primaryRouteAnimation: animation,
secondaryRouteAnimation: secondaryAnimation,
// 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.
linearTransition: isPopGestureInProgress(route),
child: _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
);
}
}
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return buildPageTransitions<T>(this, context, animation, secondaryAnimation,
child, transition, curve, alignment);
}
@override
String get debugLabel => '${super.debugLabel}(${settings.name})';
}
class _CupertinoBackGestureDetector<T> extends StatefulWidget {
const _CupertinoBackGestureDetector({
Key key,
@required this.enabledCallback,
@required this.onStartPopGesture,
@required this.child,
}) : assert(enabledCallback != null),
assert(onStartPopGesture != null),
assert(child != null),
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;
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;
}
return null;
}
@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.
double 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,
}) : assert(navigator != null),
assert(controller != null) {
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;
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 int 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 int 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.
AnimationStatusListener animationStatusCallback;
animationStatusCallback = (AnimationStatus status) {
navigator.didStopUserGesture();
controller.removeStatusListener(animationStatusCallback);
};
controller.addStatusListener(animationStatusCallback);
} else {
navigator.didStopUserGesture();
}
}
}
import 'package:flutter/material.dart';
import 'transitions_type.dart';
class GetMaterial<T> extends PageRouteBuilder<T> {
/// Construct a Modified PageRoute whose contents are defined by child.
/// The values of [child], [maintainState], [opaque], and [fullScreenDialog] must not
/// be null.
GetMaterial({
Key key,
RouteSettings settings,
this.opaque = false,
this.maintainState = true,
@required this.page,
this.transition = Transition.fade,
this.curve = Curves.linear,
this.alignment,
this.duration = const Duration(milliseconds: 400),
bool fullscreenDialog = false,
}) : assert(page != null),
assert(maintainState != null),
assert(fullscreenDialog != null),
assert(opaque != null),
super(
fullscreenDialog: fullscreenDialog,
pageBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return page;
},
transitionDuration: duration,
settings: settings,
transitionsBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
switch (transition) {
case Transition.fade:
return FadeTransition(opacity: animation, child: child);
break;
case Transition.rightToLeft:
return SlideTransition(
transformHitTests: false,
position: new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(-1.0, 0.0),
).animate(secondaryAnimation),
child: child,
),
);
break;
case Transition.leftToRight:
return SlideTransition(
transformHitTests: false,
position: Tween<Offset>(
begin: const Offset(-1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.0, 0.0),
).animate(secondaryAnimation),
child: child,
),
);
break;
case Transition.upToDown:
return SlideTransition(
transformHitTests: false,
position: Tween<Offset>(
begin: const Offset(0.0, -1.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(0.0, 1.0),
).animate(secondaryAnimation),
child: child,
),
);
break;
case Transition.downToUp:
return SlideTransition(
transformHitTests: false,
position: Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(0.0, -1.0),
).animate(secondaryAnimation),
child: child,
),
);
break;
case Transition.scale:
return ScaleTransition(
alignment: alignment,
scale: CurvedAnimation(
parent: animation,
curve: Interval(
0.00,
0.50,
curve: curve,
),
),
child: child,
);
break;
case Transition.rotate:
return RotationTransition(
alignment: alignment,
turns: animation,
child: ScaleTransition(
alignment: alignment,
scale: animation,
child: FadeTransition(
opacity: animation,
child: child,
),
),
);
break;
case Transition.size:
return Align(
alignment: alignment,
child: SizeTransition(
sizeFactor: CurvedAnimation(
parent: animation,
curve: curve,
),
child: child,
),
);
break;
case Transition.rightToLeftWithFade:
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,
),
),
);
break;
case Transition.leftToRightWithFade:
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,
),
),
);
break;
default:
return FadeTransition(opacity: animation, child: child);
}
});
@override
final bool maintainState;
/// Allows you to set opaque to false to prevent route reconstruction.
@override
final bool opaque;
@override
Duration get transitionDuration => const Duration(milliseconds: 300);
@override
Color get barrierColor => null;
@override
String get barrierLabel => null;
// @override
// bool canTransitionFrom(TransitionRoute<dynamic> previousRoute) {
// return previousRoute is GetMaterial || previousRoute is CupertinoPageRoute;
// }
// @override
// bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
// // Don't perform outgoing animation if the next route is a fullscreen dialog.
// return (nextRoute is GetMaterial && !nextRoute.fullscreenDialog) ||
// (nextRoute is CupertinoPageRoute && !nextRoute.fullscreenDialog);
// }
@override
String get debugLabel => '${super.debugLabel}(${settings.name})';
final Widget page;
final Transition transition;
final Curve curve;
final Alignment alignment;
final Duration duration;
}
... ... @@ -7,42 +7,42 @@ packages:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.11"
version: "2.0.13"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.2"
version: "1.6.0"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0"
version: "2.4.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
version: "2.0.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
version: "1.1.3"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.14.11"
version: "1.14.12"
convert:
dependency: transitive
description:
... ... @@ -56,7 +56,7 @@ packages:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
version: "2.1.4"
flutter:
dependency: "direct main"
description: flutter
... ... @@ -73,7 +73,7 @@ packages:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
version: "2.1.12"
matcher:
dependency: transitive
description:
... ... @@ -108,7 +108,7 @@ packages:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
version: "2.1.3"
sky_engine:
dependency: transitive
description: flutter
... ... @@ -120,7 +120,7 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.5"
version: "1.7.0"
stack_trace:
dependency: transitive
description:
... ... @@ -176,6 +176,6 @@ packages:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "3.5.0"
version: "3.6.1"
sdks:
dart: ">=2.4.0 <3.0.0"
dart: ">=2.6.0 <3.0.0"
... ...
name: get
description: Navigate between screens, display snackbars, dialogs and bottomSheets, from anywhere in your code without context with Get.
version: 1.13.1-dev
version: 1.14.0-dev
homepage: https://github.com/jonataslaw/get
environment:
... ...