diff --git a/README.md b/README.md index b18288a..b2fe4e8 100644 --- a/README.md +++ b/README.md @@ -119,11 +119,12 @@ AnimatedTextKit( speed: const Duration(milliseconds: 2000), ), ], - + totalRepeatCount: 4, pause: const Duration(milliseconds: 1000), displayFullTextOnTap: true, stopPauseOnTap: true, + controller: myAnimatedTextController ) ``` @@ -134,6 +135,7 @@ It has many configurable properties, including: - `isRepeatingAnimation` – controls whether the animation repeats - `repeatForever` – controls whether the animation repeats forever - `totalRepeatCount` – number of times the animation should repeat (when `repeatForever` is `false`) +- `controller` - It allows for control over the animation by providing methods to play, pause and reset the text animations programmatically There are also custom callbacks: diff --git a/example/lib/main.dart b/example/lib/main.dart index c5c618b..fa83ee1 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -33,6 +33,8 @@ class _MyHomePageState extends State { int _index = 0; int _tapCount = 0; + bool _isAnimationPaused = false; + @override void initState() { super.initState(); @@ -77,18 +79,58 @@ class _MyHomePageState extends State { ), ], ), - floatingActionButton: FloatingActionButton( - onPressed: () { - setState(() { - _index = ++_index % _examples.length; - _tapCount = 0; - }); - }, - tooltip: 'Next', - child: const Icon( - Icons.play_circle_filled, - size: 50.0, - ), + floatingActionButton: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FloatingActionButton( + onPressed: () { + animatedTextExample.controller.reset(); + setState(() { + _isAnimationPaused = false; + _tapCount = 0; + }); + }, + tooltip: 'Reset current animation', + child: const Icon( + Icons.replay_sharp, + size: 50.0, + ), + ), + const SizedBox(width: 16), + FloatingActionButton( + onPressed: () { + if (_isAnimationPaused) { + animatedTextExample.controller.play(); + setState(() { + _isAnimationPaused = false; + }); + } else { + animatedTextExample.controller.pause(); + setState(() { + _isAnimationPaused = true; + }); + } + }, + tooltip: _isAnimationPaused ? 'Play' : 'Pause', + child: Icon( + _isAnimationPaused ? Icons.play_circle : Icons.pause_circle, + size: 50, + )), + const SizedBox(width: 16), + FloatingActionButton( + onPressed: () { + setState(() { + _index = ++_index % _examples.length; + _tapCount = 0; + }); + }, + tooltip: 'Next', + child: const Icon( + Icons.arrow_right, + size: 50.0, + ), + ), + ], ), ); } @@ -98,12 +140,13 @@ class AnimatedTextExample { final String label; final Color? color; final Widget child; + final AnimatedTextController controller; - const AnimatedTextExample({ - required this.label, - required this.color, - required this.child, - }); + const AnimatedTextExample( + {required this.label, + required this.color, + required this.child, + required this.controller}); } // Colorize Text Style @@ -120,259 +163,290 @@ const _colorizeColors = [ Colors.red, ]; -List animatedTextExamples({VoidCallback? onTap}) => - [ - AnimatedTextExample( - label: 'Rotate', - color: Colors.orange[800], - child: ListView( - scrollDirection: Axis.horizontal, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox( - width: 20.0, - height: 100.0, - ), - const Text( - 'Be', - style: TextStyle(fontSize: 43.0), - ), - const SizedBox( - width: 20.0, - height: 100.0, +List animatedTextExamples({VoidCallback? onTap}) { + final rotateController = AnimatedTextController(); + final fadeController = AnimatedTextController(); + final typerController = AnimatedTextController(); + final typewriterController = AnimatedTextController(); + final scaleController = AnimatedTextController(); + final colorizeController = AnimatedTextController(); + final textLiquidFillController = AnimatedTextController(); + final wavyTextController = AnimatedTextController(); + final flickerController = AnimatedTextController(); + final combinationController = AnimatedTextController(); + + return [ + AnimatedTextExample( + label: 'Rotate', + color: Colors.orange[800], + controller: rotateController, + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + width: 20.0, + height: 100.0, + ), + const Text( + 'Be', + style: TextStyle(fontSize: 43.0), + ), + const SizedBox( + width: 20.0, + height: 100.0, + ), + DefaultTextStyle( + style: TextStyle( + fontSize: 40.0, + fontFamily: 'Horizon', ), - DefaultTextStyle( - style: TextStyle( - fontSize: 40.0, - fontFamily: 'Horizon', - ), - child: AnimatedTextKit( - animatedTexts: [ - RotateAnimatedText('AWESOME'), - RotateAnimatedText('OPTIMISTIC'), - RotateAnimatedText( - 'DIFFERENT', - textStyle: const TextStyle( - decoration: TextDecoration.underline, - ), + child: AnimatedTextKit( + animatedTexts: [ + RotateAnimatedText('AWESOME'), + RotateAnimatedText('OPTIMISTIC'), + RotateAnimatedText( + 'DIFFERENT', + textStyle: const TextStyle( + decoration: TextDecoration.underline, ), - ], - onTap: onTap, - isRepeatingAnimation: true, - totalRepeatCount: 10, - ), + ), + ], + controller: rotateController, + onTap: onTap, + isRepeatingAnimation: true, + totalRepeatCount: 10, ), - ], - ), + ), + ], + ), + ], + ), + ), + AnimatedTextExample( + label: 'Fade', + color: Colors.brown[600], + controller: fadeController, + child: DefaultTextStyle( + style: const TextStyle( + fontSize: 32.0, + fontWeight: FontWeight.bold, + ), + child: AnimatedTextKit( + animatedTexts: [ + FadeAnimatedText('do IT!'), + FadeAnimatedText('do it RIGHT!!'), + FadeAnimatedText('do it RIGHT NOW!!!'), ], + controller: fadeController, + onTap: onTap, ), ), - AnimatedTextExample( - label: 'Fade', - color: Colors.brown[600], + ), + AnimatedTextExample( + label: 'Typer', + color: Colors.lightGreen[800], + controller: typerController, + child: SizedBox( + width: 250.0, child: DefaultTextStyle( style: const TextStyle( - fontSize: 32.0, - fontWeight: FontWeight.bold, + fontSize: 30.0, + fontFamily: 'Bobbers', ), child: AnimatedTextKit( animatedTexts: [ - FadeAnimatedText('do IT!'), - FadeAnimatedText('do it RIGHT!!'), - FadeAnimatedText('do it RIGHT NOW!!!'), + TyperAnimatedText('It is not enough to do your best,'), + TyperAnimatedText('you must know what to do,'), + TyperAnimatedText('and then do your best'), + TyperAnimatedText('- W.Edwards Deming'), ], + controller: typerController, onTap: onTap, ), ), ), - AnimatedTextExample( - label: 'Typer', - color: Colors.lightGreen[800], - child: SizedBox( - width: 250.0, - child: DefaultTextStyle( - style: const TextStyle( - fontSize: 30.0, - fontFamily: 'Bobbers', - ), - child: AnimatedTextKit( - animatedTexts: [ - TyperAnimatedText('It is not enough to do your best,'), - TyperAnimatedText('you must know what to do,'), - TyperAnimatedText('and then do your best'), - TyperAnimatedText('- W.Edwards Deming'), - ], - onTap: onTap, - ), - ), - ), - ), - AnimatedTextExample( - label: 'Typewriter', - color: Colors.teal[700], - child: SizedBox( - width: 250.0, - child: DefaultTextStyle( - style: const TextStyle( - fontSize: 30.0, - fontFamily: 'Agne', - ), - child: AnimatedTextKit( - animatedTexts: [ - TypewriterAnimatedText('Discipline is the best tool'), - TypewriterAnimatedText('Design first, then code', cursor: '|'), - TypewriterAnimatedText('Do not patch bugs out, rewrite them', - cursor: '<|>'), - TypewriterAnimatedText('Do not test bugs out, design them out', - cursor: '💡'), - ], - onTap: onTap, - ), - ), - ), - ), - AnimatedTextExample( - label: 'Scale', - color: Colors.blue[700], + ), + AnimatedTextExample( + label: 'Typewriter', + color: Colors.teal[700], + controller: typewriterController, + child: SizedBox( + width: 250.0, child: DefaultTextStyle( style: const TextStyle( - fontSize: 70.0, - fontFamily: 'Canterbury', + fontSize: 30.0, + fontFamily: 'Agne', ), child: AnimatedTextKit( animatedTexts: [ - ScaleAnimatedText('Think'), - ScaleAnimatedText('Build'), - ScaleAnimatedText('Ship'), + TypewriterAnimatedText('Discipline is the best tool'), + TypewriterAnimatedText('Design first, then code', cursor: '|'), + TypewriterAnimatedText('Do not patch bugs out, rewrite them', + cursor: '<|>'), + TypewriterAnimatedText('Do not test bugs out, design them out', + cursor: '💡'), ], + controller: typewriterController, onTap: onTap, ), ), ), - AnimatedTextExample( - label: 'Colorize', - color: Colors.blueGrey[50], + ), + AnimatedTextExample( + label: 'Scale', + color: Colors.blue[700], + controller: scaleController, + child: DefaultTextStyle( + style: const TextStyle( + fontSize: 70.0, + fontFamily: 'Canterbury', + ), child: AnimatedTextKit( animatedTexts: [ - ColorizeAnimatedText( - 'Larry Page', - textStyle: _colorizeTextStyle, - colors: _colorizeColors, - ), - ColorizeAnimatedText( - 'Bill Gates', - textStyle: _colorizeTextStyle, - colors: _colorizeColors, - ), - ColorizeAnimatedText( - 'Steve Jobs', - textStyle: _colorizeTextStyle, - colors: _colorizeColors, - ), + ScaleAnimatedText('Think'), + ScaleAnimatedText('Build'), + ScaleAnimatedText('Ship'), ], + controller: scaleController, onTap: onTap, ), ), - AnimatedTextExample( - label: 'TextLiquidFill', - color: Colors.white, - child: TextLiquidFill( - text: 'LIQUIDY', - waveColor: Colors.blueAccent, - boxBackgroundColor: Colors.redAccent, - textStyle: const TextStyle( - fontSize: 70, - fontWeight: FontWeight.bold, + ), + AnimatedTextExample( + label: 'Colorize', + color: Colors.blueGrey[50], + controller: colorizeController, + child: AnimatedTextKit( + animatedTexts: [ + ColorizeAnimatedText( + 'Larry Page', + textStyle: _colorizeTextStyle, + colors: _colorizeColors, ), - boxHeight: 300, - ), - ), - AnimatedTextExample( - label: 'Wavy Text', - color: Colors.purple, - child: DefaultTextStyle( - style: const TextStyle( - fontSize: 20.0, + ColorizeAnimatedText( + 'Bill Gates', + textStyle: _colorizeTextStyle, + colors: _colorizeColors, ), - child: AnimatedTextKit( - animatedTexts: [ - WavyAnimatedText( - 'Hello World', - textStyle: const TextStyle( - fontSize: 24.0, - fontWeight: FontWeight.bold, - ), - ), - WavyAnimatedText('Look at the waves'), - WavyAnimatedText('They look so Amazing'), - ], - onTap: onTap, + ColorizeAnimatedText( + 'Steve Jobs', + textStyle: _colorizeTextStyle, + colors: _colorizeColors, ), - ), + ], + controller: colorizeController, + onTap: onTap, ), - AnimatedTextExample( - label: 'Flicker', - color: Colors.pink[300], - child: DefaultTextStyle( - style: const TextStyle( - fontSize: 35, - color: Colors.white, - shadows: [ - Shadow( - blurRadius: 7.0, - color: Colors.white, - offset: Offset(0, 0), - ), - ], - ), - child: AnimatedTextKit( - repeatForever: true, - animatedTexts: [ - FlickerAnimatedText('Flicker Frenzy'), - FlickerAnimatedText('Night Vibes On'), - FlickerAnimatedText("C'est La Vie !"), - ], - onTap: onTap, - ), + ), + AnimatedTextExample( + label: 'TextLiquidFill', + color: Colors.white, + controller: textLiquidFillController, + child: TextLiquidFill( + text: 'LIQUIDY', + waveColor: Colors.blueAccent, + boxBackgroundColor: Colors.redAccent, + textStyle: const TextStyle( + fontSize: 70, + fontWeight: FontWeight.bold, ), + boxHeight: 300, ), - AnimatedTextExample( - label: 'Combination', - color: Colors.pink, + ), + AnimatedTextExample( + label: 'Wavy Text', + color: Colors.purple, + controller: wavyTextController, + child: DefaultTextStyle( + style: const TextStyle( + fontSize: 20.0, + ), child: AnimatedTextKit( - onTap: onTap, animatedTexts: [ WavyAnimatedText( - 'On Your Marks', + 'Hello World', textStyle: const TextStyle( fontSize: 24.0, - ), - ), - FadeAnimatedText( - 'Get Set', - textStyle: const TextStyle( - fontSize: 32.0, fontWeight: FontWeight.bold, ), ), - ScaleAnimatedText( - 'Ready', - textStyle: const TextStyle( - fontSize: 48.0, - fontWeight: FontWeight.bold, - ), + WavyAnimatedText('Look at the waves'), + WavyAnimatedText('They look so Amazing'), + ], + controller: wavyTextController, + onTap: onTap, + ), + ), + ), + AnimatedTextExample( + label: 'Flicker', + color: Colors.pink[300], + controller: flickerController, + child: DefaultTextStyle( + style: const TextStyle( + fontSize: 35, + color: Colors.white, + shadows: [ + Shadow( + blurRadius: 7.0, + color: Colors.white, + offset: Offset(0, 0), ), - RotateAnimatedText( - 'Go!', - textStyle: const TextStyle( - fontSize: 64.0, - ), - rotateOut: false, - duration: const Duration(milliseconds: 400), - ) ], ), + child: AnimatedTextKit( + repeatForever: true, + animatedTexts: [ + FlickerAnimatedText('Flicker Frenzy'), + FlickerAnimatedText('Night Vibes On'), + FlickerAnimatedText("C'est La Vie !"), + ], + onTap: onTap, + controller: flickerController, + ), ), - ]; + ), + AnimatedTextExample( + label: 'Combination', + color: Colors.pink, + controller: combinationController, + child: AnimatedTextKit( + onTap: onTap, + controller: combinationController, + animatedTexts: [ + WavyAnimatedText( + 'On Your Marks', + textStyle: const TextStyle( + fontSize: 24.0, + ), + ), + FadeAnimatedText( + 'Get Set', + textStyle: const TextStyle( + fontSize: 32.0, + fontWeight: FontWeight.bold, + ), + ), + ScaleAnimatedText( + 'Ready', + textStyle: const TextStyle( + fontSize: 48.0, + fontWeight: FontWeight.bold, + ), + ), + RotateAnimatedText( + 'Go!', + textStyle: const TextStyle( + fontSize: 64.0, + ), + rotateOut: false, + duration: const Duration(milliseconds: 400), + ) + ], + ), + ), + ]; +} diff --git a/lib/animated_text_kit.dart b/lib/animated_text_kit.dart index 632b22f..c10b450 100644 --- a/lib/animated_text_kit.dart +++ b/lib/animated_text_kit.dart @@ -1,6 +1,7 @@ /// _Animated Text Kit_ is a library of some cool and awesome text animations. library animated_text_kit; +export 'src/animated_text_controller.dart'; export 'src/animated_text.dart'; export 'src/typer.dart'; export 'src/rotate.dart'; diff --git a/lib/src/animated_text.dart b/lib/src/animated_text.dart index db3f851..887d8fe 100644 --- a/lib/src/animated_text.dart +++ b/lib/src/animated_text.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:math'; +import 'package:animated_text_kit/src/animated_text_controller.dart'; import 'package:flutter/material.dart'; /// Abstract base class for text animations. @@ -111,6 +112,14 @@ class AnimatedTextKit extends StatefulWidget { /// By default it is set to 3 final int totalRepeatCount; + /// A controller for managing the state of an animated text sequence. + /// + /// This controller exposes methods to play, pause, and reset the animation. + /// The [AnimatedTextState] enum represents the various states the animation + /// can be in. By calling [play()], [pause()], or [reset()], you can transition + /// between these states and the animated widget will react accordingly. + final AnimatedTextController? controller; + const AnimatedTextKit({ Key? key, required this.animatedTexts, @@ -121,6 +130,7 @@ class AnimatedTextKit extends StatefulWidget { this.onNext, this.onNextBeforePause, this.onFinished, + this.controller, this.isRepeatingAnimation = true, this.totalRepeatCount = 3, this.repeatForever = false, @@ -140,24 +150,43 @@ class _AnimatedTextKitState extends State late AnimatedText _currentAnimatedText; + late AnimatedTextController _animatedTextController; + int _currentRepeatCount = 0; int _index = 0; - bool _isCurrentlyPausing = false; - Timer? _timer; @override void initState() { super.initState(); + _animatedTextController = widget.controller ?? AnimatedTextController(); + _animatedTextController.stateNotifier.addListener(_stateChangedCallback); _initAnimation(); } + void _stateChangedCallback() { + if (!mounted) return; + if (_animatedTextController.state == AnimatedTextState.playing && + !_controller.isAnimating) { + _controller.forward(); + } else if (_animatedTextController.state == + AnimatedTextState.pausedByUser) { + _controller.stop(); + } else if (_animatedTextController.state == AnimatedTextState.reset) { + _controller.reset(); + _animatedTextController.state = AnimatedTextState.playing; + } + } + @override void dispose() { _timer?.cancel(); _controller.dispose(); + _animatedTextController.stateNotifier.removeListener(_stateChangedCallback); + // Only dispose the controller if it was created by this widget + if (widget.controller == null) _animatedTextController.dispose(); super.dispose(); } @@ -167,7 +196,9 @@ class _AnimatedTextKitState extends State return GestureDetector( behavior: HitTestBehavior.opaque, onTap: _onTap, - child: _isCurrentlyPausing || !_controller.isAnimating + child: _animatedTextController.state == + AnimatedTextState.pausedBetweenAnimations || + !_controller.isAnimating ? completeText : AnimatedBuilder( animation: _controller, @@ -182,8 +213,6 @@ class _AnimatedTextKitState extends State void _nextAnimation() { final isLast = _isLast; - _isCurrentlyPausing = false; - // Handling onNext callback widget.onNext?.call(_index, isLast); @@ -221,15 +250,25 @@ class _AnimatedTextKitState extends State _currentAnimatedText.initAnimation(_controller); - _controller - ..addStatusListener(_animationEndCallback) - ..forward(); + _controller.addStatusListener(_animationEndCallback); + + if (_animatedTextController.state == + AnimatedTextState.pausedBetweenAnimationsByUser) { + // This post frame callback is needed to ensure that the state is set and the widget is built + // before we pause the animation. otherwise nothing will be shown during the animation cycle + WidgetsBinding.instance.addPostFrameCallback((_) { + _animatedTextController.state = AnimatedTextState.pausedByUser; + }); + } + _animatedTextController.state = AnimatedTextState.playing; + _controller.forward(); } - void _setPause() { + void _setPauseBetweenAnimations() { final isLast = _isLast; - _isCurrentlyPausing = true; + _animatedTextController.state = AnimatedTextState.pausedBetweenAnimations; + if (mounted) setState(() {}); // Handle onNextBeforePause callback @@ -238,7 +277,7 @@ class _AnimatedTextKitState extends State void _animationEndCallback(AnimationStatus state) { if (state == AnimationStatus.completed) { - _setPause(); + _setPauseBetweenAnimations(); assert(null == _timer || !_timer!.isActive); _timer = Timer(widget.pause, _nextAnimation); } @@ -246,7 +285,8 @@ class _AnimatedTextKitState extends State void _onTap() { if (widget.displayFullTextOnTap) { - if (_isCurrentlyPausing) { + if (_animatedTextController.state == + AnimatedTextState.pausedBetweenAnimations) { if (widget.stopPauseOnTap) { _timer?.cancel(); _nextAnimation(); @@ -258,7 +298,7 @@ class _AnimatedTextKitState extends State _controller.stop(); - _setPause(); + _setPauseBetweenAnimations(); assert(null == _timer || !_timer!.isActive); _timer = Timer( diff --git a/lib/src/animated_text_controller.dart b/lib/src/animated_text_controller.dart new file mode 100644 index 0000000..3bac495 --- /dev/null +++ b/lib/src/animated_text_controller.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; + +/// The various states that the animated text can be in: +/// +/// * [playing]: The animation is currently running. +/// * [userPaused]: The animation is paused due to a user action. +/// * [pausingBetweenAnimations]: The animation has completed one segment and is +/// currently in the built-in pause period before the next segment starts. +/// * [pausingBetweenAnimationsWithUserPauseRequested]: The user requested a pause +/// during the pause between animations, so once this pause period ends, +/// the animation should remain paused. +/// * [stopped]: The animation is stopped and will not progress further. +/// * [reset]: The animation should reset to its initial state. +enum AnimatedTextState { + playing, + pausedByUser, + pausedBetweenAnimations, + pausedBetweenAnimationsByUser, + stopped, + reset, +} + +/// A controller for managing the state of an animated text sequence. +/// +/// This controller exposes methods to play, pause, and reset the animation. +/// The [AnimatedTextState] enum represents the various states the animation +/// can be in. By calling [play()], [pause()], or [reset()], you can transition +/// between these states and the animated widget will react accordingly. +class AnimatedTextController { + /// A [ValueNotifier] that holds the current state of the animation. + /// Listeners can be attached to react when the state changes. + final ValueNotifier stateNotifier = + ValueNotifier(AnimatedTextState.playing); + + /// Returns the current state of the animation. + AnimatedTextState get state => stateNotifier.value; + + /// Sets the current state of the animation. + set state(AnimatedTextState state) { + stateNotifier.value = state; + } + + /// Disposes of the [ValueNotifier]. This should be called when the + /// [AnimatedTextController] is no longer needed. + void dispose() { + stateNotifier.dispose(); + } + + /// Transitions the animation into the [playing] state, unless the controller is + /// currently in the [pausingBetweenAnimationsWithUserPauseRequested] state, + /// in which case it returns to the [pausingBetweenAnimations] state. + /// + /// Call this to resume the animation if it was previously paused. + void play() { + if (stateNotifier.value == + AnimatedTextState.pausedBetweenAnimationsByUser) { + stateNotifier.value = AnimatedTextState.pausedBetweenAnimations; + } else { + stateNotifier.value = AnimatedTextState.playing; + } + } + + /// Pauses the animation. If the animation is currently in the [pausingBetweenAnimations] + /// state, it moves to [pausingBetweenAnimationsWithUserPauseRequested], indicating + /// that once the internal pause finishes, the animation should remain paused. + /// Otherwise, it transitions directly into the [userPaused] state. + /// + /// Call this to pause the animation due to user interaction. + void pause() { + if (stateNotifier.value == AnimatedTextState.pausedBetweenAnimations) { + stateNotifier.value = AnimatedTextState.pausedBetweenAnimationsByUser; + } else { + stateNotifier.value = AnimatedTextState.pausedByUser; + } + } + + /// Resets the animation to its initial state by setting the state to [reset]. + /// This typically means the animated text should return to the start of its + /// animation in this cycle and be ready to begin again. + void reset() { + stateNotifier.value = AnimatedTextState.reset; + } +} diff --git a/lib/src/colorize.dart b/lib/src/colorize.dart index f04f5b2..953d3f0 100644 --- a/lib/src/colorize.dart +++ b/lib/src/colorize.dart @@ -165,8 +165,8 @@ class ColorizeAnimatedTextKit extends AnimatedTextKit { TextDirection textDirection, ) => text - .map((_) => ColorizeAnimatedText( - _, + .map((str) => ColorizeAnimatedText( + str, textAlign: textAlign, textStyle: textStyle, speed: speed, diff --git a/lib/src/fade.dart b/lib/src/fade.dart index a6cdce8..e819890 100644 --- a/lib/src/fade.dart +++ b/lib/src/fade.dart @@ -36,7 +36,6 @@ class FadeAnimatedText extends AnimatedText { curve: Interval(0.0, fadeInEnd, curve: Curves.linear), ), ); - _fadeOut = Tween(begin: 1.0, end: 0.0).animate( CurvedAnimation( parent: controller, @@ -105,8 +104,8 @@ class FadeAnimatedTextKit extends AnimatedTextKit { double fadeOutBegin, ) => text - .map((_) => FadeAnimatedText( - _, + .map((str) => FadeAnimatedText( + str, textAlign: textAlign, textStyle: textStyle, duration: duration, diff --git a/lib/src/wavy.dart b/lib/src/wavy.dart index af79b00..6edbffb 100644 --- a/lib/src/wavy.dart +++ b/lib/src/wavy.dart @@ -35,21 +35,21 @@ class WavyAnimatedText extends AnimatedText { @override Widget animatedBuilder(BuildContext context, Widget? child) { final defaultTextStyle = DefaultTextStyle.of(context).style; - final scaleFactor = MediaQuery.of(context).textScaleFactor; + final textScaler = MediaQuery.textScalerOf(context); return RepaintBoundary( child: CustomPaint( painter: _WTextPainter( progress: _waveAnim.value, text: text, textStyle: defaultTextStyle.merge(textStyle), - scaleFactor: scaleFactor, + textScaler: textScaler, ), child: Text( text, style: defaultTextStyle .merge(textStyle) .merge(TextStyle(color: Colors.transparent)), - textScaleFactor: scaleFactor, + textScaler: textScaler, ), ), ); @@ -114,10 +114,11 @@ class _WTextPainter extends CustomPainter { required this.progress, required this.text, required this.textStyle, - required this.scaleFactor, + required this.textScaler, }); - final double progress, scaleFactor; + final double progress; + final TextScaler textScaler; final String text; // Private class to store text information final _textLayoutInfo = <_TextLayoutInfo>[]; @@ -204,7 +205,7 @@ class _WTextPainter extends CustomPainter { style: textStyle, ), textDirection: TextDirection.ltr, - textScaleFactor: scaleFactor, + textScaler: textScaler, )..layout(); textPainter.paint( @@ -227,7 +228,7 @@ class _WTextPainter extends CustomPainter { ), textDirection: TextDirection.ltr, maxLines: 1, - textScaleFactor: scaleFactor, + textScaler: textScaler, ); textPainter.layout(); diff --git a/test/controller_test.dart b/test/controller_test.dart new file mode 100644 index 0000000..81fdcb1 --- /dev/null +++ b/test/controller_test.dart @@ -0,0 +1,56 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:animated_text_kit/src/animated_text_controller.dart'; + +void main() { + late AnimatedTextController controller; + + setUp(() { + controller = AnimatedTextController(); + }); + + tearDown(() { + controller.dispose(); + }); + + test('Initial state should be playing', () { + expect(controller.state, AnimatedTextState.playing); + }); + + test('Calling pause when playing should set state to userPaused', () { + controller.pause(); + expect(controller.state, AnimatedTextState.pausedByUser); + }); + + test('Calling play after paused should set state to playing', () { + controller.pause(); // userPaused + controller.play(); + expect(controller.state, AnimatedTextState.playing); + }); + + test( + 'Pausing during pausingBetweenAnimations should set state to pausingBetweenAnimationsWithUserPauseRequested', + () { + // Directly set state to pausingBetweenAnimations to simulate this scenario. + controller.state = AnimatedTextState.pausedBetweenAnimations; + controller.pause(); + expect(controller.state, AnimatedTextState.pausedBetweenAnimationsByUser); + }); + + test( + 'Calling play when in pausingBetweenAnimationsWithUserPauseRequested should revert to pausingBetweenAnimations', + () { + controller.state = AnimatedTextState.pausedBetweenAnimationsByUser; + controller.play(); + expect(controller.state, AnimatedTextState.pausedBetweenAnimations); + }); + + test('Resetting should set state to reset', () { + controller.reset(); + expect(controller.state, AnimatedTextState.reset); + }); + + test('Changing state directly via setter works', () { + controller.state = AnimatedTextState.pausedByUser; + expect(controller.state, AnimatedTextState.pausedByUser); + }); +} diff --git a/test/smoke_test.dart b/test/smoke_test.dart index c9772f3..e2b8399 100644 --- a/test/smoke_test.dart +++ b/test/smoke_test.dart @@ -18,7 +18,7 @@ void main() { final pumpCount = await tester.pumpAndSettle(); print(' > ${example.label} pumped $pumpCount'); - await tester.tap(find.byIcon(Icons.play_circle_filled)); + await tester.tap(find.byIcon(Icons.arrow_right)); await tester.pump(); }