Showing
7 changed files
with
496 additions
and
40 deletions
| @@ -11,13 +11,15 @@ increasing your productivity, and eliminating all the bugs present in Flutter's | @@ -11,13 +11,15 @@ increasing your productivity, and eliminating all the bugs present in Flutter's | ||
| 11 | 11 | ||
| 12 | ##### If you use MODULAR, add on your MaterialApp this: navigatorKey: Get.addKey(Modular.navigatorKey) | 12 | ##### If you use MODULAR, add on your MaterialApp this: navigatorKey: Get.addKey(Modular.navigatorKey) |
| 13 | 13 | ||
| 14 | +##### If you use master/dev branch of Flutter, use the version 1.12.0-dev. | ||
| 15 | + | ||
| 14 | ## How to use? | 16 | ## How to use? |
| 15 | 17 | ||
| 16 | Add this to your package's pubspec.yaml file: | 18 | Add this to your package's pubspec.yaml file: |
| 17 | 19 | ||
| 18 | ``` | 20 | ``` |
| 19 | dependencies: | 21 | dependencies: |
| 20 | - get: ^1.11.1 | 22 | + get: ^1.11.1 // get: ^1.12.0-dev on dev/master |
| 21 | ``` | 23 | ``` |
| 22 | 24 | ||
| 23 | And import it: | 25 | And import it: |
| @@ -49,7 +49,7 @@ class Get { | @@ -49,7 +49,7 @@ class Get { | ||
| 49 | /// It replaces Navigator.push, but needs no context, and it doesn't have the Navigator.push | 49 | /// It replaces Navigator.push, but needs no context, and it doesn't have the Navigator.push |
| 50 | /// routes rebuild bug present in Flutter. If for some strange reason you want the default behavior | 50 | /// routes rebuild bug present in Flutter. If for some strange reason you want the default behavior |
| 51 | /// of rebuilding every app after a route, use rebuildRoutes = true as the parameter. | 51 | /// of rebuilding every app after a route, use rebuildRoutes = true as the parameter. |
| 52 | - static to(Widget page, | 52 | + static Future<T> to<T>(Widget page, |
| 53 | {bool rebuildRoutes, | 53 | {bool rebuildRoutes, |
| 54 | Transition transition, | 54 | Transition transition, |
| 55 | Duration duration = const Duration(milliseconds: 400)}) { | 55 | Duration duration = const Duration(milliseconds: 400)}) { |
| @@ -72,47 +72,47 @@ class Get { | @@ -72,47 +72,47 @@ class Get { | ||
| 72 | /// It replaces Navigator.pushNamed, but needs no context, and it doesn't have the Navigator.pushNamed | 72 | /// It replaces Navigator.pushNamed, but needs no context, and it doesn't have the Navigator.pushNamed |
| 73 | /// routes rebuild bug present in Flutter. If for some strange reason you want the default behavior | 73 | /// routes rebuild bug present in Flutter. If for some strange reason you want the default behavior |
| 74 | /// of rebuilding every app after a route, use rebuildRoutes = true as the parameter. | 74 | /// of rebuilding every app after a route, use rebuildRoutes = true as the parameter. |
| 75 | - static toNamed(String page, {arguments}) { | 75 | + static Future<T> toNamed<T>(String page, {arguments}) { |
| 76 | // if (key.currentState.mounted) // add this if appear problems on future with route navigate | 76 | // if (key.currentState.mounted) // add this if appear problems on future with route navigate |
| 77 | // when widget don't mounted | 77 | // when widget don't mounted |
| 78 | return key.currentState.pushNamed(page, arguments: arguments); | 78 | return key.currentState.pushNamed(page, arguments: arguments); |
| 79 | } | 79 | } |
| 80 | 80 | ||
| 81 | /// It replaces Navigator.pushReplacementNamed, but needs no context. | 81 | /// It replaces Navigator.pushReplacementNamed, but needs no context. |
| 82 | - static offNamed(String page, {arguments}) { | 82 | + static Future<T> offNamed<T>(String page, {arguments}) { |
| 83 | // if (key.currentState.mounted) // add this if appear problems on future with route navigate | 83 | // if (key.currentState.mounted) // add this if appear problems on future with route navigate |
| 84 | // when widget don't mounted | 84 | // when widget don't mounted |
| 85 | return key.currentState.pushReplacementNamed(page, arguments: arguments); | 85 | return key.currentState.pushReplacementNamed(page, arguments: arguments); |
| 86 | } | 86 | } |
| 87 | 87 | ||
| 88 | /// It replaces Navigator.popUntil, but needs no context. | 88 | /// It replaces Navigator.popUntil, but needs no context. |
| 89 | - static until(String page, predicate) { | 89 | + static void until(String page, predicate) { |
| 90 | // if (key.currentState.mounted) // add this if appear problems on future with route navigate | 90 | // if (key.currentState.mounted) // add this if appear problems on future with route navigate |
| 91 | // when widget don't mounted | 91 | // when widget don't mounted |
| 92 | return key.currentState.popUntil(predicate); | 92 | return key.currentState.popUntil(predicate); |
| 93 | } | 93 | } |
| 94 | 94 | ||
| 95 | /// It replaces Navigator.pushAndRemoveUntil, but needs no context. | 95 | /// It replaces Navigator.pushAndRemoveUntil, but needs no context. |
| 96 | - static offUntil(page, predicate) { | 96 | + static Future<T> offUntil<T>(page, predicate) { |
| 97 | // if (key.currentState.mounted) // add this if appear problems on future with route navigate | 97 | // if (key.currentState.mounted) // add this if appear problems on future with route navigate |
| 98 | // when widget don't mounted | 98 | // when widget don't mounted |
| 99 | return key.currentState.pushAndRemoveUntil(page, predicate); | 99 | return key.currentState.pushAndRemoveUntil(page, predicate); |
| 100 | } | 100 | } |
| 101 | 101 | ||
| 102 | /// It replaces Navigator.pushNamedAndRemoveUntil, but needs no context. | 102 | /// It replaces Navigator.pushNamedAndRemoveUntil, but needs no context. |
| 103 | - static offNamedUntil(page, predicate) { | 103 | + static Future<T> offNamedUntil<T>(page, predicate) { |
| 104 | // if (key.currentState.mounted) // add this if appear problems on future with route navigate | 104 | // if (key.currentState.mounted) // add this if appear problems on future with route navigate |
| 105 | // when widget don't mounted | 105 | // when widget don't mounted |
| 106 | return key.currentState.pushNamedAndRemoveUntil(page, predicate); | 106 | return key.currentState.pushNamedAndRemoveUntil(page, predicate); |
| 107 | } | 107 | } |
| 108 | 108 | ||
| 109 | /// It replaces Navigator.removeRoute, but needs no context. | 109 | /// It replaces Navigator.removeRoute, but needs no context. |
| 110 | - static removeRoute(route) { | 110 | + static void removeRoute(route) { |
| 111 | return key.currentState.removeRoute(route); | 111 | return key.currentState.removeRoute(route); |
| 112 | } | 112 | } |
| 113 | 113 | ||
| 114 | /// It replaces Navigator.pushNamedAndRemoveUntil, but needs no context. | 114 | /// It replaces Navigator.pushNamedAndRemoveUntil, but needs no context. |
| 115 | - static offAllNamed( | 115 | + static Future<T> offAllNamed<T>( |
| 116 | String newRouteName, { | 116 | String newRouteName, { |
| 117 | RoutePredicate predicate, | 117 | RoutePredicate predicate, |
| 118 | arguments, | 118 | arguments, |
| @@ -124,12 +124,12 @@ class Get { | @@ -124,12 +124,12 @@ class Get { | ||
| 124 | } | 124 | } |
| 125 | 125 | ||
| 126 | /// It replaces Navigator.pop, but needs no context. | 126 | /// It replaces Navigator.pop, but needs no context. |
| 127 | - static back({dynamic result}) { | 127 | + static void back({dynamic result}) { |
| 128 | return key.currentState.pop(result); | 128 | return key.currentState.pop(result); |
| 129 | } | 129 | } |
| 130 | 130 | ||
| 131 | /// It will close as many screens as you define. Times must be> 0; | 131 | /// It will close as many screens as you define. Times must be> 0; |
| 132 | - static close(int times) { | 132 | + static void close(int times) { |
| 133 | if ((times == null) || (times < 1)) { | 133 | if ((times == null) || (times < 1)) { |
| 134 | times = 1; | 134 | times = 1; |
| 135 | } | 135 | } |
| @@ -143,25 +143,33 @@ class Get { | @@ -143,25 +143,33 @@ class Get { | ||
| 143 | /// It replaces Navigator.pushReplacement, but needs no context, and it doesn't have the Navigator.pushReplacement | 143 | /// It replaces Navigator.pushReplacement, but needs no context, and it doesn't have the Navigator.pushReplacement |
| 144 | /// routes rebuild bug present in Flutter. If for some strange reason you want the default behavior | 144 | /// routes rebuild bug present in Flutter. If for some strange reason you want the default behavior |
| 145 | /// of rebuilding every app after a route, use rebuildRoutes = true as the parameter. | 145 | /// of rebuilding every app after a route, use rebuildRoutes = true as the parameter. |
| 146 | - static off(Widget page, | 146 | + static Future<T> off<T>(Widget page, |
| 147 | {bool rebuildRoutes = false, | 147 | {bool rebuildRoutes = false, |
| 148 | - Transition transition = Transition.rightToLeft, | 148 | + Transition transition, |
| 149 | Duration duration = const Duration(milliseconds: 400)}) { | 149 | Duration duration = const Duration(milliseconds: 400)}) { |
| 150 | return key.currentState.pushReplacement(GetRoute( | 150 | return key.currentState.pushReplacement(GetRoute( |
| 151 | opaque: rebuildRoutes, | 151 | opaque: rebuildRoutes, |
| 152 | page: page, | 152 | page: page, |
| 153 | - transition: transition, | 153 | + transition: transition ?? Platform.isIOS |
| 154 | + ? Transition.cupertino | ||
| 155 | + : Transition.fade, | ||
| 154 | duration: duration)); | 156 | duration: duration)); |
| 155 | } | 157 | } |
| 156 | 158 | ||
| 157 | /// It replaces Navigator.pushAndRemoveUntil, but needs no context | 159 | /// It replaces Navigator.pushAndRemoveUntil, but needs no context |
| 158 | - static offAll(Widget page, | 160 | + static Future<T> offAll<T>(Widget page, |
| 159 | {RoutePredicate predicate, | 161 | {RoutePredicate predicate, |
| 160 | bool rebuildRoutes = false, | 162 | bool rebuildRoutes = false, |
| 161 | - Transition transition = Transition.rightToLeft}) { | 163 | + Transition transition}) { |
| 162 | var route = (Route<dynamic> rota) => false; | 164 | var route = (Route<dynamic> rota) => false; |
| 163 | return key.currentState.pushAndRemoveUntil( | 165 | return key.currentState.pushAndRemoveUntil( |
| 164 | - GetRoute(opaque: rebuildRoutes, page: page, transition: transition), | 166 | + GetRoute( |
| 167 | + opaque: rebuildRoutes, | ||
| 168 | + page: page, | ||
| 169 | + transition: transition ?? Platform.isIOS | ||
| 170 | + ? Transition.cupertino | ||
| 171 | + : Transition.fade, | ||
| 172 | + ), | ||
| 165 | predicate ?? route); | 173 | predicate ?? route); |
| 166 | } | 174 | } |
| 167 | 175 | ||
| @@ -258,7 +266,7 @@ class Get { | @@ -258,7 +266,7 @@ class Get { | ||
| 258 | return ModalRoute.of(context).settings.arguments; | 266 | return ModalRoute.of(context).settings.arguments; |
| 259 | } | 267 | } |
| 260 | 268 | ||
| 261 | - static backdrop(Widget child, | 269 | + static Future backdrop(Widget child, |
| 262 | {double radius = 20.0, | 270 | {double radius = 20.0, |
| 263 | double blurRadius: 20.0, | 271 | double blurRadius: 20.0, |
| 264 | int duration = 300, | 272 | int duration = 300, |
| @@ -282,7 +290,7 @@ class Get { | @@ -282,7 +290,7 @@ class Get { | ||
| 282 | })); | 290 | })); |
| 283 | } | 291 | } |
| 284 | 292 | ||
| 285 | - static snackbar(title, message, | 293 | + static void snackbar(title, message, |
| 286 | {Color colorText, | 294 | {Color colorText, |
| 287 | Duration duration, | 295 | Duration duration, |
| 288 | SnackPosition snackPosition, | 296 | SnackPosition snackPosition, |
| @@ -369,15 +377,23 @@ class Get { | @@ -369,15 +377,23 @@ class Get { | ||
| 369 | }); | 377 | }); |
| 370 | } | 378 | } |
| 371 | 379 | ||
| 372 | - static iconColor() { | ||
| 373 | - return Theme.of(key.currentContext).iconTheme.color; | 380 | + static BuildContext context() { |
| 381 | + return key.currentContext; | ||
| 382 | + } | ||
| 383 | + | ||
| 384 | + static ThemeData theme() { | ||
| 385 | + return Theme.of(context()); | ||
| 386 | + } | ||
| 387 | + | ||
| 388 | + static Color iconColor() { | ||
| 389 | + return Theme.of(context()).iconTheme.color; | ||
| 374 | } | 390 | } |
| 375 | 391 | ||
| 376 | - static height() { | ||
| 377 | - return MediaQuery.of(key.currentContext).size.height; | 392 | + static double height() { |
| 393 | + return MediaQuery.of(context()).size.height; | ||
| 378 | } | 394 | } |
| 379 | 395 | ||
| 380 | - static width() { | ||
| 381 | - return MediaQuery.of(key.currentContext).size.width; | 396 | + static double width() { |
| 397 | + return MediaQuery.of(context()).size.width; | ||
| 382 | } | 398 | } |
| 383 | } | 399 | } |
lib/src/sample.dart
0 → 100644
| 1 | +import 'dart:math'; | ||
| 2 | +import 'dart:ui' show lerpDouble; | ||
| 3 | + | ||
| 4 | +import 'package:flutter/cupertino.dart'; | ||
| 5 | +import 'package:flutter/foundation.dart'; | ||
| 6 | +import 'package:flutter/gestures.dart'; | ||
| 7 | + | ||
| 8 | +const double _kBackGestureWidth = 20.0; | ||
| 9 | +const double _kMinFlingVelocity = 1.0; | ||
| 10 | +const int _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds. | ||
| 11 | + | ||
| 12 | +// The maximum time for a page to get reset to it's original position if the | ||
| 13 | +// user releases a page mid swipe. | ||
| 14 | +const int _kMaxPageBackAnimationTime = 300; | ||
| 15 | + | ||
| 16 | +class GetCupertino<T> extends PageRoute<T> { | ||
| 17 | + /// Creates a page route for use in an iOS designed app. | ||
| 18 | + /// | ||
| 19 | + /// The [builder], [maintainState], and [fullscreenDialog] arguments must not | ||
| 20 | + /// be null. | ||
| 21 | + GetCupertino({ | ||
| 22 | + @required this.builder, | ||
| 23 | + this.title, | ||
| 24 | + RouteSettings settings, | ||
| 25 | + this.maintainState = true, | ||
| 26 | + bool fullscreenDialog = false, | ||
| 27 | + }) : assert(builder != null), | ||
| 28 | + assert(maintainState != null), | ||
| 29 | + assert(fullscreenDialog != null), | ||
| 30 | + assert(opaque), | ||
| 31 | + super(settings: settings, fullscreenDialog: fullscreenDialog); | ||
| 32 | + | ||
| 33 | + /// Builds the primary contents of the route. | ||
| 34 | + final WidgetBuilder builder; | ||
| 35 | + | ||
| 36 | + /// A title string for this route. | ||
| 37 | + /// | ||
| 38 | + /// Used to auto-populate [CupertinoNavigationBar] and | ||
| 39 | + /// [CupertinoSliverNavigationBar]'s `middle`/`largeTitle` widgets when | ||
| 40 | + /// one is not manually supplied. | ||
| 41 | + final String title; | ||
| 42 | + | ||
| 43 | + ValueNotifier<String> _previousTitle; | ||
| 44 | + | ||
| 45 | + /// The title string of the previous [GetCupertino]. | ||
| 46 | + /// | ||
| 47 | + /// The [ValueListenable]'s value is readable after the route is installed | ||
| 48 | + /// onto a [Navigator]. The [ValueListenable] will also notify its listeners | ||
| 49 | + /// if the value changes (such as by replacing the previous route). | ||
| 50 | + /// | ||
| 51 | + /// The [ValueListenable] itself will be null before the route is installed. | ||
| 52 | + /// Its content value will be null if the previous route has no title or | ||
| 53 | + /// is not a [GetCupertino]. | ||
| 54 | + /// | ||
| 55 | + /// See also: | ||
| 56 | + /// | ||
| 57 | + /// * [ValueListenableBuilder], which can be used to listen and rebuild | ||
| 58 | + /// widgets based on a ValueListenable. | ||
| 59 | + ValueListenable<String> get previousTitle { | ||
| 60 | + assert( | ||
| 61 | + _previousTitle != null, | ||
| 62 | + 'Cannot read the previousTitle for a route that has not yet been installed', | ||
| 63 | + ); | ||
| 64 | + return _previousTitle; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + @override | ||
| 68 | + void didChangePrevious(Route<dynamic> previousRoute) { | ||
| 69 | + final String previousTitleString = | ||
| 70 | + previousRoute is GetCupertino ? previousRoute.title : null; | ||
| 71 | + if (_previousTitle == null) { | ||
| 72 | + _previousTitle = ValueNotifier<String>(previousTitleString); | ||
| 73 | + } else { | ||
| 74 | + _previousTitle.value = previousTitleString; | ||
| 75 | + } | ||
| 76 | + super.didChangePrevious(previousRoute); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + @override | ||
| 80 | + final bool maintainState; | ||
| 81 | + | ||
| 82 | + @override | ||
| 83 | + // A relatively rigorous eyeball estimation. | ||
| 84 | + Duration get transitionDuration => const Duration(milliseconds: 400); | ||
| 85 | + | ||
| 86 | + @override | ||
| 87 | + Color get barrierColor => null; | ||
| 88 | + | ||
| 89 | + @override | ||
| 90 | + String get barrierLabel => null; | ||
| 91 | + | ||
| 92 | + @override | ||
| 93 | + bool canTransitionTo(TransitionRoute<dynamic> nextRoute) { | ||
| 94 | + // Don't perform outgoing animation if the next route is a fullscreen dialog. | ||
| 95 | + return nextRoute is GetCupertino && !nextRoute.fullscreenDialog; | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + /// True if an iOS-style back swipe pop gesture is currently underway for [route]. | ||
| 99 | + /// | ||
| 100 | + /// This just check the route's [NavigatorState.userGestureInProgress]. | ||
| 101 | + /// | ||
| 102 | + /// See also: | ||
| 103 | + /// | ||
| 104 | + /// * [popGestureEnabled], which returns true if a user-triggered pop gesture | ||
| 105 | + /// would be allowed. | ||
| 106 | + static bool isPopGestureInProgress(PageRoute<dynamic> route) { | ||
| 107 | + return route.navigator.userGestureInProgress; | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + /// True if an iOS-style back swipe pop gesture is currently underway for this route. | ||
| 111 | + /// | ||
| 112 | + /// See also: | ||
| 113 | + /// | ||
| 114 | + /// * [isPopGestureInProgress], which returns true if a Cupertino pop gesture | ||
| 115 | + /// is currently underway for specific route. | ||
| 116 | + /// * [popGestureEnabled], which returns true if a user-triggered pop gesture | ||
| 117 | + /// would be allowed. | ||
| 118 | + bool get popGestureInProgress => isPopGestureInProgress(this); | ||
| 119 | + | ||
| 120 | + /// Whether a pop gesture can be started by the user. | ||
| 121 | + /// | ||
| 122 | + /// Returns true if the user can edge-swipe to a previous route. | ||
| 123 | + /// | ||
| 124 | + /// Returns false once [isPopGestureInProgress] is true, but | ||
| 125 | + /// [isPopGestureInProgress] can only become true if [popGestureEnabled] was | ||
| 126 | + /// true first. | ||
| 127 | + /// | ||
| 128 | + /// This should only be used between frames, not during build. | ||
| 129 | + bool get popGestureEnabled => _isPopGestureEnabled(this); | ||
| 130 | + | ||
| 131 | + static bool _isPopGestureEnabled<T>(PageRoute<T> route) { | ||
| 132 | + // If there's nothing to go back to, then obviously we don't support | ||
| 133 | + // the back gesture. | ||
| 134 | + if (route.isFirst) return false; | ||
| 135 | + // If the route wouldn't actually pop if we popped it, then the gesture | ||
| 136 | + // would be really confusing (or would skip internal routes), so disallow it. | ||
| 137 | + if (route.willHandlePopInternally) return false; | ||
| 138 | + // If attempts to dismiss this route might be vetoed such as in a page | ||
| 139 | + // with forms, then do not allow the user to dismiss the route with a swipe. | ||
| 140 | + if (route.hasScopedWillPopCallback) return false; | ||
| 141 | + // Fullscreen dialogs aren't dismissible by back swipe. | ||
| 142 | + if (route.fullscreenDialog) return false; | ||
| 143 | + // If we're in an animation already, we cannot be manually swiped. | ||
| 144 | + if (route.animation.status != AnimationStatus.completed) return false; | ||
| 145 | + // If we're being popped into, we also cannot be swiped until the pop above | ||
| 146 | + // it completes. This translates to our secondary animation being | ||
| 147 | + // dismissed. | ||
| 148 | + if (route.secondaryAnimation.status != AnimationStatus.dismissed) | ||
| 149 | + return false; | ||
| 150 | + // If we're in a gesture already, we cannot start another. | ||
| 151 | + if (isPopGestureInProgress(route)) return false; | ||
| 152 | + | ||
| 153 | + // Looks like a back gesture would be welcome! | ||
| 154 | + return true; | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + @override | ||
| 158 | + Widget buildPage(BuildContext context, Animation<double> animation, | ||
| 159 | + Animation<double> secondaryAnimation) { | ||
| 160 | + final Widget child = builder(context); | ||
| 161 | + final Widget result = Semantics( | ||
| 162 | + scopesRoute: true, | ||
| 163 | + explicitChildNodes: true, | ||
| 164 | + child: child, | ||
| 165 | + ); | ||
| 166 | + assert(() { | ||
| 167 | + if (child == null) { | ||
| 168 | + throw FlutterError.fromParts(<DiagnosticsNode>[ | ||
| 169 | + ErrorSummary( | ||
| 170 | + 'The builder for route "${settings.name}" returned null.'), | ||
| 171 | + ErrorDescription('Route builders must never return null.'), | ||
| 172 | + ]); | ||
| 173 | + } | ||
| 174 | + return true; | ||
| 175 | + }()); | ||
| 176 | + return result; | ||
| 177 | + } | ||
| 178 | + | ||
| 179 | + // Called by _CupertinoBackGestureDetector when a pop ("back") drag start | ||
| 180 | + // gesture is detected. The returned controller handles all of the subsequent | ||
| 181 | + // drag events. | ||
| 182 | + static _CupertinoBackGestureController<T> _startPopGesture<T>( | ||
| 183 | + PageRoute<T> route) { | ||
| 184 | + assert(_isPopGestureEnabled(route)); | ||
| 185 | + | ||
| 186 | + return _CupertinoBackGestureController<T>( | ||
| 187 | + navigator: route.navigator, | ||
| 188 | + controller: route.controller, // protected access | ||
| 189 | + ); | ||
| 190 | + } | ||
| 191 | + | ||
| 192 | + /// Returns a [CupertinoFullscreenDialogTransition] if [route] is a full | ||
| 193 | + /// screen dialog, otherwise a [CupertinoPageTransition] is returned. | ||
| 194 | + /// | ||
| 195 | + /// Used by [GetCupertino.buildTransitions]. | ||
| 196 | + /// | ||
| 197 | + /// This method can be applied to any [PageRoute], not just | ||
| 198 | + /// [GetCupertino]. It's typically used to provide a Cupertino style | ||
| 199 | + /// horizontal transition for material widgets when the target platform | ||
| 200 | + /// is [TargetPlatform.iOS]. | ||
| 201 | + /// | ||
| 202 | + /// See also: | ||
| 203 | + /// | ||
| 204 | + /// * [CupertinoPageTransitionsBuilder], which uses this method to define a | ||
| 205 | + /// [PageTransitionsBuilder] for the [PageTransitionsTheme]. | ||
| 206 | + static Widget buildPageTransitions<T>( | ||
| 207 | + PageRoute<T> route, | ||
| 208 | + BuildContext context, | ||
| 209 | + Animation<double> animation, | ||
| 210 | + Animation<double> secondaryAnimation, | ||
| 211 | + Widget child, | ||
| 212 | + ) { | ||
| 213 | + if (route.fullscreenDialog) { | ||
| 214 | + return CupertinoFullscreenDialogTransition( | ||
| 215 | + animation: animation, | ||
| 216 | + child: child, | ||
| 217 | + ); | ||
| 218 | + } else { | ||
| 219 | + return CupertinoPageTransition( | ||
| 220 | + primaryRouteAnimation: animation, | ||
| 221 | + secondaryRouteAnimation: secondaryAnimation, | ||
| 222 | + // Check if the route has an animation that's currently participating | ||
| 223 | + // in a back swipe gesture. | ||
| 224 | + // | ||
| 225 | + // In the middle of a back gesture drag, let the transition be linear to | ||
| 226 | + // match finger motions. | ||
| 227 | + linearTransition: isPopGestureInProgress(route), | ||
| 228 | + child: _CupertinoBackGestureDetector<T>( | ||
| 229 | + enabledCallback: () => _isPopGestureEnabled<T>(route), | ||
| 230 | + onStartPopGesture: () => _startPopGesture<T>(route), | ||
| 231 | + child: child, | ||
| 232 | + ), | ||
| 233 | + ); | ||
| 234 | + } | ||
| 235 | + } | ||
| 236 | + | ||
| 237 | + @override | ||
| 238 | + Widget buildTransitions(BuildContext context, Animation<double> animation, | ||
| 239 | + Animation<double> secondaryAnimation, Widget child) { | ||
| 240 | + return buildPageTransitions<T>( | ||
| 241 | + this, context, animation, secondaryAnimation, child); | ||
| 242 | + } | ||
| 243 | + | ||
| 244 | + @override | ||
| 245 | + String get debugLabel => '${super.debugLabel}(${settings.name})'; | ||
| 246 | +} | ||
| 247 | + | ||
| 248 | +class _CupertinoBackGestureDetector<T> extends StatefulWidget { | ||
| 249 | + const _CupertinoBackGestureDetector({ | ||
| 250 | + Key key, | ||
| 251 | + @required this.enabledCallback, | ||
| 252 | + @required this.onStartPopGesture, | ||
| 253 | + @required this.child, | ||
| 254 | + }) : assert(enabledCallback != null), | ||
| 255 | + assert(onStartPopGesture != null), | ||
| 256 | + assert(child != null), | ||
| 257 | + super(key: key); | ||
| 258 | + | ||
| 259 | + final Widget child; | ||
| 260 | + | ||
| 261 | + final ValueGetter<bool> enabledCallback; | ||
| 262 | + | ||
| 263 | + final ValueGetter<_CupertinoBackGestureController<T>> onStartPopGesture; | ||
| 264 | + | ||
| 265 | + @override | ||
| 266 | + _CupertinoBackGestureDetectorState<T> createState() => | ||
| 267 | + _CupertinoBackGestureDetectorState<T>(); | ||
| 268 | +} | ||
| 269 | + | ||
| 270 | +class _CupertinoBackGestureDetectorState<T> | ||
| 271 | + extends State<_CupertinoBackGestureDetector<T>> { | ||
| 272 | + _CupertinoBackGestureController<T> _backGestureController; | ||
| 273 | + | ||
| 274 | + HorizontalDragGestureRecognizer _recognizer; | ||
| 275 | + | ||
| 276 | + @override | ||
| 277 | + void initState() { | ||
| 278 | + super.initState(); | ||
| 279 | + _recognizer = HorizontalDragGestureRecognizer(debugOwner: this) | ||
| 280 | + ..onStart = _handleDragStart | ||
| 281 | + ..onUpdate = _handleDragUpdate | ||
| 282 | + ..onEnd = _handleDragEnd | ||
| 283 | + ..onCancel = _handleDragCancel; | ||
| 284 | + } | ||
| 285 | + | ||
| 286 | + @override | ||
| 287 | + void dispose() { | ||
| 288 | + _recognizer.dispose(); | ||
| 289 | + super.dispose(); | ||
| 290 | + } | ||
| 291 | + | ||
| 292 | + void _handleDragStart(DragStartDetails details) { | ||
| 293 | + assert(mounted); | ||
| 294 | + assert(_backGestureController == null); | ||
| 295 | + _backGestureController = widget.onStartPopGesture(); | ||
| 296 | + } | ||
| 297 | + | ||
| 298 | + void _handleDragUpdate(DragUpdateDetails details) { | ||
| 299 | + assert(mounted); | ||
| 300 | + assert(_backGestureController != null); | ||
| 301 | + _backGestureController.dragUpdate( | ||
| 302 | + _convertToLogical(details.primaryDelta / context.size.width)); | ||
| 303 | + } | ||
| 304 | + | ||
| 305 | + void _handleDragEnd(DragEndDetails details) { | ||
| 306 | + assert(mounted); | ||
| 307 | + assert(_backGestureController != null); | ||
| 308 | + _backGestureController.dragEnd(_convertToLogical( | ||
| 309 | + details.velocity.pixelsPerSecond.dx / context.size.width)); | ||
| 310 | + _backGestureController = null; | ||
| 311 | + } | ||
| 312 | + | ||
| 313 | + void _handleDragCancel() { | ||
| 314 | + assert(mounted); | ||
| 315 | + // This can be called even if start is not called, paired with the "down" event | ||
| 316 | + // that we don't consider here. | ||
| 317 | + _backGestureController?.dragEnd(0.0); | ||
| 318 | + _backGestureController = null; | ||
| 319 | + } | ||
| 320 | + | ||
| 321 | + void _handlePointerDown(PointerDownEvent event) { | ||
| 322 | + if (widget.enabledCallback()) _recognizer.addPointer(event); | ||
| 323 | + } | ||
| 324 | + | ||
| 325 | + double _convertToLogical(double value) { | ||
| 326 | + switch (Directionality.of(context)) { | ||
| 327 | + case TextDirection.rtl: | ||
| 328 | + return -value; | ||
| 329 | + case TextDirection.ltr: | ||
| 330 | + return value; | ||
| 331 | + } | ||
| 332 | + return null; | ||
| 333 | + } | ||
| 334 | + | ||
| 335 | + @override | ||
| 336 | + Widget build(BuildContext context) { | ||
| 337 | + assert(debugCheckHasDirectionality(context)); | ||
| 338 | + // For devices with notches, the drag area needs to be larger on the side | ||
| 339 | + // that has the notch. | ||
| 340 | + double dragAreaWidth = Directionality.of(context) == TextDirection.ltr | ||
| 341 | + ? MediaQuery.of(context).padding.left | ||
| 342 | + : MediaQuery.of(context).padding.right; | ||
| 343 | + dragAreaWidth = max(dragAreaWidth, _kBackGestureWidth); | ||
| 344 | + return Stack( | ||
| 345 | + fit: StackFit.passthrough, | ||
| 346 | + children: <Widget>[ | ||
| 347 | + widget.child, | ||
| 348 | + PositionedDirectional( | ||
| 349 | + start: 0.0, | ||
| 350 | + width: dragAreaWidth, | ||
| 351 | + top: 0.0, | ||
| 352 | + bottom: 0.0, | ||
| 353 | + child: Listener( | ||
| 354 | + onPointerDown: _handlePointerDown, | ||
| 355 | + behavior: HitTestBehavior.translucent, | ||
| 356 | + ), | ||
| 357 | + ), | ||
| 358 | + ], | ||
| 359 | + ); | ||
| 360 | + } | ||
| 361 | +} | ||
| 362 | + | ||
| 363 | +class _CupertinoBackGestureController<T> { | ||
| 364 | + /// Creates a controller for an iOS-style back gesture. | ||
| 365 | + /// | ||
| 366 | + /// The [navigator] and [controller] arguments must not be null. | ||
| 367 | + _CupertinoBackGestureController({ | ||
| 368 | + @required this.navigator, | ||
| 369 | + @required this.controller, | ||
| 370 | + }) : assert(navigator != null), | ||
| 371 | + assert(controller != null) { | ||
| 372 | + navigator.didStartUserGesture(); | ||
| 373 | + } | ||
| 374 | + | ||
| 375 | + final AnimationController controller; | ||
| 376 | + final NavigatorState navigator; | ||
| 377 | + | ||
| 378 | + /// The drag gesture has changed by [fractionalDelta]. The total range of the | ||
| 379 | + /// drag should be 0.0 to 1.0. | ||
| 380 | + void dragUpdate(double delta) { | ||
| 381 | + controller.value -= delta; | ||
| 382 | + } | ||
| 383 | + | ||
| 384 | + /// The drag gesture has ended with a horizontal motion of | ||
| 385 | + /// [fractionalVelocity] as a fraction of screen width per second. | ||
| 386 | + void dragEnd(double velocity) { | ||
| 387 | + // Fling in the appropriate direction. | ||
| 388 | + // AnimationController.fling is guaranteed to | ||
| 389 | + // take at least one frame. | ||
| 390 | + // | ||
| 391 | + // This curve has been determined through rigorously eyeballing native iOS | ||
| 392 | + // animations. | ||
| 393 | + const Curve animationCurve = Curves.fastLinearToSlowEaseIn; | ||
| 394 | + bool animateForward; | ||
| 395 | + | ||
| 396 | + // If the user releases the page before mid screen with sufficient velocity, | ||
| 397 | + // or after mid screen, we should animate the page out. Otherwise, the page | ||
| 398 | + // should be animated back in. | ||
| 399 | + if (velocity.abs() >= _kMinFlingVelocity) | ||
| 400 | + animateForward = velocity <= 0; | ||
| 401 | + else | ||
| 402 | + animateForward = controller.value > 0.5; | ||
| 403 | + | ||
| 404 | + if (animateForward) { | ||
| 405 | + // The closer the panel is to dismissing, the shorter the animation is. | ||
| 406 | + // We want to cap the animation time, but we want to use a linear curve | ||
| 407 | + // to determine it. | ||
| 408 | + final int droppedPageForwardAnimationTime = min( | ||
| 409 | + lerpDouble( | ||
| 410 | + _kMaxDroppedSwipePageForwardAnimationTime, 0, controller.value) | ||
| 411 | + .floor(), | ||
| 412 | + _kMaxPageBackAnimationTime, | ||
| 413 | + ); | ||
| 414 | + controller.animateTo(1.0, | ||
| 415 | + duration: Duration(milliseconds: droppedPageForwardAnimationTime), | ||
| 416 | + curve: animationCurve); | ||
| 417 | + } else { | ||
| 418 | + // This route is destined to pop at this point. Reuse navigator's pop. | ||
| 419 | + navigator.pop(); | ||
| 420 | + | ||
| 421 | + // The popping may have finished inline if already at the target destination. | ||
| 422 | + if (controller.isAnimating) { | ||
| 423 | + // Otherwise, use a custom popping animation duration and curve. | ||
| 424 | + final int droppedPageBackAnimationTime = lerpDouble( | ||
| 425 | + 0, _kMaxDroppedSwipePageForwardAnimationTime, controller.value) | ||
| 426 | + .floor(); | ||
| 427 | + controller.animateBack(0.0, | ||
| 428 | + duration: Duration(milliseconds: droppedPageBackAnimationTime), | ||
| 429 | + curve: animationCurve); | ||
| 430 | + } | ||
| 431 | + } | ||
| 432 | + | ||
| 433 | + if (controller.isAnimating) { | ||
| 434 | + // Keep the userGestureInProgress in true state so we don't change the | ||
| 435 | + // curve of the page transition mid-flight since CupertinoPageTransition | ||
| 436 | + // depends on userGestureInProgress. | ||
| 437 | + AnimationStatusListener animationStatusCallback; | ||
| 438 | + animationStatusCallback = (AnimationStatus status) { | ||
| 439 | + navigator.didStopUserGesture(); | ||
| 440 | + controller.removeStatusListener(animationStatusCallback); | ||
| 441 | + }; | ||
| 442 | + controller.addStatusListener(animationStatusCallback); | ||
| 443 | + } else { | ||
| 444 | + navigator.didStopUserGesture(); | ||
| 445 | + } | ||
| 446 | + } | ||
| 447 | +} |
| @@ -276,7 +276,7 @@ class SnackRoute<T> extends OverlayRoute<T> { | @@ -276,7 +276,7 @@ class SnackRoute<T> extends OverlayRoute<T> { | ||
| 276 | } | 276 | } |
| 277 | 277 | ||
| 278 | @override | 278 | @override |
| 279 | - void install(OverlayEntry insertionPoint) { | 279 | + void install() { |
| 280 | assert(!_transitionCompleter.isCompleted, | 280 | assert(!_transitionCompleter.isCompleted, |
| 281 | 'Cannot install a $runtimeType after disposing it.'); | 281 | 'Cannot install a $runtimeType after disposing it.'); |
| 282 | _controller = createAnimationController(); | 282 | _controller = createAnimationController(); |
| @@ -286,7 +286,7 @@ class SnackRoute<T> extends OverlayRoute<T> { | @@ -286,7 +286,7 @@ class SnackRoute<T> extends OverlayRoute<T> { | ||
| 286 | _filterColorAnimation = createColorFilterAnimation(); | 286 | _filterColorAnimation = createColorFilterAnimation(); |
| 287 | _animation = createAnimation(); | 287 | _animation = createAnimation(); |
| 288 | assert(_animation != null, '$runtimeType.createAnimation() returned null.'); | 288 | assert(_animation != null, '$runtimeType.createAnimation() returned null.'); |
| 289 | - super.install(insertionPoint); | 289 | + super.install(); |
| 290 | } | 290 | } |
| 291 | 291 | ||
| 292 | @override | 292 | @override |
| @@ -95,13 +95,6 @@ packages: | @@ -95,13 +95,6 @@ packages: | ||
| 95 | url: "https://pub.dartlang.org" | 95 | url: "https://pub.dartlang.org" |
| 96 | source: hosted | 96 | source: hosted |
| 97 | version: "1.6.4" | 97 | version: "1.6.4" |
| 98 | - pedantic: | ||
| 99 | - dependency: transitive | ||
| 100 | - description: | ||
| 101 | - name: pedantic | ||
| 102 | - url: "https://pub.dartlang.org" | ||
| 103 | - source: hosted | ||
| 104 | - version: "1.8.0+1" | ||
| 105 | petitparser: | 98 | petitparser: |
| 106 | dependency: transitive | 99 | dependency: transitive |
| 107 | description: | 100 | description: |
| @@ -162,7 +155,7 @@ packages: | @@ -162,7 +155,7 @@ packages: | ||
| 162 | name: test_api | 155 | name: test_api |
| 163 | url: "https://pub.dartlang.org" | 156 | url: "https://pub.dartlang.org" |
| 164 | source: hosted | 157 | source: hosted |
| 165 | - version: "0.2.11" | 158 | + version: "0.2.15" |
| 166 | typed_data: | 159 | typed_data: |
| 167 | dependency: transitive | 160 | dependency: transitive |
| 168 | description: | 161 | description: |
| 1 | name: get | 1 | name: get |
| 2 | description: A consistent navigation library that lets you navigate between screens, open dialogs, and display snackbars easily with no context. | 2 | description: A consistent navigation library that lets you navigate between screens, open dialogs, and display snackbars easily with no context. |
| 3 | -version: 1.11.1 | 3 | +version: 1.12.0-dev |
| 4 | homepage: https://github.com/jonataslaw/get | 4 | homepage: https://github.com/jonataslaw/get |
| 5 | 5 | ||
| 6 | environment: | 6 | environment: |
-
Please register or login to post a comment