diff --git a/CHANGELOG.md b/CHANGELOG.md index 9243e70..b3041bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.2.0 + +- feat: add freeze and unfreeze methods on controller to pause and resume animation +- docs(README): update documentation on freeze and unfreeze method +- docs(example): update example app with the freeze and unfreeze method showcases + ## 0.1.1 - chore(deps): bump to flutter version 3.16.0 diff --git a/README.md b/README.md index 4abfa84..ad9d564 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-purple.svg)](https://opensource.org/licenses/MIT) [![Package Version](https://img.shields.io/pub/v/typethis.svg)](https://pub.dev/packages/typethis) -A flutter package that aims to simplify versatile typing animation with reset functionality. +A flutter package that aims to simplify versatile typing animation with reset, freeze and unfreeze functionality. ## Create a TypeThis widget @@ -27,9 +27,23 @@ final typeThisWidget = TypeThis( typeThisWidget.controller.reset(); ``` +## Freeze (pause) the animation + +```dart +// Call the `freeze()` method on controller to freeze/pause the typing animation. +typeThisWidget.controller.freeze(); +``` + +## Unfreeze (resume) the animation + +```dart +// Call the `unfreeze()` method on controller to unfreeze/resume the typing animation. +typeThisWidget.controller.unfreeze(); +``` + ## Demo -[![Demo](screenshots/typethis.gif)](https://github.com/thecodexhub/typethis) +[![Demo](demo/typethis.gif)](https://github.com/thecodexhub/typethis) ## License @@ -38,9 +52,6 @@ The project is released under the [MIT License](LICENSE). Learn more about it, [ ---

- - thecodexhub -

Developed and Maintained with 💜 by thecodexhub

