Skip to content

Commit

Permalink
Merge pull request #3700 from element-hq/feature/bma/removeDuplicated…
Browse files Browse the repository at this point in the history
…Code

Remove duplicated code regarding user (room member and user profile) screens
  • Loading branch information
bmarty authored Oct 18, 2024
2 parents 41238db + 6106d7b commit f632a1f
Show file tree
Hide file tree
Showing 20 changed files with 281 additions and 502 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.UserProfilePresenterFactory
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

Expand All @@ -22,13 +21,16 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
object RoomMemberModule {
@Provides
fun provideRoomMemberDetailsPresenterFactory(
matrixClient: MatrixClient,
room: MatrixRoom,
startDMAction: StartDMAction,
userProfilePresenterFactory: UserProfilePresenterFactory,
): RoomMemberDetailsPresenter.Factory {
return object : RoomMemberDetailsPresenter.Factory {
override fun create(roomMemberId: UserId): RoomMemberDetailsPresenter {
return RoomMemberDetailsPresenter(roomMemberId, matrixClient, room, startDMAction)
return RoomMemberDetailsPresenter(
roomMemberId = roomMemberId,
room = room,
userProfilePresenterFactory = userProfilePresenterFactory,
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.UserProfilePresenterFactory
import io.element.android.features.userprofile.api.UserProfileState
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 UserProfilePresenter, 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,
userProfilePresenterFactory: UserProfilePresenterFactory,
) : Presenter<UserProfileState> {
interface Factory {
fun create(roomMemberId: UserId): RoomMemberDetailsPresenter
}

private val userProfilePresenterHelper = UserProfilePresenterHelper(
userId = roomMemberId,
client = client,
)
private val userProfilePresenter = userProfilePresenterFactory.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,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import app.cash.turbine.TurbineTestContext
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.Interaction
import io.element.android.features.createroom.test.FakeStartDMAction
import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.leaveroom.api.aLeaveRoomState
Expand All @@ -25,6 +24,8 @@ import io.element.android.features.roomdetails.impl.RoomDetailsType
import io.element.android.features.roomdetails.impl.RoomTopicState
import io.element.android.features.roomdetails.impl.members.aRoomMember
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
import io.element.android.features.userprofile.shared.aUserProfileState
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
Expand Down Expand Up @@ -82,7 +83,13 @@ class RoomDetailsPresenterTest {
val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService)
val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory {
override fun create(roomMemberId: UserId): RoomMemberDetailsPresenter {
return RoomMemberDetailsPresenter(roomMemberId, matrixClient, room, FakeStartDMAction())
return RoomMemberDetailsPresenter(
roomMemberId = roomMemberId,
room = room,
userProfilePresenterFactory = {
Presenter { aUserProfileState() }
},
)
}
}
val featureFlagService = FakeFeatureFlagService(
Expand Down
Loading

0 comments on commit f632a1f

Please sign in to comment.