RoomMembers: change the API again..
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ class RoomUserListDataSource @Inject constructor(
|
||||
.dropWhile { it !is MatrixRoomMembersState.Ready }
|
||||
.first()
|
||||
.roomMembers()
|
||||
.orEmpty()
|
||||
val filteredMembers = if (query.isBlank()) {
|
||||
roomMembers
|
||||
} else {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user