RoomMembers: change the API again..

This commit is contained in:
ganfra
2023-04-28 15:04:33 +02:00
parent 16a43befcc
commit d7cd8a2915
12 changed files with 109 additions and 73 deletions

View File

@@ -17,6 +17,7 @@
package io.element.android.features.roomdetails.impl
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
@@ -33,7 +34,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.getDmMemberFlow
import io.element.android.libraries.matrix.ui.room.directRoomMember
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -54,14 +55,14 @@ class RoomDetailsPresenter @Inject constructor(
val error = remember {
mutableStateOf<RoomDetailsError?>(null)
}
LaunchedEffect(Unit) {
room.updateMembers()
}
val membersState by room.membersStateFlow.collectAsState()
val memberCount by getMemberCount(membersState)
val dmMemberState by room.getDmMemberFlow()
.collectAsState(initial = null, context = coroutineDispatchers.computation)
val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMemberState)
val roomType = getRoomType(dmMemberState)
val dmMember by room.directRoomMember()
val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember)
val roomType = getRoomType(dmMember)
fun handleEvents(event: RoomDetailsEvent) {
when (event) {
@@ -119,9 +120,9 @@ class RoomDetailsPresenter @Inject constructor(
derivedStateOf {
when (membersState) {
MatrixRoomMembersState.Unknown -> Async.Uninitialized
MatrixRoomMembersState.Pending -> Async.Loading()
is MatrixRoomMembersState.Pending -> Async.Loading(prevState = membersState.prevRoomMembers?.size)
is MatrixRoomMembersState.Error -> Async.Failure(membersState.failure, prevState = membersState.prevRoomMembers?.size)
is MatrixRoomMembersState.Ready -> Async.Success(membersState.roomMembers.size)
is MatrixRoomMembersState.Error -> Async.Failure(membersState.failure)
}
}
}

View File

@@ -117,7 +117,7 @@ fun RoomDetailsView(
}
if (state.roomType is RoomDetailsType.Room) {
val memberCount = (state.memberCount as? Async.Success<Int>)?.state
val memberCount = state.memberCount.dataOrNull()
MembersSection(
memberCount = memberCount,
isLoading = state.memberCount.isLoading(),

View File

@@ -18,7 +18,6 @@ package io.element.android.features.roomdetails.impl.members
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import io.element.android.features.userlist.api.SelectionMode
@@ -30,14 +29,9 @@ import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.getMemberFlow
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Named
@@ -74,6 +68,5 @@ class RoomMemberListPresenter @Inject constructor(
userListState = userListState,
)
}
}

View File

@@ -41,6 +41,7 @@ class RoomUserListDataSource @Inject constructor(
.dropWhile { it !is MatrixRoomMembersState.Ready }
.first()
.roomMembers()
.orEmpty()
val filteredMembers = if (query.isBlank()) {
roomMembers
} else {

View File

@@ -17,8 +17,8 @@
package io.element.android.features.roomdetails.impl.members.details
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
@@ -33,9 +33,10 @@ import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.getMemberFlow
import io.element.android.libraries.matrix.ui.room.roomMember
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
class RoomMemberDetailsPresenter @AssistedInject constructor(
private val client: MatrixClient,
@@ -51,11 +52,14 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
override fun present(): RoomMemberDetailsState {
val coroutineScope = rememberCoroutineScope()
var confirmationDialog by remember { mutableStateOf<ConfirmationDialog?>(null) }
val roomMember by room.getMemberFlow(roomMemberId).collectAsState(initial = null)
val isBlocked = remember(roomMember?.isIgnored) {
val roomMember by room.roomMember(roomMemberId)
// the room member is not really live...
val isBlocked = remember {
mutableStateOf(roomMember?.isIgnored.orFalse())
}
LaunchedEffect(Unit) {
room.updateMembers()
}
fun handleEvents(event: RoomMemberDetailsEvents) {
when (event) {

View File

@@ -96,6 +96,7 @@ class RoomDetailsPresenterTests {
room.givenRoomMembersState(MatrixRoomMembersState.Ready(emptyList()))
val finalState = awaitItem()
Truth.assertThat(finalState.memberCount).isEqualTo(Async.Success(0))
cancelAndIgnoreRemainingEvents()
}
}
@@ -129,12 +130,7 @@ class RoomDetailsPresenterTests {
presenter.present()
}.test {
val initialState = awaitItem()
// It's not configured yet in the first iteration
Truth.assertThat(initialState.roomType).isEqualTo(RoomDetailsType.Room)
// Once updated, the RoomDetailsType becomes 'Dm'
val updatedState = awaitItem()
Truth.assertThat(updatedState.roomType).isEqualTo(RoomDetailsType.Dm(otherRoomMember))
Truth.assertThat(initialState.roomType).isEqualTo(RoomDetailsType.Dm(otherRoomMember))
cancelAndIgnoreRemainingEvents()
}

View File

@@ -26,6 +26,7 @@ import io.element.android.features.roomdetails.aRoomMember
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -37,12 +38,13 @@ class RoomMemberDetailsPresenterTests {
@Test
fun `present - returns the room member's data, then updates it if needed`() = runTest {
val roomMember = aRoomMember(displayName = "Alice")
val room = aMatrixRoom().apply {
givenUserDisplayNameResult(Result.success("A custom name"))
givenUserAvatarUrlResult(Result.success("A custom avatar"))
givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(roomMember)))
}
val roomMember = aRoomMember(displayName = "Alice")
val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember)
val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember.userId)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
@@ -60,12 +62,13 @@ class RoomMemberDetailsPresenterTests {
@Test
fun `present - will recover when retrieving room member details fails`() = runTest {
val roomMember = aRoomMember(displayName = "Alice")
val room = aMatrixRoom().apply {
givenUserDisplayNameResult(Result.failure(Throwable()))
givenUserAvatarUrlResult(Result.failure(Throwable()))
givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(roomMember)))
}
val roomMember = aRoomMember(displayName = "Alice")
val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember)
val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember.userId)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
@@ -79,12 +82,13 @@ class RoomMemberDetailsPresenterTests {
@Test
fun `present - will fallback to original data if the updated data is null`() = runTest {
val roomMember = aRoomMember(displayName = "Alice")
val room = aMatrixRoom().apply {
givenUserDisplayNameResult(Result.success(null))
givenUserAvatarUrlResult(Result.success(null))
givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(roomMember)))
}
val roomMember = aRoomMember(displayName = "Alice")
val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember)
val presenter =RoomMemberDetailsPresenter(matrixClient, room, roomMember.userId)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
@@ -100,7 +104,7 @@ class RoomMemberDetailsPresenterTests {
fun `present - BlockUser needing confirmation displays confirmation dialog`() = runTest {
val room = aMatrixRoom()
val roomMember = aRoomMember()
val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember)
val presenter =RoomMemberDetailsPresenter(matrixClient, room, roomMember.userId)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
@@ -121,7 +125,7 @@ class RoomMemberDetailsPresenterTests {
fun `present - BlockUser and UnblockUser without confirmation change the 'blocked' state`() = runTest {
val room = aMatrixRoom()
val roomMember = aRoomMember()
val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember)
val presenter =RoomMemberDetailsPresenter(matrixClient, room, roomMember.userId)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
@@ -138,7 +142,7 @@ class RoomMemberDetailsPresenterTests {
fun `present - UnblockUser needing confirmation displays confirmation dialog`() = runTest {
val room = aMatrixRoom()
val roomMember = aRoomMember()
val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember)
val presenter =RoomMemberDetailsPresenter(matrixClient, room, roomMember.userId)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {

View File

@@ -23,7 +23,6 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import java.io.Closeable
interface MatrixRoom : Closeable {
@@ -74,22 +73,3 @@ interface MatrixRoom : Closeable {
suspend fun rejectInvitation(): Result<Unit>
}
fun MatrixRoom.getMemberFlow(userId: UserId): Flow<RoomMember?> {
return membersStateFlow.map { state ->
state.roomMembers().find {
it.userId == userId
}
}
}
fun MatrixRoom.getDmMemberFlow(): Flow<RoomMember?> {
return membersStateFlow.map { state ->
val members = state.roomMembers()
if (members.size == 2 && isDirect && isEncrypted) {
members.find { it.userId != this.sessionId }
} else {
null
}
}
}

View File

@@ -18,14 +18,18 @@ package io.element.android.libraries.matrix.api.room
sealed interface MatrixRoomMembersState {
object Unknown : MatrixRoomMembersState
object Pending : MatrixRoomMembersState
data class Error(val failure: Throwable) : MatrixRoomMembersState
data class Pending(val prevRoomMembers: List<RoomMember>? = null) : MatrixRoomMembersState
data class Error(val failure: Throwable, val prevRoomMembers: List<RoomMember>? = null) : MatrixRoomMembersState
data class Ready(val roomMembers: List<RoomMember>) : MatrixRoomMembersState
}
fun MatrixRoomMembersState.roomMembers(): List<RoomMember> {
fun MatrixRoomMembersState.roomMembers(): List<RoomMember>? {
return when (this) {
is MatrixRoomMembersState.Ready -> roomMembers
else -> emptyList()
is MatrixRoomMembersState.Pending -> prevRoomMembers
is MatrixRoomMembersState.Error -> prevRoomMembers
else -> null
}
}

View File

@@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.roomMembers
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
import kotlinx.coroutines.CoroutineScope
@@ -32,14 +33,13 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.onSubscription
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.SlidingSyncRoom
import org.matrix.rustcomponents.sdk.UpdateSummary
import org.matrix.rustcomponents.sdk.genTransactionId
import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
import org.matrix.rustcomponents.sdk.use
import org.matrix.rustcomponents.sdk.RoomMember as RustRoomMember
class RustMatrixRoom(
override val sessionId: SessionId,
@@ -128,13 +128,15 @@ class RustMatrixRoom(
get() = innerRoom.isDirect()
override suspend fun updateMembers(): Result<Unit> = withContext(coroutineDispatchers.io) {
_membersStateFlow.value = MatrixRoomMembersState.Pending
val currentState = _membersStateFlow.value
val currentMembers = currentState.roomMembers()
_membersStateFlow.value = MatrixRoomMembersState.Pending(prevRoomMembers = currentMembers)
runCatching {
innerRoom.members().map(RoomMemberMapper::map)
}.map {
_membersStateFlow.value = MatrixRoomMembersState.Ready(it)
}.onFailure {
_membersStateFlow.value = MatrixRoomMembersState.Error(it)
_membersStateFlow.value = MatrixRoomMembersState.Error(prevRoomMembers = currentMembers, failure = it)
}
}
@@ -201,10 +203,4 @@ class RustMatrixRoom(
}
}
private fun findRoomMember(userId: UserId, action: (RustRoomMember).() -> Unit) {
return innerRoom.members()
.find { it.userId() == userId.value }
?.use(action)
?: error("No member with userId $userId exists in room $roomId")
}
}

View File

@@ -153,8 +153,7 @@ class RustMatrixTimeline(
RequiredState(key = "m.room.topic", value = ""),
RequiredState(key = "m.room.join_rules", value = ""),
),
//TODO allow configuration
timelineLimit = 20.toUInt()
timelineLimit = null
)
val result = slidingSyncRoom.subscribeAndAddTimelineListener(timelineListener, settings)
launch {

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.ui.room
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.roomMembers
@Composable
fun MatrixRoom.roomMember(userId: UserId): State<RoomMember?> {
val roomMembersState by membersStateFlow.collectAsState()
val roomMembers = roomMembersState.roomMembers()
return remember(roomMembers) {
derivedStateOf {
roomMembers?.find {
it.userId == userId
}
}
}
}
@Composable
fun MatrixRoom.directRoomMember(): State<RoomMember?> {
val roomMembersState by membersStateFlow.collectAsState()
val roomMembers = roomMembersState.roomMembers()
return remember(roomMembers) {
derivedStateOf {
if (roomMembers == null) {
null
} else if (roomMembers.size == 2 && isDirect && isEncrypted) {
roomMembers.find { it.userId != this.sessionId }
} else {
null
}
}
}
}