Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down Expand Up @@ -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") {
Expand All @@ -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") {
Expand All @@ -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") {
Expand All @@ -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") {
Expand All @@ -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") {
Expand All @@ -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") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public fun MessageReadStatusIcon(
message = message,
isMessageRead = isMessageRead,
isMessageDelivered = isMessageDelivered,
readEventsEnabled = channel.config.readEventsEnabled,
deliveryEventsEnabled = channel.config.deliveryEventsEnabled,
)
}

Expand All @@ -73,13 +75,19 @@ 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(
message: Message,
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) },
Expand All @@ -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()
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

SyncStatus.FAILED_PERMANENTLY -> IsErrorIcon(modifier = modifier)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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,
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 <init> (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 <init> (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 <init> (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 <init> (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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,7 @@ public class MessageListController(
): List<MessageListItemState> {
val parentMessageId = (_mode.value as? MessageMode.MessageThread)?.parentMessage?.id
val currentUser = user.value
val channelConfig = channelState.value?.channelConfig?.value
val groupedMessages = mutableListOf<MessageListItemState>()
val membersMap = members.associateBy { it.user.id }
val sortedReads = reads
Expand Down Expand Up @@ -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,
),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -87,6 +89,8 @@ public data class MessageItemState(
public val showOriginalText: Boolean = false,
public val ownCapabilities: Set<String>,
public val isPreviewMode: Boolean = false,
public val readEventsEnabled: Boolean = true,
public val deliveryEventsEnabled: Boolean = true,
) : HasMessageListItemState()

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<MessageItemState>().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<MessageItemState>().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`() =
Expand Down Expand Up @@ -1320,10 +1346,11 @@ internal class MessageListControllerTests {
typingUsers: List<User> = listOf(),
typingState: StateFlow<TypingEvent> = MutableStateFlow(TypingEvent(cid, typingUsers)),
read: StateFlow<ChannelUserRead?> = 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
Expand Down
Loading
Loading