Merge pull request #5950 from element-hq/feature/fga/iterate_permissions_screen
Changes : iterate again on permissions
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
|
||||
@@ -52,7 +55,6 @@ class ChangeRoomPermissionsPresenter(
|
||||
)
|
||||
RoomPermissionsSection.ManageSpace -> persistentListOf(
|
||||
RoomPermissionType.SPACE_MANAGE_ROOMS,
|
||||
RoomPermissionType.CHANGE_SETTINGS,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -90,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 -> {
|
||||
@@ -108,7 +114,6 @@ class ChangeRoomPermissionsPresenter(
|
||||
RoomPermissionType.ROOM_AVATAR -> currentPermissions?.copy(roomAvatar = powerLevel)
|
||||
RoomPermissionType.ROOM_TOPIC -> currentPermissions?.copy(roomTopic = powerLevel)
|
||||
RoomPermissionType.SPACE_MANAGE_ROOMS -> currentPermissions?.copy(spaceChild = powerLevel)
|
||||
RoomPermissionType.CHANGE_SETTINGS -> currentPermissions?.copy(stateDefault = powerLevel)
|
||||
}
|
||||
}
|
||||
is ChangeRoomPermissionsEvent.Save -> coroutineScope.save()
|
||||
@@ -125,6 +130,7 @@ class ChangeRoomPermissionsPresenter(
|
||||
}
|
||||
}
|
||||
return ChangeRoomPermissionsState(
|
||||
ownPowerLevel = ownPowerLevel,
|
||||
currentPermissions = currentPermissions,
|
||||
itemsBySection = itemsBySection,
|
||||
hasChanges = hasChanges,
|
||||
|
||||
@@ -18,35 +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)
|
||||
RoomPermissionType.CHANGE_SETTINGS -> RoomMember.Role.forPowerLevel(currentPermissions.stateDefault)
|
||||
}
|
||||
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 {
|
||||
@@ -84,5 +104,4 @@ enum class RoomPermissionType {
|
||||
ROOM_AVATAR,
|
||||
ROOM_TOPIC,
|
||||
SPACE_MANAGE_ROOMS,
|
||||
CHANGE_SETTINGS,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -30,7 +30,6 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -74,7 +73,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(
|
||||
@@ -127,7 +127,6 @@ private fun titleForType(type: RoomPermissionType): String = when (type) {
|
||||
RoomPermissionType.ROOM_AVATAR -> stringResource(R.string.screen_room_change_permissions_room_avatar)
|
||||
RoomPermissionType.ROOM_TOPIC -> stringResource(R.string.screen_room_change_permissions_room_topic)
|
||||
RoomPermissionType.SPACE_MANAGE_ROOMS -> stringResource(R.string.screen_room_change_permissions_manage_space_rooms)
|
||||
RoomPermissionType.CHANGE_SETTINGS -> stringResource(R.string.screen_room_change_permissions_change_settings)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
|
||||
@@ -36,6 +36,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.usersWithRole
|
||||
import io.element.android.libraries.matrix.api.room.toMatrixUser
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.model.powerLevelOf
|
||||
import io.element.android.libraries.matrix.ui.model.roleOf
|
||||
import io.element.android.libraries.matrix.ui.room.PowerLevelRoomMemberComparator
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
@@ -124,9 +125,10 @@ class ChangeRolesPresenter(
|
||||
|
||||
val roomInfo by room.roomInfoFlow.collectAsState()
|
||||
fun canChangeMemberRole(userId: UserId): Boolean {
|
||||
val currentUserRole = roomInfo.roleOf(room.sessionId)
|
||||
val otherUserRole = roomInfo.roleOf(userId)
|
||||
return currentUserRole.powerLevel > otherUserRole.powerLevel
|
||||
val currentUserPowerLevel = roomInfo.powerLevelOf(room.sessionId)
|
||||
val otherUserPowerLevel = roomInfo.powerLevelOf(userId)
|
||||
return currentUserPowerLevel > otherUserPowerLevel &&
|
||||
currentUserPowerLevel >= role.powerLevel
|
||||
}
|
||||
|
||||
fun handleEvent(event: ChangeRolesEvent) {
|
||||
|
||||
@@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.userCountWithRole
|
||||
import io.element.android.libraries.matrix.ui.model.roleOf
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -49,7 +50,16 @@ class RolesAndPermissionsPresenter(
|
||||
room.userCountWithRole { role -> role is RoomMember.Role.Admin || role is RoomMember.Role.Owner }
|
||||
}.collectAsState(null)
|
||||
|
||||
val canDemoteSelf = remember { derivedStateOf { roomInfo.roleOf(room.sessionId) !is RoomMember.Role.Owner } }
|
||||
val availableDemoteActions by remember {
|
||||
derivedStateOf {
|
||||
val currentRole = roomInfo.roleOf(room.sessionId)
|
||||
when (currentRole) {
|
||||
is RoomMember.Role.Admin -> persistentListOf(SelfDemoteAction.ToModerator, SelfDemoteAction.ToMember)
|
||||
is RoomMember.Role.Moderator -> persistentListOf(SelfDemoteAction.ToMember)
|
||||
else -> persistentListOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
val changeOwnRoleAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||
val resetPermissionsAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||
|
||||
@@ -78,7 +88,7 @@ class RolesAndPermissionsPresenter(
|
||||
roomSupportsOwnerRole = roomInfo.privilegedCreatorRole,
|
||||
adminCount = adminCount,
|
||||
moderatorCount = moderatorCount,
|
||||
canDemoteSelf = canDemoteSelf.value,
|
||||
availableSelfDemoteActions = availableDemoteActions,
|
||||
changeOwnRoleAction = changeOwnRoleAction.value,
|
||||
resetPermissionsAction = resetPermissionsAction.value,
|
||||
eventSink = ::handleEvent,
|
||||
|
||||
@@ -8,14 +8,24 @@
|
||||
|
||||
package io.element.android.features.rolesandpermissions.impl.root
|
||||
|
||||
import io.element.android.features.rolesandpermissions.impl.R
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class RolesAndPermissionsState(
|
||||
val roomSupportsOwnerRole: Boolean,
|
||||
val adminCount: Int?,
|
||||
val moderatorCount: Int?,
|
||||
val canDemoteSelf: Boolean,
|
||||
val availableSelfDemoteActions: ImmutableList<SelfDemoteAction>,
|
||||
val changeOwnRoleAction: AsyncAction<Unit>,
|
||||
val resetPermissionsAction: AsyncAction<Unit>,
|
||||
val eventSink: (RolesAndPermissionsEvents) -> Unit,
|
||||
)
|
||||
) {
|
||||
val canSelfDemote = availableSelfDemoteActions.isNotEmpty()
|
||||
}
|
||||
|
||||
enum class SelfDemoteAction(val role: RoomMember.Role, val titleRes: Int) {
|
||||
ToModerator(RoomMember.Role.Moderator, R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator),
|
||||
ToMember(RoomMember.Role.User, R.string.screen_room_roles_and_permissions_change_role_demote_to_member)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ package io.element.android.features.rolesandpermissions.impl.root
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
class RolesAndPermissionsStateProvider : PreviewParameterProvider<RolesAndPermissionsState> {
|
||||
override val values: Sequence<RolesAndPermissionsState>
|
||||
@@ -46,7 +47,7 @@ class RolesAndPermissionsStateProvider : PreviewParameterProvider<RolesAndPermis
|
||||
moderatorCount = 2,
|
||||
resetPermissionsAction = AsyncAction.Failure(IllegalStateException("Failed to reset permissions")),
|
||||
),
|
||||
aRolesAndPermissionsState(canDemoteSelf = false),
|
||||
aRolesAndPermissionsState(availableSelfDemoteActions = emptyList()),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -54,14 +55,14 @@ internal fun aRolesAndPermissionsState(
|
||||
roomSupportsOwners: Boolean = true,
|
||||
adminCount: Int = 0,
|
||||
moderatorCount: Int = 0,
|
||||
canDemoteSelf: Boolean = true,
|
||||
availableSelfDemoteActions: List<SelfDemoteAction> = listOf(SelfDemoteAction.ToModerator, SelfDemoteAction.ToMember),
|
||||
changeOwnRoleAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
resetPermissionsAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
eventSink: (RolesAndPermissionsEvents) -> Unit = {},
|
||||
) = RolesAndPermissionsState(
|
||||
roomSupportsOwnerRole = roomSupportsOwners,
|
||||
adminCount = adminCount,
|
||||
canDemoteSelf = canDemoteSelf,
|
||||
availableSelfDemoteActions = availableSelfDemoteActions.toImmutableList(),
|
||||
moderatorCount = moderatorCount,
|
||||
changeOwnRoleAction = changeOwnRoleAction,
|
||||
resetPermissionsAction = resetPermissionsAction,
|
||||
|
||||
@@ -39,8 +39,8 @@ import io.element.android.libraries.designsystem.theme.components.ListSectionHea
|
||||
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.hide
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
fun RolesAndPermissionsView(
|
||||
@@ -76,7 +76,7 @@ fun RolesAndPermissionsView(
|
||||
},
|
||||
onClick = { rolesAndPermissionsNavigator.openModeratorList() },
|
||||
)
|
||||
if (state.canDemoteSelf) {
|
||||
if (state.canSelfDemote) {
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_my_role)) },
|
||||
onClick = { state.eventSink(RolesAndPermissionsEvents.ChangeOwnRole) },
|
||||
@@ -117,6 +117,7 @@ fun RolesAndPermissionsView(
|
||||
when (state.changeOwnRoleAction) {
|
||||
is AsyncAction.Confirming -> {
|
||||
ChangeOwnRoleBottomSheet(
|
||||
availableDemoteActions = state.availableSelfDemoteActions,
|
||||
eventSink = state.eventSink,
|
||||
)
|
||||
}
|
||||
@@ -136,6 +137,7 @@ fun RolesAndPermissionsView(
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ChangeOwnRoleBottomSheet(
|
||||
availableDemoteActions: ImmutableList<SelfDemoteAction>,
|
||||
eventSink: (RolesAndPermissionsEvents) -> Unit,
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
@@ -164,24 +166,17 @@ private fun ChangeOwnRoleBottomSheet(
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator)) },
|
||||
onClick = {
|
||||
sheetState.hide(coroutineScope) {
|
||||
eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator))
|
||||
}
|
||||
},
|
||||
style = ListItemStyle.Destructive,
|
||||
)
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_role_demote_to_member)) },
|
||||
onClick = {
|
||||
sheetState.hide(coroutineScope) {
|
||||
eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.User))
|
||||
}
|
||||
},
|
||||
style = ListItemStyle.Destructive,
|
||||
)
|
||||
for (demoteAction in availableDemoteActions) {
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(demoteAction.titleRes)) },
|
||||
onClick = {
|
||||
sheetState.hide(coroutineScope) {
|
||||
eventSink(RolesAndPermissionsEvents.DemoteSelfTo(demoteAction.role))
|
||||
}
|
||||
},
|
||||
style = ListItemStyle.Destructive,
|
||||
)
|
||||
}
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(CommonStrings.action_cancel)) },
|
||||
onClick = ::dismiss,
|
||||
|
||||
@@ -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,10 @@ 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 +304,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 {
|
||||
|
||||
@@ -26,7 +26,7 @@ data class RoomMemberListState(
|
||||
val moderationState: RoomMemberModerationState,
|
||||
val eventSink: (RoomMemberListEvents) -> Unit,
|
||||
) {
|
||||
val showBannedSection: Boolean = moderationState.permissions.canBan && roomMembers.dataOrNull()?.banned?.isNotEmpty() == true
|
||||
val showBannedSection: Boolean = moderationState.permissions.hasAny && roomMembers.dataOrNull()?.banned?.isNotEmpty() == true
|
||||
}
|
||||
|
||||
enum class SelectedSection {
|
||||
|
||||
@@ -13,6 +13,8 @@ data class RoomMemberModerationPermissions(
|
||||
val canKick: Boolean,
|
||||
val canBan: Boolean,
|
||||
) {
|
||||
val hasAny = canKick || canBan
|
||||
|
||||
companion object {
|
||||
val DEFAULT = RoomMemberModerationPermissions(
|
||||
canKick = false,
|
||||
|
||||
@@ -28,6 +28,7 @@ import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runUpdatingState
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.coroutine.mapState
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
@@ -35,7 +36,7 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.room.userPowerLevelAsState
|
||||
import io.element.android.libraries.matrix.ui.model.powerLevelOf
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@@ -56,11 +57,14 @@ class RoomMemberModerationPresenter(
|
||||
@Composable
|
||||
override fun present(): RoomMemberModerationState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
||||
val permissions by room.permissionsAsState(RoomMemberModerationPermissions.DEFAULT) { perms ->
|
||||
perms.roomMemberModerationPermissions()
|
||||
}
|
||||
val currentUserMemberPowerLevel = room.userPowerLevelAsState(syncUpdateFlow.value)
|
||||
val currentUserPowerLevel by remember {
|
||||
room.roomInfoFlow.mapState { info ->
|
||||
info.powerLevelOf(room.sessionId)
|
||||
}
|
||||
}.collectAsState()
|
||||
|
||||
val kickUserAsyncAction =
|
||||
remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
|
||||
@@ -83,7 +87,7 @@ class RoomMemberModerationPresenter(
|
||||
moderationActions.value = computeModerationActions(
|
||||
member = member,
|
||||
permissions = permissions,
|
||||
currentUserMemberPowerLevel = currentUserMemberPowerLevel.value,
|
||||
currentUserPowerLevel = currentUserPowerLevel,
|
||||
)
|
||||
}
|
||||
is RoomMemberModerationEvents.ProcessAction -> {
|
||||
@@ -148,26 +152,26 @@ class RoomMemberModerationPresenter(
|
||||
private fun computeModerationActions(
|
||||
member: RoomMember?,
|
||||
permissions: RoomMemberModerationPermissions,
|
||||
currentUserMemberPowerLevel: Long,
|
||||
currentUserPowerLevel: Long,
|
||||
): ImmutableList<ModerationActionState> {
|
||||
return buildList {
|
||||
add(ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true))
|
||||
// Assume the member is a regular user when it's unknown
|
||||
val targetMemberPowerLevel = member?.powerLevel ?: 0
|
||||
val canModerateThisUser = currentUserMemberPowerLevel > targetMemberPowerLevel
|
||||
val canModerateThisUser = currentUserPowerLevel > targetMemberPowerLevel
|
||||
// Assume the member is joined when it's unknown
|
||||
val membership = member?.membership ?: RoomMembershipState.JOIN
|
||||
if (permissions.canKick) {
|
||||
val isKickEnabled = canModerateThisUser && membership.isActive()
|
||||
add(ModerationActionState(action = ModerationAction.KickUser, isEnabled = isKickEnabled))
|
||||
}
|
||||
if (permissions.canBan) {
|
||||
// Unban requires kick permission instead of a dedicated unban permission
|
||||
if (membership == RoomMembershipState.BAN) {
|
||||
add(ModerationActionState(action = ModerationAction.UnbanUser, isEnabled = canModerateThisUser))
|
||||
} else {
|
||||
add(ModerationActionState(action = ModerationAction.BanUser, isEnabled = canModerateThisUser))
|
||||
} else if (membership != RoomMembershipState.LEAVE) {
|
||||
add(ModerationActionState(action = ModerationAction.KickUser, isEnabled = canModerateThisUser))
|
||||
}
|
||||
}
|
||||
if (permissions.canBan && membership != RoomMembershipState.BAN) {
|
||||
add(ModerationActionState(action = ModerationAction.BanUser, isEnabled = canModerateThisUser))
|
||||
}
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
|
||||
@@ -21,11 +21,14 @@ 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.RoomMembersState
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.test.A_USER_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.aRoomMember
|
||||
import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevelValues
|
||||
import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
@@ -33,6 +36,7 @@ import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.test
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -161,7 +165,6 @@ class RoomMemberModerationPresenterTest {
|
||||
assertThat(updatedState.selectedUser).isEqualTo(targetUser)
|
||||
assertThat(updatedState.actions).containsExactly(
|
||||
ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true),
|
||||
ModerationActionState(action = ModerationAction.KickUser, isEnabled = false),
|
||||
ModerationActionState(action = ModerationAction.UnbanUser, isEnabled = true),
|
||||
)
|
||||
}
|
||||
@@ -223,9 +226,11 @@ class RoomMemberModerationPresenterTest {
|
||||
val room = aJoinedRoom()
|
||||
room.baseRoom.givenUpdateMembersResult {
|
||||
// Simulate the member list being updated
|
||||
room.givenRoomMembersState(RoomMembersState.Ready(
|
||||
persistentListOf(aRoomMember())
|
||||
))
|
||||
room.givenRoomMembersState(
|
||||
RoomMembersState.Ready(
|
||||
persistentListOf(aRoomMember())
|
||||
)
|
||||
)
|
||||
}
|
||||
createRoomMemberModerationPresenter(room = room).test {
|
||||
val initialState = awaitState()
|
||||
@@ -251,9 +256,11 @@ class RoomMemberModerationPresenterTest {
|
||||
val room = aJoinedRoom()
|
||||
room.baseRoom.givenUpdateMembersResult {
|
||||
// Simulate the member list being updated
|
||||
room.givenRoomMembersState(RoomMembersState.Ready(
|
||||
persistentListOf(aRoomMember())
|
||||
))
|
||||
room.givenRoomMembersState(
|
||||
RoomMembersState.Ready(
|
||||
persistentListOf(aRoomMember())
|
||||
)
|
||||
)
|
||||
}
|
||||
createRoomMemberModerationPresenter(room = room).test {
|
||||
val initialState = awaitState()
|
||||
@@ -279,9 +286,11 @@ class RoomMemberModerationPresenterTest {
|
||||
val room = aJoinedRoom()
|
||||
room.baseRoom.givenUpdateMembersResult {
|
||||
// Simulate the member list being updated
|
||||
room.givenRoomMembersState(RoomMembersState.Ready(
|
||||
persistentListOf(aRoomMember())
|
||||
))
|
||||
room.givenRoomMembersState(
|
||||
RoomMembersState.Ready(
|
||||
persistentListOf(aRoomMember())
|
||||
)
|
||||
)
|
||||
}
|
||||
createRoomMemberModerationPresenter(room = room).test {
|
||||
val initialState = awaitState()
|
||||
@@ -361,7 +370,13 @@ class RoomMemberModerationPresenterTest {
|
||||
canKick = canKick
|
||||
),
|
||||
userRoleResult = { Result.success(myUserRole) },
|
||||
updateMembersResult = { Result.success(Unit) }
|
||||
updateMembersResult = { Result.success(Unit) },
|
||||
initialRoomInfo = aRoomInfo(
|
||||
roomPowerLevels = RoomPowerLevels(
|
||||
values = defaultRoomPowerLevelValues(),
|
||||
users = persistentMapOf(A_USER_ID to myUserRole.powerLevel)
|
||||
)
|
||||
)
|
||||
),
|
||||
).apply {
|
||||
val roomMembers = listOfNotNull(targetRoomMember).toImmutableList()
|
||||
|
||||
@@ -70,7 +70,7 @@ private fun PreferenceBlockUser(
|
||||
isLoading: Boolean,
|
||||
eventSink: (UserProfileEvents) -> Unit,
|
||||
) {
|
||||
val loadingCurrentValue = @Composable {
|
||||
val loadingCurrentValue = @Composable { _: Boolean ->
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.progressSemantics()
|
||||
|
||||
@@ -85,7 +85,7 @@ sealed interface ListItemContent {
|
||||
data class Text(val text: String) : ListItemContent
|
||||
|
||||
/** Displays any custom content. */
|
||||
data class Custom(val content: @Composable () -> Unit) : ListItemContent
|
||||
data class Custom(val content: @Composable (enabled: Boolean) -> Unit) : ListItemContent
|
||||
|
||||
/** Displays a badge. */
|
||||
data object Badge : ListItemContent
|
||||
@@ -131,7 +131,7 @@ sealed interface ListItemContent {
|
||||
is Counter -> {
|
||||
CounterAtom(count = count)
|
||||
}
|
||||
is Custom -> content()
|
||||
is Custom -> content(isItemEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ fun PreferenceCheckbox(
|
||||
leadingContent = preferenceIcon(
|
||||
icon = icon,
|
||||
iconResourceId = iconResourceId,
|
||||
enabled = enabled,
|
||||
showIconAreaIfNoIcon = showIconAreaIfNoIcon,
|
||||
),
|
||||
headlineContent = {
|
||||
|
||||
@@ -40,7 +40,7 @@ import io.element.android.libraries.designsystem.theme.components.DropdownMenuIt
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.toEnabledColor
|
||||
import io.element.android.libraries.designsystem.toIconSecondaryEnabledColor
|
||||
import io.element.android.libraries.designsystem.toSecondaryEnabledColor
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -64,7 +64,6 @@ fun <T : DropdownOption> PreferenceDropdown(
|
||||
leadingContent = preferenceIcon(
|
||||
icon = icon,
|
||||
iconResourceId = iconResourceId,
|
||||
enabled = enabled,
|
||||
showIconAreaIfNoIcon = showIconAreaIfNoIcon,
|
||||
),
|
||||
headlineContent = {
|
||||
@@ -72,7 +71,6 @@ fun <T : DropdownOption> PreferenceDropdown(
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = title,
|
||||
color = enabled.toEnabledColor(),
|
||||
)
|
||||
},
|
||||
supportingContent = supportingText?.let {
|
||||
@@ -80,22 +78,23 @@ fun <T : DropdownOption> PreferenceDropdown(
|
||||
Text(
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
text = it,
|
||||
color = enabled.toSecondaryEnabledColor(),
|
||||
)
|
||||
}
|
||||
},
|
||||
trailingContent = ListItemContent.Custom(
|
||||
content = {
|
||||
content = { enabled ->
|
||||
DropdownTrailingContent(
|
||||
selectedOption = selectedOption,
|
||||
options = options,
|
||||
onSelectOption = onSelectOption,
|
||||
expanded = isDropdownExpanded,
|
||||
onExpandedChange = { isDropdownExpanded = it },
|
||||
enabled = enabled,
|
||||
modifier = Modifier.fillMaxSize(0.3f)
|
||||
)
|
||||
}
|
||||
),
|
||||
enabled = enabled,
|
||||
onClick = { isDropdownExpanded = true }.takeIf { !isDropdownExpanded },
|
||||
)
|
||||
}
|
||||
@@ -118,6 +117,7 @@ private fun <T : DropdownOption> DropdownTrailingContent(
|
||||
expanded: Boolean,
|
||||
onExpandedChange: (Boolean) -> Unit,
|
||||
onSelectOption: (T) -> Unit,
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
@@ -129,7 +129,7 @@ private fun <T : DropdownOption> DropdownTrailingContent(
|
||||
text = selectedOption?.getText().orEmpty(),
|
||||
maxLines = 1,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
color = enabled.toSecondaryEnabledColor(),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.End,
|
||||
modifier = Modifier.weight(1f),
|
||||
@@ -137,7 +137,7 @@ private fun <T : DropdownOption> DropdownTrailingContent(
|
||||
Icon(
|
||||
imageVector = CompoundIcons.ChevronDown(),
|
||||
contentDescription = null,
|
||||
tint = ElementTheme.colors.iconSecondary,
|
||||
tint = enabled.toIconSecondaryEnabledColor(),
|
||||
)
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
@@ -146,6 +146,7 @@ private fun <T : DropdownOption> DropdownTrailingContent(
|
||||
) {
|
||||
options.forEach { option ->
|
||||
DropdownMenuItem(
|
||||
enabled = enabled,
|
||||
text = {
|
||||
Text(
|
||||
text = option.getText(),
|
||||
@@ -206,5 +207,14 @@ internal fun PreferenceDropdownPreview() = ElementThemedPreview {
|
||||
options = options,
|
||||
onSelectOption = {},
|
||||
)
|
||||
PreferenceDropdown(
|
||||
title = "Dropdown",
|
||||
supportingText = "Options for dropdown",
|
||||
icon = CompoundIcons.Threads(),
|
||||
selectedOption = options.first(),
|
||||
options = options,
|
||||
onSelectOption = {},
|
||||
enabled = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ fun PreferenceSlide(
|
||||
leadingContent = preferenceIcon(
|
||||
icon = icon,
|
||||
iconResourceId = iconResourceId,
|
||||
enabled = enabled,
|
||||
showIconAreaIfNoIcon = showIconAreaIfNoIcon,
|
||||
),
|
||||
headlineContent = {
|
||||
|
||||
@@ -42,7 +42,6 @@ fun PreferenceSwitch(
|
||||
leadingContent = preferenceIcon(
|
||||
icon = icon,
|
||||
iconResourceId = iconResourceId,
|
||||
enabled = enabled,
|
||||
showIconAreaIfNoIcon = showIconAreaIfNoIcon,
|
||||
),
|
||||
headlineContent = {
|
||||
|
||||
@@ -34,11 +34,10 @@ fun preferenceIcon(
|
||||
@DrawableRes iconResourceId: Int? = null,
|
||||
showIconBadge: Boolean = false,
|
||||
tintColor: Color? = null,
|
||||
enabled: Boolean = true,
|
||||
showIconAreaIfNoIcon: Boolean = false,
|
||||
): ListItemContent.Custom? {
|
||||
return if (icon != null || iconResourceId != null || showIconAreaIfNoIcon) {
|
||||
ListItemContent.Custom {
|
||||
ListItemContent.Custom { enabled ->
|
||||
PreferenceIcon(
|
||||
icon = icon,
|
||||
iconResourceId = iconResourceId,
|
||||
|
||||
@@ -21,6 +21,20 @@ fun RoomInfo.getAvatarData(size: AvatarSize) = AvatarData(
|
||||
size = size,
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns the power level of the user in the room.
|
||||
* If the user is a creator and [RoomInfo.privilegedCreatorRole] is true, returns the power level of [RoomMember.Role.Owner].
|
||||
* Otherwise, checks the room's power levels for the user's power level.
|
||||
* If no specific power level is set for the user, defaults to 0.
|
||||
*/
|
||||
fun RoomInfo.powerLevelOf(userId: UserId): Long {
|
||||
return if (privilegedCreatorRole && creators.contains(userId)) {
|
||||
RoomMember.Role.Owner(isCreator = true).powerLevel
|
||||
} else {
|
||||
roomPowerLevels?.powerLevelOf(userId = userId) ?: 0L
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the role of the user in the room.
|
||||
* If the user is a creator and [RoomInfo.privilegedCreatorRole] is true, returns [RoomMember.Role.Owner].
|
||||
@@ -28,9 +42,6 @@ fun RoomInfo.getAvatarData(size: AvatarSize) = AvatarData(
|
||||
* If no specific power level is set for the user, defaults to [RoomMember.Role.User].
|
||||
*/
|
||||
fun RoomInfo.roleOf(userId: UserId): RoomMember.Role {
|
||||
return if (privilegedCreatorRole && creators.contains(userId)) {
|
||||
RoomMember.Role.Owner(isCreator = true)
|
||||
} else {
|
||||
roomPowerLevels?.roleOf(userId) ?: RoomMember.Role.User
|
||||
}
|
||||
val powerLevel = powerLevelOf(userId = userId)
|
||||
return RoomMember.Role.forPowerLevel(powerLevel)
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector 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.libraries.matrix.ui.room
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.produceState
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.ui.model.roleOf
|
||||
|
||||
@Composable
|
||||
fun BaseRoom.userPowerLevelAsState(updateKey: Long): State<Long> {
|
||||
return produceState(initialValue = 0, key1 = updateKey) {
|
||||
value = userRole(sessionId)
|
||||
.getOrDefault(RoomMember.Role.User)
|
||||
.powerLevel
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BaseRoom.isOwnUserAdmin(): Boolean {
|
||||
val roomInfo by roomInfoFlow.collectAsState()
|
||||
val role = roomInfo.roleOf(sessionId)
|
||||
return role == RoomMember.Role.Admin || role is RoomMember.Role.Owner
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user