Skip to content

Commit 0a6ceb0

Browse files
authored
Show muted icon on channel items for DMs with muted users (#6302)
1 parent b5937ec commit 0a6ceb0

2 files changed

Lines changed: 111 additions & 4 deletions

File tree

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModel.kt

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,14 @@ import io.getstream.chat.android.models.DraftMessage
4545
import io.getstream.chat.android.models.FilterObject
4646
import io.getstream.chat.android.models.Filters
4747
import io.getstream.chat.android.models.Message
48+
import io.getstream.chat.android.models.Mute
4849
import io.getstream.chat.android.models.TypingEvent
4950
import io.getstream.chat.android.models.User
5051
import io.getstream.chat.android.models.querysort.QuerySortByField
5152
import io.getstream.chat.android.models.querysort.QuerySorter
5253
import io.getstream.chat.android.ui.common.state.channels.actions.ChannelAction
5354
import io.getstream.chat.android.ui.common.utils.extensions.defaultChannelListFilter
55+
import io.getstream.chat.android.ui.common.utils.extensions.isOneToOne
5456
import io.getstream.log.taggedLogger
5557
import io.getstream.result.call.toUnitCall
5658
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -200,7 +202,7 @@ public class ChannelListViewModel(
200202
.flatMapLatest { it.channelMutes }
201203
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
202204

203-
private val globalMuted: StateFlow<List<io.getstream.chat.android.models.Mute>> = globalState
205+
private val globalMuted: StateFlow<List<Mute>> = globalState
204206
.flatMapLatest { it.muted }
205207
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
206208

@@ -432,7 +434,8 @@ public class ChannelListViewModel(
432434
channelMutes,
433435
typingChannels,
434436
channelDraftMessages,
435-
) { state, channelMutes, typingChannels, channelDraftMessages ->
437+
globalMuted,
438+
) { state, channelMutes, typingChannels, channelDraftMessages, userMutes ->
436439
when (state) {
437440
ChannelsStateData.NoQueryActive,
438441
ChannelsStateData.Loading,
@@ -457,6 +460,8 @@ public class ChannelListViewModel(
457460
channelItems = createChannelItems(
458461
channels = state.channels,
459462
channelMutes = channelMutes,
463+
userMutes = userMutes,
464+
currentUser = user.value,
460465
typingEvents = typingChannels,
461466
draftMessages = channelDraftMessages.takeIf { isDraftMessageEnabled } ?: emptyMap(),
462467
),
@@ -800,25 +805,41 @@ public class ChannelListViewModel(
800805
*
801806
* @param channels The channels to show.
802807
* @param channelMutes The list of channels muted for the current user.
803-
*
808+
* @param userMutes The list of users muted by the current user.
809+
* @param currentUser The currently logged in user.
804810
*/
811+
@Suppress("LongParameterList")
805812
private fun createChannelItems(
806813
channels: List<Channel>,
807814
channelMutes: List<ChannelMute>,
815+
userMutes: List<Mute>,
816+
currentUser: User?,
808817
typingEvents: Map<String, TypingEvent>,
809818
draftMessages: Map<String, DraftMessage>,
810819
): List<ItemState.ChannelItemState> {
811820
val mutedChannelIds = channelMutes.map { channelMute -> channelMute.channel?.cid }.toSet()
821+
val mutedUserIds = userMutes.mapNotNullTo(mutableSetOf()) { it.target?.id }
812822
return channels.map {
813823
ItemState.ChannelItemState(
814824
channel = it,
815-
isMuted = it.cid in mutedChannelIds,
825+
isMuted = it.cid in mutedChannelIds || it.isOneToOneMutedByUser(currentUser, mutedUserIds),
816826
typingUsers = typingEvents[it.cid]?.users ?: emptyList(),
817827
draftMessage = draftMessages[it.cid],
818828
)
819829
}
820830
}
821831

832+
/**
833+
* Checks if a 1:1 channel is muted via user mute (i.e. the other member is muted).
834+
*/
835+
private fun Channel.isOneToOneMutedByUser(currentUser: User?, mutedUserIds: Set<String>) =
836+
if (mutedUserIds.isEmpty() || currentUser == null || !isOneToOne(currentUser)) {
837+
false
838+
} else {
839+
val otherUser = members.find { it.user.id != currentUser.id }?.user
840+
otherUser != null && otherUser.id in mutedUserIds
841+
}
842+
822843
internal companion object {
823844
/**
824845
* Default value of number of channels to return when querying channels.

stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModelTest.kt

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ import io.getstream.chat.android.models.ChannelMute
3636
import io.getstream.chat.android.models.FilterObject
3737
import io.getstream.chat.android.models.Filters
3838
import io.getstream.chat.android.models.InitializationState
39+
import io.getstream.chat.android.models.Member
3940
import io.getstream.chat.android.models.Message
41+
import io.getstream.chat.android.models.Mute
4042
import io.getstream.chat.android.models.OrFilterObject
4143
import io.getstream.chat.android.models.SearchMessagesResult
4244
import io.getstream.chat.android.models.TypingEvent
@@ -191,6 +193,68 @@ internal class ChannelListViewModelTest {
191193
verify(chatClient).unmuteChannel("messaging", "channel1")
192194
}
193195

196+
@Test
197+
fun `Given a DM with a muted user Should mark the channel as muted`() = runTest {
198+
val viewModel = Fixture()
199+
.givenCurrentUser(currentUser)
200+
.givenChannelsQuery()
201+
.givenChannelsState(
202+
channelsStateData = ChannelsStateData.Result(listOf(directChannel)),
203+
loading = false,
204+
)
205+
.givenChannelMutes()
206+
.givenUserMutes(listOf(otherUserMute))
207+
.givenTypingChannels()
208+
.get(this)
209+
210+
val channelItem = viewModel.channelsState.channelItems.first() as ItemState.ChannelItemState
211+
assertTrue(channelItem.isMuted)
212+
}
213+
214+
@Test
215+
fun `Given a DM without a muted user Should not mark the channel as muted`() = runTest {
216+
val viewModel = Fixture()
217+
.givenCurrentUser(currentUser)
218+
.givenChannelsQuery()
219+
.givenChannelsState(
220+
channelsStateData = ChannelsStateData.Result(listOf(directChannel)),
221+
loading = false,
222+
)
223+
.givenChannelMutes()
224+
.givenTypingChannels()
225+
.get(this)
226+
227+
val channelItem = viewModel.channelsState.channelItems.first() as ItemState.ChannelItemState
228+
assertFalse(channelItem.isMuted)
229+
}
230+
231+
@Test
232+
fun `Given a group channel with a muted user Should not mark the channel as muted`() = runTest {
233+
val groupChannel = Channel(
234+
type = "messaging",
235+
id = "groupChannel",
236+
members = listOf(
237+
Member(user = currentUser),
238+
Member(user = otherUser),
239+
Member(user = User(id = "thirdUser")),
240+
),
241+
)
242+
val viewModel = Fixture()
243+
.givenCurrentUser(currentUser)
244+
.givenChannelsQuery()
245+
.givenChannelsState(
246+
channelsStateData = ChannelsStateData.Result(listOf(groupChannel)),
247+
loading = false,
248+
)
249+
.givenChannelMutes()
250+
.givenUserMutes(listOf(otherUserMute))
251+
.givenTypingChannels()
252+
.get(this)
253+
254+
val channelItem = viewModel.channelsState.channelItems.first() as ItemState.ChannelItemState
255+
assertFalse(channelItem.isMuted)
256+
}
257+
194258
@Test
195259
fun `Given channel list in content state When selecting a channel and dismissing the menu Should hide the menu`() =
196260
runTest {
@@ -569,6 +633,10 @@ internal class ChannelListViewModelTest {
569633
whenever(globalState.channelMutes) doReturn MutableStateFlow(channelMutes)
570634
}
571635

636+
fun givenUserMutes(userMutes: List<Mute> = emptyList()) = apply {
637+
whenever(globalState.muted) doReturn MutableStateFlow(userMutes)
638+
}
639+
572640
fun givenTypingChannels(typingChannels: Map<String, TypingEvent> = emptyMap()) = apply {
573641
whenever(globalState.typingChannels) doReturn MutableStateFlow(typingChannels)
574642
}
@@ -649,6 +717,9 @@ internal class ChannelListViewModelTest {
649717
)
650718
private val querySort = QuerySortByField.descByName<Channel>("lastUpdated")
651719

720+
private val currentUser = User(id = "currentUser")
721+
private val otherUser = User(id = "otherUser")
722+
652723
private val channel1: Channel = Channel(
653724
type = "messaging",
654725
id = "channel1",
@@ -657,5 +728,20 @@ internal class ChannelListViewModelTest {
657728
type = "messaging",
658729
id = "channel2",
659730
)
731+
private val directChannel = Channel(
732+
type = "messaging",
733+
id = "!members-currentUser-otherUser",
734+
members = listOf(
735+
Member(user = currentUser),
736+
Member(user = otherUser),
737+
),
738+
)
739+
private val otherUserMute = Mute(
740+
user = currentUser,
741+
target = otherUser,
742+
createdAt = Date(),
743+
updatedAt = Date(),
744+
expires = null,
745+
)
660746
}
661747
}

0 commit comments

Comments
 (0)