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 64c50d4468
commit 4b9f2fdae0
12 changed files with 52 additions and 63 deletions

View file

@ -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<RoomMemberListNode>(buildContext, listOf(roomMemberListCallback))
}
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
private fun roomMemberDetailsPresenter(dmMemberState: RoomMember?) = remember(dmMemberState) {
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.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)
}
}
}

View file

@ -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<Callback>()
private fun openRoomMemberDetails(roomMember: RoomMember) {
private fun openRoomMemberDetails(roomMemberId: UserId) {
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.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<ImmutableList<MatrixUser>>>(Async.Loading()) }
val selectedMember: MutableState<RoomMember?> = 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<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(
val allUsers: Async<ImmutableList<MatrixUser>>,
val userListState: UserListState,
val selectedRoomMember: RoomMember? = null,
val eventSink: (RoomMemberListEvents) -> Unit,
)

View file

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

View file

@ -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(

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.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<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,

View file

@ -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<RoomMemberDetailsState> {
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<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) {
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<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 {
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 {
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)

View file

@ -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