Browse Source
Move canDisplayModerationActions from presenter API to the state it emits.pull/3618/head
Benoit Marty
2 weeks ago
committed by
Benoit Marty
9 changed files with 243 additions and 257 deletions
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
/* |
||||
* Copyright 2024 New Vector Ltd. |
||||
* |
||||
* SPDX-License-Identifier: AGPL-3.0-only |
||||
* Please see LICENSE in the repository root for full details. |
||||
*/ |
||||
|
||||
package io.element.android.features.roomdetails.impl.di |
||||
|
||||
import com.squareup.anvil.annotations.ContributesTo |
||||
import dagger.Binds |
||||
import dagger.Module |
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter |
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState |
||||
import io.element.android.libraries.architecture.Presenter |
||||
import io.element.android.libraries.di.RoomScope |
||||
|
||||
@Module |
||||
@ContributesTo(RoomScope::class) |
||||
interface RoomDetailsModule { |
||||
@Binds |
||||
fun bindRoomMembersModerationPresenter(presenter: RoomMembersModerationPresenter): Presenter<RoomMembersModerationState> |
||||
} |
@ -1,174 +0,0 @@
@@ -1,174 +0,0 @@
|
||||
/* |
||||
* Copyright 2024 New Vector Ltd. |
||||
* |
||||
* SPDX-License-Identifier: AGPL-3.0-only |
||||
* Please see LICENSE in the repository root for full details. |
||||
*/ |
||||
|
||||
package io.element.android.features.roomdetails.impl.members.moderation |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
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 com.squareup.anvil.annotations.ContributesBinding |
||||
import im.vector.app.features.analytics.plan.RoomModeration |
||||
import io.element.android.libraries.architecture.AsyncAction |
||||
import io.element.android.libraries.architecture.runUpdatingState |
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers |
||||
import io.element.android.libraries.core.extensions.finally |
||||
import io.element.android.libraries.di.RoomScope |
||||
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.RoomMembershipState |
||||
import io.element.android.libraries.matrix.api.room.isDm |
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canBan |
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canKick |
||||
import io.element.android.services.analytics.api.AnalyticsService |
||||
import kotlinx.collections.immutable.persistentListOf |
||||
import kotlinx.collections.immutable.toPersistentList |
||||
import kotlinx.coroutines.CoroutineScope |
||||
import kotlinx.coroutines.flow.drop |
||||
import kotlinx.coroutines.flow.take |
||||
import kotlinx.coroutines.launch |
||||
import javax.inject.Inject |
||||
|
||||
@ContributesBinding(RoomScope::class) |
||||
class DefaultRoomMembersModerationPresenter @Inject constructor( |
||||
private val room: MatrixRoom, |
||||
private val dispatchers: CoroutineDispatchers, |
||||
private val analyticsService: AnalyticsService, |
||||
) : RoomMembersModerationPresenter { |
||||
private var selectedMember by mutableStateOf<RoomMember?>(null) |
||||
|
||||
private suspend fun canBan() = room.canBan().getOrDefault(false) |
||||
private suspend fun canKick() = room.canKick().getOrDefault(false) |
||||
|
||||
override suspend fun canDisplayModerationActions(): Boolean { |
||||
return !room.isDm && (canBan() || canKick()) |
||||
} |
||||
|
||||
@Composable |
||||
override fun present(): RoomMembersModerationState { |
||||
val coroutineScope = rememberCoroutineScope() |
||||
var moderationActions by remember { mutableStateOf(persistentListOf<ModerationAction>()) } |
||||
|
||||
val kickUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) } |
||||
val banUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) } |
||||
val unbanUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) } |
||||
|
||||
val canDisplayBannedUsers by produceState(initialValue = false) { |
||||
value = !room.isDm && canBan() |
||||
} |
||||
|
||||
fun handleEvent(event: RoomMembersModerationEvents) { |
||||
when (event) { |
||||
is RoomMembersModerationEvents.SelectRoomMember -> { |
||||
coroutineScope.launch { |
||||
selectedMember = event.roomMember |
||||
if (event.roomMember.membership == RoomMembershipState.BAN && canBan()) { |
||||
unbanUserAsyncAction.value = AsyncAction.Confirming |
||||
} else { |
||||
moderationActions = buildList { |
||||
add(ModerationAction.DisplayProfile(event.roomMember.userId)) |
||||
val currentUserMemberPowerLevel = room.userRole(room.sessionId).getOrDefault(RoomMember.Role.USER).powerLevel |
||||
if (currentUserMemberPowerLevel > event.roomMember.powerLevel) { |
||||
if (canKick()) { |
||||
add(ModerationAction.KickUser(event.roomMember.userId)) |
||||
} |
||||
if (canBan()) { |
||||
add(ModerationAction.BanUser(event.roomMember.userId)) |
||||
} |
||||
} |
||||
}.toPersistentList() |
||||
} |
||||
} |
||||
} |
||||
is RoomMembersModerationEvents.KickUser -> { |
||||
moderationActions = persistentListOf() |
||||
selectedMember?.let { |
||||
coroutineScope.kickUser(it.userId, kickUserAsyncAction) |
||||
} |
||||
} |
||||
is RoomMembersModerationEvents.BanUser -> { |
||||
if (banUserAsyncAction.value.isConfirming()) { |
||||
moderationActions = persistentListOf() |
||||
selectedMember?.let { |
||||
coroutineScope.banUser(it.userId, banUserAsyncAction) |
||||
} |
||||
} else { |
||||
banUserAsyncAction.value = AsyncAction.Confirming |
||||
} |
||||
} |
||||
is RoomMembersModerationEvents.UnbanUser -> { |
||||
if (unbanUserAsyncAction.value.isConfirming()) { |
||||
moderationActions = persistentListOf() |
||||
selectedMember?.let { |
||||
coroutineScope.unbanUser(it.userId, unbanUserAsyncAction) |
||||
} |
||||
} else { |
||||
unbanUserAsyncAction.value = AsyncAction.Confirming |
||||
} |
||||
} |
||||
is RoomMembersModerationEvents.Reset -> { |
||||
selectedMember = null |
||||
moderationActions = persistentListOf() |
||||
kickUserAsyncAction.value = AsyncAction.Uninitialized |
||||
banUserAsyncAction.value = AsyncAction.Uninitialized |
||||
unbanUserAsyncAction.value = AsyncAction.Uninitialized |
||||
} |
||||
} |
||||
} |
||||
|
||||
return RoomMembersModerationState( |
||||
selectedRoomMember = selectedMember, |
||||
actions = moderationActions, |
||||
kickUserAsyncAction = kickUserAsyncAction.value, |
||||
banUserAsyncAction = banUserAsyncAction.value, |
||||
unbanUserAsyncAction = unbanUserAsyncAction.value, |
||||
canDisplayBannedUsers = canDisplayBannedUsers, |
||||
eventSink = { handleEvent(it) }, |
||||
) |
||||
} |
||||
|
||||
private fun CoroutineScope.kickUser( |
||||
userId: UserId, |
||||
kickUserAction: MutableState<AsyncAction<Unit>>, |
||||
) = runActionAndWaitForMembershipChange(kickUserAction) { |
||||
analyticsService.capture(RoomModeration(RoomModeration.Action.KickMember)) |
||||
room.kickUser(userId).finally { selectedMember = null } |
||||
} |
||||
|
||||
private fun CoroutineScope.banUser( |
||||
userId: UserId, |
||||
banUserAction: MutableState<AsyncAction<Unit>>, |
||||
) = runActionAndWaitForMembershipChange(banUserAction) { |
||||
analyticsService.capture(RoomModeration(RoomModeration.Action.BanMember)) |
||||
room.banUser(userId).finally { selectedMember = null } |
||||
} |
||||
|
||||
private fun CoroutineScope.unbanUser( |
||||
userId: UserId, |
||||
unbanUserAction: MutableState<AsyncAction<Unit>>, |
||||
) = runActionAndWaitForMembershipChange(unbanUserAction) { |
||||
analyticsService.capture(RoomModeration(RoomModeration.Action.UnbanMember)) |
||||
room.unbanUser(userId).finally { selectedMember = null } |
||||
} |
||||
|
||||
private fun <T> CoroutineScope.runActionAndWaitForMembershipChange(action: MutableState<AsyncAction<T>>, block: suspend () -> Result<T>) { |
||||
launch(dispatchers.io) { |
||||
action.runUpdatingState { |
||||
val result = block() |
||||
if (result.isSuccess) { |
||||
room.membersStateFlow.drop(1).take(1) |
||||
} |
||||
result |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,34 +0,0 @@
@@ -1,34 +0,0 @@
|
||||
/* |
||||
* Copyright 2024 New Vector Ltd. |
||||
* |
||||
* SPDX-License-Identifier: AGPL-3.0-only |
||||
* Please see LICENSE in the repository root for full details. |
||||
*/ |
||||
|
||||
package io.element.android.features.roomdetails.members.moderation |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.runtime.mutableStateOf |
||||
import androidx.compose.runtime.setValue |
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter |
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState |
||||
|
||||
class FakeRoomMembersModerationPresenter( |
||||
private val canDisplayModerationActions: Boolean = true, |
||||
) : RoomMembersModerationPresenter { |
||||
private var state by mutableStateOf(dummyState()) |
||||
|
||||
override suspend fun canDisplayModerationActions(): Boolean { |
||||
return canDisplayModerationActions |
||||
} |
||||
|
||||
@Composable |
||||
override fun present(): RoomMembersModerationState { |
||||
return state |
||||
} |
||||
|
||||
fun givenState(state: RoomMembersModerationState) { |
||||
this.state = state |
||||
} |
||||
} |
Loading…
Reference in new issue