Skip to content

Conversation

@andremion
Copy link
Contributor

@andremion andremion commented Oct 23, 2025

🎯 Goal

  • Send message delivery receipts, supporting local data buffering, batched reporting, event handling, and UI indicators.
  • Marks messages as delivered on:
    • Single channel query result (first page only)
    • Channel list query result
    • Push notification received
    • WS message.new event

🛠 Implementation details

Client module

  • New persistence and repository for delivery receipts (MessageReceiptDao/Entity/Repository, ChatClientDatabase, DateConverter).
  • New delivery reporting pipeline (MessageReceiptManager, MessageReceiptReporter) with 1s buffer and max 100 confirmations per call.
  • message.delivered WS event support.
  • Introduced new Channel extensions: readsOf, deliveredReadsOf.
  • PrivacySettings.delivery_receipts support.
  • Channel read state extended with last_delivered_at and last_delivered_message_id.

Delivery receipts are:

  • Buffered and sent every 1s, max 100 per request.
  • Not sent for own messages, not sent if already read or already delivered.
  • Reconciled against reads (reads supersede delivered).

Privacy:

  • Suppressed if ownUser.privacySettings.deliveryReceipts.enabled == false.

UI:

  • Compose and XML indicators updated to support the new grey double tick (delivered)
  • Unified logic to hide the message status indicator when a message is deleted / ephemeral / sync status failed permanently.

🎨 UI Changes

Add relevant screenshots

Before After
img img

Add relevant videos

Before After

🧪 Testing

Explain how this change can be tested (or why it can't be tested)

Provide a patch below if it is necessary for testing

Provide the patch summary here
Provide the patch code here

@andremion andremion added the core label Oct 23, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Oct 23, 2025

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 3.23 MB 3.25 MB 0.02 MB 🟢
stream-chat-android-offline 3.45 MB 3.47 MB 0.02 MB 🟢
stream-chat-android-ui-components 10.54 MB 10.57 MB 0.03 MB 🟢
stream-chat-android-compose 12.76 MB 12.79 MB 0.03 MB 🟢

@andremion andremion force-pushed the AND-769-message-delivery-status branch 3 times, most recently from cbf13fa to 0b052a3 Compare October 27, 2025 15:21
@github-actions
Copy link
Contributor

github-actions bot commented Oct 27, 2025

DB Entities have been updated. Do we need to upgrade DB Version?
Modified Entities :

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/persistence/db/entity/MessageReceiptEntity.kt
stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/channelconfig/internal/ChannelConfigEntity.kt

@andremion andremion force-pushed the AND-769-message-delivery-status branch 4 times, most recently from e3bbb11 to a43c291 Compare October 30, 2025 09:32
…`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.
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.
…rving 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.
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.
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.
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.
…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.
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.
…er is disconnected. This ensures that coroutines related to the previous user are properly cancelled before the new user connects.
Introduced a `MessageDeliveredPlugin` that automatically marks messages as delivered upon a successful `queryChannels` call. This plugin is enabled by default.
… used by `ChatClient`. This new repository is now a required dependency for `ChatClient`.
…Api` 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`.
- 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`.
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.
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.
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.
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.
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.
@andremion andremion force-pushed the AND-769-message-delivery-status branch from ead6af1 to eb33b8c Compare October 31, 2025 13:09
@sonarqubecloud
Copy link

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.
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants