From 0725dcd401dd165196ec12535214bfa235b54ebe Mon Sep 17 00:00:00 2001 From: Ezeagu Princewill Date: Fri, 31 Jan 2025 03:08:26 +0100 Subject: [PATCH 1/7] added typing functionality --- lib/src/widgets/text_message_view.dart | 36 ++++++++++++++++++++------ pubspec.yaml | 1 + 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/lib/src/widgets/text_message_view.dart b/lib/src/widgets/text_message_view.dart index 4dacc90b..de00af94 100644 --- a/lib/src/widgets/text_message_view.dart +++ b/lib/src/widgets/text_message_view.dart @@ -19,6 +19,7 @@ * 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:flutter/material.dart'; import 'package:chatview/src/extensions/extensions.dart'; @@ -93,14 +94,33 @@ class TextMessageView extends StatelessWidget { linkPreviewConfig: _linkPreviewConfig, url: textMessage, ) - : Text( - textMessage, - style: _textStyle ?? - textTheme.bodyMedium!.copyWith( - color: Colors.white, - fontSize: 16, - ), - ), + : !isMessageBySender + ? AnimatedTextKit( + animatedTexts: [ + TypewriterAnimatedText( + textMessage, + textStyle: _textStyle ?? + textTheme.bodyMedium!.copyWith( + color: Colors.white, + fontSize: 16, + ), + speed: const Duration(milliseconds: 50), + ), + ], + totalRepeatCount: 0, isRepeatingAnimation: false, + pause: const Duration(milliseconds: 1000), + displayFullTextOnTap: true, + stopPauseOnTap: false, + // controller: myAnimatedTextController, + ) + : 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..420bb1ee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: From 8a6b481e62d0eee1992a3a225647797b15e9c7f7 Mon Sep 17 00:00:00 2001 From: Ezeagu Princewill Date: Fri, 31 Jan 2025 05:23:18 +0100 Subject: [PATCH 2/7] improved the animated typewritter --- lib/chatview.dart | 1 + lib/src/controller/chat_controller.dart | 8 ++- .../config_models/typing_configuration.dart | 49 +++++++++++++++++++ lib/src/widgets/text_message_view.dart | 17 ++++--- 4 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 lib/src/models/config_models/typing_configuration.dart 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..53813d7c 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,17 @@ class ChatController { /// Provides current user which is sending messages. final ChatUser currentUser; + ///Configuration for Animated Typewriter functionality as in a chatbot. + TypewriterAnimatedConfiguration typewriterAnimatedConfiguration; + ChatController({ required this.initialMessageList, required this.scrollController, required this.otherUsers, required this.currentUser, - }); + TypewriterAnimatedConfiguration? typewriterAnimatedConfiguration, + }) : typewriterAnimatedConfiguration = typewriterAnimatedConfiguration ?? + TypewriterAnimatedConfiguration(); /// Represents message stream of chat StreamController> messageStreamController = StreamController(); 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..3ca68b16 --- /dev/null +++ b/lib/src/models/config_models/typing_configuration.dart @@ -0,0 +1,49 @@ +/* + * 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; + + TypewriterAnimatedConfiguration({ + this.displayFullTextOnTap = false, + this.controller, + this.enableConfiguration = false, + }); +} diff --git a/lib/src/widgets/text_message_view.dart b/lib/src/widgets/text_message_view.dart index de00af94..faa4c020 100644 --- a/lib/src/widgets/text_message_view.dart +++ b/lib/src/widgets/text_message_view.dart @@ -20,6 +20,7 @@ * 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'; @@ -70,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: [ @@ -94,7 +96,10 @@ class TextMessageView extends StatelessWidget { linkPreviewConfig: _linkPreviewConfig, url: textMessage, ) - : !isMessageBySender + : !isMessageBySender && + chatController.typewriterAnimatedConfiguration + .enableConfiguration && + chatController.initialMessageList.isNotEmpty ? AnimatedTextKit( animatedTexts: [ TypewriterAnimatedText( @@ -107,11 +112,11 @@ class TextMessageView extends StatelessWidget { speed: const Duration(milliseconds: 50), ), ], - totalRepeatCount: 0, isRepeatingAnimation: false, - pause: const Duration(milliseconds: 1000), - displayFullTextOnTap: true, - stopPauseOnTap: false, - // controller: myAnimatedTextController, + isRepeatingAnimation: false, + displayFullTextOnTap: chatController + .typewriterAnimatedConfiguration.displayFullTextOnTap, + controller: chatController + .typewriterAnimatedConfiguration.controller, ) : Text( textMessage, From 2b755a086de8994ea35a1eb9f903a5ea761acaae Mon Sep 17 00:00:00 2001 From: Ezeagu Princewill Date: Fri, 31 Jan 2025 05:49:08 +0100 Subject: [PATCH 3/7] PR update following contributing guidelines --- CHANGELOG.md | 4 ++++ example/lib/main.dart | 5 +++++ pubspec.yaml | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) 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/pubspec.yaml b/pubspec.yaml index 420bb1ee..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 From 2a7682dc19b147fbda9b4ca1ea026e118b763552 Mon Sep 17 00:00:00 2001 From: Ezeagu Princewill Date: Fri, 31 Jan 2025 19:21:05 +0100 Subject: [PATCH 4/7] ensured animated totalRepeatCount is once --- lib/src/widgets/text_message_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/widgets/text_message_view.dart b/lib/src/widgets/text_message_view.dart index faa4c020..7e1f97d0 100644 --- a/lib/src/widgets/text_message_view.dart +++ b/lib/src/widgets/text_message_view.dart @@ -117,6 +117,7 @@ class TextMessageView extends StatelessWidget { .typewriterAnimatedConfiguration.displayFullTextOnTap, controller: chatController .typewriterAnimatedConfiguration.controller, + totalRepeatCount: 1, ) : Text( textMessage, From 41fba07c54a9a256fc1db44858283bb4b46641a0 Mon Sep 17 00:00:00 2001 From: Ezeagu Princewill Date: Fri, 31 Jan 2025 19:26:13 +0100 Subject: [PATCH 5/7] gave user control of the animation duration --- lib/src/models/config_models/typing_configuration.dart | 6 ++++++ lib/src/widgets/text_message_view.dart | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/src/models/config_models/typing_configuration.dart b/lib/src/models/config_models/typing_configuration.dart index 3ca68b16..25d1998e 100644 --- a/lib/src/models/config_models/typing_configuration.dart +++ b/lib/src/models/config_models/typing_configuration.dart @@ -41,9 +41,15 @@ class TypewriterAnimatedConfiguration { /// 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/text_message_view.dart b/lib/src/widgets/text_message_view.dart index 7e1f97d0..d3e2cfe5 100644 --- a/lib/src/widgets/text_message_view.dart +++ b/lib/src/widgets/text_message_view.dart @@ -109,7 +109,8 @@ class TextMessageView extends StatelessWidget { color: Colors.white, fontSize: 16, ), - speed: const Duration(milliseconds: 50), + speed: chatController + .typewriterAnimatedConfiguration.duration, ), ], isRepeatingAnimation: false, From 8fbd67892166b68f6e8732e055b150aa4859c29d Mon Sep 17 00:00:00 2001 From: Ezeagu Princewill Date: Thu, 27 Feb 2025 22:54:04 +0100 Subject: [PATCH 6/7] added textediting controller giving full control to the textfield of the current user --- lib/src/controller/chat_controller.dart | 10 ++++++++-- lib/src/widgets/chatui_textfield.dart | 4 +++- lib/src/widgets/send_message_widget.dart | 14 +++++++++----- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/src/controller/chat_controller.dart b/lib/src/controller/chat_controller.dart index 53813d7c..a0f1c5b6 100644 --- a/lib/src/controller/chat_controller.dart +++ b/lib/src/controller/chat_controller.dart @@ -78,14 +78,20 @@ class ChatController { ///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; + ChatController({ required this.initialMessageList, required this.scrollController, required this.otherUsers, required this.currentUser, + TextEditingController? textEdittingController, TypewriterAnimatedConfiguration? typewriterAnimatedConfiguration, - }) : typewriterAnimatedConfiguration = typewriterAnimatedConfiguration ?? - TypewriterAnimatedConfiguration(); + }) : typewriterAnimatedConfiguration = typewriterAnimatedConfiguration ?? + TypewriterAnimatedConfiguration(), + textEdittingController = + textEdittingController ?? TextEditingController(); /// Represents message stream of chat StreamController> messageStreamController = StreamController(); diff --git a/lib/src/widgets/chatui_textfield.dart b/lib/src/widgets/chatui_textfield.dart index ec6cf0bc..f8a4fba1 100644 --- a/lib/src/widgets/chatui_textfield.dart +++ b/lib/src/widgets/chatui_textfield.dart @@ -174,6 +174,7 @@ class _ChatUITextFieldState extends State { Expanded( child: TextField( focusNode: widget.focusNode, + autofocus: false, controller: widget.textEditingController, style: textFieldConfig?.textStyle ?? const TextStyle(color: Colors.white), @@ -210,7 +211,8 @@ class _ChatUITextFieldState extends State { ValueListenableBuilder( valueListenable: _inputText, builder: (_, inputTextValue, child) { - if (inputTextValue.isNotEmpty) { + if (inputTextValue.isNotEmpty || + widget.textEditingController.text.isNotEmpty) { return IconButton( color: sendMessageConfig?.defaultSendButtonColor ?? Colors.green, diff --git a/lib/src/widgets/send_message_widget.dart b/lib/src/widgets/send_message_widget.dart index e284517c..798978ee 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,9 @@ class SendMessageWidgetState extends State { Widget build(BuildContext context) { final scrollToBottomButtonConfig = chatListConfig.scrollToBottomButtonConfig; + textEditingController = ChatViewInheritedWidget.of(context)! + .chatController + .textEdittingController; return Align( alignment: Alignment.bottomCenter, child: widget.sendMessageBuilder != null @@ -258,7 +262,7 @@ class SendMessageWidgetState extends State { ), ChatUITextField( focusNode: _focusNode, - textEditingController: _textEditingController, + textEditingController: textEditingController, onPressed: _onPressed, sendMessageConfig: widget.sendMessageConfig, onRecordingComplete: _onRecordingComplete, @@ -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(); From 824273bf590cac99caf78107b2fbf59e7655374a Mon Sep 17 00:00:00 2001 From: Ezeagu Princewill Date: Fri, 28 Feb 2025 17:15:33 +0100 Subject: [PATCH 7/7] modified the textformfield --- lib/src/controller/chat_controller.dart | 4 + .../send_message_configuration.dart | 10 +- lib/src/widgets/chatui_textfield.dart | 308 +++++++++++------- lib/src/widgets/send_message_widget.dart | 6 +- 4 files changed, 198 insertions(+), 130 deletions(-) diff --git a/lib/src/controller/chat_controller.dart b/lib/src/controller/chat_controller.dart index a0f1c5b6..f361cc43 100644 --- a/lib/src/controller/chat_controller.dart +++ b/lib/src/controller/chat_controller.dart @@ -81,11 +81,15 @@ class ChatController { ///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 ?? 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/widgets/chatui_textfield.dart b/lib/src/widgets/chatui_textfield.dart index f8a4fba1..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,135 +177,183 @@ class _ChatUITextFieldState extends State { ) else Expanded( - child: 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) { - if (inputTextValue.isNotEmpty || - widget.textEditingController.text.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, - ), - ], - ); - } - }, - ), + ); + }, + ), + ], + ), + ), ], ); }, @@ -376,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/send_message_widget.dart b/lib/src/widgets/send_message_widget.dart index 798978ee..3c41f304 100644 --- a/lib/src/widgets/send_message_widget.dart +++ b/lib/src/widgets/send_message_widget.dart @@ -100,9 +100,8 @@ class SendMessageWidgetState extends State { Widget build(BuildContext context) { final scrollToBottomButtonConfig = chatListConfig.scrollToBottomButtonConfig; - textEditingController = ChatViewInheritedWidget.of(context)! - .chatController - .textEdittingController; + final cntrl = ChatViewInheritedWidget.of(context)!.chatController; + textEditingController = cntrl.textEdittingController; return Align( alignment: Alignment.bottomCenter, child: widget.sendMessageBuilder != null @@ -267,6 +266,7 @@ class SendMessageWidgetState extends State { sendMessageConfig: widget.sendMessageConfig, onRecordingComplete: _onRecordingComplete, onImageSelected: _onImageSelected, + onChangedFunction: cntrl.onChangedFunct, ) ], ),