Adapt 'change roles' screens to the new creator/owner role (#5076)

* Replace `RoomMember.Role.CREATOR` with `RoomMember.Role.Owner` - Make `RoomMember.Role` a sealed interface instead

* Adapt room member role mapping to include the power level to distinguish between admins and owners

* Use new `RoomMember.Role` sealed interface through the app

* Change how `MembersByRole` groups members to add owners to the admins section

* Adapt the `ChangeRoles` screen to the new roles:
    - Owners can't modify other owner's roles.
    - They can modify the roles of any other user, without confirmation.

* Adapt 'roles and permissions' screen:
    - Owners can't demote themselves.
    - The admin count also counts owners.

* Add more tests and screenshots

* Add owners to its own section in the 'change roles' screen

* Update screenshots

---------

Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
Jorge Martin Espinosa 2025-07-29 16:07:16 +02:00 committed by GitHub
parent 4534229e84
commit 51f67741ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
77 changed files with 663 additions and 301 deletions

View file

@ -85,7 +85,7 @@ data class RoomInfo(
* Returns the list of users with the given [role] in this room.
*/
fun usersWithRole(role: RoomMember.Role): List<UserId> {
return if (role == RoomMember.Role.CREATOR) {
return if (role is RoomMember.Role.Owner && role.isCreator) {
this.creators
} else {
this.roomPowerLevels?.usersWithRole(role).orEmpty().toList()

View file

@ -25,21 +25,34 @@ data class RoomMember(
/**
* Role of the RoomMember, based on its [powerLevel].
*/
enum class Role(val powerLevel: Long) {
CREATOR(Long.MAX_VALUE),
ADMIN(100L),
MODERATOR(50L),
USER(0L);
sealed interface Role {
data class Owner(val isCreator: Boolean) : Role
data object Admin : Role
data object Moderator : Role
data object User : Role
val powerLevel: Long
get() = when (this) {
is Owner -> if (isCreator) CREATOR_POWERLEVEL else SUPERADMIN_POWERLEVEL
Admin -> ADMIN_POWERLEVEL
Moderator -> MODERATOR_POWERLEVEL
User -> USER_POWERLEVEL
}
companion object {
const val SUPER_ADMIN_LEVEL = 150L
private const val CREATOR_POWERLEVEL = Long.MAX_VALUE
private const val SUPERADMIN_POWERLEVEL = 150L
private const val ADMIN_POWERLEVEL = 100L
private const val MODERATOR_POWERLEVEL = 50L
private const val USER_POWERLEVEL = 0L
fun forPowerLevel(powerLevel: Long): Role {
return when {
powerLevel > SUPER_ADMIN_LEVEL -> CREATOR
powerLevel >= ADMIN.powerLevel -> ADMIN
powerLevel >= MODERATOR.powerLevel -> MODERATOR
else -> USER
powerLevel == CREATOR_POWERLEVEL -> Owner(isCreator = true)
powerLevel >= SUPERADMIN_POWERLEVEL -> Owner(isCreator = false)
powerLevel >= ADMIN_POWERLEVEL -> Admin
powerLevel >= MODERATOR_POWERLEVEL -> Moderator
else -> User
}
}
}
@ -87,11 +100,3 @@ fun RoomMember.toMatrixUser() = MatrixUser(
displayName = displayName,
avatarUrl = avatarUrl,
)
/**
* Returns `true` if the [RoomMember] is an owner of the room.
* Owners are defined as members with either the [RoomMember.Role.CREATOR] role or a power level greater than or equal to [RoomMember.Role.SUPER_ADMIN_LEVEL].
*/
fun RoomMember.isOwner(): Boolean {
return role == RoomMember.Role.CREATOR || powerLevel >= RoomMember.Role.SUPER_ADMIN_LEVEL
}

View file

@ -25,14 +25,23 @@ data class RoomPowerLevels(
val values: RoomPowerLevelsValues,
private val users: ImmutableMap<UserId, Long>,
) {
/**
* Returns the power level of the user in the room.
*
* If the user is not found, returns 0.
*/
fun powerLevelOf(userId: UserId): Long {
return users[userId] ?: 0L
}
/**
* Returns the set of [UserId]s that have the given role in the room.
*
* **WARNING**: This method must not be used with the [RoomMember.Role.CREATOR] role. It'll result in a runtime error.
* **WARNING**: This method must not be used with a creator role. It'll result in a runtime error.
*/
fun usersWithRole(role: RoomMember.Role): Set<UserId> {
return if (role == RoomMember.Role.CREATOR) {
error("RoomPowerLevels.usersWithRole should not be used with CREATOR role, use roomInfo.creators instead")
return if (role is RoomMember.Role.Owner && role.isCreator) {
error("RoomPowerLevels.usersWithRole should not be used with a creator role, use roomInfo.creators instead")
} else {
users.filterValues { RoomMember.Role.forPowerLevel(it) == role }.keys
}
@ -42,7 +51,7 @@ data class RoomPowerLevels(
* Returns the role of the user in the room based on their power level.
* If the user is not found, returns null.
*
* **WARNING**: This method must not be used with the [RoomMember.Role.CREATOR] role, as it won't return any results.
* **WARNING**: This method must not be used with a creator role, as it won't return any results.
*/
fun roleOf(userId: UserId): RoomMember.Role? {
return users[userId]?.let(RoomMember.Role::forPowerLevel)