Line data Source code
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 : import 'package:flutter/material.dart';
8 : import 'package:get/src/get_main.dart';
9 : import 'package:get/src/routes/bindings_interface.dart';
10 :
11 : import '../platform/platform.dart';
12 : import 'transitions_type.dart';
13 :
14 : const double _kBackGestureWidth = 20.0;
15 : const double _kMinFlingVelocity = 1.0;
16 : const int _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds.
17 :
18 : // The maximum time for a page to get reset to it's original position if the
19 : // user releases a page mid swipe.
20 : const int _kMaxPageBackAnimationTime = 300;
21 :
22 : class GetRouteBase<T> extends PageRoute<T> {
23 : /// The [builder], [maintainState], and [fullscreenDialog] arguments must not
24 : /// be null.
25 1 : GetRouteBase({
26 : @required this.page,
27 : this.title,
28 : RouteSettings settings,
29 : this.maintainState = true,
30 : this.curve = Curves.linear,
31 : this.alignment,
32 : this.parameter,
33 : this.binding,
34 : this.bindings,
35 : this.opaque = true,
36 : this.transitionDuration = const Duration(milliseconds: 400),
37 : this.popGesture,
38 : this.transition,
39 : // this.duration = const Duration(milliseconds: 400),
40 : bool fullscreenDialog = false,
41 0 : }) : assert(page != null),
42 0 : assert(maintainState != null),
43 0 : assert(fullscreenDialog != null),
44 : // assert(opaque),
45 1 : super(settings: settings, fullscreenDialog: fullscreenDialog) {
46 : /// prebuild dependencies
47 1 : if (binding != null) {
48 0 : binding.dependencies();
49 : }
50 1 : if (bindings != null) {
51 0 : bindings.forEach((element) => element.dependencies());
52 : }
53 : }
54 :
55 : /// Builds the primary contents of the route.
56 : final Widget page;
57 :
58 : final bool popGesture;
59 :
60 : final Bindings binding;
61 :
62 : final List<Bindings> bindings;
63 :
64 : // final Duration duration;
65 :
66 : final Map<String, String> parameter;
67 :
68 : final String title;
69 :
70 : final Transition transition;
71 :
72 : final Curve curve;
73 :
74 : final Alignment alignment;
75 :
76 : ValueNotifier<String> _previousTitle;
77 :
78 : /// The title string of the previous [GetRoute].
79 : ///
80 : /// The [ValueListenable]'s value is readable after the route is installed
81 : /// onto a [Navigator]. The [ValueListenable] will also notify its listeners
82 : /// if the value changes (such as by replacing the previous route).
83 : ///
84 : /// The [ValueListenable] itself will be null before the route is installed.
85 : /// Its content value will be null if the previous route has no title or
86 : /// is not a [GetRoute].
87 : ///
88 : /// See also:
89 : ///
90 : /// * [ValueListenableBuilder], which can be used to listen and rebuild
91 : /// widgets based on a ValueListenable.
92 0 : ValueListenable<String> get previousTitle {
93 : assert(
94 0 : _previousTitle != null,
95 : 'Cannot read the previousTitle for a route that has not yet been installed',
96 : );
97 0 : return _previousTitle;
98 : }
99 :
100 1 : @override
101 : void didChangePrevious(Route<dynamic> previousRoute) {
102 : final String previousTitleString =
103 2 : previousRoute is GetRouteBase ? previousRoute.title : null;
104 1 : if (_previousTitle == null) {
105 2 : _previousTitle = ValueNotifier<String>(previousTitleString);
106 : } else {
107 2 : _previousTitle.value = previousTitleString;
108 : }
109 1 : super.didChangePrevious(previousRoute);
110 : }
111 :
112 : @override
113 : final bool maintainState;
114 :
115 : /// Allows you to set opaque to false to prevent route reconstruction.
116 : @override
117 : final bool opaque;
118 :
119 : @override
120 : final Duration transitionDuration;
121 :
122 1 : @override
123 : Color get barrierColor => null; //Color(0x00FFFFFF);
124 :
125 1 : @override
126 : String get barrierLabel => null;
127 :
128 1 : @override
129 : bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
130 : // Don't perform outgoing animation if the next route is a fullscreen dialog.
131 2 : return nextRoute is GetRouteBase && !nextRoute.fullscreenDialog;
132 : }
133 :
134 : /// True if an iOS-style back swipe pop gesture is currently underway for [route].
135 : ///
136 : /// This just check the route's [NavigatorState.userGestureInProgress].
137 : ///
138 : /// See also:
139 : ///
140 : /// * [popGestureEnabled], which returns true if a user-triggered pop gesture
141 : /// would be allowed.
142 1 : static bool isPopGestureInProgress(PageRoute<dynamic> route) {
143 2 : return route.navigator.userGestureInProgress;
144 : }
145 :
146 : /// True if an iOS-style back swipe pop gesture is currently underway for this route.
147 : ///
148 : /// See also:
149 : ///
150 : /// * [isPopGestureInProgress], which returns true if a Cupertino pop gesture
151 : /// is currently underway for specific route.
152 : /// * [popGestureEnabled], which returns true if a user-triggered pop gesture
153 : /// would be allowed.
154 0 : bool get popGestureInProgress => isPopGestureInProgress(this);
155 :
156 : /// Whether a pop gesture can be started by the user.
157 : ///
158 : /// Returns true if the user can edge-swipe to a previous route.
159 : ///
160 : /// Returns false once [isPopGestureInProgress] is true, but
161 : /// [isPopGestureInProgress] can only become true if [popGestureEnabled] was
162 : /// true first.
163 : ///
164 : /// This should only be used between frames, not during build.
165 0 : bool get popGestureEnabled => _isPopGestureEnabled(this);
166 :
167 0 : static bool _isPopGestureEnabled<T>(PageRoute<T> route) {
168 : // If there's nothing to go back to, then obviously we don't support
169 : // the back gesture.
170 0 : if (route.isFirst) return false;
171 : // If the route wouldn't actually pop if we popped it, then the gesture
172 : // would be really confusing (or would skip internal routes), so disallow it.
173 0 : if (route.willHandlePopInternally) return false;
174 : // If attempts to dismiss this route might be vetoed such as in a page
175 : // with forms, then do not allow the user to dismiss the route with a swipe.
176 0 : if (route.hasScopedWillPopCallback) return false;
177 : // Fullscreen dialogs aren't dismissible by back swipe.
178 0 : if (route.fullscreenDialog) return false;
179 : // If we're in an animation already, we cannot be manually swiped.
180 0 : if (route.animation.status != AnimationStatus.completed) return false;
181 : // If we're being popped into, we also cannot be swiped until the pop above
182 : // it completes. This translates to our secondary animation being
183 : // dismissed.
184 0 : if (route.secondaryAnimation.status != AnimationStatus.dismissed)
185 : return false;
186 : // If we're in a gesture already, we cannot start another.
187 0 : if (isPopGestureInProgress(route)) return false;
188 :
189 : // Looks like a back gesture would be welcome!
190 : return true;
191 : }
192 :
193 1 : @override
194 : Widget buildPage(BuildContext context, Animation<double> animation,
195 : Animation<double> secondaryAnimation) {
196 1 : final Widget child = page;
197 1 : final Widget result = Semantics(
198 : scopesRoute: true,
199 : explicitChildNodes: true,
200 : child: child,
201 : );
202 1 : assert(() {
203 : if (child == null) {
204 0 : throw FlutterError.fromParts(<DiagnosticsNode>[
205 0 : ErrorSummary(
206 0 : 'The builder for route "${settings.name}" returned null.'),
207 0 : ErrorDescription('Route builders must never return null.'),
208 : ]);
209 : }
210 : return true;
211 1 : }());
212 : return result;
213 : }
214 :
215 : // Called by _CupertinoBackGestureDetector when a pop ("back") drag start
216 : // gesture is detected. The returned controller handles all of the subsequent
217 : // drag events.
218 0 : static _CupertinoBackGestureController<T> _startPopGesture<T>(
219 : PageRoute<T> route) {
220 0 : assert(_isPopGestureEnabled(route));
221 :
222 0 : return _CupertinoBackGestureController<T>(
223 0 : navigator: route.navigator,
224 0 : controller: route.controller, // protected access
225 : );
226 : }
227 :
228 : /// Returns a [CupertinoFullscreenDialogTransition] if [route] is a full
229 : /// screen dialog, otherwise a [CupertinoPageTransition] is returned.
230 : ///
231 : /// Used by [GetRoute.buildTransitions].
232 : ///
233 : /// This method can be applied to any [PageRoute], not just
234 : /// [GetRoute]. It's typically used to provide a Cupertino style
235 : /// horizontal transition for material widgets when the target platform
236 : /// is [TargetPlatform.iOS].
237 : ///
238 : /// See also:
239 : ///
240 : /// * [CupertinoPageTransitionsBuilder], which uses this method to define a
241 : /// [PageTransitionsBuilder] for the [PageTransitionsTheme].
242 1 : static Widget buildPageTransitions<T>(
243 : PageRoute<T> route,
244 : BuildContext context,
245 : bool popGesture,
246 : Animation<double> animation,
247 : Animation<double> secondaryAnimation,
248 : Widget child,
249 : Transition tr,
250 : Curve curve,
251 : Alignment alignment,
252 : ) {
253 1 : Transition transition = (tr ?? Get.defaultTransition);
254 :
255 1 : if (route.fullscreenDialog) {
256 0 : final bool linearTransition = isPopGestureInProgress(route);
257 0 : return CupertinoFullscreenDialogTransition(
258 : primaryRouteAnimation: animation,
259 : secondaryRouteAnimation: secondaryAnimation,
260 : child: child,
261 : linearTransition: linearTransition,
262 : );
263 : } else {
264 : switch (transition) {
265 1 : case Transition.fade:
266 : final PageTransitionsBuilder matchingBuilder =
267 1 : FadeUpwardsPageTransitionsBuilder();
268 1 : return matchingBuilder.buildTransitions<T>(
269 : route,
270 : context,
271 : animation,
272 : secondaryAnimation,
273 : popGesture
274 0 : ? _CupertinoBackGestureDetector<T>(
275 0 : enabledCallback: () => _isPopGestureEnabled<T>(route),
276 0 : onStartPopGesture: () => _startPopGesture<T>(route),
277 : child: child)
278 : : child);
279 : break;
280 1 : case Transition.rightToLeft:
281 0 : return SlideTransition(
282 : transformHitTests: false,
283 0 : position: new Tween<Offset>(
284 : begin: const Offset(1.0, 0.0),
285 : end: Offset.zero,
286 0 : ).animate(animation),
287 0 : child: new SlideTransition(
288 0 : position: new Tween<Offset>(
289 : begin: Offset.zero,
290 : end: const Offset(-1.0, 0.0),
291 0 : ).animate(secondaryAnimation),
292 : child: popGesture
293 0 : ? _CupertinoBackGestureDetector<T>(
294 0 : enabledCallback: () => _isPopGestureEnabled<T>(route),
295 0 : onStartPopGesture: () => _startPopGesture<T>(route),
296 : child: child)
297 : : child),
298 : );
299 : break;
300 1 : case Transition.leftToRight:
301 0 : return SlideTransition(
302 : transformHitTests: false,
303 0 : position: Tween<Offset>(
304 : begin: const Offset(-1.0, 0.0),
305 : end: Offset.zero,
306 0 : ).animate(animation),
307 0 : child: new SlideTransition(
308 0 : position: new Tween<Offset>(
309 : begin: Offset.zero,
310 : end: const Offset(1.0, 0.0),
311 0 : ).animate(secondaryAnimation),
312 : child: popGesture
313 0 : ? _CupertinoBackGestureDetector<T>(
314 0 : enabledCallback: () => _isPopGestureEnabled<T>(route),
315 0 : onStartPopGesture: () => _startPopGesture<T>(route),
316 : child: child)
317 : : child),
318 : );
319 : break;
320 1 : case Transition.upToDown:
321 0 : return SlideTransition(
322 : transformHitTests: false,
323 0 : position: Tween<Offset>(
324 : begin: const Offset(0.0, -1.0),
325 : end: Offset.zero,
326 0 : ).animate(animation),
327 0 : child: new SlideTransition(
328 0 : position: new Tween<Offset>(
329 : begin: Offset.zero,
330 : end: const Offset(0.0, 1.0),
331 0 : ).animate(secondaryAnimation),
332 : child: popGesture
333 0 : ? _CupertinoBackGestureDetector<T>(
334 0 : enabledCallback: () => _isPopGestureEnabled<T>(route),
335 0 : onStartPopGesture: () => _startPopGesture<T>(route),
336 : child: child)
337 : : child),
338 : );
339 : break;
340 1 : case Transition.downToUp:
341 0 : return SlideTransition(
342 : transformHitTests: false,
343 0 : position: Tween<Offset>(
344 : begin: const Offset(0.0, 1.0),
345 : end: Offset.zero,
346 0 : ).animate(animation),
347 0 : child: new SlideTransition(
348 0 : position: new Tween<Offset>(
349 : begin: Offset.zero,
350 : end: const Offset(0.0, -1.0),
351 0 : ).animate(secondaryAnimation),
352 : child: popGesture
353 0 : ? _CupertinoBackGestureDetector<T>(
354 0 : enabledCallback: () => _isPopGestureEnabled<T>(route),
355 0 : onStartPopGesture: () => _startPopGesture<T>(route),
356 : child: child)
357 : : child),
358 : );
359 : break;
360 1 : case Transition.scale:
361 0 : return ScaleTransition(
362 : alignment: alignment,
363 0 : scale: CurvedAnimation(
364 : parent: animation,
365 0 : curve: Interval(
366 : 0.00,
367 : 0.50,
368 : curve: curve,
369 : ),
370 : ),
371 : child: popGesture
372 0 : ? _CupertinoBackGestureDetector<T>(
373 0 : enabledCallback: () => _isPopGestureEnabled<T>(route),
374 0 : onStartPopGesture: () => _startPopGesture<T>(route),
375 : child: child)
376 : : child);
377 : break;
378 1 : case Transition.rotate:
379 0 : return RotationTransition(
380 : alignment: alignment,
381 : turns: animation,
382 0 : child: ScaleTransition(
383 : alignment: alignment,
384 : scale: animation,
385 0 : child: FadeTransition(
386 : opacity: animation,
387 : child: popGesture
388 0 : ? _CupertinoBackGestureDetector<T>(
389 0 : enabledCallback: () => _isPopGestureEnabled<T>(route),
390 0 : onStartPopGesture: () => _startPopGesture<T>(route),
391 : child: child)
392 : : child),
393 : ),
394 : );
395 : break;
396 :
397 1 : case Transition.rightToLeftWithFade:
398 0 : return SlideTransition(
399 0 : position: Tween<Offset>(
400 : begin: const Offset(1.0, 0.0),
401 : end: Offset.zero,
402 0 : ).animate(animation),
403 0 : child: FadeTransition(
404 : opacity: animation,
405 0 : child: SlideTransition(
406 0 : position: Tween<Offset>(
407 : begin: Offset.zero,
408 : end: const Offset(-1.0, 0.0),
409 0 : ).animate(secondaryAnimation),
410 : child: popGesture
411 0 : ? _CupertinoBackGestureDetector<T>(
412 0 : enabledCallback: () => _isPopGestureEnabled<T>(route),
413 0 : onStartPopGesture: () => _startPopGesture<T>(route),
414 : child: child)
415 : : child),
416 : ),
417 : );
418 : break;
419 1 : case Transition.leftToRightWithFade:
420 0 : return SlideTransition(
421 0 : position: Tween<Offset>(
422 : begin: const Offset(-1.0, 0.0),
423 : end: Offset.zero,
424 0 : ).animate(animation),
425 0 : child: FadeTransition(
426 : opacity: animation,
427 0 : child: SlideTransition(
428 0 : position: Tween<Offset>(
429 : begin: Offset.zero,
430 : end: const Offset(1.0, 0.0),
431 0 : ).animate(secondaryAnimation),
432 : child: popGesture
433 0 : ? _CupertinoBackGestureDetector<T>(
434 0 : enabledCallback: () => _isPopGestureEnabled<T>(route),
435 0 : onStartPopGesture: () => _startPopGesture<T>(route),
436 : child: child)
437 : : child),
438 : ),
439 : );
440 : break;
441 1 : case Transition.cupertino:
442 1 : return CupertinoPageTransition(
443 : primaryRouteAnimation: animation,
444 : secondaryRouteAnimation: secondaryAnimation,
445 : // Check if the route has an animation that's currently participating
446 : // in a back swipe gesture.
447 : //
448 : // In the middle of a back gesture drag, let the transition be linear to
449 : // match finger motions.
450 1 : linearTransition: isPopGestureInProgress(route),
451 1 : child: _CupertinoBackGestureDetector<T>(
452 0 : enabledCallback: () => _isPopGestureEnabled<T>(route),
453 0 : onStartPopGesture: () => _startPopGesture<T>(route),
454 : child: child,
455 : ),
456 : );
457 : break;
458 : default:
459 0 : return CupertinoPageTransition(
460 : primaryRouteAnimation: animation,
461 : secondaryRouteAnimation: secondaryAnimation,
462 : // Check if the route has an animation that's currently participating
463 : // in a back swipe gesture.
464 : //
465 : // In the middle of a back gesture drag, let the transition be linear to
466 : // match finger motions.
467 0 : linearTransition: isPopGestureInProgress(route),
468 : child: popGesture
469 0 : ? _CupertinoBackGestureDetector<T>(
470 0 : enabledCallback: () => _isPopGestureEnabled<T>(route),
471 0 : onStartPopGesture: () => _startPopGesture<T>(route),
472 : child: child)
473 : : child,
474 : );
475 : }
476 : }
477 : }
478 :
479 1 : @override
480 : Widget buildTransitions(BuildContext context, Animation<double> animation,
481 : Animation<double> secondaryAnimation, Widget child) {
482 1 : return buildPageTransitions<T>(
483 : this,
484 : context,
485 2 : popGesture ?? GetPlatform.isIOS,
486 : animation,
487 : secondaryAnimation,
488 : child,
489 1 : transition,
490 1 : curve,
491 1 : alignment);
492 : }
493 :
494 1 : @override
495 4 : String get debugLabel => '${super.debugLabel}(${settings.name})';
496 : }
497 :
498 : class _CupertinoBackGestureDetector<T> extends StatefulWidget {
499 1 : const _CupertinoBackGestureDetector({
500 : Key key,
501 : @required this.enabledCallback,
502 : @required this.onStartPopGesture,
503 : @required this.child,
504 0 : }) : assert(enabledCallback != null),
505 0 : assert(onStartPopGesture != null),
506 0 : assert(child != null),
507 1 : super(key: key);
508 :
509 : final Widget child;
510 :
511 : final ValueGetter<bool> enabledCallback;
512 :
513 : final ValueGetter<_CupertinoBackGestureController<T>> onStartPopGesture;
514 :
515 1 : @override
516 : _CupertinoBackGestureDetectorState<T> createState() =>
517 1 : _CupertinoBackGestureDetectorState<T>();
518 : }
519 :
520 : class _CupertinoBackGestureDetectorState<T>
521 : extends State<_CupertinoBackGestureDetector<T>> {
522 : _CupertinoBackGestureController<T> _backGestureController;
523 :
524 : HorizontalDragGestureRecognizer _recognizer;
525 :
526 1 : @override
527 : void initState() {
528 1 : super.initState();
529 2 : _recognizer = HorizontalDragGestureRecognizer(debugOwner: this)
530 2 : ..onStart = _handleDragStart
531 2 : ..onUpdate = _handleDragUpdate
532 2 : ..onEnd = _handleDragEnd
533 2 : ..onCancel = _handleDragCancel;
534 : }
535 :
536 1 : @override
537 : void dispose() {
538 2 : _recognizer.dispose();
539 1 : super.dispose();
540 : }
541 :
542 0 : void _handleDragStart(DragStartDetails details) {
543 0 : assert(mounted);
544 0 : assert(_backGestureController == null);
545 0 : _backGestureController = widget.onStartPopGesture();
546 : }
547 :
548 0 : void _handleDragUpdate(DragUpdateDetails details) {
549 0 : assert(mounted);
550 0 : assert(_backGestureController != null);
551 0 : _backGestureController.dragUpdate(
552 0 : _convertToLogical(details.primaryDelta / context.size.width));
553 : }
554 :
555 0 : void _handleDragEnd(DragEndDetails details) {
556 0 : assert(mounted);
557 0 : assert(_backGestureController != null);
558 0 : _backGestureController.dragEnd(_convertToLogical(
559 0 : details.velocity.pixelsPerSecond.dx / context.size.width));
560 0 : _backGestureController = null;
561 : }
562 :
563 0 : void _handleDragCancel() {
564 0 : assert(mounted);
565 : // This can be called even if start is not called, paired with the "down" event
566 : // that we don't consider here.
567 0 : _backGestureController?.dragEnd(0.0);
568 0 : _backGestureController = null;
569 : }
570 :
571 0 : void _handlePointerDown(PointerDownEvent event) {
572 0 : if (widget.enabledCallback()) _recognizer.addPointer(event);
573 : }
574 :
575 0 : double _convertToLogical(double value) {
576 0 : switch (Directionality.of(context)) {
577 0 : case TextDirection.rtl:
578 0 : return -value;
579 0 : case TextDirection.ltr:
580 : return value;
581 : }
582 : return null;
583 : }
584 :
585 1 : @override
586 : Widget build(BuildContext context) {
587 1 : assert(debugCheckHasDirectionality(context));
588 : // For devices with notches, the drag area needs to be larger on the side
589 : // that has the notch.
590 2 : double dragAreaWidth = Directionality.of(context) == TextDirection.ltr
591 3 : ? MediaQuery.of(context).padding.left
592 0 : : MediaQuery.of(context).padding.right;
593 13 : dragAreaWidth = max(dragAreaWidth, _kBackGestureWidth);
594 1 : return Stack(
595 : fit: StackFit.passthrough,
596 1 : children: <Widget>[
597 2 : widget.child,
598 1 : PositionedDirectional(
599 : start: 0.0,
600 : width: dragAreaWidth,
601 : top: 0.0,
602 : bottom: 0.0,
603 1 : child: Listener(
604 1 : onPointerDown: _handlePointerDown,
605 : behavior: HitTestBehavior.translucent,
606 : ),
607 : ),
608 : ],
609 : );
610 : }
611 : }
612 :
613 : class _CupertinoBackGestureController<T> {
614 : /// Creates a controller for an iOS-style back gesture.
615 : ///
616 : /// The [navigator] and [controller] arguments must not be null.
617 0 : _CupertinoBackGestureController({
618 : @required this.navigator,
619 : @required this.controller,
620 0 : }) : assert(navigator != null),
621 0 : assert(controller != null) {
622 0 : navigator.didStartUserGesture();
623 : }
624 :
625 : final AnimationController controller;
626 : final NavigatorState navigator;
627 :
628 : /// The drag gesture has changed by [fractionalDelta]. The total range of the
629 : /// drag should be 0.0 to 1.0.
630 0 : void dragUpdate(double delta) {
631 0 : controller.value -= delta;
632 : }
633 :
634 : /// The drag gesture has ended with a horizontal motion of
635 : /// [fractionalVelocity] as a fraction of screen width per second.
636 0 : void dragEnd(double velocity) {
637 : // Fling in the appropriate direction.
638 : // AnimationController.fling is guaranteed to
639 : // take at least one frame.
640 : //
641 : // This curve has been determined through rigorously eyeballing native iOS
642 : // animations.
643 : const Curve animationCurve = Curves.fastLinearToSlowEaseIn;
644 : bool animateForward;
645 :
646 : // If the user releases the page before mid screen with sufficient velocity,
647 : // or after mid screen, we should animate the page out. Otherwise, the page
648 : // should be animated back in.
649 0 : if (velocity.abs() >= _kMinFlingVelocity)
650 0 : animateForward = velocity <= 0;
651 : else
652 0 : animateForward = controller.value > 0.5;
653 :
654 : if (animateForward) {
655 : // The closer the panel is to dismissing, the shorter the animation is.
656 : // We want to cap the animation time, but we want to use a linear curve
657 : // to determine it.
658 13 : final int droppedPageForwardAnimationTime = min(
659 0 : lerpDouble(
660 0 : _kMaxDroppedSwipePageForwardAnimationTime, 0, controller.value)
661 0 : .floor(),
662 : _kMaxPageBackAnimationTime,
663 : );
664 0 : controller.animateTo(1.0,
665 0 : duration: Duration(milliseconds: droppedPageForwardAnimationTime),
666 : curve: animationCurve);
667 : } else {
668 : // This route is destined to pop at this point. Reuse navigator's pop.
669 0 : navigator.pop();
670 :
671 : // The popping may have finished inline if already at the target destination.
672 0 : if (controller.isAnimating) {
673 : // Otherwise, use a custom popping animation duration and curve.
674 0 : final int droppedPageBackAnimationTime = lerpDouble(
675 0 : 0, _kMaxDroppedSwipePageForwardAnimationTime, controller.value)
676 0 : .floor();
677 0 : controller.animateBack(0.0,
678 0 : duration: Duration(milliseconds: droppedPageBackAnimationTime),
679 : curve: animationCurve);
680 : }
681 : }
682 :
683 0 : if (controller.isAnimating) {
684 : // Keep the userGestureInProgress in true state so we don't change the
685 : // curve of the page transition mid-flight since CupertinoPageTransition
686 : // depends on userGestureInProgress.
687 : AnimationStatusListener animationStatusCallback;
688 0 : animationStatusCallback = (AnimationStatus status) {
689 0 : navigator.didStopUserGesture();
690 0 : controller.removeStatusListener(animationStatusCallback);
691 : };
692 0 : controller.addStatusListener(animationStatusCallback);
693 : } else {
694 0 : navigator.didStopUserGesture();
695 : }
696 : }
697 : }
|