change (member moderation) : allow disabled action and render unban too
This commit is contained in:
@@ -263,10 +263,10 @@ class MessagesNode @AssistedInject constructor(
|
||||
)
|
||||
roomMemberModerationRenderer.Render(
|
||||
state = state.roomMemberModerationState,
|
||||
onSelectAction = { action ->
|
||||
onSelectAction = { action, target ->
|
||||
when (action) {
|
||||
is ModerationAction.DisplayProfile -> onUserDataClick(action.user.userId)
|
||||
else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action))
|
||||
is ModerationAction.DisplayProfile -> onUserDataClick(target.userId)
|
||||
else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target))
|
||||
}
|
||||
},
|
||||
modifier = Modifier,
|
||||
|
||||
@@ -75,10 +75,10 @@ class RoomMemberListNode @AssistedInject constructor(
|
||||
)
|
||||
roomMemberModerationRenderer.Render(
|
||||
state = state.moderationState,
|
||||
onSelectAction = { action ->
|
||||
onSelectAction = { action, target ->
|
||||
when (action) {
|
||||
is ModerationAction.DisplayProfile -> openRoomMemberDetails(action.user.userId)
|
||||
else -> state.moderationState.eventSink(RoomMemberModerationEvents.ProcessAction(action))
|
||||
is ModerationAction.DisplayProfile -> openRoomMemberDetails(target.userId)
|
||||
else -> state.moderationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target))
|
||||
}
|
||||
},
|
||||
modifier = Modifier,
|
||||
|
||||
@@ -167,7 +167,7 @@ class RoomMemberListPresenter @AssistedInject constructor(
|
||||
is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query
|
||||
is RoomMemberListEvents.RoomMemberSelected ->
|
||||
if (event.roomMember.membership == RoomMembershipState.BAN) {
|
||||
roomModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(ModerationAction.UnbanUser(event.roomMember.toMatrixUser())))
|
||||
roomModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(ModerationAction.UnbanUser, event.roomMember.toMatrixUser()))
|
||||
} else if (!isDm.value && (roomModerationState.canBan || roomModerationState.canKick)) {
|
||||
roomModerationState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(event.roomMember.toMatrixUser()))
|
||||
} else {
|
||||
|
||||
@@ -11,5 +11,5 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
||||
interface RoomMemberModerationEvents {
|
||||
data class ShowActionsForUser(val user: MatrixUser) : RoomMemberModerationEvents
|
||||
data class ProcessAction(val action: ModerationAction) : RoomMemberModerationEvents
|
||||
data class ProcessAction(val action: ModerationAction, val targetUser: MatrixUser) : RoomMemberModerationEvents
|
||||
}
|
||||
|
||||
@@ -9,12 +9,13 @@ package io.element.android.features.roommembermoderation.api
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
||||
interface RoomMemberModerationRenderer {
|
||||
@Composable
|
||||
fun Render(
|
||||
state: RoomMemberModerationState,
|
||||
onSelectAction: (ModerationAction) -> Unit,
|
||||
onSelectAction: (ModerationAction, MatrixUser) -> Unit,
|
||||
modifier: Modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,17 +7,20 @@
|
||||
|
||||
package io.element.android.features.roommembermoderation.api
|
||||
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
||||
interface RoomMemberModerationState {
|
||||
val canKick: Boolean
|
||||
val canBan: Boolean
|
||||
val eventSink: (RoomMemberModerationEvents) -> Unit
|
||||
}
|
||||
|
||||
data class ModerationActionState(
|
||||
val action: ModerationAction,
|
||||
val isEnabled: Boolean,
|
||||
)
|
||||
|
||||
sealed interface ModerationAction {
|
||||
data class DisplayProfile(val user: MatrixUser) : ModerationAction
|
||||
data class KickUser(val user: MatrixUser) : ModerationAction
|
||||
data class BanUser(val user: MatrixUser) : ModerationAction
|
||||
data class UnbanUser(val user: MatrixUser) : ModerationAction
|
||||
data object DisplayProfile : ModerationAction
|
||||
data object KickUser : ModerationAction
|
||||
data object BanUser : ModerationAction
|
||||
data object UnbanUser : ModerationAction
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import io.element.android.features.roommembermoderation.api.ModerationAction
|
||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationRenderer
|
||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -23,7 +24,7 @@ class DefaultRoomMemberModerationRenderer @Inject constructor() : RoomMemberMode
|
||||
@Composable
|
||||
override fun Render(
|
||||
state: RoomMemberModerationState,
|
||||
onSelectAction: (ModerationAction) -> Unit,
|
||||
onSelectAction: (ModerationAction, MatrixUser) -> Unit,
|
||||
modifier: Modifier
|
||||
) {
|
||||
if (state is InternalRoomMemberModerationState) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
package io.element.android.features.roommembermoderation.impl
|
||||
|
||||
import io.element.android.features.roommembermoderation.api.ModerationAction
|
||||
import io.element.android.features.roommembermoderation.api.ModerationActionState
|
||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
|
||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
@@ -20,7 +21,7 @@ data class InternalRoomMemberModerationState(
|
||||
override val canKick: Boolean,
|
||||
override val canBan: Boolean,
|
||||
val selectedUser: MatrixUser?,
|
||||
val actions: ImmutableList<ModerationAction>,
|
||||
val actions: ImmutableList<ModerationActionState>,
|
||||
val kickUserAsyncAction: AsyncAction<Unit>,
|
||||
val banUserAsyncAction: AsyncAction<Unit>,
|
||||
val unbanUserAsyncAction: AsyncAction<Unit>,
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import im.vector.app.features.analytics.plan.RoomModeration
|
||||
import io.element.android.features.roommembermoderation.api.ModerationAction
|
||||
import io.element.android.features.roommembermoderation.api.ModerationActionState
|
||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
|
||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
@@ -26,7 +27,8 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.toMatrixUser
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.room.canBanAsState
|
||||
import io.element.android.libraries.matrix.ui.room.canKickAsState
|
||||
@@ -64,38 +66,37 @@ class RoomMemberModerationPresenter @Inject constructor(
|
||||
var selectedUser by remember {
|
||||
mutableStateOf<MatrixUser?>(null)
|
||||
}
|
||||
val moderationActions = remember { mutableStateOf(persistentListOf<ModerationAction>()) }
|
||||
val moderationActions = remember { mutableStateOf(persistentListOf<ModerationActionState>()) }
|
||||
|
||||
fun handleEvent(event: RoomMemberModerationEvents) {
|
||||
when (event) {
|
||||
is RoomMemberModerationEvents.ShowActionsForUser -> {
|
||||
coroutineScope.launch {
|
||||
selectedUser = event.user
|
||||
moderationActions.value = persistentListOf(ModerationAction.DisplayProfile(event.user))
|
||||
room.getUpdatedMember(event.user.userId)
|
||||
.onSuccess {
|
||||
moderationActions.value = computeModerationActions(
|
||||
member = it,
|
||||
canKick = canKick.value,
|
||||
canBan = canBan.value,
|
||||
currentUserMemberPowerLevel = currentUserMemberPowerLevel.value,
|
||||
)
|
||||
}
|
||||
val member = room.membersStateFlow.value.roomMembers()?.firstOrNull {
|
||||
it.userId == event.user.userId
|
||||
}
|
||||
moderationActions.value = computeModerationActions(
|
||||
member = member,
|
||||
canKick = canKick.value,
|
||||
canBan = canBan.value,
|
||||
currentUserMemberPowerLevel = currentUserMemberPowerLevel.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
is RoomMemberModerationEvents.ProcessAction -> {
|
||||
when (val action = event.action) {
|
||||
is ModerationAction.DisplayProfile -> Unit
|
||||
is ModerationAction.KickUser -> {
|
||||
selectedUser = action.user
|
||||
selectedUser = event.targetUser
|
||||
kickUserAsyncAction.value = AsyncAction.ConfirmingNoParams
|
||||
}
|
||||
is ModerationAction.BanUser -> {
|
||||
selectedUser = action.user
|
||||
selectedUser = event.targetUser
|
||||
banUserAsyncAction.value = AsyncAction.ConfirmingNoParams
|
||||
}
|
||||
is ModerationAction.UnbanUser -> {
|
||||
selectedUser = action.user
|
||||
selectedUser = event.targetUser
|
||||
unbanUserAsyncAction.value = AsyncAction.ConfirmingNoParams
|
||||
}
|
||||
}
|
||||
@@ -112,18 +113,18 @@ class RoomMemberModerationPresenter @Inject constructor(
|
||||
}
|
||||
selectedUser = null
|
||||
}
|
||||
is InternalRoomMemberModerationEvents.Reset -> {
|
||||
selectedUser = null
|
||||
kickUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
banUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
unbanUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
is InternalRoomMemberModerationEvents.DoUnbanUser -> {
|
||||
selectedUser?.let {
|
||||
coroutineScope.unbanUser(it.userId, unbanUserAsyncAction)
|
||||
}
|
||||
selectedUser = null
|
||||
}
|
||||
is InternalRoomMemberModerationEvents.Reset -> {
|
||||
selectedUser = null
|
||||
kickUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
banUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
unbanUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,20 +141,27 @@ class RoomMemberModerationPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
private fun computeModerationActions(
|
||||
member: RoomMember,
|
||||
member: RoomMember?,
|
||||
canKick: Boolean,
|
||||
canBan: Boolean,
|
||||
currentUserMemberPowerLevel: Long,
|
||||
): PersistentList<ModerationAction> {
|
||||
val memberAsUser = member.toMatrixUser()
|
||||
): PersistentList<ModerationActionState> {
|
||||
return buildList {
|
||||
add(ModerationAction.DisplayProfile(memberAsUser))
|
||||
val canModerateThisUser = member.powerLevel < currentUserMemberPowerLevel && member.membership.isActive()
|
||||
if (canKick && canModerateThisUser) {
|
||||
add(ModerationAction.KickUser(memberAsUser))
|
||||
add(ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true))
|
||||
// Assume the member is a regular user when it's unknown
|
||||
val canModerateThisUser = (member?.powerLevel ?: 0) < currentUserMemberPowerLevel
|
||||
// Assume the member is joined when it's unknown
|
||||
val membership = member?.membership ?: RoomMembershipState.JOIN
|
||||
if (canKick) {
|
||||
val isKickEnabled = canModerateThisUser && membership.isActive()
|
||||
add(ModerationActionState(action = ModerationAction.KickUser, isEnabled = isKickEnabled))
|
||||
}
|
||||
if (canBan && canModerateThisUser) {
|
||||
add(ModerationAction.BanUser(memberAsUser))
|
||||
if (canBan) {
|
||||
if (membership == RoomMembershipState.BAN) {
|
||||
add(ModerationActionState(action = ModerationAction.UnbanUser, isEnabled = canModerateThisUser))
|
||||
} else {
|
||||
add(ModerationActionState(action = ModerationAction.BanUser, isEnabled = canModerateThisUser))
|
||||
}
|
||||
}
|
||||
}.toPersistentList()
|
||||
}
|
||||
@@ -204,5 +212,4 @@ class RoomMemberModerationPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ package io.element.android.features.roommembermoderation.impl
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.roommembermoderation.api.ModerationAction
|
||||
import io.element.android.features.roommembermoderation.api.ModerationActionState
|
||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
@@ -25,24 +26,32 @@ class RoomMemberModerationStateProvider : PreviewParameterProvider<InternalRoomM
|
||||
aRoomMembersModerationState(
|
||||
selectedUser = anAlice(),
|
||||
actions = listOf(
|
||||
ModerationAction.DisplayProfile(anAlice()),
|
||||
ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true),
|
||||
),
|
||||
),
|
||||
aRoomMembersModerationState(
|
||||
selectedUser = anAlice(),
|
||||
actions = listOf(
|
||||
ModerationAction.DisplayProfile(anAlice()),
|
||||
ModerationAction.KickUser(anAlice()),
|
||||
ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true),
|
||||
ModerationActionState(action = ModerationAction.KickUser, isEnabled = true),
|
||||
),
|
||||
),
|
||||
aRoomMembersModerationState(
|
||||
selectedUser = anAlice(),
|
||||
actions = listOf(
|
||||
ModerationAction.DisplayProfile(anAlice()),
|
||||
ModerationAction.KickUser(anAlice()),
|
||||
ModerationAction.BanUser(anAlice()),
|
||||
ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true),
|
||||
ModerationActionState(action = ModerationAction.KickUser, isEnabled = false),
|
||||
ModerationActionState(action = ModerationAction.BanUser, isEnabled = true),
|
||||
),
|
||||
),
|
||||
aRoomMembersModerationState(
|
||||
selectedUser = anAlice(),
|
||||
actions = listOf(
|
||||
ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true),
|
||||
ModerationActionState(action = ModerationAction.KickUser, isEnabled = false),
|
||||
ModerationActionState(action = ModerationAction.UnbanUser, isEnabled = true),
|
||||
),
|
||||
),
|
||||
aRoomMembersModerationState(
|
||||
selectedUser = anAlice(),
|
||||
kickUserAsyncAction = AsyncAction.ConfirmingNoParams,
|
||||
@@ -72,7 +81,7 @@ fun aRoomMembersModerationState(
|
||||
canKick: Boolean = false,
|
||||
canBan: Boolean = false,
|
||||
selectedUser: MatrixUser? = null,
|
||||
actions: List<ModerationAction> = emptyList(),
|
||||
actions: List<ModerationActionState> = emptyList(),
|
||||
kickUserAsyncAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
banUserAsyncAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
unbanUserAsyncAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
|
||||
@@ -31,6 +31,7 @@ import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.roommembermoderation.api.ModerationAction
|
||||
import io.element.android.features.roommembermoderation.api.ModerationActionState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncIndicator
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
|
||||
@@ -58,7 +59,7 @@ import timber.log.Timber
|
||||
@Composable
|
||||
fun RoomMemberModerationView(
|
||||
state: InternalRoomMemberModerationState,
|
||||
onSelectAction: (ModerationAction) -> Unit,
|
||||
onSelectAction: (ModerationAction, MatrixUser) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
@@ -201,8 +202,8 @@ private fun RoomMemberAsyncActions(
|
||||
@Composable
|
||||
private fun RoomMemberActionsBottomSheet(
|
||||
user: MatrixUser,
|
||||
actions: ImmutableList<ModerationAction>,
|
||||
onSelectAction: (ModerationAction) -> Unit,
|
||||
actions: ImmutableList<ModerationActionState>,
|
||||
onSelectAction: (ModerationAction, MatrixUser) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
@@ -223,8 +224,8 @@ private fun RoomMemberActionsBottomSheet(
|
||||
Avatar(
|
||||
avatarData = user.getAvatarData(size = AvatarSize.RoomListManageUser),
|
||||
modifier = Modifier
|
||||
.padding(bottom = 28.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(bottom = 28.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
user.displayName?.let {
|
||||
Text(
|
||||
@@ -234,8 +235,8 @@ private fun RoomMemberActionsBottomSheet(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 8.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 8.dp)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
Text(
|
||||
@@ -246,35 +247,38 @@ private fun RoomMemberActionsBottomSheet(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
for (action in actions) {
|
||||
when (action) {
|
||||
for (actionState in actions) {
|
||||
when (val action = actionState.action) {
|
||||
is ModerationAction.DisplayProfile -> {
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.screen_bottom_sheet_manage_room_member_member_user_info)) },
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Info())),
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
onSelectAction(action)
|
||||
onSelectAction(action, user)
|
||||
bottomSheetState.hide()
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = actionState.isEnabled
|
||||
)
|
||||
}
|
||||
is ModerationAction.KickUser -> {
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.screen_bottom_sheet_manage_room_member_remove)) },
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Block())),
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Close())),
|
||||
style = ListItemStyle.Destructive,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
bottomSheetState.hide()
|
||||
onSelectAction(action)
|
||||
onSelectAction(action, user)
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = actionState.isEnabled
|
||||
)
|
||||
}
|
||||
is ModerationAction.BanUser -> {
|
||||
@@ -285,12 +289,26 @@ private fun RoomMemberActionsBottomSheet(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
bottomSheetState.hide()
|
||||
onSelectAction(action)
|
||||
onSelectAction(action, user)
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = actionState.isEnabled
|
||||
)
|
||||
}
|
||||
is ModerationAction.UnbanUser -> {
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.screen_room_member_list_manage_member_unban_action)) },
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Restart())),
|
||||
style = ListItemStyle.Destructive,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
bottomSheetState.hide()
|
||||
onSelectAction(action, user)
|
||||
}
|
||||
},
|
||||
enabled = actionState.isEnabled
|
||||
)
|
||||
}
|
||||
is ModerationAction.UnbanUser -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -303,12 +321,14 @@ internal fun RoomMembersModerationViewPreview(@PreviewParameter(RoomMemberModera
|
||||
ElementPreview {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 64.dp)
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 64.dp)
|
||||
) {
|
||||
RoomMemberModerationView(
|
||||
state = state,
|
||||
onSelectAction = {},
|
||||
onSelectAction = { _, _ ->
|
||||
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user