Don't pass RoomMember to Node but a UserId instead

This commit is contained in:
ganfra 2023-04-27 22:38:52 +02:00
parent 884cdb9575
commit 16a43befcc
12 changed files with 52 additions and 63 deletions

View file

@ -59,7 +59,7 @@ class RoomDetailsFlowNode @AssistedInject constructor(
object RoomMemberList : NavTarget object RoomMemberList : NavTarget
@Parcelize @Parcelize
data class RoomMemberDetails(val roomMember: RoomMember) : NavTarget data class RoomMemberDetails(val roomMemberId: UserId) : NavTarget
} }
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
@ -74,14 +74,14 @@ class RoomDetailsFlowNode @AssistedInject constructor(
} }
NavTarget.RoomMemberList -> { NavTarget.RoomMemberList -> {
val roomMemberListCallback = object : RoomMemberListNode.Callback { val roomMemberListCallback = object : RoomMemberListNode.Callback {
override fun openRoomMemberDetails(roomMember: RoomMember) { override fun openRoomMemberDetails(roomMemberId: UserId) {
backstack.push(NavTarget.RoomMemberDetails(roomMember)) backstack.push(NavTarget.RoomMemberDetails(roomMemberId))
} }
} }
createNode<RoomMemberListNode>(buildContext, listOf(roomMemberListCallback)) createNode<RoomMemberListNode>(buildContext, listOf(roomMemberListCallback))
} }
is NavTarget.RoomMemberDetails -> { is NavTarget.RoomMemberDetails -> {
createNode<RoomMemberDetailsNode>(buildContext, listOf(RoomMemberDetailsNode.Inputs(navTarget.roomMember))) createNode<RoomMemberDetailsNode>(buildContext, listOf(RoomMemberDetailsNode.Inputs(navTarget.roomMemberId)))
} }
} }
} }

View file

@ -99,7 +99,7 @@ class RoomDetailsPresenter @Inject constructor(
@Composable @Composable
private fun roomMemberDetailsPresenter(dmMemberState: RoomMember?) = remember(dmMemberState) { private fun roomMemberDetailsPresenter(dmMemberState: RoomMember?) = remember(dmMemberState) {
dmMemberState?.let { roomMember -> dmMemberState?.let { roomMember ->
roomMembersDetailsPresenterFactory.create(roomMember) roomMembersDetailsPresenterFactory.create(roomMember.userId)
} }
} }

View file

@ -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.features.userlist.api.UserListDataSource
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.MatrixClient 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.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMember
import javax.inject.Named import javax.inject.Named
@ -48,8 +49,8 @@ object RoomMemberProvidesModule {
room: MatrixRoom, room: MatrixRoom,
): RoomMemberDetailsPresenter.Factory { ): RoomMemberDetailsPresenter.Factory {
return object : RoomMemberDetailsPresenter.Factory { return object : RoomMemberDetailsPresenter.Factory {
override fun create(roomMember: RoomMember): RoomMemberDetailsPresenter { override fun create(roomMemberId: UserId): RoomMemberDetailsPresenter {
return RoomMemberDetailsPresenter(matrixClient, room, roomMember) return RoomMemberDetailsPresenter(matrixClient, room, roomMemberId)
} }
} }
} }

View file

@ -26,6 +26,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.di.RoomScope 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 import io.element.android.libraries.matrix.api.room.RoomMember
@ContributesNode(RoomScope::class) @ContributesNode(RoomScope::class)
@ -36,14 +37,14 @@ class RoomMemberListNode @AssistedInject constructor(
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin { interface Callback : Plugin {
fun openRoomMemberDetails(roomMember: RoomMember) fun openRoomMemberDetails(roomMemberId: UserId)
} }
private val callbacks = plugins<Callback>() private val callbacks = plugins<Callback>()
private fun openRoomMemberDetails(roomMember: RoomMember) { private fun openRoomMemberDetails(roomMemberId: UserId) {
callbacks.forEach { callbacks.forEach {
it.openRoomMemberDetails(roomMember) it.openRoomMemberDetails(roomMemberId)
} }
} }

View file

@ -21,7 +21,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember 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.SelectionMode
import io.element.android.features.userlist.api.UserListDataSource import io.element.android.features.userlist.api.UserListDataSource
import io.element.android.features.userlist.api.UserListDataStore import io.element.android.features.userlist.api.UserListDataStore
@ -61,33 +60,20 @@ class RoomMemberListPresenter @Inject constructor(
@Composable @Composable
override fun present(): RoomMemberListState { override fun present(): RoomMemberListState {
val coroutineScope = rememberCoroutineScope()
val userListState = userListPresenter.present() val userListState = userListPresenter.present()
val allUsers = remember { mutableStateOf<Async<ImmutableList<MatrixUser>>>(Async.Loading()) } val allUsers = remember { mutableStateOf<Async<ImmutableList<MatrixUser>>>(Async.Loading()) }
val selectedMember: MutableState<RoomMember?> = remember {
mutableStateOf(null)
}
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
allUsers.value = Async.Success(userListDataSource.search("").toImmutableList()) allUsers.value = Async.Success(userListDataSource.search("").toImmutableList())
} }
} }
fun handleEvents(roomMemberListEvents: RoomMemberListEvents) {
when (roomMemberListEvents) {
is RoomMemberListEvents.SelectUser -> coroutineScope.loadRoomMember(roomMemberListEvents.user, selectedMember)
}
}
return RoomMemberListState( return RoomMemberListState(
allUsers = allUsers.value, allUsers = allUsers.value,
userListState = userListState, userListState = userListState,
selectedRoomMember = selectedMember.value,
eventSink = ::handleEvents
) )
} }
private fun CoroutineScope.loadRoomMember(user: MatrixUser, selectedMember: MutableState<RoomMember?>) = launch(coroutineDispatchers.io) {
selectedMember.value = room.getMemberFlow(user.id).firstOrNull()
}
} }

