change(roles and permissions): update change permission design

This commit is contained in:
ganfra 2025-11-03 21:40:37 +01:00
parent 3b5c2bb8eb
commit 08227d4403
10 changed files with 145 additions and 167 deletions

View file

@ -23,7 +23,6 @@ import io.element.android.annotations.ContributesNode
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
import io.element.android.features.rolesandpermissions.impl.permissions.ChangeRoomPermissionsNode import io.element.android.features.rolesandpermissions.impl.permissions.ChangeRoomPermissionsNode
import io.element.android.features.rolesandpermissions.impl.permissions.ChangeRoomPermissionsSection
import io.element.android.features.rolesandpermissions.impl.root.RolesAndPermissionsNode import io.element.android.features.rolesandpermissions.impl.root.RolesAndPermissionsNode
import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.BaseFlowNode
@ -59,7 +58,7 @@ class RolesAndPermissionsFlowNode(
data object ModeratorList : NavTarget data object ModeratorList : NavTarget
@Parcelize @Parcelize
data class ChangeRoomPermissions(val section: ChangeRoomPermissionsSection) : NavTarget data object ChangeRoomPermissions: NavTarget
} }
override fun onBuilt() { override fun onBuilt() {
@ -84,17 +83,10 @@ class RolesAndPermissionsFlowNode(
backstack.push(NavTarget.ModeratorList) backstack.push(NavTarget.ModeratorList)
} }
override fun openEditRoomDetailsPermissions() { override fun openEditPermissions() {
backstack.push(NavTarget.ChangeRoomPermissions(ChangeRoomPermissionsSection.RoomDetails)) backstack.push(NavTarget.ChangeRoomPermissions)
} }
override fun openMessagesAndContentPermissions() {
backstack.push(NavTarget.ChangeRoomPermissions(ChangeRoomPermissionsSection.MessagesAndContent))
}
override fun openModerationPermissions() {
backstack.push(NavTarget.ChangeRoomPermissions(ChangeRoomPermissionsSection.MembershipModeration))
}
} }
createNode<RolesAndPermissionsNode>( createNode<RolesAndPermissionsNode>(
buildContext = buildContext, buildContext = buildContext,
@ -118,11 +110,7 @@ class RolesAndPermissionsFlowNode(
) )
} }
is NavTarget.ChangeRoomPermissions -> { is NavTarget.ChangeRoomPermissions -> {
val inputs = ChangeRoomPermissionsNode.Inputs(navTarget.section) createNode<ChangeRoomPermissionsNode>(buildContext = buildContext)
createNode<ChangeRoomPermissionsNode>(
buildContext = buildContext,
plugins = listOf(inputs),
)
} }
} }
} }

View file

@ -7,10 +7,8 @@
package io.element.android.features.rolesandpermissions.impl.permissions package io.element.android.features.rolesandpermissions.impl.permissions
import io.element.android.libraries.matrix.api.room.RoomMember
interface ChangeRoomPermissionsEvent { interface ChangeRoomPermissionsEvent {
data class ChangeMinimumRoleForAction(val action: RoomPermissionType, val role: RoomMember.Role) : ChangeRoomPermissionsEvent data class ChangeMinimumRoleForAction(val action: RoomPermissionType, val role: SelectableRole) : ChangeRoomPermissionsEvent
data object Save : ChangeRoomPermissionsEvent data object Save : ChangeRoomPermissionsEvent
data object Exit : ChangeRoomPermissionsEvent data object Exit : ChangeRoomPermissionsEvent
data object ResetPendingActions : ChangeRoomPermissionsEvent data object ResetPendingActions : ChangeRoomPermissionsEvent

View file

@ -7,7 +7,6 @@
package io.element.android.features.rolesandpermissions.impl.permissions package io.element.android.features.rolesandpermissions.impl.permissions
import android.os.Parcelable
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
@ -16,25 +15,15 @@ import com.bumble.appyx.core.plugin.Plugin
import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode import io.element.android.annotations.ContributesNode
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import kotlinx.parcelize.Parcelize
@ContributesNode(RoomScope::class) @ContributesNode(RoomScope::class)
@AssistedInject @AssistedInject
class ChangeRoomPermissionsNode( class ChangeRoomPermissionsNode(
@Assisted buildContext: BuildContext, @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
presenterFactory: ChangeRoomPermissionsPresenter.Factory, private val presenter: ChangeRoomPermissionsPresenter,
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
@Parcelize
data class Inputs(
val section: ChangeRoomPermissionsSection,
) : NodeInputs, Parcelable
private val inputs: Inputs = inputs()
private val presenter = presenterFactory.create(inputs.section)
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
@ -46,10 +35,3 @@ class ChangeRoomPermissionsNode(
) )
} }
} }
@Parcelize
enum class ChangeRoomPermissionsSection : Parcelable {
RoomDetails,
MessagesAndContent,
MembershipModeration,
}

