Merge pull request #5950 from element-hq/feature/fga/iterate_permissions_screen
Changes : iterate again on permissions
This commit is contained in:
commit
76bc487f28
36 changed files with 243 additions and 151 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f82f648e9f5d67abfe2cf783df7e70568a74ff5dcae876e80b54995981a65886
|
||||
size 49650
|
||||
oid sha256:12c69b646ec09afdbc6e90873ee831adbd62ea7c45679c54620f74a9bd74941c
|
||||
size 48321
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0ebf86fc0ac80375edd1730caab4a669aa2cc545b2ea946981901fd360114320
|
||||
size 44555
|
||||
oid sha256:f82f648e9f5d67abfe2cf783df7e70568a74ff5dcae876e80b54995981a65886
|
||||
size 49650
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fae74539f53342110e50a077ad78b8422d08ba931b948b2b167d90f6cbe8ade6
|
||||
size 43535
|
||||
oid sha256:0ebf86fc0ac80375edd1730caab4a669aa2cc545b2ea946981901fd360114320
|
||||
size 44555
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:61bced1ea61f5952e83956b4c4447a0371fe99c874e2a20df431bc59e02dc838
|
||||
size 50316
|
||||
oid sha256:fae74539f53342110e50a077ad78b8422d08ba931b948b2b167d90f6cbe8ade6
|
||||
size 43535
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:73d9461a1d51964ba1a912376658e3e550e952821f7aa1d9bff765ed11deb920
|
||||
size 49123
|
||||
oid sha256:61bced1ea61f5952e83956b4c4447a0371fe99c874e2a20df431bc59e02dc838
|
||||
size 50316
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:73d9461a1d51964ba1a912376658e3e550e952821f7aa1d9bff765ed11deb920
|
||||
size 49123
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:192fce45ea8d83d8f7aa40f9337b2dd92c3b1194da611fed1c42fd771d74fe0a
|
||||
size 48534
|
||||
oid sha256:e72874066861ce76c76134dd84b9ab058ea3c7ca1c54176f8863c6bc1b8226fc
|
||||
size 47273
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b3ed16d04a1106b82183c4fe6ae741b598448050e2ddae2fa842b0b01d475e0a
|
||||
size 43230
|
||||
oid sha256:192fce45ea8d83d8f7aa40f9337b2dd92c3b1194da611fed1c42fd771d74fe0a
|
||||
size 48534
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0cb2f7d7cad98d111ed2c1c0b8120e5837f2586f845594e581b41b7cb96e00f2
|
||||
size 41555
|
||||
oid sha256:b3ed16d04a1106b82183c4fe6ae741b598448050e2ddae2fa842b0b01d475e0a
|
||||
size 43230
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0862da8d120e276afd6bdef6a9b247d3976ae70adab47077d8749600dd695884
|
||||
size 48280
|
||||
oid sha256:0cb2f7d7cad98d111ed2c1c0b8120e5837f2586f845594e581b41b7cb96e00f2
|
||||
size 41555
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:48e5f7460a13798d2f2963930e4e7a95c6a1ffa704d034ec21e33ba1bde6606c
|
||||
size 48123
|
||||
oid sha256:0862da8d120e276afd6bdef6a9b247d3976ae70adab47077d8749600dd695884
|
||||
size 48280
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:48e5f7460a13798d2f2963930e4e7a95c6a1ffa704d034ec21e33ba1bde6606c
|
||||
size 48123
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:00eb9c6756c353ac82144c391617a515d871940c32d0604d86203f8568e9e5e2
|
||||
size 32077
|
||||
oid sha256:ed643834f8fd2bc167dfd95113ab9098392f4d626ccdd58c9fae2cfd264f70c5
|
||||
size 41611
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue