Skip to content

Commit 1343aae

Browse files
authored
Reload room member list when active members count changes (#5129)
1 parent 1a33569 commit 1343aae

File tree

2 files changed

+70
-2
lines changed

2 files changed

+70
-2
lines changed

features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ import kotlinx.collections.immutable.ImmutableMap
3838
import kotlinx.collections.immutable.persistentMapOf
3939
import kotlinx.collections.immutable.toImmutableList
4040
import kotlinx.collections.immutable.toPersistentMap
41+
import kotlinx.coroutines.flow.distinctUntilChanged
4142
import kotlinx.coroutines.flow.first
4243
import kotlinx.coroutines.flow.launchIn
44+
import kotlinx.coroutines.flow.map
4345
import kotlinx.coroutines.flow.onEach
4446
import kotlinx.coroutines.withContext
4547
import javax.inject.Inject
@@ -64,6 +66,11 @@ class RoomMemberListPresenter @Inject constructor(
6466
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
6567
val canInvite by room.canInviteAsState(syncUpdateFlow.value)
6668
val roomModerationState = roomMembersModerationPresenter.present()
69+
val activeRoomMemberCount by produceState(0L) {
70+
room.roomInfoFlow.map { it.activeMembersCount }
71+
.distinctUntilChanged()
72+
.collect { value = it }
73+
}
6774

6875
val roomMemberIdentityStates by produceState(persistentMapOf<UserId, IdentityState>()) {
6976
room.roomMemberIdentityStateChange(waitForEncryption = true)
@@ -73,8 +80,8 @@ class RoomMemberListPresenter @Inject constructor(
7380
.launchIn(this)
7481
}
7582

76-
// Ensure we load the latest data when entering this screen
77-
LaunchedEffect(Unit) {
83+
// Update the room members when the screen is loaded or the active member count changes
84+
LaunchedEffect(activeRoomMemberCount) {
7885
room.updateMembers()
7986
}
8087

features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,20 @@ import io.element.android.libraries.matrix.test.room.FakeBaseRoom
2323
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
2424
import io.element.android.libraries.matrix.test.room.aRoomInfo
2525
import io.element.android.tests.testutils.WarmUpRule
26+
import io.element.android.tests.testutils.lambda.lambdaRecorder
2627
import io.element.android.tests.testutils.testCoroutineDispatchers
28+
import kotlinx.collections.immutable.persistentListOf
2729
import kotlinx.coroutines.ExperimentalCoroutinesApi
30+
import kotlinx.coroutines.launch
31+
import kotlinx.coroutines.sync.Mutex
32+
import kotlinx.coroutines.sync.withLock
2833
import kotlinx.coroutines.test.TestScope
2934
import kotlinx.coroutines.test.runTest
35+
import kotlinx.coroutines.time.withTimeout
36+
import kotlinx.coroutines.withTimeout
3037
import org.junit.Rule
3138
import org.junit.Test
39+
import kotlin.time.Duration.Companion.seconds
3240

3341
@ExperimentalCoroutinesApi
3442
class RoomMemberListPresenterTest {
@@ -67,6 +75,59 @@ class RoomMemberListPresenterTest {
6775
}
6876
}
6977

78+
@Test
79+
fun `member loading is done automatically when RoomInfo's activeMemberCount changes`() = runTest {
80+
val reloadMembersMutex = Mutex()
81+
val updateMembersLambda = lambdaRecorder<Unit> {
82+
if (reloadMembersMutex.isLocked) {
83+
reloadMembersMutex.unlock()
84+
}
85+
}
86+
val room = FakeJoinedRoom(
87+
baseRoom = FakeBaseRoom(
88+
updateMembersResult = updateMembersLambda,
89+
canInviteResult = { Result.success(true) }
90+
).apply {
91+
// Needed to avoid discarding the loaded members as a partial and invalid result
92+
givenRoomInfo(aRoomInfo(joinedMembersCount = 2))
93+
}
94+
)
95+
val presenter = createPresenter(joinedRoom = room)
96+
moleculeFlow(RecompositionMode.Immediate) {
97+
presenter.present()
98+
}.test {
99+
skipItems(1)
100+
val initialState = awaitItem()
101+
assertThat(initialState.roomMembers.isLoading()).isTrue()
102+
room.givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
103+
// Skip item while the new members state is processed
104+
skipItems(1)
105+
val loadedMembersState = awaitItem()
106+
assertThat(loadedMembersState.roomMembers.isLoading()).isFalse()
107+
assertThat(loadedMembersState.roomMembers.dataOrNull()?.joined).isNotEmpty()
108+
109+
// Assert no events are emitted only with that change
110+
expectNoEvents()
111+
112+
// This will only progress if the `Room.updateMembers()` function is called, triggered by the RoomInfo change
113+
withTimeout(10.seconds) {
114+
reloadMembersMutex.withLock {
115+
launch { room.givenRoomInfo(aRoomInfo(activeMembersCount = 0L)) }
116+
}
117+
}
118+
119+
// Wait for the update to be processed
120+
skipItems(1)
121+
122+
// Update the room members state as `Room.updateMembers()` would have done with the actual implementation
123+
room.givenRoomMembersState(RoomMembersState.Ready(persistentListOf()))
124+
// Wait for another update
125+
skipItems(1)
126+
// The members should be reloaded now
127+
assertThat(awaitItem().roomMembers.dataOrNull()?.joined).isEmpty()
128+
}
129+
}
130+
70131
@Test
71132
fun `open search`() = runTest {
72133
val presenter = createPresenter(

0 commit comments

Comments
 (0)