From 7006aace129e08a4ddd7c5b8183a9da58c7b465e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 10 Jun 2020 15:21:04 +0200 Subject: [PATCH] add read indicators --- lib/src/message_list_view.dart | 13 ++++- lib/src/message_widget.dart | 91 ++++++++++++++++++++++++++-------- pubspec.yaml | 2 +- 3 files changed, 82 insertions(+), 24 deletions(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 85a1b0b0..b3fb7924 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -425,6 +425,16 @@ class _MessageListViewState extends State { final isNextUser = index - 1 >= 0 && message.user.id == messages[index - 1]?.user?.id; + final readList = StreamChannel.of(context) + .channel + .state + .read + .where((read) => + read.lastRead.isAfter(message.createdAt) && + (index == 0 || + read.lastRead.isBefore(messages[index - 1].createdAt))) + .toList(); + return MessageWidget( message: message, reverse: isMyMessage, @@ -439,7 +449,7 @@ class _MessageListViewState extends State { (index == 0 || message.status != MessageSendingStatus.SENT) ? DisplayWidget.show : DisplayWidget.hide, - showTimestamp: !isNextUser, + showTimestamp: !isNextUser || readList.isNotEmpty, showEditMessage: isMyMessage, showDeleteMessage: isMyMessage, borderSide: isMyMessage ? BorderSide.none : null, @@ -455,6 +465,7 @@ class _MessageListViewState extends State { messageTheme: isMyMessage ? StreamChatTheme.of(context).ownMessageTheme : StreamChatTheme.of(context).otherMessageTheme, + readList: readList, ); } diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 83705894..f93aeaa0 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -97,6 +97,8 @@ class MessageWidget extends StatefulWidget { /// The function called when tapping on UserAvatar final void Function(User) onUserAvatarTap; + final List readList; + /// If true show the users username next to the timestamp of the message final bool showUsername; final bool showTimestamp; @@ -130,6 +132,7 @@ class MessageWidget extends StatefulWidget { this.editMessageInputBuilder, this.textBuilder, Map customAttachmentBuilders, + this.readList, this.padding, this.textPadding = const EdgeInsets.all(8.0), this.attachmentPadding = EdgeInsets.zero, @@ -298,8 +301,9 @@ class _MessageWidgetState extends State { ), if ((widget.message.createdAt != null && widget.showTimestamp) || - widget.showUsername) - _buildUsernameAndTimestamp(leftPadding), + widget.showUsername || + widget.readList?.isNotEmpty == true) + _buildBottomRow(leftPadding), ], ), ), @@ -360,36 +364,79 @@ class _MessageWidgetState extends State { ); } - Padding _buildUsernameAndTimestamp(double leftPadding) { + Padding _buildBottomRow(double leftPadding) { return Padding( padding: EdgeInsets.only( left: leftPadding, top: 2, ), - child: Transform( - alignment: Alignment.center, - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - child: RichText( - text: TextSpan( - style: widget.messageTheme.createdAt, - children: [ - if (widget.showUsername) - TextSpan( - text: widget.message.user.name, - style: TextStyle(fontWeight: FontWeight.bold), - ), - if (widget.message.createdAt != null && widget.showTimestamp) - TextSpan( - text: Jiffy(widget.message.createdAt.toLocal()) - .format(' HH:mm'), - ), - ], + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY(widget.reverse ? pi : 0), + child: RichText( + text: TextSpan( + style: widget.messageTheme.createdAt, + children: [ + if (widget.showUsername) + TextSpan( + text: widget.message.user.name, + style: TextStyle(fontWeight: FontWeight.bold), + ), + if (widget.message.createdAt != null && widget.showTimestamp) + TextSpan( + text: Jiffy(widget.message.createdAt.toLocal()) + .format(' HH:mm'), + ), + ], + ), + ), ), - ), + if (widget.readList?.isNotEmpty == true) + SizedBox.fromSize( + size: Size(widget.readList.length * 20.0, 17), + child: Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY(widget.reverse ? pi : 0), + child: Padding( + padding: const EdgeInsets.only(left: 4.0), + child: _buildReadIndicator(), + ), + ), + ), + ], ), ); } + Widget _buildReadIndicator() { + var padding = 0.0; + return Stack( + children: widget.readList.map((e) { + padding += 10.0; + return Positioned( + left: padding - 10, + bottom: 0, + top: 0, + child: Material( + color: Colors.white, + shape: CircleBorder(), + child: Padding( + padding: const EdgeInsets.all(1.0), + child: UserAvatar( + user: e.user, + constraints: BoxConstraints.loose(Size.fromRadius(16)), + ), + ), + ), + ); + }).toList(), + ); + } + Widget _buildReactionIndicator(BuildContext context) { return AnimatedSwitcher( key: _reactionPickerKey, diff --git a/pubspec.yaml b/pubspec.yaml index 7335e45e..f3007463 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: file_picker: ^1.9.0+1 image_picker: ^0.6.7 flutter_keyboard_visibility: ^3.0.0 - stream_chat: ^0.2.0-alpha+19 + stream_chat: ^0.2.0-alpha+20 mime: ^0.9.6+3 visibility_detector: ^0.1.5 http_parser: ^3.1.4