From c3a917ea198cd727419c72ef5889650b99763366 Mon Sep 17 00:00:00 2001 From: Jaimin Rana Date: Sat, 23 Sep 2023 00:07:39 +0530 Subject: [PATCH] =?UTF-8?q?feat:=E2=9C=A8Added=20a=20support=20for=20showi?= =?UTF-8?q?ng=20message=20time=20in=20message=20bubble.=20Removed=20enable?= =?UTF-8?q?SwipeToSeeTime(bool)=20&=20added=20messageTimePositionType(Enum?= =?UTF-8?q?)=20to=20FeatureActiveConfig=20class.(#115)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 43 +++--- example/lib/data.dart | 70 ++++++++- example/lib/main.dart | 11 +- lib/src/models/feature_active_config.dart | 11 +- lib/src/models/message_configuration.dart | 11 +- lib/src/values/enumaration.dart | 29 +++- lib/src/values/typedefs.dart | 1 + lib/src/widgets/chat_bubble_widget.dart | 39 ++++- lib/src/widgets/chat_list_widget.dart | 2 +- lib/src/widgets/image_message_view.dart | 37 ++++- lib/src/widgets/link_preview.dart | 31 ++++ lib/src/widgets/message_time_widget.dart | 86 +++++++---- lib/src/widgets/message_view.dart | 92 ++++++++--- lib/src/widgets/reaction_widget.dart | 28 ++-- lib/src/widgets/text_message_view.dart | 176 +++++++++++++++++----- lib/src/widgets/voice_message_view.dart | 170 +++++++++++++-------- pubspec.yaml | 2 +- 17 files changed, 629 insertions(+), 210 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebb6ca0b..9535f474 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,32 +1,38 @@ ## [1.3.2] (Unreleased) + +* **Feat**: [115](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/115) Added + option for showing message sent/received time in message bubble. Removed 'enableSwipeToSeeTime' + & added 'messageTimePositionType' to FeatureActiveConfig class for changing position of message + time. * **Fix**: [126](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/126) Added flag to hide user name in chat. ## [1.3.1] -* **Feat**: [105](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/pull/105) Allow user - to get callback when image is picked so user can perform operation like crop. Allow user to pass +* **Feat**: [105](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/pull/105) Allow user + to get callback when image is picked so user can perform operation like crop. Allow user to pass configuration like height, width, image quality and preferredCameraDevice. -* **Fix**: [95](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/95) Fix issue of +* **Fix**: [95](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/95) Fix issue of chat is added to bottom while `loadMoreData` callback. -* **Fix**: [109](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/109) Added - support for the hiding/Un-hiding gallery and camera buttons - +* **Fix**: [109](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/109) Added + support for the hiding/Un-hiding gallery and camera buttons + ## [1.3.0] * **Feat**: [71](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/pull/71) Added Callback when a user starts/stops composing typing a message. * **Fix**: [78](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/pull/78) Fix issue of unmodifiable list. -* **Feat**: [76](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/pull/76) Message Receipts. +* **Feat**: [76](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/pull/76) Message + Receipts. * **Fix**: [81](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/pull/81) Fix issue of TypingIndicator Rebuilding ChatView. * **Fix**: [94](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/pull/94) Fixed deprecated `showRecentsTab` property with new `recentTabBehavior`. * Support for latest flutter version `3.10.5`. -* Update dependencies `http` to version `1.1.0` and `image_picker` to version range `'>=0.8.9 <2.0.0'`. - +* Update dependencies `http` to version `1.1.0` and `image_picker` to version + range `'>=0.8.9 <2.0.0'`. ## [1.2.1] @@ -34,42 +40,43 @@ file is not loaded. * **Fix**: [61](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/61) Fix issue of audio message is not working. -* **Feat**: [65](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/65) Add callback +* **Feat**: [65](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/65) Add callback when user react on message. - ## [1.2.0+1] -* **Feat**: [42](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/42) Ability to +* **Feat**: [42](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/42) Ability to get callback on tap of profile circle avatar. * **Breaking**: Add `messageType` in `onSendTap` callback for encountering messages. * **Breaking**: Remove `onRecordingComplete` and you can get Recorded audio in `onSendTap` callback with `messageType`. * **Breaking**: Remove `onImageSelected` from `ImagePickerIconsConfiguration` and can get selected image in `onSendTap` callback with `messageType`. -* **Feat**: [49](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/49) Add `onUrlDetect` +* **Feat**: [49](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/49) + Add `onUrlDetect` callback for opening urls. * **Feat**: [51](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/51) Ability to get callback on long press of profile circle avatar. ## [1.1.0] -* **Feat**: [37](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/37) Ability to +* **Feat**: [37](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/37) Ability to enable or disable specific features. * **Feat**: [34](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/34) Ability to add voice message. * **Breaking**: Remove `onEmojiTap` from `ReactionPopupConfiguration`, it can be handled internally. -* **Breaking**: Remove `horizontalDragToShowMessageTime` from `ChatBackgroundConfiguration` and +* **Breaking**: Remove `horizontalDragToShowMessageTime` from `ChatBackgroundConfiguration` and add `enableSwipeToSeeTime` parameter with same feature in `FeatureActiveConfig`. * **Breaking**: Remove `showReceiverProfileCircle` and add `enableOtherUserProfileAvatar` parameter with same feature in `FeatureActiveConfig`. -* * **Breaking**: Move `enablePagination` parameter from `ChatView` to `FeatureActiveConfig`. +* + * **Breaking**: Move `enablePagination` parameter from `ChatView` to `FeatureActiveConfig`. ## [1.0.1] -* **Fix**: [32](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/32) Fix issue of +* **Fix**: [32](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/32) Fix issue of while replying to image it highlights the link instead of the image. -* **Fix**: [35](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/35) Fix issue of +* **Fix**: [35](https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues/35) Fix issue of removing reaction which is reacted accidentally. ## [1.0.0+1] diff --git a/example/lib/data.dart b/example/lib/data.dart index cf086eae..63917f8e 100644 --- a/example/lib/data.dart +++ b/example/lib/data.dart @@ -4,12 +4,39 @@ class Data { static const profileImage = "https://raw.githubusercontent.com/SimformSolutionsPvtLtd/flutter_showcaseview/master/example/assets/simform.png"; static final messageList = [ + Message( + id: '58', + message: "https://bit.ly/3JHS2Wl", + createdAt: DateTime.now(), + sendBy: '2', + reaction: Reaction( + reactions: ['\u{2764}', '\u{1F44D}', '\u{1F44D}'], + reactedUserIds: ['2', '3', '4'], + ), + status: MessageStatus.read, + ), + Message( + id: '88', + message: "https://bit.ly/3JHS2Wl", + createdAt: DateTime.now(), + sendBy: '1', + reaction: Reaction( + reactions: ['\u{2764}', '\u{1F44D}', '\u{1F44D}'], + reactedUserIds: ['2', '3', '4'], + ), + status: MessageStatus.read, + ), Message( id: '1', message: "Hi!", createdAt: DateTime.now(), - sendBy: '1', // userId of who sends the message + sendBy: '1', + // userId of who sends the message status: MessageStatus.read, + reaction: Reaction( + reactions: ['\u{2764}', '\u{2763}', '\u{2762}', '\u{2761}'], + reactedUserIds: ['2', '4', '3', '1'], + ), ), Message( id: '2', @@ -32,6 +59,14 @@ class Data { sendBy: '1', status: MessageStatus.read, ), + Message( + id: '5', + message: + "Can you write the time and place of the meeting? Can you write the time and place of the meeting?", + createdAt: DateTime.now(), + sendBy: '1', + status: MessageStatus.read, + ), Message( id: '5', message: "That's fine", @@ -105,6 +140,14 @@ class Data { sendBy: '1', reaction: Reaction(reactions: ['\u{2764}'], reactedUserIds: ['2']), status: MessageStatus.read, + ), Message( + id: '1321', + message: "https://miro.medium.com/max/1000/0*s7of7kWnf9fDg4XM.jpeg", + createdAt: DateTime.now(), + messageType: MessageType.image, + sendBy: '2', + reaction: Reaction(reactions: ['\u{2764}'], reactedUserIds: ['2']), + status: MessageStatus.read, ), Message( id: '12', @@ -113,5 +156,30 @@ class Data { sendBy: '2', status: MessageStatus.read, ), + // Message( + // id: '13', + // message: "", + // createdAt: DateTime.now(), + // sendBy: '2', + // status: MessageStatus.read, + // messageType: MessageType.voice), + // Message( + // id: '14', + // message: "", + // createdAt: DateTime.now(), + // sendBy: '1', + // status: MessageStatus.read, + // messageType: MessageType.voice), + Message( + id: '94', + message: "messageMe", + reaction: Reaction( + reactions: ['\u{2764}', '\u{2761}', '\u{2763}', '\u{2762}'], + reactedUserIds: ['2', '4', '3', '1'], + ), + createdAt: DateTime.now(), + sendBy: '1', + status: MessageStatus.read, + ), ]; } diff --git a/example/lib/main.dart b/example/lib/main.dart index bdc02f92..0630af04 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import 'package:chatview/chatview.dart'; import 'package:example/data.dart'; import 'package:example/models/theme.dart'; @@ -81,6 +83,8 @@ class _ChatScreenState extends State { featureActiveConfig: const FeatureActiveConfig( lastSeenAgoBuilderVisibility: true, receiptsBuilderVisibility: true, + enableOtherUserProfileAvatar: true, + messageTimePositionType: MessageTimePositionType.onRightSwipe, ), chatViewState: ChatViewState.hasMessages, chatViewStateConfig: ChatViewStateConfiguration( @@ -212,6 +216,11 @@ class _ChatScreenState extends State { backgroundColor: theme.reactionPopupColor, ), messageConfig: MessageConfiguration( + messageTimeTextStyle: TextStyle( + color: theme.messageTimeTextColor, + fontWeight: FontWeight.bold, + fontSize: 12, + ), messageReactionConfig: MessageReactionConfiguration( backgroundColor: theme.messageReactionBackGroundColor, borderColor: theme.messageReactionBackGroundColor, @@ -238,7 +247,7 @@ class _ChatScreenState extends State { ), ), imageMessageConfig: ImageMessageConfiguration( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 15), + margin: const EdgeInsets.fromLTRB(12, 0, 12, 15), shareIconConfig: ShareIconConfiguration( defaultIconBackgroundColor: theme.shareIconBackgroundColor, defaultIconColor: theme.shareIconColor, diff --git a/lib/src/models/feature_active_config.dart b/lib/src/models/feature_active_config.dart index baeb37d4..3e028a07 100644 --- a/lib/src/models/feature_active_config.dart +++ b/lib/src/models/feature_active_config.dart @@ -1,9 +1,10 @@ +import 'package:chatview/chatview.dart'; + class FeatureActiveConfig { const FeatureActiveConfig({ this.enableSwipeToReply = true, this.enableReactionPopup = true, this.enableTextField = true, - this.enableSwipeToSeeTime = true, this.enableCurrentUserProfileAvatar = false, this.enableOtherUserProfileAvatar = true, this.enableReplySnackBar = true, @@ -13,6 +14,7 @@ class FeatureActiveConfig { this.lastSeenAgoBuilderVisibility = true, this.receiptsBuilderVisibility = true, this.enableOtherUserName = true, + this.messageTimePositionType = MessageTimePositionType.onRightSwipe, }); /// Used for enable/disable swipe to reply. @@ -24,9 +26,6 @@ class FeatureActiveConfig { /// Used for enable/disable text field. final bool enableTextField; - /// Used for enable/disable swipe whole chat to see message created time. - final bool enableSwipeToSeeTime; - /// Used for enable/disable current user profile circle. final bool enableCurrentUserProfileAvatar; @@ -53,4 +52,8 @@ class FeatureActiveConfig { /// Used for enable/disable other users name. final bool enableOtherUserName; + + /// Controls the Position of message created time. + /// default value: onRightSwipe + final MessageTimePositionType messageTimePositionType; } diff --git a/lib/src/models/message_configuration.dart b/lib/src/models/message_configuration.dart index 2844c60e..d8ec712e 100644 --- a/lib/src/models/message_configuration.dart +++ b/lib/src/models/message_configuration.dart @@ -19,10 +19,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import 'package:chatview/src/models/models.dart'; +import 'package:chatview/chatview.dart'; import 'package:chatview/src/models/voice_message_configuration.dart'; import 'package:flutter/material.dart'; + class MessageConfiguration { /// Provides configuration of image message appearance. final ImageMessageConfiguration? imageMessageConfig; @@ -39,11 +40,19 @@ class MessageConfiguration { /// Configurations for voice message bubble final VoiceMessageConfiguration? voiceMessageConfig; + /// Allow user to set custom formatting of message time. + final MessageDateTimeBuilder? messageDateTimeBuilder; + + /// Used to give text style of message's time of a chat bubble + final TextStyle? messageTimeTextStyle; + const MessageConfiguration({ this.imageMessageConfig, this.messageReactionConfig, this.emojiMessageConfig, this.customMessageBuilder, this.voiceMessageConfig, + this.messageDateTimeBuilder, + this.messageTimeTextStyle, }); } diff --git a/lib/src/values/enumaration.dart b/lib/src/values/enumaration.dart index 4e44b1d1..718a00d7 100644 --- a/lib/src/values/enumaration.dart +++ b/lib/src/values/enumaration.dart @@ -30,7 +30,7 @@ enum MessageType { custom } -/// Events, Wheter the user is still typing a message or has +/// Events, Whether the user is still typing a message or has /// typed the message enum TypeWriterStatus { typing, typed } @@ -52,3 +52,30 @@ extension ChatViewStateExtension on ChatViewState { bool get noMessages => this == ChatViewState.noData; } + +enum MessageTimePositionType { + /// Used for viewing the message created time inside the chatBubble. + insideChatBubble, + + /// Used for viewing the message created time outside the chatBubble. + outsideChatBubbleAtBottom, + + /// Used for enable/disable swipe whole chat to see message created time. + onRightSwipe, + + /// Used for viewing the message created time outside the chatBubble. + outsideChatBubbleAtTop, + + /// Used for disabling the viewing of the message created time. + disable; + + bool get isInsideChatBubble => this == insideChatBubble; + + bool get isOutSideChatBubbleAtBottom => this == outsideChatBubbleAtBottom; + + bool get isOnRightSwipe => this == onRightSwipe; + + bool get isOutSideChatBubbleAtTop => this == outsideChatBubbleAtTop; + + bool get isDisable => this == disable; +} diff --git a/lib/src/values/typedefs.dart b/lib/src/values/typedefs.dart index 43ea2e41..dd7beb0e 100644 --- a/lib/src/values/typedefs.dart +++ b/lib/src/values/typedefs.dart @@ -35,3 +35,4 @@ typedef VoidCallBackWithFuture = Future Function(); typedef StringsCallBack = void Function(String emoji, String messageId); typedef StringWithReturnWidget = Widget Function(String separator); typedef DragUpdateDetailsCallback = void Function(DragUpdateDetails); +typedef MessageDateTimeBuilder = Widget Function(DateTime date); diff --git a/lib/src/widgets/chat_bubble_widget.dart b/lib/src/widgets/chat_bubble_widget.dart index 06cdf003..1b754d45 100644 --- a/lib/src/widgets/chat_bubble_widget.dart +++ b/lib/src/widgets/chat_bubble_widget.dart @@ -127,13 +127,16 @@ class _ChatBubbleWidgetState extends State { final messagedUser = chatController?.getUserFromId(widget.message.sendBy); return Stack( children: [ - if (featureActiveConfig?.enableSwipeToSeeTime ?? true) ...[ + if (featureActiveConfig?.messageTimePositionType.isOnRightSwipe ?? + true) ...[ Visibility( visible: widget.slideAnimation?.value.dx == 0.0 ? false : true, child: Positioned.fill( child: Align( alignment: Alignment.centerRight, child: MessageTimeWidget( + messageDateTimeBuilder: + widget.messageConfig?.messageDateTimeBuilder, messageTime: widget.message.createdAt, isCurrentUser: isMessageBySender, messageTimeIconColor: widget.messageTimeIconColor, @@ -297,17 +300,36 @@ class _ChatBubbleWidgetState extends State { crossAxisAlignment: isMessageBySender ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ - if ((chatController?.chatUsers.length ?? 0) > 1 && - !isMessageBySender && - (featureActiveConfig?.enableOtherUserName ?? true)) + if ((chatController?.chatUsers.length ?? 0) > 1) Padding( padding: widget.chatBubbleConfig?.inComingChatBubbleConfig?.padding ?? const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - child: Text( - messagedUser?.name ?? '', - style: widget.chatBubbleConfig?.inComingChatBubbleConfig - ?.senderNameTextStyle, + child: Row( + mainAxisAlignment: isMessageBySender + ? MainAxisAlignment.end + : MainAxisAlignment.start, + children: [ + if (!isMessageBySender && (featureActiveConfig?.enableOtherUserName ?? true)) + Text( + messagedUser?.name ?? '', + style: widget.chatBubbleConfig?.inComingChatBubbleConfig + ?.senderNameTextStyle, + ), + if (featureActiveConfig + ?.messageTimePositionType.isOutSideChatBubbleAtTop ?? + false) ...[ + const SizedBox(width: 4), + widget.messageConfig?.messageDateTimeBuilder + ?.call(widget.message.createdAt) ?? + MessageTimeWidget( + isCurrentUser: isMessageBySender, + messageTime: widget.message.createdAt, + messageTimeTextStyle: + widget.messageConfig?.messageTimeTextStyle, + ), + ] + ], ), ), if (replyMessage.isNotEmpty) @@ -354,6 +376,7 @@ class _ChatBubbleWidgetState extends State { ?.repliedMsgAutoScrollConfig.highlightScale ?? 1.1, onMaxDuration: _onMaxDuration, + profileCircleConfig: profileCircleConfig, ), ], ); diff --git a/lib/src/widgets/chat_list_widget.dart b/lib/src/widgets/chat_list_widget.dart index 2e769d03..3beaff94 100644 --- a/lib/src/widgets/chat_list_widget.dart +++ b/lib/src/widgets/chat_list_widget.dart @@ -192,7 +192,7 @@ class _ChatListWidgetState extends State showTypingIndicator: showTypingIndicator, scrollController: scrollController, isEnableSwipeToSeeTime: - featureActiveConfig?.enableSwipeToSeeTime ?? true, + featureActiveConfig?.messageTimePositionType.isOnRightSwipe ?? true, chatBackgroundConfig: widget.chatBackgroundConfig, assignReplyMessage: widget.assignReplyMessage, replyMessage: widget.replyMessage, diff --git a/lib/src/widgets/image_message_view.dart b/lib/src/widgets/image_message_view.dart index ac7607ea..0a801a42 100644 --- a/lib/src/widgets/image_message_view.dart +++ b/lib/src/widgets/image_message_view.dart @@ -22,10 +22,12 @@ import 'dart:convert'; import 'dart:io'; +import 'package:chatview/chatview.dart'; import 'package:chatview/src/extensions/extensions.dart'; -import 'package:chatview/src/models/models.dart'; +import 'package:chatview/src/widgets/message_time_widget.dart'; import 'package:flutter/material.dart'; +import 'chat_view_inherited_widget.dart'; import 'reaction_widget.dart'; import 'share_icon.dart'; @@ -38,6 +40,8 @@ class ImageMessageView extends StatelessWidget { this.messageReactionConfig, this.highlightImage = false, this.highlightScale = 1.2, + this.messageDateTimeBuilder, + this.messageTimeTextStyle, }) : super(key: key); /// Provides message instance of chat. @@ -58,6 +62,12 @@ class ImageMessageView extends StatelessWidget { /// Provides scale of highlighted image when user taps on replied image. final double highlightScale; + /// Allow user to set custom formatting of message time. + final MessageDateTimeBuilder? messageDateTimeBuilder; + + /// Used to give text style of message's time of a chat bubble + final TextStyle? messageTimeTextStyle; + String get imageUrl => message.message; Widget get iconButton => ShareIcon( @@ -67,6 +77,10 @@ class ImageMessageView extends StatelessWidget { @override Widget build(BuildContext context) { + final messageTimePositionType = ChatViewInheritedWidget.of(context) + ?.featureActiveConfig + .messageTimePositionType ?? + MessageTimePositionType.onRightSwipe; return Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: @@ -88,7 +102,9 @@ class ImageMessageView extends StatelessWidget { padding: imageMessageConfig?.padding ?? EdgeInsets.zero, margin: imageMessageConfig?.margin ?? EdgeInsets.only( - top: 6, + top: messageTimePositionType.isOutSideChatBubbleAtTop + ? 0 + : 6, right: isMessageBySender ? 6 : 0, left: isMessageBySender ? 0 : 6, bottom: message.reaction.reactions.isNotEmpty ? 15 : 0, @@ -136,9 +152,24 @@ class ImageMessageView extends StatelessWidget { if (message.reaction.reactions.isNotEmpty) ReactionWidget( isMessageBySender: isMessageBySender, - reaction: message.reaction, + message: message, messageReactionConfig: messageReactionConfig, ), + if (!messageTimePositionType.isOnRightSwipe && + !messageTimePositionType.isDisable && + !messageTimePositionType.isOutSideChatBubbleAtTop) + Positioned( + right: message.reaction.reactions.isNotEmpty ? 16 : 18, + bottom: messageTimePositionType.isOutSideChatBubbleAtBottom + ? 0 + : 20, + child: messageDateTimeBuilder?.call(message.createdAt) ?? + MessageTimeWidget( + isCurrentUser: isMessageBySender, + messageTime: message.createdAt, + messageTimeTextStyle: messageTimeTextStyle, + ), + ), ], ), if (!isMessageBySender) iconButton, diff --git a/lib/src/widgets/link_preview.dart b/lib/src/widgets/link_preview.dart index 88316866..405b5dd4 100644 --- a/lib/src/widgets/link_preview.dart +++ b/lib/src/widgets/link_preview.dart @@ -20,18 +20,26 @@ * SOFTWARE. */ import 'package:any_link_preview/any_link_preview.dart'; +import 'package:chatview/chatview.dart'; import 'package:chatview/src/extensions/extensions.dart'; import 'package:chatview/src/models/link_preview_configuration.dart'; +import 'package:chatview/src/values/typedefs.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; import '../utils/constants/constants.dart'; +import 'chat_view_inherited_widget.dart'; +import 'message_time_widget.dart'; class LinkPreview extends StatelessWidget { const LinkPreview({ Key? key, required this.url, + required this.message, + required this.isMessageBySender, + this.messageDateTimeBuilder, this.linkPreviewConfig, + this.messageTimeTextStyle, }) : super(key: key); /// Provides url which is passed in message. @@ -41,8 +49,21 @@ class LinkPreview extends StatelessWidget { /// in message. final LinkPreviewConfiguration? linkPreviewConfig; + final MessageDateTimeBuilder? messageDateTimeBuilder; + + final Message message; + + final bool isMessageBySender; + + /// Used to give text style of message's time of a chat bubble + final TextStyle? messageTimeTextStyle; + @override Widget build(BuildContext context) { + final messageTimePositionType = ChatViewInheritedWidget.of(context) + ?.featureActiveConfig + .messageTimePositionType ?? + MessageTimePositionType.onRightSwipe; return Padding( padding: linkPreviewConfig?.padding ?? const EdgeInsets.symmetric(horizontal: 6, vertical: verticalPadding), @@ -96,6 +117,16 @@ class LinkPreview extends StatelessWidget { ), ), ), + if (messageTimePositionType.isInsideChatBubble) + Align( + alignment: Alignment.bottomRight, + child: messageDateTimeBuilder?.call(message.createdAt) ?? + MessageTimeWidget( + messageTime: message.createdAt, + isCurrentUser: isMessageBySender, + messageTimeTextStyle: messageTimeTextStyle, + ), + ), ], ), ); diff --git a/lib/src/widgets/message_time_widget.dart b/lib/src/widgets/message_time_widget.dart index c542261e..bf6d4e3f 100644 --- a/lib/src/widgets/message_time_widget.dart +++ b/lib/src/widgets/message_time_widget.dart @@ -19,9 +19,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ +import 'dart:ui'; + +import 'package:chatview/chatview.dart'; import 'package:chatview/src/extensions/extensions.dart'; +import 'package:chatview/src/values/typedefs.dart'; import 'package:flutter/material.dart'; +import 'chat_view_inherited_widget.dart'; + class MessageTimeWidget extends StatelessWidget { const MessageTimeWidget({ Key? key, @@ -29,6 +35,7 @@ class MessageTimeWidget extends StatelessWidget { required this.isCurrentUser, this.messageTimeTextStyle, this.messageTimeIconColor, + this.messageDateTimeBuilder, }) : super(key: key); /// Provides message crated date time. @@ -44,37 +51,58 @@ class MessageTimeWidget extends StatelessWidget { /// seeing message sending time final Color? messageTimeIconColor; + /// Allow user to set custom formatting of message time. + final MessageDateTimeBuilder? messageDateTimeBuilder; + @override Widget build(BuildContext context) { - return Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.all(2), - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: messageTimeIconColor ?? Colors.black, + final messageTimePositionType = ChatViewInheritedWidget.of(context) + ?.featureActiveConfig + .messageTimePositionType ?? + MessageTimePositionType.onRightSwipe; + return !messageTimePositionType.isOnRightSwipe + ? Text( + messageTime.getTimeFromDateTime, + style: messageTimeTextStyle ?? + TextStyle( + fontSize: 14, + color: Colors.black.withOpacity(0.6), + ), + ) + : Align( + alignment: Alignment.centerRight, + child: messageDateTimeBuilder?.call(messageTime) ?? + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: messageTimeIconColor ?? Colors.black, + ), + ), + child: Icon( + isCurrentUser + ? Icons.arrow_forward + : Icons.arrow_back, + size: 10, + color: messageTimeIconColor ?? Colors.black, + ), + ), + const SizedBox(width: 4), + Text( + messageTime.getTimeFromDateTime, + style: messageTimeTextStyle ?? + const TextStyle(fontSize: 12), + ), + ], + ), ), - ), - child: Icon( - isCurrentUser ? Icons.arrow_forward : Icons.arrow_back, - size: 10, - color: messageTimeIconColor ?? Colors.black, - ), - ), - const SizedBox(width: 4), - Text( - messageTime.getTimeFromDateTime, - style: messageTimeTextStyle ?? const TextStyle(fontSize: 12), - ), - ], - ), - ), - ); + ); } } diff --git a/lib/src/widgets/message_view.dart b/lib/src/widgets/message_view.dart index 2b834bd9..3440332c 100644 --- a/lib/src/widgets/message_view.dart +++ b/lib/src/widgets/message_view.dart @@ -26,6 +26,7 @@ import 'package:flutter/material.dart'; import 'package:chatview/src/extensions/extensions.dart'; import '../utils/constants/constants.dart'; import 'image_message_view.dart'; +import 'message_time_widget.dart'; import 'text_message_view.dart'; import 'reaction_widget.dart'; import 'voice_message_view.dart'; @@ -37,6 +38,7 @@ class MessageView extends StatefulWidget { required this.isMessageBySender, required this.onLongPress, required this.isLongPressEnable, + required this.profileCircleConfig, this.chatBubbleMaxWidth, this.inComingChatBubbleConfig, this.outgoingChatBubbleConfig, @@ -94,6 +96,9 @@ class MessageView extends StatefulWidget { final Function(int)? onMaxDuration; + /// Provides configuration related to user profile circle avatar. + final ProfileCircleConfiguration? profileCircleConfig; + @override State createState() => _MessageViewState(); } @@ -155,8 +160,12 @@ class _MessageViewState extends State } Widget get _messageView { + final messageTimePositionType = + provide?.featureActiveConfig.messageTimePositionType ?? + MessageTimePositionType.onRightSwipe; final message = widget.message.message; final emojiMessageConfiguration = messageConfig?.emojiMessageConfig; + return Padding( padding: EdgeInsets.only( bottom: widget.message.reaction.reactions.isNotEmpty ? 6 : 0, @@ -177,7 +186,9 @@ class _MessageViewState extends State leftPadding2, widget.message.reaction.reactions.isNotEmpty ? 14 - : 0, + : !messageTimePositionType.isOnRightSwipe + ? 20 + : 0, ), child: Transform.scale( scale: widget.shouldHighlight @@ -192,11 +203,28 @@ class _MessageViewState extends State ), if (widget.message.reaction.reactions.isNotEmpty) ReactionWidget( - reaction: widget.message.reaction, + message: widget.message, messageReactionConfig: messageConfig?.messageReactionConfig, isMessageBySender: widget.isMessageBySender, ), + if (!messageTimePositionType.isOnRightSwipe && + !messageTimePositionType.isDisable && + !messageTimePositionType.isOutSideChatBubbleAtTop) + Positioned( + bottom: widget.message.reaction.reactions.isNotEmpty + ? -12 + : 5, + right: widget.isMessageBySender ? 10 : null, + left: widget.isMessageBySender ? null : 10, + child: messageConfig?.messageDateTimeBuilder + ?.call(widget.message.createdAt) ?? + MessageTimeWidget( + isCurrentUser: widget.isMessageBySender, + messageTime: widget.message.createdAt, + messageTimeTextStyle: messageConfig?.messageTimeTextStyle, + ), + ), ], ); } else if (widget.message.messageType.isImage) { @@ -207,6 +235,9 @@ class _MessageViewState extends State messageReactionConfig: messageConfig?.messageReactionConfig, highlightImage: widget.shouldHighlight, highlightScale: widget.highlightScale, + messageDateTimeBuilder: + messageConfig?.messageDateTimeBuilder, + messageTimeTextStyle: messageConfig?.messageTimeTextStyle, ); } else if (widget.message.messageType.isText) { return TextMessageView( @@ -218,6 +249,9 @@ class _MessageViewState extends State messageReactionConfig: messageConfig?.messageReactionConfig, highlightColor: widget.highlightColor, highlightMessage: widget.shouldHighlight, + messageDateTimeBuilder: + messageConfig?.messageDateTimeBuilder, + messageTimeTextStyle: messageConfig?.messageTimeTextStyle, ); } else if (widget.message.messageType.isVoice) { return VoiceMessageView( @@ -229,6 +263,9 @@ class _MessageViewState extends State messageReactionConfig: messageConfig?.messageReactionConfig, inComingChatBubbleConfig: widget.inComingChatBubbleConfig, outgoingChatBubbleConfig: widget.outgoingChatBubbleConfig, + messageDateTimeBuilder: + messageConfig?.messageDateTimeBuilder, + messageTimeTextStyle: messageConfig?.messageTimeTextStyle, ); } else if (widget.message.messageType.isCustom && messageConfig?.customMessageBuilder != null) { @@ -236,31 +273,36 @@ class _MessageViewState extends State } }()) ?? const SizedBox(), - ValueListenableBuilder( - valueListenable: widget.message.statusNotifier, - builder: (context, value, child) { - if (widget.isMessageBySender && - widget.controller?.initialMessageList.last.id == - widget.message.id && - widget.message.status == MessageStatus.read) { - if (ChatViewInheritedWidget.of(context) - ?.featureActiveConfig - .lastSeenAgoBuilderVisibility ?? - true) { - return widget.outgoingChatBubbleConfig?.receiptsWidgetConfig - ?.lastSeenAgoBuilder - ?.call( - widget.message, - applicationDateFormatter( - widget.message.createdAt)) ?? - lastSeenAgoBuilder(widget.message, - applicationDateFormatter(widget.message.createdAt)); + Padding( + padding: EdgeInsets.only( + top: messageTimePositionType.isOutSideChatBubbleAtBottom ? 8 : 0, + ), + child: ValueListenableBuilder( + valueListenable: widget.message.statusNotifier, + builder: (context, value, child) { + if (widget.isMessageBySender && + widget.controller?.initialMessageList.last.id == + widget.message.id && + widget.message.status == MessageStatus.read) { + if (ChatViewInheritedWidget.of(context) + ?.featureActiveConfig + .lastSeenAgoBuilderVisibility ?? + true) { + return widget.outgoingChatBubbleConfig?.receiptsWidgetConfig + ?.lastSeenAgoBuilder + ?.call( + widget.message, + applicationDateFormatter( + widget.message.createdAt)) ?? + lastSeenAgoBuilder(widget.message, + applicationDateFormatter(widget.message.createdAt)); + } + return const SizedBox(); } return const SizedBox(); - } - return const SizedBox(); - }, - ) + }, + ), + ), ], ), ); diff --git a/lib/src/widgets/reaction_widget.dart b/lib/src/widgets/reaction_widget.dart index ce29663e..534b3b0d 100644 --- a/lib/src/widgets/reaction_widget.dart +++ b/lib/src/widgets/reaction_widget.dart @@ -24,18 +24,18 @@ import 'package:chatview/src/utils/measure_size.dart'; import 'package:chatview/src/widgets/reactions_bottomsheet.dart'; import 'package:flutter/material.dart'; -import '../../chatview.dart'; +import 'package:chatview/chatview.dart'; class ReactionWidget extends StatefulWidget { const ReactionWidget({ Key? key, - required this.reaction, + required this.message, this.messageReactionConfig, required this.isMessageBySender, }) : super(key: key); /// Provides reaction instance of message. - final Reaction reaction; + final Message message; /// Provides configuration of reaction appearance in chat bubble. final MessageReactionConfiguration? messageReactionConfig; @@ -66,7 +66,8 @@ class _ReactionWidgetState extends State { @override Widget build(BuildContext context) { //// Convert into set to remove reduntant values - final reactionsSet = widget.reaction.reactions.toSet(); + final reactionsSet = widget.message.reaction.reactions.toSet(); + return Positioned( bottom: 0, right: widget.isMessageBySender && needToExtend ? 0 : null, @@ -74,7 +75,7 @@ class _ReactionWidgetState extends State { onTap: () => chatController != null ? ReactionsBottomSheet().show( context: context, - reaction: widget.reaction, + reaction: widget.message.reaction, chatController: chatController!, reactionsBottomSheetConfig: messageReactionConfig?.reactionsBottomSheetConfig, @@ -84,7 +85,7 @@ class _ReactionWidgetState extends State { onSizeChange: (extend) => setState(() => needToExtend = extend), child: Container( padding: messageReactionConfig?.padding ?? - const EdgeInsets.symmetric(vertical: 1.7, horizontal: 6), + const EdgeInsets.symmetric(vertical: 1, horizontal: 6), margin: messageReactionConfig?.margin ?? EdgeInsets.only( left: widget.isMessageBySender ? 10 : 16, @@ -92,7 +93,7 @@ class _ReactionWidgetState extends State { ), decoration: BoxDecoration( color: messageReactionConfig?.backgroundColor ?? - Colors.grey.shade200, + Colors.grey.shade300, borderRadius: messageReactionConfig?.borderRadius ?? BorderRadius.circular(16), border: Border.all( @@ -109,12 +110,12 @@ class _ReactionWidgetState extends State { ), ), if ((chatController?.chatUsers.length ?? 0) > 1) ...[ - if (!(widget.reaction.reactedUserIds.length > 3) && + if (!(widget.message.reaction.reactedUserIds.length > 3) && !(reactionsSet.length > 1)) ...List.generate( - widget.reaction.reactedUserIds.length, + widget.message.reaction.reactedUserIds.length, (reactedUserIndex) => widget - .reaction.reactedUserIds[reactedUserIndex] + .message.reaction.reactedUserIds[reactedUserIndex] .getUserProfilePicture( getChatUser: (userId) => chatController?.getUserFromId(userId), @@ -124,12 +125,12 @@ class _ReactionWidgetState extends State { messageReactionConfig?.profileCircleRadius, ), ), - if (widget.reaction.reactedUserIds.length > 3 && + if (widget.message.reaction.reactedUserIds.length > 3 && !(reactionsSet.length > 1)) Padding( padding: const EdgeInsets.only(left: 2), child: Text( - '+${widget.reaction.reactedUserIds.length}', + '+${widget.message.reaction.reactedUserIds.length}', style: messageReactionConfig?.reactedUserCountTextStyle ?? _reactionTextStyle, @@ -139,7 +140,8 @@ class _ReactionWidgetState extends State { Padding( padding: const EdgeInsets.only(left: 2), child: Text( - widget.reaction.reactedUserIds.length.toString(), + widget.message.reaction.reactedUserIds.length + .toString(), style: messageReactionConfig?.reactionCountTextStyle ?? _reactionTextStyle, ), diff --git a/lib/src/widgets/text_message_view.dart b/lib/src/widgets/text_message_view.dart index 4dacc90b..afeac685 100644 --- a/lib/src/widgets/text_message_view.dart +++ b/lib/src/widgets/text_message_view.dart @@ -19,12 +19,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ +import 'dart:ui'; + +import 'package:chatview/chatview.dart'; +import 'package:chatview/src/widgets/message_time_widget.dart'; import 'package:flutter/material.dart'; import 'package:chatview/src/extensions/extensions.dart'; -import 'package:chatview/src/models/models.dart'; import '../utils/constants/constants.dart'; +import 'chat_view_inherited_widget.dart'; import 'link_preview.dart'; import 'reaction_widget.dart'; @@ -39,6 +43,8 @@ class TextMessageView extends StatelessWidget { this.messageReactionConfig, this.highlightMessage = false, this.highlightColor, + this.messageDateTimeBuilder, + this.messageTimeTextStyle, }) : super(key: key); /// Represents current message is sent by current user. @@ -65,51 +71,141 @@ class TextMessageView extends StatelessWidget { /// Allow user to set color of highlighted message. final Color? highlightColor; + /// Allow user to set custom formatting of message time. + final MessageDateTimeBuilder? messageDateTimeBuilder; + + /// Used to give text style of message's time of a chat bubble + final TextStyle? messageTimeTextStyle; + @override Widget build(BuildContext context) { final textTheme = Theme.of(context).textTheme; final textMessage = message.message; - return Stack( - clipBehavior: Clip.none, - children: [ - Container( - constraints: BoxConstraints( - maxWidth: chatBubbleMaxWidth ?? - MediaQuery.of(context).size.width * 0.75), - padding: _padding ?? - const EdgeInsets.symmetric( - horizontal: 12, - vertical: 10, - ), - margin: _margin ?? - EdgeInsets.fromLTRB( - 5, 0, 6, message.reaction.reactions.isNotEmpty ? 15 : 2), - decoration: BoxDecoration( - color: highlightMessage ? highlightColor : _color, - borderRadius: _borderRadius(textMessage), - ), - child: textMessage.isUrl - ? LinkPreview( - linkPreviewConfig: _linkPreviewConfig, - url: textMessage, - ) - : Text( - textMessage, - style: _textStyle ?? - textTheme.bodyMedium!.copyWith( - color: Colors.white, - fontSize: 16, - ), + final messageTimePositionType = ChatViewInheritedWidget.of(context) + ?.featureActiveConfig + .messageTimePositionType ?? + MessageTimePositionType.onRightSwipe; + final defaultTextStyle = textTheme.bodyMedium!.copyWith( + color: Colors.white, + fontSize: 16, + ); + + final messageMetaData = _textSize( + text: textMessage, + context: context, + style: _textStyle ?? defaultTextStyle, + ); + + final isSenderMessageOrURl = isMessageBySender || textMessage.isUrl; + + return Padding( + padding: messageTimePositionType.isOutSideChatBubbleAtBottom + ? const EdgeInsets.only(bottom: 10) + : EdgeInsets.zero, + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + constraints: BoxConstraints( + maxWidth: chatBubbleMaxWidth ?? + MediaQuery.of(context).size.width * 0.75), + padding: _padding ?? + const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, ), - ), - if (message.reaction.reactions.isNotEmpty) - ReactionWidget( - key: key, - isMessageBySender: isMessageBySender, - reaction: message.reaction, - messageReactionConfig: messageReactionConfig, + margin: _margin ?? + EdgeInsets.fromLTRB( + 5, 0, 6, message.reaction.reactions.isNotEmpty ? 15 : 2), + decoration: BoxDecoration( + color: highlightMessage ? highlightColor : _color, + borderRadius: _borderRadius(textMessage), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + textMessage.isUrl + ? LinkPreview( + linkPreviewConfig: _linkPreviewConfig, + url: textMessage, + message: message, + isMessageBySender: isMessageBySender, + messageDateTimeBuilder: messageDateTimeBuilder, + messageTimeTextStyle: messageTimeTextStyle, + ) + : Wrap( + alignment: WrapAlignment.end, + spacing: messageMetaData.numberOfLine < 2 ? 10 : 0, + children: [ + Text( + textMessage, + style: _textStyle ?? defaultTextStyle, + ), + if (messageTimePositionType.isInsideChatBubble) + Transform.translate( + offset: const Offset(0, 8), + child: messageDateTimeBuilder + ?.call(message.createdAt) ?? + MessageTimeWidget( + messageTime: message.createdAt, + isCurrentUser: isMessageBySender, + messageTimeTextStyle: messageTimeTextStyle, + ), + ), + ], + ), + ], + ), ), - ], + if (message.reaction.reactions.isNotEmpty) + ReactionWidget( + key: key, + isMessageBySender: isMessageBySender, + message: message, + messageReactionConfig: messageReactionConfig, + ), + if (messageTimePositionType.isOutSideChatBubbleAtBottom) + Positioned( + bottom: messageMetaData.numberOfLine <= 1 && + messageMetaData.messageWidth <= 90 + ? -18 + : message.reaction.reactions.isNotEmpty + ? -4 + : -16, + right: isSenderMessageOrURl ? 10 : null, + left: isSenderMessageOrURl ? null : 12, + child: messageDateTimeBuilder?.call(message.createdAt) ?? + MessageTimeWidget( + isCurrentUser: isMessageBySender, + messageTime: message.createdAt, + messageTimeTextStyle: messageTimeTextStyle, + ), + ), + ], + ), + ); + } + + ({int numberOfLine, double messageWidth}) _textSize({ + required String text, + required TextStyle style, + required BuildContext context, + }) { + final TextPainter textPainter = TextPainter( + text: TextSpan( + text: text, + style: style, + ), + textDirection: TextDirection.ltr, + )..layout( + maxWidth: + (chatBubbleMaxWidth ?? MediaQuery.of(context).size.width * 0.75) - + (_padding?.horizontal ?? 24)); + + return ( + numberOfLine: textPainter.computeLineMetrics().length, + messageWidth: textPainter.width ); } diff --git a/lib/src/widgets/voice_message_view.dart b/lib/src/widgets/voice_message_view.dart index 51dd06ac..ec6f43d7 100644 --- a/lib/src/widgets/voice_message_view.dart +++ b/lib/src/widgets/voice_message_view.dart @@ -2,7 +2,9 @@ import 'dart:async'; import 'package:audio_waveforms/audio_waveforms.dart'; import 'package:chatview/chatview.dart'; +import 'package:chatview/src/extensions/extensions.dart'; import 'package:chatview/src/models/voice_message_configuration.dart'; +import 'package:chatview/src/widgets/message_time_widget.dart'; import 'package:chatview/src/widgets/reaction_widget.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -18,6 +20,8 @@ class VoiceMessageView extends StatefulWidget { this.onMaxDuration, this.messageReactionConfig, this.config, + this.messageDateTimeBuilder, + this.messageTimeTextStyle, }) : super(key: key); /// Provides configuration related to voice message. @@ -42,6 +46,12 @@ class VoiceMessageView extends StatefulWidget { /// Provides configuration of chat bubble appearance from current user of chat. final ChatBubble? outgoingChatBubbleConfig; + /// Allow user to set custom formatting of message time. + final MessageDateTimeBuilder? messageDateTimeBuilder; + + /// Used to give text style of message's time of a chat bubble + final TextStyle? messageTimeTextStyle; + @override State createState() => _VoiceMessageViewState(); } @@ -81,71 +91,103 @@ class _VoiceMessageViewState extends State { @override Widget build(BuildContext context) { - return Stack( - clipBehavior: Clip.none, - children: [ - Container( - decoration: widget.config?.decoration ?? - BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: widget.isMessageBySender - ? widget.outgoingChatBubbleConfig?.color - : widget.inComingChatBubbleConfig?.color, - ), - padding: widget.config?.padding ?? - const EdgeInsets.symmetric(horizontal: 8), - margin: widget.config?.margin ?? - EdgeInsets.symmetric( - horizontal: 8, - vertical: widget.message.reaction.reactions.isNotEmpty ? 15 : 0, - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - ValueListenableBuilder( - builder: (context, state, child) { - return IconButton( - onPressed: _playOrPause, - icon: - state.isStopped || state.isPaused || state.isInitialised - ? widget.config?.playIcon ?? - const Icon( - Icons.play_arrow, - color: Colors.white, - ) - : widget.config?.pauseIcon ?? - const Icon( - Icons.stop, - color: Colors.white, - ), - ); - }, - valueListenable: _playerState, - ), - AudioFileWaveforms( - size: Size(widget.screenWidth * 0.50, 60), - playerController: controller, - waveformType: WaveformType.fitWidth, - playerWaveStyle: - widget.config?.playerWaveStyle ?? playerWaveStyle, - padding: widget.config?.waveformPadding ?? - const EdgeInsets.only(right: 10), - margin: widget.config?.waveformMargin, - animationCurve: widget.config?.animationCurve ?? Curves.easeIn, - animationDuration: widget.config?.animationDuration ?? - const Duration(milliseconds: 500), - enableSeekGesture: widget.config?.enableSeekGesture ?? true, - ), - ], - ), - ), - if (widget.message.reaction.reactions.isNotEmpty) - ReactionWidget( - isMessageBySender: widget.isMessageBySender, - reaction: widget.message.reaction, - messageReactionConfig: widget.messageReactionConfig, + final messageTimePositionType = + provide?.featureActiveConfig.messageTimePositionType ?? + MessageTimePositionType.onRightSwipe; + return Padding( + padding: messageTimePositionType.isOutSideChatBubbleAtBottom + ? const EdgeInsets.only(bottom: 10) + : EdgeInsets.zero, + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + decoration: widget.config?.decoration ?? + BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: widget.isMessageBySender + ? widget.outgoingChatBubbleConfig?.color + : widget.inComingChatBubbleConfig?.color, + ), + padding: widget.config?.padding ?? + const EdgeInsets.symmetric(horizontal: 8), + margin: widget.config?.margin ?? + EdgeInsets.fromLTRB( + 8, + 0, + 8, + widget.message.reaction.reactions.isNotEmpty ? 15 : 0, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + ValueListenableBuilder( + builder: (context, state, child) { + return IconButton( + onPressed: _playOrPause, + icon: state.isStopped || + state.isPaused || + state.isInitialised + ? widget.config?.playIcon ?? + const Icon( + Icons.play_arrow, + color: Colors.white, + ) + : widget.config?.pauseIcon ?? + const Icon( + Icons.stop, + color: Colors.white, + ), + ); + }, + valueListenable: _playerState, + ), + AudioFileWaveforms( + size: Size(widget.screenWidth * 0.50, 60), + playerController: controller, + waveformType: WaveformType.fitWidth, + playerWaveStyle: + widget.config?.playerWaveStyle ?? playerWaveStyle, + padding: widget.config?.waveformPadding ?? + const EdgeInsets.only(right: 10), + margin: widget.config?.waveformMargin, + animationCurve: + widget.config?.animationCurve ?? Curves.easeIn, + animationDuration: widget.config?.animationDuration ?? + const Duration(milliseconds: 500), + enableSeekGesture: widget.config?.enableSeekGesture ?? true, + ), + ], + ), ), - ], + if (widget.message.reaction.reactions.isNotEmpty) + ReactionWidget( + isMessageBySender: widget.isMessageBySender, + message: widget.message, + messageReactionConfig: widget.messageReactionConfig, + ), + if (!(messageTimePositionType.isOnRightSwipe || + messageTimePositionType.isDisable || + messageTimePositionType.isOutSideChatBubbleAtTop)) + Positioned( + right: 18, + bottom: messageTimePositionType.isInsideChatBubble + ? widget.message.reaction.reactions.isNotEmpty + ? 18 + : 4 + : widget.message.reaction.reactions.isNotEmpty + ? -2 + : -16, + child: widget.messageDateTimeBuilder + ?.call(widget.message.createdAt) ?? + MessageTimeWidget( + isCurrentUser: widget.isMessageBySender, + messageTime: widget.message.createdAt, + messageTimeTextStyle: widget.messageTimeTextStyle, + ), + ), + ], + ), ); } diff --git a/pubspec.yaml b/pubspec.yaml index bf4ee825..1dba6af0 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: 1.3.1 +version: 1.4.0 issue_tracker: https://github.com/SimformSolutionsPvtLtd/flutter_chatview/issues repository: https://github.com/SimformSolutionsPvtLtd/flutter_chatview