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