Skip to content

Commit 57f41ff

Browse files
Introduce message delivery receipts (#5979)
* Add ChatClient.markMessagesAsDelivered function to mark messages as delivered * Add delivery receipts support to user privacy settings * Introduce a initial DeliveryReceiptsManager to handle message delivery receipts * Introduce MessageReceiptDao and MessageReceiptEntity for handling message receipts * Introduce MessageReceipt model and repository for handling message delivery receipts * Make MessageReceiptRepository `getAllByType` return a Flow and add a `clear` function The `MessageReceiptRepository.getAllByType` function now accepts a `limit` parameter and returns a `Flow<List<MessageReceipt>>` instead of a `suspend` function returning `List<MessageReceipt>`. A new `clear()` function has been added to `MessageReceiptRepository` to delete all receipts from the database. * Refactor DeliveryReceiptsManager to store receipts locally The `DeliveryReceiptsManager` has been renamed to `MessageReceiptManager`. Instead of making a direct API call to mark messages as delivered, the manager now creates `MessageReceipt` objects and upserts them into the local `MessageReceiptRepository`. This change also refactors the corresponding tests to verify the local database interaction rather than the API call. * Introduced `MessageReceiptReporter`, a new class responsible for observing the local database for delivery receipts and reporting them to the backend. This reporter collects receipts in batches and sends them periodically to reduce network traffic. After a successful API call, the reported receipts are removed from the local database. New tests are included to verify this functionality. * Prapare to move internal persistence to the client module * Add ChatClientDatabase, DateConverter, and ChatClientRepository for message receipt management * Refactor: Move message receipt logic to client Moves the `MessageReceiptRepository` and related classes from the `stream-chat-android-offline` module to the `stream-chat-android-client` module. This refactoring centralizes persistence logic within the client module. The moved classes have been renamed and their package structure updated. Method names within the repository have been made more specific (e.g., `upsert` is now `upsertMessageReceipts`). The `MessageReceiptRepository` is now an internal interface with a companion object factory. * Support `message.delivered` event This commit adds support for the `message.delivered` event, which is triggered when a message is marked as delivered. The following changes are included: - A new `MessageDeliveredEvent` data class. - New fields (`last_delivered_at`, `last_delivered_message_id`) to the `ChannelUserRead` DTO and model. - Logic to parse and map the `message.delivered` event from the backend DTO to the domain model. * Refactor: Add default empty implementations for `QueryChannelsListener` interface instead * Deprecate `hasUnread` in favor of `currentUserUnreadCount` * Add `userRead` and `deliveredReads` helper functions Extracted the logic for retrieving a user's read state into a new `Channel.userRead()` helper function. This simplifies the implementation of `countUnreadMentionsForUser` and `currentUserUnreadCount`. Additionally, introduced `Channel.deliveredReads(message)` to get a list of users who have received a specific message. * Introduce `markChannelsAsDelivered`, a new function to mark the last message in a list of channels as delivered. It contains logic to identify the appropriate message to mark as delivered, considering factors like whether the message is already read or delivered. * Refactor MessageReceiptReporter to use a polling mechanism Replaced the Flow-based implementation in `MessageReceiptReporter` with a `while(isActive)` loop and a `delay`. This change simplifies the logic for polling and reporting message delivery receipts. The `getAllMessageReceiptsByType` function in `MessageReceiptRepository` and `MessageReceiptDao` is now a `suspend` function instead of returning a `Flow`. The corresponding tests have been updated to reflect these changes. * Moves the user ID update in the `switchUser` function to after the user is disconnected. This ensures that coroutines related to the previous user are properly cancelled before the new user connects. * Rename MessageReceiptReporter.init to start and add logging * Refactor MessageReceiptManagerTest to standardize verification methods for upsertMessageReceipts calls * Automatically mark messages as delivered when querying channels Introduced a `MessageDeliveredPlugin` that automatically marks messages as delivered upon a successful `queryChannels` call. This plugin is enabled by default. * Introduce `ChatClientRepository` to encapsulate internal repositories used by `ChatClient`. This new repository is now a required dependency for `ChatClient`. * Add MessageReceiptManager and MessageReceiptReporter to ChatClient * Decoupled `MessageReceiptReporter` from `ChatClient` by passing `ChatApi` directly to its constructor. This simplifies dependencies and removes the need for `ChatClient` as an intermediary for API calls. Updated tests to reflect this change, mocking `ChatApi` instead of `ChatClient`. * Refactor ChatClientTest to simplify test setup - Remove unused properties and simplify the `setUp` method. - Make test properties `private`. - Remove `runTest` from test function signatures as it's already handled by the `TestCoroutineExtension`. * Rename deliveredReads to deliveredReadsOf * feat: Add delivered status indicator for messages This commit introduces a new "delivered" status for messages, which is displayed when a message has been delivered but not yet read. **Key changes:** - Added `isMessageDelivered` property to `MessageItemState` and `MessageListItem`. - Introduced a new grey double-check icon (`stream_ui_ic_check_double_grey`) for the delivered state. - Updated `ChannelListViewStyle` and `MessageListItemStyle` to include `indicatorDeliveredIcon`. - The message status indicator logic in both UI Components and Compose has been updated to show the delivered icon between the "sent" and "read" states. - Added a new `stream_ui_message_list_semantics_message_status_delivered` string for accessibility. - In Compose, `MessageFooterStatusIndicator` is updated to accept `MessageFooterStatusIndicatorParams` to handle the new delivered state, deprecating the old signature. * Fix flaky test * Add more tests * Feat: Add `Channel.readsOf()` extension function Adds a `readsOf(message: Message)` extension function to the `Channel` class. This function returns a list of `ChannelUserRead` objects for users who have read the given message, excluding the message sender. Also includes: - Adding corresponding unit tests for the new function. - Renaming `ChannelExtensionsTests.kt` to `ChannelExtensionTest.kt`. - Migrating existing tests from `kluent` to `JUnit Jupiter` assertions. * Add pending status indicator snapshot test * Add unit test for MessageReceiptRepository instantiation * Fix the message status indicator paddings * Mark messages as delivered on push notification * sonar lint * Mark channel as delivered on query a single channel * Provide a ChatClient lazily in ChatNotifications * Hide the message status indicator when a message is deleted * Do not expose ChatClient.markMessagesAsDelivered * feat: Add delivery_events flag to Config Added a `deliveryEventsEnabled` flag to the `Config` model. This allows disabling message delivery events on a per-channel basis. Delivery receipts will not be stored or sent if this flag is disabled in the channel configuration. * Refactor: Improve message delivery receipts logic This commit refactors the `MessageReceiptManager` to enhance the logic for marking messages as delivered. Key changes include: - Renaming `markMessagesAsDelivered(messages: List<Message>)` to `markMessageAsDelivered(message: Message)` and making it the public API. The old function is now private and handles batch processing. - Before marking a message as delivered, the manager now fetches the corresponding channel from the local repository or the API to ensure all conditions are met. - The responsibility of filtering messages that can be marked as delivered is moved into a new private function `canMarkMessageAsDelivered`. - Test cases have been updated to reflect these changes and improve coverage. * Add DELIVERY_EVENTS to ChannelCapabilities * Refactor: Make MessageReceiptManager functions suspend The `MessageReceiptManager` is refactored to use `suspend` functions instead of launching new coroutines from a provided `CoroutineScope`. This simplifies the class by removing the need for an injected scope and improves testability. Key changes: - `markChannelsAsDelivered` and `markMessageAsDelivered` are now `suspend` functions. - The `CoroutineScope` dependency has been removed from `MessageReceiptManager` and its initialization in `ChatClient`. - `MessageDeliveredPlugin` is updated to use `onSuccessSuspend` to call the new suspend functions. - `ChatNotificationsImpl` now launches a coroutine to call the suspend function `markMessageAsDelivered`. - Tests are updated to use `verifyBlocking` for testing suspend functions and to provide the necessary `CoroutineScope` where required. * Refactor: Lazy initialize messageReceiptManager in MessageDeliveredPlugin and ChatClient * Fix: Fetch message from API when marking as delivered by ID When `markMessageAsDelivered` is called with a `messageId`, the SDK now fetches the message from the local repository. If the message is not found locally, it falls back to fetching it from the API. This ensures that a delivery receipt can be sent for a push notification even if the message is not yet in the local database. * Skip sending delivery receipts for shadowed messages and muted users This change prevents sending message delivery receipts for messages that are either shadowed or sent by a user who has been muted by the current user. * Add deliveryEventsEnabled to channel configuration and mapping * Add message info option to message menu * Extra PaneTitle and PaneRow components for reusing * Add message info component to display read and delivered status * Stop using kluent assertions * Update read and delivered status checks to include equal comparison * Add user profile privacy settings screen * Add privacy settings mapping to domain model * Fix message info component to display read and delivered timestamps correctly * typo * CHANGELOG * Change userRead function visibility to internal * Use getCreatedAtOrDefault * Remove MessageReceipt's 'type' field * Move MessageReceiptRepositoryImpl to the same file as MessageReceiptRepository * Fix tests after rebased from develop * Update User.mergePartially to conditionally merge based on update timestamps * Store UserMuteEntity properly * Refactor event handling to consolidate user updates * Refactor `MessageReceiptManager` to fetch the current user from the repository * Refactor: Lazily inject RepositoryFacade into MessageReceiptManager * Expose `markMessageAsDelivered` and allow custom handling of incoming push messages before they are processed by the SDK. * Enhance UserRepositoryTests: Add tests for user insertion and selection with privacy settings and mutes * improve test coverage * Rename `onNewPushMessage` to `onNewMessage` to keep consistency between SDKs * Stop using kluent * Refactor markMessageAsDelivered methods to return Boolean indicating success or failure * Check for the `MESSAGE_NEW` event type before marking a message as delivered. * Add unit tests for MessagingStyleNotificationHandler * Remove timestamps of the read and delivered members since the timestamp is always related to the latest message in the channel, and not for each message individually. * kdoc * Check for the `MESSAGE_NEW` event type before marking a message as delivered. * Add unit tests for NotificationHandlerFactory --------- Co-authored-by: Petar Velikov <[email protected]>
1 parent d9c13ea commit 57f41ff

File tree

143 files changed

+4850
-504
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

143 files changed

+4850
-504
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### ⬆️ Improved
66

77
### ✅ Added
8+
- Introduce message delivery receipts. [#5979](https://github.com/GetStream/stream-chat-android/pull/5979)
89

910
### ⚠️ Changed
1011

@@ -17,8 +18,12 @@
1718
- Use `ExoPlayer` instead of `MediaPlayer` for audio message playback. [#5980](https://github.com/GetStream/stream-chat-android/pull/5980)
1819

1920
### ✅ Added
21+
- Introduce `Channel.userRead` extension function to get the read status of a specific user in the channel. [#5979](https://github.com/GetStream/stream-chat-android/pull/5979)
22+
- Introduce `Channel.readsOf` extension function to get the read statuses representing which users have read the given message in the channel. [#5979](https://github.com/GetStream/stream-chat-android/pull/5979)
23+
- Introduce `ChatClient.markMessageAsDelivered` to mark a message as delivered for the current user. [#5979](https://github.com/GetStream/stream-chat-android/pull/5979)
2024

2125
### ⚠️ Changed
26+
- Deprecate `Channel.hasUnread` property in favor of `Channel.currentUserUnreadCount`. [#5979](https://github.com/GetStream/stream-chat-android/pull/5979)
2227

2328
### ❌ Removed
2429

@@ -3613,7 +3618,7 @@ The following items are breaking changes, since it was very important to improve
36133618
- Added `ChatUI.channelNameFormatter` to allow customizing the channel's name format. [#3068](https://github.com/GetStream/stream-chat-android/pull/3068)
36143619
- Added a customizable height attribute to SearchInputView [#3081](https://github.com/GetStream/stream-chat-android/pull/3081)
36153620
- Added `ChatUI.dateFormatter` to allow customizing the way the dates are formatted. [#3085](https://github.com/GetStream/stream-chat-android/pull/3085)
3616-
- Added ways to show/hide the delivery status indicators for channels and messages. [#3102](https://github.com/GetStream/stream-chat-android/pull/3102)
3621+
- Added ways to show/hide the delivery receipts indicators for channels and messages. [#3102](https://github.com/GetStream/stream-chat-android/pull/3102)
36173622

36183623
### ⚠️ Changed
36193624
- Disabled editing on Giphy messages given that it's breaking the UX and can override the GIF that was previously put in. [#3071](https://github.com/GetStream/stream-chat-android/pull/3071)

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import io.getstream.chat.android.client.events.ConnectedEvent
2727
import io.getstream.chat.android.client.events.MarkAllReadEvent
2828
import io.getstream.chat.android.client.events.MemberAddedEvent
2929
import io.getstream.chat.android.client.events.MemberRemovedEvent
30+
import io.getstream.chat.android.client.events.MessageDeliveredEvent
3031
import io.getstream.chat.android.client.events.MessageReadEvent
3132
import io.getstream.chat.android.client.events.MessageUpdatedEvent
3233
import io.getstream.chat.android.client.events.NewMessageEvent
@@ -260,6 +261,26 @@ public fun randomMessageReadEvent(
260261
)
261262
}
262263

264+
public fun randomMessageDeliveredEvent(
265+
createdAt: Date = Date(),
266+
user: User = randomUser(),
267+
cid: String = randomCID(),
268+
channelType: String = randomString(),
269+
channelId: String = randomString(),
270+
lastDeliveredAt: Date = randomDate(),
271+
lastDeliveredMessageId: String = randomString(),
272+
) = MessageDeliveredEvent(
273+
type = EventType.MESSAGE_DELIVERED,
274+
createdAt = createdAt,
275+
rawCreatedAt = streamFormatter.format(createdAt),
276+
user = user,
277+
cid = cid,
278+
channelType = channelType,
279+
channelId = channelId,
280+
lastDeliveredAt = lastDeliveredAt,
281+
lastDeliveredMessageId = lastDeliveredMessageId,
282+
)
283+
263284
public fun randomNotificationMarkReadEvent(
264285
createdAt: Date = Date(),
265286
user: User = randomUser(),

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

Lines changed: 56 additions & 10 deletions
Large diffs are not rendered by default.

stream-chat-android-client/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ dependencies {
9292
implementation(libs.okhttp.logging.interceptor)
9393
implementation(libs.ok2curl)
9494

95+
implementation(libs.androidx.room.runtime)
96+
implementation(libs.androidx.room.ktx)
97+
ksp(libs.androidx.room.compiler)
98+
9599
// Unused dependencies: The following dependencies (appcompat, constraintlayout, livedata-ktx) are not used in the
96100
// `stream-chat-android-client` module. They are still declared here to prevent potential breaking changes for
97101
// integrations that might be relying on them transitively. Consider removing them in future major releases.
@@ -106,8 +110,10 @@ dependencies {
106110
testImplementation(libs.stream.result)
107111
testImplementation(libs.androidx.test.junit)
108112
testImplementation(libs.androidx.lifecycle.runtime.testing)
113+
testImplementation(libs.androidx.work.testing)
109114
testImplementation(libs.junit.jupiter.api)
110115
testImplementation(libs.junit.jupiter.params)
116+
testImplementation(libs.turbine)
111117
testRuntimeOnly(libs.junit.jupiter.engine)
112118
testRuntimeOnly(libs.junit.vintage.engine)
113119

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

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,17 @@ import io.getstream.chat.android.client.parser2.adapters.internal.StreamDateForm
122122
import io.getstream.chat.android.client.persistance.repository.RepositoryFacade
123123
import io.getstream.chat.android.client.persistance.repository.factory.RepositoryFactory
124124
import io.getstream.chat.android.client.persistance.repository.noop.NoOpRepositoryFactory
125+
import io.getstream.chat.android.client.persistence.db.ChatClientDatabase
126+
import io.getstream.chat.android.client.persistence.repository.ChatClientRepository
125127
import io.getstream.chat.android.client.plugin.DependencyResolver
128+
import io.getstream.chat.android.client.plugin.MessageDeliveredPluginFactory
126129
import io.getstream.chat.android.client.plugin.Plugin
127130
import io.getstream.chat.android.client.plugin.factory.PluginFactory
128131
import io.getstream.chat.android.client.plugin.factory.ThrottlingPluginFactory
129132
import io.getstream.chat.android.client.query.AddMembersParams
130133
import io.getstream.chat.android.client.query.CreateChannelParams
134+
import io.getstream.chat.android.client.receipts.MessageReceiptManager
135+
import io.getstream.chat.android.client.receipts.MessageReceiptReporter
131136
import io.getstream.chat.android.client.scope.ClientScope
132137
import io.getstream.chat.android.client.scope.UserScope
133138
import io.getstream.chat.android.client.setup.state.ClientState
@@ -275,6 +280,9 @@ internal constructor(
275280
@InternalStreamChatApi
276281
public val audioPlayer: AudioPlayer,
277282
private val now: () -> Date = ::Date,
283+
private val repository: ChatClientRepository,
284+
private val messageReceiptReporter: MessageReceiptReporter,
285+
internal val messageReceiptManager: MessageReceiptManager,
278286
) {
279287
private val logger by taggedLogger(TAG)
280288
private val waitConnection = MutableSharedFlow<Result<ConnectionData>>()
@@ -447,13 +455,13 @@ internal constructor(
447455
mutableClientState.setUser(user)
448456
}
449457

450-
is NewMessageEvent,
451-
is NotificationReminderDueEvent,
452-
-> {
453-
// No other events should potentially show notifications
458+
is NewMessageEvent -> {
454459
notifications.onChatEvent(event)
460+
messageReceiptManager.markMessageAsDelivered(event.message)
455461
}
456462

463+
is NotificationReminderDueEvent -> notifications.onChatEvent(event)
464+
457465
is ConnectingEvent -> {
458466
logger.i { "[handleEvent] event: ConnectingEvent" }
459467
mutableClientState.setConnectionState(ConnectionState.Connecting)
@@ -642,6 +650,7 @@ internal constructor(
642650
tokenManager.setTokenProvider(tokenProvider)
643651
appSettingsManager.loadAppSettings()
644652
warmUp()
653+
messageReceiptReporter.start()
645654
logger.i { "[initializeClientWithUser] user.id: '${user.id}'completed" }
646655
}
647656

@@ -737,9 +746,11 @@ internal constructor(
737746
): Call<ConnectionData> {
738747
return CoroutineCall(clientScope) {
739748
logger.d { "[switchUser] user.id: '${user.id}'" }
740-
userScope.userId.value = user.id
741749
notifications.deleteDevice() // always delete device if switching users
742750
disconnectUserSuspend(flushPersistence = true)
751+
// change userId only after disconnect,
752+
// otherwise the userScope won't cancel coroutines related to the previous user.
753+
userScope.userId.value = user.id
743754
onDisconnectionComplete()
744755
connectUserSuspend(user, tokenProvider, timeoutMilliseconds).also {
745756
logger.v { "[switchUser] completed('${user.id}')" }
@@ -1505,6 +1516,8 @@ internal constructor(
15051516
userCredentialStorage.clear()
15061517
}
15071518

1519+
repository.clear()
1520+
15081521
_repositoryFacade = null
15091522
attachmentsSender.cancelJobs()
15101523
appSettingsManager.clear()
@@ -2869,6 +2882,43 @@ internal constructor(
28692882
}
28702883
}
28712884

2885+
/**
2886+
* Request to mark the message with the given id as delivered if:
2887+
*
2888+
* - Delivery receipts are enabled for the current user.
2889+
* - Delivery events are enabled in the channel config.
2890+
*
2891+
* and if all of the following conditions are met for the message:
2892+
*
2893+
* - Not sent by the current user.
2894+
* - Not shadow banned.
2895+
* - Not sent by a muted user.
2896+
* - Not yet marked as read by the current user.
2897+
* - Not yet marked as delivered by the current user.
2898+
*
2899+
* IMPORTANT: For this feature to function correctly and efficiently,
2900+
* the [offline plugin](https://getstream.io/chat/docs/sdk/android/client/guides/offline-support/)
2901+
* must be enabled to avoid extra API calls to retrieve message and channel data.
2902+
*
2903+
* @param messageId The ID of the message to mark as delivered.
2904+
*
2905+
* @return Executable async [Call] which completes with [Result] having data equal to true if the message
2906+
* was marked as delivered, false otherwise.
2907+
*/
2908+
@CheckResult
2909+
public fun markMessageAsDelivered(messageId: String): Call<Boolean> =
2910+
CoroutineCall(userScope) {
2911+
runCatching { messageReceiptManager.markMessageAsDelivered(messageId) }
2912+
.fold(
2913+
onSuccess = { Result.Success(it) },
2914+
onFailure = { Result.Failure(Error.GenericError(it.message.orEmpty())) },
2915+
)
2916+
}.doOnStart(userScope) {
2917+
logger.d { "[markMessageAsDelivered] #doOnStart; messageId: $messageId" }
2918+
}.doOnResult(userScope) {
2919+
logger.v { "[markMessageAsDelivered] #doOnResult; completed($messageId)" }
2920+
}
2921+
28722922
/**
28732923
* Marks the given message as read.
28742924
*
@@ -4716,7 +4766,8 @@ internal constructor(
47164766
appVersion = this.appVersion,
47174767
)
47184768

4719-
val appSettingsManager = AppSettingManager(module.api())
4769+
val api = module.api()
4770+
val appSettingsManager = AppSettingManager(api)
47204771

47214772
val audioPlayer: AudioPlayer = StreamAudioPlayer(
47224773
mediaPlayer = NativeMediaPlayerImpl(appContext) {
@@ -4732,12 +4783,15 @@ internal constructor(
47324783
userScope = userScope,
47334784
)
47344785

4786+
val database = ChatClientDatabase.build(appContext)
4787+
val repository = ChatClientRepository.from(database)
4788+
47354789
return ChatClient(
4736-
config,
4737-
module.api(),
4738-
module.dtoMapping,
4739-
module.notifications(),
4740-
tokenManager,
4790+
config = config,
4791+
api = api,
4792+
dtoMapping = module.dtoMapping,
4793+
notifications = module.notifications(),
4794+
tokenManager = tokenManager,
47414795
userCredentialStorage = userCredentialStorage ?: SharedPreferencesCredentialStorage(appContext),
47424796
userStateService = module.userStateService,
47434797
clientDebugger = clientDebugger ?: StubChatClientDebugger,
@@ -4755,6 +4809,18 @@ internal constructor(
47554809
mutableClientState = MutableClientState(module.networkStateProvider),
47564810
currentUserFetcher = module.currentUserFetcher,
47574811
audioPlayer = audioPlayer,
4812+
repository = repository,
4813+
messageReceiptReporter = MessageReceiptReporter(
4814+
scope = userScope,
4815+
messageReceiptRepository = repository,
4816+
api = api,
4817+
),
4818+
messageReceiptManager = MessageReceiptManager(
4819+
now = ::Date,
4820+
getRepositoryFacade = { instance().repositoryFacade },
4821+
messageReceiptRepository = repository,
4822+
api = api,
4823+
),
47584824
).apply {
47594825
attachmentsSender = AttachmentsSender(
47604826
context = appContext,
@@ -4799,7 +4865,10 @@ internal constructor(
47994865
* @see [Plugin]
48004866
* @see [PluginFactory]
48014867
*/
4802-
protected val pluginFactories: MutableList<PluginFactory> = mutableListOf(ThrottlingPluginFactory)
4868+
protected val pluginFactories: MutableList<PluginFactory> = mutableListOf(
4869+
ThrottlingPluginFactory,
4870+
MessageDeliveredPluginFactory,
4871+
)
48034872

48044873
/**
48054874
* Create a [ChatClient] instance based on the current configuration

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,11 @@ internal interface ChatApi {
338338
messageId: String = "",
339339
): Call<Unit>
340340

341+
@CheckResult
342+
fun markDelivered(
343+
messages: List<Message>,
344+
): Call<Unit>
345+
341346
@CheckResult
342347
fun markThreadRead(
343348
channelType: String,

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import io.getstream.chat.android.client.api2.model.requests.FlagUserRequest
5757
import io.getstream.chat.android.client.api2.model.requests.GuestUserRequest
5858
import io.getstream.chat.android.client.api2.model.requests.HideChannelRequest
5959
import io.getstream.chat.android.client.api2.model.requests.InviteMembersRequest
60+
import io.getstream.chat.android.client.api2.model.requests.MarkDeliveredRequest
6061
import io.getstream.chat.android.client.api2.model.requests.MarkReadRequest
6162
import io.getstream.chat.android.client.api2.model.requests.MarkUnreadRequest
6263
import io.getstream.chat.android.client.api2.model.requests.MuteChannelRequest
@@ -960,6 +961,11 @@ constructor(
960961
).toUnitCall()
961962
}
962963

964+
override fun markDelivered(messages: List<Message>): Call<Unit> =
965+
channelApi.markDelivered(
966+
request = MarkDeliveredRequest.create(messages),
967+
).toUnitCall()
968+
963969
override fun markThreadRead(channelType: String, channelId: String, threadId: String): Call<Unit> {
964970
return channelApi.markRead(
965971
channelType = channelType,

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import io.getstream.chat.android.client.api2.model.requests.AcceptInviteRequest
2323
import io.getstream.chat.android.client.api2.model.requests.AddMembersRequest
2424
import io.getstream.chat.android.client.api2.model.requests.HideChannelRequest
2525
import io.getstream.chat.android.client.api2.model.requests.InviteMembersRequest
26+
import io.getstream.chat.android.client.api2.model.requests.MarkDeliveredRequest
2627
import io.getstream.chat.android.client.api2.model.requests.MarkReadRequest
2728
import io.getstream.chat.android.client.api2.model.requests.MarkUnreadRequest
2829
import io.getstream.chat.android.client.api2.model.requests.PinnedMessagesRequest
@@ -211,4 +212,9 @@ internal interface ChannelApi {
211212
@Path("id") channelId: String,
212213
@UrlQueryPayload @Query("payload") payload: PinnedMessagesRequest,
213214
): RetrofitCall<MessagesResponse>
215+
216+
@POST("/channels/delivered")
217+
fun markDelivered(
218+
@Body request: MarkDeliveredRequest,
219+
): RetrofitCall<CompletableResponse>
214220
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616

1717
package io.getstream.chat.android.client.api2.mapping
1818

19+
import io.getstream.chat.android.DeliveryReceipts
1920
import io.getstream.chat.android.PrivacySettings
2021
import io.getstream.chat.android.ReadReceipts
2122
import io.getstream.chat.android.TypingIndicators
2223
import io.getstream.chat.android.client.api2.model.dto.AttachmentDto
2324
import io.getstream.chat.android.client.api2.model.dto.ChannelInfoDto
2425
import io.getstream.chat.android.client.api2.model.dto.CommandDto
2526
import io.getstream.chat.android.client.api2.model.dto.ConfigDto
27+
import io.getstream.chat.android.client.api2.model.dto.DeliveryReceiptsDto
2628
import io.getstream.chat.android.client.api2.model.dto.DeviceDto
2729
import io.getstream.chat.android.client.api2.model.dto.DownstreamChannelDto
2830
import io.getstream.chat.android.client.api2.model.dto.DownstreamChannelMuteDto
@@ -309,6 +311,7 @@ internal class DomainMapping(
309311
image = image ?: "",
310312
role = role,
311313
invisible = invisible,
314+
privacySettings = privacy_settings?.toDomain(),
312315
language = language ?: "",
313316
banned = banned,
314317
devices = devices.orEmpty().map { it.toDomain() },
@@ -525,6 +528,8 @@ internal class DomainMapping(
525528
lastRead = last_read,
526529
unreadMessages = unread_messages,
527530
lastReadMessageId = last_read_message_id,
531+
lastDeliveredAt = last_delivered_at,
532+
lastDeliveredMessageId = last_delivered_message_id,
528533
)
529534

530535
/**
@@ -599,6 +604,7 @@ internal class DomainMapping(
599604
name = name ?: "",
600605
typingEventsEnabled = typing_events,
601606
readEventsEnabled = read_events,
607+
deliveryEventsEnabled = delivery_events,
602608
connectEventsEnabled = connect_events,
603609
searchEnabled = search,
604610
isReactionsEnabled = reactions,
@@ -675,6 +681,7 @@ internal class DomainMapping(
675681
*/
676682
internal fun PrivacySettingsDto.toDomain(): PrivacySettings = PrivacySettings(
677683
typingIndicators = typing_indicators?.toDomain(),
684+
deliveryReceipts = delivery_receipts?.toDomain(),
678685
readReceipts = read_receipts?.toDomain(),
679686
)
680687

@@ -685,6 +692,11 @@ internal class DomainMapping(
685692
enabled = enabled,
686693
)
687694

695+
/**
696+
* Transforms [DeliveryReceiptsDto] to [DeliveryReceipts].
697+
*/
698+
internal fun DeliveryReceiptsDto.toDomain() = DeliveryReceipts(enabled = enabled)
699+
688700
/**
689701
* Transforms [ReadReceiptsDto] to [ReadReceipts].
690702
*/

0 commit comments

Comments
 (0)