change(space settings): manage permissions

This commit is contained in:
ganfra 2025-12-10 13:44:46 +01:00
parent de52c991fe
commit dfc3ebb718
6 changed files with 62 additions and 8 deletions

View file

@ -17,11 +17,14 @@ 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.Inject 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.SeenInvitesStore
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents 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.acceptdecline.AcceptDeclineInviteState
import io.element.android.features.invite.api.toInviteData 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.AsyncAction
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.mapState 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.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.api.room.CurrentUserMembership 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.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.SpaceRoom
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar
@ -50,6 +54,7 @@ import kotlin.jvm.optionals.getOrNull
@Inject @Inject
class SpacePresenter( class SpacePresenter(
private val spaceRoomList: SpaceRoomList, private val spaceRoomList: SpaceRoomList,
private val room: JoinedRoom,
private val client: MatrixClient, private val client: MatrixClient,
private val seenInvitesStore: SeenInvitesStore, private val seenInvitesStore: SeenInvitesStore,
private val joinRoom: JoinRoom, private val joinRoom: JoinRoom,
@ -82,6 +87,9 @@ class SpacePresenter(
} }
}.collectAsState() }.collectAsState()
val permissions by room.permissionsAsState(SpaceSettingsPermissions.DEFAULT) { perms ->
perms.spaceSettingsPermissions()
}
val isSpaceSettingsEnabled by remember { val isSpaceSettingsEnabled by remember {
featureFlagService.isFeatureEnabledFlow(FeatureFlags.SpaceSettings) featureFlagService.isFeatureEnabledFlow(FeatureFlags.SpaceSettings)
}.collectAsState(false) }.collectAsState(false)
@ -136,7 +144,7 @@ class SpacePresenter(
joinActions = joinActions.toImmutableMap(), joinActions = joinActions.toImmutableMap(),
acceptDeclineInviteState = acceptDeclineInviteState, acceptDeclineInviteState = acceptDeclineInviteState,
topicViewerState = topicViewerState, topicViewerState = topicViewerState,
canAccessSpaceSettings = isSpaceSettingsEnabled, canAccessSpaceSettings = isSpaceSettingsEnabled && permissions.hasAny,
eventSink = ::handleEvent, eventSink = ::handleEvent,
) )
} }
@ -150,7 +158,7 @@ class SpacePresenter(
joinRoom.invoke( joinRoom.invoke(
roomIdOrAlias = spaceRoom.roomId.toRoomIdOrAlias(), roomIdOrAlias = spaceRoom.roomId.toRoomIdOrAlias(),
serverNames = spaceRoom.via, serverNames = spaceRoom.via,
trigger = JoinedRoom.Trigger.SpaceHierarchy, trigger = Trigger.SpaceHierarchy,
).onFailure { ).onFailure {
setJoinActions(joinActions + mapOf(spaceRoom.roomId to AsyncAction.Failure(it))) setJoinActions(joinActions + mapOf(spaceRoom.roomId to AsyncAction.Failure(it)))
} }

View file

@ -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,
)
}

View file

@ -14,7 +14,7 @@ import androidx.compose.runtime.getValue
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
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.ui.room.isOwnUserAdmin import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
@Inject @Inject
class SpaceSettingsPresenter( class SpaceSettingsPresenter(
@ -23,15 +23,18 @@ class SpaceSettingsPresenter(
@Composable @Composable
override fun present(): SpaceSettingsState { override fun present(): SpaceSettingsState {
val roomInfo by room.roomInfoFlow.collectAsState() val roomInfo by room.roomInfoFlow.collectAsState()
val isUserAdmin = room.isOwnUserAdmin() val permissions by room.permissionsAsState(SpaceSettingsPermissions.DEFAULT) { perms ->
perms.spaceSettingsPermissions()
}
return SpaceSettingsState( return SpaceSettingsState(
roomId = room.roomId, roomId = room.roomId,
name = roomInfo.name.orEmpty(), name = roomInfo.name.orEmpty(),
canonicalAlias = roomInfo.canonicalAlias, canonicalAlias = roomInfo.canonicalAlias,
avatarUrl = roomInfo.avatarUrl, avatarUrl = roomInfo.avatarUrl,
memberCount = roomInfo.activeMembersCount, memberCount = roomInfo.activeMembersCount,
showRolesAndPermissions = isUserAdmin, canEditDetails = permissions.canEditDetails,
showSecurityAndPrivacy = isUserAdmin, showRolesAndPermissions = permissions.canManageRolesAndPermissions,
showSecurityAndPrivacy = permissions.canManageSecurityAndPrivacy,
eventSink = {}, eventSink = {},
) )
} }

View file

@ -17,6 +17,7 @@ data class SpaceSettingsState(
val canonicalAlias: RoomAlias?, val canonicalAlias: RoomAlias?,
val avatarUrl: String?, val avatarUrl: String?,
val memberCount: Long, val memberCount: Long,
val canEditDetails: Boolean,
val showRolesAndPermissions: Boolean, val showRolesAndPermissions: Boolean,
val showSecurityAndPrivacy: Boolean, val showSecurityAndPrivacy: Boolean,
val eventSink: (SpaceSettingsEvents) -> Unit val eventSink: (SpaceSettingsEvents) -> Unit

View file

@ -30,6 +30,7 @@ fun aSpaceSettingsState(
memberCount: Long = 100, memberCount: Long = 100,
showRolesAndPermissions: Boolean = false, showRolesAndPermissions: Boolean = false,
showSecurityAndPrivacy: Boolean = false, showSecurityAndPrivacy: Boolean = false,
canEditDetails: Boolean = false,
eventSink: (SpaceSettingsEvents) -> Unit = {}, eventSink: (SpaceSettingsEvents) -> Unit = {},
) = SpaceSettingsState( ) = SpaceSettingsState(
roomId = roomId, roomId = roomId,
@ -37,6 +38,7 @@ fun aSpaceSettingsState(
canonicalAlias = alias, canonicalAlias = alias,
avatarUrl = avatarUrl, avatarUrl = avatarUrl,
memberCount = memberCount, memberCount = memberCount,
canEditDetails = canEditDetails,
showRolesAndPermissions = showRolesAndPermissions, showRolesAndPermissions = showRolesAndPermissions,
showSecurityAndPrivacy = showSecurityAndPrivacy, showSecurityAndPrivacy = showSecurityAndPrivacy,
eventSink = eventSink, eventSink = eventSink,

View file

@ -73,6 +73,7 @@ fun SpaceSettingsView(
name = state.name, name = state.name,
avatarUrl = state.avatarUrl, avatarUrl = state.avatarUrl,
canonicalAlias = state.canonicalAlias?.value, canonicalAlias = state.canonicalAlias?.value,
canEditDetails = state.canEditDetails,
onSpaceInfoClick = onSpaceInfoClick, onSpaceInfoClick = onSpaceInfoClick,
) )
Section(isVisible = state.showSecurityAndPrivacy, content = { Section(isVisible = state.showSecurityAndPrivacy, content = {
@ -101,12 +102,13 @@ private fun SpaceInfoSection(
name: String, name: String,
avatarUrl: String?, avatarUrl: String?,
canonicalAlias: String?, canonicalAlias: String?,
canEditDetails: Boolean,
onSpaceInfoClick: () -> Unit, onSpaceInfoClick: () -> Unit,
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable(onClick = onSpaceInfoClick) .clickable(enabled = canEditDetails, onClick = onSpaceInfoClick)
.padding(16.dp), .padding(16.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {