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 @@ -30,6 +30,7 @@ import io.getstream.chat.android.client.events.DraftMessageUpdatedEvent
import io.getstream.chat.android.client.events.MarkAllReadEvent
import io.getstream.chat.android.client.events.MemberAddedEvent
import io.getstream.chat.android.client.events.MemberRemovedEvent
import io.getstream.chat.android.client.events.MemberUpdatedEvent
import io.getstream.chat.android.client.events.MessageDeliveredEvent
import io.getstream.chat.android.client.events.MessageReadEvent
import io.getstream.chat.android.client.events.MessageUpdatedEvent
Expand Down Expand Up @@ -440,6 +441,26 @@ public fun randomMemberAddedEvent(
)
}

public fun randomMemberUpdatedEvent(
createdAt: Date = Date(),
user: User = randomUser(),
cid: String = randomCID(),
channelType: String = randomString(),
channelId: String = randomString(),
member: Member = randomMember(),
): MemberUpdatedEvent {
return MemberUpdatedEvent(
type = EventType.MEMBER_UPDATED,
createdAt = createdAt,
rawCreatedAt = streamFormatter.format(createdAt),
user = user,
cid = cid,
channelType = channelType,
channelId = channelId,
member = member,
)
}

public fun randomNotificationAddedToChannelEvent(
createdAt: Date = Date(),
cid: String = randomCID(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ public open class DefaultChatEventHandler(
}

private fun addIfMembershipUpdated(channel: Channel?, member: Member): EventHandlingResult {
return if (channel?.membership?.getUserId() == member.getUserId()) {
// A membership update must not resurface a hidden channel
return if (channel?.membership?.getUserId() == member.getUserId() && channel.hidden != true) {
Comment thread
andremion marked this conversation as resolved.
EventHandlingResult.Add(
channel.updateMembership(member)
.updateMember(member),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,16 @@ internal class GroupAwareChatEventHandler(
* The membership guard prevents a `ChannelUpdatedEvent` from (re-)adding a channel the user
* has already left — relies on [cachedChannel] (in-memory state after `member.removed` is
* applied) rather than `event.channel.membership`, which is not guaranteed on `channel.updated`.
*
* The hidden guard does the same for a channel the user has hidden: `hidden` is per-user and
* absent from `channel.updated` payloads, so it is read from [cachedChannel], which has already
* absorbed a preceding `channel.hidden` in the same batch. A hidden channel stays out of the
* list until a new message or `channel.visible` clears the flag.
*/
private fun routeByGroup(channel: Channel, cachedChannel: Channel?): EventHandlingResult {
val belongsHere = channelBelongsHere(channel) && isCurrentUserMember(cachedChannel)
val belongsHere = channelBelongsHere(channel) &&
isCurrentUserMember(cachedChannel) &&
cachedChannel?.hidden != true
val isInList = channels.value?.containsKey(channel.cid) == true
return when {
belongsHere && !isInList -> EventHandlingResult.Add(channel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ internal class ChannelMutableState(
hidden = hidden.value,
pinnedMessages = sortedPinnedMessages.value,
activeLiveLocations = activeLiveLocations.value,
).syncUnreadCountWithReads()
).syncUnreadCountWithReads(userFlow.value?.id)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ internal class GroupAwareChatEventHandlerTest {
fun `Given channel belongs to this group and is not cached When ChannelUpdatedEvent arrives Should add`() {
val currentUser = randomUser()
val channel = randomChannel(extraData = mapOf("group" to "vip"))
val cachedChannel = channel.copy(membership = randomMember(user = currentUser))
val cachedChannel = channel.copy(membership = randomMember(user = currentUser), hidden = false)
val handler = handlerFor(
groupKey = "vip",
cachedChannels = emptyMap(),
Expand All @@ -62,7 +62,7 @@ internal class GroupAwareChatEventHandlerTest {
fun `Given channel belongs to this group and is already cached When ChannelUpdatedEvent arrives Should skip`() {
val currentUser = randomUser()
val channel = randomChannel(extraData = mapOf("group" to "vip"))
val cachedChannel = channel.copy(membership = randomMember(user = currentUser))
val cachedChannel = channel.copy(membership = randomMember(user = currentUser), hidden = false)
val handler = handlerFor(
groupKey = "vip",
cachedChannels = mapOf(channel.cid to channel),
Expand Down Expand Up @@ -101,7 +101,7 @@ internal class GroupAwareChatEventHandlerTest {
fun `Given handler is for the all group When ChannelUpdatedEvent arrives Should always add`() {
val currentUser = randomUser()
val channel = randomChannel(extraData = mapOf("group" to "vip"))
val cachedChannel = channel.copy(membership = randomMember(user = currentUser))
val cachedChannel = channel.copy(membership = randomMember(user = currentUser), hidden = false)
val handler = handlerFor(
groupKey = "all",
cachedChannels = emptyMap(),
Expand Down Expand Up @@ -238,6 +238,56 @@ internal class GroupAwareChatEventHandlerTest {
assertEquals(EventHandlingResult.Remove(channel.cid), result)
}

@Test
fun `Given cached channel is hidden When ChannelUpdatedEvent moves channel into this group Should skip`() {
val currentUser = randomUser()
val channel = randomChannel(extraData = mapOf("group" to "vip"))
val cachedChannel = channel.copy(membership = randomMember(user = currentUser), hidden = true)
val handler = handlerFor(
groupKey = "vip",
cachedChannels = emptyMap(),
currentUser = currentUser,
)
val event = randomChannelUpdatedEvent(cid = channel.cid, channel = channel)

val result = handler.handleChatEvent(event, Filters.neutral(), cachedChannel = cachedChannel)

assertEquals(EventHandlingResult.Skip, result)
}

@Test
fun `Given cached channel is hidden and is in the list When ChannelUpdatedEvent arrives Should remove`() {
val currentUser = randomUser()
val channel = randomChannel(extraData = mapOf("group" to "vip"))
val cachedChannel = channel.copy(membership = randomMember(user = currentUser), hidden = true)
val handler = handlerFor(
groupKey = "vip",
cachedChannels = mapOf(channel.cid to channel),
currentUser = currentUser,
)
val event = randomChannelUpdatedEvent(cid = channel.cid, channel = channel)

val result = handler.handleChatEvent(event, Filters.neutral(), cachedChannel = cachedChannel)

assertEquals(EventHandlingResult.Remove(channel.cid), result)
}

@Test
fun `Given no connected user When ChannelUpdatedEvent arrives Should skip`() {
val channel = randomChannel(extraData = mapOf("group" to "vip"))
val cachedChannel = channel.copy(membership = randomMember(), hidden = false)
val handler = handlerFor(
groupKey = "vip",
cachedChannels = emptyMap(),
currentUser = null,
)
val event = randomChannelUpdatedEvent(cid = channel.cid, channel = channel)

val result = handler.handleChatEvent(event, Filters.neutral(), cachedChannel = cachedChannel)

assertEquals(EventHandlingResult.Skip, result)
}

@Test
fun `Given current user has a connection but no cachedChannel When ChannelUpdatedEvent arrives Should skip`() {
val currentUser = randomUser()
Expand Down Expand Up @@ -272,7 +322,7 @@ internal class GroupAwareChatEventHandlerTest {
fun `Given a custom resolver that reads a different field When ChannelUpdatedEvent arrives Should use custom field`() {
val currentUser = randomUser()
val channel = randomChannel(extraData = mapOf("tier" to "vip"))
val cachedChannel = channel.copy(membership = randomMember(user = currentUser))
val cachedChannel = channel.copy(membership = randomMember(user = currentUser), hidden = false)
val customResolver = ChannelGroupResolver { ch ->
setOfNotNull(ch.extraData["tier"] as? String)
}
Expand Down
Loading
Loading