Skip to content

Commit 633db4e

Browse files
authored
Add support for marking a channel as unread from a given timestamp (#6027)
* Add support for marking a channel as unread from a given timestamp. * Update CHANGELOG.md. * Update tests.
1 parent 47e5873 commit 633db4e

File tree

13 files changed

+226
-77
lines changed

13 files changed

+226
-77
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@
1616
### ⬆️ Improved
1717

1818
### ✅ Added
19+
- Add `ChatClient.markUnread(String, String, Date)` for marking a channel as unread from a given timestamp. [#6027](https://github.com/GetStream/stream-chat-android/pull/6027)
20+
- Add `ChatClient.markThreadUnread(String, String, String)` for marking a thread as unread. [#6027](https://github.com/GetStream/stream-chat-android/pull/6027)
21+
- Add `ChannelClient.markUnread(Date)` for marking a channel as unread from a given timestamp. [#6027](https://github.com/GetStream/stream-chat-android/pull/6027)
22+
- Add `ChannelClient.markThreadUnread(String)` for marking a thread as unread. [#6027](https://github.com/GetStream/stream-chat-android/pull/6027)
1923

2024
### ⚠️ Changed
25+
- Deprecate `ChatClient.markThreadUnread(String, String, String, String)` because marking a thread as unread from a given message is currently not supported. [#6027](https://github.com/GetStream/stream-chat-android/pull/6027)
26+
- Deprecate `ChannelClient.markThreadUnread(String, String)` because marking a thread as unread from a given message is currently not supported. [#6027](https://github.com/GetStream/stream-chat-android/pull/6027)
2127

2228
### ❌ Removed
2329

DEPRECATIONS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ This document lists deprecated constructs in the SDK, with their expected time
44

55
| API / Feature | Deprecated (warning) | Deprecated (error) | Removed | Notes |
66
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------|-----------------------|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
7+
| `ChatClient.markThreadUnread(channelType: String, channelId: String, threadId: String, mesageId: String)` method | 2025.11.24 ⌛ | | | This method is deprecated because marking a thread as unread from a given message is currently not supported, and the passed `messageId` is ignored. Use `ChatClient.markThreadUnread(channelType: String, channelId: String, threadId: String)` instead. |
8+
| `ChannelClient.markThreadUnread(threadId: String, mesageId: String)` method | 2025.11.24 ⌛ | | | This method is deprecated because marking a thread as unread from a given message is currently not supported, and the passed `messageId` is ignored. Use `ChannelClient.markThreadUnread(threadId: String)` instead. |
79
| `StatePluginConfig.backgroundSyncEnabled` property | 2025.11.24 ⌛ | | | This property has been deprecated and will be removed in the future. We recommend disabling it to avoid unnecessary background work. |
810
| `PollConfig(String, List<String>, String, VotingVisibility, Boolean, Int, Boolean, Boolean)` constructor | 2025.11.07 ⌛ | | | This constructor has been deprecated. Please use `PollConfig(String, List<PollOption>, String, VotingVisibility, Boolean, Int, Boolean, Boolean, Map<String, Any>)` instead. |
911
| `ChatClient.suggestPollOption(pollId: String, option: String)` method | 2025.11.07 ⌛ | | | This method has been deprecated. Please use `ChatClient.createPollOption(pollId: String, option: CreatePollOptionRequest)` instead. |

stream-chat-android-client/api/stream-chat-android-client.api

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,10 @@ public final class io/getstream/chat/android/client/ChatClient {
121121
public final fun markMessageRead (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call;
122122
public final fun markRead (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call;
123123
public final fun markThreadRead (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call;
124+
public final fun markThreadUnread (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call;
124125
public final fun markThreadUnread (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call;
125126
public final fun markUnread (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call;
127+
public final fun markUnread (Ljava/lang/String;Ljava/lang/String;Ljava/util/Date;)Lio/getstream/result/call/Call;
126128
public final fun muteChannel (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call;
127129
public final fun muteChannel (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)Lio/getstream/result/call/Call;
128130
public static synthetic fun muteChannel$default (Lio/getstream/chat/android/client/ChatClient;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)Lio/getstream/result/call/Call;
@@ -746,8 +748,10 @@ public final class io/getstream/chat/android/client/channel/ChannelClient {
746748
public final fun markMessageRead (Ljava/lang/String;)Lio/getstream/result/call/Call;
747749
public final fun markRead ()Lio/getstream/result/call/Call;
748750
public final fun markThreadRead (Ljava/lang/String;)Lio/getstream/result/call/Call;
751+
public final fun markThreadUnread (Ljava/lang/String;)Lio/getstream/result/call/Call;
749752
public final fun markThreadUnread (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call;
750753
public final fun markUnread (Ljava/lang/String;)Lio/getstream/result/call/Call;
754+
public final fun markUnread (Ljava/util/Date;)Lio/getstream/result/call/Call;
751755
public final fun mute ()Lio/getstream/result/call/Call;
752756
public final fun mute (Ljava/lang/Integer;)Lio/getstream/result/call/Call;
753757
public static synthetic fun mute$default (Lio/getstream/chat/android/client/channel/ChannelClient;Ljava/lang/Integer;ILjava/lang/Object;)Lio/getstream/result/call/Call;

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3458,31 +3458,62 @@ internal constructor(
34583458
channelId: String,
34593459
messageId: String,
34603460
): Call<Unit> {
3461-
return api.markUnread(channelType, channelId, messageId)
3462-
.doOnStart(userScope) {
3463-
logger.d { "[markUnread] #doOnStart; cid: $channelType:$channelId, msgId: $messageId" }
3464-
}
3465-
.doOnResult(userScope) {
3466-
logger.v { "[markUnread] #doOnResult; completed($channelType:$channelId, $messageId): $it" }
3467-
}
3461+
return api.markUnread(channelType, channelId, messageId = messageId)
3462+
}
3463+
3464+
/**
3465+
* Marks all messages in the channel as unread that were created after the specified timestamp.
3466+
*
3467+
* @param channelType Type of the channel.
3468+
* @param channelId Id of the channel.
3469+
* @param timestamp The timestamp used to find the first message to mark as unread.
3470+
*/
3471+
@CheckResult
3472+
public fun markUnread(
3473+
channelType: String,
3474+
channelId: String,
3475+
timestamp: Date,
3476+
): Call<Unit> {
3477+
return api.markUnread(channelType, channelId, messageTimestamp = timestamp)
34683478
}
34693479

34703480
/**
3471-
* Marks a given thread starting from the given message as unread.
3481+
* Marks a thread as unread.
3482+
*
3483+
* @param channelType Type of the channel.
3484+
* @param channelId Id of the channel.
3485+
* @param threadId Id of the thread to mark as unread.
3486+
*/
3487+
@CheckResult
3488+
public fun markThreadUnread(
3489+
channelType: String,
3490+
channelId: String,
3491+
threadId: String,
3492+
): Call<Unit> {
3493+
return api.markUnread(channelType, channelId, threadId = threadId)
3494+
}
3495+
3496+
/**
3497+
* Marks a thread as unread.
34723498
*
34733499
* @param channelType Type of the channel.
34743500
* @param channelId Id of the channel.
34753501
* @param threadId Id of the thread to mark as unread.
34763502
* @param messageId Id of the message from where the thread should be marked as unread.
34773503
*/
3504+
@Deprecated(
3505+
"Marking a thread as unread from a given message is currently not supported. " +
3506+
"Passing messageId has no effect and the whole thread is marked as unread." +
3507+
"Use markThreadUnread(channelType, channelId, threadId) instead.",
3508+
)
34783509
@CheckResult
34793510
public fun markThreadUnread(
34803511
channelType: String,
34813512
channelId: String,
34823513
threadId: String,
34833514
messageId: String,
34843515
): Call<Unit> {
3485-
return api.markThreadUnread(channelType, channelId, threadId = threadId, messageId = messageId)
3516+
return api.markUnread(channelType, channelId, messageId = messageId, threadId = threadId)
34863517
}
34873518

34883519
/**

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api/ChatApi.kt

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -357,15 +357,9 @@ internal interface ChatApi {
357357
fun markUnread(
358358
channelType: String,
359359
channelId: String,
360-
messageId: String,
361-
): Call<Unit>
362-
363-
@CheckResult
364-
fun markThreadUnread(
365-
channelType: String,
366-
channelId: String,
367-
threadId: String,
368-
messageId: String,
360+
messageId: String? = null,
361+
messageTimestamp: Date? = null,
362+
threadId: String? = null,
369363
): Call<Unit>
370364

371365
@CheckResult

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/MoshiChatApi.kt

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -978,28 +978,19 @@ constructor(
978978
).toUnitCall()
979979
}
980980

981-
override fun markUnread(channelType: String, channelId: String, messageId: String): Call<Unit> {
982-
return channelApi.markUnread(
983-
channelType = channelType,
984-
channelId = channelId,
985-
request = MarkUnreadRequest(messageId),
986-
).toUnitCall()
987-
}
988-
989-
override fun markThreadUnread(
981+
override fun markUnread(
990982
channelType: String,
991983
channelId: String,
992-
threadId: String,
993-
messageId: String,
984+
messageId: String?,
985+
messageTimestamp: Date?,
986+
threadId: String?,
994987
): Call<Unit> {
995-
return channelApi.markUnread(
996-
channelType = channelType,
997-
channelId = channelId,
998-
request = MarkUnreadRequest(
999-
thread_id = threadId,
1000-
message_id = messageId,
1001-
),
1002-
).toUnitCall()
988+
val request = MarkUnreadRequest(
989+
message_id = messageId,
990+
message_timestamp = messageTimestamp,
991+
thread_id = threadId,
992+
)
993+
return channelApi.markUnread(channelType, channelId, request).toUnitCall()
1003994
}
1004995

1005996
override fun markAllRead(): Call<Unit> {

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/requests/MarkUnreadRequest.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
package io.getstream.chat.android.client.api2.model.requests
1818

1919
import com.squareup.moshi.JsonClass
20+
import java.util.Date
2021

2122
@JsonClass(generateAdapter = true)
2223
internal data class MarkUnreadRequest(
23-
val message_id: String,
24+
val message_id: String? = null,
25+
val message_timestamp: Date? = null,
2426
val thread_id: String? = null,
2527
)

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/channel/ChannelClient.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,12 +473,37 @@ public class ChannelClient internal constructor(
473473
return client.markUnread(channelType, channelId, messageId)
474474
}
475475

476+
/**
477+
* Marks all messages in the channel as unread that were created after the specified timestamp.
478+
*
479+
* @param timestamp The timestamp used to find the first message to mark as unread.
480+
*/
481+
@CheckResult
482+
public fun markUnread(timestamp: Date): Call<Unit> {
483+
return client.markUnread(channelType, channelId, timestamp)
484+
}
485+
486+
/**
487+
* Marks a given thread in the channel as unread.
488+
*
489+
* @param threadId Id of the thread to mark as unread.
490+
*/
491+
@CheckResult
492+
public fun markThreadUnread(threadId: String): Call<Unit> {
493+
return client.markThreadUnread(channelType, channelId, threadId = threadId)
494+
}
495+
476496
/**
477497
* Marks a given thread in the channel starting from the given message as unread.
478498
*
479499
* @param messageId Id of the message from where the thread should be marked as unread.
480500
* @param threadId Id of the thread to mark as unread.
481501
*/
502+
@Deprecated(
503+
"Marking a thread as unread from a given message is currently not supported. " +
504+
"Passing messageId has no effect and the whole thread is marked as unread." +
505+
"Use markThreadUnread(channelType, channelId, threadId) instead.",
506+
)
482507
@CheckResult
483508
public fun markThreadUnread(threadId: String, messageId: String): Call<Unit> {
484509
return client.markThreadUnread(channelType, channelId, threadId = threadId, messageId = messageId)

stream-chat-android-client/src/test/java/io/getstream/chat/android/client/ChatClientChannelApiTests.kt

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,7 +1075,7 @@ internal class ChatClientChannelApiTests : BaseChatClientTest() {
10751075
}
10761076

10771077
@Test
1078-
fun markUnreadSuccess() = runTest {
1078+
fun markUnreadFromMessageSuccess() = runTest {
10791079
// given
10801080
val channelType = randomString()
10811081
val channelId = randomString()
@@ -1090,7 +1090,7 @@ internal class ChatClientChannelApiTests : BaseChatClientTest() {
10901090
}
10911091

10921092
@Test
1093-
fun markUnreadError() = runTest {
1093+
fun markUnreadFromMessageError() = runTest {
10941094
// given
10951095
val channelType = randomString()
10961096
val channelId = randomString()
@@ -1105,6 +1105,37 @@ internal class ChatClientChannelApiTests : BaseChatClientTest() {
11051105
verifyNetworkError(result, errorCode)
11061106
}
11071107

1108+
@Test
1109+
fun markUnreadFromTimestampSuccess() = runTest {
1110+
// given
1111+
val channelType = randomString()
1112+
val channelId = randomString()
1113+
val timestamp = randomDate()
1114+
val sut = Fixture()
1115+
.givenMarkUnreadResult(RetroSuccess(Unit).toRetrofitCall())
1116+
.get()
1117+
// when
1118+
val result = sut.markUnread(channelType, channelId, timestamp).await()
1119+
// then
1120+
verifySuccess(result, Unit)
1121+
}
1122+
1123+
@Test
1124+
fun markUnreadFromTimestampError() = runTest {
1125+
// given
1126+
val channelType = randomString()
1127+
val channelId = randomString()
1128+
val timestamp = randomDate()
1129+
val errorCode = positiveRandomInt()
1130+
val sut = Fixture()
1131+
.givenMarkUnreadResult(RetroError<Unit>(errorCode).toRetrofitCall())
1132+
.get()
1133+
// when
1134+
val result = sut.markUnread(channelType, channelId, timestamp).await()
1135+
// then
1136+
verifyNetworkError(result, errorCode)
1137+
}
1138+
11081139
@Test
11091140
fun markThreadReadSuccess() = runTest {
11101141
// given
@@ -1138,13 +1169,44 @@ internal class ChatClientChannelApiTests : BaseChatClientTest() {
11381169

11391170
@Test
11401171
fun markThreadUnreadSuccess() = runTest {
1172+
// given
1173+
val channelType = randomString()
1174+
val channelId = randomString()
1175+
val threadId = randomString()
1176+
val sut = Fixture()
1177+
.givenMarkUnreadResult(RetroSuccess(Unit).toRetrofitCall())
1178+
.get()
1179+
// when
1180+
val result = sut.markThreadUnread(channelType, channelId, threadId).await()
1181+
// then
1182+
verifySuccess(result, Unit)
1183+
}
1184+
1185+
@Test
1186+
fun markThreadUnreadError() = runTest {
1187+
// given
1188+
val channelType = randomString()
1189+
val channelId = randomString()
1190+
val threadId = randomString()
1191+
val errorCode = positiveRandomInt()
1192+
val sut = Fixture()
1193+
.givenMarkUnreadResult(RetroError<Unit>(errorCode).toRetrofitCall())
1194+
.get()
1195+
// when
1196+
val result = sut.markThreadUnread(channelType, channelId, threadId).await()
1197+
// then
1198+
verifyNetworkError(result, errorCode)
1199+
}
1200+
1201+
@Test
1202+
fun markThreadUnreadFromMessageSuccess() = runTest {
11411203
// given
11421204
val channelType = randomString()
11431205
val channelId = randomString()
11441206
val threadId = randomString()
11451207
val messageId = randomString()
11461208
val sut = Fixture()
1147-
.givenMarkThreadUnreadResult(RetroSuccess(Unit).toRetrofitCall())
1209+
.givenMarkUnreadResult(RetroSuccess(Unit).toRetrofitCall())
11481210
.get()
11491211
// when
11501212
val result = sut.markThreadUnread(channelType, channelId, threadId, messageId).await()
@@ -1153,15 +1215,15 @@ internal class ChatClientChannelApiTests : BaseChatClientTest() {
11531215
}
11541216

11551217
@Test
1156-
fun markThreadUnreadError() = runTest {
1218+
fun markThreadUnreadFromMessageError() = runTest {
11571219
// given
11581220
val channelType = randomString()
11591221
val channelId = randomString()
11601222
val threadId = randomString()
11611223
val messageId = randomString()
11621224
val errorCode = positiveRandomInt()
11631225
val sut = Fixture()
1164-
.givenMarkThreadUnreadResult(RetroError<Unit>(errorCode).toRetrofitCall())
1226+
.givenMarkUnreadResult(RetroError<Unit>(errorCode).toRetrofitCall())
11651227
.get()
11661228
// when
11671229
val result = sut.markThreadUnread(channelType, channelId, threadId, messageId).await()
@@ -1648,17 +1710,13 @@ internal class ChatClientChannelApiTests : BaseChatClientTest() {
16481710
}
16491711

16501712
fun givenMarkUnreadResult(result: Call<Unit>) = apply {
1651-
whenever(api.markUnread(any(), any(), any())).thenReturn(result)
1713+
whenever(api.markUnread(any(), any(), anyOrNull(), anyOrNull(), anyOrNull())).thenReturn(result)
16521714
}
16531715

16541716
fun givenMarkThreadReadResult(result: Call<Unit>) = apply {
16551717
whenever(api.markThreadRead(any(), any(), any())).thenReturn(result)
16561718
}
16571719

1658-
fun givenMarkThreadUnreadResult(result: Call<Unit>) = apply {
1659-
whenever(api.markThreadUnread(any(), any(), any(), any())).thenReturn(result)
1660-
}
1661-
16621720
fun givenStopWatchingResult(result: Call<Unit>) = apply {
16631721
whenever(api.stopWatching(any(), any())).thenReturn(result)
16641722
}

0 commit comments

Comments
 (0)