Update dependency org.matrix.rustcomponents:sdk-android to v25.7.23 (#5073)

* Update dependency org.matrix.rustcomponents:sdk-android to v25.7.23

* Adapt to SDK changes:

- Add 'creator' role, adapt existing logic to it.
- Remove `ReplyParameters`, replace with `EventId` where possible.
- Fix changes in OIDC auth methods.
- Add more join rules.

* Make sure both creators and users with power level >= 150 are displayed as 'owners' in the room member list.

* Don't close the roles and permissions screen if the user is a creator

* Use `MediaPreviewValue.DEFAULT` for `MediaPreviewConfig.DEFAULT` too

* Improve APIs around checking roles and power levels:
    - Ensure `RoomInfo.RoomPowerLevels.users` can't be directly used to check power levels since it can't check the power levels for creators.
    - Add a few helper functions to handle actions that relied on the previous `users` property, and docs to explain their usages.

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jorge Martín <jorgem@element.io>
This commit is contained in:
renovate[bot] 2025-07-24 11:58:30 +02:00 committed by GitHub
parent 33aa7a914f
commit 040fde7f22
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
57 changed files with 264 additions and 227 deletions

View file

@ -142,7 +142,7 @@ interface MatrixClient {
/**
* Execute generic GET requests through the SDKs internal HTTP client.
*/
suspend fun getUrl(url: String): Result<String>
suspend fun getUrl(url: String): Result<ByteArray>
/**
* Get a room preview for a given room ID or alias. This is especially useful for rooms that the user is not a member of, or hasn't joined yet.

View file

@ -19,7 +19,7 @@ data class MediaPreviewConfig(
* The default config if unknown (no local nor server config).
*/
val DEFAULT = MediaPreviewConfig(
mediaPreviewValue = MediaPreviewValue.On,
mediaPreviewValue = MediaPreviewValue.DEFAULT,
hideInviteAvatar = false
)
}

View file

@ -21,12 +21,19 @@ import io.element.android.libraries.matrix.api.room.join.JoinRule
enum class MediaPreviewValue {
On,
Off,
Private
Private;
companion object {
/**
* The default value if unknown (no local nor server config).
*/
val DEFAULT = On
}
}
fun MediaPreviewValue.isPreviewEnabled(joinRule: JoinRule?): Boolean {
fun MediaPreviewValue?.isPreviewEnabled(joinRule: JoinRule?): Boolean {
return when (this) {
On -> true
null, On -> true
Off -> false
Private -> when (joinRule) {
is JoinRule.Private,

View file

@ -72,10 +72,21 @@ data class RoomInfo(
val numUnreadMentions: Long,
val heroes: ImmutableList<MatrixUser>,
val pinnedEventIds: ImmutableList<EventId>,
val creator: UserId?,
val creators: ImmutableList<UserId>,
val historyVisibility: RoomHistoryVisibility,
val successorRoom: SuccessorRoom?,
) {
val aliases: List<RoomAlias>
get() = listOfNotNull(canonicalAlias) + alternativeAliases
/**
* Returns the list of users with the given [role] in this room.
*/
fun usersWithRole(role: RoomMember.Role): List<UserId> {
return if (role == RoomMember.Role.CREATOR) {
this.creators
} else {
this.roomPowerLevels?.usersWithRole(role).orEmpty().toList()
}
}
}

View file

@ -26,13 +26,17 @@ data class RoomMember(
* Role of the RoomMember, based on its [powerLevel].
*/
enum class Role(val powerLevel: Long) {
CREATOR(Long.MAX_VALUE),
ADMIN(100L),
MODERATOR(50L),
USER(0L);
companion object {
const val SUPER_ADMIN_LEVEL = 150L
fun forPowerLevel(powerLevel: Long): Role {
return when {
powerLevel > SUPER_ADMIN_LEVEL -> CREATOR
powerLevel >= ADMIN.powerLevel -> ADMIN
powerLevel >= MODERATOR.powerLevel -> MODERATOR
else -> USER
@ -83,3 +87,11 @@ fun RoomMember.toMatrixUser() = MatrixUser(
displayName = displayName,
avatarUrl = avatarUrl,
)
/**
* Returns `true` if the [RoomMember] is an owner of the room.
* Owners are defined as members with either the [RoomMember.Role.CREATOR] role or a power level greater than or equal to [RoomMember.Role.SUPER_ADMIN_LEVEL].
*/
fun RoomMember.isOwner(): Boolean {
return role == RoomMember.Role.CREATOR || powerLevel >= RoomMember.Role.SUPER_ADMIN_LEVEL
}

View file

@ -1,22 +0,0 @@
/*
* 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 io.element.android.libraries.matrix.api.room.message
import io.element.android.libraries.matrix.api.core.EventId
data class ReplyParameters(
val inReplyToEventId: EventId,
val enforceThreadReply: Boolean,
val replyWithinThread: Boolean,
)
fun replyInThread(eventId: EventId, explicitReply: Boolean = false) = ReplyParameters(
inReplyToEventId = eventId,
enforceThreadReply = true,
replyWithinThread = explicitReply,
)

View file

@ -22,10 +22,10 @@ import kotlinx.coroutines.flow.map
*/
fun BaseRoom.usersWithRole(role: RoomMember.Role): Flow<ImmutableList<RoomMember>> {
return roomInfoFlow
.map { it.roomPowerLevels?.users.orEmpty().filter { (_, powerLevel) -> RoomMember.Role.forPowerLevel(powerLevel) == role } }
.map { roomInfo -> roomInfo.usersWithRole(role) }
.combine(membersStateFlow) { powerLevels, membersState ->
membersState.activeRoomMembers()
.filter { powerLevels.containsKey(it.userId) }
.filter { powerLevels.contains(it.userId) }
.toPersistentList()
}
.distinctUntilChanged()

View file

@ -8,9 +8,43 @@
package io.element.android.libraries.matrix.api.room.powerlevels
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.RoomMember
import kotlinx.collections.immutable.ImmutableMap
/**
* Represents the power levels in a Matrix room, containing both the levels needed to perform actions and the custom power levels for users.
*
* **WARNING**: this won't contain the power level of the room creators, as it is not stored in the power levels event. The `users` property is private to
* enforce this restriction and try to avoid using this property directly to check if a user has a certain role.
* Use the [usersWithRole] or [roleOf] methods instead, and never for creators, that logic should be handled separately.
*/
data class RoomPowerLevels(
/**
* The power levels required to perform various actions in the room.
*/
val values: RoomPowerLevelsValues,
val users: ImmutableMap<UserId, Long>,
)
private val users: ImmutableMap<UserId, Long>,
) {
/**
* Returns the set of [UserId]s that have the given role in the room.
*
* **WARNING**: This method must not be used with the [RoomMember.Role.CREATOR] role. It'll result in a runtime error.
*/
fun usersWithRole(role: RoomMember.Role): Set<UserId> {
return if (role == RoomMember.Role.CREATOR) {
error("RoomPowerLevels.usersWithRole should not be used with CREATOR role, use roomInfo.creators instead")
} else {
users.filterValues { RoomMember.Role.forPowerLevel(it) == role }.keys
}
}
/**
* Returns the role of the user in the room based on their power level.
* If the user is not found, returns null.
*
* **WARNING**: This method must not be used with the [RoomMember.Role.CREATOR] role, as it won't return any results.
*/
fun roleOf(userId: UserId): RoomMember.Role? {
return users[userId]?.let(RoomMember.Role::forPowerLevel)
}
}

View file

@ -7,7 +7,6 @@
package io.element.android.libraries.matrix.api.room.tombstone
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
/**
@ -17,15 +16,10 @@ import io.element.android.libraries.matrix.api.core.RoomId
* about the predecessor room.
*
* A room is tombstoned if it has received a m.room.tombstone state event.
*
*/
data class PredecessorRoom(
/**
* The ID of the replaced room.
*/
val roomId: RoomId,
/**
* The event ID of the last known event in the predecessor room.
*/
val lastEventId: EventId,
)

View file

@ -23,6 +23,9 @@ data class RoomDescription(
enum class JoinRule {
PUBLIC,
KNOCK,
RESTRICTED,
KNOCK_RESTRICTED,
INVITE,
UNKNOWN
}
}

View file

@ -19,7 +19,6 @@ import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.matrix.api.room.IntentionalMention
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
@ -76,7 +75,7 @@ interface Timeline : AutoCloseable {
): Result<Unit>
suspend fun replyMessage(
replyParameters: ReplyParameters,
repliedToEventId: EventId,
body: String,
htmlBody: String?,
intentionalMentions: List<IntentionalMention>,
@ -90,7 +89,7 @@ interface Timeline : AutoCloseable {
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
inReplyToEventId: EventId?,
): Result<MediaUploadHandler>
suspend fun sendVideo(
@ -100,7 +99,7 @@ interface Timeline : AutoCloseable {
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
inReplyToEventId: EventId?,
): Result<MediaUploadHandler>
suspend fun sendAudio(
@ -109,7 +108,7 @@ interface Timeline : AutoCloseable {
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
inReplyToEventId: EventId?,
): Result<MediaUploadHandler>
suspend fun sendFile(
@ -118,7 +117,7 @@ interface Timeline : AutoCloseable {
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
inReplyToEventId: EventId?,
): Result<MediaUploadHandler>
/**
@ -131,7 +130,7 @@ interface Timeline : AutoCloseable {
* @param zoomLevel Optional zoom level to display the map at.
* @param assetType Optional type of the location asset.
* Set to SENDER if sharing own location. Set to PIN if sharing any location.
* @param replyParameters Optional reply parameters to use when sending the location.
* @param inReplyToEventId Optional [EventId] for the event this message should reply to.
*/
suspend fun sendLocation(
body: String,
@ -139,7 +138,7 @@ interface Timeline : AutoCloseable {
description: String? = null,
zoomLevel: Int? = null,
assetType: AssetType? = null,
replyParameters: ReplyParameters?,
inReplyToEventId: EventId?,
): Result<Unit>
suspend fun sendVoiceMessage(
@ -147,7 +146,7 @@ interface Timeline : AutoCloseable {
audioInfo: AudioInfo,
waveform: List<Float>,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
inReplyToEventId: EventId?,
): Result<MediaUploadHandler>
suspend fun redactEvent(eventOrTransactionId: EventOrTransactionId, reason: String?): Result<Unit>