Let RoomMemberDetailsPresenter use UserProfilePresenter to reduce code duplication.

This commit is contained in:
Benoit Marty 2024-10-17 13:10:59 +02:00 committed by Benoit Marty
parent 9b9fef1aa8
commit 9b82c6df1c
18 changed files with 153 additions and 368 deletions

View file

@ -9,7 +9,7 @@ package io.element.android.features.roomdetails.impl
import androidx.compose.runtime.Immutable
import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.userprofile.shared.UserProfileState
import io.element.android.features.userprofile.api.UserProfileState
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomMember

View file

@ -11,7 +11,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.leaveroom.api.aLeaveRoomState
import io.element.android.features.roomdetails.impl.members.aRoomMember
import io.element.android.features.userprofile.shared.UserProfileState
import io.element.android.features.userprofile.api.UserProfileState
import io.element.android.features.userprofile.shared.aUserProfileState
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId

View file

@ -10,10 +10,9 @@ package io.element.android.features.roomdetails.impl.di
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
import io.element.android.features.createroom.api.StartDMAction
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
import io.element.android.features.userprofile.api.UserProfileStatePresenterFactory
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
@ -22,13 +21,12 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
object RoomMemberModule {
@Provides
fun provideRoomMemberDetailsPresenterFactory(
matrixClient: MatrixClient,
room: MatrixRoom,
startDMAction: StartDMAction,
userProfileStatePresenterFactory: UserProfileStatePresenterFactory,
): RoomMemberDetailsPresenter.Factory {
return object : RoomMemberDetailsPresenter.Factory {
override fun create(roomMemberId: UserId): RoomMemberDetailsPresenter {
return RoomMemberDetailsPresenter(roomMemberId, matrixClient, room, startDMAction)
return RoomMemberDetailsPresenter(roomMemberId, room, userProfileStatePresenterFactory)
}
}
}

View file

@ -9,153 +9,60 @@ 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.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.features.createroom.api.StartDMAction
import io.element.android.features.userprofile.shared.UserProfileEvents
import io.element.android.features.userprofile.shared.UserProfilePresenterHelper
import io.element.android.features.userprofile.shared.UserProfileState
import io.element.android.features.userprofile.shared.UserProfileState.ConfirmationDialog
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData
import io.element.android.features.userprofile.api.UserProfileState
import io.element.android.features.userprofile.api.UserProfileStatePresenterFactory
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.RoomId
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.user.MatrixUser
import io.element.android.libraries.matrix.ui.room.getRoomMemberAsState
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
/**
* Presenter for room member details screen.
* Rely on UserProfileStatePresenter, but override some fields with room member info when available.
*/
class RoomMemberDetailsPresenter @AssistedInject constructor(
@Assisted private val roomMemberId: UserId,
private val client: MatrixClient,
private val room: MatrixRoom,
private val startDMAction: StartDMAction,
userProfileStatePresenterFactory: UserProfileStatePresenterFactory,
) : Presenter<UserProfileState> {
interface Factory {
fun create(roomMemberId: UserId): RoomMemberDetailsPresenter
}
private val userProfilePresenterHelper = UserProfilePresenterHelper(
userId = roomMemberId,
client = client,
)
private val userProfilePresenter = userProfileStatePresenterFactory.create(roomMemberId)
@Composable
override fun present(): UserProfileState {
val coroutineScope = rememberCoroutineScope()
var confirmationDialog by remember { mutableStateOf<ConfirmationDialog?>(null) }
val roomMember by room.getRoomMemberAsState(roomMemberId)
var userProfile by remember { mutableStateOf<MatrixUser?>(null) }
val startDmActionState: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
val isBlocked: MutableState<AsyncData<Boolean>> = remember { mutableStateOf(AsyncData.Uninitialized) }
val isCurrentUser = remember { client.isMe(roomMemberId) }
val dmRoomId by userProfilePresenterHelper.getDmRoomId()
val canCall by userProfilePresenterHelper.getCanCall(dmRoomId)
LaunchedEffect(Unit) {
client.ignoredUsersFlow
.map { ignoredUsers -> roomMemberId in ignoredUsers }
.distinctUntilChanged()
.onEach { isBlocked.value = AsyncData.Success(it) }
.launchIn(this)
}
LaunchedEffect(Unit) {
// Update room member info when opening this screen
// We don't need to assign the result as it will be automatically propagated by `room.getRoomMemberAsState`
room.getUpdatedMember(roomMemberId)
.onFailure {
// Not a member of the room, try to get the user profile
userProfile = client.getProfile(roomMemberId).getOrNull()
}
}
fun handleEvents(event: UserProfileEvents) {
when (event) {
is UserProfileEvents.BlockUser -> {
if (event.needsConfirmation) {
confirmationDialog = ConfirmationDialog.Block
} else {
confirmationDialog = null
userProfilePresenterHelper.blockUser(coroutineScope, isBlocked)
}
}
is UserProfileEvents.UnblockUser -> {
if (event.needsConfirmation) {
confirmationDialog = ConfirmationDialog.Unblock
} else {
confirmationDialog = null
userProfilePresenterHelper.unblockUser(coroutineScope, isBlocked)
}
}
UserProfileEvents.ClearConfirmationDialog -> confirmationDialog = null
UserProfileEvents.ClearBlockUserError -> {
isBlocked.value = AsyncData.Success(isBlocked.value.dataOrNull().orFalse())
}
UserProfileEvents.StartDM -> {
coroutineScope.launch {
startDMAction.execute(roomMemberId, startDmActionState)
}
}
UserProfileEvents.ClearStartDMState -> {
startDmActionState.value = AsyncAction.Uninitialized
}
}
}
val userName: String? by produceState(
initialValue = roomMember?.displayName ?: userProfile?.displayName,
val roomUserName: String? by produceState(
initialValue = roomMember?.displayName,
key1 = roomMember,
key2 = userProfile,
) {
value = room.userDisplayName(roomMemberId)
.fold(
onSuccess = { it },
onFailure = {
// Fallback to user profile
userProfile?.displayName
}
)
value = room.userDisplayName(roomMemberId).getOrNull() ?: roomMember?.displayName
}
val userAvatar: String? by produceState(
initialValue = roomMember?.avatarUrl ?: userProfile?.avatarUrl,
val roomUserAvatar: String? by produceState(
initialValue = roomMember?.avatarUrl,
key1 = roomMember,
key2 = userProfile,
) {
value = room.userAvatarUrl(roomMemberId)
.fold(
onSuccess = { it },
onFailure = {
// Fallback to user profile
userProfile?.avatarUrl
}
)
value = room.userAvatarUrl(roomMemberId).getOrNull() ?: roomMember?.avatarUrl
}
return UserProfileState(
userId = roomMemberId,
userName = userName,
avatarUrl = userAvatar,
isBlocked = isBlocked.value,
startDmActionState = startDmActionState.value,
displayConfirmationDialog = confirmationDialog,
isCurrentUser = isCurrentUser,
dmRoomId = dmRoomId,
canCall = canCall,
eventSink = ::handleEvents
val userProfileState = userProfilePresenter.present()
return userProfileState.copy(
userName = roomUserName ?: userProfileState.userName,
avatarUrl = roomUserAvatar ?: userProfileState.avatarUrl,
)
}
}