1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-05 15:52:47 +02:00

change (power level) : support new InfinitePowerLevel (first draft)

This commit is contained in:
ganfra
2025-07-24 22:25:10 +02:00
parent 926e64bb6e
commit fd3f7e3a24
60 changed files with 566 additions and 448 deletions

View File

@@ -120,6 +120,7 @@
<string name="notice_widget_modified">%1$s modified %2$s widget</string>
<string name="notice_widget_modified_by_you">You modified %1$s widget</string>
<string name="power_level_owner">Owner</string>
<string name="power_level_admin">Admin</string>
<string name="power_level_moderator">Moderator</string>
<string name="power_level_default">Default</string>
@@ -2383,6 +2384,7 @@
<string name="room_member_power_level_invites">Invites</string>
<string name="room_member_power_level_users">Users</string>
<string name="room_member_power_level_owner_in">Owner in %1$s</string>
<string name="room_member_power_level_admin_in">Admin in %1$s</string>
<string name="room_member_power_level_moderator_in">Moderator in %1$s</string>
<string name="room_member_power_level_default_in">Default in %1$s</string>

View File

@@ -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<PowerLevelsContent>()
?.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 ?: ""))

View File

@@ -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)
}
}

View File

@@ -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<PowerLevelsContent>()
val roomCreateContent = getStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty)?.getRoomCreateContentWithSender()
return RoomPowerLevels(
powerLevelsContent,
roomCreateContent
)
}

View File

@@ -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)

View File

@@ -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<String>? = 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<RoomCreateContent>() ?: 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)
}

View File

@@ -17,26 +17,22 @@
package org.matrix.android.sdk.api.session.room.powerlevels
sealed class Role(open val value: Int) : Comparable<Role> {
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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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<UserPowerLevel> {
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)
}
}

View File

@@ -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<PowerLevelsContent>()
?.let { PowerLevelsHelper(it) }
return powerLevelsHelper?.isUserAbleToInvite(userId) ?: false
val roomPowerLevels = stateEventDataSource.getRoomPowerLevels(roomId)
return roomPowerLevels.isUserAbleToInvite(userId)
}
}

View File

@@ -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>()
?: PowerLevelsContent()
return condition.isSatisfied(event, powerLevelsContent)
val roomPowerLevels = room.getRoomPowerLevels()
return condition.isSatisfied(event, roomPowerLevels)
}
override fun resolveContainsDisplayNameCondition(

View File

@@ -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<PowerLevelsContent>()
?.let { PowerLevelsHelper(it) }
}
private fun handleInitialAggregatedRelations(
realm: Realm,
event: Event,

View File

@@ -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
}

View File

@@ -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

View File

@@ -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<PowerLevelsContent>()
val roomCreateContent = getStateEvent(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty)?.getRoomCreateContentWithSender()
return RoomPowerLevels(powerLevelsContent, roomCreateContent)
}

View File

@@ -96,3 +96,4 @@ internal class StateEventDataSource @Inject constructor(
}
}
}

View File

@@ -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<PowerLevelsContent>() }
?.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<RoomCreateContent>()
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) {

View File

@@ -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<PowerLevelsContent>()
?.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 {

View File

@@ -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<PowerLevelsContent>()
?: 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")
}
}

View File

@@ -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<PowerLevelsContent>() ?: 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)
}
}

View File

@@ -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<PowerLevelsHelper>()
every { powerLevelsHelper.isUserAbleToRedact(userId) } returns isAbleToRedact
return powerLevelsHelper
private fun mockRedactionPowerLevels(userId: String, isAbleToRedact: Boolean): RoomPowerLevels {
val roomPowerLevels = mockk<RoomPowerLevels>()
every { roomPowerLevels.isUserAbleToRedact(userId) } returns isAbleToRedact
return roomPowerLevels
}
}

View File

@@ -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 }

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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<PowerLevelsContent>() }
?.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)

View File

@@ -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<String>()
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)

View File

@@ -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)

View File