View file

@ -25,6 +25,4 @@ import kotlinx.collections.immutable.ImmutableList
data class RoomMemberListState( data class RoomMemberListState(
val allUsers: Async<ImmutableList<MatrixUser>>, val allUsers: Async<ImmutableList<MatrixUser>>,
val userListState: UserListState, val userListState: UserListState,
val selectedRoomMember: RoomMember? = null,
val eventSink: (RoomMemberListEvents) -> Unit,
) )

View file

@ -39,5 +39,4 @@ internal fun aRoomMemberListState(
RoomMemberListState( RoomMemberListState(
userListState = aUserListState().copy(searchResults = searchResults), userListState = aUserListState().copy(searchResults = searchResults),
allUsers = allUsers, allUsers = allUsers,
eventSink = {}
) )

View file

@ -28,7 +28,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource 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.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text 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 import io.element.android.libraries.matrix.ui.model.MatrixUser
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -61,17 +60,11 @@ fun RoomMemberListView(
state: RoomMemberListState, state: RoomMemberListState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onBackPressed: () -> Unit = {}, onBackPressed: () -> Unit = {},
onMemberSelected: (RoomMember) -> Unit = {}, onMemberSelected: (UserId) -> Unit = {},
) { ) {
LaunchedEffect(state.selectedRoomMember) {
if (state.selectedRoomMember != null) {
onMemberSelected(state.selectedRoomMember)
}
}
fun onUserSelected(user: MatrixUser) { fun onUserSelected(user: MatrixUser) {
state.eventSink(RoomMemberListEvents.SelectUser(user)) onMemberSelected(user.id)
} }
Scaffold( Scaffold(

View file

@ -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.NodeInputs
import io.element.android.libraries.architecture.inputs import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.RoomScope 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.permalink.PermalinkBuilder
import io.element.android.libraries.matrix.api.room.RoomMember
import timber.log.Timber import timber.log.Timber
import io.element.android.libraries.androidutils.R as AndroidUtilsR import io.element.android.libraries.androidutils.R as AndroidUtilsR
@ -43,18 +43,18 @@ class RoomMemberDetailsNode @AssistedInject constructor(
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
data class Inputs( data class Inputs(
val member: RoomMember, val roomMemberId: UserId,
) : NodeInputs ) : NodeInputs
private val inputs = inputs<Inputs>() private val inputs = inputs<Inputs>()
private val presenter = presenterFactory.create(inputs.member) private val presenter = presenterFactory.create(inputs.roomMemberId)
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val context = LocalContext.current val context = LocalContext.current
fun onShareUser() { fun onShareUser() {
val permalinkResult = PermalinkBuilder.permalinkForUser(inputs.member.userId) val permalinkResult = PermalinkBuilder.permalinkForUser(inputs.roomMemberId)
permalinkResult.onSuccess { permalink -> permalinkResult.onSuccess { permalink ->
startSharePlainTextIntent( startSharePlainTextIntent(
context = context, context = context,

View file

@ -18,6 +18,7 @@ package io.element.android.features.roomdetails.impl.members.details
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
@ -28,29 +29,33 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState.ConfirmationDialog import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState.ConfirmationDialog
import io.element.android.libraries.architecture.Presenter 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.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.core.UserId
import io.element.android.libraries.matrix.api.room.MatrixRoom 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.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class RoomMemberDetailsPresenter @AssistedInject constructor( class RoomMemberDetailsPresenter @AssistedInject constructor(
private val client: MatrixClient, private val client: MatrixClient,
private val room: MatrixRoom, private val room: MatrixRoom,
@Assisted private val roomMember: RoomMember, @Assisted private val roomMemberId: UserId,
) : Presenter<RoomMemberDetailsState> { ) : Presenter<RoomMemberDetailsState> {
interface Factory { interface Factory {
fun create(roomMember: RoomMember): RoomMemberDetailsPresenter fun create(roomMemberId: UserId): RoomMemberDetailsPresenter
} }
@Composable @Composable
override fun present(): RoomMemberDetailsState { override fun present(): RoomMemberDetailsState {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
var confirmationDialog by remember { mutableStateOf<ConfirmationDialog?>(null) } var confirmationDialog by remember { mutableStateOf<ConfirmationDialog?>(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) { fun handleEvents(event: RoomMemberDetailsEvents) {
when (event) { when (event) {
@ -59,7 +64,7 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
confirmationDialog = ConfirmationDialog.Block confirmationDialog = ConfirmationDialog.Block
} else { } else {
confirmationDialog = null confirmationDialog = null
coroutineScope.blockUser(roomMember.userId, isBlocked) coroutineScope.blockUser(roomMemberId, isBlocked)
} }
} }
is RoomMemberDetailsEvents.UnblockUser -> { is RoomMemberDetailsEvents.UnblockUser -> {
@ -67,41 +72,50 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
confirmationDialog = ConfirmationDialog.Unblock confirmationDialog = ConfirmationDialog.Unblock
} else { } else {
confirmationDialog = null confirmationDialog = null
coroutineScope.unblockUser(roomMember.userId, isBlocked) coroutineScope.unblockUser(roomMemberId, isBlocked)
} }
} }
RoomMemberDetailsEvents.ClearConfirmationDialog -> confirmationDialog = null RoomMemberDetailsEvents.ClearConfirmationDialog -> confirmationDialog = null
} }
} }
val userName by produceState(initialValue = roomMember.displayName) { val userName by produceState(initialValue = roomMember?.displayName) {
room.userDisplayName(roomMember.userId).onSuccess { displayName -> room.userDisplayName(roomMemberId).onSuccess { displayName ->
if (displayName != null) value = displayName if (displayName != null) value = displayName
} }
} }
val userAvatar by produceState(initialValue = roomMember.avatarUrl) { val userAvatar by produceState(initialValue = roomMember?.avatarUrl) {
room.userAvatarUrl(roomMember.userId).onSuccess { avatarUrl -> room.userAvatarUrl(roomMemberId).onSuccess { avatarUrl ->
if (avatarUrl != null) value = avatarUrl if (avatarUrl != null) value = avatarUrl
} }
} }
return RoomMemberDetailsState( return RoomMemberDetailsState(
userId = roomMember.userId.value, userId = roomMemberId.value,
userName = userName, userName = userName,
avatarUrl = userAvatar, avatarUrl = userAvatar,
isBlocked = isBlocked.value, isBlocked = isBlocked.value,
displayConfirmationDialog = confirmationDialog, displayConfirmationDialog = confirmationDialog,
isCurrentUser = roomMember.userId == client.sessionId, isCurrentUser = roomMember?.userId == client.sessionId,
eventSink = ::handleEvents eventSink = ::handleEvents
) )
} }
private fun CoroutineScope.blockUser(userId: UserId, isBlockedState: MutableState<Boolean>) = launch { private fun CoroutineScope.blockUser(userId: UserId, isBlockedState: MutableState<Boolean>) = 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<Boolean>) = launch { private fun CoroutineScope.unblockUser(userId: UserId, isBlockedState: MutableState<Boolean>) = launch {
client.unignoreUser(userId).onSuccess { isBlockedState.value = false } client.unignoreUser(userId)
.map {
isBlockedState.value = false
room.updateMembers()
}
} }
} }

View file

@ -58,8 +58,8 @@ class RoomDetailsPresenterTests {
private fun aRoomDetailsPresenter(room: MatrixRoom): RoomDetailsPresenter { private fun aRoomDetailsPresenter(room: MatrixRoom): RoomDetailsPresenter {
val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory { val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory {
override fun create(roomMember: RoomMember): RoomMemberDetailsPresenter { override fun create(roomMemberId: UserId): RoomMemberDetailsPresenter {
return RoomMemberDetailsPresenter(aMatrixClient(), room, roomMember) return RoomMemberDetailsPresenter(aMatrixClient(), room, roomMemberId)
} }
} }
return RoomDetailsPresenter(room, roomMembershipObserver, testCoroutineDispatchers, roomMemberDetailsPresenterFactory) return RoomDetailsPresenter(room, roomMembershipObserver, testCoroutineDispatchers, roomMemberDetailsPresenterFactory)

View file

@ -16,11 +16,8 @@
package io.element.android.libraries.matrix.api.room package io.element.android.libraries.matrix.api.room
import android.os.Parcelable
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import kotlinx.parcelize.Parcelize
@Parcelize
data class RoomMember( data class RoomMember(
val userId: UserId, val userId: UserId,
val displayName: String?, val displayName: String?,
@ -30,7 +27,7 @@ data class RoomMember(
val powerLevel: Long, val powerLevel: Long,
val normalizedPowerLevel: Long, val normalizedPowerLevel: Long,
val isIgnored: Boolean, val isIgnored: Boolean,
) : Parcelable )
enum class RoomMembershipState { enum class RoomMembershipState {
BAN, INVITE, JOIN, KNOCK, LEAVE BAN, INVITE, JOIN, KNOCK, LEAVE