Jonny Borges

The easiest way of create animations with Flutter.

@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 /// injection, and route management in a quick and practical way. 3 /// injection, and route management in a quick and practical way.
4 library get; 4 library get;
5 5
  6 +export 'get_animations/index.dart';
6 export 'get_common/get_reset.dart'; 7 export 'get_common/get_reset.dart';
7 export 'get_connect/connect.dart'; 8 export 'get_connect/connect.dart';
8 export 'get_core/get_core.dart'; 9 export 'get_core/get_core.dart';
  1 +import 'dart:math';
  2 +
  3 +import 'package:flutter/widgets.dart';
  4 +
  5 +import 'get_animated_builder.dart';
  6 +
  7 +class FadeInAnimation extends OpacityAnimation {
  8 + FadeInAnimation({
  9 + super.key,
  10 + required super.duration,
  11 + required super.delay,
  12 + required super.child,
  13 + super.onComplete,
  14 + super.begin = 0,
  15 + super.end = 1,
  16 + super.idleValue = 0,
  17 + });
  18 +}
  19 +
  20 +class FadeOutAnimation extends OpacityAnimation {
  21 + FadeOutAnimation({
  22 + super.key,
  23 + required super.duration,
  24 + required super.delay,
  25 + required super.child,
  26 + super.onComplete,
  27 + super.begin = 1,
  28 + super.end = 0,
  29 + super.idleValue = 1,
  30 + });
  31 +}
  32 +
  33 +class OpacityAnimation extends GetAnimatedBuilder<double> {
  34 + OpacityAnimation({
  35 + super.key,
  36 + required super.duration,
  37 + required super.delay,
  38 + required super.child,
  39 + required super.onComplete,
  40 + required double begin,
  41 + required double end,
  42 + required super.idleValue,
  43 + }) : super(
  44 + tween: Tween<double>(begin: begin, end: end),
  45 + builder: (context, value, child) {
  46 + return Opacity(
  47 + opacity: value,
  48 + child: child!,
  49 + );
  50 + },
  51 + );
  52 +}
  53 +
  54 +class RotateAnimation extends GetAnimatedBuilder<double> {
  55 + RotateAnimation({
  56 + super.key,
  57 + required super.duration,
  58 + required super.delay,
  59 + required super.child,
  60 + super.onComplete,
  61 + required double begin,
  62 + required double end,
  63 + super.idleValue = 0,
  64 + }) : super(
  65 + builder: (context, value, child) => Transform.rotate(
  66 + angle: value,
  67 + child: child,
  68 + ),
  69 + tween: Tween<double>(begin: begin, end: end),
  70 + );
  71 +}
  72 +
  73 +class ScaleAnimation extends GetAnimatedBuilder<double> {
  74 + ScaleAnimation({
  75 + super.key,
  76 + required super.duration,
  77 + required super.delay,
  78 + required super.child,
  79 + super.onComplete,
  80 + required double begin,
  81 + required double end,
  82 + super.idleValue = 0,
  83 + }) : super(
  84 + builder: (context, value, child) => Transform.scale(
  85 + scale: value,
  86 + child: child,
  87 + ),
  88 + tween: Tween<double>(begin: begin, end: end),
  89 + );
  90 +}
  91 +
  92 +class SlideAnimation extends GetAnimatedBuilder<Offset> {
  93 + SlideAnimation({
  94 + super.key,
  95 + required super.duration,
  96 + required super.delay,
  97 + required super.child,
  98 + super.onComplete,
  99 + required Offset begin,
  100 + required Offset end,
  101 + super.idleValue = const Offset(0, 0),
  102 + }) : super(
  103 + builder: (context, value, child) => Transform.translate(
  104 + offset: value,
  105 + child: child,
  106 + ),
  107 + tween: Tween(begin: begin, end: end),
  108 + );
  109 +}
  110 +
  111 +class BounceAnimation extends GetAnimatedBuilder<double> {
  112 + BounceAnimation({
  113 + super.key,
  114 + required super.duration,
  115 + required super.delay,
  116 + required super.child,
  117 + super.onComplete,
  118 + super.curve = Curves.bounceOut,
  119 + required double begin,
  120 + required double end,
  121 + super.idleValue = 0,
  122 + }) : super(
  123 + builder: (context, value, child) => Transform.scale(
  124 + scale: 1 + value.abs(),
  125 + child: child,
  126 + ),
  127 + tween: Tween<double>(begin: begin, end: end),
  128 + );
  129 +}
  130 +
  131 +class ShakeAnimation extends GetAnimatedBuilder<double> {
  132 + ShakeAnimation({
  133 + super.key,
  134 + required super.duration,
  135 + required super.delay,
  136 + required super.child,
  137 + super.onComplete,
  138 + required double begin,
  139 + required double end,
  140 + super.idleValue = 0,
  141 + }) : super(
  142 + builder: (context, value, child) => Transform.rotate(
  143 + angle: value * pi / 180.0,
  144 + child: child,
  145 + ),
  146 + tween: Tween<double>(begin: begin, end: end),
  147 + );
  148 +}
  149 +
  150 +class SpinAnimation extends GetAnimatedBuilder<double> {
  151 + SpinAnimation({
  152 + super.key,
  153 + required super.duration,
  154 + required super.delay,
  155 + required super.child,
  156 + super.onComplete,
  157 + super.idleValue = 0,
  158 + }) : super(
  159 + builder: (context, value, child) => Transform.rotate(
  160 + angle: value * pi / 180.0,
  161 + child: child,
  162 + ),
  163 + tween: Tween<double>(begin: 0, end: 360),
  164 + );
  165 +}
  166 +
  167 +class ColorAnimation extends GetAnimatedBuilder<Color?> {
  168 + ColorAnimation({
  169 + super.key,
  170 + required super.duration,
  171 + required super.delay,
  172 + required super.child,
  173 + super.onComplete,
  174 + required Color begin,
  175 + required Color end,
  176 + Color? idleColor,
  177 + }) : super(
  178 + builder: (context, value, child) => ColorFiltered(
  179 + colorFilter: ColorFilter.mode(
  180 + Color.lerp(begin, end, value!.value.toDouble())!,
  181 + BlendMode.srcIn,
  182 + ),
  183 + child: child,
  184 + ),
  185 + idleValue: idleColor ?? begin,
  186 + tween: ColorTween(begin: begin, end: end),
  187 + );
  188 +}
  189 +
  190 +class SizeAnimation extends GetAnimatedBuilder<double> {
  191 + SizeAnimation({
  192 + super.key,
  193 + required super.duration,
  194 + required super.delay,
  195 + required super.child,
  196 + super.onComplete,
  197 + super.idleValue = 0,
  198 + required double begin,
  199 + required double end,
  200 + }) : super(
  201 + builder: (context, value, child) => Transform.scale(
  202 + scale: value,
  203 + child: child,
  204 + ),
  205 + tween: Tween<double>(begin: begin, end: end),
  206 + );
  207 +}
  1 +import 'package:flutter/material.dart';
  2 +
  3 +import 'animations.dart';
  4 +import 'get_animated_builder.dart';
  5 +
  6 +const _defaultDuration = Duration(seconds: 2);
  7 +const _defaultDelay = Duration.zero;
  8 +
  9 +extension AnimationExtension on Widget {
  10 + GetAnimatedBuilder? get _currentAnimation =>
  11 + (this is GetAnimatedBuilder) ? this as GetAnimatedBuilder : null;
  12 +
  13 + GetAnimatedBuilder fadeIn({
  14 + Duration duration = _defaultDuration,
  15 + Duration delay = _defaultDelay,
  16 + ValueSetter<AnimationController>? onComplete,
  17 + bool isSequential = false,
  18 + }) {
  19 + assert(isSequential || this is! FadeOutAnimation,
  20 + 'Can not use fadeOut + fadeIn when isSequential is false');
  21 +
  22 + return FadeInAnimation(
  23 + duration: duration,
  24 + delay: _getDelay(isSequential, delay),
  25 + onComplete: onComplete,
  26 + child: this,
  27 + );
  28 + }
  29 +
  30 + GetAnimatedBuilder fadeOut({
  31 + Duration duration = _defaultDuration,
  32 + Duration delay = _defaultDelay,
  33 + ValueSetter<AnimationController>? onComplete,
  34 + bool isSequential = false,
  35 + }) {
  36 + assert(isSequential || this is! FadeInAnimation,
  37 + 'Can not use fadeOut() + fadeIn when isSequential is false');
  38 +
  39 + return FadeOutAnimation(
  40 + duration: duration,
  41 + delay: _getDelay(isSequential, delay),
  42 + onComplete: onComplete,
  43 + child: this,
  44 + );
  45 + }
  46 +
  47 + GetAnimatedBuilder rotate({
  48 + required double begin,
  49 + required double end,
  50 + Duration duration = _defaultDuration,
  51 + Duration delay = _defaultDelay,
  52 + ValueSetter<AnimationController>? onComplete,
  53 + bool isSequential = false,
  54 + }) {
  55 + return RotateAnimation(
  56 + duration: duration,
  57 + delay: _getDelay(isSequential, delay),
  58 + begin: begin,
  59 + end: end,
  60 + onComplete: onComplete,
  61 + child: this,
  62 + );
  63 + }
  64 +
  65 + GetAnimatedBuilder scale({
  66 + required double begin,
  67 + required double end,
  68 + Duration duration = _defaultDuration,
  69 + Duration delay = _defaultDelay,
  70 + ValueSetter<AnimationController>? onComplete,
  71 + bool isSequential = false,
  72 + }) {
  73 + return ScaleAnimation(
  74 + duration: duration,
  75 + delay: _getDelay(isSequential, delay),
  76 + begin: begin,
  77 + end: end,
  78 + onComplete: onComplete,
  79 + child: this,
  80 + );
  81 + }
  82 +
  83 + GetAnimatedBuilder slide({
  84 + required Offset begin,
  85 + required Offset end,
  86 + Duration duration = _defaultDuration,
  87 + Duration delay = _defaultDelay,
  88 + ValueSetter<AnimationController>? onComplete,
  89 + bool isSequential = false,
  90 + }) {
  91 + return SlideAnimation(
  92 + duration: duration,
  93 + delay: _getDelay(isSequential, delay),
  94 + begin: begin,
  95 + end: end,
  96 + onComplete: onComplete,
  97 + child: this,
  98 + );
  99 + }
  100 +
  101 + GetAnimatedBuilder bounce({
  102 + required double begin,
  103 + required double end,
  104 + Duration duration = _defaultDuration,
  105 + Duration delay = _defaultDelay,
  106 + ValueSetter<AnimationController>? onComplete,
  107 + bool isSequential = false,
  108 + }) {
  109 + return BounceAnimation(
  110 + duration: duration,
  111 + delay: _getDelay(isSequential, delay),
  112 + begin: begin,
  113 + end: end,
  114 + onComplete: onComplete,
  115 + child: this,
  116 + );
  117 + }
  118 +
  119 + GetAnimatedBuilder shake({
  120 + required double begin,
  121 + required double end,
  122 + Duration duration = _defaultDuration,
  123 + Duration delay = _defaultDelay,
  124 + ValueSetter<AnimationController>? onComplete,
  125 + bool isSequential = false,
  126 + }) {
  127 + return ShakeAnimation(
  128 + duration: duration,
  129 + delay: _getDelay(isSequential, delay),
  130 + begin: begin,
  131 + end: end,
  132 + onComplete: onComplete,
  133 + child: this,
  134 + );
  135 + }
  136 +
  137 + GetAnimatedBuilder spin({
  138 + Duration duration = _defaultDuration,
  139 + Duration delay = _defaultDelay,
  140 + ValueSetter<AnimationController>? onComplete,
  141 + bool isSequential = false,
  142 + }) {
  143 + return SpinAnimation(
  144 + duration: duration,
  145 + delay: _getDelay(isSequential, delay),
  146 + onComplete: onComplete,
  147 + child: this,
  148 + );
  149 + }
  150 +
  151 + GetAnimatedBuilder size({
  152 + required double begin,
  153 + required double end,
  154 + Duration duration = _defaultDuration,
  155 + Duration delay = _defaultDelay,
  156 + ValueSetter<AnimationController>? onComplete,
  157 + bool isSequential = false,
  158 + }) {
  159 + return SizeAnimation(
  160 + duration: duration,
  161 + delay: _getDelay(isSequential, delay),
  162 + begin: begin,
  163 + end: end,
  164 + onComplete: onComplete,
  165 + child: this,
  166 + );
  167 + }
  168 +
  169 + Duration _getDelay(bool isSequential, Duration delay) {
  170 + assert(!(isSequential && delay != Duration.zero),
  171 + "Error: When isSequential is true, delay must be non-zero. Context: isSequential: $isSequential delay: $delay");
  172 +
  173 + return isSequential
  174 + ? (_currentAnimation?.totalDuration ?? Duration.zero)
  175 + : delay;
  176 + }
  177 +}
  1 +import 'package:flutter/material.dart';
  2 +
  3 +import 'animations.dart';
  4 +
  5 +class GetAnimatedBuilder<T> extends StatefulWidget {
  6 + final Duration duration;
  7 + final Duration delay;
  8 + final Widget child;
  9 + final ValueSetter<AnimationController>? onComplete;
  10 + final ValueSetter<AnimationController>? onStart;
  11 + final Tween<T> tween;
  12 + final T idleValue;
  13 + final ValueWidgetBuilder<T> builder;
  14 + final Curve curve;
  15 +
  16 + Duration get totalDuration => duration + delay;
  17 +
  18 + const GetAnimatedBuilder({
  19 + super.key,
  20 + this.curve = Curves.linear,
  21 + this.onComplete,
  22 + this.onStart,
  23 + required this.duration,
  24 + required this.tween,
  25 + required this.idleValue,
  26 + required this.builder,
  27 + required this.child,
  28 + required this.delay,
  29 + });
  30 + @override
  31 + GetAnimatedBuilderState<T> createState() => GetAnimatedBuilderState<T>();
  32 +}
  33 +
  34 +class GetAnimatedBuilderState<T> extends State<GetAnimatedBuilder<T>>
  35 + with SingleTickerProviderStateMixin {
  36 + late final AnimationController _controller;
  37 + late final Animation<T> _animation;
  38 +
  39 + // AnimationController get controller => _controller;
  40 + // Animation<T> get animation => _animation;
  41 +
  42 + bool _wasStarted = false;
  43 + // bool get wasStarted => _wasStarted;
  44 +
  45 + late T _idleValue;
  46 +
  47 + bool _willResetOnDispose = false;
  48 +
  49 + bool get willResetOnDispose => _willResetOnDispose;
  50 +
  51 + void _listener(AnimationStatus status) {
  52 + switch (status) {
  53 + case AnimationStatus.completed:
  54 + widget.onComplete?.call(_controller);
  55 + if (_willResetOnDispose) {
  56 + _controller.reset();
  57 + }
  58 + break;
  59 + // case AnimationStatus.dismissed:
  60 + case AnimationStatus.forward:
  61 + widget.onStart?.call(_controller);
  62 + break;
  63 + // case AnimationStatus.reverse:
  64 + default:
  65 + break;
  66 + }
  67 + }
  68 +
  69 + @override
  70 + void initState() {
  71 + super.initState();
  72 +
  73 + if (widget is OpacityAnimation) {
  74 + final current =
  75 + context.findRootAncestorStateOfType<GetAnimatedBuilderState>();
  76 + final isLast = current == null;
  77 +
  78 + if (widget is FadeInAnimation) {
  79 + _idleValue = 1.0 as dynamic;
  80 + } else {
  81 + if (isLast) {
  82 + _willResetOnDispose = false;
  83 + } else {
  84 + _willResetOnDispose = true;
  85 + }
  86 + _idleValue = widget.idleValue;
  87 + }
  88 + } else {
  89 + _idleValue = widget.idleValue;
  90 + }
  91 +
  92 + _controller = AnimationController(
  93 + vsync: this,
  94 + duration: widget.duration,
  95 + );
  96 +
  97 + _controller.addStatusListener(_listener);
  98 +
  99 + _animation = widget.tween.animate(
  100 + CurvedAnimation(
  101 + parent: _controller,
  102 + curve: widget.curve,
  103 + ),
  104 + );
  105 +
  106 + Future.delayed(widget.delay, () {
  107 + if (mounted) {
  108 + setState(() {
  109 + _wasStarted = true;
  110 + _controller.forward();
  111 + });
  112 + }
  113 + });
  114 + }
  115 +
  116 + @override
  117 + void dispose() {
  118 + _controller.removeStatusListener(_listener);
  119 + _controller.dispose();
  120 + super.dispose();
  121 + }
  122 +
  123 + @override
  124 + Widget build(BuildContext context) {
  125 + return AnimatedBuilder(
  126 + animation: _animation,
  127 + builder: (context, child) {
  128 + final value = _wasStarted ? _animation.value : _idleValue;
  129 + return widget.builder(context, value, child);
  130 + },
  131 + child: widget.child,
  132 + );
  133 + }
  134 +}
  1 +export './animations.dart';
  2 +export './extensions.dart';
  3 +export './get_animated_builder.dart';
  1 +import 'package:flutter/material.dart';
  2 +import 'package:flutter_test/flutter_test.dart';
  3 +import 'package:get/get.dart';
  4 +
  5 +void main() {
  6 + group('Animation Extension', () {
  7 + Widget buildWidget() {
  8 + return Container(
  9 + width: 100,
  10 + height: 100,
  11 + color: Colors.red,
  12 + );
  13 + }
  14 +
  15 + testWidgets('fadeIn() and fadeOut() can not be used sequentially',
  16 + (WidgetTester tester) async {
  17 + final widget = buildWidget();
  18 +
  19 + expect(() => widget.fadeIn().fadeOut(), throwsAssertionError);
  20 + expect(() => widget.fadeOut().fadeIn(), throwsAssertionError);
  21 +
  22 + expect(() => widget.fadeIn(isSequential: true).fadeOut(),
  23 + throwsAssertionError);
  24 + expect(() => widget.fadeOut(isSequential: true).fadeIn(),
  25 + throwsAssertionError);
  26 + });
  27 +
  28 + testWidgets('can not use delay when isSequential is true',
  29 + (WidgetTester tester) async {
  30 + final widget = buildWidget();
  31 +
  32 + expect(
  33 + () => widget.fadeIn(
  34 + isSequential: true, delay: const Duration(seconds: 1)),
  35 + throwsAssertionError);
  36 + });
  37 +
  38 + testWidgets(
  39 + 'fadeIn() and fadeOut() can be used together when isSequential is true',
  40 + (WidgetTester tester) async {
  41 + final widget = buildWidget();
  42 +
  43 + expect(
  44 + () => widget.fadeIn(isSequential: true).fadeOut(isSequential: true),
  45 + isNot(throwsException));
  46 +
  47 + expect(() => widget.fadeIn().fadeOut(isSequential: true),
  48 + isNot(throwsException));
  49 + });
  50 +
  51 + testWidgets('fadeIn() returns a FadeInAnimation',
  52 + (WidgetTester tester) async {
  53 + final widget = buildWidget();
  54 + const begin = 0.0;
  55 + const end = 1.0;
  56 + final animation = widget.fadeIn();
  57 +
  58 + expect(animation, isA<FadeInAnimation>());
  59 +
  60 + _testDefaultValues(
  61 + animation: animation, widget: widget, begin: begin, end: end);
  62 + });
  63 +
  64 + testWidgets('fadeOut() returns a animation', (WidgetTester tester) async {
  65 + final widget = buildWidget();
  66 + const begin = 1.0;
  67 + const end = 0.0;
  68 + final animation = widget.fadeOut();
  69 +
  70 + expect(animation, isA<FadeOutAnimation>());
  71 +
  72 + _testDefaultValues(
  73 + animation: animation, widget: widget, begin: begin, end: end);
  74 + });
  75 +
  76 + testWidgets('rotate() returns a RotateAnimation',
  77 + (WidgetTester tester) async {
  78 + const begin = 0.9;
  79 + const end = 1.1;
  80 + final widget = buildWidget();
  81 + final animation = widget.rotate(begin: begin, end: end);
  82 +
  83 + expect(animation, isA<RotateAnimation>());
  84 +
  85 + _testDefaultValues(
  86 + animation: animation, widget: widget, begin: begin, end: end);
  87 + });
  88 +
  89 + testWidgets('scale() returns a ScaleAnimation',
  90 + (WidgetTester tester) async {
  91 + const begin = 0.9;
  92 + const end = 1.1;
  93 + final widget = buildWidget();
  94 + final animation = widget.scale(begin: begin, end: end);
  95 +
  96 + expect(animation, isA<ScaleAnimation>());
  97 +
  98 + _testDefaultValues(
  99 + animation: animation, widget: widget, begin: begin, end: end);
  100 + });
  101 +
  102 + testWidgets('slide() returns a SlideAnimation',
  103 + (WidgetTester tester) async {
  104 + const begin = Offset.zero;
  105 + const end = Offset.zero;
  106 + final widget = buildWidget();
  107 + final animation = widget.slide(begin: begin, end: end);
  108 +
  109 + expect(animation, isA<SlideAnimation>());
  110 +
  111 + _testDefaultValues<Offset>(
  112 + animation: animation, widget: widget, begin: begin, end: end);
  113 + });
  114 +
  115 + testWidgets('bounce() returns a BounceAnimation',
  116 + (WidgetTester tester) async {
  117 + const begin = 0.9;
  118 + const end = 1.1;
  119 + final widget = buildWidget();
  120 + final animation = widget.bounce(begin: begin, end: end);
  121 +
  122 + expect(animation, isA<BounceAnimation>());
  123 +
  124 + _testDefaultValues(
  125 + animation: animation,
  126 + widget: widget,
  127 + begin: begin,
  128 + end: end,
  129 + curve: Curves.bounceOut,
  130 + );
  131 + });
  132 +
  133 + testWidgets('shake() returns a ShakeAnimation',
  134 + (WidgetTester tester) async {
  135 + const begin = 0.9;
  136 + const end = 1.1;
  137 + final widget = buildWidget();
  138 + final animation = widget.shake(begin: begin, end: end);
  139 +
  140 + expect(animation, isA<ShakeAnimation>());
  141 +
  142 + _testDefaultValues(
  143 + animation: animation, widget: widget, begin: begin, end: end);
  144 + });
  145 +
  146 + testWidgets('spin() returns a SpinAnimation', (WidgetTester tester) async {
  147 + final widget = buildWidget();
  148 + const begin = 0.0;
  149 + const end = 360;
  150 + final animation = widget.spin();
  151 +
  152 + expect(animation, isA<SpinAnimation>());
  153 +
  154 + _testDefaultValues(
  155 + animation: animation, widget: widget, begin: begin, end: end);
  156 + });
  157 +
  158 + testWidgets('size() returns a SizeAnimation', (WidgetTester tester) async {
  159 + final widget = buildWidget();
  160 +
  161 + const begin = 0.9;
  162 + const end = 1.1;
  163 + final animation = widget.size(begin: begin, end: end);
  164 +
  165 + expect(animation, isA<SizeAnimation>());
  166 +
  167 + _testDefaultValues(
  168 + animation: animation, widget: widget, begin: begin, end: end);
  169 + });
  170 + });
  171 +}
  172 +
  173 +void _testDefaultValues<T>({
  174 + required GetAnimatedBuilder animation,
  175 + required Widget widget,
  176 + required T begin,
  177 + required T end,
  178 + Curve curve = Curves.linear,
  179 +}) {
  180 + expect(animation.tween.begin, begin);
  181 + expect(animation.tween.end, end);
  182 + if (animation.idleValue is Offset) {
  183 + expect(animation.idleValue, Offset.zero);
  184 + } else if (animation is FadeOutAnimation) {
  185 + expect(animation.idleValue, 1);
  186 + } else {
  187 + expect(animation.idleValue, 0);
  188 + }
  189 +
  190 + expect(animation.delay, Duration.zero);
  191 + expect(animation.child, widget);
  192 + expect(animation.curve, curve);
  193 +}
  1 +import 'package:flutter/material.dart';
  2 +import 'package:flutter_test/flutter_test.dart';
  3 +import 'package:get/get.dart';
  4 +
  5 +class _Wrapper extends StatelessWidget {
  6 + const _Wrapper({required this.child});
  7 + final Widget child;
  8 +
  9 + @override
  10 + Widget build(BuildContext context) {
  11 + return MaterialApp(
  12 + home: Scaffold(body: child),
  13 + );
  14 + }
  15 +}
  16 +
  17 +void main() {
  18 + testWidgets('GetAnimatedBuilder defaults', (WidgetTester tester) async {
  19 + await tester.pumpWidget(
  20 + _Wrapper(
  21 + child: GetAnimatedBuilder<int>(
  22 + duration: const Duration(milliseconds: 500),
  23 + tween: Tween(begin: 0, end: 10),
  24 + idleValue: 0,
  25 + builder: (_, value, __) => Text(value.toString()),
  26 + delay: Duration.zero,
  27 + child: Container(),
  28 + ),
  29 + ),
  30 + );
  31 +
  32 + // Verify that the widget starts with the idle value.
  33 + expect(find.text('0'), findsOneWidget);
  34 +
  35 + // Wait for the animation to complete.
  36 + await tester.pumpAndSettle(const Duration(milliseconds: 500));
  37 +
  38 + // Verify that the widget ends with the final value.
  39 + expect(find.text('10'), findsOneWidget);
  40 + });
  41 +
  42 + testWidgets('GetAnimatedBuilder changes value over time', (tester) async {
  43 + await tester.pumpWidget(
  44 + _Wrapper(
  45 + child: GetAnimatedBuilder<double>(
  46 + duration: const Duration(milliseconds: 500),
  47 + tween: Tween<double>(begin: 0.0, end: 1.0),
  48 + idleValue: 0.0,
  49 + builder: (context, value, child) {
  50 + return Opacity(opacity: value);
  51 + },
  52 + delay: const Duration(milliseconds: 500),
  53 + child: Container(
  54 + width: 100,
  55 + height: 100,
  56 + color: Colors.red,
  57 + ),
  58 + ),
  59 + ),
  60 + );
  61 +
  62 + // Initial state is idleValue
  63 + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, 0.0);
  64 +
  65 + // Wait for the delay to finish
  66 + await tester.pump(const Duration(milliseconds: 500));
  67 +
  68 + // Verify that the value changes over time
  69 + await tester.pump(const Duration(milliseconds: 100));
  70 + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity,
  71 + closeTo(0.2, 0.01));
  72 +
  73 + await tester.pump(const Duration(milliseconds: 100));
  74 + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity,
  75 + closeTo(0.4, 0.01));
  76 +
  77 + await tester.pump(const Duration(milliseconds: 100));
  78 + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity,
  79 + closeTo(0.6, 0.01));
  80 +
  81 + await tester.pump(const Duration(milliseconds: 100));
  82 + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity,
  83 + closeTo(0.8, 0.01));
  84 +
  85 + await tester.pump(const Duration(milliseconds: 100));
  86 + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity,
  87 + closeTo(1.0, 0.01));
  88 + });
  89 +
  90 + testWidgets('onComplete callback is called when animation finishes',
  91 + (WidgetTester tester) async {
  92 + AnimationController? controller;
  93 + var onCompleteCalled = false;
  94 +
  95 + await tester.pumpWidget(
  96 + _Wrapper(
  97 + child: GetAnimatedBuilder<int>(
  98 + duration: const Duration(milliseconds: 500),
  99 + tween: Tween(begin: 0, end: 10),
  100 + idleValue: 0,
  101 + builder: (_, value, __) => Text(value.toString()),
  102 + delay: Duration.zero,
  103 + onComplete: (c) {
  104 + onCompleteCalled = true;
  105 + controller = c;
  106 + },
  107 + child: Container(),
  108 + ),
  109 + ),
  110 + );
  111 +
  112 + expect(onCompleteCalled, isFalse);
  113 +
  114 + // Wait for the animation to complete.
  115 + await tester.pumpAndSettle(const Duration(milliseconds: 500));
  116 +
  117 + // Verify that the onComplete callback was called.
  118 + expect(controller, isNotNull);
  119 +
  120 + expect(onCompleteCalled, isTrue);
  121 + });
  122 +
  123 + testWidgets('onStart callback is called when animation starts',
  124 + (WidgetTester tester) async {
  125 + var onStartCalled = false;
  126 +
  127 + await tester.pumpWidget(
  128 + _Wrapper(
  129 + child: GetAnimatedBuilder(
  130 + duration: const Duration(seconds: 1),
  131 + delay: Duration.zero,
  132 + tween: Tween<double>(begin: 0, end: 1),
  133 + idleValue: 0,
  134 + builder: (context, value, child) => Container(),
  135 + child: Container(),
  136 + onStart: (_) {
  137 + onStartCalled = true;
  138 + },
  139 + ),
  140 + ),
  141 + );
  142 +
  143 + expect(onStartCalled, isFalse);
  144 +
  145 + await tester.pump(const Duration(milliseconds: 500));
  146 + expect(onStartCalled, isTrue);
  147 + });
  148 +
  149 + testWidgets('GetAnimatedBuilder delay', (WidgetTester tester) async {
  150 + await tester.pumpWidget(
  151 + _Wrapper(
  152 + child: GetAnimatedBuilder<int>(
  153 + duration: const Duration(milliseconds: 500),
  154 + tween: Tween(begin: 0, end: 10),
  155 + idleValue: 0,
  156 + builder: (_, value, __) => Text(value.toString()),
  157 + delay: const Duration(milliseconds: 500),
  158 + child: Container(),
  159 + ),
  160 + ),
  161 + );
  162 +
  163 + // Verify that the widget starts with the idle value.
  164 + expect(find.text('0'), findsOneWidget);
  165 +
  166 + // Wait for the delay to pass.
  167 + await tester.pump(const Duration(milliseconds: 500));
  168 +
  169 + // Verify that the animation has started.
  170 + expect(find.text('0'), findsOneWidget);
  171 +
  172 + // Wait for the animation to complete.
  173 + await tester.pumpAndSettle(const Duration(milliseconds: 500));
  174 +
  175 + // Verify that the widget ends with the final value.
  176 + expect(find.text('10'), findsOneWidget);
  177 + });
  178 +
  179 + testWidgets(
  180 + 'FadeInAnimation in idle should be visible, but not visible when the animation starts',
  181 + (WidgetTester tester) async {
  182 + await tester.pumpWidget(
  183 + _Wrapper(
  184 + child: FadeInAnimation(
  185 + delay: const Duration(milliseconds: 500),
  186 + duration: const Duration(milliseconds: 500),
  187 + idleValue: 0,
  188 + child: const Text('Hello'),
  189 + ),
  190 + ),
  191 + );
  192 +
  193 + // in idle, the opacity should be 1.0
  194 + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, 1.0);
  195 +
  196 + // Wait for the delay to finish
  197 + await tester.pump(const Duration(milliseconds: 500));
  198 +
  199 + // When the animation starts the opacity should not be visible
  200 + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, 0.0);
  201 +
  202 + // Verify that the value changes over time
  203 + await tester.pump(const Duration(milliseconds: 100));
  204 +
  205 + // The value should be updated
  206 + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity,
  207 + closeTo(0.2, 0.01));
  208 +
  209 + await tester.pump(const Duration(milliseconds: 100));
  210 + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity,
  211 + closeTo(0.4, 0.01));
  212 +
  213 + await tester.pump(const Duration(milliseconds: 100));
  214 + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity,
  215 + closeTo(0.6, 0.01));
  216 +
  217 + await tester.pump(const Duration(milliseconds: 100));
  218 + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity,
  219 + closeTo(0.8, 0.01));
  220 +
  221 + await tester.pump(const Duration(milliseconds: 100));
  222 + expect(tester.widget<Opacity>(find.byType(Opacity)).opacity,
  223 + closeTo(1.0, 0.01));
  224 + });
  225 +
  226 + testWidgets(
  227 + 'willResetOnDispose should false when fadeOut is the last animation in a sequential animation',
  228 + (WidgetTester tester) async {
  229 + await tester.pumpWidget(
  230 + _Wrapper(
  231 + child: const Text('Hello')
  232 + .fadeIn(
  233 + isSequential: true,
  234 + duration: const Duration(milliseconds: 500),
  235 + )
  236 + .fadeOut(
  237 + isSequential: true,
  238 + duration: const Duration(milliseconds: 500),
  239 + ),
  240 + ),
  241 + );
  242 +
  243 + // The variable starts as false
  244 + expect(
  245 + tester
  246 + .state<GetAnimatedBuilderState>(find.byType(FadeOutAnimation))
  247 + .willResetOnDispose,
  248 + false);
  249 +
  250 + // Jump to middle of next animation
  251 + await tester.pump(const Duration(milliseconds: 500));
  252 +
  253 + // The value should be false
  254 + expect(
  255 + tester
  256 + .state<GetAnimatedBuilderState>(find.byType(FadeOutAnimation))
  257 + .willResetOnDispose,
  258 + false);
  259 +
  260 + await tester.pumpAndSettle();
  261 + });
  262 +
  263 + testWidgets(
  264 + 'willResetOnDispose should true when fadeOut is not last animation in a sequential animation',
  265 + (WidgetTester tester) async {
  266 + await tester.pumpWidget(
  267 + _Wrapper(
  268 + child: const Text('Hello')
  269 + .fadeOut(
  270 + isSequential: true,
  271 + duration: const Duration(milliseconds: 500),
  272 + )
  273 + .fadeIn(
  274 + isSequential: true,
  275 + duration: const Duration(milliseconds: 500),
  276 + ),
  277 + ),
  278 + );
  279 +
  280 + // The variable starts as true
  281 + expect(
  282 + tester
  283 + .state<GetAnimatedBuilderState>(find.byType(FadeOutAnimation))
  284 + .willResetOnDispose,
  285 + true);
  286 +
  287 + // Jump to middle of next animation
  288 + await tester.pump(const Duration(milliseconds: 500));
  289 +
  290 + // The value should be true
  291 + expect(
  292 + tester
  293 + .state<GetAnimatedBuilderState>(find.byType(FadeOutAnimation))
  294 + .willResetOnDispose,
  295 + true);
  296 +
  297 + await tester.pumpAndSettle();
  298 + });
  299 +
  300 + testWidgets('RotateAnimation', (WidgetTester tester) async {
  301 + await tester.pumpWidget(
  302 + RotateAnimation(
  303 + duration: const Duration(seconds: 1),
  304 + delay: Duration.zero,
  305 + begin: 0.0,
  306 + end: 360.0,
  307 + child: Container(),
  308 + ),
  309 + );
  310 + expect(find.byType(RotateAnimation), findsOneWidget);
  311 + await tester.pumpAndSettle();
  312 + });
  313 +
  314 + testWidgets('ScaleAnimation', (WidgetTester tester) async {
  315 + await tester.pumpWidget(
  316 + ScaleAnimation(
  317 + duration: const Duration(seconds: 1),
  318 + delay: Duration.zero,
  319 + begin: 1.0,
  320 + end: 2.0,
  321 + child: Container(),
  322 + ),
  323 + );
  324 + expect(find.byType(ScaleAnimation), findsOneWidget);
  325 + await tester.pumpAndSettle();
  326 + });
  327 +
  328 + testWidgets('SlideAnimation', (WidgetTester tester) async {
  329 + await tester.pumpWidget(
  330 + SlideAnimation(
  331 + duration: const Duration(seconds: 1),
  332 + delay: Duration.zero,
  333 + begin: Offset.zero,
  334 + end: const Offset(1.0, 1.0),
  335 + child: Container(),
  336 + ),
  337 + );
  338 + expect(find.byType(SlideAnimation), findsOneWidget);
  339 + await tester.pumpAndSettle();
  340 + });
  341 +
  342 + testWidgets('BounceAnimation', (WidgetTester tester) async {
  343 + await tester.pumpWidget(
  344 + BounceAnimation(
  345 + duration: const Duration(seconds: 1),
  346 + delay: Duration.zero,
  347 + begin: 0.0,
  348 + end: 1.0,
  349 + child: Container(),
  350 + ),
  351 + );
  352 + expect(find.byType(BounceAnimation), findsOneWidget);
  353 + await tester.pumpAndSettle();
  354 + });
  355 +
  356 + testWidgets('ShakeAnimation', (WidgetTester tester) async {
  357 + await tester.pumpWidget(
  358 + ShakeAnimation(
  359 + duration: const Duration(seconds: 1),
  360 + delay: Duration.zero,
  361 + begin: 0.0,
  362 + end: 10.0,
  363 + child: Container(),
  364 + ),
  365 + );
  366 + expect(find.byType(ShakeAnimation), findsOneWidget);
  367 + await tester.pumpAndSettle();
  368 + });
  369 +
  370 + testWidgets('SpinAnimation', (WidgetTester tester) async {
  371 + await tester.pumpWidget(
  372 + SpinAnimation(
  373 + duration: const Duration(seconds: 1),
  374 + delay: Duration.zero,
  375 + child: Container(),
  376 + ),
  377 + );
  378 + expect(find.byType(SpinAnimation), findsOneWidget);
  379 + await tester.pumpAndSettle();
  380 + });
  381 +
  382 + testWidgets('ColorAnimation', (WidgetTester tester) async {
  383 + await tester.pumpWidget(
  384 + ColorAnimation(
  385 + duration: const Duration(seconds: 1),
  386 + delay: Duration.zero,
  387 + begin: Colors.blue,
  388 + end: Colors.red,
  389 + child: Container(),
  390 + ),
  391 + );
  392 + expect(find.byType(ColorAnimation), findsOneWidget);
  393 + await tester.pumpAndSettle();
  394 + });
  395 +
  396 + testWidgets('SizeAnimation', (WidgetTester tester) async {
  397 + await tester.pumpWidget(
  398 + SizeAnimation(
  399 + duration: const Duration(seconds: 1),
  400 + delay: Duration.zero,
  401 + begin: 1.0,
  402 + end: 2.0,
  403 + child: Container(),
  404 + ),
  405 + );
  406 + expect(find.byType(SizeAnimation), findsOneWidget);
  407 + await tester.pumpAndSettle();
  408 + });
  409 +}