Reload room member list when active members count changes (#5129)

This commit is contained in:
Jorge Martin Espinosa 2025-08-06 12:37:52 +02:00 committed by GitHub
parent 955263bee1
commit 2a42a71490
2 changed files with 70 additions and 2 deletions

View file

@ -38,8 +38,10 @@ import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentMap import kotlinx.collections.immutable.toPersistentMap
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import javax.inject.Inject import javax.inject.Inject
@ -64,6 +66,11 @@ class RoomMemberListPresenter @Inject constructor(
val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
val canInvite by room.canInviteAsState(syncUpdateFlow.value) val canInvite by room.canInviteAsState(syncUpdateFlow.value)
val roomModerationState = roomMembersModerationPresenter.present() val roomModerationState = roomMembersModerationPresenter.present()
val activeRoomMemberCount by produceState(0L) {
room.roomInfoFlow.map { it.activeMembersCount }
.distinctUntilChanged()
.collect { value = it }
}
val roomMemberIdentityStates by produceState(persistentMapOf<UserId, IdentityState>()) { val roomMemberIdentityStates by produceState(persistentMapOf<UserId, IdentityState>()) {
room.roomMemberIdentityStateChange(waitForEncryption = true) room.roomMemberIdentityStateChange(waitForEncryption = true)
@ -73,8 +80,8 @@ class RoomMemberListPresenter @Inject constructor(
.launchIn(this) .launchIn(this)
} }
// Ensure we load the latest data when entering this screen // Update the room members when the screen is loaded or the active member count changes
LaunchedEffect(Unit) { LaunchedEffect(activeRoomMemberCount) {
room.updateMembers() room.updateMembers()
} }

View file

@ -23,12 +23,20 @@ import io.element.android.libraries.matrix.test.room.FakeBaseRoom
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.testCoroutineDispatchers import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.time.withTimeout
import kotlinx.coroutines.withTimeout
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import kotlin.time.Duration.Companion.seconds
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
class RoomMemberListPresenterTest { class RoomMemberListPresenterTest {
@ -67,6 +75,59 @@ class RoomMemberListPresenterTest {
} }
} }
@Test
fun `member loading is done automatically when RoomInfo's activeMemberCount changes`() = runTest {
val reloadMembersMutex = Mutex()
val updateMembersLambda = lambdaRecorder<Unit> {
if (reloadMembersMutex.isLocked) {
reloadMembersMutex.unlock()
}
}
val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
updateMembersResult = updateMembersLambda,
canInviteResult = { Result.success(true) }
).apply {
// Needed to avoid discarding the loaded members as a partial and invalid result
givenRoomInfo(aRoomInfo(joinedMembersCount = 2))
}
)
val presenter = createPresenter(joinedRoom = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.roomMembers.isLoading()).isTrue()
room.givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
// Skip item while the new members state is processed
skipItems(1)
val loadedMembersState = awaitItem()
assertThat(loadedMembersState.roomMembers.isLoading()).isFalse()
assertThat(loadedMembersState.roomMembers.dataOrNull()?.joined).isNotEmpty()
// Assert no events are emitted only with that change
expectNoEvents()
// This will only progress if the `Room.updateMembers()` function is called, triggered by the RoomInfo change
withTimeout(10.seconds) {
reloadMembersMutex.withLock {
launch { room.givenRoomInfo(aRoomInfo(activeMembersCount = 0L)) }
}
}
// Wait for the update to be processed
skipItems(1)
// Update the room members state as `Room.updateMembers()` would have done with the actual implementation
room.givenRoomMembersState(RoomMembersState.Ready(persistentListOf()))
// Wait for another update
skipItems(1)
// The members should be reloaded now
assertThat(awaitItem().roomMembers.dataOrNull()?.joined).isEmpty()
}
}
@Test @Test
fun `open search`() = runTest { fun `open search`() = runTest {
val presenter = createPresenter( val presenter = createPresenter(