LCOV - code coverage report
Current view: top level - src/snackbar - snack_route.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 104 197 52.8 %
Date: 2020-06-02 18:55:34 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:async';
       2             : import 'dart:ui';
       3             : import 'snack.dart';
       4             : import 'package:flutter/material.dart';
       5             : import 'package:flutter/scheduler.dart';
       6             : 
       7             : class SnackRoute<T> extends OverlayRoute<T> {
       8             :   Animation<double> _filterBlurAnimation;
       9             :   Animation<Color> _filterColorAnimation;
      10             : 
      11           1 :   SnackRoute({
      12             :     @required this.snack,
      13             :     RouteSettings settings,
      14           1 :   }) : super(settings: settings) {
      15           2 :     this._builder = Builder(builder: (BuildContext innerContext) {
      16           0 :       return GestureDetector(
      17           0 :         child: snack,
      18           0 :         onTap: snack.onTap != null
      19           0 :             ? () {
      20           0 :                 snack.onTap(snack);
      21             :               }
      22             :             : null,
      23             :       );
      24             :     });
      25             : 
      26           3 :     _configureAlignment(this.snack.snackPosition);
      27           3 :     _onStatusChanged = snack.onStatusChanged;
      28             :   }
      29             : 
      30           1 :   _configureAlignment(SnackPosition snackPosition) {
      31           2 :     switch (snack.snackPosition) {
      32           1 :       case SnackPosition.TOP:
      33             :         {
      34           4 :           _initialAlignment = Alignment(-1.0, -2.0);
      35           4 :           _endAlignment = Alignment(-1.0, -1.0);
      36             :           break;
      37             :         }
      38           1 :       case SnackPosition.BOTTOM:
      39             :         {
      40           3 :           _initialAlignment = Alignment(-1.0, 2.0);
      41           3 :           _endAlignment = Alignment(-1.0, 1.0);
      42             :           break;
      43             :         }
      44             :     }
      45             :   }
      46             : 
      47             :   GetBar snack;
      48             :   Builder _builder;
      49             : 
      50           0 :   Future<T> get completed => _transitionCompleter.future;
      51             :   final Completer<T> _transitionCompleter = Completer<T>();
      52             : 
      53             :   SnackStatusCallback _onStatusChanged;
      54             :   Alignment _initialAlignment;
      55             :   Alignment _endAlignment;
      56             :   bool _wasDismissedBySwipe = false;
      57             : 
      58             :   Timer _timer;
      59             : 
      60           1 :   bool get opaque => false;
      61             : 
      62           1 :   @override
      63             :   Iterable<OverlayEntry> createOverlayEntries() {
      64           1 :     List<OverlayEntry> overlays = [];
      65             : 
      66           3 :     if (snack.overlayBlur > 0.0) {
      67           0 :       overlays.add(
      68           0 :         OverlayEntry(
      69           0 :             builder: (BuildContext context) {
      70           0 :               return GestureDetector(
      71           0 :                 onTap: snack.isDismissible ? () => snack.dismiss() : null,
      72           0 :                 child: AnimatedBuilder(
      73           0 :                   animation: _filterBlurAnimation,
      74           0 :                   builder: (context, child) {
      75           0 :                     return BackdropFilter(
      76           0 :                       filter: ImageFilter.blur(
      77           0 :                           sigmaX: _filterBlurAnimation.value,
      78           0 :                           sigmaY: _filterBlurAnimation.value),
      79           0 :                       child: Container(
      80           0 :                         constraints: BoxConstraints.expand(),
      81           0 :                         color: _filterColorAnimation.value,
      82             :                       ),
      83             :                     );
      84             :                   },
      85             :                 ),
      86             :               );
      87             :             },
      88             :             maintainState: false,
      89           0 :             opaque: opaque),
      90             :       );
      91             :     }
      92             : 
      93           1 :     overlays.add(
      94           1 :       OverlayEntry(
      95           0 :           builder: (BuildContext context) {
      96           0 :             final Widget annotatedChild = Semantics(
      97           0 :               child: AlignTransition(
      98           0 :                 alignment: _animation,
      99           0 :                 child: snack.isDismissible
     100           0 :                     ? _getDismissibleSnack(_builder)
     101           0 :                     : _getSnack(),
     102             :               ),
     103             :               focused: false,
     104             :               container: true,
     105             :               explicitChildNodes: true,
     106             :             );
     107             :             return annotatedChild;
     108             :           },
     109             :           maintainState: false,
     110           1 :           opaque: opaque),
     111             :     );
     112             : 
     113             :     return overlays;
     114             :   }
     115             : 
     116             :   /// This string is a workaround until Dismissible supports a returning item
     117             :   String dismissibleKeyGen = "";
     118             : 
     119           0 :   Widget _getDismissibleSnack(Widget child) {
     120           0 :     return Dismissible(
     121           0 :       direction: _getDismissDirection(),
     122             :       resizeDuration: null,
     123           0 :       confirmDismiss: (_) {
     124           0 :         if (currentStatus == SnackStatus.IS_APPEARING ||
     125           0 :             currentStatus == SnackStatus.IS_HIDING) {
     126           0 :           return Future.value(false);
     127             :         }
     128           0 :         return Future.value(true);
     129             :       },
     130           0 :       key: Key(dismissibleKeyGen),
     131           0 :       onDismissed: (_) {
     132           0 :         dismissibleKeyGen += "1";
     133           0 :         _cancelTimer();
     134           0 :         _wasDismissedBySwipe = true;
     135             : 
     136           0 :         if (isCurrent) {
     137           0 :           navigator.pop();
     138             :         } else {
     139           0 :           navigator.removeRoute(this);
     140             :         }
     141             :       },
     142           0 :       child: _getSnack(),
     143             :     );
     144             :   }
     145             : 
     146           0 :   Widget _getSnack() {
     147           0 :     return Container(
     148           0 :       margin: snack.margin,
     149           0 :       child: _builder,
     150             :     );
     151             :   }
     152             : 
     153           0 :   DismissDirection _getDismissDirection() {
     154           0 :     if (snack.dismissDirection == SnackDismissDirection.HORIZONTAL) {
     155             :       return DismissDirection.horizontal;
     156             :     } else {
     157           0 :       if (snack.snackPosition == SnackPosition.TOP) {
     158             :         return DismissDirection.up;
     159             :       } else {
     160             :         return DismissDirection.down;
     161             :       }
     162             :     }
     163             :   }
     164             : 
     165           1 :   @override
     166             :   bool get finishedWhenPopped =>
     167           3 :       _controller.status == AnimationStatus.dismissed;
     168             : 
     169             :   /// The animation that drives the route's transition and the previous route's
     170             :   /// forward transition.
     171           0 :   Animation<Alignment> get animation => _animation;
     172             :   Animation<Alignment> _animation;
     173             : 
     174             :   /// The animation controller that the route uses to drive the transitions.
     175             :   ///
     176             :   /// The animation itself is exposed by the [animation] property.
     177           0 :   @protected
     178           0 :   AnimationController get controller => _controller;
     179             :   AnimationController _controller;
     180             : 
     181             :   /// Called to create the animation controller that will drive the transitions to
     182             :   /// this route from the previous one, and back to the previous route from this
     183             :   /// one.
     184           1 :   AnimationController createAnimationController() {
     185           2 :     assert(!_transitionCompleter.isCompleted,
     186           0 :         'Cannot reuse a $runtimeType after disposing it.');
     187           2 :     assert(snack.animationDuration != null &&
     188           3 :         snack.animationDuration >= Duration.zero);
     189           1 :     return AnimationController(
     190           2 :       duration: snack.animationDuration,
     191           1 :       debugLabel: debugLabel,
     192           1 :       vsync: navigator,
     193             :     );
     194             :   }
     195             : 
     196             :   /// Called to create the animation that exposes the current progress of
     197             :   /// the transition controlled by the animation controller created by
     198             :   /// [createAnimationController()].
     199           1 :   Animation<Alignment> createAnimation() {
     200           2 :     assert(!_transitionCompleter.isCompleted,
     201           0 :         'Cannot reuse a $runtimeType after disposing it.');
     202           1 :     assert(_controller != null);
     203           4 :     return AlignmentTween(begin: _initialAlignment, end: _endAlignment).animate(
     204           1 :       CurvedAnimation(
     205           1 :         parent: _controller,
     206           2 :         curve: snack.forwardAnimationCurve,
     207           2 :         reverseCurve: snack.reverseAnimationCurve,
     208             :       ),
     209             :     );
     210             :   }
     211             : 
     212           1 :   Animation<double> createBlurFilterAnimation() {
     213           4 :     return Tween(begin: 0.0, end: snack.overlayBlur).animate(
     214           1 :       CurvedAnimation(
     215           1 :         parent: _controller,
     216           1 :         curve: Interval(
     217             :           0.0,
     218             :           0.35,
     219             :           curve: Curves.easeInOutCirc,
     220             :         ),
     221             :       ),
     222             :     );
     223             :   }
     224             : 
     225           1 :   Animation<Color> createColorFilterAnimation() {
     226           3 :     return ColorTween(begin: Colors.transparent, end: snack.overlayColor)
     227           1 :         .animate(
     228           1 :       CurvedAnimation(
     229           1 :         parent: _controller,
     230           1 :         curve: Interval(
     231             :           0.0,
     232             :           0.35,
     233             :           curve: Curves.easeInOutCirc,
     234             :         ),
     235             :       ),
     236             :     );
     237             :   }
     238             : 
     239             :   T _result;
     240             :   SnackStatus currentStatus;
     241             : 
     242             :   //copy of `routes.dart`
     243           1 :   void _handleStatusChanged(AnimationStatus status) {
     244             :     switch (status) {
     245           1 :       case AnimationStatus.completed:
     246           0 :         currentStatus = SnackStatus.SHOWING;
     247           0 :         _onStatusChanged(currentStatus);
     248           0 :         if (overlayEntries.isNotEmpty) overlayEntries.first.opaque = opaque;
     249             : 
     250             :         break;
     251           1 :       case AnimationStatus.forward:
     252           1 :         currentStatus = SnackStatus.IS_APPEARING;
     253           3 :         _onStatusChanged(currentStatus);
     254             :         break;
     255           1 :       case AnimationStatus.reverse:
     256           0 :         currentStatus = SnackStatus.IS_HIDING;
     257           0 :         _onStatusChanged(currentStatus);
     258           0 :         if (overlayEntries.isNotEmpty) overlayEntries.first.opaque = false;
     259             :         break;
     260           1 :       case AnimationStatus.dismissed:
     261           3 :         assert(!overlayEntries.first.opaque);
     262             :         // We might still be the current route if a subclass is controlling the
     263             :         // the transition and hits the dismissed status. For example, the iOS
     264             :         // back gesture drives this animation to the dismissed status before
     265             :         // popping the navigator.
     266           1 :         currentStatus = SnackStatus.DISMISSED;
     267           3 :         _onStatusChanged(currentStatus);
     268             : 
     269           1 :         if (!isCurrent) {
     270           0 :           navigator.finalizeRoute(this);
     271           0 :           assert(overlayEntries.isEmpty);
     272             :         }
     273             :         break;
     274             :     }
     275           1 :     changedInternalState();
     276             :   }
     277             : 
     278           1 :   @override
     279             :   void install() {
     280           2 :     assert(!_transitionCompleter.isCompleted,
     281           0 :         'Cannot install a $runtimeType after disposing it.');
     282           2 :     _controller = createAnimationController();
     283           1 :     assert(_controller != null,
     284           0 :         '$runtimeType.createAnimationController() returned null.');
     285           2 :     _filterBlurAnimation = createBlurFilterAnimation();
     286           2 :     _filterColorAnimation = createColorFilterAnimation();
     287           2 :     _animation = createAnimation();
     288           1 :     assert(_animation != null, '$runtimeType.createAnimation() returned null.');
     289           1 :     super.install();
     290             :   }
     291             : 
     292           1 :   @override
     293             :   TickerFuture didPush() {
     294           1 :     super.didPush();
     295           1 :     assert(_controller != null,
     296           0 :         '$runtimeType.didPush called before calling install() or after calling dispose().');
     297           2 :     assert(!_transitionCompleter.isCompleted,
     298           0 :         'Cannot reuse a $runtimeType after disposing it.');
     299           3 :     _animation.addStatusListener(_handleStatusChanged);
     300           1 :     _configureTimer();
     301           2 :     return _controller.forward();
     302             :   }
     303             : 
     304           0 :   @override
     305             :   void didReplace(Route<dynamic> oldRoute) {
     306           0 :     assert(_controller != null,
     307           0 :         '$runtimeType.didReplace called before calling install() or after calling dispose().');
     308           0 :     assert(!_transitionCompleter.isCompleted,
     309           0 :         'Cannot reuse a $runtimeType after disposing it.');
     310           0 :     if (oldRoute is SnackRoute) _controller.value = oldRoute._controller.value;
     311           0 :     _animation.addStatusListener(_handleStatusChanged);
     312           0 :     super.didReplace(oldRoute);
     313             :   }
     314             : 
     315           1 :   @override
     316             :   bool didPop(T result) {
     317           1 :     assert(_controller != null,
     318           0 :         '$runtimeType.didPop called before calling install() or after calling dispose().');
     319           2 :     assert(!_transitionCompleter.isCompleted,
     320           0 :         'Cannot reuse a $runtimeType after disposing it.');
     321             : 
     322           1 :     _result = result;
     323           1 :     _cancelTimer();
     324             : 
     325           1 :     if (_wasDismissedBySwipe) {
     326           0 :       Timer(Duration(milliseconds: 200), () {
     327           0 :         _controller.reset();
     328             :       });
     329             : 
     330           0 :       _wasDismissedBySwipe = false;
     331             :     } else {
     332           2 :       _controller.reverse();
     333             :     }
     334             : 
     335           1 :     return super.didPop(result);
     336             :   }
     337             : 
     338           1 :   void _configureTimer() {
     339           2 :     if (snack.duration != null) {
     340           1 :       if (_timer != null && _timer.isActive) {
     341           0 :         _timer.cancel();
     342             :       }
     343           5 :       _timer = Timer(snack.duration, () {
     344           1 :         if (this.isCurrent) {
     345           2 :           navigator.pop();
     346           0 :         } else if (this.isActive) {
     347           0 :           navigator.removeRoute(this);
     348             :         }
     349             :       });
     350             :     } else {
     351           0 :       if (_timer != null) {
     352           0 :         _timer.cancel();
     353             :       }
     354             :     }
     355             :   }
     356             : 
     357           1 :   void _cancelTimer() {
     358           3 :     if (_timer != null && _timer.isActive) {
     359           0 :       _timer.cancel();
     360             :     }
     361             :   }
     362             : 
     363             :   /// Whether this route can perform a transition to the given route.
     364             :   /// Subclasses can override this method to restrict the set of routes they
     365             :   /// need to coordinate transitions with.
     366           0 :   bool canTransitionTo(SnackRoute<dynamic> nextRoute) => true;
     367             : 
     368             :   /// Whether this route can perform a transition from the given route.
     369             :   ///
     370             :   /// Subclasses can override this method to restrict the set of routes they
     371             :   /// need to coordinate transitions with.
     372           0 :   bool canTransitionFrom(SnackRoute<dynamic> previousRoute) => true;
     373             : 
     374           1 :   @override
     375             :   void dispose() {
     376           2 :     assert(!_transitionCompleter.isCompleted,
     377           0 :         'Cannot dispose a $runtimeType twice.');
     378           2 :     _controller?.dispose();
     379           3 :     _transitionCompleter.complete(_result);
     380           1 :     super.dispose();
     381             :   }
     382             : 
     383             :   /// A short description of this route useful for debugging.
     384           3 :   String get debugLabel => '$runtimeType';
     385             : 
     386           1 :   @override
     387           3 :   String toString() => '$runtimeType(animation: $_controller)';
     388             : }
     389             : 
     390           1 : SnackRoute showSnack<T>({@required GetBar snack}) {
     391           0 :   assert(snack != null);
     392             : 
     393           1 :   return SnackRoute<T>(
     394             :     snack: snack,
     395           1 :     settings: RouteSettings(name: "snackbar"),
     396             :   );
     397             : }

Generated by: LCOV version 1.14