change(room permissions): user can edit only roles <= to his own role
This commit is contained in:
parent
74dd3f381e
commit
f13d9259c5
5 changed files with 84 additions and 15 deletions
|
|
@ -10,6 +10,7 @@ package io.element.android.features.rolesandpermissions.impl.permissions
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
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.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.core.coroutine.mapState
|
||||||
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.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.matrix.ui.model.powerLevelOf
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableMap
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
|
|
@ -89,6 +92,10 @@ class ChangeRoomPermissionsPresenter(
|
||||||
derivedStateOf { initialPermissions != currentPermissions }
|
derivedStateOf { initialPermissions != currentPermissions }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ownPowerLevel by remember {
|
||||||
|
room.roomInfoFlow.mapState { it.powerLevelOf(room.sessionId) }
|
||||||
|
}.collectAsState()
|
||||||
|
|
||||||
fun handleEvent(event: ChangeRoomPermissionsEvent) {
|
fun handleEvent(event: ChangeRoomPermissionsEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction -> {
|
is ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction -> {
|
||||||
|
|
@ -123,6 +130,7 @@ class ChangeRoomPermissionsPresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ChangeRoomPermissionsState(
|
return ChangeRoomPermissionsState(
|
||||||
|
ownPowerLevel = ownPowerLevel,
|
||||||
currentPermissions = currentPermissions,
|
currentPermissions = currentPermissions,
|
||||||
itemsBySection = itemsBySection,
|
itemsBySection = itemsBySection,
|
||||||
hasChanges = hasChanges,
|
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 io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.ImmutableMap
|
import kotlinx.collections.immutable.ImmutableMap
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
data class ChangeRoomPermissionsState(
|
data class ChangeRoomPermissionsState(
|
||||||
|
private val ownPowerLevel: Long,
|
||||||
val currentPermissions: RoomPowerLevelsValues?,
|
val currentPermissions: RoomPowerLevelsValues?,
|
||||||
val itemsBySection: ImmutableMap<RoomPermissionsSection, ImmutableList<RoomPermissionType>>,
|
val itemsBySection: ImmutableMap<RoomPermissionsSection, ImmutableList<RoomPermissionType>>,
|
||||||
val hasChanges: Boolean,
|
val hasChanges: Boolean,
|
||||||
val saveAction: AsyncAction<Boolean>,
|
val saveAction: AsyncAction<Boolean>,
|
||||||
val eventSink: (ChangeRoomPermissionsEvent) -> Unit,
|
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? {
|
fun selectedRoleForType(type: RoomPermissionType): SelectableRole? {
|
||||||
if (currentPermissions == null) return null
|
val powerLevel = currentPowerLevelForType(type = type) ?: return null
|
||||||
val role = when (type) {
|
return when (RoomMember.Role.forPowerLevel(powerLevel)) {
|
||||||
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) {
|
|
||||||
is RoomMember.Role.Owner,
|
is RoomMember.Role.Owner,
|
||||||
RoomMember.Role.Admin -> SelectableRole.Admin
|
RoomMember.Role.Admin -> SelectableRole.Admin
|
||||||
RoomMember.Role.Moderator -> SelectableRole.Moderator
|
RoomMember.Role.Moderator -> SelectableRole.Moderator
|
||||||
RoomMember.Role.User -> SelectableRole.Everyone
|
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 {
|
enum class RoomPermissionsSection {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ class ChangeRoomPermissionsStateProvider : PreviewParameterProvider<ChangeRoomPe
|
||||||
override val values: Sequence<ChangeRoomPermissionsState>
|
override val values: Sequence<ChangeRoomPermissionsState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aChangeRoomPermissionsState(),
|
aChangeRoomPermissionsState(),
|
||||||
|
aChangeRoomPermissionsState(ownPowerLevel = RoomMember.Role.Moderator.powerLevel),
|
||||||
aChangeRoomPermissionsState(hasChanges = true),
|
aChangeRoomPermissionsState(hasChanges = true),
|
||||||
aChangeRoomPermissionsState(hasChanges = true, saveAction = AsyncAction.Loading),
|
aChangeRoomPermissionsState(hasChanges = true, saveAction = AsyncAction.Loading),
|
||||||
aChangeRoomPermissionsState(
|
aChangeRoomPermissionsState(
|
||||||
|
|
@ -31,12 +32,14 @@ class ChangeRoomPermissionsStateProvider : PreviewParameterProvider<ChangeRoomPe
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun aChangeRoomPermissionsState(
|
internal fun aChangeRoomPermissionsState(
|
||||||
|
ownPowerLevel: Long = RoomMember.Role.Admin.powerLevel,
|
||||||
currentPermissions: RoomPowerLevelsValues = previewPermissions(),
|
currentPermissions: RoomPowerLevelsValues = previewPermissions(),
|
||||||
itemsBySection: Map<RoomPermissionsSection, ImmutableList<RoomPermissionType>> = ChangeRoomPermissionsPresenter.buildItems(false),
|
itemsBySection: Map<RoomPermissionsSection, ImmutableList<RoomPermissionType>> = ChangeRoomPermissionsPresenter.buildItems(false),
|
||||||
hasChanges: Boolean = false,
|
hasChanges: Boolean = false,
|
||||||
saveAction: AsyncAction<Boolean> = AsyncAction.Uninitialized,
|
saveAction: AsyncAction<Boolean> = AsyncAction.Uninitialized,
|
||||||
eventSink: (ChangeRoomPermissionsEvent) -> Unit = {},
|
eventSink: (ChangeRoomPermissionsEvent) -> Unit = {},
|
||||||
) = ChangeRoomPermissionsState(
|
) = ChangeRoomPermissionsState(
|
||||||
|
ownPowerLevel = ownPowerLevel,
|
||||||
currentPermissions = currentPermissions,
|
currentPermissions = currentPermissions,
|
||||||
itemsBySection = itemsBySection.toImmutableMap(),
|
itemsBySection = itemsBySection.toImmutableMap(),
|
||||||
hasChanges = hasChanges,
|
hasChanges = hasChanges,
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,8 @@ fun ChangeRoomPermissionsView(
|
||||||
PreferenceDropdown(
|
PreferenceDropdown(
|
||||||
title = titleForType(permissionType),
|
title = titleForType(permissionType),
|
||||||
selectedOption = state.selectedRoleForType(permissionType),
|
selectedOption = state.selectedRoleForType(permissionType),
|
||||||
options = SelectableRole.entries.toImmutableList(),
|
options = state.selectableRoles,
|
||||||
|
enabled = state.canChangePermission(permissionType),
|
||||||
onSelectOption = { role ->
|
onSelectOption = { role ->
|
||||||
state.eventSink(
|
state.eventSink(
|
||||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(
|
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,18 @@ import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import im.vector.app.features.analytics.plan.RoomModeration
|
import im.vector.app.features.analytics.plan.RoomModeration
|
||||||
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.Role.Admin
|
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.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.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.FakeBaseRoom
|
||||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
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.libraries.matrix.test.room.defaultRoomPowerLevelValues
|
||||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||||
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
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
|
@Test
|
||||||
fun `present - ChangeMinimumRoleForAction updates the current permissions and hasChanges`() = runTest {
|
fun `present - ChangeMinimumRoleForAction updates the current permissions and hasChanges`() = runTest {
|
||||||
val presenter = createChangeRoomPermissionsPresenter()
|
val presenter = createChangeRoomPermissionsPresenter()
|
||||||
|
|
@ -266,7 +293,9 @@ class ChangeRoomPermissionsPresenterTest {
|
||||||
|
|
||||||
private fun createChangeRoomPermissionsPresenter(
|
private fun createChangeRoomPermissionsPresenter(
|
||||||
room: FakeJoinedRoom = FakeJoinedRoom(
|
room: FakeJoinedRoom = FakeJoinedRoom(
|
||||||
baseRoom = FakeBaseRoom(powerLevelsResult = { Result.success(defaultPermissions()) }),
|
baseRoom = FakeBaseRoom(
|
||||||
|
initialRoomInfo = initialRoomInfo(),
|
||||||
|
powerLevelsResult = { Result.success(defaultPermissions()) }),
|
||||||
),
|
),
|
||||||
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||||
) = ChangeRoomPermissionsPresenter(
|
) = ChangeRoomPermissionsPresenter(
|
||||||
|
|
@ -274,6 +303,13 @@ class ChangeRoomPermissionsPresenterTest {
|
||||||
analyticsService = analyticsService,
|
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 fun defaultPermissions() = defaultRoomPowerLevelValues()
|
||||||
|
|
||||||
private suspend fun TurbineTestContext<ChangeRoomPermissionsState>.awaitUpdatedItem(): ChangeRoomPermissionsState {
|
private suspend fun TurbineTestContext<ChangeRoomPermissionsState>.awaitUpdatedItem(): ChangeRoomPermissionsState {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue