Skip to content

Commit 52f1ea2

Browse files
authored
feat(llc): add support for markUnreadByTimestamp (#2460)
1 parent 3e517e4 commit 52f1ea2

File tree

7 files changed

+197
-8
lines changed

7 files changed

+197
-8
lines changed

packages/stream_chat/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
✅ Added
44

5+
- Added support for `Channel.markUnreadByTimestamp` and `Client.markChannelUnreadByTimestamp`
6+
methods to mark all messages after a given timestamp as unread.
57
- Added support for `hideHistoryBefore` in `Channel.addMembers` and `Client.addChannelMembers` to
68
specify a timestamp before which channel history should be hidden for newly added members. When
79
provided, it takes precedence over the `hideHistory` boolean flag.

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,10 +1643,9 @@ class Channel {
16431643
return _client.markChannelRead(id!, type, messageId: messageId);
16441644
}
16451645

1646-
/// Mark message as unread.
1646+
/// Marks the channel as unread by a given [messageId].
16471647
///
1648-
/// You have to provide a [messageId] from which you want the channel
1649-
/// to be marked as unread.
1648+
/// All messages from the provided message onwards will be marked as unread.
16501649
Future<EmptyResponse> markUnread(String messageId) async {
16511650
_checkInitialized();
16521651

@@ -1660,6 +1659,22 @@ class Channel {
16601659
return _client.markChannelUnread(id!, type, messageId);
16611660
}
16621661

1662+
/// Marks the channel as unread by a given [timestamp].
1663+
///
1664+
/// All messages after the provided timestamp will be marked as unread.
1665+
Future<EmptyResponse> markUnreadByTimestamp(DateTime timestamp) async {
1666+
_checkInitialized();
1667+
1668+
if (!canUseReadReceipts) {
1669+
throw const StreamChatError(
1670+
'Cannot mark as unread: Channel does not support read events. '
1671+
'Enable read_events in your channel type configuration.',
1672+
);
1673+
}
1674+
1675+
return _client.markChannelUnreadByTimestamp(id!, type, timestamp);
1676+
}
1677+
16631678
/// Mark the thread with [threadId] in the channel as read.
16641679
Future<EmptyResponse> markThreadRead(String threadId) async {
16651680
_checkInitialized();

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,9 +1343,10 @@ class StreamChatClient {
13431343
messageId: messageId,
13441344
);
13451345

1346-
/// Mark [channelId] of type [channelType] all messages as read
1347-
/// Optionally provide a [messageId] if you want to mark a
1348-
/// particular message as read
1346+
/// Marks the [channelId] of type [channelType] as unread
1347+
/// by a given [messageId].
1348+
///
1349+
/// All messages from the provided message onwards will be marked as unread.
13491350
Future<EmptyResponse> markChannelUnread(
13501351
String channelId,
13511352
String channelType,
@@ -1357,6 +1358,21 @@ class StreamChatClient {
13571358
messageId,
13581359
);
13591360

1361+
/// Marks the [channelId] of type [channelType] as unread
1362+
/// by a given [timestamp].
1363+
///
1364+
/// All messages after the provided timestamp will be marked as unread.
1365+
Future<EmptyResponse> markChannelUnreadByTimestamp(
1366+
String channelId,
1367+
String channelType,
1368+
DateTime timestamp,
1369+
) =>
1370+
_chatApi.channel.markUnreadByTimestamp(
1371+
channelId,
1372+
channelType,
1373+
timestamp,
1374+
);
1375+
13601376
/// Mark the thread with [threadId] in the channel with [channelId] of type
13611377
/// [channelType] as read.
13621378
Future<EmptyResponse> markThreadRead(

packages/stream_chat/lib/src/core/api/channel_api.dart

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,11 +328,13 @@ class ChannelApi {
328328
return EmptyResponse.fromJson(response.data);
329329
}
330330

331-
/// Marks all messages from the provided [messageId] onwards as unread
331+
/// Marks the channel as unread by a given [messageId].
332+
///
333+
/// All messages from the provided message onwards will be marked as unread.
332334
Future<EmptyResponse> markUnread(
333335
String channelId,
334336
String channelType,
335-
String? messageId,
337+
String messageId,
336338
) async {
337339
final response = await _client.post(
338340
'${_getChannelUrl(channelId, channelType)}/unread',
@@ -341,6 +343,21 @@ class ChannelApi {
341343
return EmptyResponse.fromJson(response.data);
342344
}
343345

346+
/// Marks the channel as unread by a given [timestamp].
347+
///
348+
/// All messages after the provided timestamp will be marked as unread.
349+
Future<EmptyResponse> markUnreadByTimestamp(
350+
String channelId,
351+
String channelType,
352+
DateTime timestamp,
353+
) async {
354+
final response = await _client.post(
355+
'${_getChannelUrl(channelId, channelType)}/unread',
356+
data: {'message_timestamp': timestamp.toUtc().toIso8601String()},
357+
);
358+
return EmptyResponse.fromJson(response.data);
359+
}
360+
344361
/// Mark the provided [threadId] of the channel as read.
345362
Future<EmptyResponse> markThreadRead(
346363
String channelId,

packages/stream_chat/test/src/client/channel_test.dart

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6819,6 +6819,64 @@ void main() {
68196819
},
68206820
);
68216821

6822+
test(
6823+
".markUnreadByTimestamp should throw if we don't have the capability",
6824+
() async {
6825+
final channelState = _generateChannelState(
6826+
channelId,
6827+
channelType,
6828+
ownCapabilities: [], // no readEvents capability
6829+
);
6830+
6831+
final channel = Channel.fromState(client, channelState);
6832+
addTearDown(channel.dispose);
6833+
6834+
final timestamp = DateTime.parse('2024-01-01T00:00:00Z');
6835+
6836+
await expectLater(
6837+
channel.markUnreadByTimestamp(timestamp),
6838+
throwsA(isA<StreamChatError>()),
6839+
);
6840+
},
6841+
);
6842+
6843+
test(
6844+
'.markUnreadByTimestamp should succeed if we have the capability',
6845+
() async {
6846+
final channelState = _generateChannelState(
6847+
channelId,
6848+
channelType,
6849+
ownCapabilities: [ChannelCapability.readEvents],
6850+
);
6851+
6852+
final channel = Channel.fromState(client, channelState);
6853+
addTearDown(channel.dispose);
6854+
6855+
final timestamp = DateTime.parse('2024-01-01T00:00:00Z');
6856+
6857+
when(
6858+
() => client.markChannelUnreadByTimestamp(
6859+
channelId,
6860+
channelType,
6861+
timestamp,
6862+
),
6863+
).thenAnswer((_) async => EmptyResponse());
6864+
6865+
await expectLater(
6866+
channel.markUnreadByTimestamp(timestamp),
6867+
completes,
6868+
);
6869+
6870+
verify(
6871+
() => client.markChannelUnreadByTimestamp(
6872+
channelId,
6873+
channelType,
6874+
timestamp,
6875+
),
6876+
).called(1);
6877+
},
6878+
);
6879+
68226880
test(
68236881
".markThreadRead should throw if we don't have the capability",
68246882
() async {

packages/stream_chat/test/src/client/client_test.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2099,6 +2099,33 @@ void main() {
20992099
verifyNoMoreInteractions(api.channel);
21002100
});
21012101

2102+
test('`.markChannelUnreadByTimestamp`', () async {
2103+
const channelType = 'test-channel-type';
2104+
const channelId = 'test-channel-id';
2105+
final timestamp = DateTime.parse('2024-01-01T00:00:00Z');
2106+
2107+
when(() => api.channel.markUnreadByTimestamp(
2108+
channelId,
2109+
channelType,
2110+
timestamp,
2111+
)).thenAnswer((_) async => EmptyResponse());
2112+
2113+
final res = await client.markChannelUnreadByTimestamp(
2114+
channelId,
2115+
channelType,
2116+
timestamp,
2117+
);
2118+
2119+
expect(res, isNotNull);
2120+
2121+
verify(() => api.channel.markUnreadByTimestamp(
2122+
channelId,
2123+
channelType,
2124+
timestamp,
2125+
)).called(1);
2126+
verifyNoMoreInteractions(api.channel);
2127+
});
2128+
21022129
test('`.createPoll`', () async {
21032130
final poll = Poll(
21042131
name: 'What is your favorite color?',

packages/stream_chat/test/src/core/api/channel_api_test.dart

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,60 @@ void main() {
684684
verifyNoMoreInteractions(client);
685685
});
686686

687+
test('markUnread', () async {
688+
const channelId = 'test-channel-id';
689+
const channelType = 'test-channel-type';
690+
const messageId = 'test-message-id';
691+
692+
final path = '${_getChannelUrl(channelId, channelType)}/unread';
693+
694+
when(() => client.post(
695+
path,
696+
data: {'message_id': messageId},
697+
))
698+
.thenAnswer(
699+
(_) async => successResponse(path, data: <String, dynamic>{}));
700+
701+
final res = await channelApi.markUnread(
702+
channelId,
703+
channelType,
704+
messageId,
705+
);
706+
707+
expect(res, isNotNull);
708+
709+
verify(() => client.post(path, data: any(named: 'data'))).called(1);
710+
verifyNoMoreInteractions(client);
711+
});
712+
713+
test('markUnreadByTimestamp', () async {
714+
const channelId = 'test-channel-id';
715+
const channelType = 'test-channel-type';
716+
final timestamp = DateTime.parse('2024-01-01T00:00:00Z');
717+
718+
final path = '${_getChannelUrl(channelId, channelType)}/unread';
719+
720+
when(() => client.post(
721+
path,
722+
data: {
723+
'message_timestamp': timestamp.toUtc().toIso8601String(),
724+
},
725+
))
726+
.thenAnswer(
727+
(_) async => successResponse(path, data: <String, dynamic>{}));
728+
729+
final res = await channelApi.markUnreadByTimestamp(
730+
channelId,
731+
channelType,
732+
timestamp,
733+
);
734+
735+
expect(res, isNotNull);
736+
737+
verify(() => client.post(path, data: any(named: 'data'))).called(1);
738+
verifyNoMoreInteractions(client);
739+
});
740+
687741
test('archiveChannel', () async {
688742
const channelId = 'test-channel-id';
689743
const channelType = 'test-channel-type';

0 commit comments

Comments
 (0)