From fd3f7e3a240f287b6bd10fac033e88f268f3d57c Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 24 Jul 2025 22:25:10 +0200 Subject: [PATCH] change (power level) : support new InfinitePowerLevel (first draft) --- .../src/main/res/values/strings.xml | 2 + .../sdk/session/space/SpaceHierarchyTest.kt | 8 +-- .../SenderNotificationPermissionCondition.kt | 8 +-- .../sdk/api/session/room/RoomExtensions.kt | 18 ++++++ .../session/room/model/PowerLevelsContent.kt | 32 ++++++---- .../room/model/create/RoomCreateContent.kt | 26 +++++++- .../sdk/api/session/room/powerlevels/Role.kt | 30 ++++----- ...owerLevelsHelper.kt => RoomPowerLevels.kt} | 60 ++++++++++++------ .../room/powerlevels/UserPowerLevel.kt | 35 +++++++++++ .../session/permalinks/ViaParameterFinder.kt | 13 +--- .../pushers/DefaultConditionResolver.kt | 16 ++--- .../EventRelationsAggregationProcessor.kt | 16 ++--- .../poll/DefaultPollAggregationProcessor.kt | 6 +- .../poll/PollAggregationProcessor.kt | 4 +- .../room/powerlevels/RoomPowerLevels.kt | 23 +++++++ .../room/state/StateEventDataSource.kt | 1 + .../room/summary/RoomSummaryUpdater.kt | 21 +++++-- .../room/version/DefaultRoomVersionService.kt | 15 +++-- .../session/space/DefaultSpaceService.kt | 18 +++--- .../internal/session/widgets/WidgetManager.kt | 13 +--- .../DefaultPollAggregationProcessorTest.kt | 10 +-- ...faultCreateLocalRoomStateEventsTaskTest.kt | 4 +- .../home/room/detail/TimelineViewModel.kt | 9 ++- .../composer/MessageComposerViewModel.kt | 3 +- .../action/MessageActionsViewModel.kt | 11 ++-- .../factory/MergedHeaderItemFactory.kt | 15 +++-- .../timeline/format/NoticeEventFormatter.kt | 6 +- .../location/LocationSharingViewModel.kt | 7 +-- .../powerlevel/PowerLevelsFlowFactory.kt | 25 ++++++-- .../RoomMemberProfileAction.kt | 3 +- .../RoomMemberProfileController.kt | 11 ++-- .../RoomMemberProfileFragment.kt | 8 +-- .../RoomMemberProfileViewEvents.kt | 5 +- .../RoomMemberProfileViewModel.kt | 38 ++++++------ .../RoomMemberProfileViewState.kt | 4 +- .../powerlevel/EditPowerLevelDialogs.kt | 27 ++++---- .../roomprofile/RoomProfileViewModel.kt | 12 ++-- .../roomprofile/alias/RoomAliasViewModel.kt | 7 +-- .../banned/RoomBannedMemberListViewModel.kt | 12 ++-- .../members/RoomMemberListComparator.kt | 53 ++++++++++++++++ .../members/RoomMemberListController.kt | 37 ++++++----- .../members/RoomMemberListViewModel.kt | 62 +++++++++---------- .../members/RoomMemberListViewState.kt | 11 +++- .../members/RoomMemberSummaryComparator.kt | 52 ---------------- .../roomprofile/permissions/RoleFormatter.kt | 5 +- .../permissions/RoomPermissionsAction.kt | 3 +- .../permissions/RoomPermissionsController.kt | 26 +++----- .../permissions/RoomPermissionsFragment.kt | 6 +- .../permissions/RoomPermissionsViewModel.kt | 42 +++++++------ .../settings/RoomSettingsViewModel.kt | 23 +++---- .../app/features/spaces/SpaceMenuViewModel.kt | 20 +++--- .../spaces/create/CreateSpaceViewModelTask.kt | 5 +- .../spaces/explore/SpaceDirectoryViewModel.kt | 13 ++-- .../leave/SpaceLeaveAdvancedViewModel.kt | 15 +++-- .../people/SpacePeopleListController.kt | 12 ++-- .../spaces/share/ShareSpaceViewModel.kt | 7 +-- .../usecase/StartVoiceBroadcastUseCase.kt | 11 ++-- .../features/widgets/WidgetPostAPIHandler.kt | 12 ++-- .../app/features/widgets/WidgetViewModel.kt | 13 ++-- .../app/features/MemberListViewModelTest.kt | 4 +- 60 files changed, 566 insertions(+), 448 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/{PowerLevelsHelper.kt => RoomPowerLevels.kt} (59%) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/UserPowerLevel.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListComparator.kt delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryComparator.kt diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 803510ed76..02fef53306 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -120,6 +120,7 @@ %1$s modified %2$s widget You modified %1$s widget + Owner Admin Moderator Default @@ -2383,6 +2384,7 @@ Invites Users + Owner in %1$s Admin in %1$s Moderator in %1$s Default in %1$s diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt index de661275a7..6c9d0ad983 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt @@ -40,7 +40,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest @@ -500,12 +500,12 @@ class SpaceHierarchyTest : InstrumentedTest { room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!) commonTestHelper.retryPeriodically { - val powerLevelsHelper = aliceSession.getRoom(bobRoomId)!! + val roomPowerLevels = aliceSession.getRoom(bobRoomId)!! .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) ?.content ?.toModel() - ?.let { PowerLevelsHelper(it) } - powerLevelsHelper!!.isUserAllowedToSend(aliceSession.myUserId, true, EventType.STATE_SPACE_PARENT) + ?.let { RoomPowerLevels(it) } + roomPowerLevels!!.isUserAllowedToSend(aliceSession.myUserId, true, EventType.STATE_SPACE_PARENT) } aliceSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: "")) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt index 82f5023c2f..c3e9b9f527 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt @@ -16,8 +16,7 @@ package org.matrix.android.sdk.api.session.pushrules import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels class SenderNotificationPermissionCondition( /** @@ -35,8 +34,7 @@ class SenderNotificationPermissionCondition( override fun technicalDescription() = "User power level <$key>" - fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean { - val powerLevelsHelper = PowerLevelsHelper(powerLevels) - return event.senderId != null && powerLevelsHelper.getUserPowerLevelValue(event.senderId) >= powerLevels.notificationLevel(key) + fun isSatisfied(event: Event, roomPowerLevels: RoomPowerLevels): Boolean { + return event.senderId != null && roomPowerLevels.isUserAbleToTriggerNotification(event.senderId, key) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt index b30c60554f..29638857bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt @@ -17,7 +17,13 @@ package org.matrix.android.sdk.api.session.room import org.matrix.android.sdk.api.query.QueryStateEventValue +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent /** @@ -34,3 +40,15 @@ fun Room.getTimelineEvent(eventId: String): TimelineEvent? = */ fun Room.getStateEvent(eventType: String, stateKey: QueryStateEventValue): Event? = stateService().getStateEvent(eventType, stateKey) + +/** + * Get the current RoomPowerLevels of the room. + */ +fun Room.getRoomPowerLevels(): RoomPowerLevels { + val powerLevelsContent = getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)?.content?.toModel() + val roomCreateContent = getStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty)?.getRoomCreateContentWithSender() + return RoomPowerLevels( + powerLevelsContent, + roomCreateContent + ) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt index 0329828130..894640a488 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt @@ -18,7 +18,8 @@ package org.matrix.android.sdk.api.session.room.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent.Companion.NOTIFICATIONS_ROOM_KEY +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel /** * Class representing the EventType.EVENT_TYPE_STATE_ROOM_POWER_LEVELS state event content. @@ -88,7 +89,7 @@ data class PowerLevelsContent( * Get the notification level for a dedicated key. * * @param key the notification key - * @return the level, default to Moderator if the key is not found + * @return the level */ fun notificationLevel(key: String): Int { return when (val value = notifications.orEmpty()[key]) { @@ -96,10 +97,9 @@ data class PowerLevelsContent( is String -> value.toInt() is Double -> value.toInt() is Int -> value - else -> Role.Moderator.value + else -> defaultNotificationLevel(key) } } - companion object { /** * Key to use for content.notifications and get the level required to trigger an @room notification. Defaults to 50 if unspecified. @@ -108,11 +108,21 @@ data class PowerLevelsContent( } } +private fun defaultNotificationLevel(key: String): Int { + return when (key) { + NOTIFICATIONS_ROOM_KEY -> UserPowerLevel.Moderator.value + else -> UserPowerLevel.User.value + } +} + + // Fallback to default value, defined in the Matrix specification -fun PowerLevelsContent.banOrDefault() = ban ?: Role.Moderator.value -fun PowerLevelsContent.kickOrDefault() = kick ?: Role.Moderator.value -fun PowerLevelsContent.inviteOrDefault() = invite ?: Role.Moderator.value -fun PowerLevelsContent.redactOrDefault() = redact ?: Role.Moderator.value -fun PowerLevelsContent.eventsDefaultOrDefault() = eventsDefault ?: Role.Default.value -fun PowerLevelsContent.usersDefaultOrDefault() = usersDefault ?: Role.Default.value -fun PowerLevelsContent.stateDefaultOrDefault() = stateDefault ?: Role.Moderator.value +fun PowerLevelsContent?.banOrDefault() = this?.ban ?: UserPowerLevel.Moderator.value +fun PowerLevelsContent?.kickOrDefault() = this?.kick ?: UserPowerLevel.Moderator.value +fun PowerLevelsContent?.inviteOrDefault() = this?.invite ?: UserPowerLevel.User.value +fun PowerLevelsContent?.redactOrDefault() = this?.redact ?: UserPowerLevel.Moderator.value +fun PowerLevelsContent?.eventsDefaultOrDefault() = this?.eventsDefault ?: UserPowerLevel.User.value +fun PowerLevelsContent?.usersDefaultOrDefault() = this?.usersDefault ?: UserPowerLevel.User.value +fun PowerLevelsContent?.stateDefaultOrDefault() = this?.stateDefault ?: UserPowerLevel.Moderator.value + +fun PowerLevelsContent?.notificationLevelOrDefault(key: String) = this?.notificationLevel(key) ?: defaultNotificationLevel(key) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt index d73c941a86..2408f4a004 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt @@ -18,15 +18,39 @@ package org.matrix.android.sdk.api.session.room.model.create import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel /** * Content of a m.room.create type event. */ @JsonClass(generateAdapter = true) data class RoomCreateContent( + // Creator should be replaced by the sender of the event @Json(name = "creator") val creator: String? = null, @Json(name = "room_version") val roomVersion: String? = null, @Json(name = "predecessor") val predecessor: Predecessor? = null, // Defines the room type, see #RoomType (user extensible) - @Json(name = "type") val type: String? = null + @Json(name = "type") val type: String? = null, + @Json(name = "additional_creators") val additionalCreators: List? = null, ) + +data class RoomCreateContentWithSender( + val senderId: String, + val inner: RoomCreateContent +) { + val creators = setOf(senderId) + inner.additionalCreators.orEmpty().toSet() +} + +fun Event.getRoomCreateContentWithSender(): RoomCreateContentWithSender? { + if (this.type != EventType.STATE_ROOM_CREATE) return null + val innerContent = getClearContent().toModel() ?: return null + val senderId = senderId ?: return null + return RoomCreateContentWithSender(senderId, innerContent) +} + +fun RoomCreateContent.explicitlyPrivilegeRoomCreators(): Boolean { + val supportedRoomVersions = listOf("org.matrix.hydra.11", "12") + return supportedRoomVersions.contains(roomVersion) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/Role.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/Role.kt index c5cc573458..57cd75e70d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/Role.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/Role.kt @@ -17,26 +17,22 @@ package org.matrix.android.sdk.api.session.room.powerlevels -sealed class Role(open val value: Int) : Comparable { - object Admin : Role(100) - object Moderator : Role(50) - object Default : Role(0) - data class Custom(override val value: Int) : Role(value) +enum class Role { + Creator, + SuperAdmin, + Admin, + Moderator, + User; - override fun compareTo(other: Role): Int { - return value.compareTo(other.value) - } companion object { - - // Order matters, default value should be checked after defined roles - fun fromValue(value: Int, default: Int): Role { - return when (value) { - Admin.value -> Admin - Moderator.value -> Moderator - Default.value, - default -> Default - else -> Custom(value) + fun getSuggestedRole(userPowerLevel: UserPowerLevel): Role { + return when { + userPowerLevel == UserPowerLevel.Infinite -> Creator + userPowerLevel >= UserPowerLevel.SuperAdmin -> SuperAdmin + userPowerLevel >= UserPowerLevel.Admin -> Admin + userPowerLevel >= UserPowerLevel.Moderator -> Moderator + else -> User } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/RoomPowerLevels.kt similarity index 59% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/RoomPowerLevels.kt index 36993074aa..d7fbeb8769 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/RoomPowerLevels.kt @@ -19,17 +19,23 @@ package org.matrix.android.sdk.api.session.room.powerlevels import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.banOrDefault +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContentWithSender +import org.matrix.android.sdk.api.session.room.model.create.explicitlyPrivilegeRoomCreators import org.matrix.android.sdk.api.session.room.model.eventsDefaultOrDefault import org.matrix.android.sdk.api.session.room.model.inviteOrDefault import org.matrix.android.sdk.api.session.room.model.kickOrDefault +import org.matrix.android.sdk.api.session.room.model.notificationLevelOrDefault import org.matrix.android.sdk.api.session.room.model.redactOrDefault import org.matrix.android.sdk.api.session.room.model.stateDefaultOrDefault import org.matrix.android.sdk.api.session.room.model.usersDefaultOrDefault /** - * This class is an helper around PowerLevelsContent. + * This class is an helper around PowerLevelsContent and RoomCreateContent. */ -class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { +class RoomPowerLevels( + val powerLevelsContent: PowerLevelsContent?, + private val roomCreateContent: RoomCreateContentWithSender?, +) { /** * Returns the user power level of a dedicated user Id. @@ -37,10 +43,14 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { * @param userId the user id * @return the power level */ - fun getUserPowerLevelValue(userId: String): Int { - return powerLevelsContent.users + fun getUserPowerLevel(userId: String): UserPowerLevel { + if (shouldGiveInfinitePowerLevel(userId)) return UserPowerLevel.Infinite + if (powerLevelsContent == null) return UserPowerLevel.User + val value = powerLevelsContent.users ?.get(userId) ?: powerLevelsContent.usersDefaultOrDefault() + + return UserPowerLevel.Value(value) } /** @@ -50,9 +60,8 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { * @return the power level */ fun getUserRole(userId: String): Role { - val value = getUserPowerLevelValue(userId) - // I think we should use powerLevelsContent.usersDefault, but Ganfra told me that it was like that on riot-Web - return Role.fromValue(value, powerLevelsContent.eventsDefaultOrDefault()) + val value = getUserPowerLevel(userId) + return Role.getSuggestedRole(value) } /** @@ -65,14 +74,14 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { */ fun isUserAllowedToSend(userId: String, isState: Boolean, eventType: String?): Boolean { return if (userId.isNotEmpty()) { - val powerLevel = getUserPowerLevelValue(userId) - val minimumPowerLevel = powerLevelsContent.events?.get(eventType) + val powerLevel = getUserPowerLevel(userId) + val minimumPowerLevel = powerLevelsContent?.events?.get(eventType) ?: if (isState) { powerLevelsContent.stateDefaultOrDefault() } else { powerLevelsContent.eventsDefaultOrDefault() } - powerLevel >= minimumPowerLevel + powerLevel >= UserPowerLevel.Value(minimumPowerLevel) } else false } @@ -82,8 +91,8 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { * @return true if able to invite */ fun isUserAbleToInvite(userId: String): Boolean { - val powerLevel = getUserPowerLevelValue(userId) - return powerLevel >= powerLevelsContent.inviteOrDefault() + val powerLevel = getUserPowerLevel(userId) + return powerLevel >= UserPowerLevel.Value(powerLevelsContent.inviteOrDefault()) } /** @@ -92,8 +101,8 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { * @return true if able to ban */ fun isUserAbleToBan(userId: String): Boolean { - val powerLevel = getUserPowerLevelValue(userId) - return powerLevel >= powerLevelsContent.banOrDefault() + val powerLevel = getUserPowerLevel(userId) + return powerLevel >= UserPowerLevel.Value(powerLevelsContent.banOrDefault()) } /** @@ -102,8 +111,8 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { * @return true if able to kick */ fun isUserAbleToKick(userId: String): Boolean { - val powerLevel = getUserPowerLevelValue(userId) - return powerLevel >= powerLevelsContent.kickOrDefault() + val powerLevel = getUserPowerLevel(userId) + return powerLevel >= UserPowerLevel.Value(powerLevelsContent.kickOrDefault()) } /** @@ -112,7 +121,22 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { * @return true if able to redact */ fun isUserAbleToRedact(userId: String): Boolean { - val powerLevel = getUserPowerLevelValue(userId) - return powerLevel >= powerLevelsContent.redactOrDefault() + val powerLevel = getUserPowerLevel(userId) + return powerLevel >= UserPowerLevel.Value(powerLevelsContent.redactOrDefault()) + } + + fun isUserAbleToTriggerNotification(userId: String, notificationKey: String): Boolean { + val userPowerLevel = getUserPowerLevel(userId) + val notificationPowerLevel = UserPowerLevel.Value(powerLevelsContent.notificationLevelOrDefault(key = notificationKey)) + return userPowerLevel >= notificationPowerLevel + } + + private fun shouldGiveInfinitePowerLevel(userId: String): Boolean { + if (roomCreateContent == null) return false + return if (roomCreateContent.inner.explicitlyPrivilegeRoomCreators()) { + roomCreateContent.creators.contains(userId) + } else { + false + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/UserPowerLevel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/UserPowerLevel.kt new file mode 100644 index 0000000000..8932f16685 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/UserPowerLevel.kt @@ -0,0 +1,35 @@ +/* + * Copyright 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 org.matrix.android.sdk.api.session.room.powerlevels + +sealed interface UserPowerLevel : Comparable { + data object Infinite : UserPowerLevel + + @JvmInline + value class Value(val value: Int) : UserPowerLevel + + override fun compareTo(other: UserPowerLevel): Int { + return when (this) { + Infinite -> when (other) { + Infinite -> 0 + is Value -> 1 + } + is Value -> when (other) { + Infinite -> -1 + is Value -> value.compareTo(other.value) + } + } + } + + companion object { + val User = Value(0) + val Moderator = Value(50) + val Admin = Value(100) + val SuperAdmin = Value(150) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt index 5fb20bb259..7f1277864c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt @@ -17,15 +17,11 @@ package org.matrix.android.sdk.internal.session.permalinks import org.matrix.android.sdk.api.MatrixPatterns.getServerName -import org.matrix.android.sdk.api.query.QueryStringValue -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.RoomGetter +import org.matrix.android.sdk.internal.session.room.powerlevels.getRoomPowerLevels import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import java.net.URLEncoder import javax.inject.Inject @@ -101,10 +97,7 @@ internal class ViaParameterFinder @Inject constructor( } fun userCanInvite(userId: String, roomId: String): Boolean { - val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - ?.content?.toModel() - ?.let { PowerLevelsHelper(it) } - - return powerLevelsHelper?.isUserAbleToInvite(userId) ?: false + val roomPowerLevels = stateEventDataSource.getRoomPowerLevels(roomId) + return roomPowerLevels.isUserAbleToInvite(userId) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt index c2310f4fda..c1fb93b3ff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt @@ -24,15 +24,20 @@ import org.matrix.android.sdk.api.session.pushrules.ContainsDisplayNameCondition import org.matrix.android.sdk.api.session.pushrules.EventMatchCondition import org.matrix.android.sdk.api.session.pushrules.RoomMemberCountCondition import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition +import org.matrix.android.sdk.api.session.room.getRoomPowerLevels import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.RoomGetter +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import javax.inject.Inject internal class DefaultConditionResolver @Inject constructor( private val roomGetter: RoomGetter, - @UserId private val userId: String + @UserId private val userId: String, + private val stateEventDataSource: StateEventDataSource, ) : ConditionResolver { override fun resolveEventMatchCondition( @@ -55,13 +60,8 @@ internal class DefaultConditionResolver @Inject constructor( ): Boolean { val roomId = event.roomId ?: return false val room = roomGetter.getRoom(roomId) ?: return false - - val powerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - ?.content - ?.toModel() - ?: PowerLevelsContent() - - return condition.isSatisfied(event, powerLevelsContent) + val roomPowerLevels = room.getRoomPowerLevels() + return condition.isSatisfied(event, roomPowerLevels) } override fun resolveContainsDisplayNameCondition( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index edc10bd187..3b963c13cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.session.room import io.realm.Realm -import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.crypto.verification.VerificationState import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation import org.matrix.android.sdk.api.session.events.model.Event @@ -27,7 +26,6 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent @@ -36,7 +34,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.verification.toState import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent @@ -62,6 +60,7 @@ import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor +import org.matrix.android.sdk.internal.session.room.powerlevels.getRoomPowerLevels import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber @@ -216,9 +215,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } in EventType.POLL_END.values -> { sessionManager.getSessionComponent(sessionId)?.session()?.let { session -> - getPowerLevelsHelper(event.roomId)?.let { - pollAggregationProcessor.handlePollEndEvent(session, it, realm, event) - } + val roomPowerLevels = stateEventDataSource.getRoomPowerLevels(event.roomId) + pollAggregationProcessor.handlePollEndEvent(session, roomPowerLevels, realm, event) } } in EventType.STATE_ROOM_BEACON_INFO.values -> { @@ -381,12 +379,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } - private fun getPowerLevelsHelper(roomId: String): PowerLevelsHelper? { - return stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - ?.content?.toModel() - ?.let { PowerLevelsHelper(it) } - } - private fun handleInitialAggregatedRelations( realm: Realm, event: Event, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt index ca224cd543..5a6648934e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt @@ -32,7 +32,7 @@ import org.matrix.android.sdk.api.session.room.model.VoteInfo import org.matrix.android.sdk.api.session.room.model.VoteSummary import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.internal.database.mapper.ContentMapper @@ -160,13 +160,13 @@ internal class DefaultPollAggregationProcessor @Inject constructor( return true } - override fun handlePollEndEvent(session: Session, powerLevelsHelper: PowerLevelsHelper, realm: Realm, event: Event): Boolean { + override fun handlePollEndEvent(session: Session, roomPowerLevels: RoomPowerLevels, realm: Realm, event: Event): Boolean { val roomId = event.roomId ?: return false val pollEventId = event.getRelationContent()?.eventId ?: return false val pollOwnerId = getPollEvent(session, roomId, pollEventId)?.root?.senderId val isPollOwner = pollOwnerId == event.senderId - if (!isPollOwner && !powerLevelsHelper.isUserAbleToRedact(event.senderId ?: "")) { + if (!isPollOwner && !roomPowerLevels.isUserAbleToRedact(event.senderId ?: "")) { return false } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt index 33a69b720a..578ba6064e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.session.room.aggregation.poll import io.realm.Realm import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels internal interface PollAggregationProcessor { /** @@ -48,7 +48,7 @@ internal interface PollAggregationProcessor { */ fun handlePollEndEvent( session: Session, - powerLevelsHelper: PowerLevelsHelper, + roomPowerLevels: RoomPowerLevels, realm: Realm, event: Event ): Boolean diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt new file mode 100644 index 0000000000..d3556324e7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt @@ -0,0 +1,23 @@ +/* + * Copyright 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 org.matrix.android.sdk.internal.session.room.powerlevels + +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource + +internal fun StateEventDataSource.getRoomPowerLevels(roomId: String): RoomPowerLevels { + val powerLevelsContent = getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) + ?.content?.toModel() + val roomCreateContent = getStateEvent(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty)?.getRoomCreateContentWithSender() + return RoomPowerLevels(powerLevelsContent, roomCreateContent) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt index 723d604ad4..433a4399a3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt @@ -96,3 +96,4 @@ internal class StateEventDataSource @Inject constructor( } } } + diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index cbb75398c4..8820af2034 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -34,7 +34,8 @@ import org.matrix.android.sdk.api.session.room.model.RoomTopicContent import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.VersioningState import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContentWithSender +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary import org.matrix.android.sdk.api.session.sync.model.RoomSyncUnreadNotifications @@ -313,13 +314,25 @@ internal class RoomSummaryUpdater @Inject constructor( // check if sender can post child relation in parent? val senderId = parentInfo.stateEventSender val parentRoomId = parentInfo.roomId - val powerLevelsHelper = CurrentStateEventEntity + val powerLevelsContent = CurrentStateEventEntity .getOrNull(realm, parentRoomId, "", EventType.STATE_ROOM_POWER_LEVELS) ?.root ?.let { ContentMapper.map(it.content).toModel() } - ?.let { PowerLevelsHelper(it) } - isValidRelation = powerLevelsHelper?.isUserAllowedToSend(senderId, true, EventType.STATE_SPACE_CHILD) ?: false + val roomCreateContent = CurrentStateEventEntity + .getOrNull(realm, parentRoomId, "", EventType.STATE_ROOM_CREATE) + ?.root + ?.let { + val content = ContentMapper.map(it.content).toModel() + val sender = it.sender + if (content != null && sender != null) { + RoomCreateContentWithSender(sender, content) + } else { + null + } + } + val roomPowerLevels = RoomPowerLevels(powerLevelsContent, roomCreateContent) + isValidRelation = roomPowerLevels.isUserAllowedToSend(senderId, true, EventType.STATE_SPACE_CHILD) } if (isValidRelation) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt index 0bde3a11d2..fd99bb7158 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt @@ -25,7 +25,8 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.version.RoomVersionService import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource @@ -71,11 +72,17 @@ internal class DefaultRoomVersionService @AssistedInject constructor( } override fun userMayUpgradeRoom(userId: String): Boolean { - val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) + val powerLevelsContent = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) ?.content?.toModel() - ?.let { PowerLevelsHelper(it) } - return powerLevelsHelper?.isUserAllowedToSend(userId, true, EventType.STATE_ROOM_TOMBSTONE) ?: false + val roomCreateContent = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty) + ?.getRoomCreateContentWithSender() + + val roomPowerLevels = RoomPowerLevels( + powerLevelsContent = powerLevelsContent, + roomCreateContent = roomCreateContent + ) + return roomPowerLevels.isUserAllowedToSend(userId, true, EventType.STATE_ROOM_TOMBSTONE) } companion object { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index cd13b03017..80ecc4fa81 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -35,8 +35,9 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import org.matrix.android.sdk.api.session.space.CreateSpaceParams import org.matrix.android.sdk.api.session.space.JoinSpaceResult import org.matrix.android.sdk.api.session.space.Space @@ -47,11 +48,13 @@ import org.matrix.android.sdk.api.session.space.model.SpaceChildContent import org.matrix.android.sdk.api.session.space.model.SpaceChildSummaryEvent import org.matrix.android.sdk.api.session.space.model.SpaceParentContent import org.matrix.android.sdk.api.session.space.peeking.SpacePeekResult +import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.RoomGetter import org.matrix.android.sdk.internal.session.room.SpaceGetter import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoomTask +import org.matrix.android.sdk.internal.session.room.powerlevels.getRoomPowerLevels import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask @@ -83,7 +86,7 @@ internal class DefaultSpaceService @Inject constructor( if (isPublic) { this.roomAliasName = roomAliasLocalPart this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy( - invite = if (isPublic) Role.Default.value else Role.Moderator.value + invite = UserPowerLevel.User.value ) this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT this.historyVisibility = RoomHistoryVisibility.WORLD_READABLE @@ -253,15 +256,8 @@ internal class DefaultSpaceService @Inject constructor( if (roomSummaryDataSource.getRoomSummary(parentSpaceId)?.membership != Membership.JOIN) { throw UnsupportedOperationException("Cannot add canonical child if not member of parent") } - val powerLevelsEvent = stateEventDataSource.getStateEvent( - roomId = parentSpaceId, - eventType = EventType.STATE_ROOM_POWER_LEVELS, - stateKey = QueryStringValue.IsEmpty - ) - val powerLevelsContent = powerLevelsEvent?.content?.toModel() - ?: throw UnsupportedOperationException("Cannot add canonical child, missing power level") - val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) - if (!powerLevelsHelper.isUserAllowedToSend(userId, true, EventType.STATE_SPACE_CHILD)) { + val roomPowerLevels = stateEventDataSource.getRoomPowerLevels(parentSpaceId) + if (!roomPowerLevels.isUserAllowedToSend(userId, true, EventType.STATE_SPACE_CHILD)) { throw UnsupportedOperationException("Cannot add canonical child, not enough power level") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt index 7359fdbd91..8f197706ff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt @@ -30,15 +30,13 @@ import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager +import org.matrix.android.sdk.internal.session.room.powerlevels.getRoomPowerLevels import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory @@ -200,12 +198,7 @@ internal class WidgetManager @Inject constructor( } fun hasPermissionsToHandleWidgets(roomId: String): Boolean { - val powerLevelsEvent = stateEventDataSource.getStateEvent( - roomId = roomId, - eventType = EventType.STATE_ROOM_POWER_LEVELS, - stateKey = QueryStringValue.IsEmpty - ) - val powerLevelsContent = powerLevelsEvent?.content?.toModel() ?: return false - return PowerLevelsHelper(powerLevelsContent).isUserAllowedToSend(userId, true, EventType.STATE_ROOM_WIDGET_LEGACY) + val roomPowerLevels = stateEventDataSource.getRoomPowerLevels(roomId) + return roomPowerLevels.isUserAllowedToSend(userId, true, EventType.STATE_ROOM_WIDGET_LEGACY) } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt index 248c4b322d..bdfffa9d51 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt @@ -33,7 +33,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.getTimelineEvent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity @@ -255,9 +255,9 @@ class DefaultPollAggregationProcessorTest { every { room.getTimelineEvent(eventId) } returns if (hasExistingTimelineEvent) A_TIMELINE_EVENT else null } - private fun mockRedactionPowerLevels(userId: String, isAbleToRedact: Boolean): PowerLevelsHelper { - val powerLevelsHelper = mockk() - every { powerLevelsHelper.isUserAbleToRedact(userId) } returns isAbleToRedact - return powerLevelsHelper + private fun mockRedactionPowerLevels(userId: String, isAbleToRedact: Boolean): RoomPowerLevels { + val roomPowerLevels = mockk() + every { roomPowerLevels.isUserAbleToRedact(userId) } returns isAbleToRedact + return roomPowerLevels } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt index 1c2cf293b6..0669776394 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt @@ -376,8 +376,8 @@ internal class DefaultCreateLocalRoomStateEventsTaskTest { powerLevelsContent.kick shouldBeEqualTo Role.Moderator.value powerLevelsContent.invite shouldBeEqualTo Role.Moderator.value powerLevelsContent.redact shouldBeEqualTo Role.Moderator.value - powerLevelsContent.eventsDefault shouldBeEqualTo Role.Default.value - powerLevelsContent.usersDefault shouldBeEqualTo Role.Default.value + powerLevelsContent.eventsDefault shouldBeEqualTo Role.User.value + powerLevelsContent.usersDefault shouldBeEqualTo Role.User.value powerLevelsContent.stateDefault shouldBeEqualTo Role.Moderator.value // Guest access result.find { it.type == EventType.STATE_ROOM_GUEST_ACCESS } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 618b31796f..72b72990e8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -110,7 +110,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachme import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.read.ReadService import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -304,11 +303,11 @@ class TimelineViewModel @AssistedInject constructor( private fun observePowerLevel() { if (room == null) return PowerLevelsFlowFactory(room).createFlow() - .onEach { - val canInvite = PowerLevelsHelper(it).isUserAbleToInvite(session.myUserId) + .onEach { powerLevels -> + val canInvite = powerLevels.isUserAbleToInvite(session.myUserId) val isAllowedToManageWidgets = session.widgetService().hasPermissionsToHandleWidgets(room.roomId) - val isAllowedToStartWebRTCCall = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.CALL_INVITE) - val isAllowedToSetupEncryption = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) + val isAllowedToStartWebRTCCall = powerLevels.isUserAllowedToSend(session.myUserId, false, EventType.CALL_INVITE) + val isAllowedToSetupEncryption = powerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) setState { copy( canInvite = canInvite, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index 928adb63f4..00bc1890e7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -69,7 +69,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.relation.shouldRenderInThread -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.session.room.timeline.getRelationContent import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent @@ -183,7 +182,7 @@ class MessageComposerViewModel @AssistedInject constructor( PowerLevelsFlowFactory(room).createFlow(), room.flow().liveRoomSummary().unwrap() ) { pl, sum -> - val canSendMessage = PowerLevelsHelper(pl).isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE) + val canSendMessage = pl.isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE) if (canSendMessage) { val isE2E = sum.isEncrypted if (isE2E) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 1028a0548f..8a4e3bb797 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -49,7 +49,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited @@ -117,11 +117,10 @@ class MessageActionsViewModel @AssistedInject constructor( return } PowerLevelsFlowFactory(room).createFlow() - .onEach { - val powerLevelsHelper = PowerLevelsHelper(it) - val canReact = powerLevelsHelper.isUserAllowedToSend(session.myUserId, false, EventType.REACTION) - val canRedact = powerLevelsHelper.isUserAbleToRedact(session.myUserId) - val canSendMessage = powerLevelsHelper.isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE) + .onEach { roomPowerLevels -> + val canReact = roomPowerLevels.isUserAllowedToSend(session.myUserId, false, EventType.REACTION) + val canRedact = roomPowerLevels.isUserAbleToRedact(session.myUserId) + val canSendMessage = roomPowerLevels.isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE) val permissions = ActionPermissions(canSendMessage = canSendMessage, canRedact = canRedact, canReact = canReact) setState { copy(actionPermissions = permissions) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index 45ba700d1f..c36cd14641 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -30,10 +30,11 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getRoomPowerLevels import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject @@ -303,9 +304,7 @@ class MergedHeaderItemFactory @Inject constructor( collapsedEventIds.removeAll(mergedEventIds) } val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() } - val powerLevelsHelper = activeSessionHolder.getSafeActiveSession()?.getRoom(event.roomId) - ?.let { it.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)?.content?.toModel() } - ?.let { PowerLevelsHelper(it) } + val roomPowerLevels = activeSessionHolder.getSafeActiveSession()?.getRoom(event.roomId)?.getRoomPowerLevels() val currentUserId = activeSessionHolder.getSafeActiveSession()?.myUserId ?: "" val attributes = MergedRoomCreationItem.Attributes( isCollapsed = isCollapsed, @@ -320,10 +319,10 @@ class MergedHeaderItemFactory @Inject constructor( callback = callback, currentUserId = currentUserId, roomSummary = partialState.roomSummary, - canInvite = powerLevelsHelper?.isUserAbleToInvite(currentUserId) ?: false, - canChangeAvatar = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_AVATAR) ?: false, - canChangeTopic = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_TOPIC) ?: false, - canChangeName = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_NAME) ?: false + canInvite = roomPowerLevels?.isUserAbleToInvite(currentUserId) ?: false, + canChangeAvatar = roomPowerLevels?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_AVATAR) ?: false, + canChangeTopic = roomPowerLevels?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_TOPIC) ?: false, + canChangeName = roomPowerLevels?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_NAME) ?: false ) MergedRoomCreationItem_() .id(mergeId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index bd1c903f1f..5a8d3fc41a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -41,7 +41,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent import org.matrix.android.sdk.api.session.room.model.RoomTopicContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.widgets.model.WidgetContent import timber.log.Timber @@ -122,8 +122,8 @@ class NoticeEventFormatter @Inject constructor( userIds.addAll(previousPowerLevelsContent.users.orEmpty().keys) val diffs = ArrayList() userIds.forEach { userId -> - val from = PowerLevelsHelper(previousPowerLevelsContent).getUserRole(userId) - val to = PowerLevelsHelper(powerLevelsContent).getUserRole(userId) + val from = RoomPowerLevels(previousPowerLevelsContent,null).getUserRole(userId) + val to = RoomPowerLevels(powerLevelsContent, null).getUserRole(userId) if (from != to) { val fromStr = roleFormatter.format(from) val toStr = roleFormatter.format(to) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 82820e90cb..dfa1ec4549 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -34,7 +34,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getUserOrDefault -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.util.toMatrixItem import timber.log.Timber @@ -75,11 +75,10 @@ class LocationSharingViewModel @AssistedInject constructor( private fun observePowerLevelsForLiveLocationSharing() { PowerLevelsFlowFactory(room).createFlow() .distinctUntilChanged() - .setOnEach { - val powerLevelsHelper = PowerLevelsHelper(it) + .setOnEach { roomPowerLevels -> val canShareLiveLocation = EventType.STATE_ROOM_BEACON_INFO.values .all { beaconInfoType -> - powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, beaconInfoType) + roomPowerLevels.isUserAllowedToSend(session.myUserId, true, beaconInfoType) } copy(canShareLiveLocation = canShareLiveLocation) diff --git a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt index f065334ae7..689add249c 100644 --- a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt +++ b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt @@ -9,23 +9,40 @@ package im.vector.app.features.powerlevel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.mapOptional -import org.matrix.android.sdk.flow.unwrap class PowerLevelsFlowFactory(private val room: Room) { - fun createFlow(): Flow { - return room.flow() + fun createFlow(): Flow { + val flowRoom = room.flow() + val powerLevelsFlow = flowRoom .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) .mapOptional { it.content.toModel() } .flowOn(Dispatchers.Default) - .unwrap() + + val roomCreateFlow = flowRoom + .liveStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty) + .mapOptional { event -> + event.getRoomCreateContentWithSender() + } + .flowOn(Dispatchers.Default) + + return combine(powerLevelsFlow, roomCreateFlow) { powerLevelsContent, roomCreateContent -> + RoomPowerLevels( + powerLevelsContent = powerLevelsContent.getOrNull(), + roomCreateContent = roomCreateContent.getOrNull() + ) + } } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileAction.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileAction.kt index 8e764470c7..ade308339e 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileAction.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileAction.kt @@ -8,6 +8,7 @@ package im.vector.app.features.roommemberprofile import im.vector.app.core.platform.VectorViewModelAction +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel sealed class RoomMemberProfileAction : VectorViewModelAction { object RetryFetchingInfo : RoomMemberProfileAction() @@ -18,7 +19,7 @@ sealed class RoomMemberProfileAction : VectorViewModelAction { object InviteUser : RoomMemberProfileAction() object VerifyUser : RoomMemberProfileAction() object ShareRoomMemberProfile : RoomMemberProfileAction() - data class SetPowerLevel(val previousValue: Int, val newValue: Int, val askForValidation: Boolean) : RoomMemberProfileAction() + data class SetPowerLevel(val previousValue: UserPowerLevel, val newValue: UserPowerLevel.Value, val askForValidation: Boolean) : RoomMemberProfileAction() data class SetUserColorOverride(val newColorSpec: String) : RoomMemberProfileAction() data class OpenOrCreateDm(val userId: String) : RoomMemberProfileAction() } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt index 95ee18682e..f4420d6a42 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt @@ -17,8 +17,8 @@ import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import im.vector.lib.strings.CommonStrings import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import javax.inject.Inject class RoomMemberProfileController @Inject constructor( @@ -38,7 +38,7 @@ class RoomMemberProfileController @Inject constructor( fun onOverrideColorClicked() fun onJumpToReadReceiptClicked() fun onMentionClicked() - fun onEditPowerLevel(currentRole: Role) + fun onEditPowerLevel(userPowerLevel: UserPowerLevel) fun onKickClicked(isSpace: Boolean) fun onBanClicked(isSpace: Boolean, isUserBanned: Boolean) fun onCancelInviteClicked() @@ -243,11 +243,10 @@ class RoomMemberProfileController @Inject constructor( } private fun buildAdminSection(state: RoomMemberProfileViewState) { - val powerLevelsContent = state.powerLevelsContent ?: return val powerLevelsStr = state.userPowerLevelString() ?: return - val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) - val userPowerLevel = powerLevelsHelper.getUserRole(state.userId) - val myPowerLevel = powerLevelsHelper.getUserRole(session.myUserId) + val roomPowerLevels = state.roomPowerLevels ?: return + val userPowerLevel = roomPowerLevels.getUserPowerLevel(state.userId) + val myPowerLevel = roomPowerLevels.getUserPowerLevel(session.myUserId) if ((!state.isMine && myPowerLevel <= userPowerLevel)) { return } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index 29cb42a686..836e3857cc 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -51,7 +51,7 @@ import im.vector.app.features.roommemberprofile.powerlevel.EditPowerLevelDialogs import im.vector.lib.strings.CommonStrings import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.crypto.model.UserVerificationLevel -import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject @@ -377,9 +377,9 @@ class RoomMemberProfileFragment : .show() } - override fun onEditPowerLevel(currentRole: Role) { - EditPowerLevelDialogs.showChoice(requireActivity(), CommonStrings.power_level_edit_title, currentRole) { newPowerLevel -> - viewModel.handle(RoomMemberProfileAction.SetPowerLevel(currentRole.value, newPowerLevel, true)) + override fun onEditPowerLevel(userPowerLevel: UserPowerLevel) { + EditPowerLevelDialogs.showChoice(requireActivity(), CommonStrings.power_level_edit_title, userPowerLevel) { newPowerLevel -> + viewModel.handle(RoomMemberProfileAction.SetPowerLevel(userPowerLevel, newPowerLevel, true)) } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewEvents.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewEvents.kt index d806f521ad..11bb19c51b 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewEvents.kt @@ -8,6 +8,7 @@ package im.vector.app.features.roommemberprofile import im.vector.app.core.platform.VectorViewEvents +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel /** * Transient events for RoomMemberProfile. @@ -22,8 +23,8 @@ sealed class RoomMemberProfileViewEvents : VectorViewEvents { object OnInviteActionSuccess : RoomMemberProfileViewEvents() object OnKickActionSuccess : RoomMemberProfileViewEvents() object OnBanActionSuccess : RoomMemberProfileViewEvents() - data class ShowPowerLevelValidation(val currentValue: Int, val newValue: Int) : RoomMemberProfileViewEvents() - data class ShowPowerLevelDemoteWarning(val currentValue: Int, val newValue: Int) : RoomMemberProfileViewEvents() + data class ShowPowerLevelValidation(val currentValue: UserPowerLevel, val newValue: UserPowerLevel.Value) : RoomMemberProfileViewEvents() + data class ShowPowerLevelDemoteWarning(val currentValue: UserPowerLevel, val newValue: UserPowerLevel.Value) : RoomMemberProfileViewEvents() data class StartVerification( val userId: String, diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 91024ee136..92ba7acfb5 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -42,9 +42,9 @@ import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomEncryptionAlgorithm import org.matrix.android.sdk.api.session.room.model.RoomType -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem @@ -233,15 +233,15 @@ class RoomMemberProfileViewModel @AssistedInject constructor( if (room == null || action.previousValue == action.newValue) { return@withState } - val currentPowerLevelsContent = state.powerLevelsContent ?: return@withState - val myPowerLevel = PowerLevelsHelper(currentPowerLevelsContent).getUserPowerLevelValue(session.myUserId) + val roomPowerLevels = state.roomPowerLevels ?: return@withState + val myPowerLevel = roomPowerLevels.getUserPowerLevel(session.myUserId) if (action.askForValidation && action.newValue >= myPowerLevel) { _viewEvents.post(RoomMemberProfileViewEvents.ShowPowerLevelValidation(action.previousValue, action.newValue)) } else if (action.askForValidation && state.isMine) { _viewEvents.post(RoomMemberProfileViewEvents.ShowPowerLevelDemoteWarning(action.previousValue, action.newValue)) } else { - val newPowerLevelsContent = currentPowerLevelsContent - .setUserPowerLevel(state.userId, action.newValue) + val newPowerLevelsContent = (roomPowerLevels.powerLevelsContent ?: PowerLevelsContent()) + .setUserPowerLevel(state.userId, action.newValue.value) .toContent() viewModelScope.launch { _viewEvents.post(RoomMemberProfileViewEvents.Loading()) @@ -361,19 +361,17 @@ class RoomMemberProfileViewModel @AssistedInject constructor( private fun observeRoomSummaryAndPowerLevels(room: Room) { val roomSummaryLive = room.flow().liveRoomSummary().unwrap() - val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() - - powerLevelsContentLive - .onEach { - val powerLevelsHelper = PowerLevelsHelper(it) + val powerLevelsFlow = PowerLevelsFlowFactory(room).createFlow() + powerLevelsFlow + .onEach { roomPowerLevels -> val permissions = ActionPermissions( - canKick = powerLevelsHelper.isUserAbleToKick(session.myUserId), - canBan = powerLevelsHelper.isUserAbleToBan(session.myUserId), - canInvite = powerLevelsHelper.isUserAbleToInvite(session.myUserId), - canEditPowerLevel = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_POWER_LEVELS) + canKick = roomPowerLevels.isUserAbleToKick(session.myUserId), + canBan = roomPowerLevels.isUserAbleToBan(session.myUserId), + canInvite = roomPowerLevels.isUserAbleToInvite(session.myUserId), + canEditPowerLevel = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_POWER_LEVELS) ) setState { - copy(powerLevelsContent = it, actionPermissions = permissions) + copy(roomPowerLevels = roomPowerLevels, actionPermissions = permissions) } }.launchIn(viewModelScope) @@ -388,14 +386,14 @@ class RoomMemberProfileViewModel @AssistedInject constructor( copy(isRoomEncrypted = false) } } - roomSummaryLive.combine(powerLevelsContentLive) { roomSummary, powerLevelsContent -> + roomSummaryLive.combine(powerLevelsFlow) { roomSummary, roomPowerLevels -> val roomName = roomSummary.toMatrixItem().getBestName() - val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) - when (val userPowerLevel = powerLevelsHelper.getUserRole(initialState.userId)) { + when (roomPowerLevels.getUserRole(initialState.userId)) { + Role.SuperAdmin, + Role.Creator, Role.Admin -> stringProvider.getString(CommonStrings.room_member_power_level_admin_in, roomName) Role.Moderator -> stringProvider.getString(CommonStrings.room_member_power_level_moderator_in, roomName) - Role.Default -> stringProvider.getString(CommonStrings.room_member_power_level_default_in, roomName) - is Role.Custom -> stringProvider.getString(CommonStrings.room_member_power_level_custom_in, userPowerLevel.value, roomName) + Role.User -> stringProvider.getString(CommonStrings.room_member_power_level_default_in, roomName) } }.execute { copy(userPowerLevelString = it) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewState.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewState.kt index 7d550c891d..a51eeef8e3 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewState.kt @@ -12,7 +12,7 @@ import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.util.MatrixItem data class RoomMemberProfileViewState( @@ -24,7 +24,7 @@ data class RoomMemberProfileViewState( val isIgnored: Async = Uninitialized, val isRoomEncrypted: Boolean = false, val isAlgorithmSupported: Boolean = true, - val powerLevelsContent: PowerLevelsContent? = null, + val roomPowerLevels: RoomPowerLevels? = null, val userPowerLevelString: Async = Uninitialized, val userMatrixItem: Async = Uninitialized, val userMXCrossSigningInfo: MXCrossSigningInfo? = null, diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt index ffe6314592..6d24f198f4 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt @@ -19,6 +19,7 @@ import im.vector.app.core.extensions.hideKeyboard import im.vector.app.databinding.DialogEditPowerLevelBinding import im.vector.lib.strings.CommonStrings import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel object EditPowerLevelDialogs { @@ -26,21 +27,21 @@ object EditPowerLevelDialogs { fun showChoice( activity: Activity, @StringRes titleRes: Int, - currentRole: Role, - listener: (Int) -> Unit + currentPowerLevel: UserPowerLevel, + listener: (UserPowerLevel.Value) -> Unit ) { val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_edit_power_level, null) val views = DialogEditPowerLevelBinding.bind(dialogLayout) views.powerLevelRadioGroup.setOnCheckedChangeListener { _, checkedId -> views.powerLevelCustomEditLayout.isVisible = checkedId == R.id.powerLevelCustomRadio } - views.powerLevelCustomEdit.setText("${currentRole.value}") - + val currentRole = Role.getSuggestedRole(currentPowerLevel) when (currentRole) { + Role.Creator -> views.powerLevelAdminRadio.isChecked = true + Role.SuperAdmin -> views.powerLevelAdminRadio.isChecked = true Role.Admin -> views.powerLevelAdminRadio.isChecked = true Role.Moderator -> views.powerLevelModeratorRadio.isChecked = true - Role.Default -> views.powerLevelDefaultRadio.isChecked = true - else -> views.powerLevelCustomRadio.isChecked = true + Role.User -> views.powerLevelDefaultRadio.isChecked = true } MaterialAlertDialogBuilder(activity) @@ -48,14 +49,14 @@ object EditPowerLevelDialogs { .setView(dialogLayout) .setPositiveButton(CommonStrings.edit) { _, _ -> val newValue = when (views.powerLevelRadioGroup.checkedRadioButtonId) { - R.id.powerLevelAdminRadio -> Role.Admin.value - R.id.powerLevelModeratorRadio -> Role.Moderator.value - R.id.powerLevelDefaultRadio -> Role.Default.value - else -> { - views.powerLevelCustomEdit.text?.toString()?.toInt() ?: currentRole.value - } + R.id.powerLevelAdminRadio -> UserPowerLevel.Admin + R.id.powerLevelModeratorRadio -> UserPowerLevel.Moderator + R.id.powerLevelDefaultRadio -> UserPowerLevel.User + else -> null + } + if(newValue != null) { + listener(newValue) } - listener(newValue) } .setNegativeButton(CommonStrings.action_cancel, null) .setOnKeyListener(DialogInterface.OnKeyListener diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index 83b1c5a950..deb609509a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -39,7 +39,7 @@ import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.state.isPublic import org.matrix.android.sdk.flow.FlowRoom import org.matrix.android.sdk.flow.flow @@ -115,9 +115,8 @@ class RoomProfileViewModel @AssistedInject constructor( private fun observePowerLevels() { val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() powerLevelsContentLive - .onEach { - val powerLevelsHelper = PowerLevelsHelper(it) - val canUpdateRoomState = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) + .onEach { roomPowerLevels -> + val canUpdateRoomState = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) setState { copy(canUpdateRoomState = canUpdateRoomState) } @@ -158,10 +157,9 @@ class RoomProfileViewModel @AssistedInject constructor( private fun observePermissions() { PowerLevelsFlowFactory(room) .createFlow() - .setOnEach { - val powerLevelsHelper = PowerLevelsHelper(it) + .setOnEach { roomPowerLevels -> val permissions = RoomProfileViewState.ActionPermissions( - canEnableEncryption = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) + canEnableEncryption = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) ) copy(actionPermissions = permissions) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index a80e7b2050..73f8e92434 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.mapOptional import org.matrix.android.sdk.flow.unwrap @@ -127,10 +127,9 @@ class RoomAliasViewModel @AssistedInject constructor( private fun observePowerLevel() { PowerLevelsFlowFactory(room) .createFlow() - .onEach { - val powerLevelsHelper = PowerLevelsHelper(it) + .onEach { roomPowerLevels -> val permissions = RoomAliasViewState.ActionPermissions( - canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend( + canChangeCanonicalAlias = roomPowerLevels.isUserAllowedToSend( userId = session.myUserId, isState = true, eventType = EventType.STATE_ROOM_CANONICAL_ALIAS diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt index df697bd501..acfdbb8b1c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt @@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap @@ -62,12 +62,10 @@ class RoomBannedMemberListViewModel @AssistedInject constructor( ) } - val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() - - powerLevelsContentLive - .setOnEach { - val powerLevelsHelper = PowerLevelsHelper(it) - copy(canUserBan = powerLevelsHelper.isUserAbleToBan(session.myUserId)) + val powerLevelsFlow = PowerLevelsFlowFactory(room).createFlow() + powerLevelsFlow + .setOnEach { roomPowerLevels -> + copy(canUserBan = roomPowerLevels.isUserAbleToBan(session.myUserId)) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListComparator.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListComparator.kt new file mode 100644 index 0000000000..a4ce99f63d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListComparator.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2020-2024 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 im.vector.app.features.roomprofile.members + +import javax.inject.Inject + +class RoomMemberListComparator @Inject constructor() : Comparator { + + override fun compare(leftRoomMember: RoomMemberWithPowerLevel?, rightRoomMember: RoomMemberWithPowerLevel?): Int { + return when (leftRoomMember) { + null -> + when (rightRoomMember) { + null -> 0 + else -> 1 + } + else -> + when (rightRoomMember) { + null -> -1 + else -> + when { + leftRoomMember.powerLevel > rightRoomMember.powerLevel -> 1 + leftRoomMember.powerLevel < rightRoomMember.powerLevel -> -1 + leftRoomMember.summary.displayName.isNullOrBlank() -> + when { + rightRoomMember.summary.displayName.isNullOrBlank() -> { + // No display names, compare ids + leftRoomMember.summary.userId.compareTo(rightRoomMember.summary.userId) + } + else -> 1 + } + else -> + when { + rightRoomMember.summary.displayName.isNullOrBlank() -> -1 + else -> { + when (leftRoomMember.summary.displayName) { + rightRoomMember.summary.displayName -> + // Same display name, compare id + leftRoomMember.summary.userId.compareTo(rightRoomMember.summary.userId) + else -> + leftRoomMember.summary.displayName!!.compareTo(rightRoomMember.summary.displayName!!, true) + } + } + } + } + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt index 88f55aac70..24d2dddc8e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt @@ -16,11 +16,13 @@ import im.vector.app.core.extensions.join import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.roomprofile.permissions.RoleFormatter import me.gujun.android.span.span import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent +import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -29,7 +31,8 @@ class RoomMemberListController @Inject constructor( private val avatarRenderer: AvatarRenderer, private val stringProvider: StringProvider, private val colorProvider: ColorProvider, - private val roomMemberSummaryFilter: RoomMemberSummaryFilter + private val roomMemberSummaryFilter: RoomMemberSummaryFilter, + private val roleFormatter: RoleFormatter, ) : TypedEpoxyController() { interface Callback { @@ -56,13 +59,13 @@ class RoomMemberListController @Inject constructor( .orEmpty() var threePidInvitesDone = filteredThreePidInvites.isEmpty() - for ((powerLevelCategory, roomMemberList) in roomMembersByPowerLevel) { - val filteredRoomMemberList = roomMemberList.filter { roomMemberSummaryFilter.test(it) } + for ((category, roomMemberList) in roomMembersByPowerLevel) { + val filteredRoomMemberList = roomMemberList.filter { roomMemberSummaryFilter.test(it.summary) } if (filteredRoomMemberList.isEmpty()) { continue } - if (powerLevelCategory == RoomMemberListCategories.USER && !threePidInvitesDone) { + if (category == RoomMemberListCategories.USER && !threePidInvitesDone) { // If there is no regular invite, display threepid invite before the regular user buildProfileSection( stringProvider.getString(RoomMemberListCategories.INVITE.titleRes) @@ -73,20 +76,20 @@ class RoomMemberListController @Inject constructor( } buildProfileSection( - stringProvider.getString(powerLevelCategory.titleRes) + stringProvider.getString(category.titleRes) ) filteredRoomMemberList.join( each = { _, roomMember -> - buildRoomMember(roomMember, powerLevelCategory, host, data) + buildRoomMember(roomMember, host, data) }, between = { _, roomMemberBefore -> dividerItem { - id("divider_${roomMemberBefore.userId}") + id("divider_${roomMemberBefore.summary.userId}") } } ) - if (powerLevelCategory == RoomMemberListCategories.INVITE && !threePidInvitesDone) { + if (category == RoomMemberListCategories.INVITE && !threePidInvitesDone) { // Display the threepid invite after the regular invite dividerItem { id("divider_threepidinvites") @@ -108,24 +111,24 @@ class RoomMemberListController @Inject constructor( } private fun buildRoomMember( - roomMember: RoomMemberSummary, - powerLevelCategory: RoomMemberListCategories, + roomMember: RoomMemberWithPowerLevel, host: RoomMemberListController, data: RoomMemberListViewState ) { - val powerLabel = stringProvider.getString(powerLevelCategory.titleRes) + val role = Role.getSuggestedRole(roomMember.powerLevel) + val powerLabel = roleFormatter.format(role) profileMatrixItemWithPowerLevelWithPresence { - id(roomMember.userId) - matrixItem(roomMember.toMatrixItem()) + id(roomMember.summary.userId) + matrixItem(roomMember.summary.toMatrixItem()) avatarRenderer(host.avatarRenderer) - userVerificationLevel(data.trustLevelMap.invoke()?.get(roomMember.userId)) + userVerificationLevel(data.trustLevelMap.invoke()?.get(roomMember.summary.userId)) clickListener { - host.callback?.onRoomMemberClicked(roomMember) + host.callback?.onRoomMemberClicked(roomMember.summary) } showPresence(true) - userPresence(roomMember.userPresence) - ignoredUser(roomMember.userId in data.ignoredUserIds) + userPresence(roomMember.summary.userPresence) + ignoredUser(roomMember.summary.userId in data.ignoredUserIds) powerLevelLabel( span { span(powerLabel) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt index 465da6a9c2..2974e10649 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt @@ -31,22 +31,19 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.UserVerificationLevel import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.mapOptional import org.matrix.android.sdk.flow.unwrap import timber.log.Timber class RoomMemberListViewModel @AssistedInject constructor( @Assisted initialState: RoomMemberListViewState, - private val roomMemberSummaryComparator: RoomMemberSummaryComparator, + private val roomMemberListComparator: RoomMemberListComparator, private val session: Session ) : VectorViewModel(initialState) { @@ -75,14 +72,12 @@ class RoomMemberListViewModel @AssistedInject constructor( memberships = Membership.activeMemberships() } + val powerLevelsFlow = PowerLevelsFlowFactory(room).createFlow() combine( roomFlow.liveRoomMembers(roomMemberQueryParams), - roomFlow - .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - .mapOptional { it.content.toModel() } - .unwrap() - ) { roomMembers, powerLevelsContent -> - buildRoomMemberSummaries(powerLevelsContent, roomMembers) + powerLevelsFlow, + ) { roomMembers, roomPowerLevels -> + buildRoomMemberSummaries(roomPowerLevels, roomMembers) } .execute { async -> copy(roomMemberSummaries = async) @@ -143,10 +138,10 @@ class RoomMemberListViewModel @AssistedInject constructor( private fun observePowerLevel() { PowerLevelsFlowFactory(room).createFlow() - .onEach { + .onEach { roomPowerLevels -> val permissions = ActionPermissions( - canInvite = PowerLevelsHelper(it).isUserAbleToInvite(session.myUserId), - canRevokeThreePidInvite = PowerLevelsHelper(it).isUserAllowedToSend( + canInvite = roomPowerLevels.isUserAbleToInvite(session.myUserId), + canRevokeThreePidInvite = roomPowerLevels.isUserAllowedToSend( userId = session.myUserId, isState = true, eventType = EventType.STATE_ROOM_THIRD_PARTY_INVITE @@ -184,31 +179,34 @@ class RoomMemberListViewModel @AssistedInject constructor( } } - private fun buildRoomMemberSummaries(powerLevelsContent: PowerLevelsContent, roomMembers: List): RoomMemberSummaries { - val admins = ArrayList() - val moderators = ArrayList() - val users = ArrayList(roomMembers.size) - val customs = ArrayList() - val invites = ArrayList() - val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) + private fun buildRoomMemberSummaries(roomPowerLevels: RoomPowerLevels, roomMembers: List): RoomMembersByRole { + val admins = ArrayList() + val moderators = ArrayList() + val users = ArrayList(roomMembers.size) + val invites = ArrayList() roomMembers .forEach { roomMember -> - val userRole = powerLevelsHelper.getUserRole(roomMember.userId) + val powerLevel = roomPowerLevels.getUserPowerLevel(roomMember.userId) + val userRole = Role.getSuggestedRole(powerLevel) + val roomMemberWithPowerLevel = RoomMemberWithPowerLevel( + powerLevel = powerLevel, + summary = roomMember, + ) when { - roomMember.membership == Membership.INVITE -> invites.add(roomMember) - userRole == Role.Admin -> admins.add(roomMember) - userRole == Role.Moderator -> moderators.add(roomMember) - userRole == Role.Default -> users.add(roomMember) - else -> customs.add(roomMember) + roomMember.membership == Membership.INVITE -> invites.add(roomMemberWithPowerLevel) + userRole == Role.SuperAdmin || + userRole == Role.Creator || + userRole == Role.Admin -> admins.add(roomMemberWithPowerLevel) + userRole == Role.Moderator -> moderators.add(roomMemberWithPowerLevel) + userRole == Role.User -> users.add(roomMemberWithPowerLevel) } } return listOf( - RoomMemberListCategories.ADMIN to admins.sortedWith(roomMemberSummaryComparator), - RoomMemberListCategories.MODERATOR to moderators.sortedWith(roomMemberSummaryComparator), - RoomMemberListCategories.CUSTOM to customs.sortedWith(roomMemberSummaryComparator), - RoomMemberListCategories.INVITE to invites.sortedWith(roomMemberSummaryComparator), - RoomMemberListCategories.USER to users.sortedWith(roomMemberSummaryComparator) + RoomMemberListCategories.ADMIN to admins.sortedWith(roomMemberListComparator), + RoomMemberListCategories.MODERATOR to moderators.sortedWith(roomMemberListComparator), + RoomMemberListCategories.INVITE to invites.sortedWith(roomMemberListComparator), + RoomMemberListCategories.USER to users.sortedWith(roomMemberListComparator) ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt index 90da131cb5..d68a9fceba 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt @@ -18,11 +18,12 @@ import org.matrix.android.sdk.api.session.crypto.model.UserVerificationLevel import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel data class RoomMemberListViewState( val roomId: String, val roomSummary: Async = Uninitialized, - val roomMemberSummaries: Async = Uninitialized, + val roomMemberSummaries: Async = Uninitialized, val areAllMembersLoaded: Boolean = false, val ignoredUserIds: List = emptyList(), val filter: String = "", @@ -41,12 +42,16 @@ data class ActionPermissions( val canRevokeThreePidInvite: Boolean = false ) -typealias RoomMemberSummaries = List>> +data class RoomMemberWithPowerLevel( + val powerLevel: UserPowerLevel, + val summary: RoomMemberSummary, +) + +typealias RoomMembersByRole = List>> enum class RoomMemberListCategories(@StringRes val titleRes: Int) { ADMIN(CommonStrings.room_member_power_level_admins), MODERATOR(CommonStrings.room_member_power_level_moderators), - CUSTOM(CommonStrings.room_member_power_level_custom), INVITE(CommonStrings.room_member_power_level_invites), USER(CommonStrings.room_member_power_level_users) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryComparator.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryComparator.kt deleted file mode 100644 index fbff99b287..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryComparator.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2020-2024 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 im.vector.app.features.roomprofile.members - -import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -import javax.inject.Inject - -class RoomMemberSummaryComparator @Inject constructor() : Comparator { - - override fun compare(leftRoomMemberSummary: RoomMemberSummary?, rightRoomMemberSummary: RoomMemberSummary?): Int { - return when (leftRoomMemberSummary) { - null -> - when (rightRoomMemberSummary) { - null -> 0 - else -> 1 - } - else -> - when (rightRoomMemberSummary) { - null -> -1 - else -> - when { - leftRoomMemberSummary.displayName.isNullOrBlank() -> - when { - rightRoomMemberSummary.displayName.isNullOrBlank() -> { - // No display names, compare ids - leftRoomMemberSummary.userId.compareTo(rightRoomMemberSummary.userId) - } - else -> 1 - } - else -> - when { - rightRoomMemberSummary.displayName.isNullOrBlank() -> -1 - else -> { - when (leftRoomMemberSummary.displayName) { - rightRoomMemberSummary.displayName -> - // Same display name, compare id - leftRoomMemberSummary.userId.compareTo(rightRoomMemberSummary.userId) - else -> - leftRoomMemberSummary.displayName!!.compareTo(rightRoomMemberSummary.displayName!!, true) - } - } - } - } - } - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoleFormatter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoleFormatter.kt index 6bc62cb53d..4febb822ad 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoleFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoleFormatter.kt @@ -19,8 +19,9 @@ class RoleFormatter @Inject constructor( return when (role) { Role.Admin -> stringProvider.getString(CommonStrings.power_level_admin) Role.Moderator -> stringProvider.getString(CommonStrings.power_level_moderator) - Role.Default -> stringProvider.getString(CommonStrings.power_level_default) - is Role.Custom -> stringProvider.getString(CommonStrings.power_level_custom, role.value) + Role.User -> stringProvider.getString(CommonStrings.power_level_default) + Role.Creator -> stringProvider.getString(CommonStrings.power_level_owner) + Role.SuperAdmin -> stringProvider.getString(CommonStrings.power_level_owner) } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsAction.kt index a8eb77bd03..cb4376c98f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsAction.kt @@ -8,9 +8,10 @@ package im.vector.app.features.roomprofile.permissions import im.vector.app.core.platform.VectorViewModelAction +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel sealed class RoomPermissionsAction : VectorViewModelAction { object ToggleShowAllPermissions : RoomPermissionsAction() - data class UpdatePermission(val editablePermission: EditablePermission, val powerLevel: Int) : RoomPermissionsAction() + data class UpdatePermission(val editablePermission: EditablePermission, val powerLevel: UserPowerLevel.Value) : RoomPermissionsAction() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt index 2e5d89e409..e5255eb435 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.room.model.redactOrDefault import org.matrix.android.sdk.api.session.room.model.stateDefaultOrDefault import org.matrix.android.sdk.api.session.room.model.usersDefaultOrDefault import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import javax.inject.Inject class RoomPermissionsController @Inject constructor( @@ -34,7 +35,7 @@ class RoomPermissionsController @Inject constructor( ) : TypedEpoxyController() { interface Callback { - fun onEditPermission(editablePermission: EditablePermission, currentRole: Role) + fun onEditPermission(editablePermission: EditablePermission, currentPowerLevel: UserPowerLevel) fun toggleShowAllPermissions() } @@ -165,7 +166,8 @@ class RoomPermissionsController @Inject constructor( editable: Boolean, isSpace: Boolean ) { - val currentRole = getCurrentRole(editablePermission, content) + val currentPowerLevel = getPowerLevel(editablePermission, content) + val currentRole = Role.getSuggestedRole(currentPowerLevel) buildProfileAction( id = editablePermission.labelResId.toString(), title = stringProvider.getString( @@ -177,12 +179,12 @@ class RoomPermissionsController @Inject constructor( action = { callback ?.takeIf { editable } - ?.onEditPermission(editablePermission, currentRole) + ?.onEditPermission(editablePermission, currentPowerLevel) } ) } - private fun getCurrentRole(editablePermission: EditablePermission, content: PowerLevelsContent): Role { + private fun getPowerLevel(editablePermission: EditablePermission, content: PowerLevelsContent): UserPowerLevel.Value { val value = when (editablePermission) { is EditablePermission.EventTypeEditablePermission -> content.events?.get(editablePermission.eventType) ?: content.stateDefaultOrDefault() is EditablePermission.DefaultRole -> content.usersDefaultOrDefault() @@ -194,20 +196,6 @@ class RoomPermissionsController @Inject constructor( is EditablePermission.RemoveMessagesSentByOthers -> content.redactOrDefault() is EditablePermission.NotifyEveryone -> content.notificationLevel(PowerLevelsContent.NOTIFICATIONS_ROOM_KEY) } - - return Role.fromValue( - value, - when (editablePermission) { - is EditablePermission.EventTypeEditablePermission -> content.stateDefaultOrDefault() - is EditablePermission.DefaultRole -> Role.Default.value - is EditablePermission.SendMessages -> Role.Default.value - is EditablePermission.InviteUsers -> Role.Moderator.value - is EditablePermission.ChangeSettings -> Role.Moderator.value - is EditablePermission.KickUsers -> Role.Moderator.value - is EditablePermission.BanUsers -> Role.Moderator.value - is EditablePermission.RemoveMessagesSentByOthers -> Role.Moderator.value - is EditablePermission.NotifyEveryone -> Role.Moderator.value - } - ) + return UserPowerLevel.Value(value) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt index 5d92a96b5f..ea1afbcfc5 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt @@ -26,7 +26,7 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.roommemberprofile.powerlevel.EditPowerLevelDialogs import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.lib.strings.CommonStrings -import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -93,8 +93,8 @@ class RoomPermissionsFragment : } } - override fun onEditPermission(editablePermission: EditablePermission, currentRole: Role) { - EditPowerLevelDialogs.showChoice(requireActivity(), editablePermission.labelResId, currentRole) { newPowerLevel -> + override fun onEditPermission(editablePermission: EditablePermission, currentPowerLevel: UserPowerLevel) { + EditPowerLevelDialogs.showChoice(requireActivity(), editablePermission.labelResId, currentPowerLevel) { newPowerLevel -> viewModel.handle(RoomPermissionsAction.UpdatePermission(editablePermission, newPowerLevel)) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt index 0b942d78cb..3757a1ddd0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt @@ -9,6 +9,7 @@ package im.vector.app.features.roomprofile.permissions import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -24,7 +25,6 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap @@ -61,19 +61,23 @@ class RoomPermissionsViewModel @AssistedInject constructor( private fun observePowerLevel() { PowerLevelsFlowFactory(room) .createFlow() - .onEach { powerLevelContent -> - val powerLevelsHelper = PowerLevelsHelper(powerLevelContent) + .onEach { roomPowerLevels -> val permissions = RoomPermissionsViewState.ActionPermissions( - canChangePowerLevels = powerLevelsHelper.isUserAllowedToSend( + canChangePowerLevels = roomPowerLevels.isUserAllowedToSend( userId = session.myUserId, isState = true, eventType = EventType.STATE_ROOM_POWER_LEVELS ) ) + val powerLevelsContent = roomPowerLevels.powerLevelsContent setState { copy( actionPermissions = permissions, - currentPowerLevelsContent = Success(powerLevelContent) + currentPowerLevelsContent = if (powerLevelsContent != null) { + Success(powerLevelsContent) + } else { + Uninitialized + } ) } }.launchIn(viewModelScope) @@ -94,26 +98,26 @@ class RoomPermissionsViewModel @AssistedInject constructor( private fun updatePermission(action: RoomPermissionsAction.UpdatePermission) { withState { state -> - val currentPowerLevel = state.currentPowerLevelsContent.invoke() ?: return@withState + val currentPowerLevelsContent = state.currentPowerLevelsContent.invoke() ?: return@withState postLoading(true) viewModelScope.launch { try { val newPowerLevelsContent = when (action.editablePermission) { - is EditablePermission.EventTypeEditablePermission -> currentPowerLevel.copy( - events = currentPowerLevel.events.orEmpty().toMutableMap().apply { - put(action.editablePermission.eventType, action.powerLevel) + is EditablePermission.EventTypeEditablePermission -> currentPowerLevelsContent.copy( + events = currentPowerLevelsContent.events.orEmpty().toMutableMap().apply { + put(action.editablePermission.eventType, action.powerLevel.value) } ) - is EditablePermission.DefaultRole -> currentPowerLevel.copy(usersDefault = action.powerLevel) - is EditablePermission.SendMessages -> currentPowerLevel.copy(eventsDefault = action.powerLevel) - is EditablePermission.InviteUsers -> currentPowerLevel.copy(invite = action.powerLevel) - is EditablePermission.ChangeSettings -> currentPowerLevel.copy(stateDefault = action.powerLevel) - is EditablePermission.KickUsers -> currentPowerLevel.copy(kick = action.powerLevel) - is EditablePermission.BanUsers -> currentPowerLevel.copy(ban = action.powerLevel) - is EditablePermission.RemoveMessagesSentByOthers -> currentPowerLevel.copy(redact = action.powerLevel) - is EditablePermission.NotifyEveryone -> currentPowerLevel.copy( - notifications = currentPowerLevel.notifications.orEmpty().toMutableMap().apply { - put(PowerLevelsContent.NOTIFICATIONS_ROOM_KEY, action.powerLevel) + is EditablePermission.DefaultRole -> currentPowerLevelsContent.copy(usersDefault = action.powerLevel.value) + is EditablePermission.SendMessages -> currentPowerLevelsContent.copy(eventsDefault = action.powerLevel.value) + is EditablePermission.InviteUsers -> currentPowerLevelsContent.copy(invite = action.powerLevel.value) + is EditablePermission.ChangeSettings -> currentPowerLevelsContent.copy(stateDefault = action.powerLevel.value) + is EditablePermission.KickUsers -> currentPowerLevelsContent.copy(kick = action.powerLevel.value) + is EditablePermission.BanUsers -> currentPowerLevelsContent.copy(ban = action.powerLevel.value) + is EditablePermission.RemoveMessagesSentByOthers -> currentPowerLevelsContent.copy(redact = action.powerLevel.value) + is EditablePermission.NotifyEveryone -> currentPowerLevelsContent.copy( + notifications = currentPowerLevelsContent.notifications.orEmpty().toMutableMap().apply { + put(PowerLevelsContent.NOTIFICATIONS_ROOM_KEY, action.powerLevel.value) } ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index af043b9ab2..188068afb4 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -32,7 +32,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.mapOptional import org.matrix.android.sdk.flow.unwrap @@ -115,28 +114,26 @@ class RoomSettingsViewModel @AssistedInject constructor( ) } - val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() - - powerLevelsContentLive - .onEach { - val powerLevelsHelper = PowerLevelsHelper(it) + val powerLevelsFlow = PowerLevelsFlowFactory(room).createFlow() + powerLevelsFlow + .onEach { roomPowerLevels -> val permissions = RoomSettingsViewState.ActionPermissions( - canChangeAvatar = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR), - canChangeName = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME), - canChangeTopic = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC), - canChangeHistoryVisibility = powerLevelsHelper.isUserAllowedToSend( + canChangeAvatar = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR), + canChangeName = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME), + canChangeTopic = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC), + canChangeHistoryVisibility = roomPowerLevels.isUserAllowedToSend( session.myUserId, true, EventType.STATE_ROOM_HISTORY_VISIBILITY ), - canChangeJoinRule = powerLevelsHelper.isUserAllowedToSend( + canChangeJoinRule = roomPowerLevels.isUserAllowedToSend( session.myUserId, true, EventType.STATE_ROOM_JOIN_RULES ) && - powerLevelsHelper.isUserAllowedToSend( + roomPowerLevels.isUserAllowedToSend( session.myUserId, true, EventType.STATE_ROOM_GUEST_ACCESS ), - canAddChildren = powerLevelsHelper.isUserAllowedToSend( + canAddChildren = roomPowerLevels.isUserAllowedToSend( session.myUserId, true, EventType.STATE_SPACE_CHILD ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt index 404cfac9de..cebfb63f1f 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt @@ -32,7 +32,6 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow @@ -74,20 +73,19 @@ class SpaceMenuViewModel @AssistedInject constructor( PowerLevelsFlowFactory(room) .createFlow() - .onEach { - val powerLevelsHelper = PowerLevelsHelper(it) + .onEach { roomPowerLevels -> - val canInvite = powerLevelsHelper.isUserAbleToInvite(session.myUserId) - val canAddChild = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_SPACE_CHILD) + val canInvite = roomPowerLevels.isUserAbleToInvite(session.myUserId) + val canAddChild = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_SPACE_CHILD) - val canChangeAvatar = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR) - val canChangeName = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME) - val canChangeTopic = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC) + val canChangeAvatar = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR) + val canChangeName = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME) + val canChangeTopic = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC) - val isAdmin = powerLevelsHelper.getUserRole(session.myUserId) is Role.Admin + val isAdmin = roomPowerLevels.getUserRole(session.myUserId) == Role.Admin val otherAdminCount = roomSummary?.otherMemberIds - ?.map { powerLevelsHelper.getUserRole(it) } - ?.count { it is Role.Admin } + ?.map { roomPowerLevels.getUserRole(it) } + ?.count { it == Role.Admin } ?: 0 val isLastAdmin = isAdmin && otherAdminCount == 0 diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt index bf91be4fa0..ca65b99fd4 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt @@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import org.matrix.android.sdk.api.session.space.CreateSpaceParams import timber.log.Timber import javax.inject.Inject @@ -65,7 +66,7 @@ class CreateSpaceViewModelTask @Inject constructor( if (params.isPublic) { this.roomAliasName = params.spaceAlias this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy( - invite = Role.Default.value + invite = UserPowerLevel.User.value ) this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT this.historyVisibility = RoomHistoryVisibility.WORLD_READABLE @@ -79,7 +80,7 @@ class CreateSpaceViewModelTask @Inject constructor( } ) this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy( - invite = Role.Moderator.value + invite = UserPowerLevel.Moderator.value ) } }) diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index ae112908fc..3627dd5469 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -36,7 +36,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow import timber.log.Timber @@ -96,16 +95,14 @@ class SpaceDirectoryViewModel @AssistedInject constructor( private fun observePermissions() { val room = session.getRoom(initialState.spaceId) ?: return - val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() + val powerLevelsFlow = PowerLevelsFlowFactory(room).createFlow() - powerLevelsContentLive - .onEach { - val powerLevelsHelper = PowerLevelsHelper(it) + powerLevelsFlow + .onEach { roomPowerLevels -> setState { copy( - canAddRooms = powerLevelsHelper.isUserAllowedToSend( - session.myUserId, true, - EventType.STATE_SPACE_CHILD + canAddRooms = roomPowerLevels.isUserAllowedToSend( + userId = session.myUserId, isState = true, eventType = EventType.STATE_SPACE_CHILD ) ) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt index de68b697ef..b8a3bbd07d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt @@ -31,10 +31,11 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getRoomPowerLevels import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow @@ -50,14 +51,12 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor( init { val space = session.getRoom(initialState.spaceId) val spaceSummary = space?.roomSummary() - - val powerLevelsEvent = space?.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - powerLevelsEvent?.content?.toModel()?.let { powerLevelsContent -> - val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) - val isAdmin = powerLevelsHelper.getUserRole(session.myUserId) is Role.Admin + val roomPowerLevels = space?.getRoomPowerLevels() + roomPowerLevels?.let { + val isAdmin = roomPowerLevels.getUserRole(session.myUserId) == Role.Admin val otherAdminCount = spaceSummary?.otherMemberIds - ?.map { powerLevelsHelper.getUserRole(it) } - ?.count { it is Role.Admin } + ?.map { roomPowerLevels.getUserRole(it) } + ?.count { it == Role.Admin } ?: 0 val isLastAdmin = isAdmin && otherAdminCount == 0 setState { diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleListController.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleListController.kt index 1011952528..f8b5fada1a 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleListController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleListController.kt @@ -54,7 +54,7 @@ class SpacePeopleListController @Inject constructor( memberSummaries.forEach { memberEntry -> val filtered = memberEntry.second - .filter { roomMemberSummaryFilter.test(it) } + .filter { roomMemberSummaryFilter.test(it.summary) } if (filtered.isNotEmpty()) { dividerItem { id("divider_type_${memberEntry.first.titleRes}") @@ -65,10 +65,10 @@ class SpacePeopleListController @Inject constructor( .join( each = { _, roomMember -> profileMatrixItemWithPowerLevel { - id(roomMember.userId) - matrixItem(roomMember.toMatrixItem()) + id(roomMember.summary.userId) + matrixItem(roomMember.summary.toMatrixItem()) avatarRenderer(host.avatarRenderer) - userVerificationLevel(data.trustLevelMap.invoke()?.get(roomMember.userId)) + userVerificationLevel(data.trustLevelMap.invoke()?.get(roomMember.summary.userId)) .apply { val pl = host.toPowerLevelLabel(memberEntry.first) if (memberEntry.first == RoomMemberListCategories.INVITE) { @@ -106,13 +106,13 @@ class SpacePeopleListController @Inject constructor( } clickListener { - host.listener?.onSpaceMemberClicked(roomMember) + host.listener?.onSpaceMemberClicked(roomMember.summary) } } }, between = { _, roomMemberBefore -> dividerItem { - id("divider_${roomMemberBefore.userId}") + id("divider_${roomMemberBefore.summary.userId}") } } ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt index 09b23bbed8..f5b6a0a06c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt @@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoomSummary -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels class ShareSpaceViewModel @AssistedInject constructor( @Assisted private val initialState: ShareSpaceViewState, @@ -52,11 +52,10 @@ class ShareSpaceViewModel @AssistedInject constructor( val room = session.getRoom(initialState.spaceId) ?: return PowerLevelsFlowFactory(room) .createFlow() - .onEach { powerLevelContent -> - val powerLevelsHelper = PowerLevelsHelper(powerLevelContent) + .onEach { roomPowerLevels -> setState { copy( - canInviteByMxId = powerLevelsHelper.isUserAbleToInvite(session.myUserId) + canInviteByMxId = roomPowerLevels.isUserAbleToInvite(session.myUserId) ) } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt index 233cb82d5d..973ea11f47 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt @@ -33,10 +33,11 @@ import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.getRoomPowerLevels import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap import timber.log.Timber @@ -139,12 +140,8 @@ class StartVoiceBroadcastUseCase @Inject constructor( @VisibleForTesting fun assertHasEnoughPowerLevels(room: Room) { - val powerLevelsHelper = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - ?.content - ?.toModel() - ?.let { PowerLevelsHelper(it) } - - if (powerLevelsHelper?.isUserAllowedToSend(session.myUserId, true, VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO) != true) { + val roomPowerLevels = room.getRoomPowerLevels() + if (!roomPowerLevels.isUserAllowedToSend(session.myUserId, true, VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO)) { Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: no permission") throw VoiceBroadcastFailure.RecordingError.NoPermission } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt index 61f79662b0..326567fb07 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt @@ -25,10 +25,11 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getRoomPowerLevels import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.widgets.WidgetPostAPIMediator import org.matrix.android.sdk.api.util.JsonDict import timber.log.Timber @@ -146,13 +147,8 @@ class WidgetPostAPIHandler @AssistedInject constructor( Timber.d("## canSendEvent() : eventType $eventType isState $isState") - val powerLevelsEvent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - val powerLevelsContent = powerLevelsEvent?.content?.toModel() - val canSend = if (powerLevelsContent == null) { - false - } else { - PowerLevelsHelper(powerLevelsContent).isUserAllowedToSend(session.myUserId, isState, eventType) - } + val roomPowerLevels = room.getRoomPowerLevels() + val canSend = roomPowerLevels.isUserAllowedToSend(session.myUserId, isState, eventType) if (canSend) { Timber.d("## canSendEvent() returns true") widgetPostAPIMediator.sendBoolResponse(true, eventData) diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index 6580b52fca..1778423db7 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -19,9 +19,11 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.widgets.permissions.WidgetPermissionsHelper import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -31,7 +33,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.mapOptional @@ -102,11 +104,10 @@ class WidgetViewModel @AssistedInject constructor( if (room == null) { return } - room.flow().liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - .mapOptional { it.content.toModel() } - .unwrap() - .map { - PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, true, null) + PowerLevelsFlowFactory(room) + .createFlow() + .map { roomPowerLevels -> + roomPowerLevels.isUserAllowedToSend(session.myUserId, true, null) } .setOnEach { copy(canManageWidgets = it) diff --git a/vector/src/test/java/im/vector/app/features/MemberListViewModelTest.kt b/vector/src/test/java/im/vector/app/features/MemberListViewModelTest.kt index 18ea98db3e..0f540bd2cd 100644 --- a/vector/src/test/java/im/vector/app/features/MemberListViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/MemberListViewModelTest.kt @@ -13,7 +13,7 @@ import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.members.RoomMemberListViewModel import im.vector.app.features.roomprofile.members.RoomMemberListViewState -import im.vector.app.features.roomprofile.members.RoomMemberSummaryComparator +import im.vector.app.features.roomprofile.members.RoomMemberListComparator import im.vector.app.test.test import im.vector.app.test.testCoroutineDispatchers import io.mockk.coEvery @@ -266,7 +266,7 @@ class MemberListViewModelTest { private fun createViewModel(): RoomMemberListViewModel { return RoomMemberListViewModel( RoomMemberListViewState(args), - RoomMemberSummaryComparator(), + RoomMemberListComparator(), fakeSession, ) }