diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 5aed2e1..7eef216 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -1,9 +1,8 @@
name: Publish to pub.dev
on:
- push:
- tags:
- - "v[0-9]+.[0-9]+.[0-9]+*" # for tags like: 'v1.2.3'
+ release:
+ types: [published]
jobs:
publish:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 88658e6..0052505 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 4.3.0
+
+**New Animated Text**
+
+- **Scramble Animated Text**: Added scramble animation that displays text with a scrambling effect, where characters randomly change before settling to the final text.
+- **Bounce Animated Text**: Added bounce animation that displays text with a bouncing effect, where text bounces in from below with an elastic curve.
+
## 4.2.3
- **Feature:** Added `AnimatedTextController` to explicitly control animations. This allows developers more flexibility in managing animations. [#349](https://github.com/aagarwal1012/Animated-Text-Kit/pull/349)
diff --git a/README.md b/README.md
index b2fe4e8..12729b2 100644
--- a/README.md
+++ b/README.md
@@ -13,15 +13,6 @@
-
-
-
-
-
-
-
@@ -30,10 +21,6 @@
-
-
-
@@ -56,9 +43,12 @@
- [TextLiquidFill](#textliquidfill)
- [Wavy](#wavy)
- [Flicker](#flicker)
+ - [Scramble](#scramble)
+ - [Bounce](#bounce)
- [Create your own Animations](#create-your-own-animations)
- [Bugs or Requests](#bugs-or-requests)
- [Contributors](#contributors)
+- [Star History](#star-history)
# Flutter Package of the Week
@@ -457,6 +447,60 @@ return SizedBox(
);
```
+## Scramble
+
+
+
+```dart
+return SizedBox(
+ width: 250.0,
+ child: DefaultTextStyle(
+ style: const TextStyle(
+ fontSize: 40.0,
+ fontWeight: FontWeight.bold,
+ ),
+ child: AnimatedTextKit(
+ animatedTexts: [
+ ScrambleAnimatedText(
+ 'Mobile Dev.',
+ speed: const Duration(milliseconds: 200),
+ ),
+ ScrambleAnimatedText('Explorer'),
+ ],
+ onTap: () {
+ print("Tap Event");
+ },
+ ),
+ ),
+);
+```
+
+## Bounce
+
+
+
+```dart
+return SizedBox(
+ width: 250.0,
+ child: DefaultTextStyle(
+ style: const TextStyle(
+ fontSize: 60.0,
+ fontWeight: FontWeight.bold,
+ ),
+ child: AnimatedTextKit(
+ animatedTexts: [
+ BounceAnimatedText('Bounce!'),
+ BounceAnimatedText('Spring!'),
+ BounceAnimatedText('Jump!'),
+ ],
+ onTap: () {
+ print("Tap Event");
+ },
+ ),
+ ),
+);
+```
+
## Create your own Animations
You can easily create your own animations by creating new classes that extend
@@ -490,42 +534,14 @@ If you encounter any problems feel free to open an [issue](https://github.com/aa
See [Contributing.md](https://github.com/aagarwal1012/Animated-Text-Kit/blob/master/CONTRIBUTING.md).
-# Contributors
-
-Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
-
-
-
-
-
-
-
-
-
-
-
-This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind are welcome! See [Contributing.md](https://github.com/aagarwal1012/Animated-Text-Kit/blob/master/CONTRIBUTING.md).
+## Contributors
+
+Thanks to all our amazing contributors for their support and code!
+
+
+
+
+
+## Star History
+
+[](https://star-history.com/#aagarwal1012/Animated-Text-Kit&Date)
diff --git a/display/bounce.gif b/display/bounce.gif
new file mode 100644
index 0000000..6ec71d3
Binary files /dev/null and b/display/bounce.gif differ
diff --git a/display/scramble.gif b/display/scramble.gif
new file mode 100644
index 0000000..d5f3111
Binary files /dev/null and b/display/scramble.gif differ
diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist
index 6b4c0f7..d57061d 100644
--- a/example/ios/Flutter/AppFrameworkInfo.plist
+++ b/example/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 8.0
+ 13.0
diff --git a/example/ios/Flutter/ephemeral/flutter_lldb_helper.py b/example/ios/Flutter/ephemeral/flutter_lldb_helper.py
new file mode 100644
index 0000000..a88caf9
--- /dev/null
+++ b/example/ios/Flutter/ephemeral/flutter_lldb_helper.py
@@ -0,0 +1,32 @@
+#
+# Generated file, do not edit.
+#
+
+import lldb
+
+def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict):
+ """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages."""
+ base = frame.register["x0"].GetValueAsAddress()
+ page_len = frame.register["x1"].GetValueAsUnsigned()
+
+ # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the
+ # first page to see if handled it correctly. This makes diagnosing
+ # misconfiguration (e.g. missing breakpoint) easier.
+ data = bytearray(page_len)
+ data[0:8] = b'IHELPED!'
+
+ error = lldb.SBError()
+ frame.GetThread().GetProcess().WriteMemory(base, data, error)
+ if not error.Success():
+ print(f'Failed to write into {base}[+{page_len}]', error)
+ return
+
+def __lldb_init_module(debugger: lldb.SBDebugger, _):
+ target = debugger.GetDummyTarget()
+ # Caveat: must use BreakpointCreateByRegEx here and not
+ # BreakpointCreateByName. For some reasons callback function does not
+ # get carried over from dummy target for the later.
+ bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$")
+ bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__))
+ bp.SetAutoContinue(True)
+ print("-- LLDB integration loaded --")
diff --git a/example/ios/Flutter/ephemeral/flutter_lldbinit b/example/ios/Flutter/ephemeral/flutter_lldbinit
new file mode 100644
index 0000000..e3ba6fb
--- /dev/null
+++ b/example/ios/Flutter/ephemeral/flutter_lldbinit
@@ -0,0 +1,5 @@
+#
+# Generated file, do not edit.
+#
+
+command script import --relative-to-command-file flutter_lldb_helper.py
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index 1aec2aa..e06f452 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 46;
+ objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -127,7 +127,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 1020;
+ LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
@@ -171,10 +171,12 @@
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
+ "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
@@ -185,6 +187,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -272,7 +275,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -354,7 +357,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -403,7 +406,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index a28140c..fc5ae03 100644
--- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
@@ -45,11 +46,13 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
+ enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift
index 70693e4..b636303 100644
--- a/example/ios/Runner/AppDelegate.swift
+++ b/example/ios/Runner/AppDelegate.swift
@@ -1,7 +1,7 @@
import UIKit
import Flutter
-@UIApplicationMain
+@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index a060db6..4f68a2c 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -41,5 +41,9 @@
UIViewControllerBasedStatusBarAppearance
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 9e3ae09..5df9b16 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -169,11 +169,13 @@ List animatedTextExamples({VoidCallback? onTap}) {
final typerController = AnimatedTextController();
final typewriterController = AnimatedTextController();
final scaleController = AnimatedTextController();
+ final bounceController = AnimatedTextController();
final colorizeController = AnimatedTextController();
final textLiquidFillController = AnimatedTextController();
final wavyTextController = AnimatedTextController();
final flickerController = AnimatedTextController();
final combinationController = AnimatedTextController();
+ final scrambleController = AnimatedTextController();
return [
AnimatedTextExample(
@@ -315,6 +317,26 @@ List animatedTextExamples({VoidCallback? onTap}) {
),
),
),
+ AnimatedTextExample(
+ label: 'Bounce',
+ color: Colors.amber[700],
+ controller: bounceController,
+ child: DefaultTextStyle(
+ style: const TextStyle(
+ fontSize: 60.0,
+ fontWeight: FontWeight.bold,
+ ),
+ child: AnimatedTextKit(
+ animatedTexts: [
+ BounceAnimatedText('Bounce!'),
+ BounceAnimatedText('Spring!'),
+ BounceAnimatedText('Jump!'),
+ ],
+ controller: bounceController,
+ onTap: onTap,
+ ),
+ ),
+ ),
AnimatedTextExample(
label: 'Colorize',
color: Colors.blueGrey[50],
@@ -448,5 +470,52 @@ List animatedTextExamples({VoidCallback? onTap}) {
],
),
),
+ AnimatedTextExample(
+ label: 'Scramble',
+ 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(
+ 'Im a',
+ style: TextStyle(fontSize: 43.0),
+ ),
+ const SizedBox(
+ width: 16.0,
+ height: 100.0,
+ ),
+ DefaultTextStyle(
+ style: TextStyle(
+ fontSize: 40.0,
+ ),
+ child: AnimatedTextKit(
+ animatedTexts: [
+ ScrambleAnimatedText(
+ 'Mobile Dev.',
+ speed: const Duration(
+ milliseconds: 200,
+ ),
+ ),
+ ScrambleAnimatedText('Explorer'),
+ ],
+ controller: scrambleController,
+ onTap: onTap,
+ isRepeatingAnimation: true,
+ totalRepeatCount: 10,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
];
}
diff --git a/lib/animated_text_kit.dart b/lib/animated_text_kit.dart
index c10b450..ea5b6ad 100644
--- a/lib/animated_text_kit.dart
+++ b/lib/animated_text_kit.dart
@@ -12,3 +12,5 @@ export 'src/scale.dart';
export 'src/text_liquid_fill.dart';
export 'src/wavy.dart';
export 'src/flicker.dart';
+export 'src/scramble.dart';
+export 'src/bounce.dart';
diff --git a/lib/src/animated_text.dart b/lib/src/animated_text.dart
index 72c5741..f2151b8 100644
--- a/lib/src/animated_text.dart
+++ b/lib/src/animated_text.dart
@@ -193,18 +193,21 @@ class AnimatedTextKitState extends State
@override
Widget build(BuildContext context) {
final completeText = _currentAnimatedText.completeText(context);
- return GestureDetector(
- behavior: HitTestBehavior.opaque,
- onTap: _onTap,
- child: _animatedTextController.state ==
- AnimatedTextState.pausedBetweenAnimations ||
- !_controller.isAnimating
- ? completeText
- : AnimatedBuilder(
- animation: _controller,
- builder: _currentAnimatedText.animatedBuilder,
- child: completeText,
- ),
+ return Semantics(
+ label: _currentAnimatedText.text,
+ child: GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onTap: _onTap,
+ child: _animatedTextController.state ==
+ AnimatedTextState.pausedBetweenAnimations ||
+ !_controller.isAnimating
+ ? completeText
+ : AnimatedBuilder(
+ animation: _controller,
+ builder: _currentAnimatedText.animatedBuilder,
+ child: completeText,
+ ),
+ ),
);
}
@@ -244,9 +247,9 @@ class AnimatedTextKitState extends State
_currentAnimatedText = widget.animatedTexts[_index];
_controller = AnimationController(
- duration: _currentAnimatedText.duration,
- vsync: this,
- );
+ duration: _currentAnimatedText.duration,
+ vsync: this,
+ animationBehavior: AnimationBehavior.preserve);
_currentAnimatedText.initAnimation(_controller);
diff --git a/lib/src/bounce.dart b/lib/src/bounce.dart
new file mode 100644
index 0000000..691bd5f
--- /dev/null
+++ b/lib/src/bounce.dart
@@ -0,0 +1,126 @@
+import 'package:flutter/material.dart';
+import 'animated_text.dart';
+
+/// Animated Text that displays a [Text] element with a bouncing effect.
+///
+/// The text bounces in from below with an elastic curve, creating a playful
+/// and dynamic entrance animation.
+class BounceAnimatedText extends AnimatedText {
+ /// The height from which the text bounces in.
+ ///
+ /// By default it is set to 50.0.
+ final double bounceHeight;
+
+ /// The [Curve] of the bounce animation.
+ ///
+ /// By default it is set to Curves.elasticOut for a natural bounce effect.
+ final Curve curve;
+
+ BounceAnimatedText(
+ String text, {
+ TextAlign textAlign = TextAlign.start,
+ TextStyle? textStyle,
+ Duration duration = const Duration(milliseconds: 1500),
+ this.bounceHeight = 50.0,
+ this.curve = Curves.elasticOut,
+ }) : super(
+ text: text,
+ textAlign: textAlign,
+ textStyle: textStyle,
+ duration: duration,
+ );
+
+ late Animation _bounce;
+ late Animation _fade;
+
+ @override
+ void initAnimation(AnimationController controller) {
+ _bounce = Tween(begin: bounceHeight, end: 0.0).animate(
+ CurvedAnimation(
+ parent: controller,
+ curve: curve,
+ ),
+ );
+
+ _fade = Tween(begin: 0.0, end: 1.0).animate(
+ CurvedAnimation(
+ parent: controller,
+ curve: const Interval(0.0, 0.5, curve: Curves.easeIn),
+ ),
+ );
+ }
+
+ @override
+ Widget animatedBuilder(BuildContext context, Widget? child) {
+ return Transform.translate(
+ offset: Offset(0, _bounce.value),
+ child: Opacity(
+ opacity: _fade.value,
+ child: textWidget(text),
+ ),
+ );
+ }
+}
+
+/// Animation that displays [text] elements with a bouncing effect, one at a time.
+@Deprecated('Use AnimatedTextKit with BounceAnimatedText instead.')
+class BounceAnimatedTextKit extends AnimatedTextKit {
+ BounceAnimatedTextKit({
+ Key? key,
+ required List text,
+ TextAlign textAlign = TextAlign.start,
+ TextStyle? textStyle,
+ double bounceHeight = 50.0,
+ Duration duration = const Duration(milliseconds: 1500),
+ Duration pause = const Duration(milliseconds: 1000),
+ VoidCallback? onTap,
+ void Function(int, bool)? onNext,
+ void Function(int, bool)? onNextBeforePause,
+ VoidCallback? onFinished,
+ bool isRepeatingAnimation = true,
+ int totalRepeatCount = 3,
+ bool repeatForever = false,
+ bool displayFullTextOnTap = false,
+ bool stopPauseOnTap = false,
+ Curve curve = Curves.elasticOut,
+ }) : super(
+ key: key,
+ animatedTexts: _animatedTexts(
+ text,
+ textAlign,
+ textStyle,
+ duration,
+ bounceHeight,
+ curve,
+ ),
+ pause: pause,
+ displayFullTextOnTap: displayFullTextOnTap,
+ stopPauseOnTap: stopPauseOnTap,
+ onTap: onTap,
+ onNext: onNext,
+ onNextBeforePause: onNextBeforePause,
+ onFinished: onFinished,
+ isRepeatingAnimation: isRepeatingAnimation,
+ totalRepeatCount: totalRepeatCount,
+ repeatForever: repeatForever,
+ );
+
+ static List _animatedTexts(
+ List text,
+ TextAlign textAlign,
+ TextStyle? textStyle,
+ Duration duration,
+ double bounceHeight,
+ Curve curve,
+ ) =>
+ text
+ .map((text) => BounceAnimatedText(
+ text,
+ textAlign: textAlign,
+ textStyle: textStyle,
+ duration: duration,
+ bounceHeight: bounceHeight,
+ curve: curve,
+ ))
+ .toList();
+}
diff --git a/lib/src/scramble.dart b/lib/src/scramble.dart
new file mode 100644
index 0000000..db54c8d
--- /dev/null
+++ b/lib/src/scramble.dart
@@ -0,0 +1,118 @@
+import 'dart:async';
+import 'dart:math';
+
+import 'package:animated_text_kit/animated_text_kit.dart';
+import 'package:flutter/material.dart';
+
+class ScrambleAnimatedText extends AnimatedText {
+ // duration of single character
+ final Duration speed;
+
+ ScrambleAnimatedText(
+ String text, {
+ TextAlign textAlign = TextAlign.start,
+ TextStyle? textStyle,
+ this.speed = const Duration(milliseconds: 500),
+ }) : super(
+ text: text,
+ textAlign: textAlign,
+ textStyle: textStyle,
+ duration: speed * text.characters.length,
+ );
+
+ late Animation _scrambleText;
+
+ @override
+ void initAnimation(AnimationController controller) {
+ _scrambleText = CurveTween(
+ curve: Curves.linear,
+ ).animate(controller);
+ }
+
+ @override
+ Widget animatedBuilder(context, child) {
+ // get the count / text text length base from the animation value
+ final count = (_scrambleText.value * textCharacters.length).round();
+
+ return RichText(
+ text: TextSpan(
+ children: List.generate(
+ count,
+ (index) {
+ return WidgetSpan(
+ child: ScrambledChar(
+ char: textCharacters.elementAt(index),
+ scrambleDuration: speed,
+ style: textStyle,
+ ),
+ );
+ },
+ ),
+ ),
+ );
+ }
+}
+
+class ScrambledChar extends StatefulWidget {
+ final String char;
+ final Duration scrambleDuration;
+ final TextStyle? style;
+
+ const ScrambledChar({
+ required this.char,
+ this.style,
+ required this.scrambleDuration,
+ }) : assert(char.length == 1, "Char need to only 1 character");
+
+ @override
+ State createState() => _ScrambledCharState();
+}
+
+class _ScrambledCharState extends State {
+ final Random _random = Random();
+ static const String _allChar = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ 'abcdefghijklmnopqrstuvwxyz'
+ '0123456789'
+ '!@#\$%^&*()_+-=[]{}|;:\'",.<>?/`~';
+
+ late String _displayChar;
+ late Timer _timer;
+
+ @override
+ void initState() {
+ super.initState();
+ _displayChar = _generateScrambledChar(widget.char);
+ _startScrambling();
+ }
+
+ @override
+ void dispose() {
+ _timer.cancel();
+ super.dispose();
+ }
+
+ String _generateScrambledChar(String text) {
+ return _allChar[_random.nextInt(_allChar.length)];
+ }
+
+ void _startScrambling() {
+ int elapsed = 0;
+ _timer = Timer.periodic(const Duration(milliseconds: 50), (timer) {
+ if (elapsed >= widget.scrambleDuration.inMilliseconds) {
+ setState(() => _displayChar = widget.char);
+ timer.cancel();
+ } else {
+ setState(() => _displayChar = _generateScrambledChar(widget.char));
+ elapsed += 50;
+ }
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Text(
+ _displayChar,
+ style: widget.style,
+ );
+ }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 1fbd1cf..739a8f9 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: animated_text_kit
description: A flutter package project which contains a collection of cool and beautiful text animations.
-version: 4.2.3
+version: 4.3.0
homepage: https://github.com/aagarwal1012/Animated-Text-Kit/
maintainer: Ayush Agarwal (@aagarwal1012)