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