@@ -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<PowerLevelsContent> {
return room.flow()
fun createFlow(): Flow<RoomPowerLevels> {
val flowRoom = room.flow()
val powerLevelsFlow = flowRoom
.liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)
.mapOptional { it.content.toModel<PowerLevelsContent>() }
.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()
)
}
}
}

View File

@@ -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()
}

View File

@@ -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
}

View File

@@ -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))
}
}

View File

@@ -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,

View File

@@ -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)

View File

@@ -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<Boolean> = Uninitialized,
val isRoomEncrypted: Boolean = false,
val isAlgorithmSupported: Boolean = true,
val powerLevelsContent: PowerLevelsContent? = null,
val roomPowerLevels: RoomPowerLevels? = null,
val userPowerLevelString: Async<String> = Uninitialized,
val userMatrixItem: Async<MatrixItem> = Uninitialized,
val userMXCrossSigningInfo: MXCrossSigningInfo? = null,

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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))
}
}

View File

@@ -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<RoomMemberWithPowerLevel> {
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)
}
}
}
}
}
}
}
}

View File

@@ -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<RoomMemberListViewState>() {
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) {

View File

@@ -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<RoomMemberListViewState, RoomMemberListAction, EmptyViewEvents>(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<PowerLevelsContent>() }
.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<RoomMemberSummary>): RoomMemberSummaries {
val admins = ArrayList<RoomMemberSummary>()
val moderators = ArrayList<RoomMemberSummary>()
val users = ArrayList<RoomMemberSummary>(roomMembers.size)
val customs = ArrayList<RoomMemberSummary>()
val invites = ArrayList<RoomMemberSummary>()
val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent)
private fun buildRoomMemberSummaries(roomPowerLevels: RoomPowerLevels, roomMembers: List<RoomMemberSummary>): RoomMembersByRole {
val admins = ArrayList<RoomMemberWithPowerLevel>()
val moderators = ArrayList<RoomMemberWithPowerLevel>()
val users = ArrayList<RoomMemberWithPowerLevel>(roomMembers.size)
val invites = ArrayList<RoomMemberWithPowerLevel>()
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)
)
}

View File

@@ -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<RoomSummary> = Uninitialized,
val roomMemberSummaries: Async<RoomMemberSummaries> = Uninitialized,
val roomMemberSummaries: Async<RoomMembersByRole> = Uninitialized,
val areAllMembersLoaded: Boolean = false,
val ignoredUserIds: List<String> = emptyList(),
val filter: String = "",
@@ -41,12 +42,16 @@ data class ActionPermissions(
val canRevokeThreePidInvite: Boolean = false
)
typealias RoomMemberSummaries = List<Pair<RoomMemberListCategories, List<RoomMemberSummary>>>
data class RoomMemberWithPowerLevel(
val powerLevel: UserPowerLevel,
val summary: RoomMemberSummary,
)
typealias RoomMembersByRole = List<Pair<RoomMemberListCategories, List<RoomMemberWithPowerLevel>>>
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)
}

View File

@@ -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<RoomMemberSummary> {
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)
}
}
}
}
}
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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()
}

View File

@@ -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<RoomPermissionsViewState>() {
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)
}
}

View File

@@ -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))
}
}

View File

@@ -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)
}
)
}

View File

@@ -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
)

View File

@@ -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

View File

@@ -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
)
}
})

View File

@@ -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
)
)
}

View File

@@ -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<PowerLevelsContent>()?.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 {

View File

@@ -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}")
}
}
)

View File

@@ -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)
)
}
}

View File

@@ -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<PowerLevelsContent>()
?.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
}

View File

@@ -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<PowerLevelsContent>()
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)

View File

@@ -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<PowerLevelsContent>() }
.unwrap()
.map {
PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, true, null)
PowerLevelsFlowFactory(room)
.createFlow()
.map { roomPowerLevels ->
roomPowerLevels.isUserAllowedToSend(session.myUserId, true, null)
}
.setOnEach {
copy(canManageWidgets = it)

View File

@@ -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,
)
}