View file

@ -15,50 +15,50 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject
import io.element.android.features.rolesandpermissions.impl.analytics.trackPermissionChangeAnalytics import io.element.android.features.rolesandpermissions.impl.analytics.trackPermissionChangeAnalytics
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.room.JoinedRoom 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.powerlevels.RoomPowerLevelsValues import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@AssistedInject @Inject
class ChangeRoomPermissionsPresenter( class ChangeRoomPermissionsPresenter(
@Assisted private val section: ChangeRoomPermissionsSection,
private val room: JoinedRoom, private val room: JoinedRoom,
private val analyticsService: AnalyticsService, private val analyticsService: AnalyticsService,
) : Presenter<ChangeRoomPermissionsState> { ) : Presenter<ChangeRoomPermissionsState> {
companion object { companion object {
internal fun itemsForSection(section: ChangeRoomPermissionsSection) = when (section) { private fun itemsForSection(section: RoomPermissionsSection) = when (section) {
ChangeRoomPermissionsSection.RoomDetails -> persistentListOf( RoomPermissionsSection.RoomDetails -> persistentListOf(
RoomPermissionType.ROOM_NAME, RoomPermissionType.ROOM_NAME,
RoomPermissionType.ROOM_AVATAR, RoomPermissionType.ROOM_AVATAR,
RoomPermissionType.ROOM_TOPIC, RoomPermissionType.ROOM_TOPIC,
) )
ChangeRoomPermissionsSection.MessagesAndContent -> persistentListOf( RoomPermissionsSection.MessagesAndContent -> persistentListOf(
RoomPermissionType.SEND_EVENTS, RoomPermissionType.SEND_EVENTS,
RoomPermissionType.REDACT_EVENTS, RoomPermissionType.REDACT_EVENTS,
) )
ChangeRoomPermissionsSection.MembershipModeration -> persistentListOf( RoomPermissionsSection.MembershipModeration -> persistentListOf(
RoomPermissionType.INVITE, RoomPermissionType.INVITE,
RoomPermissionType.KICK, RoomPermissionType.KICK,
RoomPermissionType.BAN, RoomPermissionType.BAN,
) )
} }
}
@AssistedFactory internal fun buildItems(forSpace: Boolean) = persistentMapOf(
interface Factory { RoomPermissionsSection.RoomDetails to itemsForSection(RoomPermissionsSection.RoomDetails),
fun create(section: ChangeRoomPermissionsSection): ChangeRoomPermissionsPresenter RoomPermissionsSection.MessagesAndContent to itemsForSection(RoomPermissionsSection.MessagesAndContent),
RoomPermissionsSection.MembershipModeration to itemsForSection(RoomPermissionsSection.MembershipModeration),
)
} }
private val items: ImmutableList<RoomPermissionType> = itemsForSection(section) private val items = buildItems(forSpace = room.info().isSpace)
private var initialPermissions by mutableStateOf<RoomPowerLevelsValues?>(null) private var initialPermissions by mutableStateOf<RoomPowerLevelsValues?>(null)
private var currentPermissions by mutableStateOf<RoomPowerLevelsValues?>(null) private var currentPermissions by mutableStateOf<RoomPowerLevelsValues?>(null)
@ -80,15 +80,20 @@ class ChangeRoomPermissionsPresenter(
fun handleEvent(event: ChangeRoomPermissionsEvent) { fun handleEvent(event: ChangeRoomPermissionsEvent) {
when (event) { when (event) {
is ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction -> { is ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction -> {
val powerLevel = when (event.role) {
SelectableRole.Admin -> RoomMember.Role.Admin.powerLevel
SelectableRole.Moderator -> RoomMember.Role.Moderator.powerLevel
SelectableRole.Everyone -> RoomMember.Role.User.powerLevel
}
currentPermissions = when (event.action) { currentPermissions = when (event.action) {
RoomPermissionType.BAN -> currentPermissions?.copy(ban = event.role.powerLevel) RoomPermissionType.BAN -> currentPermissions?.copy(ban = powerLevel)
RoomPermissionType.INVITE -> currentPermissions?.copy(invite = event.role.powerLevel) RoomPermissionType.INVITE -> currentPermissions?.copy(invite = powerLevel)
RoomPermissionType.KICK -> currentPermissions?.copy(kick = event.role.powerLevel) RoomPermissionType.KICK -> currentPermissions?.copy(kick = powerLevel)
RoomPermissionType.SEND_EVENTS -> currentPermissions?.copy(sendEvents = event.role.powerLevel) RoomPermissionType.SEND_EVENTS -> currentPermissions?.copy(sendEvents = powerLevel)
RoomPermissionType.REDACT_EVENTS -> currentPermissions?.copy(redactEvents = event.role.powerLevel) RoomPermissionType.REDACT_EVENTS -> currentPermissions?.copy(redactEvents = powerLevel)
RoomPermissionType.ROOM_NAME -> currentPermissions?.copy(roomName = event.role.powerLevel) RoomPermissionType.ROOM_NAME -> currentPermissions?.copy(roomName = powerLevel)
RoomPermissionType.ROOM_AVATAR -> currentPermissions?.copy(roomAvatar = event.role.powerLevel) RoomPermissionType.ROOM_AVATAR -> currentPermissions?.copy(roomAvatar = powerLevel)
RoomPermissionType.ROOM_TOPIC -> currentPermissions?.copy(roomTopic = event.role.powerLevel) RoomPermissionType.ROOM_TOPIC -> currentPermissions?.copy(roomTopic = powerLevel)
} }
} }
is ChangeRoomPermissionsEvent.Save -> coroutineScope.save() is ChangeRoomPermissionsEvent.Save -> coroutineScope.save()
@ -106,9 +111,8 @@ class ChangeRoomPermissionsPresenter(
} }
} }
return ChangeRoomPermissionsState( return ChangeRoomPermissionsState(
section = section,
currentPermissions = currentPermissions, currentPermissions = currentPermissions,
items = items, itemsBySection = items,
hasChanges = hasChanges, hasChanges = hasChanges,
saveAction = saveAction, saveAction = saveAction,
confirmExitAction = confirmExitAction, confirmExitAction = confirmExitAction,

View file

@ -7,19 +7,71 @@
package io.element.android.features.rolesandpermissions.impl.permissions package io.element.android.features.rolesandpermissions.impl.permissions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.res.stringResource
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.designsystem.components.preferences.DropdownOption
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
data class ChangeRoomPermissionsState( data class ChangeRoomPermissionsState(
val section: ChangeRoomPermissionsSection,
val currentPermissions: RoomPowerLevelsValues?, val currentPermissions: RoomPowerLevelsValues?,
val items: ImmutableList<RoomPermissionType>, val itemsBySection: ImmutableMap<RoomPermissionsSection, ImmutableList<RoomPermissionType>>,
val hasChanges: Boolean, val hasChanges: Boolean,
val saveAction: AsyncAction<Unit>, val saveAction: AsyncAction<Unit>,
val confirmExitAction: AsyncAction<Unit>, val confirmExitAction: AsyncAction<Unit>,
val eventSink: (ChangeRoomPermissionsEvent) -> Unit, val eventSink: (ChangeRoomPermissionsEvent) -> Unit,
) ){
fun selectedRoleForType(type: RoomPermissionType): SelectableRole? {
if(currentPermissions == null) return null
val role = when (type) {
RoomPermissionType.BAN -> RoomMember.Role.forPowerLevel(currentPermissions.ban)
RoomPermissionType.INVITE -> RoomMember.Role.forPowerLevel(currentPermissions.invite)
RoomPermissionType.KICK -> RoomMember.Role.forPowerLevel(currentPermissions.kick)
RoomPermissionType.SEND_EVENTS -> RoomMember.Role.forPowerLevel(currentPermissions.sendEvents)
RoomPermissionType.REDACT_EVENTS -> RoomMember.Role.forPowerLevel(currentPermissions.redactEvents)
RoomPermissionType.ROOM_NAME -> RoomMember.Role.forPowerLevel(currentPermissions.roomName)
RoomPermissionType.ROOM_AVATAR -> RoomMember.Role.forPowerLevel(currentPermissions.roomAvatar)
RoomPermissionType.ROOM_TOPIC -> RoomMember.Role.forPowerLevel(currentPermissions.roomTopic)
}
return when(role){
is RoomMember.Role.Owner,
RoomMember.Role.Admin -> SelectableRole.Admin
RoomMember.Role.Moderator -> SelectableRole.Moderator
RoomMember.Role.User -> SelectableRole.Everyone
}
}
}
enum class RoomPermissionsSection {
RoomDetails,
MessagesAndContent,
MembershipModeration,
}
enum class SelectableRole: DropdownOption {
Admin {
@Composable
@ReadOnlyComposable
override fun getText(): String = stringResource(R.string.screen_room_member_list_role_administrator)
},
Moderator {
@Composable
@ReadOnlyComposable
override fun getText(): String = stringResource(R.string.screen_room_member_list_role_moderator)
},
Everyone {
@Composable
@ReadOnlyComposable
override fun getText(): String = stringResource(R.string.screen_room_change_permissions_everyone)
}
}
enum class RoomPermissionType { enum class RoomPermissionType {
BAN, BAN,

View file

@ -11,41 +11,33 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableMap
class ChangeRoomPermissionsStateProvider : PreviewParameterProvider<ChangeRoomPermissionsState> { class ChangeRoomPermissionsStateProvider : PreviewParameterProvider<ChangeRoomPermissionsState> {
override val values: Sequence<ChangeRoomPermissionsState> override val values: Sequence<ChangeRoomPermissionsState>
get() = sequenceOf( get() = sequenceOf(
aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails), aChangeRoomPermissionsState(),
aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.MessagesAndContent), aChangeRoomPermissionsState(hasChanges = true),
aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.MembershipModeration), aChangeRoomPermissionsState(hasChanges = true, saveAction = AsyncAction.Loading),
aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true),
aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, saveAction = AsyncAction.Loading),
aChangeRoomPermissionsState( aChangeRoomPermissionsState(
section = ChangeRoomPermissionsSection.RoomDetails,
hasChanges = true, hasChanges = true,
saveAction = AsyncAction.Failure(IllegalStateException("Failed to save changes")) saveAction = AsyncAction.Failure(IllegalStateException("Failed to save changes"))
), ),
aChangeRoomPermissionsState( aChangeRoomPermissionsState(hasChanges = true, confirmExitAction = AsyncAction.ConfirmingNoParams),
section = ChangeRoomPermissionsSection.RoomDetails,
hasChanges = true,
confirmExitAction = AsyncAction.ConfirmingNoParams,
),
) )
} }
internal fun aChangeRoomPermissionsState( internal fun aChangeRoomPermissionsState(
section: ChangeRoomPermissionsSection,
currentPermissions: RoomPowerLevelsValues = previewPermissions(), currentPermissions: RoomPowerLevelsValues = previewPermissions(),
items: List<RoomPermissionType> = ChangeRoomPermissionsPresenter.itemsForSection(section), itemsBySection: Map<RoomPermissionsSection, ImmutableList<RoomPermissionType>> = ChangeRoomPermissionsPresenter.buildItems(false),
hasChanges: Boolean = false, hasChanges: Boolean = false,
saveAction: AsyncAction<Unit> = AsyncAction.Uninitialized, saveAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
confirmExitAction: AsyncAction<Unit> = AsyncAction.Uninitialized, confirmExitAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
eventSink: (ChangeRoomPermissionsEvent) -> Unit = {}, eventSink: (ChangeRoomPermissionsEvent) -> Unit = {},
) = ChangeRoomPermissionsState( ) = ChangeRoomPermissionsState(
section = section,
currentPermissions = currentPermissions, currentPermissions = currentPermissions,
items = items.toImmutableList(), itemsBySection = itemsBySection.toImmutableMap(),
hasChanges = hasChanges, hasChanges = hasChanges,
saveAction = saveAction, saveAction = saveAction,
confirmExitAction = confirmExitAction, confirmExitAction = confirmExitAction,

View file

@ -23,6 +23,7 @@ import io.element.android.libraries.designsystem.components.async.AsyncActionVie
import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.list.ListItemContent
import io.element.android.libraries.designsystem.components.preferences.PreferenceDropdown
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.IconSource import io.element.android.libraries.designsystem.theme.components.IconSource
@ -36,6 +37,7 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.toImmutableList
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -50,13 +52,8 @@ fun ChangeRoomPermissionsView(
Scaffold( Scaffold(
modifier = modifier, modifier = modifier,
topBar = { topBar = {
val title = when (state.section) {
ChangeRoomPermissionsSection.RoomDetails -> stringResource(R.string.screen_room_change_permissions_room_details)
ChangeRoomPermissionsSection.MessagesAndContent -> stringResource(R.string.screen_room_change_permissions_messages_and_content)
ChangeRoomPermissionsSection.MembershipModeration -> stringResource(R.string.screen_room_change_permissions_member_moderation)
}
TopAppBar( TopAppBar(
titleStr = title, titleStr = stringResource(R.string.screen_room_roles_and_permissions_permissions_header),
navigationIcon = { navigationIcon = {
BackButton(onClick = { state.eventSink(ChangeRoomPermissionsEvent.Exit) }) BackButton(onClick = { state.eventSink(ChangeRoomPermissionsEvent.Exit) })
}, },
@ -75,29 +72,25 @@ fun ChangeRoomPermissionsView(
.padding(padding) .padding(padding)
.fillMaxSize() .fillMaxSize()
) { ) {
for ((index, permissionItem) in state.items.withIndex()) { state.itemsBySection.onEachIndexed { index, (section, items) ->
item { item {
ListSectionHeader(titleForSection(item = permissionItem), hasDivider = index > 0) ListSectionHeader(titleForSection(section), hasDivider = index>0)
SelectRoleItem( }
permissionsItem = permissionItem, for (permissionType in items) {
role = RoomMember.Role.Admin, item {
currentPermissions = state.currentPermissions PreferenceDropdown(
) { item, role -> title = titleForType(permissionType),
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) selectedOption = state.selectedRoleForType(permissionType),
} options = SelectableRole.entries.toImmutableList(),
SelectRoleItem( onSelectOption = { role ->
permissionsItem = permissionItem, state.eventSink(
role = RoomMember.Role.Moderator, ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(
currentPermissions = state.currentPermissions action = permissionType,
) { item, role -> role = role
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) )
} )
SelectRoleItem( }
permissionsItem = permissionItem, )
role = RoomMember.Role.User,
currentPermissions = state.currentPermissions
) { item, role ->
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role))
} }
} }
} }
@ -126,47 +119,15 @@ fun ChangeRoomPermissionsView(
onErrorDismiss = {}, onErrorDismiss = {},
) )
} }
@Composable @Composable
private fun SelectRoleItem( private fun titleForSection(section: RoomPermissionsSection): String = when (section) {
permissionsItem: RoomPermissionType, RoomPermissionsSection.RoomDetails -> stringResource(R.string.screen_room_change_permissions_room_details)
role: RoomMember.Role, RoomPermissionsSection.MessagesAndContent -> stringResource(R.string.screen_room_change_permissions_messages_and_content)
currentPermissions: RoomPowerLevelsValues?, RoomPermissionsSection.MembershipModeration -> stringResource(R.string.screen_room_change_permissions_member_moderation)
onClick: (RoomPermissionType, RoomMember.Role) -> Unit
) {
val title = when (role) {
RoomMember.Role.Admin -> stringResource(R.string.screen_room_change_permissions_administrators)
RoomMember.Role.Moderator -> stringResource(R.string.screen_room_change_permissions_moderators)
RoomMember.Role.User -> stringResource(R.string.screen_room_change_permissions_everyone)
else -> error("Unsupported role selected: $role")
}
ListItem(
headlineContent = { Text(text = title) },
trailingContent = if (currentPermissions?.isSelected(permissionsItem, role).orFalse()) {
ListItemContent.Icon(IconSource.Vector(CompoundIcons.Check()))
} else {
null
},
style = ListItemStyle.Primary,
onClick = { onClick(permissionsItem, role) },
)
}
private fun RoomPowerLevelsValues.isSelected(item: RoomPermissionType, role: RoomMember.Role): Boolean {
return when (item) {
RoomPermissionType.BAN -> RoomMember.Role.forPowerLevel(ban) == role
RoomPermissionType.INVITE -> RoomMember.Role.forPowerLevel(invite) == role
RoomPermissionType.KICK -> RoomMember.Role.forPowerLevel(kick) == role
RoomPermissionType.SEND_EVENTS -> RoomMember.Role.forPowerLevel(sendEvents) == role
RoomPermissionType.REDACT_EVENTS -> RoomMember.Role.forPowerLevel(redactEvents) == role
RoomPermissionType.ROOM_NAME -> RoomMember.Role.forPowerLevel(roomName) == role
RoomPermissionType.ROOM_AVATAR -> RoomMember.Role.forPowerLevel(roomAvatar) == role
RoomPermissionType.ROOM_TOPIC -> RoomMember.Role.forPowerLevel(roomTopic) == role
}
} }
@Composable @Composable
private fun titleForSection(item: RoomPermissionType): String = when (item) { private fun titleForType(type: RoomPermissionType): String = when (type) {
RoomPermissionType.INVITE -> stringResource(R.string.screen_room_change_permissions_invite_people) RoomPermissionType.INVITE -> stringResource(R.string.screen_room_change_permissions_invite_people)
RoomPermissionType.KICK -> stringResource(R.string.screen_room_change_permissions_remove_people) RoomPermissionType.KICK -> stringResource(R.string.screen_room_change_permissions_remove_people)
RoomPermissionType.BAN -> stringResource(R.string.screen_room_change_permissions_ban_people) RoomPermissionType.BAN -> stringResource(R.string.screen_room_change_permissions_ban_people)

View file

@ -39,9 +39,8 @@ class RolesAndPermissionsNode(
interface Callback : Plugin, RolesAndPermissionsNavigator { interface Callback : Plugin, RolesAndPermissionsNavigator {
override fun openAdminList() override fun openAdminList()
override fun openModeratorList() override fun openModeratorList()
override fun openEditRoomDetailsPermissions() override fun openEditPermissions()
override fun openMessagesAndContentPermissions()
override fun openModerationPermissions()
override fun onBackClick() {} override fun onBackClick() {}
} }
@ -85,7 +84,5 @@ interface RolesAndPermissionsNavigator {
fun onBackClick() {} fun onBackClick() {}
fun openAdminList() {} fun openAdminList() {}
fun openModeratorList() {} fun openModeratorList() {}
fun openEditRoomDetailsPermissions() {} fun openEditPermissions() {}
fun openMessagesAndContentPermissions() {}
fun openModerationPermissions() {}
} }

View file

@ -78,25 +78,16 @@ fun RolesAndPermissionsView(
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Edit())) leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Edit()))
) )
} }
ListSectionHeader(title = stringResource(R.string.screen_room_roles_and_permissions_permissions_header), hasDivider = true) HorizontalDivider()
ListItem( ListItem(
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_room_details)) }, headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_permissions_header)) },
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Info())), leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Settings())),
onClick = { rolesAndPermissionsNavigator.openEditRoomDetailsPermissions() }, onClick = { rolesAndPermissionsNavigator.openEditPermissions() },
)
ListItem(
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_messages_and_content)) },
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Chat())),
onClick = { rolesAndPermissionsNavigator.openMessagesAndContentPermissions() },
)
ListItem(
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_member_moderation)) },
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.User())),
onClick = { rolesAndPermissionsNavigator.openModerationPermissions() },
) )
HorizontalDivider() HorizontalDivider()
ListItem( ListItem(
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_reset)) }, headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_reset)) },
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Delete())),
onClick = { state.eventSink(RolesAndPermissionsEvents.ResetPermissions) }, onClick = { state.eventSink(RolesAndPermissionsEvents.ResetPermissions) },
style = ListItemStyle.Destructive, style = ListItemStyle.Destructive,
) )

View file

@ -10,9 +10,11 @@
package io.element.android.libraries.designsystem.components.preferences package io.element.android.libraries.designsystem.components.preferences
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.ExposedDropdownMenuDefaults
@ -26,6 +28,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.list.ListItemContent
@ -33,6 +36,7 @@ import io.element.android.libraries.designsystem.components.preferences.componen
import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.ListItem
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.toEnabledColor import io.element.android.libraries.designsystem.toEnabledColor
@ -144,6 +148,15 @@ private fun <T : DropdownOption> DropdownTrailingContent(
style = ElementTheme.typography.fontBodyMdRegular style = ElementTheme.typography.fontBodyMdRegular
) )
}, },
trailingIcon = {
if(option == selectedOption){
Icon(
imageVector = CompoundIcons.Check(),
contentDescription = null,
tint = ElementTheme.colors.iconSuccessPrimary,
)
}
},
onClick = { onClick = {
onSelectOption(option) onSelectOption(option)
onExpandedChange(false) onExpandedChange(false)