diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fa6caaa..bcb377b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.5.0] + +* **Feat**: [309](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/309) Introduced animated typewriter functionality, allowing developers to seamlessly integrate the chat view for chatbot implementations. + ## [2.4.0] * **Feat**: [251](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/251) Add diff --git a/example/lib/main.dart b/example/lib/main.dart index c95024a9..bb6952cd 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -65,6 +65,11 @@ class _ChatScreenState extends State { profilePhoto: Data.profileImage, ), ], + + ///Uncomment to enable typewriter functionality + // typewriterAnimatedConfiguration: TypewriterAnimatedConfiguration( + // enableConfiguration: true, + // ), ); void _showHideTypingIndicator() { diff --git a/lib/chatview.dart b/lib/chatview.dart index 6965fa33..32b71901 100644 --- a/lib/chatview.dart +++ b/lib/chatview.dart @@ -38,3 +38,4 @@ export 'package:audio_waveforms/audio_waveforms.dart' export 'src/models/config_models/receipts_widget_config.dart'; export 'src/extensions/extensions.dart' show MessageTypes; export 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; +export '/src/models/config_models/typing_configuration.dart'; diff --git a/lib/src/controller/chat_controller.dart b/lib/src/controller/chat_controller.dart index 6583577a..f361cc43 100644 --- a/lib/src/controller/chat_controller.dart +++ b/lib/src/controller/chat_controller.dart @@ -21,6 +21,7 @@ */ import 'dart:async'; +import 'package:chatview/src/models/config_models/typing_configuration.dart'; import 'package:chatview/src/widgets/suggestions/suggestion_list.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -74,12 +75,27 @@ class ChatController { /// Provides current user which is sending messages. final ChatUser currentUser; + ///Configuration for Animated Typewriter functionality as in a chatbot. + TypewriterAnimatedConfiguration typewriterAnimatedConfiguration; + + ///Gives us control of the TextEditting controller of the TextField of the current user + TextEditingController textEdittingController; + + ///Enable adding more function to what already exits on the [onChange] callback of the [TextField] + VoidCallback? onChangedFunct; + ChatController({ required this.initialMessageList, required this.scrollController, required this.otherUsers, required this.currentUser, - }); + this.onChangedFunct, + TextEditingController? textEdittingController, + TypewriterAnimatedConfiguration? typewriterAnimatedConfiguration, + }) : typewriterAnimatedConfiguration = typewriterAnimatedConfiguration ?? + TypewriterAnimatedConfiguration(), + textEdittingController = + textEdittingController ?? TextEditingController(); /// Represents message stream of chat StreamController> messageStreamController = StreamController(); diff --git a/lib/src/models/config_models/chat_bubble_configuration.dart b/lib/src/models/config_models/chat_bubble_configuration.dart index 09cfcb38..299d1686 100644 --- a/lib/src/models/config_models/chat_bubble_configuration.dart +++ b/lib/src/models/config_models/chat_bubble_configuration.dart @@ -47,6 +47,9 @@ class ChatBubbleConfiguration { /// Provides callback when user tap twice on chat bubble. final MessageCallBack? onDoubleTap; + /// Provides callback when user tap on chat bubble. + final MessageCallBack? onTap; + final ReceiptsWidgetConfig? receiptsWidgetConfig; /// A flag to disable link preview functionality. @@ -68,5 +71,6 @@ class ChatBubbleConfiguration { this.onDoubleTap, this.receiptsWidgetConfig, this.disableLinkPreview = false, + this.onTap, }); } diff --git a/lib/src/models/config_models/send_message_configuration.dart b/lib/src/models/config_models/send_message_configuration.dart index 8909d7a3..8822e77d 100644 --- a/lib/src/models/config_models/send_message_configuration.dart +++ b/lib/src/models/config_models/send_message_configuration.dart @@ -32,9 +32,12 @@ class SendMessageConfiguration { /// Used to give background color to text field. final Color? textFieldBackgroundColor; - /// Used to give color to send button. + /// Used to give color to send button icon. final Color? defaultSendButtonColor; + /// Used to give color to send button background. + final Color? defaultSendButtonBackgroundColor; + /// Provides ability to give custom send button. final Widget? sendButtonIcon; @@ -59,6 +62,9 @@ class SendMessageConfiguration { /// Provides configuration of text field. final TextFieldConfiguration? textFieldConfig; + ///For adding other actions component to the textfield + final List actionsWidget; + /// Enable/disable voice recording. Enabled by default. final bool allowRecordingVoice; @@ -94,6 +100,8 @@ class SendMessageConfiguration { this.voiceRecordingConfiguration, this.micIconColor, this.cancelRecordConfiguration, + this.defaultSendButtonBackgroundColor, + this.actionsWidget = const [], }); } diff --git a/lib/src/models/config_models/typing_configuration.dart b/lib/src/models/config_models/typing_configuration.dart new file mode 100644 index 00000000..25d1998e --- /dev/null +++ b/lib/src/models/config_models/typing_configuration.dart @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022 Simform Solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import 'package:animated_text_kit/animated_text_kit.dart'; + +///Configuration for Animated Typewriter functionality as in a chatbot. +class TypewriterAnimatedConfiguration { + ///Toggle to enable + /// By default it is set to false. + final bool enableConfiguration; + + /// 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. + AnimatedTextController? controller; + + /// Should the animation ends up early and display full text if you tap on it? + /// + /// By default it is set to false. + final bool displayFullTextOnTap; + + ///The [Duration] of the delay between the apparition of each characters + + ///By default it is set to 50 milliseconds. + final Duration duration; + + TypewriterAnimatedConfiguration({ + this.displayFullTextOnTap = false, + this.controller, + this.enableConfiguration = false, + this.duration = const Duration(milliseconds: 50), + }); +} diff --git a/lib/src/widgets/chat_bubble_widget.dart b/lib/src/widgets/chat_bubble_widget.dart index 4c748f96..5d999185 100644 --- a/lib/src/widgets/chat_bubble_widget.dart +++ b/lib/src/widgets/chat_bubble_widget.dart @@ -297,6 +297,7 @@ class _ChatBubbleWidgetState extends State { ) : null : null, + onTap: chatListConfig.chatBubbleConfig?.onTap, shouldHighlight: widget.shouldHighlight, controller: chatController, highlightColor: chatListConfig.repliedMessageConfig diff --git a/lib/src/widgets/chatui_textfield.dart b/lib/src/widgets/chatui_textfield.dart index ec6cf0bc..e23fc6ee 100644 --- a/lib/src/widgets/chatui_textfield.dart +++ b/lib/src/widgets/chatui_textfield.dart @@ -41,6 +41,7 @@ class ChatUITextField extends StatefulWidget { required this.onPressed, required this.onRecordingComplete, required this.onImageSelected, + required this.onChangedFunction, }) : super(key: key); /// Provides configuration of default text field in chat. @@ -61,6 +62,8 @@ class ChatUITextField extends StatefulWidget { /// Provides callback when user select images from camera/gallery. final StringsCallBack onImageSelected; + final VoidCallback? onChangedFunction; + @override State createState() => _ChatUITextFieldState(); } @@ -85,6 +88,8 @@ class _ChatUITextFieldState extends State { TextFieldConfiguration? get textFieldConfig => sendMessageConfig?.textFieldConfig; + List get actionsWidget => sendMessageConfig?.actionsWidget ?? []; + CancelRecordConfiguration? get cancelRecordConfiguration => sendMessageConfig?.cancelRecordConfiguration; @@ -172,133 +177,183 @@ class _ChatUITextFieldState extends State { ) else Expanded( - child: TextField( - focusNode: widget.focusNode, - controller: widget.textEditingController, - style: textFieldConfig?.textStyle ?? - const TextStyle(color: Colors.white), - maxLines: textFieldConfig?.maxLines ?? 5, - minLines: textFieldConfig?.minLines ?? 1, - keyboardType: textFieldConfig?.textInputType, - inputFormatters: textFieldConfig?.inputFormatters, - onChanged: _onChanged, - enabled: textFieldConfig?.enabled, - textCapitalization: textFieldConfig?.textCapitalization ?? - TextCapitalization.sentences, - decoration: InputDecoration( - hintText: - textFieldConfig?.hintText ?? PackageStrings.message, - fillColor: sendMessageConfig?.textFieldBackgroundColor ?? - Colors.white, - filled: true, - hintStyle: textFieldConfig?.hintStyle ?? - TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Colors.grey.shade600, - letterSpacing: 0.25, - ), - contentPadding: textFieldConfig?.contentPadding ?? - const EdgeInsets.symmetric(horizontal: 6), - border: outlineBorder, - focusedBorder: outlineBorder, - enabledBorder: outlineBorder, - disabledBorder: outlineBorder, - ), - ), - ), - ValueListenableBuilder( - valueListenable: _inputText, - builder: (_, inputTextValue, child) { - if (inputTextValue.isNotEmpty) { - return IconButton( - color: sendMessageConfig?.defaultSendButtonColor ?? - Colors.green, - onPressed: (textFieldConfig?.enabled ?? true) - ? () { - widget.onPressed(); - _inputText.value = ''; - } - : null, - icon: sendMessageConfig?.sendButtonIcon ?? - const Icon(Icons.send), - ); - } else { - return Row( - children: [ - if (!isRecordingValue) ...[ - if (sendMessageConfig?.enableCameraImagePicker ?? - true) - IconButton( - constraints: const BoxConstraints(), - onPressed: (textFieldConfig?.enabled ?? true) - ? () => _onIconPressed( - ImageSource.camera, - config: sendMessageConfig - ?.imagePickerConfiguration, - ) - : null, - icon: imagePickerIconsConfig - ?.cameraImagePickerIcon ?? - Icon( - Icons.camera_alt_outlined, - color: - imagePickerIconsConfig?.cameraIconColor, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + TextField( + focusNode: widget.focusNode, + autofocus: false, + controller: widget.textEditingController, + style: textFieldConfig?.textStyle ?? + const TextStyle(color: Colors.white), + maxLines: textFieldConfig?.maxLines ?? 5, + minLines: textFieldConfig?.minLines ?? 1, + keyboardType: textFieldConfig?.textInputType, + inputFormatters: textFieldConfig?.inputFormatters, + onChanged: _onChanged, + enabled: textFieldConfig?.enabled, + textCapitalization: + textFieldConfig?.textCapitalization ?? + TextCapitalization.sentences, + decoration: InputDecoration( + hintText: textFieldConfig?.hintText ?? + PackageStrings.message, + fillColor: + sendMessageConfig?.textFieldBackgroundColor ?? + Colors.white, + filled: true, + hintStyle: textFieldConfig?.hintStyle ?? + TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Colors.grey.shade600, + letterSpacing: 0.25, + ), + contentPadding: textFieldConfig?.contentPadding ?? + const EdgeInsets.symmetric(horizontal: 6), + border: outlineBorder, + focusedBorder: outlineBorder, + enabledBorder: outlineBorder, + disabledBorder: outlineBorder, + ), + ), + ValueListenableBuilder( + valueListenable: _inputText, + builder: (_, inputTextValue, child) { + final bool textFieldValueNotEmpty = + inputTextValue.isNotEmpty || + widget.textEditingController.text.isNotEmpty; + return Padding( + padding: const EdgeInsets.only( + bottom: 4, left: 5, right: 5, top: 2), + child: Row( + children: [ + if (!textFieldValueNotEmpty) + Row( + children: [ + if (!isRecordingValue) ...[ + if (sendMessageConfig + ?.enableCameraImagePicker ?? + true) + IconButton( + constraints: const BoxConstraints(), + onPressed: + (textFieldConfig?.enabled ?? + true) + ? () => _onIconPressed( + ImageSource.camera, + config: sendMessageConfig + ?.imagePickerConfiguration, + ) + : null, + icon: imagePickerIconsConfig + ?.cameraImagePickerIcon ?? + Icon( + Icons.camera_alt_outlined, + color: imagePickerIconsConfig + ?.cameraIconColor, + ), + ), + if (sendMessageConfig + ?.enableGalleryImagePicker ?? + true) + IconButton( + constraints: const BoxConstraints(), + onPressed: + (textFieldConfig?.enabled ?? + true) + ? () => _onIconPressed( + ImageSource.gallery, + config: sendMessageConfig + ?.imagePickerConfiguration, + ) + : null, + icon: imagePickerIconsConfig + ?.galleryImagePickerIcon ?? + Icon( + Icons.image, + color: imagePickerIconsConfig + ?.galleryIconColor, + ), + ), + ], + if ((sendMessageConfig + ?.allowRecordingVoice ?? + false) && + !kIsWeb && + (Platform.isIOS || + Platform.isAndroid)) + IconButton( + onPressed: + (textFieldConfig?.enabled ?? true) + ? _recordOrStop + : null, + icon: (isRecordingValue + ? voiceRecordingConfig + ?.stopIcon + : voiceRecordingConfig + ?.micIcon) ?? + Icon( + isRecordingValue + ? Icons.stop + : Icons.mic, + color: voiceRecordingConfig + ?.recorderIconColor, + ), + ), + if (isRecordingValue && + cancelRecordConfiguration != null) + IconButton( + onPressed: () { + cancelRecordConfiguration?.onCancel + ?.call(); + _cancelRecording(); + }, + icon: cancelRecordConfiguration + ?.icon ?? + const Icon(Icons.cancel_outlined), + color: cancelRecordConfiguration + ?.iconColor ?? + voiceRecordingConfig + ?.recorderIconColor, + ), + ], ), - ), - if (sendMessageConfig?.enableGalleryImagePicker ?? - true) - IconButton( - constraints: const BoxConstraints(), - onPressed: (textFieldConfig?.enabled ?? true) - ? () => _onIconPressed( - ImageSource.gallery, - config: sendMessageConfig - ?.imagePickerConfiguration, - ) - : null, - icon: imagePickerIconsConfig - ?.galleryImagePickerIcon ?? - Icon( - Icons.image, - color: imagePickerIconsConfig - ?.galleryIconColor, + ...actionsWidget, + const Spacer(), + if (textFieldValueNotEmpty) + InkWell( + onTap: (textFieldConfig?.enabled ?? true) + ? () { + widget.onPressed(); + _inputText.value = ''; + } + : null, + child: Container( + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: sendMessageConfig + ?.defaultSendButtonBackgroundColor ?? + Colors.black, + ), + child: Icon( + Icons.arrow_upward_rounded, + color: sendMessageConfig + ?.defaultSendButtonColor ?? + Colors.white, + ), + ), ), + ], ), - ], - if ((sendMessageConfig?.allowRecordingVoice ?? false) && - !kIsWeb && - (Platform.isIOS || Platform.isAndroid)) - IconButton( - onPressed: (textFieldConfig?.enabled ?? true) - ? _recordOrStop - : null, - icon: (isRecordingValue - ? voiceRecordingConfig?.stopIcon - : voiceRecordingConfig?.micIcon) ?? - Icon( - isRecordingValue ? Icons.stop : Icons.mic, - color: - voiceRecordingConfig?.recorderIconColor, - ), - ), - if (isRecordingValue && - cancelRecordConfiguration != null) - IconButton( - onPressed: () { - cancelRecordConfiguration?.onCancel?.call(); - _cancelRecording(); - }, - icon: cancelRecordConfiguration?.icon ?? - const Icon(Icons.cancel_outlined), - color: cancelRecordConfiguration?.iconColor ?? - voiceRecordingConfig?.recorderIconColor, - ), - ], - ); - } - }, - ), + ); + }, + ), + ], + ), + ), ], ); }, @@ -374,6 +429,9 @@ class _ChatUITextFieldState extends State { } void _onChanged(String inputText) { + if (widget.onChangedFunction != null) { + widget.onChangedFunction!(); + } debouncer.run(() { composingStatus.value = TypeWriterStatus.typed; }, () { diff --git a/lib/src/widgets/message_view.dart b/lib/src/widgets/message_view.dart index 2d1215f5..f97f8c66 100644 --- a/lib/src/widgets/message_view.dart +++ b/lib/src/widgets/message_view.dart @@ -48,6 +48,7 @@ class MessageView extends StatefulWidget { this.messageConfig, this.onMaxDuration, this.controller, + this.onTap, }) : super(key: key); /// Provides message instance of chat. @@ -74,6 +75,9 @@ class MessageView extends StatefulWidget { /// Allow user to set some action when user double tap on chat bubble. final MessageCallBack? onDoubleTap; + /// Allow user to set some action when user tap on chat bubble. + final MessageCallBack? onTap; + /// Allow users to pass colour of chat bubble when user taps on replied message. final Color highlightColor; @@ -131,26 +135,45 @@ class _MessageViewState extends State @override Widget build(BuildContext context) { - return GestureDetector( - onLongPressStart: isLongPressEnable ? _onLongPressStart : null, - onDoubleTap: () { - if (widget.onDoubleTap != null) widget.onDoubleTap!(widget.message); - }, - child: (() { - if (isLongPressEnable) { - return AnimatedBuilder( - builder: (_, __) { - return Transform.scale( - scale: 1 - _animationController!.value, - child: _messageView, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + onLongPressStart: isLongPressEnable ? _onLongPressStart : null, + onDoubleTap: () { + if (widget.onDoubleTap != null) widget.onDoubleTap!(widget.message); + }, + child: (() { + if (isLongPressEnable) { + return AnimatedBuilder( + builder: (_, __) { + return Transform.scale( + scale: 1 - _animationController!.value, + child: _messageView, + ); + }, + animation: _animationController!, ); - }, - animation: _animationController!, - ); - } else { - return _messageView; - } - }()), + } else { + return _messageView; + } + }()), + ), + //TODO: Maybe we can provide other options and also make it more customizable + if (!widget.isMessageBySender) + Padding( + padding: const EdgeInsets.only(left: 15), + child: InkWell( + onTap: () { + if (widget.onTap != null) { + widget.onTap!(widget.message); + } + }, + child: + Text('Report', style: Theme.of(context).textTheme.bodySmall), + ), + ), + ], ); } diff --git a/lib/src/widgets/send_message_widget.dart b/lib/src/widgets/send_message_widget.dart index e284517c..3c41f304 100644 --- a/lib/src/widgets/send_message_widget.dart +++ b/lib/src/widgets/send_message_widget.dart @@ -31,6 +31,7 @@ import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import '../utils/constants/constants.dart'; +import 'chat_view_inherited_widget.dart'; class SendMessageWidget extends StatefulWidget { const SendMessageWidget({ @@ -70,7 +71,7 @@ class SendMessageWidget extends StatefulWidget { } class SendMessageWidgetState extends State { - final _textEditingController = TextEditingController(); + late TextEditingController textEditingController; final ValueNotifier _replyMessage = ValueNotifier(const ReplyMessage()); @@ -99,6 +100,8 @@ class SendMessageWidgetState extends State { Widget build(BuildContext context) { final scrollToBottomButtonConfig = chatListConfig.scrollToBottomButtonConfig; + final cntrl = ChatViewInheritedWidget.of(context)!.chatController; + textEditingController = cntrl.textEdittingController; return Align( alignment: Alignment.bottomCenter, child: widget.sendMessageBuilder != null @@ -258,11 +261,12 @@ class SendMessageWidgetState extends State { ), ChatUITextField( focusNode: _focusNode, - textEditingController: _textEditingController, + textEditingController: textEditingController, onPressed: _onPressed, sendMessageConfig: widget.sendMessageConfig, onRecordingComplete: _onRecordingComplete, onImageSelected: _onImageSelected, + onChangedFunction: cntrl.onChangedFunct, ) ], ), @@ -298,8 +302,8 @@ class SendMessageWidgetState extends State { } void _onPressed() { - final messageText = _textEditingController.text.trim(); - _textEditingController.clear(); + final messageText = textEditingController.text.trim(); + textEditingController.clear(); if (messageText.isEmpty) return; widget.onSendTap.call( @@ -340,7 +344,7 @@ class SendMessageWidgetState extends State { @override void dispose() { - _textEditingController.dispose(); + textEditingController.dispose(); _focusNode.dispose(); _replyMessage.dispose(); super.dispose(); diff --git a/lib/src/widgets/text_message_view.dart b/lib/src/widgets/text_message_view.dart index 4dacc90b..d3e2cfe5 100644 --- a/lib/src/widgets/text_message_view.dart +++ b/lib/src/widgets/text_message_view.dart @@ -19,6 +19,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ +import 'package:animated_text_kit/animated_text_kit.dart'; +import 'package:chatview/src/widgets/chat_view_inherited_widget.dart'; import 'package:flutter/material.dart'; import 'package:chatview/src/extensions/extensions.dart'; @@ -69,6 +71,7 @@ class TextMessageView extends StatelessWidget { Widget build(BuildContext context) { final textTheme = Theme.of(context).textTheme; final textMessage = message.message; + final chatController = ChatViewInheritedWidget.of(context)!.chatController; return Stack( clipBehavior: Clip.none, children: [ @@ -93,14 +96,38 @@ class TextMessageView extends StatelessWidget { linkPreviewConfig: _linkPreviewConfig, url: textMessage, ) - : Text( - textMessage, - style: _textStyle ?? - textTheme.bodyMedium!.copyWith( - color: Colors.white, - fontSize: 16, - ), - ), + : !isMessageBySender && + chatController.typewriterAnimatedConfiguration + .enableConfiguration && + chatController.initialMessageList.isNotEmpty + ? AnimatedTextKit( + animatedTexts: [ + TypewriterAnimatedText( + textMessage, + textStyle: _textStyle ?? + textTheme.bodyMedium!.copyWith( + color: Colors.white, + fontSize: 16, + ), + speed: chatController + .typewriterAnimatedConfiguration.duration, + ), + ], + isRepeatingAnimation: false, + displayFullTextOnTap: chatController + .typewriterAnimatedConfiguration.displayFullTextOnTap, + controller: chatController + .typewriterAnimatedConfiguration.controller, + totalRepeatCount: 1, + ) + : Text( + textMessage, + style: _textStyle ?? + textTheme.bodyMedium!.copyWith( + color: Colors.white, + fontSize: 16, + ), + ), ), if (message.reaction.reactions.isNotEmpty) ReactionWidget( diff --git a/pubspec.yaml b/pubspec.yaml index 5ad50dfe..d999cf55 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: chatview description: A Flutter package that allows you to integrate Chat View with highly customization options. -version: 2.4.0 +version: 2.5.0 issue_tracker: https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues repository: https://github.com/SimformSolutionsPvtLtd/flutter_chatview @@ -26,6 +26,7 @@ dependencies: audio_waveforms: ^1.2.0 # For formatting time locale in message receipts cached_network_image: ^3.4.1 + animated_text_kit: ^4.2.3 dev_dependencies: flutter_test: