Skip to content

Commit 76bc487

Browse files
authored
Merge pull request #5950 from element-hq/feature/fga/iterate_permissions_screen
Changes : iterate again on permissions
2 parents d6ba53b + 7c8830b commit 76bc487

File tree

36 files changed

+243
-151
lines changed

36 files changed

+243
-151
lines changed

features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package io.element.android.features.rolesandpermissions.impl.permissions
1010

1111
import androidx.compose.runtime.Composable
1212
import androidx.compose.runtime.LaunchedEffect
13+
import androidx.compose.runtime.collectAsState
1314
import androidx.compose.runtime.derivedStateOf
1415
import androidx.compose.runtime.getValue
1516
import androidx.compose.runtime.mutableStateOf
@@ -20,9 +21,11 @@ import dev.zacsweers.metro.Inject
2021
import io.element.android.features.rolesandpermissions.impl.analytics.trackPermissionChangeAnalytics
2122
import io.element.android.libraries.architecture.AsyncAction
2223
import io.element.android.libraries.architecture.Presenter
24+
import io.element.android.libraries.core.coroutine.mapState
2325
import io.element.android.libraries.matrix.api.room.JoinedRoom
2426
import io.element.android.libraries.matrix.api.room.RoomMember
2527
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
28+
import io.element.android.libraries.matrix.ui.model.powerLevelOf
2629
import io.element.android.services.analytics.api.AnalyticsService
2730
import kotlinx.collections.immutable.persistentListOf
2831
import kotlinx.collections.immutable.toImmutableMap
@@ -52,7 +55,6 @@ class ChangeRoomPermissionsPresenter(
5255
)
5356
RoomPermissionsSection.ManageSpace -> persistentListOf(
5457
RoomPermissionType.SPACE_MANAGE_ROOMS,
55-
RoomPermissionType.CHANGE_SETTINGS,
5658
)
5759
}
5860

@@ -90,6 +92,10 @@ class ChangeRoomPermissionsPresenter(
9092
derivedStateOf { initialPermissions != currentPermissions }
9193
}
9294

95+
val ownPowerLevel by remember {
96+
room.roomInfoFlow.mapState { it.powerLevelOf(room.sessionId) }
97+
}.collectAsState()
98+
9399
fun handleEvent(event: ChangeRoomPermissionsEvent) {
94100
when (event) {
95101
is ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction -> {
@@ -108,7 +114,6 @@ class ChangeRoomPermissionsPresenter(
108114
RoomPermissionType.ROOM_AVATAR -> currentPermissions?.copy(roomAvatar = powerLevel)
109115
RoomPermissionType.ROOM_TOPIC -> currentPermissions?.copy(roomTopic = powerLevel)
110116
RoomPermissionType.SPACE_MANAGE_ROOMS -> currentPermissions?.copy(spaceChild = powerLevel)
111-
RoomPermissionType.CHANGE_SETTINGS -> currentPermissions?.copy(stateDefault = powerLevel)
112117
}
113118
}
114119
is ChangeRoomPermissionsEvent.Save -> coroutineScope.save()
@@ -125,6 +130,7 @@ class ChangeRoomPermissionsPresenter(
125130
}
126131
}
127132
return ChangeRoomPermissionsState(
133+
ownPowerLevel = ownPowerLevel,
128134
currentPermissions = currentPermissions,
129135
itemsBySection = itemsBySection,
130136
hasChanges = hasChanges,

features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,35 +18,55 @@ import io.element.android.libraries.matrix.api.room.RoomMember
1818
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
1919
import kotlinx.collections.immutable.ImmutableList
2020
import kotlinx.collections.immutable.ImmutableMap
21+
import kotlinx.collections.immutable.persistentListOf
2122

2223
data class ChangeRoomPermissionsState(
24+
private val ownPowerLevel: Long,
2325
val currentPermissions: RoomPowerLevelsValues?,
2426
val itemsBySection: ImmutableMap<RoomPermissionsSection, ImmutableList<RoomPermissionType>>,
2527
val hasChanges: Boolean,
2628
val saveAction: AsyncAction<Boolean>,
2729
val eventSink: (ChangeRoomPermissionsEvent) -> Unit,
2830
) {
31+
private val ownRole = RoomMember.Role.forPowerLevel(ownPowerLevel)
32+
33+
// Roles that the user can select based on their own role
34+
val selectableRoles: ImmutableList<SelectableRole> = when (ownRole) {
35+
is RoomMember.Role.Owner,
36+
RoomMember.Role.Admin -> persistentListOf(SelectableRole.Admin, SelectableRole.Moderator, SelectableRole.Everyone)
37+
RoomMember.Role.Moderator -> persistentListOf(SelectableRole.Moderator, SelectableRole.Everyone)
38+
RoomMember.Role.User -> persistentListOf(SelectableRole.Everyone)
39+
}
40+
2941
fun selectedRoleForType(type: RoomPermissionType): SelectableRole? {
30-
if (currentPermissions == null) return null
31-
val role = when (type) {
32-
RoomPermissionType.BAN -> RoomMember.Role.forPowerLevel(currentPermissions.ban)
33-
RoomPermissionType.INVITE -> RoomMember.Role.forPowerLevel(currentPermissions.invite)
34-
RoomPermissionType.KICK -> RoomMember.Role.forPowerLevel(currentPermissions.kick)
35-
RoomPermissionType.SEND_EVENTS -> RoomMember.Role.forPowerLevel(currentPermissions.eventsDefault)
36-
RoomPermissionType.REDACT_EVENTS -> RoomMember.Role.forPowerLevel(currentPermissions.redactEvents)
37-
RoomPermissionType.ROOM_NAME -> RoomMember.Role.forPowerLevel(currentPermissions.roomName)
38-
RoomPermissionType.ROOM_AVATAR -> RoomMember.Role.forPowerLevel(currentPermissions.roomAvatar)
39-
RoomPermissionType.ROOM_TOPIC -> RoomMember.Role.forPowerLevel(currentPermissions.roomTopic)
40-
RoomPermissionType.SPACE_MANAGE_ROOMS -> RoomMember.Role.forPowerLevel(currentPermissions.spaceChild)
41-
RoomPermissionType.CHANGE_SETTINGS -> RoomMember.Role.forPowerLevel(currentPermissions.stateDefault)
42-
}
43-
return when (role) {
42+
val powerLevel = currentPowerLevelForType(type = type) ?: return null
43+
return when (RoomMember.Role.forPowerLevel(powerLevel)) {
4444
is RoomMember.Role.Owner,
4545
RoomMember.Role.Admin -> SelectableRole.Admin
4646
RoomMember.Role.Moderator -> SelectableRole.Moderator
4747
RoomMember.Role.User -> SelectableRole.Everyone
4848
}
4949
}
50+
51+
fun canChangePermission(type: RoomPermissionType): Boolean {
52+
val currentPowerLevel = currentPowerLevelForType(type) ?: return false
53+
return ownPowerLevel >= currentPowerLevel
54+
}
55+
56+
private fun currentPowerLevelForType(type: RoomPermissionType): Long? {
57+
if (currentPermissions == null) return null
58+
return when (type) {
59+
RoomPermissionType.BAN -> currentPermissions.ban
60+
RoomPermissionType.INVITE -> currentPermissions.invite
61+
RoomPermissionType.KICK -> currentPermissions.kick
62+
RoomPermissionType.SEND_EVENTS -> currentPermissions.eventsDefault
63+
RoomPermissionType.REDACT_EVENTS -> currentPermissions.redactEvents
64+
RoomPermissionType.ROOM_NAME -> currentPermissions.roomName
65+
RoomPermissionType.ROOM_AVATAR -> currentPermissions.roomAvatar
66+
RoomPermissionType.ROOM_TOPIC -> currentPermissions.roomTopic
67+
RoomPermissionType.SPACE_MANAGE_ROOMS -> currentPermissions.spaceChild
68+
}
69+
}
5070
}
5171

5272
enum class RoomPermissionsSection {
@@ -84,5 +104,4 @@ enum class RoomPermissionType {
84104
ROOM_AVATAR,
85105
ROOM_TOPIC,
86106
SPACE_MANAGE_ROOMS,
87-
CHANGE_SETTINGS,
88107
}

features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsStateProvider.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class ChangeRoomPermissionsStateProvider : PreviewParameterProvider<ChangeRoomPe
1919
override val values: Sequence<ChangeRoomPermissionsState>
2020
get() = sequenceOf(
2121
aChangeRoomPermissionsState(),
22+
aChangeRoomPermissionsState(ownPowerLevel = RoomMember.Role.Moderator.powerLevel),
2223
aChangeRoomPermissionsState(hasChanges = true),
2324
aChangeRoomPermissionsState(hasChanges = true, saveAction = AsyncAction.Loading),
2425
aChangeRoomPermissionsState(
@@ -31,12 +32,14 @@ class ChangeRoomPermissionsStateProvider : PreviewParameterProvider<ChangeRoomPe
3132
}
3233

3334
internal fun aChangeRoomPermissionsState(
35+
ownPowerLevel: Long = RoomMember.Role.Admin.powerLevel,
3436
currentPermissions: RoomPowerLevelsValues = previewPermissions(),
3537
itemsBySection: Map<RoomPermissionsSection, ImmutableList<RoomPermissionType>> = ChangeRoomPermissionsPresenter.buildItems(false),
3638
hasChanges: Boolean = false,
3739
saveAction: AsyncAction<Boolean> = AsyncAction.Uninitialized,
3840
eventSink: (ChangeRoomPermissionsEvent) -> Unit = {},
3941
) = ChangeRoomPermissionsState(
42+
ownPowerLevel = ownPowerLevel,
4043
currentPermissions = currentPermissions,
4144
itemsBySection = itemsBySection.toImmutableMap(),
4245
hasChanges = hasChanges,

features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold
3030
import io.element.android.libraries.designsystem.theme.components.TextButton
3131
import io.element.android.libraries.designsystem.theme.components.TopAppBar
3232
import io.element.android.libraries.ui.strings.CommonStrings
33-
import kotlinx.collections.immutable.toImmutableList
3433

3534
@OptIn(ExperimentalMaterial3Api::class)
3635
@Composable
@@ -74,7 +73,8 @@ fun ChangeRoomPermissionsView(
7473
PreferenceDropdown(
7574
title = titleForType(permissionType),
7675
selectedOption = state.selectedRoleForType(permissionType),
77-
options = SelectableRole.entries.toImmutableList(),
76+
options = state.selectableRoles,
77+
enabled = state.canChangePermission(permissionType),
7878
onSelectOption = { role ->
7979
state.eventSink(
8080
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(
@@ -127,7 +127,6 @@ private fun titleForType(type: RoomPermissionType): String = when (type) {
127127
RoomPermissionType.ROOM_AVATAR -> stringResource(R.string.screen_room_change_permissions_room_avatar)
128128
RoomPermissionType.ROOM_TOPIC -> stringResource(R.string.screen_room_change_permissions_room_topic)
129129
RoomPermissionType.SPACE_MANAGE_ROOMS -> stringResource(R.string.screen_room_change_permissions_manage_space_rooms)
130-
RoomPermissionType.CHANGE_SETTINGS -> stringResource(R.string.screen_room_change_permissions_change_settings)
131130
}
132131

133132
@PreviewsDayNight

features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenter.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
3636
import io.element.android.libraries.matrix.api.room.powerlevels.usersWithRole
3737
import io.element.android.libraries.matrix.api.room.toMatrixUser
3838
import io.element.android.libraries.matrix.api.user.MatrixUser
39+
import io.element.android.libraries.matrix.ui.model.powerLevelOf
3940
import io.element.android.libraries.matrix.ui.model.roleOf
4041
import io.element.android.libraries.matrix.ui.room.PowerLevelRoomMemberComparator
4142
import io.element.android.services.analytics.api.AnalyticsService
@@ -124,9 +125,10 @@ class ChangeRolesPresenter(
124125

125126
val roomInfo by room.roomInfoFlow.collectAsState()
126127
fun canChangeMemberRole(userId: UserId): Boolean {
127-
val currentUserRole = roomInfo.roleOf(room.sessionId)
128-
val otherUserRole = roomInfo.roleOf(userId)
129-
return currentUserRole.powerLevel > otherUserRole.powerLevel
128+
val currentUserPowerLevel = roomInfo.powerLevelOf(room.sessionId)
129+
val otherUserPowerLevel = roomInfo.powerLevelOf(userId)
130+
return currentUserPowerLevel > otherUserPowerLevel &&
131+
currentUserPowerLevel >= role.powerLevel
130132
}
131133

132134
fun handleEvent(event: ChangeRolesEvent) {

features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
2828
import io.element.android.libraries.matrix.api.room.powerlevels.userCountWithRole
2929
import io.element.android.libraries.matrix.ui.model.roleOf
3030
import io.element.android.services.analytics.api.AnalyticsService
31+
import kotlinx.collections.immutable.persistentListOf
3132
import kotlinx.coroutines.CoroutineScope
3233
import kotlinx.coroutines.launch
3334

@@ -49,7 +50,16 @@ class RolesAndPermissionsPresenter(
4950
room.userCountWithRole { role -> role is RoomMember.Role.Admin || role is RoomMember.Role.Owner }
5051
}.collectAsState(null)
5152

52-
val canDemoteSelf = remember { derivedStateOf { roomInfo.roleOf(room.sessionId) !is RoomMember.Role.Owner } }
53+
val availableDemoteActions by remember {
54+
derivedStateOf {
55+
val currentRole = roomInfo.roleOf(room.sessionId)
56+
when (currentRole) {
57+
is RoomMember.Role.Admin -> persistentListOf(SelfDemoteAction.ToModerator, SelfDemoteAction.ToMember)
58+
is RoomMember.Role.Moderator -> persistentListOf(SelfDemoteAction.ToMember)
59+
else -> persistentListOf()
60+
}
61+
}
62+
}
5363
val changeOwnRoleAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
5464
val resetPermissionsAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
5565

@@ -78,7 +88,7 @@ class RolesAndPermissionsPresenter(
7888
roomSupportsOwnerRole = roomInfo.privilegedCreatorRole,
7989
adminCount = adminCount,
8090
moderatorCount = moderatorCount,
81-
canDemoteSelf = canDemoteSelf.value,
91+
availableSelfDemoteActions = availableDemoteActions,
8292
changeOwnRoleAction = changeOwnRoleAction.value,
8393
resetPermissionsAction = resetPermissionsAction.value,
8494
eventSink = ::handleEvent,

features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,24 @@
88

99
package io.element.android.features.rolesandpermissions.impl.root
1010

11+
import io.element.android.features.rolesandpermissions.impl.R
1112
import io.element.android.libraries.architecture.AsyncAction
13+
import io.element.android.libraries.matrix.api.room.RoomMember
14+
import kotlinx.collections.immutable.ImmutableList
1215

1316
data class RolesAndPermissionsState(
1417
val roomSupportsOwnerRole: Boolean,
1518
val adminCount: Int?,
1619
val moderatorCount: Int?,
17-
val canDemoteSelf: Boolean,
20+
val availableSelfDemoteActions: ImmutableList<SelfDemoteAction>,
1821
val changeOwnRoleAction: AsyncAction<Unit>,
1922
val resetPermissionsAction: AsyncAction<Unit>,
2023
val eventSink: (RolesAndPermissionsEvents) -> Unit,
21-
)
24+
) {
25+
val canSelfDemote = availableSelfDemoteActions.isNotEmpty()
26+
}
27+
28+
enum class SelfDemoteAction(val role: RoomMember.Role, val titleRes: Int) {
29+
ToModerator(RoomMember.Role.Moderator, R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator),
30+
ToMember(RoomMember.Role.User, R.string.screen_room_roles_and_permissions_change_role_demote_to_member)
31+
}

features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStateProvider.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package io.element.android.features.rolesandpermissions.impl.root
1010

1111
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
1212
import io.element.android.libraries.architecture.AsyncAction
13+
import kotlinx.collections.immutable.toImmutableList
1314

1415
class RolesAndPermissionsStateProvider : PreviewParameterProvider<RolesAndPermissionsState> {
1516
override val values: Sequence<RolesAndPermissionsState>
@@ -46,22 +47,22 @@ class RolesAndPermissionsStateProvider : PreviewParameterProvider<RolesAndPermis
4647
moderatorCount = 2,
4748
resetPermissionsAction = AsyncAction.Failure(IllegalStateException("Failed to reset permissions")),
4849
),
49-
aRolesAndPermissionsState(canDemoteSelf = false),
50+
aRolesAndPermissionsState(availableSelfDemoteActions = emptyList()),
5051
)
5152
}
5253

5354
internal fun aRolesAndPermissionsState(
5455
roomSupportsOwners: Boolean = true,
5556
adminCount: Int = 0,
5657
moderatorCount: Int = 0,
57-
canDemoteSelf: Boolean = true,
58+
availableSelfDemoteActions: List<SelfDemoteAction> = listOf(SelfDemoteAction.ToModerator, SelfDemoteAction.ToMember),
5859
changeOwnRoleAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
5960
resetPermissionsAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
6061
eventSink: (RolesAndPermissionsEvents) -> Unit = {},
6162
) = RolesAndPermissionsState(
6263
roomSupportsOwnerRole = roomSupportsOwners,
6364
adminCount = adminCount,
64-
canDemoteSelf = canDemoteSelf,
65+
availableSelfDemoteActions = availableSelfDemoteActions.toImmutableList(),
6566
moderatorCount = moderatorCount,
6667
changeOwnRoleAction = changeOwnRoleAction,
6768
resetPermissionsAction = resetPermissionsAction,

features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ import io.element.android.libraries.designsystem.theme.components.ListSectionHea
3939
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet
4040
import io.element.android.libraries.designsystem.theme.components.Text
4141
import io.element.android.libraries.designsystem.theme.components.hide
42-
import io.element.android.libraries.matrix.api.room.RoomMember
4342
import io.element.android.libraries.ui.strings.CommonStrings
43+
import kotlinx.collections.immutable.ImmutableList
4444

4545
@Composable
4646
fun RolesAndPermissionsView(
@@ -76,7 +76,7 @@ fun RolesAndPermissionsView(
7676
},
7777
onClick = { rolesAndPermissionsNavigator.openModeratorList() },
7878
)
79-
if (state.canDemoteSelf) {
79+
if (state.canSelfDemote) {
8080
ListItem(
8181
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_my_role)) },
8282
onClick = { state.eventSink(RolesAndPermissionsEvents.ChangeOwnRole) },
@@ -117,6 +117,7 @@ fun RolesAndPermissionsView(
117117
when (state.changeOwnRoleAction) {
118118
is AsyncAction.Confirming -> {
119119
ChangeOwnRoleBottomSheet(
120+
availableDemoteActions = state.availableSelfDemoteActions,
120121
eventSink = state.eventSink,
121122
)
122123
}
@@ -136,6 +137,7 @@ fun RolesAndPermissionsView(
136137
@OptIn(ExperimentalMaterial3Api::class)
137138
@Composable
138139
private fun ChangeOwnRoleBottomSheet(
140+
availableDemoteActions: ImmutableList<SelfDemoteAction>,
139141
eventSink: (RolesAndPermissionsEvents) -> Unit,
140142
) {
141143
val coroutineScope = rememberCoroutineScope()
@@ -164,24 +166,17 @@ private fun ChangeOwnRoleBottomSheet(
164166
style = ElementTheme.typography.fontBodyLgRegular,
165167
color = ElementTheme.colors.textPrimary,
166168
)
167-
ListItem(
168-
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator)) },
169-
onClick = {
170-
sheetState.hide(coroutineScope) {
171-
eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator))
172-
}
173-
},
174-
style = ListItemStyle.Destructive,
175-
)
176-
ListItem(
177-
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_role_demote_to_member)) },
178-
onClick = {
179-
sheetState.hide(coroutineScope) {
180-
eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.User))
181-
}
182-
},
183-
style = ListItemStyle.Destructive,
184-
)
169+
for (demoteAction in availableDemoteActions) {
170+
ListItem(
171+
headlineContent = { Text(stringResource(demoteAction.titleRes)) },
172+
onClick = {
173+
sheetState.hide(coroutineScope) {
174+
eventSink(RolesAndPermissionsEvents.DemoteSelfTo(demoteAction.role))
175+
}
176+
},
177+
style = ListItemStyle.Destructive,
178+
)
179+
}
185180
ListItem(
186181
headlineContent = { Text(stringResource(CommonStrings.action_cancel)) },
187182
onClick = ::dismiss,

0 commit comments

Comments
 (0)