Skip to content

Commit 752691b

Browse files
authored
fix(ui): microseconds diffs result in wrong unread indicator (#1932)
Take into account the lastReadMessageId property
1 parent 27cbb03 commit 752691b

File tree

4 files changed

+151
-11
lines changed

4 files changed

+151
-11
lines changed

packages/stream_chat_flutter/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
✅ Added
44
- Added `VoiceRecordingAttachmentBuilder`, for displaying voice recording attachments in the chat.
55

6+
🐞 Fixed
7+
- Fixed wrong calculation of the last unread message indicator.
8+
69
## 7.2.0-hotfix.1
710

811
🔄 Changed

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

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -520,17 +520,7 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
520520
Widget _buildListView(List<Message> data) {
521521
messages = data;
522522

523-
if (_userRead != null &&
524-
messages.isNotEmpty &&
525-
messages.first.createdAt.isAfter(_userRead!.lastRead) &&
526-
messages.last.createdAt.isBefore(_userRead!.lastRead)) {
527-
_oldestUnreadMessage = messages.lastWhereOrNull(
528-
(it) =>
529-
it.user?.id !=
530-
streamChannel?.channel.client.state.currentUser?.id &&
531-
it.createdAt.compareTo(_userRead!.lastRead) > 0,
532-
);
533-
}
523+
_oldestUnreadMessage = messages.lastUnreadMessage(_userRead);
534524

535525
for (var index = 0; index < messages.length; index++) {
536526
messagesIndex[messages[index].id] = index;

packages/stream_chat_flutter/lib/src/utils/extensions.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:io';
22
import 'dart:math';
33

4+
import 'package:collection/collection.dart';
45
import 'package:diacritic/diacritic.dart';
56
import 'package:file_picker/file_picker.dart';
67
import 'package:flutter/foundation.dart';
@@ -563,3 +564,29 @@ extension OriginalSizeX on Attachment {
563564
return Size(width.toDouble(), height.toDouble());
564565
}
565566
}
567+
568+
/// Useful extensions on [List<Message>].
569+
extension MessageListX on Iterable<Message> {
570+
/// Returns the last unread message in the list.
571+
/// Returns null if the list is empty or the userRead is null.
572+
///
573+
/// The [userRead] is the last read message by the user.
574+
///
575+
/// The last unread message is the last message in the list that is not
576+
/// sent by the current user and is sent after the last read message.
577+
Message? lastUnreadMessage(Read? userRead) {
578+
if (isEmpty || userRead == null) return null;
579+
580+
if (first.createdAt.isAfter(userRead.lastRead) &&
581+
last.createdAt.isBefore(userRead.lastRead)) {
582+
return lastWhereOrNull(
583+
(it) =>
584+
it.user?.id != userRead.user.id &&
585+
it.id != userRead.lastReadMessageId &&
586+
it.createdAt.compareTo(userRead.lastRead) > 0,
587+
);
588+
}
589+
590+
return null;
591+
}
592+
}

packages/stream_chat_flutter/test/src/utils/extension_test.dart

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,4 +270,124 @@ void main() {
270270
expect(modifiedMessage.text, isNot(contains('@Alice')));
271271
});
272272
});
273+
274+
group('Message List Extension Tests', () {
275+
group('lastUnreadMessage', () {
276+
test('should return null when list is empty', () {
277+
final messages = <Message>[];
278+
final userRead = Read(
279+
lastRead: DateTime.now(),
280+
user: User(id: 'user1'),
281+
);
282+
expect(messages.lastUnreadMessage(userRead), isNull);
283+
});
284+
285+
test('should return null when userRead is null', () {
286+
final messages = <Message>[
287+
Message(id: '1'),
288+
Message(id: '2'),
289+
];
290+
expect(messages.lastUnreadMessage(null), isNull);
291+
});
292+
293+
test('should return null when all messages are read', () {
294+
final lastRead = DateTime.now();
295+
final messages = <Message>[
296+
Message(
297+
id: '1',
298+
createdAt: lastRead.subtract(const Duration(seconds: 1))),
299+
Message(id: '2', createdAt: lastRead),
300+
];
301+
final userRead = Read(
302+
lastRead: lastRead,
303+
user: User(id: 'user1'),
304+
);
305+
expect(messages.lastUnreadMessage(userRead), isNull);
306+
});
307+
308+
test('should return null when all messages are mine', () {
309+
final lastRead = DateTime.now();
310+
final userRead = Read(
311+
lastRead: lastRead,
312+
user: User(id: 'user1'),
313+
);
314+
final messages = <Message>[
315+
Message(
316+
id: '1',
317+
user: userRead.user,
318+
createdAt: lastRead.add(const Duration(seconds: 1))),
319+
Message(id: '2', user: userRead.user, createdAt: lastRead),
320+
];
321+
expect(messages.lastUnreadMessage(userRead), isNull);
322+
});
323+
324+
test('should return the message', () {
325+
final lastRead = DateTime.now();
326+
final otherUser = User(id: 'user2');
327+
final userRead = Read(
328+
lastRead: lastRead,
329+
user: User(id: 'user1'),
330+
);
331+
332+
final messages = <Message>[
333+
Message(
334+
id: '1',
335+
user: otherUser,
336+
createdAt: lastRead.add(const Duration(seconds: 2)),
337+
),
338+
Message(
339+
id: '2',
340+
user: otherUser,
341+
createdAt: lastRead.add(const Duration(seconds: 1)),
342+
),
343+
Message(
344+
id: '3',
345+
user: otherUser,
346+
createdAt: lastRead.subtract(const Duration(seconds: 1)),
347+
),
348+
];
349+
350+
final lastUnreadMessage = messages.lastUnreadMessage(userRead);
351+
expect(lastUnreadMessage, isNotNull);
352+
expect(lastUnreadMessage!.id, '2');
353+
});
354+
355+
test('should not return the last message read', () {
356+
final lastRead = DateTime.timestamp();
357+
final otherUser = User(id: 'user2');
358+
final userRead = Read(
359+
lastRead: lastRead,
360+
user: User(id: 'user1'),
361+
lastReadMessageId: '3',
362+
);
363+
364+
final messages = <Message>[
365+
Message(
366+
id: '1',
367+
user: otherUser,
368+
createdAt: lastRead.add(const Duration(seconds: 2)),
369+
),
370+
Message(
371+
id: '2',
372+
user: otherUser,
373+
createdAt: lastRead.add(const Duration(milliseconds: 1)),
374+
),
375+
Message(
376+
id: '3',
377+
user: otherUser,
378+
createdAt: lastRead.add(const Duration(microseconds: 1)),
379+
),
380+
Message(
381+
id: '4',
382+
user: otherUser,
383+
createdAt: lastRead.subtract(const Duration(seconds: 1)),
384+
),
385+
];
386+
387+
final lastUnreadMessage = messages.lastUnreadMessage(userRead);
388+
expect(lastUnreadMessage, isNotNull);
389+
expect(lastUnreadMessage!.id, '2');
390+
});
391+
});
392+
});
273393
}

0 commit comments

Comments
 (0)