diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/MessageDeliveryStatusTests.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/MessageDeliveryStatusTests.kt index e3d4ae772e8..e476a25dbd8 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/MessageDeliveryStatusTests.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/MessageDeliveryStatusTests.kt @@ -507,7 +507,6 @@ class MessageDeliveryStatusTests : StreamTestCase() { } @AllureId("5759") - @Ignore("https://linear.app/stream/issue/AND-1309") @Test fun test_deliveryStatusHidden_whenMessageIsSentAndReadEventsIsDisabled() { step("GIVEN read events are disabled and user opens the channel") { @@ -555,7 +554,6 @@ class MessageDeliveryStatusTests : StreamTestCase() { } @AllureId("5762") - @Ignore("https://linear.app/stream/issue/AND-1309") @Test fun test_deliveryStatusHidden_whenMessageReadByParticipantAndReadEventsIsDisabled() { step("GIVEN read events are disabled and user opens the channel") { @@ -574,7 +572,6 @@ class MessageDeliveryStatusTests : StreamTestCase() { } @AllureId("5763") - @Ignore("https://linear.app/stream/issue/AND-1309") @Test fun test_deliveryStatusHidden_whenNewParticipantAddedAndReadEventsIsDisabled() { step("GIVEN read events are disabled and user opens the channel") { @@ -596,7 +593,6 @@ class MessageDeliveryStatusTests : StreamTestCase() { } @AllureId("5764") - @Ignore("https://linear.app/stream/issue/AND-1309") @Test fun test_deliveryStatusHidden_whenParticipantIsRemovedAndReadEventsIsDisabled() { step("GIVEN read events are disabled and user opens the channel") { @@ -618,7 +614,6 @@ class MessageDeliveryStatusTests : StreamTestCase() { } @AllureId("5765") - @Ignore("https://linear.app/stream/issue/AND-1309") @Test fun test_deliveryStatusHiddenForMessagesInGroup_whenReadEventsIsDisabled() { step("GIVEN read events are disabled and user opens the channel") { @@ -637,7 +632,6 @@ class MessageDeliveryStatusTests : StreamTestCase() { } @AllureId("5766") - @Ignore("https://linear.app/stream/issue/AND-1309") @Test fun test_deliveryStatusHidden_whenMessageIsDeletedAndReadEventsIsDisabled() { step("GIVEN read events are disabled and user opens the channel") { @@ -659,7 +653,6 @@ class MessageDeliveryStatusTests : StreamTestCase() { } @AllureId("5771") - @Ignore("https://linear.app/stream/issue/AND-1309") @Test fun test_deliveryStatusHiddenInPreview_whenMessageIsSentAndReadEventsIsDisabled() { step("GIVEN read events are disabled and user opens the channel") { diff --git a/stream-chat-android-compose/api/stream-chat-android-compose.api b/stream-chat-android-compose/api/stream-chat-android-compose.api index 74fd4e286d5..9dfd41cc24b 100644 --- a/stream-chat-android-compose/api/stream-chat-android-compose.api +++ b/stream-chat-android-compose/api/stream-chat-android-compose.api @@ -1338,7 +1338,7 @@ public final class io/getstream/chat/android/compose/ui/components/channels/Comp public final class io/getstream/chat/android/compose/ui/components/channels/MessageReadStatusIconKt { public static final fun MessageReadStatusIcon (Lio/getstream/chat/android/models/Channel;Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/User;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V - public static final fun MessageReadStatusIcon (Lio/getstream/chat/android/models/Message;ZLandroidx/compose/ui/Modifier;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun MessageReadStatusIcon (Lio/getstream/chat/android/models/Message;ZLandroidx/compose/ui/Modifier;ZZZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V } public final class io/getstream/chat/android/compose/ui/components/channels/UnreadCountIndicatorKt { diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/MessageReadStatusIcon.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/MessageReadStatusIcon.kt index 98adba2c1fa..b9d49fdb7c0 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/MessageReadStatusIcon.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/MessageReadStatusIcon.kt @@ -63,6 +63,8 @@ public fun MessageReadStatusIcon( message = message, isMessageRead = isMessageRead, isMessageDelivered = isMessageDelivered, + readEventsEnabled = channel.config.readEventsEnabled, + deliveryEventsEnabled = channel.config.deliveryEventsEnabled, ) } @@ -73,6 +75,10 @@ public fun MessageReadStatusIcon( * @param isMessageRead If the message is read by any member. * @param isMessageDelivered If the message is delivered to any member. * @param modifier Modifier for styling. + * @param readEventsEnabled If read events are enabled for the channel. When disabled, the sent and read + * icons are not shown. + * @param deliveryEventsEnabled If delivery events are enabled for the channel. When disabled, the delivered + * icon is not shown. */ @Composable public fun MessageReadStatusIcon( @@ -80,6 +86,8 @@ public fun MessageReadStatusIcon( isMessageRead: Boolean, modifier: Modifier = Modifier, isMessageDelivered: Boolean = false, + readEventsEnabled: Boolean = true, + deliveryEventsEnabled: Boolean = true, isReadIcon: @Composable () -> Unit = { IsReadIcon(modifier = modifier) }, isPendingIcon: @Composable () -> Unit = { IsPendingIcon(modifier = modifier) }, isSentIcon: @Composable () -> Unit = { IsSentIcon(modifier = modifier) }, @@ -94,9 +102,9 @@ public fun MessageReadStatusIcon( -> isPendingIcon() SyncStatus.COMPLETED -> when { - isMessageRead -> isReadIcon() - isMessageDelivered -> isDeliveredIcon() - else -> isSentIcon() + isMessageRead -> if (readEventsEnabled) isReadIcon() + isMessageDelivered -> if (deliveryEventsEnabled) isDeliveredIcon() + else -> if (readEventsEnabled) isSentIcon() } SyncStatus.FAILED_PERMANENTLY -> IsErrorIcon(modifier = modifier) diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt index e7fccf1b4df..73c4e4269c0 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt @@ -1250,6 +1250,8 @@ public interface ChatComponentFactory { message = params.messageItem.message, isMessageRead = params.messageItem.isMessageRead, isMessageDelivered = params.messageItem.isMessageDelivered, + readEventsEnabled = params.messageItem.readEventsEnabled, + deliveryEventsEnabled = params.messageItem.deliveryEventsEnabled, ) } diff --git a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/components/channels/MessageReadStatusIconBehaviorTest.kt b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/components/channels/MessageReadStatusIconBehaviorTest.kt new file mode 100644 index 00000000000..2c88dc86a90 --- /dev/null +++ b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/components/channels/MessageReadStatusIconBehaviorTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.compose.ui.components.channels + +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.getstream.chat.android.client.test.MockedChatClientTest +import io.getstream.chat.android.compose.ui.theme.ChatTheme +import io.getstream.chat.android.models.ConnectionState +import io.getstream.chat.android.models.SyncStatus +import io.getstream.chat.android.randomMessage +import io.getstream.chat.android.randomUser +import kotlinx.coroutines.flow.MutableStateFlow +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.whenever +import org.robolectric.annotation.Config + +@RunWith(AndroidJUnit4::class) +@Config(sdk = [33]) +internal class MessageReadStatusIconBehaviorTest : MockedChatClientTest { + + @get:Rule + val composeTestRule = createComposeRule() + + @Before + fun prepare() { + whenever(mockClientState.user) doReturn MutableStateFlow(randomUser()) + whenever(mockClientState.connectionState) doReturn MutableStateFlow(ConnectionState.Connected) + } + + @Test + fun `sent icon is shown when read events are enabled`() { + setIconContent(readEventsEnabled = true) + + composeTestRule + .onNodeWithTag("Stream_MessageReadStatus_isSent", useUnmergedTree = true) + .assertExists() + } + + @Test + fun `sent icon is hidden when read events are disabled`() { + setIconContent(readEventsEnabled = false) + + composeTestRule + .onNodeWithTag("Stream_MessageReadStatus_isSent", useUnmergedTree = true) + .assertDoesNotExist() + } + + @Test + fun `read icon is hidden when read events are disabled`() { + setIconContent(isMessageRead = true, readEventsEnabled = false) + + composeTestRule + .onNodeWithTag("Stream_MessageReadStatus_isRead", useUnmergedTree = true) + .assertDoesNotExist() + } + + @Test + fun `delivered icon is hidden when delivery events are disabled`() { + setIconContent(isMessageDelivered = true, deliveryEventsEnabled = false) + + composeTestRule + .onNodeWithTag("Stream_MessageReadStatus_isDelivered", useUnmergedTree = true) + .assertDoesNotExist() + } + + @Test + fun `pending icon is shown when read events are disabled`() { + setIconContent(syncStatus = SyncStatus.IN_PROGRESS, readEventsEnabled = false) + + composeTestRule + .onNodeWithTag("Stream_MessageReadStatus_isPending", useUnmergedTree = true) + .assertExists() + } + + @Test + fun `error icon is shown when read events are disabled`() { + setIconContent(syncStatus = SyncStatus.FAILED_PERMANENTLY, readEventsEnabled = false) + + composeTestRule + .onNodeWithTag("Stream_MessageReadStatus_isError", useUnmergedTree = true) + .assertExists() + } + + private fun setIconContent( + syncStatus: SyncStatus = SyncStatus.COMPLETED, + isMessageRead: Boolean = false, + isMessageDelivered: Boolean = false, + readEventsEnabled: Boolean = true, + deliveryEventsEnabled: Boolean = true, + ) { + composeTestRule.setContent { + ChatTheme { + MessageReadStatusIcon( + message = randomMessage(syncStatus = syncStatus), + isMessageRead = isMessageRead, + isMessageDelivered = isMessageDelivered, + readEventsEnabled = readEventsEnabled, + deliveryEventsEnabled = deliveryEventsEnabled, + ) + } + } + } +} diff --git a/stream-chat-android-ui-common/api/stream-chat-android-ui-common.api b/stream-chat-android-ui-common/api/stream-chat-android-ui-common.api index 1083b80d489..ddc08e3cd89 100644 --- a/stream-chat-android-ui-common/api/stream-chat-android-ui-common.api +++ b/stream-chat-android-ui-common/api/stream-chat-android-ui-common.api @@ -2757,14 +2757,16 @@ public final class io/getstream/chat/android/ui/common/state/messages/list/Messa public final class io/getstream/chat/android/ui/common/state/messages/list/MessageItemState : io/getstream/chat/android/ui/common/state/messages/list/HasMessageListItemState { public static final field $stable I - public fun (Lio/getstream/chat/android/models/Message;Ljava/lang/String;ZZZLio/getstream/chat/android/models/User;Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition;ZZLio/getstream/chat/android/ui/common/state/messages/list/MessageFocusState;Ljava/util/List;ZLjava/util/Set;Z)V - public synthetic fun (Lio/getstream/chat/android/models/Message;Ljava/lang/String;ZZZLio/getstream/chat/android/models/User;Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition;ZZLio/getstream/chat/android/ui/common/state/messages/list/MessageFocusState;Ljava/util/List;ZLjava/util/Set;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/getstream/chat/android/models/Message;Ljava/lang/String;ZZZLio/getstream/chat/android/models/User;Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition;ZZLio/getstream/chat/android/ui/common/state/messages/list/MessageFocusState;Ljava/util/List;ZLjava/util/Set;ZZZ)V + public synthetic fun (Lio/getstream/chat/android/models/Message;Ljava/lang/String;ZZZLio/getstream/chat/android/models/User;Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition;ZZLio/getstream/chat/android/ui/common/state/messages/list/MessageFocusState;Ljava/util/List;ZLjava/util/Set;ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/getstream/chat/android/models/Message; public final fun component10 ()Lio/getstream/chat/android/ui/common/state/messages/list/MessageFocusState; public final fun component11 ()Ljava/util/List; public final fun component12 ()Z public final fun component13 ()Ljava/util/Set; public final fun component14 ()Z + public final fun component15 ()Z + public final fun component16 ()Z public final fun component2 ()Ljava/lang/String; public final fun component3 ()Z public final fun component4 ()Z @@ -2773,16 +2775,18 @@ public final class io/getstream/chat/android/ui/common/state/messages/list/Messa public final fun component7 ()Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition; public final fun component8 ()Z public final fun component9 ()Z - public final fun copy (Lio/getstream/chat/android/models/Message;Ljava/lang/String;ZZZLio/getstream/chat/android/models/User;Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition;ZZLio/getstream/chat/android/ui/common/state/messages/list/MessageFocusState;Ljava/util/List;ZLjava/util/Set;Z)Lio/getstream/chat/android/ui/common/state/messages/list/MessageItemState; - public static synthetic fun copy$default (Lio/getstream/chat/android/ui/common/state/messages/list/MessageItemState;Lio/getstream/chat/android/models/Message;Ljava/lang/String;ZZZLio/getstream/chat/android/models/User;Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition;ZZLio/getstream/chat/android/ui/common/state/messages/list/MessageFocusState;Ljava/util/List;ZLjava/util/Set;ZILjava/lang/Object;)Lio/getstream/chat/android/ui/common/state/messages/list/MessageItemState; + public final fun copy (Lio/getstream/chat/android/models/Message;Ljava/lang/String;ZZZLio/getstream/chat/android/models/User;Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition;ZZLio/getstream/chat/android/ui/common/state/messages/list/MessageFocusState;Ljava/util/List;ZLjava/util/Set;ZZZ)Lio/getstream/chat/android/ui/common/state/messages/list/MessageItemState; + public static synthetic fun copy$default (Lio/getstream/chat/android/ui/common/state/messages/list/MessageItemState;Lio/getstream/chat/android/models/Message;Ljava/lang/String;ZZZLio/getstream/chat/android/models/User;Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition;ZZLio/getstream/chat/android/ui/common/state/messages/list/MessageFocusState;Ljava/util/List;ZLjava/util/Set;ZZZILjava/lang/Object;)Lio/getstream/chat/android/ui/common/state/messages/list/MessageItemState; public fun equals (Ljava/lang/Object;)Z public final fun getCurrentUser ()Lio/getstream/chat/android/models/User; + public final fun getDeliveryEventsEnabled ()Z public final fun getFocusState ()Lio/getstream/chat/android/ui/common/state/messages/list/MessageFocusState; public final fun getGroupPosition ()Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition; public fun getMessage ()Lio/getstream/chat/android/models/Message; public final fun getMessageReadBy ()Ljava/util/List; public final fun getOwnCapabilities ()Ljava/util/Set; public final fun getParentMessageId ()Ljava/lang/String; + public final fun getReadEventsEnabled ()Z public final fun getShowMessageFooter ()Z public final fun getShowOriginalText ()Z public fun hashCode ()I diff --git a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListController.kt b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListController.kt index 716443e74ea..16b4699bb9f 100644 --- a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListController.kt +++ b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListController.kt @@ -905,6 +905,7 @@ public class MessageListController( ): List { val parentMessageId = (_mode.value as? MessageMode.MessageThread)?.parentMessage?.id val currentUser = user.value + val channelConfig = channelState.value?.channelConfig?.value val groupedMessages = mutableListOf() val membersMap = members.associateBy { it.user.id } val sortedReads = reads @@ -1006,6 +1007,8 @@ public class MessageListController( focusState = if (isMessageFocused) MessageFocused else null, ownCapabilities = ownCapabilities, showOriginalText = messagesInOriginalLanguage.contains(message.id), + readEventsEnabled = channelConfig?.readEventsEnabled ?: true, + deliveryEventsEnabled = channelConfig?.deliveryEventsEnabled ?: true, ), ) } diff --git a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/messages/list/MessageListItemState.kt b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/messages/list/MessageListItemState.kt index efc7ad09810..97c872f8084 100644 --- a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/messages/list/MessageListItemState.kt +++ b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/messages/list/MessageListItemState.kt @@ -71,6 +71,8 @@ public sealed class HasMessageListItemState : MessageListItemState() { * the message was auto-translated). * @param ownCapabilities The capabilities of the current user in the channel. * @param isPreviewMode Whether the message is displayed as a preview (e.g. inside the selected message menu). + * @param readEventsEnabled Whether read events are enabled for the channel. + * @param deliveryEventsEnabled Whether delivery events are enabled for the channel. */ public data class MessageItemState( public override val message: Message = Message(), @@ -87,6 +89,8 @@ public data class MessageItemState( public val showOriginalText: Boolean = false, public val ownCapabilities: Set, public val isPreviewMode: Boolean = false, + public val readEventsEnabled: Boolean = true, + public val deliveryEventsEnabled: Boolean = true, ) : HasMessageListItemState() /** diff --git a/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListControllerTests.kt b/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListControllerTests.kt index 41a1abbbde0..22a0a7d5b1e 100644 --- a/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListControllerTests.kt +++ b/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListControllerTests.kt @@ -285,6 +285,32 @@ internal class MessageListControllerTests { messagesCount `should be equal to` 10 } + @Test + fun `When read events are disabled in the channel config Should propagate it to message items`() = runTest { + val messagesState = MutableStateFlow(listOf(randomMessage())) + val controller = Fixture() + .givenCurrentUser() + .givenChannelQuery() + .givenChannelState(messagesState = messagesState, config = Config(readEventsEnabled = false)) + .get() + + val messageItem = controller.messageListState.value.messageItems.filterIsInstance().first() + messageItem.readEventsEnabled `should be equal to` false + } + + @Test + fun `When delivery events are disabled in the channel config Should propagate it to message items`() = runTest { + val messagesState = MutableStateFlow(listOf(randomMessage())) + val controller = Fixture() + .givenCurrentUser() + .givenChannelQuery() + .givenChannelState(messagesState = messagesState, config = Config(deliveryEventsEnabled = false)) + .get() + + val messageItem = controller.messageListState.value.messageItems.filterIsInstance().first() + messageItem.deliveryEventsEnabled `should be equal to` false + } + // footer visibility @Test fun `When footer visibility is with time difference When message is after specified time Show message footer`() = @@ -1320,10 +1346,11 @@ internal class MessageListControllerTests { typingUsers: List = listOf(), typingState: StateFlow = MutableStateFlow(TypingEvent(cid, typingUsers)), read: StateFlow = MutableStateFlow(randomChannelUserRead(lastReadMessageId = null)), + config: Config = Config(), ) = apply { whenever(channelState.cid) doReturn CID whenever(channelState.channelData) doReturn channelDataState - whenever(channelState.channelConfig) doReturn MutableStateFlow(Config()) + whenever(channelState.channelConfig) doReturn MutableStateFlow(config) whenever(channelState.members) doReturn membersState whenever(channelState.membersCount) doReturn membersCountState whenever(channelState.watchers) doReturn watchersState diff --git a/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api b/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api index dfb7e0045c7..346f976e33a 100644 --- a/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api +++ b/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api @@ -2902,9 +2902,11 @@ public final class io/getstream/chat/android/ui/feature/messages/list/adapter/Me } public final class io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItem$MessageItem : io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItem { - public fun (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition;ZLjava/util/List;ZZZZZ)V - public synthetic fun (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition;ZLjava/util/List;ZZZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition;ZLjava/util/List;ZZZZZZZ)V + public synthetic fun (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition;ZLjava/util/List;ZZZZZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/getstream/chat/android/models/Message; + public final fun component10 ()Z + public final fun component11 ()Z public final fun component2 ()Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition; public final fun component3 ()Z public final fun component4 ()Ljava/util/List; @@ -2913,12 +2915,14 @@ public final class io/getstream/chat/android/ui/feature/messages/list/adapter/Me public final fun component7 ()Z public final fun component8 ()Z public final fun component9 ()Z - public final fun copy (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition;ZLjava/util/List;ZZZZZ)Lio/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItem$MessageItem; - public static synthetic fun copy$default (Lio/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItem$MessageItem;Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition;ZLjava/util/List;ZZZZZILjava/lang/Object;)Lio/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItem$MessageItem; + public final fun copy (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition;ZLjava/util/List;ZZZZZZZ)Lio/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItem$MessageItem; + public static synthetic fun copy$default (Lio/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItem$MessageItem;Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition;ZLjava/util/List;ZZZZZZZILjava/lang/Object;)Lio/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItem$MessageItem; public fun equals (Ljava/lang/Object;)Z + public final fun getDeliveryEventsEnabled ()Z public final fun getMessage ()Lio/getstream/chat/android/models/Message; public final fun getMessageReadBy ()Ljava/util/List; public final fun getPosition ()Lio/getstream/chat/android/ui/common/state/messages/list/MessagePosition; + public final fun getReadEventsEnabled ()Z public final fun getShowMessageFooter ()Z public final fun getShowOriginalText ()Z public fun hashCode ()I diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/channels/list/adapter/viewholder/internal/ChannelViewHolder.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/channels/list/adapter/viewholder/internal/ChannelViewHolder.kt index 9719aba3851..371941882e3 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/channels/list/adapter/viewholder/internal/ChannelViewHolder.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/channels/list/adapter/viewholder/internal/ChannelViewHolder.kt @@ -361,11 +361,11 @@ internal class ChannelViewHolder @JvmOverloads constructor( val lastMessageWasDelivered = channel.deliveredReadsOf(lastMessage).isNotEmpty() if (lastMessageWasRead) { - style.indicatorReadIcon + style.indicatorReadIcon.takeIf { channel.config.readEventsEnabled } } else if (lastMessageWasDelivered) { - style.indicatorDeliveredIcon + style.indicatorDeliveredIcon.takeIf { channel.config.deliveryEventsEnabled } } else { - style.indicatorSentIcon + style.indicatorSentIcon.takeIf { channel.config.readEventsEnabled } } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItem.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItem.kt index cee9e1bb971..d404cbf8f08 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItem.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItem.kt @@ -81,6 +81,8 @@ public sealed class MessageListItem { * @property showMessageFooter True if the message footer should be displayed, otherwise false. * @property showOriginalText If the original text of the message should be shown in the UI instead of its * translation (if the message was auto-translated). + * @property readEventsEnabled Whether read events are enabled for the channel. + * @property deliveryEventsEnabled Whether delivery events are enabled for the channel. */ public data class MessageItem( val message: Message, @@ -92,6 +94,8 @@ public sealed class MessageListItem { val isMessageDelivered: Boolean = false, val showMessageFooter: Boolean = false, val showOriginalText: Boolean = false, + val readEventsEnabled: Boolean = true, + val deliveryEventsEnabled: Boolean = true, ) : MessageListItem() { public val isTheirs: Boolean get() = !isMine diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/decorator/internal/FootnoteDecorator.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/decorator/internal/FootnoteDecorator.kt index fda52b39500..1170f5dcd08 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/decorator/internal/FootnoteDecorator.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/decorator/internal/FootnoteDecorator.kt @@ -390,9 +390,9 @@ internal class FootnoteDecorator( -> itemStyle.iconIndicatorPendingSync SyncStatus.COMPLETED -> when { - data.isMessageRead -> itemStyle.iconIndicatorRead - data.isMessageDelivered -> itemStyle.iconIndicatorDelivered - else -> itemStyle.iconIndicatorSent + data.isMessageRead -> itemStyle.iconIndicatorRead.takeIf { data.readEventsEnabled } + data.isMessageDelivered -> itemStyle.iconIndicatorDelivered.takeIf { data.deliveryEventsEnabled } + else -> itemStyle.iconIndicatorSent.takeIf { data.readEventsEnabled } } else -> null diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/utils/extensions/MessageListItem.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/utils/extensions/MessageListItem.kt index a6159461086..650ed8f80f8 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/utils/extensions/MessageListItem.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/utils/extensions/MessageListItem.kt @@ -51,6 +51,8 @@ public fun MessageListItemCommon.toUiMessageListItem(): MessageListItem { isMessageDelivered = isMessageDelivered, showMessageFooter = showMessageFooter, showOriginalText = showOriginalText, + readEventsEnabled = readEventsEnabled, + deliveryEventsEnabled = deliveryEventsEnabled, ) is EmptyThreadPlaceholderItemState -> MessageListItem.ThreadPlaceholderItem is UnreadSeparatorItemState -> MessageListItem.UnreadSeparatorItem(unreadCount = unreadCount) diff --git a/stream-chat-android-ui-components/src/test/kotlin/io/getstream/chat/android/ui/utils/extensions/MessageListItemMappingTest.kt b/stream-chat-android-ui-components/src/test/kotlin/io/getstream/chat/android/ui/utils/extensions/MessageListItemMappingTest.kt new file mode 100644 index 00000000000..dceacfdcfac --- /dev/null +++ b/stream-chat-android-ui-components/src/test/kotlin/io/getstream/chat/android/ui/utils/extensions/MessageListItemMappingTest.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.ui.utils.extensions + +import io.getstream.chat.android.randomBoolean +import io.getstream.chat.android.randomMessage +import io.getstream.chat.android.ui.common.state.messages.list.MessageItemState +import io.getstream.chat.android.ui.feature.messages.list.adapter.MessageListItem +import org.amshove.kluent.`should be equal to` +import org.junit.jupiter.api.Test + +internal class MessageListItemMappingTest { + + @Test + fun `toUiMessageListItem should copy the channel config event flags`() { + val readEventsEnabled = randomBoolean() + val deliveryEventsEnabled = randomBoolean() + val state = MessageItemState( + message = randomMessage(), + ownCapabilities = emptySet(), + readEventsEnabled = readEventsEnabled, + deliveryEventsEnabled = deliveryEventsEnabled, + ) + + val item = state.toUiMessageListItem() as MessageListItem.MessageItem + + item.readEventsEnabled `should be equal to` readEventsEnabled + item.deliveryEventsEnabled `should be equal to` deliveryEventsEnabled + } +}