change(room permissions): fix some role&permissions inconsistencies after last changes

This commit is contained in:
ganfra 2025-12-18 21:37:56 +01:00
parent a0d6fddf73
commit 4c2aa0ba33
5 changed files with 47 additions and 29 deletions

View file

@ -36,6 +36,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
import io.element.android.libraries.matrix.api.room.powerlevels.usersWithRole import io.element.android.libraries.matrix.api.room.powerlevels.usersWithRole
import io.element.android.libraries.matrix.api.room.toMatrixUser import io.element.android.libraries.matrix.api.room.toMatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.model.powerLevelOf
import io.element.android.libraries.matrix.ui.model.roleOf import io.element.android.libraries.matrix.ui.model.roleOf
import io.element.android.libraries.matrix.ui.room.PowerLevelRoomMemberComparator import io.element.android.libraries.matrix.ui.room.PowerLevelRoomMemberComparator
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
@ -124,9 +125,10 @@ class ChangeRolesPresenter(
val roomInfo by room.roomInfoFlow.collectAsState() val roomInfo by room.roomInfoFlow.collectAsState()
fun canChangeMemberRole(userId: UserId): Boolean { fun canChangeMemberRole(userId: UserId): Boolean {
val currentUserRole = roomInfo.roleOf(room.sessionId) val currentUserPowerLevel = roomInfo.powerLevelOf(room.sessionId)
val otherUserRole = roomInfo.roleOf(userId) val otherUserPowerLevel = roomInfo.powerLevelOf(userId)
return currentUserRole.powerLevel > otherUserRole.powerLevel return currentUserPowerLevel > otherUserPowerLevel &&
currentUserPowerLevel >= role.powerLevel
} }
fun handleEvent(event: ChangeRolesEvent) { fun handleEvent(event: ChangeRolesEvent) {

View file

@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
import io.element.android.libraries.matrix.api.room.powerlevels.userCountWithRole import io.element.android.libraries.matrix.api.room.powerlevels.userCountWithRole
import io.element.android.libraries.matrix.ui.model.roleOf import io.element.android.libraries.matrix.ui.model.roleOf
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -49,7 +50,16 @@ class RolesAndPermissionsPresenter(
room.userCountWithRole { role -> role is RoomMember.Role.Admin || role is RoomMember.Role.Owner } room.userCountWithRole { role -> role is RoomMember.Role.Admin || role is RoomMember.Role.Owner }
}.collectAsState(null) }.collectAsState(null)
val canDemoteSelf = remember { derivedStateOf { roomInfo.roleOf(room.sessionId) !is RoomMember.Role.Owner } } val availableDemoteActions by remember {
derivedStateOf {
val currentRole = roomInfo.roleOf(room.sessionId)
when (currentRole) {
is RoomMember.Role.Admin -> persistentListOf(DemoteActions.ToModerator, DemoteActions.ToMember)
is RoomMember.Role.Moderator -> persistentListOf(DemoteActions.ToMember)
else -> persistentListOf()
}
}
}
val changeOwnRoleAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) } val changeOwnRoleAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
val resetPermissionsAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) } val resetPermissionsAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
@ -78,7 +88,7 @@ class RolesAndPermissionsPresenter(
roomSupportsOwnerRole = roomInfo.privilegedCreatorRole, roomSupportsOwnerRole = roomInfo.privilegedCreatorRole,
adminCount = adminCount, adminCount = adminCount,
moderatorCount = moderatorCount, moderatorCount = moderatorCount,
canDemoteSelf = canDemoteSelf.value, availableDemoteActions = availableDemoteActions,
changeOwnRoleAction = changeOwnRoleAction.value, changeOwnRoleAction = changeOwnRoleAction.value,
resetPermissionsAction = resetPermissionsAction.value, resetPermissionsAction = resetPermissionsAction.value,
eventSink = ::handleEvent, eventSink = ::handleEvent,

View file

@ -8,14 +8,24 @@
package io.element.android.features.rolesandpermissions.impl.root package io.element.android.features.rolesandpermissions.impl.root
import io.element.android.features.rolesandpermissions.impl.R
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.room.RoomMember
import kotlinx.collections.immutable.ImmutableList
data class RolesAndPermissionsState( data class RolesAndPermissionsState(
val roomSupportsOwnerRole: Boolean, val roomSupportsOwnerRole: Boolean,
val adminCount: Int?, val adminCount: Int?,
val moderatorCount: Int?, val moderatorCount: Int?,
val canDemoteSelf: Boolean, val availableDemoteActions: ImmutableList<DemoteActions>,
val changeOwnRoleAction: AsyncAction<Unit>, val changeOwnRoleAction: AsyncAction<Unit>,
val resetPermissionsAction: AsyncAction<Unit>, val resetPermissionsAction: AsyncAction<Unit>,
val eventSink: (RolesAndPermissionsEvents) -> Unit, val eventSink: (RolesAndPermissionsEvents) -> Unit,
) ) {
val canDemoteSelf = availableDemoteActions.isNotEmpty()
}
enum class DemoteActions(val role: RoomMember.Role, val titleRes: Int) {
ToModerator(RoomMember.Role.Moderator, R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator),
ToMember(RoomMember.Role.User, R.string.screen_room_roles_and_permissions_change_role_demote_to_member)
}

View file

@ -10,6 +10,7 @@ package io.element.android.features.rolesandpermissions.impl.root
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import kotlinx.collections.immutable.toImmutableList
class RolesAndPermissionsStateProvider : PreviewParameterProvider<RolesAndPermissionsState> { class RolesAndPermissionsStateProvider : PreviewParameterProvider<RolesAndPermissionsState> {
override val values: Sequence<RolesAndPermissionsState> override val values: Sequence<RolesAndPermissionsState>
@ -46,7 +47,7 @@ class RolesAndPermissionsStateProvider : PreviewParameterProvider<RolesAndPermis
moderatorCount = 2, moderatorCount = 2,
resetPermissionsAction = AsyncAction.Failure(IllegalStateException("Failed to reset permissions")), resetPermissionsAction = AsyncAction.Failure(IllegalStateException("Failed to reset permissions")),
), ),
aRolesAndPermissionsState(canDemoteSelf = false), aRolesAndPermissionsState(availableDemoteActions = emptyList()),
) )
} }
@ -54,14 +55,14 @@ internal fun aRolesAndPermissionsState(
roomSupportsOwners: Boolean = true, roomSupportsOwners: Boolean = true,
adminCount: Int = 0, adminCount: Int = 0,
moderatorCount: Int = 0, moderatorCount: Int = 0,
canDemoteSelf: Boolean = true, availableDemoteActions: List<DemoteActions> = listOf(DemoteActions.ToModerator, DemoteActions.ToMember),
changeOwnRoleAction: AsyncAction<Unit> = AsyncAction.Uninitialized, changeOwnRoleAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
resetPermissionsAction: AsyncAction<Unit> = AsyncAction.Uninitialized, resetPermissionsAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
eventSink: (RolesAndPermissionsEvents) -> Unit = {}, eventSink: (RolesAndPermissionsEvents) -> Unit = {},
) = RolesAndPermissionsState( ) = RolesAndPermissionsState(
roomSupportsOwnerRole = roomSupportsOwners, roomSupportsOwnerRole = roomSupportsOwners,
adminCount = adminCount, adminCount = adminCount,
canDemoteSelf = canDemoteSelf, availableDemoteActions = availableDemoteActions.toImmutableList(),
moderatorCount = moderatorCount, moderatorCount = moderatorCount,
changeOwnRoleAction = changeOwnRoleAction, changeOwnRoleAction = changeOwnRoleAction,
resetPermissionsAction = resetPermissionsAction, resetPermissionsAction = resetPermissionsAction,

View file

@ -39,8 +39,8 @@ import io.element.android.libraries.designsystem.theme.components.ListSectionHea
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.hide import io.element.android.libraries.designsystem.theme.components.hide
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
@Composable @Composable
fun RolesAndPermissionsView( fun RolesAndPermissionsView(
@ -117,6 +117,7 @@ fun RolesAndPermissionsView(
when (state.changeOwnRoleAction) { when (state.changeOwnRoleAction) {
is AsyncAction.Confirming -> { is AsyncAction.Confirming -> {
ChangeOwnRoleBottomSheet( ChangeOwnRoleBottomSheet(
availableDemoteActions = state.availableDemoteActions,
eventSink = state.eventSink, eventSink = state.eventSink,
) )
} }
@ -136,6 +137,7 @@ fun RolesAndPermissionsView(
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun ChangeOwnRoleBottomSheet( private fun ChangeOwnRoleBottomSheet(
availableDemoteActions: ImmutableList<DemoteActions>,
eventSink: (RolesAndPermissionsEvents) -> Unit, eventSink: (RolesAndPermissionsEvents) -> Unit,
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
@ -164,24 +166,17 @@ private fun ChangeOwnRoleBottomSheet(
style = ElementTheme.typography.fontBodyLgRegular, style = ElementTheme.typography.fontBodyLgRegular,
color = ElementTheme.colors.textPrimary, color = ElementTheme.colors.textPrimary,
) )
ListItem( for (demoteAction in availableDemoteActions) {
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator)) }, ListItem(
onClick = { headlineContent = { Text(stringResource(demoteAction.titleRes)) },
sheetState.hide(coroutineScope) { onClick = {
eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator)) sheetState.hide(coroutineScope) {
} eventSink(RolesAndPermissionsEvents.DemoteSelfTo(demoteAction.role))
}, }
style = ListItemStyle.Destructive, },
) style = ListItemStyle.Destructive,
ListItem( )
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_role_demote_to_member)) }, }
onClick = {
sheetState.hide(coroutineScope) {
eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.User))
}
},
style = ListItemStyle.Destructive,
)
ListItem( ListItem(
headlineContent = { Text(stringResource(CommonStrings.action_cancel)) }, headlineContent = { Text(stringResource(CommonStrings.action_cancel)) },
onClick = ::dismiss, onClick = ::dismiss,