|
| 1 | +import 'package:flutter/material.dart'; |
| 2 | +import 'package:get/get.dart'; |
| 3 | + |
| 4 | +import 'package:namida/core/icon_fonts/broken_icons.dart'; |
| 5 | + |
| 6 | +mixin PullToRefreshMixin<T extends StatefulWidget> on State<T> implements TickerProvider { |
| 7 | + final turnsTween = Tween<double>(begin: 0.0, end: 1.0); |
| 8 | + late final animation = AnimationController(vsync: this, duration: Duration.zero); |
| 9 | + AnimationController get animation2; |
| 10 | + |
| 11 | + final _minTrigger = 20; |
| 12 | + |
| 13 | + num get pullNormalizer => 20; |
| 14 | + |
| 15 | + bool onScrollNotification(ScrollMetricsNotification notification) { |
| 16 | + final pixels = notification.metrics.pixels; |
| 17 | + if (pixels < -_minTrigger) { |
| 18 | + animation.animateTo(((pixels + _minTrigger).abs() / pullNormalizer).clamp(0, 1)); |
| 19 | + } else if (animation.value > 0) { |
| 20 | + animation.animateTo(0); |
| 21 | + } |
| 22 | + return true; |
| 23 | + } |
| 24 | + |
| 25 | + double _distanceDragged = 0; |
| 26 | + bool onVerticalDragUpdate(double dy) { |
| 27 | + _distanceDragged -= dy; |
| 28 | + if (_distanceDragged < -_minTrigger) { |
| 29 | + animation.animateTo(((_distanceDragged + _minTrigger).abs() / pullNormalizer).clamp(0, 1)); |
| 30 | + } else if (animation.value > 0) { |
| 31 | + animation.animateTo(0); |
| 32 | + } |
| 33 | + return true; |
| 34 | + } |
| 35 | + |
| 36 | + void onVerticalDragFinish() { |
| 37 | + animation.animateTo(0, duration: const Duration(milliseconds: 100)); |
| 38 | + _distanceDragged = 0; |
| 39 | + } |
| 40 | + |
| 41 | + Future<void> showRefreshingAnimation(Future<void> Function() whileExecuting) async { |
| 42 | + animation2.repeat(); |
| 43 | + await whileExecuting(); |
| 44 | + await animation2.fling(); |
| 45 | + animation2.stop(); |
| 46 | + } |
| 47 | + |
| 48 | + Widget get pullToRefreshWidget { |
| 49 | + return Positioned( |
| 50 | + left: 0, |
| 51 | + right: 0, |
| 52 | + child: AnimatedBuilder( |
| 53 | + animation: animation, |
| 54 | + child: CircleAvatar( |
| 55 | + radius: 24.0, |
| 56 | + backgroundColor: context.theme.colorScheme.secondaryContainer, |
| 57 | + child: const Icon(Broken.refresh_2), |
| 58 | + ), |
| 59 | + builder: (context, circleAvatar) { |
| 60 | + final p = animation.value; |
| 61 | + if (!animation2.isAnimating && p == 0) return const SizedBox(); |
| 62 | + const multiplier = 4.5; |
| 63 | + const minus = multiplier / 3; |
| 64 | + return Padding( |
| 65 | + padding: EdgeInsets.only(top: 12.0 + p * 128.0), |
| 66 | + child: Transform.rotate( |
| 67 | + angle: (p * multiplier) - minus, |
| 68 | + child: AnimatedBuilder( |
| 69 | + animation: animation2, |
| 70 | + child: circleAvatar, |
| 71 | + builder: (context, circleAvatar) { |
| 72 | + return Opacity( |
| 73 | + opacity: animation2.status == AnimationStatus.forward ? 1.0 : p, |
| 74 | + child: RotationTransition( |
| 75 | + key: const Key('rotatie'), |
| 76 | + turns: turnsTween.animate(animation2), |
| 77 | + child: circleAvatar, |
| 78 | + ), |
| 79 | + ); |
| 80 | + }, |
| 81 | + ), |
| 82 | + ), |
| 83 | + ); |
| 84 | + }, |
| 85 | + ), |
| 86 | + ); |
| 87 | + } |
| 88 | + |
| 89 | + @override |
| 90 | + void dispose() { |
| 91 | + animation.dispose(); |
| 92 | + super.dispose(); |
| 93 | + } |
| 94 | +} |
0 commit comments