From d7cd8a2915acd7aca6769518d82b1107248cb06c Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 28 Apr 2023 15:04:33 +0200 Subject: [PATCH] RoomMembers: change the API again.. --- .../roomdetails/impl/RoomDetailsPresenter.kt | 19 +++--- .../roomdetails/impl/RoomDetailsView.kt | 2 +- .../impl/members/RoomMemberListPresenter.kt | 7 --- .../impl/members/RoomUserListDataSource.kt | 1 + .../details/RoomMemberDetailsPresenter.kt | 14 +++-- .../roomdetails/RoomDetailsPresenterTests.kt | 8 +-- .../RoomMemberDetailsPresenterTests.kt | 22 ++++--- .../libraries/matrix/api/room/MatrixRoom.kt | 20 ------- .../matrix/api/room/MatrixRoomMembersState.kt | 12 ++-- .../matrix/impl/room/RustMatrixRoom.kt | 16 ++--- .../impl/timeline/RustMatrixTimeline.kt | 3 +- .../matrix/ui/room/MatrixRoomMembers.kt | 58 +++++++++++++++++++ 12 files changed, 109 insertions(+), 73 deletions(-) create mode 100644 libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembers.kt diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 3479adbb81..aa46dacd6d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -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(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) } } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index 9b3f83d456..dbee610dba 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -117,7 +117,7 @@ fun RoomDetailsView( } if (state.roomType is RoomDetailsType.Room) { - val memberCount = (state.memberCount as? Async.Success)?.state + val memberCount = state.memberCount.dataOrNull() MembersSection( memberCount = memberCount, isLoading = state.memberCount.isLoading(), diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index 4d87c0e158..8d66d60bc2 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -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, ) } - } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomUserListDataSource.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomUserListDataSource.kt index 9133db7688..e0559f3caf 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomUserListDataSource.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomUserListDataSource.kt @@ -41,6 +41,7 @@ class RoomUserListDataSource @Inject constructor( .dropWhile { it !is MatrixRoomMembersState.Ready } .first() .roomMembers() + .orEmpty() val filteredMembers = if (query.isBlank()) { roomMembers } else { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt index e51205728e..904d4f183b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt @@ -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(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) { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt index bd0b5906cb..585a61f05d 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt @@ -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() } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt index 3731c910fc..13eb28ca85 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt @@ -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 { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 41cd874098..35451874aa 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -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 } - -fun MatrixRoom.getMemberFlow(userId: UserId): Flow { - return membersStateFlow.map { state -> - state.roomMembers().find { - it.userId == userId - } - } -} - -fun MatrixRoom.getDmMemberFlow(): Flow { - return membersStateFlow.map { state -> - val members = state.roomMembers() - if (members.size == 2 && isDirect && isEncrypted) { - members.find { it.userId != this.sessionId } - } else { - null - } - } -} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt index 319a5f7605..4e41fd43ba 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt @@ -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? = null) : MatrixRoomMembersState + data class Error(val failure: Throwable, val prevRoomMembers: List? = null) : MatrixRoomMembersState data class Ready(val roomMembers: List) : MatrixRoomMembersState } -fun MatrixRoomMembersState.roomMembers(): List { +fun MatrixRoomMembersState.roomMembers(): List? { return when (this) { is MatrixRoomMembersState.Ready -> roomMembers - else -> emptyList() + is MatrixRoomMembersState.Pending -> prevRoomMembers + is MatrixRoomMembersState.Error -> prevRoomMembers + else -> null } } + + diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 1e75867215..9caba227ce 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -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 = 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") - } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt index bffaa30886..6f64db76ef 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt @@ -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 { diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembers.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembers.kt new file mode 100644 index 0000000000..2df7c1a152 --- /dev/null +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembers.kt @@ -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 { + val roomMembersState by membersStateFlow.collectAsState() + val roomMembers = roomMembersState.roomMembers() + return remember(roomMembers) { + derivedStateOf { + roomMembers?.find { + it.userId == userId + } + } + } +} + +@Composable +fun MatrixRoom.directRoomMember(): State { + 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 + } + } + } +}