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 @@ -67,15 +67,16 @@ public abstract class io/getstream/chat/android/compose/state/channels/list/Item

public final class io/getstream/chat/android/compose/state/channels/list/ItemState$ChannelItemState : io/getstream/chat/android/compose/state/channels/list/ItemState {
public static final field $stable I
public fun <init> (Lio/getstream/chat/android/models/Channel;ZLjava/util/List;Lio/getstream/chat/android/models/DraftMessage;Z)V
public synthetic fun <init> (Lio/getstream/chat/android/models/Channel;ZLjava/util/List;Lio/getstream/chat/android/models/DraftMessage;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lio/getstream/chat/android/models/Channel;ZLjava/util/List;Lio/getstream/chat/android/models/DraftMessage;ZZ)V
public synthetic fun <init> (Lio/getstream/chat/android/models/Channel;ZLjava/util/List;Lio/getstream/chat/android/models/DraftMessage;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lio/getstream/chat/android/models/Channel;
public final fun component2 ()Z
public final fun component3 ()Ljava/util/List;
public final fun component4 ()Lio/getstream/chat/android/models/DraftMessage;
public final fun component5 ()Z
public final fun copy (Lio/getstream/chat/android/models/Channel;ZLjava/util/List;Lio/getstream/chat/android/models/DraftMessage;Z)Lio/getstream/chat/android/compose/state/channels/list/ItemState$ChannelItemState;
public static synthetic fun copy$default (Lio/getstream/chat/android/compose/state/channels/list/ItemState$ChannelItemState;Lio/getstream/chat/android/models/Channel;ZLjava/util/List;Lio/getstream/chat/android/models/DraftMessage;ZILjava/lang/Object;)Lio/getstream/chat/android/compose/state/channels/list/ItemState$ChannelItemState;
public final fun component6 ()Z
public final fun copy (Lio/getstream/chat/android/models/Channel;ZLjava/util/List;Lio/getstream/chat/android/models/DraftMessage;ZZ)Lio/getstream/chat/android/compose/state/channels/list/ItemState$ChannelItemState;
public static synthetic fun copy$default (Lio/getstream/chat/android/compose/state/channels/list/ItemState$ChannelItemState;Lio/getstream/chat/android/models/Channel;ZLjava/util/List;Lio/getstream/chat/android/models/DraftMessage;ZZILjava/lang/Object;)Lio/getstream/chat/android/compose/state/channels/list/ItemState$ChannelItemState;
public fun equals (Ljava/lang/Object;)Z
public final fun getChannel ()Lio/getstream/chat/android/models/Channel;
public final fun getDraftMessage ()Lio/getstream/chat/android/models/DraftMessage;
Expand All @@ -84,6 +85,7 @@ public final class io/getstream/chat/android/compose/state/channels/list/ItemSta
public fun hashCode ()I
public final fun isMuted ()Z
public final fun isSelected ()Z
public final fun isUserMuted ()Z
public fun toString ()Ljava/lang/String;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ public sealed class ItemState {
* @param typingUsers The list of users currently typing in the channel.
* @param draftMessage The draft message for the current user in the channel.
* @param isSelected Whether this channel is currently selected (e.g. via long-press context menu).
* @param isUserMuted If this is a 1:1 channel and the other member is muted by the current user.
*/
public data class ChannelItemState(
val channel: Channel,
val isMuted: Boolean = false,
val typingUsers: List<User> = emptyList(),
val draftMessage: DraftMessage? = null,
val isSelected: Boolean = false,
val isUserMuted: Boolean = false,
) : ItemState() {
override val key: String = channel.cid
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ private fun TitleRow(
unreadCount: Int,
) {
val channel = channelItemState.channel
val isMuted = channelItemState.isMuted
val isMuted = channelItemState.isMuted || channelItemState.isUserMuted
val mutePosition = ChatTheme.config.channelList.muteIndicatorPosition
Row(
modifier = Modifier.fillMaxWidth(),
Expand Down Expand Up @@ -344,7 +344,9 @@ private fun MessageRow(
MessageContent(channelItemState, currentUser, lastMessage, isDirectMessaging)
}

if (channelItemState.isMuted && mutePosition == MuteIndicatorPosition.TrailingBottom) {
if ((channelItemState.isMuted || channelItemState.isUserMuted) &&
mutePosition == MuteIndicatorPosition.TrailingBottom
) {
Icon(
modifier = Modifier
.testTag("Stream_ChannelMutedIcon")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import io.getstream.chat.android.models.querysort.QuerySorter
import io.getstream.chat.android.ui.common.state.channels.actions.ChannelAction
import io.getstream.chat.android.ui.common.utils.extensions.defaultChannelListFilter
import io.getstream.chat.android.ui.common.utils.extensions.isOneToOne
import io.getstream.log.taggedLogger
import io.getstream.result.call.toUnitCall
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand Down Expand Up @@ -283,6 +284,7 @@
),
)
}

is SearchQuery.Messages -> {
chListScope.coroutineContext.cancelChildren()
handleSearchQuery(query.query)
Expand Down Expand Up @@ -401,6 +403,7 @@
next = result.value.next,
)
}

is io.getstream.result.Result.Failure -> {
logger.e { "[searchMessages] #$src; failed: ${result.value}" }
currentState.copy(
Expand Down Expand Up @@ -433,7 +436,8 @@
channelMutes,
typingChannels,
channelDraftMessages,
) { state, channelMutes, typingChannels, channelDraftMessages ->
globalMuted,
) { state, channelMutes, typingChannels, channelDraftMessages, userMutes ->
when (state) {
ChannelsStateData.NoQueryActive,
ChannelsStateData.Loading,
Expand All @@ -458,6 +462,7 @@
channelItems = createChannelItems(
channels = state.channels,
channelMutes = channelMutes,
userMutes = userMutes,
typingEvents = typingChannels,
draftMessages = channelDraftMessages.takeIf { draftMessagesEnabled } ?: emptyMap(),
),
Expand Down Expand Up @@ -575,6 +580,7 @@
is SearchQuery.Empty,
is SearchQuery.Channels,
-> chListScope.launch { loadMoreQueryChannels() }

is SearchQuery.Messages,
-> searchScope.launch { loadMoreQueryMessages() }
}
Expand Down Expand Up @@ -801,25 +807,37 @@
*
* @param channels The channels to show.
* @param channelMutes The list of channels muted for the current user.
*
* @param userMutes The list of users muted by the current user.
*/
private fun createChannelItems(
channels: List<Channel>,
channelMutes: List<ChannelMute>,
userMutes: List<Mute>,
typingEvents: Map<String, TypingEvent>,
draftMessages: Map<String, DraftMessage>,
): List<ItemState.ChannelItemState> {
val mutedChannelIds = channelMutes.map { channelMute -> channelMute.channel?.cid }.toSet()
val mutedUserIds = userMutes.mapNotNullTo(mutableSetOf()) { it.target?.id }

Check warning on line 820 in stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModel.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make this collection immutable.

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-chat-android&issues=AZ5JkgEbWjkvW0ghlNtG&open=AZ5JkgEbWjkvW0ghlNtG&pullRequest=6449
val currentUser = user.value
return channels.map {
ItemState.ChannelItemState(
channel = it,
isMuted = it.cid in mutedChannelIds,
isUserMuted = it.isOneToOneMutedByUser(currentUser, mutedUserIds),
typingUsers = typingEvents[it.cid]?.users ?: emptyList(),
draftMessage = draftMessages[it.cid],
)
}
}

/** Checks if a 1:1 channel is muted via user mute (i.e. the other member is muted). */
private fun Channel.isOneToOneMutedByUser(currentUser: User?, mutedUserIds: Set<String>): Boolean =
if (mutedUserIds.isEmpty() || !isOneToOne(currentUser)) {
false
} else {
members.any { it.user.id != currentUser?.id && it.user.id in mutedUserIds }
}

internal companion object {
/**
* Default value of number of channels to return when querying channels.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ import io.getstream.chat.android.models.ChannelMute
import io.getstream.chat.android.models.FilterObject
import io.getstream.chat.android.models.Filters
import io.getstream.chat.android.models.InitializationState
import io.getstream.chat.android.models.Member
import io.getstream.chat.android.models.Message
import io.getstream.chat.android.models.Mute
import io.getstream.chat.android.models.OrFilterObject
import io.getstream.chat.android.models.SearchMessagesResult
import io.getstream.chat.android.models.TypingEvent
Expand Down Expand Up @@ -191,6 +193,97 @@ internal class ChannelListViewModelTest {
verify(chatClient).unmuteChannel("messaging", "channel1")
}

@Test
fun `Given a DM with a muted user Should mark the channel item as user-muted`() = runTest {
val viewModel = Fixture()
.givenCurrentUser(currentUser)
.givenChannelsQuery()
.givenChannelsState(
channelsStateData = ChannelsStateData.Result(listOf(directChannel)),
loading = false,
)
.givenChannelMutes()
.givenUserMutes(listOf(otherUserMute))
.givenTypingChannels()
.get(this)

val channelItem = viewModel.channelsState.channelItems.first() as ItemState.ChannelItemState
assertFalse(channelItem.isMuted)
assertTrue(channelItem.isUserMuted)
}

@Test
fun `Given a DM without a muted user Should not mark the channel item as muted`() = runTest {
val viewModel = Fixture()
.givenCurrentUser(currentUser)
.givenChannelsQuery()
.givenChannelsState(
channelsStateData = ChannelsStateData.Result(listOf(directChannel)),
loading = false,
)
.givenChannelMutes()
.givenTypingChannels()
.get(this)

val channelItem = viewModel.channelsState.channelItems.first() as ItemState.ChannelItemState
assertFalse(channelItem.isMuted)
assertFalse(channelItem.isUserMuted)
}

@Test
fun `Given a DM whose other member is not muted Should not mark the channel item as muted`() = runTest {
val unrelatedUserMute = Mute(
user = currentUser,
target = User(id = "unrelatedUser"),
createdAt = Date(),
updatedAt = Date(),
expires = null,
)
val viewModel = Fixture()
.givenCurrentUser(currentUser)
.givenChannelsQuery()
.givenChannelsState(
channelsStateData = ChannelsStateData.Result(listOf(directChannel)),
loading = false,
)
.givenChannelMutes()
.givenUserMutes(listOf(unrelatedUserMute))
.givenTypingChannels()
.get(this)

val channelItem = viewModel.channelsState.channelItems.first() as ItemState.ChannelItemState
assertFalse(channelItem.isMuted)
assertFalse(channelItem.isUserMuted)
}

@Test
fun `Given a group channel with a muted user Should not mark the channel item as muted`() = runTest {
val groupChannel = Channel(
type = "messaging",
id = "groupChannel",
members = listOf(
Member(user = currentUser),
Member(user = otherUser),
Member(user = User(id = "thirdUser")),
),
)
val viewModel = Fixture()
.givenCurrentUser(currentUser)
.givenChannelsQuery()
.givenChannelsState(
channelsStateData = ChannelsStateData.Result(listOf(groupChannel)),
loading = false,
)
.givenChannelMutes()
.givenUserMutes(listOf(otherUserMute))
.givenTypingChannels()
.get(this)

val channelItem = viewModel.channelsState.channelItems.first() as ItemState.ChannelItemState
assertFalse(channelItem.isMuted)
assertFalse(channelItem.isUserMuted)
}

@Test
fun `Given channel list in content state When selecting a channel and dismissing the menu Should hide the menu`() =
runTest {
Expand Down Expand Up @@ -569,6 +662,10 @@ internal class ChannelListViewModelTest {
whenever(globalState.channelMutes) doReturn MutableStateFlow(channelMutes)
}

fun givenUserMutes(userMutes: List<Mute> = emptyList()) = apply {
whenever(globalState.muted) doReturn MutableStateFlow(userMutes)
}

fun givenTypingChannels(typingChannels: Map<String, TypingEvent> = emptyMap()) = apply {
whenever(globalState.typingChannels) doReturn MutableStateFlow(typingChannels)
}
Expand Down Expand Up @@ -649,6 +746,9 @@ internal class ChannelListViewModelTest {
)
private val querySort = QuerySortByField.descByName<Channel>("lastUpdated")

private val currentUser = User(id = "currentUser")
private val otherUser = User(id = "otherUser")

private val channel1: Channel = Channel(
type = "messaging",
id = "channel1",
Expand All @@ -657,5 +757,20 @@ internal class ChannelListViewModelTest {
type = "messaging",
id = "channel2",
)
private val directChannel = Channel(
type = "messaging",
id = "!members-currentUser-otherUser",
members = listOf(
Member(user = currentUser),
Member(user = otherUser),
),
)
private val otherUserMute = Mute(
user = currentUser,
target = otherUser,
createdAt = Date(),
updatedAt = Date(),
expires = null,
)
}
}
Loading