change(room permissions): user can edit only roles <= to his own role
This commit is contained in:
@@ -10,6 +10,7 @@ package io.element.android.features.rolesandpermissions.impl.permissions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -20,9 +21,11 @@ import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.rolesandpermissions.impl.analytics.trackPermissionChangeAnalytics
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.mapState
|
||||
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.ui.model.powerLevelOf
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
@@ -89,6 +92,10 @@ class ChangeRoomPermissionsPresenter(
|
||||
derivedStateOf { initialPermissions != currentPermissions }
|
||||
}
|
||||
|
||||
val ownPowerLevel by remember {
|
||||
room.roomInfoFlow.mapState { it.powerLevelOf(room.sessionId) }
|
||||
}.collectAsState()
|
||||
|
||||
fun handleEvent(event: ChangeRoomPermissionsEvent) {
|
||||
when (event) {
|
||||
is ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction -> {
|
||||
@@ -123,6 +130,7 @@ class ChangeRoomPermissionsPresenter(
|
||||
}
|
||||
}
|
||||
return ChangeRoomPermissionsState(
|
||||
ownPowerLevel = ownPowerLevel,
|
||||
currentPermissions = currentPermissions,
|
||||
itemsBySection = itemsBySection,
|
||||
hasChanges = hasChanges,
|
||||
|
||||
@@ -18,34 +18,55 @@ import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
data class ChangeRoomPermissionsState(
|
||||
private val ownPowerLevel: Long,
|
||||
val currentPermissions: RoomPowerLevelsValues?,
|
||||
val itemsBySection: ImmutableMap<RoomPermissionsSection, ImmutableList<RoomPermissionType>>,
|
||||
val hasChanges: Boolean,
|
||||
val saveAction: AsyncAction<Boolean>,
|
||||
val eventSink: (ChangeRoomPermissionsEvent) -> Unit,
|
||||
) {
|
||||
private val ownRole = RoomMember.Role.forPowerLevel(ownPowerLevel)
|
||||
|
||||
// Roles that the user can select based on their own role
|
||||
val selectableRoles: ImmutableList<SelectableRole> = when (ownRole) {
|
||||
is RoomMember.Role.Owner,
|
||||
RoomMember.Role.Admin -> persistentListOf(SelectableRole.Admin, SelectableRole.Moderator, SelectableRole.Everyone)
|
||||
RoomMember.Role.Moderator -> persistentListOf(SelectableRole.Moderator, SelectableRole.Everyone)
|
||||
RoomMember.Role.User -> persistentListOf(SelectableRole.Everyone)
|
||||
}
|
||||
|
||||
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.eventsDefault)
|
||||
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)
|
||||
RoomPermissionType.SPACE_MANAGE_ROOMS -> RoomMember.Role.forPowerLevel(currentPermissions.spaceChild)
|
||||
}
|
||||
return when (role) {
|
||||
val powerLevel = currentPowerLevelForType(type = type) ?: return null
|
||||
return when (RoomMember.Role.forPowerLevel(powerLevel)) {
|
||||
is RoomMember.Role.Owner,
|
||||
RoomMember.Role.Admin -> SelectableRole.Admin
|
||||
RoomMember.Role.Moderator -> SelectableRole.Moderator
|
||||
RoomMember.Role.User -> SelectableRole.Everyone
|
||||
}
|
||||
}
|
||||
|
||||
fun canChangePermission(type: RoomPermissionType): Boolean {
|
||||
val currentPowerLevel = currentPowerLevelForType(type) ?: return false
|
||||
return ownPowerLevel >= currentPowerLevel
|
||||
}
|
||||
|
||||
private fun currentPowerLevelForType(type: RoomPermissionType): Long? {
|
||||
if (currentPermissions == null) return null
|
||||
return when (type) {
|
||||
RoomPermissionType.BAN -> currentPermissions.ban
|
||||
RoomPermissionType.INVITE -> currentPermissions.invite
|
||||
RoomPermissionType.KICK -> currentPermissions.kick
|
||||
RoomPermissionType.SEND_EVENTS -> currentPermissions.eventsDefault
|
||||
RoomPermissionType.REDACT_EVENTS -> currentPermissions.redactEvents
|
||||
RoomPermissionType.ROOM_NAME -> currentPermissions.roomName
|
||||
RoomPermissionType.ROOM_AVATAR -> currentPermissions.roomAvatar
|
||||
RoomPermissionType.ROOM_TOPIC -> currentPermissions.roomTopic
|
||||
RoomPermissionType.SPACE_MANAGE_ROOMS -> currentPermissions.spaceChild
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class RoomPermissionsSection {
|
||||
|
||||
@@ -19,6 +19,7 @@ class ChangeRoomPermissionsStateProvider : PreviewParameterProvider<ChangeRoomPe
|
||||
override val values: Sequence<ChangeRoomPermissionsState>
|
||||
get() = sequenceOf(
|
||||
aChangeRoomPermissionsState(),
|
||||
aChangeRoomPermissionsState(ownPowerLevel = RoomMember.Role.Moderator.powerLevel),
|
||||
aChangeRoomPermissionsState(hasChanges = true),
|
||||
aChangeRoomPermissionsState(hasChanges = true, saveAction = AsyncAction.Loading),
|
||||
aChangeRoomPermissionsState(
|
||||
@@ -31,12 +32,14 @@ class ChangeRoomPermissionsStateProvider : PreviewParameterProvider<ChangeRoomPe
|
||||
}
|
||||
|
||||
internal fun aChangeRoomPermissionsState(
|
||||
ownPowerLevel: Long = RoomMember.Role.Admin.powerLevel,
|
||||
currentPermissions: RoomPowerLevelsValues = previewPermissions(),
|
||||
itemsBySection: Map<RoomPermissionsSection, ImmutableList<RoomPermissionType>> = ChangeRoomPermissionsPresenter.buildItems(false),
|
||||
hasChanges: Boolean = false,
|
||||
saveAction: AsyncAction<Boolean> = AsyncAction.Uninitialized,
|
||||
eventSink: (ChangeRoomPermissionsEvent) -> Unit = {},
|
||||
) = ChangeRoomPermissionsState(
|
||||
ownPowerLevel = ownPowerLevel,
|
||||
currentPermissions = currentPermissions,
|
||||
itemsBySection = itemsBySection.toImmutableMap(),
|
||||
hasChanges = hasChanges,
|
||||
|
||||
@@ -74,7 +74,8 @@ fun ChangeRoomPermissionsView(
|
||||
PreferenceDropdown(
|
||||
title = titleForType(permissionType),
|
||||
selectedOption = state.selectedRoleForType(permissionType),
|
||||
options = SelectableRole.entries.toImmutableList(),
|
||||
options = state.selectableRoles,
|
||||
enabled = state.canChangePermission(permissionType),
|
||||
onSelectOption = { role ->
|
||||
state.eventSink(
|
||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(
|
||||
|
||||
@@ -16,13 +16,18 @@ import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.RoomModeration
|
||||
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.Role.Admin
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember.Role.Moderator
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevelValues
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
@@ -70,6 +75,28 @@ class ChangeRoomPermissionsPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - check canChangePermissions and selectableOptions for moderator`() = runTest {
|
||||
val room = FakeJoinedRoom(
|
||||
baseRoom = FakeBaseRoom(
|
||||
initialRoomInfo = initialRoomInfo(role = Moderator),
|
||||
powerLevelsResult = { Result.success(defaultPermissions()) }
|
||||
),
|
||||
)
|
||||
val presenter = createChangeRoomPermissionsPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val state = awaitUpdatedItem()
|
||||
assertThat(state.selectableRoles).containsExactly(SelectableRole.Moderator, SelectableRole.Everyone)
|
||||
for (sectionItems in state.itemsBySection.values) {
|
||||
for (permissionType in sectionItems) {
|
||||
assertThat(state.canChangePermission(permissionType)).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - ChangeMinimumRoleForAction updates the current permissions and hasChanges`() = runTest {
|
||||
val presenter = createChangeRoomPermissionsPresenter()
|
||||
@@ -266,7 +293,9 @@ class ChangeRoomPermissionsPresenterTest {
|
||||
|
||||
private fun createChangeRoomPermissionsPresenter(
|
||||
room: FakeJoinedRoom = FakeJoinedRoom(
|
||||
baseRoom = FakeBaseRoom(powerLevelsResult = { Result.success(defaultPermissions()) }),
|
||||
baseRoom = FakeBaseRoom(
|
||||
initialRoomInfo = initialRoomInfo(),
|
||||
powerLevelsResult = { Result.success(defaultPermissions()) }),
|
||||
),
|
||||
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||
) = ChangeRoomPermissionsPresenter(
|
||||
@@ -274,6 +303,13 @@ class ChangeRoomPermissionsPresenterTest {
|
||||
analyticsService = analyticsService,
|
||||
)
|
||||
|
||||
private fun initialRoomInfo(role: RoomMember.Role = Admin) = aRoomInfo(
|
||||
roomPowerLevels = RoomPowerLevels(
|
||||
values = defaultPermissions(),
|
||||
users = persistentMapOf(A_SESSION_ID to role.powerLevel),
|
||||
)
|
||||
)
|
||||
|
||||
private fun defaultPermissions() = defaultRoomPowerLevelValues()
|
||||
|
||||
private suspend fun TurbineTestContext<ChangeRoomPermissionsState>.awaitUpdatedItem(): ChangeRoomPermissionsState {
|
||||
|
||||
Reference in New Issue
Block a user