From dfc3ebb71841b8dd58c47c1cff5760a2c9c85849 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 10 Dec 2025 13:44:46 +0100 Subject: [PATCH] change(space settings): manage permissions --- .../space/impl/root/SpacePresenter.kt | 14 +++++-- .../impl/settings/SpaceSettingsPermissions.kt | 38 +++++++++++++++++++ .../impl/settings/SpaceSettingsPresenter.kt | 11 ++++-- .../space/impl/settings/SpaceSettingsState.kt | 1 + .../settings/SpaceSettingsStateProvider.kt | 2 + .../space/impl/settings/SpaceSettingsView.kt | 4 +- 6 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPermissions.kt diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt index 3d36de0cea..0794d02920 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt @@ -17,11 +17,14 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import dev.zacsweers.metro.Inject -import im.vector.app.features.analytics.plan.JoinedRoom +import im.vector.app.features.analytics.plan.JoinedRoom.Trigger +import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState import io.element.android.features.invite.api.toInviteData +import io.element.android.features.space.impl.settings.SpaceSettingsPermissions +import io.element.android.features.space.impl.settings.spaceSettingsPermissions import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.mapState @@ -33,6 +36,7 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.join.JoinRoom +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.api.spaces.SpaceRoomList import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar @@ -50,6 +54,7 @@ import kotlin.jvm.optionals.getOrNull @Inject class SpacePresenter( private val spaceRoomList: SpaceRoomList, + private val room: JoinedRoom, private val client: MatrixClient, private val seenInvitesStore: SeenInvitesStore, private val joinRoom: JoinRoom, @@ -82,6 +87,9 @@ class SpacePresenter( } }.collectAsState() + val permissions by room.permissionsAsState(SpaceSettingsPermissions.DEFAULT) { perms -> + perms.spaceSettingsPermissions() + } val isSpaceSettingsEnabled by remember { featureFlagService.isFeatureEnabledFlow(FeatureFlags.SpaceSettings) }.collectAsState(false) @@ -136,7 +144,7 @@ class SpacePresenter( joinActions = joinActions.toImmutableMap(), acceptDeclineInviteState = acceptDeclineInviteState, topicViewerState = topicViewerState, - canAccessSpaceSettings = isSpaceSettingsEnabled, + canAccessSpaceSettings = isSpaceSettingsEnabled && permissions.hasAny, eventSink = ::handleEvent, ) } @@ -150,7 +158,7 @@ class SpacePresenter( joinRoom.invoke( roomIdOrAlias = spaceRoom.roomId.toRoomIdOrAlias(), serverNames = spaceRoom.via, - trigger = JoinedRoom.Trigger.SpaceHierarchy, + trigger = Trigger.SpaceHierarchy, ).onFailure { setJoinActions(joinActions + mapOf(spaceRoom.roomId to AsyncAction.Failure(it))) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPermissions.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPermissions.kt new file mode 100644 index 0000000000..d7588b59e1 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPermissions.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import io.element.android.features.roomdetailsedit.api.roomDetailsEditPermissions +import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissions +import io.element.android.libraries.matrix.api.room.StateEventType +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions + +data class SpaceSettingsPermissions( + val canEditDetails: Boolean, + val canManageRolesAndPermissions: Boolean, + val canManageSecurityAndPrivacy: Boolean, +){ + + val hasAny = canEditDetails || canManageRolesAndPermissions || canManageSecurityAndPrivacy + + companion object { + val DEFAULT = SpaceSettingsPermissions( + canEditDetails = false, + canManageRolesAndPermissions = false, + canManageSecurityAndPrivacy = false, + ) + } +} + +fun RoomPermissions.spaceSettingsPermissions(): SpaceSettingsPermissions { + return SpaceSettingsPermissions( + canEditDetails = roomDetailsEditPermissions().hasAny, + canManageRolesAndPermissions = canOwnUserSendState(StateEventType.ROOM_POWER_LEVELS), + canManageSecurityAndPrivacy = securityAndPrivacyPermissions().hasAny, + ) +} diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt index 5238914c56..96c3fcbab6 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt @@ -14,7 +14,7 @@ import androidx.compose.runtime.getValue import dev.zacsweers.metro.Inject import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.room.JoinedRoom -import io.element.android.libraries.matrix.ui.room.isOwnUserAdmin +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState @Inject class SpaceSettingsPresenter( @@ -23,15 +23,18 @@ class SpaceSettingsPresenter( @Composable override fun present(): SpaceSettingsState { val roomInfo by room.roomInfoFlow.collectAsState() - val isUserAdmin = room.isOwnUserAdmin() + val permissions by room.permissionsAsState(SpaceSettingsPermissions.DEFAULT) { perms -> + perms.spaceSettingsPermissions() + } return SpaceSettingsState( roomId = room.roomId, name = roomInfo.name.orEmpty(), canonicalAlias = roomInfo.canonicalAlias, avatarUrl = roomInfo.avatarUrl, memberCount = roomInfo.activeMembersCount, - showRolesAndPermissions = isUserAdmin, - showSecurityAndPrivacy = isUserAdmin, + canEditDetails = permissions.canEditDetails, + showRolesAndPermissions = permissions.canManageRolesAndPermissions, + showSecurityAndPrivacy = permissions.canManageSecurityAndPrivacy, eventSink = {}, ) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt index 40ceadc112..5c7e9b5c6e 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt @@ -17,6 +17,7 @@ data class SpaceSettingsState( val canonicalAlias: RoomAlias?, val avatarUrl: String?, val memberCount: Long, + val canEditDetails: Boolean, val showRolesAndPermissions: Boolean, val showSecurityAndPrivacy: Boolean, val eventSink: (SpaceSettingsEvents) -> Unit diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt index 2abe7efebe..2030b6885a 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt @@ -30,6 +30,7 @@ fun aSpaceSettingsState( memberCount: Long = 100, showRolesAndPermissions: Boolean = false, showSecurityAndPrivacy: Boolean = false, + canEditDetails: Boolean = false, eventSink: (SpaceSettingsEvents) -> Unit = {}, ) = SpaceSettingsState( roomId = roomId, @@ -37,6 +38,7 @@ fun aSpaceSettingsState( canonicalAlias = alias, avatarUrl = avatarUrl, memberCount = memberCount, + canEditDetails = canEditDetails, showRolesAndPermissions = showRolesAndPermissions, showSecurityAndPrivacy = showSecurityAndPrivacy, eventSink = eventSink, diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt index 379d75f1dd..5a8aac4a30 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt @@ -73,6 +73,7 @@ fun SpaceSettingsView( name = state.name, avatarUrl = state.avatarUrl, canonicalAlias = state.canonicalAlias?.value, + canEditDetails = state.canEditDetails, onSpaceInfoClick = onSpaceInfoClick, ) Section(isVisible = state.showSecurityAndPrivacy, content = { @@ -101,12 +102,13 @@ private fun SpaceInfoSection( name: String, avatarUrl: String?, canonicalAlias: String?, + canEditDetails: Boolean, onSpaceInfoClick: () -> Unit, ) { Row( modifier = Modifier .fillMaxWidth() - .clickable(onClick = onSpaceInfoClick) + .clickable(enabled = canEditDetails, onClick = onSpaceInfoClick) .padding(16.dp), verticalAlignment = Alignment.CenterVertically, ) {