Skip to content

Commit e93e223

Browse files
authored
fix(llc, ui): improve read status handling (#2402)
1 parent 65cb290 commit e93e223

File tree

5 files changed

+78
-38
lines changed

5 files changed

+78
-38
lines changed

packages/stream_chat/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
🐞 Fixed
44

5+
- Fixed thread messages increasing the unread count in the main channel.
56
- Fixed `ChannelState.memberCount`, `ChannelState.config` and `ChannelState.extraData` getting reset
67
on first load.
78

packages/stream_chat/lib/src/client/channel.dart

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3229,7 +3229,7 @@ class ChannelClientState {
32293229
if (message.isEphemeral) return false;
32303230

32313231
// Don't count thread replies which are not shown in the channel as unread.
3232-
if (message.parentId != null && message.showInChannel == false) {
3232+
if (message.parentId != null && message.showInChannel != true) {
32333233
return false;
32343234
}
32353235

@@ -3251,6 +3251,18 @@ class ChannelClientState {
32513251
final isMuted = currentUser.mutes.any((it) => it.user.id == messageUser.id);
32523252
if (isMuted) return false;
32533253

3254+
final lastRead = currentUserRead?.lastRead;
3255+
// Don't count messages created before the last read time as unread.
3256+
if (lastRead case final read? when message.createdAt.isBefore(read)) {
3257+
return false;
3258+
}
3259+
3260+
final lastReadMessageId = currentUserRead?.lastReadMessageId;
3261+
// Don't count if the last read message id is the same as the message id.
3262+
if (lastReadMessageId case final id? when message.id == id) {
3263+
return false;
3264+
}
3265+
32543266
// If we've passed all checks, count the message as unread.
32553267
return true;
32563268
}

packages/stream_chat_flutter/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## Upcoming
2+
3+
🐞 Fixed
4+
5+
- Fixed `StreamMessageListView` not marking thread messages as read when scrolled to the bottom of the list.
6+
- Fixed `StreamMessageInput` not validating draft messages before creating/updating them.
7+
18
## 9.17.0
29

310
✅ Added

packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -463,11 +463,11 @@ class StreamMessageInput extends StatefulWidget {
463463
}
464464

465465
static bool _defaultValidator(Message message) {
466-
// The message is valid if it has text or attachments.
467-
if (message.attachments.isNotEmpty) return true;
468-
if (message.text?.trim() case final text? when text.isNotEmpty) return true;
466+
final hasText = message.text?.trim().isNotEmpty == true;
467+
final hasAttachments = message.attachments.isNotEmpty;
468+
final hasPoll = message.pollId != null;
469469

470-
return false;
470+
return hasText || hasAttachments || hasPoll;
471471
}
472472

473473
static bool _defaultSendMessageKeyPredicate(
@@ -1594,6 +1594,10 @@ class StreamMessageInputState extends State<StreamMessageInput>
15941594

15951595
final draftMessage = message.toDraftMessage();
15961596

1597+
// If the draft message is not valid, we don't need to update it.
1598+
final isDraftValid = widget.validator.call(draftMessage.toMessage());
1599+
if (!isDraftValid) return;
1600+
15971601
// If the draft message didn't change, we don't need to update it.
15981602
if (draft?.message == draftMessage) return;
15991603

packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart

Lines changed: 49 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,9 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
417417
if (newStreamChannel != streamChannel) {
418418
streamChannel = newStreamChannel;
419419

420+
debouncedMarkRead.cancel();
421+
debouncedMarkThreadRead.cancel();
422+
420423
_messageNewListener?.cancel();
421424
_userReadListener?.cancel();
422425

@@ -467,8 +470,8 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
467470

468471
@override
469472
void dispose() {
470-
debouncedMarkRead?.cancel();
471-
debouncedMarkThreadRead?.cancel();
473+
debouncedMarkRead.cancel();
474+
debouncedMarkThreadRead.cancel();
472475
_messageNewListener?.cancel();
473476
_userReadListener?.cancel();
474477
_itemPositionListener.itemPositions
@@ -954,29 +957,23 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
954957
}
955958
}
956959

957-
late final debouncedMarkRead = switch (streamChannel) {
958-
final streamChannel? => debounce(
959-
streamChannel.channel.markRead,
960-
const Duration(seconds: 1),
961-
),
962-
_ => null,
963-
};
960+
late final debouncedMarkRead = debounce(
961+
([String? id]) => streamChannel?.channel.markRead(messageId: id),
962+
const Duration(seconds: 1),
963+
);
964964

965-
late final debouncedMarkThreadRead = switch (streamChannel) {
966-
final streamChannel? => debounce(
967-
streamChannel.channel.markThreadRead,
968-
const Duration(seconds: 1),
969-
),
970-
_ => null,
971-
};
965+
late final debouncedMarkThreadRead = debounce(
966+
(String parentId) => streamChannel?.channel.markThreadRead(parentId),
967+
const Duration(seconds: 1),
968+
);
972969

973970
Future<void> _markMessagesAsRead() async {
974-
// Mark regular messages as read.
975-
debouncedMarkRead?.call();
976-
977-
// Mark thread messages as read.
978971
if (widget.parentMessage case final parent?) {
979-
debouncedMarkThreadRead?.call([parent.id]);
972+
// If we are in a thread, mark the thread as read.
973+
debouncedMarkThreadRead.call([parent.id]);
974+
} else {
975+
// Otherwise, mark the channel as read.
976+
debouncedMarkRead.call();
980977
}
981978
}
982979

@@ -1473,24 +1470,43 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
14731470
null => true, // Allows setting the initial value.
14741471
};
14751472

1476-
// If the channel is upToDate and the last fully visible message has
1477-
// been changed, we need to update the value and mark the messages as read.
1478-
if (_upToDate && lastFullyVisibleMessageChanged) {
1473+
// If the last fully visible message has been changed, we need to update the
1474+
// value and maybe mark messages as read if needed.
1475+
if (lastFullyVisibleMessageChanged) {
14791476
_lastFullyVisibleMessage = newLastFullyVisibleMessage;
14801477

1481-
if (streamChannel?.channel case final channel?) {
1482-
final hasUnread = (channel.state?.unreadCount ?? 0) > 0;
1483-
final allowMarkRead = channel.config?.readEvents == true;
1484-
final canMarkReadAtBottom = widget.markReadWhenAtTheBottom;
1485-
1486-
// Mark messages as read if it's allowed.
1487-
if (hasUnread && allowMarkRead && canMarkReadAtBottom) {
1488-
return _markMessagesAsRead().ignore();
1489-
}
1478+
// Mark messages as read if needed.
1479+
if (widget.markReadWhenAtTheBottom) {
1480+
_maybeMarkMessagesAsRead().ignore();
14901481
}
14911482
}
14921483
}
14931484

1485+
// Marks messages as read if the conditions are met.
1486+
//
1487+
// The conditions are:
1488+
// 1. The channel is up to date or we are in a thread conversation.
1489+
// 2. There are unread messages or we are in a thread conversation.
1490+
//
1491+
// If any of the conditions are not met, the function returns early.
1492+
// Otherwise, it calls the _markMessagesAsRead function to mark the messages
1493+
// as read.
1494+
Future<void> _maybeMarkMessagesAsRead() async {
1495+
final channel = streamChannel?.channel;
1496+
if (channel == null) return;
1497+
1498+
final isInThread = widget.parentMessage != null;
1499+
1500+
final isUpToDate = channel.state?.isUpToDate ?? false;
1501+
if (!isInThread && !isUpToDate) return;
1502+
1503+
final hasUnread = (channel.state?.unreadCount ?? 0) > 0;
1504+
if (!isInThread && !hasUnread) return;
1505+
1506+
// Mark messages as read if it's allowed.
1507+
return _markMessagesAsRead();
1508+
}
1509+
14941510
void _getOnThreadTap() {
14951511
if (widget.onThreadTap != null) {
14961512
_onThreadTap = (Message message) {

0 commit comments

Comments
 (0)