diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index f86b9952a8..f3a0ef9001 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -59,7 +59,7 @@ class RoomDetailsFlowNode @AssistedInject constructor( object RoomMemberList : NavTarget @Parcelize - data class RoomMemberDetails(val roomMember: RoomMember) : NavTarget + data class RoomMemberDetails(val roomMemberId: UserId) : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { @@ -74,14 +74,14 @@ class RoomDetailsFlowNode @AssistedInject constructor( } NavTarget.RoomMemberList -> { val roomMemberListCallback = object : RoomMemberListNode.Callback { - override fun openRoomMemberDetails(roomMember: RoomMember) { - backstack.push(NavTarget.RoomMemberDetails(roomMember)) + override fun openRoomMemberDetails(roomMemberId: UserId) { + backstack.push(NavTarget.RoomMemberDetails(roomMemberId)) } } createNode(buildContext, listOf(roomMemberListCallback)) } is NavTarget.RoomMemberDetails -> { - createNode(buildContext, listOf(RoomMemberDetailsNode.Inputs(navTarget.roomMember))) + createNode(buildContext, listOf(RoomMemberDetailsNode.Inputs(navTarget.roomMemberId))) } } } 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 bb4cae8423..3479adbb81 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 @@ -99,7 +99,7 @@ class RoomDetailsPresenter @Inject constructor( @Composable private fun roomMemberDetailsPresenter(dmMemberState: RoomMember?) = remember(dmMemberState) { dmMemberState?.let { roomMember -> - roomMembersDetailsPresenterFactory.create(roomMember) + roomMembersDetailsPresenterFactory.create(roomMember.userId) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModules.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModules.kt index 66fae2b50c..2cd3b7eb08 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModules.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModules.kt @@ -25,6 +25,7 @@ import io.element.android.features.roomdetails.impl.members.details.RoomMemberDe import io.element.android.features.userlist.api.UserListDataSource import io.element.android.libraries.di.RoomScope 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.RoomMember import javax.inject.Named @@ -48,8 +49,8 @@ object RoomMemberProvidesModule { room: MatrixRoom, ): RoomMemberDetailsPresenter.Factory { return object : RoomMemberDetailsPresenter.Factory { - override fun create(roomMember: RoomMember): RoomMemberDetailsPresenter { - return RoomMemberDetailsPresenter(matrixClient, room, roomMember) + override fun create(roomMemberId: UserId): RoomMemberDetailsPresenter { + return RoomMemberDetailsPresenter(matrixClient, room, roomMemberId) } } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt index 48743b66fa..5b1e7a72e0 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt @@ -26,6 +26,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.RoomMember @ContributesNode(RoomScope::class) @@ -36,14 +37,14 @@ class RoomMemberListNode @AssistedInject constructor( ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun openRoomMemberDetails(roomMember: RoomMember) + fun openRoomMemberDetails(roomMemberId: UserId) } private val callbacks = plugins() - private fun openRoomMemberDetails(roomMember: RoomMember) { + private fun openRoomMemberDetails(roomMemberId: UserId) { callbacks.forEach { - it.openRoomMemberDetails(roomMember) + it.openRoomMemberDetails(roomMemberId) } } 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 897e56728b..4d87c0e158 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 @@ -21,7 +21,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import io.element.android.features.userlist.api.SelectionMode import io.element.android.features.userlist.api.UserListDataSource import io.element.android.features.userlist.api.UserListDataStore @@ -61,33 +60,20 @@ class RoomMemberListPresenter @Inject constructor( @Composable override fun present(): RoomMemberListState { - val coroutineScope = rememberCoroutineScope() val userListState = userListPresenter.present() val allUsers = remember { mutableStateOf>>(Async.Loading()) } - val selectedMember: MutableState = remember { - mutableStateOf(null) - } + LaunchedEffect(Unit) { withContext(coroutineDispatchers.io) { allUsers.value = Async.Success(userListDataSource.search("").toImmutableList()) } } - fun handleEvents(roomMemberListEvents: RoomMemberListEvents) { - when (roomMemberListEvents) { - is RoomMemberListEvents.SelectUser -> coroutineScope.loadRoomMember(roomMemberListEvents.user, selectedMember) - } - } return RoomMemberListState( allUsers = allUsers.value, userListState = userListState, - selectedRoomMember = selectedMember.value, - eventSink = ::handleEvents ) } - private fun CoroutineScope.loadRoomMember(user: MatrixUser, selectedMember: MutableState) = launch(coroutineDispatchers.io) { - selectedMember.value = room.getMemberFlow(user.id).firstOrNull() - } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt index 42289b9ebe..28885006b1 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt @@ -25,6 +25,4 @@ import kotlinx.collections.immutable.ImmutableList data class RoomMemberListState( val allUsers: Async>, val userListState: UserListState, - val selectedRoomMember: RoomMember? = null, - val eventSink: (RoomMemberListEvents) -> Unit, ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt index 57012c6d86..fc98ae7544 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt @@ -39,5 +39,4 @@ internal fun aRoomMemberListState( RoomMemberListState( userListState = aUserListState().copy(searchResults = searchResults), allUsers = allUsers, - eventSink = {} ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt index 47aeb635df..99c6a9299a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt @@ -28,7 +28,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.pluralStringResource @@ -52,7 +51,7 @@ import io.element.android.libraries.designsystem.theme.components.CenterAlignedT import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.ui.model.MatrixUser @OptIn(ExperimentalMaterial3Api::class) @@ -61,17 +60,11 @@ fun RoomMemberListView( state: RoomMemberListState, modifier: Modifier = Modifier, onBackPressed: () -> Unit = {}, - onMemberSelected: (RoomMember) -> Unit = {}, + onMemberSelected: (UserId) -> Unit = {}, ) { - LaunchedEffect(state.selectedRoomMember) { - if (state.selectedRoomMember != null) { - onMemberSelected(state.selectedRoomMember) - } - } - fun onUserSelected(user: MatrixUser) { - state.eventSink(RoomMemberListEvents.SelectUser(user)) + onMemberSelected(user.id) } Scaffold( diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt index 72e335c1d2..7fd4dd3876 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt @@ -30,8 +30,8 @@ import io.element.android.libraries.androidutils.system.startSharePlainTextInten import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder -import io.element.android.libraries.matrix.api.room.RoomMember import timber.log.Timber import io.element.android.libraries.androidutils.R as AndroidUtilsR @@ -43,18 +43,18 @@ class RoomMemberDetailsNode @AssistedInject constructor( ) : Node(buildContext, plugins = plugins) { data class Inputs( - val member: RoomMember, + val roomMemberId: UserId, ) : NodeInputs private val inputs = inputs() - private val presenter = presenterFactory.create(inputs.member) + private val presenter = presenterFactory.create(inputs.roomMemberId) @Composable override fun View(modifier: Modifier) { val context = LocalContext.current fun onShareUser() { - val permalinkResult = PermalinkBuilder.permalinkForUser(inputs.member.userId) + val permalinkResult = PermalinkBuilder.permalinkForUser(inputs.roomMemberId) permalinkResult.onSuccess { permalink -> startSharePlainTextIntent( context = context, 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 33814c29f0..e51205728e 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 @@ -18,6 +18,7 @@ package io.element.android.features.roomdetails.impl.members.details import androidx.compose.runtime.Composable 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 @@ -28,29 +29,33 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState.ConfirmationDialog import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.matrix.api.MatrixClient -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.RoomMember +import io.element.android.libraries.matrix.api.room.getMemberFlow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch class RoomMemberDetailsPresenter @AssistedInject constructor( private val client: MatrixClient, private val room: MatrixRoom, - @Assisted private val roomMember: RoomMember, + @Assisted private val roomMemberId: UserId, ) : Presenter { interface Factory { - fun create(roomMember: RoomMember): RoomMemberDetailsPresenter + fun create(roomMemberId: UserId): RoomMemberDetailsPresenter } @Composable override fun present(): RoomMemberDetailsState { val coroutineScope = rememberCoroutineScope() var confirmationDialog by remember { mutableStateOf(null) } - val isBlocked = remember { mutableStateOf(roomMember.isIgnored) } + val roomMember by room.getMemberFlow(roomMemberId).collectAsState(initial = null) + + val isBlocked = remember(roomMember?.isIgnored) { + mutableStateOf(roomMember?.isIgnored.orFalse()) + } fun handleEvents(event: RoomMemberDetailsEvents) { when (event) { @@ -59,7 +64,7 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( confirmationDialog = ConfirmationDialog.Block } else { confirmationDialog = null - coroutineScope.blockUser(roomMember.userId, isBlocked) + coroutineScope.blockUser(roomMemberId, isBlocked) } } is RoomMemberDetailsEvents.UnblockUser -> { @@ -67,41 +72,50 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( confirmationDialog = ConfirmationDialog.Unblock } else { confirmationDialog = null - coroutineScope.unblockUser(roomMember.userId, isBlocked) + coroutineScope.unblockUser(roomMemberId, isBlocked) } } RoomMemberDetailsEvents.ClearConfirmationDialog -> confirmationDialog = null } } - val userName by produceState(initialValue = roomMember.displayName) { - room.userDisplayName(roomMember.userId).onSuccess { displayName -> + val userName by produceState(initialValue = roomMember?.displayName) { + room.userDisplayName(roomMemberId).onSuccess { displayName -> if (displayName != null) value = displayName } } - val userAvatar by produceState(initialValue = roomMember.avatarUrl) { - room.userAvatarUrl(roomMember.userId).onSuccess { avatarUrl -> + val userAvatar by produceState(initialValue = roomMember?.avatarUrl) { + room.userAvatarUrl(roomMemberId).onSuccess { avatarUrl -> if (avatarUrl != null) value = avatarUrl } } return RoomMemberDetailsState( - userId = roomMember.userId.value, + userId = roomMemberId.value, userName = userName, avatarUrl = userAvatar, isBlocked = isBlocked.value, displayConfirmationDialog = confirmationDialog, - isCurrentUser = roomMember.userId == client.sessionId, + isCurrentUser = roomMember?.userId == client.sessionId, eventSink = ::handleEvents ) } private fun CoroutineScope.blockUser(userId: UserId, isBlockedState: MutableState) = launch { - client.ignoreUser(userId).onSuccess { isBlockedState.value = true } + client.ignoreUser(userId) + .map { + isBlockedState.value = true + room.updateMembers() + } + } private fun CoroutineScope.unblockUser(userId: UserId, isBlockedState: MutableState) = launch { - client.unignoreUser(userId).onSuccess { isBlockedState.value = false } + client.unignoreUser(userId) + .map { + isBlockedState.value = false + room.updateMembers() + } } } 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 2409172f04..bd0b5906cb 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 @@ -58,8 +58,8 @@ class RoomDetailsPresenterTests { private fun aRoomDetailsPresenter(room: MatrixRoom): RoomDetailsPresenter { val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory { - override fun create(roomMember: RoomMember): RoomMemberDetailsPresenter { - return RoomMemberDetailsPresenter(aMatrixClient(), room, roomMember) + override fun create(roomMemberId: UserId): RoomMemberDetailsPresenter { + return RoomMemberDetailsPresenter(aMatrixClient(), room, roomMemberId) } } return RoomDetailsPresenter(room, roomMembershipObserver, testCoroutineDispatchers, roomMemberDetailsPresenterFactory) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt index 25bb3e8a7e..3c9bd030b0 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt @@ -16,11 +16,8 @@ package io.element.android.libraries.matrix.api.room -import android.os.Parcelable import io.element.android.libraries.matrix.api.core.UserId -import kotlinx.parcelize.Parcelize -@Parcelize data class RoomMember( val userId: UserId, val displayName: String?, @@ -30,7 +27,7 @@ data class RoomMember( val powerLevel: Long, val normalizedPowerLevel: Long, val isIgnored: Boolean, -) : Parcelable +) enum class RoomMembershipState { BAN, INVITE, JOIN, KNOCK, LEAVE