diff --git a/example/lib/customize_message_widget.dart b/example/lib/customize_message_widget.dart index a3713b69..84e65eab 100644 --- a/example/lib/customize_message_widget.dart +++ b/example/lib/customize_message_widget.dart @@ -120,7 +120,7 @@ class ChannelPage extends StatelessWidget { top: !details.isLastUser ? Radius.circular(16) : Radius.zero, bottom: !details.isNextUser ? Radius.circular(16) : Radius.zero, ), - showSendingIndicator: DisplayWidget.gone, + showSendingIndicator: false, reverse: false, showUserAvatar: details.isNextUser ? DisplayWidget.hide : DisplayWidget.show, diff --git a/lib/src/message_actions_modal.dart b/lib/src/message_actions_modal.dart index d7497676..0aa53824 100644 --- a/lib/src/message_actions_modal.dart +++ b/lib/src/message_actions_modal.dart @@ -1,4 +1,3 @@ -import 'dart:math'; import 'dart:ui'; import 'package:flutter/foundation.dart'; @@ -139,7 +138,7 @@ class MessageActionsModal extends StatelessWidget { MessageSendingStatus.SENT || message.status == null), showInChannelIndicator: false, - showSendingIndicator: DisplayWidget.gone, + showSendingIndicator: false, shape: messageShape, ), ), diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 415539b8..41792390 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -220,6 +220,8 @@ class _MessageListViewState extends State { bool get _upToDate => streamChannel.channel.state.isUpToDate; + bool get _isThreadConversation => widget.parentMessage != null; + bool _topPaginationActive = false; bool _bottomPaginationActive = false; @@ -234,7 +236,7 @@ class _MessageListViewState extends State { @override Widget build(BuildContext context) { - final messagesStream = widget.parentMessage != null + final messagesStream = _isThreadConversation ? streamChannel.channel.state.threadsStream .where((threads) => threads.containsKey(widget.parentMessage.id)) .map((threads) => threads[widget.parentMessage.id]) @@ -333,9 +335,8 @@ class _MessageListViewState extends State { physics: widget.scrollPhysics, itemScrollController: _scrollController, reverse: true, - itemCount: messages.length + - 2 + - (widget.parentMessage != null ? 1 : 0), + itemCount: + messages.length + 2 + (_isThreadConversation ? 1 : 0), separatorBuilder: (context, i) { if (i == messages.length) return Offstage(); if (i == messages.length + 2) return Offstage(); @@ -497,7 +498,7 @@ class _MessageListViewState extends State { Future _paginateData( StreamChannelState channel, QueryDirection direction) { - if (widget.parentMessage == null) { + if (!_isThreadConversation) { return channel.queryMessages(direction: direction); } else { return channel.getReplies(widget.parentMessage.id); @@ -722,7 +723,7 @@ class _MessageListViewState extends State { right: 8.0, bottom: 16.0, ), - showSendingIndicator: DisplayWidget.hide, + showSendingIndicator: false, onThreadTap: _onThreadTap, showEditMessage: false, showDeleteMessage: false, @@ -768,32 +769,60 @@ class _MessageListViewState extends State { } final channel = streamChannel.channel; - final readList = channel.state?.read - ?.where((element) => element.user.id != userId) - ?.where((read) => - (read.lastRead.isAfter(message.createdAt) || - read.lastRead.isAtSameMomentAs(message.createdAt)) && - (index == 0 || - read.lastRead.isBefore(messages[index - 1].createdAt))) - ?.toList() ?? + final readList = channel.state?.read?.where((read) { + if (read.user.id == userId) return false; + return (read.lastRead.isAfter(message.createdAt) || + read.lastRead.isAtSameMomentAs(message.createdAt)) && + (index == 0 || + read.lastRead.isBefore(messages[index - 1].createdAt)); + })?.toList() ?? []; final allRead = readList.length >= (channel.memberCount ?? 0) - 1; - - final isThreadMessage = - widget.parentMessage != null || message?.showInChannel == true; - final hasFileAttachment = message.attachments.any((it) => it.type == 'file'); + final isThreadMessage = + message?.parentId != null && message?.showInChannel == true; + + final hasReplies = message.replyCount > 0; + final attachmentBorderRadius = hasFileAttachment ? 12.0 : 14.0; + final showTimeStamp = message.createdAt != null && + (!isThreadMessage || _isThreadConversation) && + !hasReplies && + (timeDiff >= 1 || !isNextUserSame); + + final showUsername = !isMyMessage && + (!isThreadMessage || _isThreadConversation) && + !hasReplies && + (timeDiff >= 1 || !isNextUserSame); + + final showUserAvatar = isMyMessage + ? DisplayWidget.gone + : (timeDiff >= 1 || !isNextUserSame) + ? DisplayWidget.show + : DisplayWidget.hide; + + final showSendingIndicator = + isMyMessage && (index == 0 || timeDiff >= 1 || !isNextUserSame); + + bool showInChannelIndicator = !_isThreadConversation && isThreadMessage; + bool showThreadReplyIndicator = !_isThreadConversation && hasReplies; + Widget child = MessageWidget( key: ValueKey('MESSAGE-${message.id}'), message: message, reverse: isMyMessage, showReactions: !message.isDeleted, padding: const EdgeInsets.symmetric(horizontal: 8.0), + showInChannelIndicator: showInChannelIndicator, + showThreadReplyIndicator: showThreadReplyIndicator, + showUsername: showUsername, + showTimestamp: showTimeStamp, + showSendingIndicator: showSendingIndicator, + showUserAvatar: showUserAvatar, onQuotedMessageTap: (quotedMessageId) async { final scrollToIndex = () { final index = messages.indexWhere((m) => m.id == quotedMessageId); @@ -814,15 +843,6 @@ class _MessageListViewState extends State { }); } }, - showInChannelIndicator: widget.parentMessage == null, - showThreadReplyIndicator: widget.parentMessage == null, - showUsername: !isMyMessage && (timeDiff >= 1 || !isNextUserSame), - showSendingIndicator: - isMyMessage && (index == 0 || timeDiff >= 1 || !isNextUserSame) - ? DisplayWidget.show - : DisplayWidget.hide, - showTimestamp: - !isNextUserSame || readList?.isNotEmpty == true || timeDiff >= 1, showEditMessage: isMyMessage, showDeleteMessage: isMyMessage, borderSide: isMyMessage ? BorderSide.none : null, @@ -842,11 +862,6 @@ class _MessageListViewState extends State { topRight: Radius.circular(16), bottomRight: Radius.circular(16), ), - showUserAvatar: isMyMessage - ? DisplayWidget.gone - : (timeDiff >= 1 || !isNextUserSame - ? DisplayWidget.show - : DisplayWidget.hide), messageTheme: isMyMessage ? StreamChatTheme.of(context).ownMessageTheme : StreamChatTheme.of(context).otherMessageTheme, @@ -918,7 +933,7 @@ class _MessageListViewState extends State { } }); - if (widget.parentMessage != null) { + if (_isThreadConversation) { streamChannel.getReplies(widget.parentMessage.id); } diff --git a/lib/src/message_reactions_modal.dart b/lib/src/message_reactions_modal.dart index 702bb0ed..5c24e047 100644 --- a/lib/src/message_reactions_modal.dart +++ b/lib/src/message_reactions_modal.dart @@ -116,7 +116,7 @@ class MessageReactionsModal extends StatelessWidget { showThreadReplyIndicator: false, showTimestamp: false, translateUserAvatar: false, - showSendingIndicator: DisplayWidget.gone, + showSendingIndicator: false, shape: messageShape, showInChannelIndicator: false, showReactionPickerIndicator: showReactions && diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 12137c64..22f91b95 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -96,7 +96,7 @@ class MessageWidget extends StatefulWidget { final DisplayWidget showUserAvatar; /// It controls the display behaviour of the sending indicator - final DisplayWidget showSendingIndicator; + final bool showSendingIndicator; /// If true the widget will show the reactions final bool showReactions; @@ -157,7 +157,7 @@ class MessageWidget extends StatefulWidget { this.onMentionTap, this.showReactionPickerIndicator = false, this.showUserAvatar = DisplayWidget.show, - this.showSendingIndicator = DisplayWidget.show, + this.showSendingIndicator = true, this.showThreadReplyIndicator = true, this.showInChannelIndicator = true, this.showReplyIndicator = true, @@ -239,18 +239,17 @@ class MessageWidget extends StatefulWidget { } class _MessageWidgetState extends State { - bool get showThreadReplyIndicator => - widget.showThreadReplyIndicator && widget.message.replyCount > 0; + bool get showThreadReplyIndicator => widget.showThreadReplyIndicator; + + bool get showSendingIndicator => widget.showSendingIndicator; bool get showUsername => widget.showUsername; - bool get showTimeStamp => - widget.message.createdAt != null && widget.showTimestamp; + bool get showTimeStamp => widget.showTimestamp; bool get isMessageRead => widget.readList?.isNotEmpty == true; - bool get showInChannel => - widget.showInChannelIndicator && widget.message?.showInChannel == true; + bool get showInChannel => widget.showInChannelIndicator; bool get hasQuotedMessage => widget.message?.quotedMessage != null; @@ -494,88 +493,84 @@ class _MessageWidgetState extends State { } Widget _buildBottomRow(double leftPadding) { - final deleted = widget.message.isDeleted; - var children = []; - if (deleted) { - children.add( - Row( - mainAxisSize: MainAxisSize.min, - children: [ - StreamSvgIcon.eye( - color: StreamChatTheme.of(context).colorTheme.grey, - size: 16.0, - ), - SizedBox(width: 8.0), - Text( - 'Only visible to you', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith(color: StreamChatTheme.of(context).colorTheme.grey), - ), - ], + if (widget.message.isDeleted) { + return Padding( + padding: EdgeInsets.only(left: leftPadding), + child: Transform( + transform: Matrix4.rotationY(widget.reverse ? pi : 0), + alignment: Alignment.center, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + StreamSvgIcon.eye( + color: StreamChatTheme.of(context).colorTheme.grey, + size: 16.0, + ), + SizedBox(width: 8.0), + Text( + 'Only visible to you', + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context).colorTheme.grey), + ), + ], + ), ), ); - } else if (showInChannel) { - final onThreadTap = () async { - try { + } + + var children = []; + + final threadParticipants = widget.message.threadParticipants; + final showThreadParticipants = threadParticipants?.isNotEmpty == true; + final replyCount = widget.message.replyCount; + + var msg = 'Thread Reply'; + if (showThreadReplyIndicator && replyCount > 1) { + msg = '$replyCount Thread Replies'; + } + + final onThreadTap = () async { + try { + var message = widget.message; + if (showInChannel) { final channel = StreamChannel.of(context); - final message = await channel.getMessage(widget.message.parentId); - return widget.onThreadTap(message); - } catch (e, stk) { - print(e); - print(stk); - return null; + message = await channel.getMessage(widget.message.parentId); } - }; - children.add( + return widget.onThreadTap(message); + } catch (e, stk) { + print(e); + print(stk); + return null; + } + }; + + children.addAll([ + if (showSendingIndicator) _buildSendingIndicator(), + if (showInChannel || showThreadReplyIndicator) ...[ + if (showThreadParticipants) + SizedBox.fromSize( + size: Size((threadParticipants.length * 8.0) + 10, 16), + child: _buildThreadParticipantsIndicator(), + ), InkWell( onTap: widget.onThreadTap != null ? onThreadTap : null, - child: Text('Thread Reply', style: widget.messageTheme?.replies), + child: Text(msg, style: widget.messageTheme?.replies), ), - ); - } else { - final showSendingIndicator = - widget.showSendingIndicator == DisplayWidget.show; - final threadParticipants = widget.message.threadParticipants; - final showThreadParticipants = threadParticipants?.isNotEmpty == true; - final replyCount = widget.message.replyCount; - final msg = replyCount != 0 - ? '$replyCount ${replyCount > 1 ? 'Thread Replies' : 'Thread Reply'}' - : 'Thread Reply'; + ], + if (showUsername) + Text( + widget.message.user.name, + style: widget.messageTheme.replies.copyWith( + color: widget.messageTheme.createdAt.color, + ), + ), + if (showTimeStamp) + Text( + Jiffy(widget.message.createdAt.toLocal()).jm, + style: widget.messageTheme.createdAt, + ), + ]); - final onThreadTap = () async { - var message = widget.message; - return widget.onThreadTap(message); - }; - - children.addAll([ - if (showSendingIndicator) _buildSendingIndicator(), - if (showThreadReplyIndicator) ...[ - if (showThreadParticipants) - SizedBox.fromSize( - size: Size((threadParticipants.length * 8.0) + 10, 16), - child: _buildThreadParticipantsIndicator(), - ), - InkWell( - onTap: widget.onThreadTap != null ? onThreadTap : null, - child: Text(msg, style: widget.messageTheme?.replies), - ), - ], - if (showUsername) - Text( - widget.message.user.name, - style: widget.messageTheme.replies.copyWith( - color: widget.messageTheme.createdAt.color, - ), - ), - if (showTimeStamp) - Text( - Jiffy(widget.message.createdAt.toLocal()).jm, - style: widget.messageTheme.createdAt, - ), - ]); - } if (widget.reverse) children = children.reversed.toList(); return Padding( @@ -585,7 +580,7 @@ class _MessageWidgetState extends State { clipBehavior: Clip.none, crossAxisAlignment: CrossAxisAlignment.end, children: [ - if (!deleted && (showThreadReplyIndicator || showInChannel)) + if (showThreadReplyIndicator || showInChannel) Container( margin: EdgeInsets.only( bottom: widget.messageTheme.replies.fontSize / 2,