diff --git a/demo/typethis.gif b/demo/typethis.gif new file mode 100644 index 0000000..d497cdf Binary files /dev/null and b/demo/typethis.gif differ diff --git a/example/lib/main.dart b/example/lib/main.dart index b451b00..098bd22 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:example/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:typethis/typethis.dart'; @@ -13,24 +14,24 @@ class MyApp extends StatelessWidget { final typeThisWidget = TypeThis( string: 'Hi there! How are you doing?', speed: 100, - style: const TextStyle(fontSize: 20), + style: const TextStyle(fontSize: 18), ); return MaterialApp( title: 'Flutter TypeThis Demo', - theme: ThemeData( - primarySwatch: Colors.deepPurple, - ), + theme: ThemeData(primarySwatch: Colors.deepPurple), home: Scaffold( appBar: AppBar( title: const Text('TypeThis Example'), + backgroundColor: Colors.grey[200], ), body: Center( - child: typeThisWidget, - ), - floatingActionButton: FloatingActionButton( - onPressed: () => typeThisWidget.controller.reset(), - child: const Icon(Icons.refresh), + child: CustomDecoratedBox( + typeThisWidget: typeThisWidget, + onResetPressed: () => typeThisWidget.controller.reset(), + onFreezePressed: () => typeThisWidget.controller.freeze(), + onUnfreezePressed: () => typeThisWidget.controller.unfreeze(), + ), ), ), ); diff --git a/example/lib/widgets/custom_decorated_box.dart b/example/lib/widgets/custom_decorated_box.dart new file mode 100644 index 0000000..78b131a --- /dev/null +++ b/example/lib/widgets/custom_decorated_box.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; + +class CustomDecoratedBox extends StatelessWidget { + const CustomDecoratedBox({ + super.key, + required this.typeThisWidget, + required this.onResetPressed, + required this.onFreezePressed, + required this.onUnfreezePressed, + }); + final Widget typeThisWidget; + final VoidCallback onResetPressed; + final VoidCallback onFreezePressed; + final VoidCallback onUnfreezePressed; + + @override + Widget build(BuildContext context) { + return Container( + constraints: const BoxConstraints(maxWidth: 550), + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 24), + const _BuildDocumentation(), + const SizedBox(height: 24), + Container( + height: 60, + width: double.infinity, + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: Colors.grey[200], + ), + child: typeThisWidget, + ), + const SizedBox(height: 24), + Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + OutlinedButton.icon( + onPressed: onResetPressed, + icon: const Icon(Icons.refresh), + label: const Text('Reset'), + ), + const SizedBox(width: 12), + OutlinedButton.icon( + onPressed: onFreezePressed, + icon: const Icon(Icons.pause), + label: const Text('Freeze'), + ), + const SizedBox(width: 12), + OutlinedButton.icon( + onPressed: onUnfreezePressed, + icon: const Icon(Icons.play_arrow), + label: const Text('Unfreeze'), + ), + ], + ), + ], + ), + ); + } +} + +class _BuildDocumentation extends StatelessWidget { + const _BuildDocumentation(); + + @override + Widget build(BuildContext context) { + return const Text.rich( + TextSpan( + style: TextStyle(fontSize: 16), + children: [ + TextSpan( + text: '• reset() : ', + style: TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan( + text: 'Resets the typing animation.', + ), + TextSpan( + text: '\n• freeze() : ', + style: TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan( + text: 'Freezes / pauses the typing animation.', + ), + TextSpan( + text: '\n• unfreeze() : ', + style: TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan( + text: 'Resumes the typing animation.', + ), + ], + ), + ); + } +} diff --git a/example/lib/widgets/widgets.dart b/example/lib/widgets/widgets.dart new file mode 100644 index 0000000..50ada98 --- /dev/null +++ b/example/lib/widgets/widgets.dart @@ -0,0 +1 @@ +export 'custom_decorated_box.dart'; diff --git a/lib/src/typethis_controller.dart b/lib/src/typethis_controller.dart index 2d6953c..46b6212 100644 --- a/lib/src/typethis_controller.dart +++ b/lib/src/typethis_controller.dart @@ -68,6 +68,34 @@ class TypeThisController with ChangeNotifier { _startTimerAndNotify(); } + /// Freezes the typing animation. + /// + /// ```dart + /// // Extract the [TypeThis] widget into a separate variable. + /// final typeThisWidget = TypeThis( + /// string: 'Hi there! How are you doing?', + /// speed: 50, + /// style: TextStyle(fontSize: 18, color: Colors.black), + /// ); + /// + /// // Freezes the animation + /// typeThisWidget.controller.freeze(); + /// ``` + void freeze() { + timer?.cancel(); + } + + /// Unfreezes the typing animation, means the animation resumes + /// from where it was frozen last time. + void unfreeze() { + // for safety, if unfreeze() is called without freeze() being called + // previously. + timer?.cancel(); + if (_steps != _maxBoundLength) { + _startTimerAndNotify(); + } + } + @override void dispose() { timer?.cancel(); diff --git a/pubspec.yaml b/pubspec.yaml index 1ab4ae8..3a6ef68 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,11 @@ name: typethis -description: A flutter package that aims to simplify versatile typing animation with reset functionality. +description: A flutter package that aims to simplify versatile typing animation with reset, freeze and unfreeze functionality. repository: https://github.com/thecodexhub/typethis issue_tracker: https://github.com/thecodexhub/typethis/issues homepage: https://github.com/thecodexhub/typethis documentation: https://github.com/thecodexhub/typethis -version: 0.1.1 +version: 0.2.0 environment: sdk: ">=3.1.3 <4.0.0" diff --git a/screenshots/typethis.gif b/screenshots/typethis.gif deleted file mode 100644 index 625d483..0000000 Binary files a/screenshots/typethis.gif and /dev/null differ diff --git a/test/typethis_controller_test.dart b/test/typethis_controller_test.dart index 7e087e4..224f57b 100644 --- a/test/typethis_controller_test.dart +++ b/test/typethis_controller_test.dart @@ -66,5 +66,45 @@ void main() { expect(secondTimer?.isActive, false); expect(controller.timer?.isActive, true); }); + + test('freeze method freezes the controller', () async { + final controller = TypeThisController( + const Duration(milliseconds: 100), + 5, + ); + final previousTimer = controller.timer; + + expect(previousTimer?.isActive, true); + + await Future.delayed(const Duration(milliseconds: 350)); + controller.freeze(); + + expect(controller.timer?.isActive, false); + expect(controller.steps, equals(3)); + }); + + test( + 'unfreeze method resumes the controller from freezing point', + () async { + final controller = TypeThisController( + const Duration(milliseconds: 100), + 7, + ); + final previousTimer = controller.timer; + + expect(previousTimer?.isActive, true); + + await Future.delayed(const Duration(milliseconds: 350)); + controller.freeze(); + + expect(controller.steps, equals(3)); + + controller.unfreeze(); + await Future.delayed(const Duration(milliseconds: 250)); + + expect(controller.timer?.isActive, true); + expect(controller.steps, equals(5)); + }, + ); }); }