mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Adopt room info power levels (#4245)
* Use the newly RoomInfo published PowerLevels; update permissions at the same time as the room info and avoid unnecessary async calls. * Introduce a RoomInfoProxyProtocol and associated mock and move away from mocked SDK RoomInfos * Bump the SDK to v25.06.23 * Expose new non-throwing methods for checking one's own user permissions * Fix the unit and preview tests (except the TimelineMediaPreviewDetails for which simplified snapshots were generated) * Converge on one single implementation for computing a room's avatar. * Fix more unit tests * Fix the TimelineMediaPreviewDetailsView snapshot tests This reverts commit 9b6c819e611ad2fa3de2c34a4a7aa556fc9faadd. --------- Co-authored-by: Doug <douglase@element.io>
This commit is contained in:
@@ -46,8 +46,8 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
|
||||
|
||||
let timeline: TimelineProxyProtocol
|
||||
|
||||
private let infoSubject: CurrentValueSubject<RoomInfoProxy, Never>
|
||||
var infoPublisher: CurrentValuePublisher<RoomInfoProxy, Never> {
|
||||
private let infoSubject: CurrentValueSubject<RoomInfoProxyProtocol, Never>
|
||||
var infoPublisher: CurrentValuePublisher<RoomInfoProxyProtocol, Never> {
|
||||
infoSubject.asCurrentValuePublisher()
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
|
||||
|
||||
roomInfoObservationToken = room.subscribeToRoomInfoUpdates(listener: SDKListener { [weak self] roomInfo in
|
||||
MXLog.info("Received room info update")
|
||||
self?.infoSubject.send(.init(roomInfo: roomInfo))
|
||||
self?.infoSubject.send(RoomInfoProxy(roomInfo: roomInfo))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,20 +8,7 @@
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
protocol BaseRoomInfoProxyProtocol {
|
||||
var id: String { get }
|
||||
var displayName: String? { get }
|
||||
var avatar: RoomAvatar { get }
|
||||
var topic: String? { get }
|
||||
var canonicalAlias: String? { get }
|
||||
var avatarURL: URL? { get }
|
||||
var activeMembersCount: Int { get }
|
||||
var joinedMembersCount: Int { get }
|
||||
var isDirect: Bool { get }
|
||||
var isSpace: Bool { get }
|
||||
}
|
||||
|
||||
struct RoomInfoProxy: BaseRoomInfoProxyProtocol {
|
||||
struct RoomInfoProxy: RoomInfoProxyProtocol {
|
||||
let roomInfo: RoomInfo
|
||||
|
||||
var id: String { roomInfo.id }
|
||||
@@ -31,18 +18,6 @@ struct RoomInfoProxy: BaseRoomInfoProxyProtocol {
|
||||
var topic: String? { roomInfo.topic }
|
||||
/// The room's avatar URL. Use this for editing and favour ``avatar`` for display.
|
||||
var avatarURL: URL? { roomInfo.avatarUrl.flatMap(URL.init) }
|
||||
/// The room's avatar info for use in a ``RoomAvatarImage``.
|
||||
var avatar: RoomAvatar {
|
||||
guard successor == nil else {
|
||||
return .tombstoned
|
||||
}
|
||||
|
||||
if isDirect, avatarURL == nil, heroes.count == 1 {
|
||||
return .heroes(heroes.map(UserProfileProxy.init))
|
||||
}
|
||||
|
||||
return .room(id: id, name: displayName, avatarURL: avatarURL)
|
||||
}
|
||||
|
||||
// Here we're assuming unknown rooms are unencrypted.
|
||||
// Fortunately https://github.com/matrix-org/matrix-rust-sdk/pull/4778 makes that very much of an edge case and we
|
||||
@@ -54,28 +29,17 @@ struct RoomInfoProxy: BaseRoomInfoProxyProtocol {
|
||||
var isDirect: Bool { roomInfo.isDirect }
|
||||
var isPublic: Bool { roomInfo.isPublic }
|
||||
|
||||
// A room might be non public but also not private given the fact that the join rule might be missing or unsupported.
|
||||
var isPrivate: Bool {
|
||||
switch roomInfo.joinRule {
|
||||
case .invite, .knock, .restricted, .knockRestricted:
|
||||
true
|
||||
default:
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
var isSpace: Bool { roomInfo.isSpace }
|
||||
var successor: SuccessorRoom? { roomInfo.successorRoom }
|
||||
var isFavourite: Bool { roomInfo.isFavourite }
|
||||
var canonicalAlias: String? { roomInfo.canonicalAlias }
|
||||
var alternativeAliases: [String] { roomInfo.alternativeAliases }
|
||||
var membership: Membership { roomInfo.membership }
|
||||
var inviter: RoomMemberProxy? { roomInfo.inviter.map(RoomMemberProxy.init) }
|
||||
var inviter: RoomMemberProxyProtocol? { roomInfo.inviter.map(RoomMemberProxy.init) }
|
||||
var heroes: [RoomHero] { roomInfo.heroes }
|
||||
var activeMembersCount: Int { Int(roomInfo.activeMembersCount) }
|
||||
var invitedMembersCount: Int { Int(roomInfo.invitedMembersCount) }
|
||||
var joinedMembersCount: Int { Int(roomInfo.joinedMembersCount) }
|
||||
var userPowerLevels: [String: Int] { roomInfo.userPowerLevels.mapValues(Int.init) }
|
||||
var highlightCount: Int { Int(roomInfo.highlightCount) }
|
||||
var notificationCount: Int { Int(roomInfo.notificationCount) }
|
||||
var cachedUserDefinedNotificationMode: RoomNotificationMode? { roomInfo.cachedUserDefinedNotificationMode }
|
||||
@@ -89,41 +53,14 @@ struct RoomInfoProxy: BaseRoomInfoProxyProtocol {
|
||||
var joinRule: JoinRule? { roomInfo.joinRule }
|
||||
var historyVisibility: RoomHistoryVisibility { roomInfo.historyVisibility }
|
||||
|
||||
/// Find the first alias that matches the given homeserver
|
||||
/// - Parameters:
|
||||
/// - serverName: the homserver in question
|
||||
/// - useFallback: whether to return any alias if none match
|
||||
func firstAliasMatching(serverName: String?, useFallback: Bool) -> String? {
|
||||
guard let serverName else { return nil }
|
||||
|
||||
// Check if the canonical alias matches the homeserver
|
||||
if let canonicalAlias = roomInfo.canonicalAlias,
|
||||
canonicalAlias.range(of: serverName) != nil {
|
||||
return canonicalAlias
|
||||
}
|
||||
|
||||
// Otherwise check the alternative aliases and return the first one that matches
|
||||
if let matchingAlternativeAlias = roomInfo.alternativeAliases.filter({ $0.range(of: serverName) != nil }).first {
|
||||
return matchingAlternativeAlias
|
||||
}
|
||||
|
||||
guard useFallback else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Or just return the canonical alias if any
|
||||
if let canonicalAlias = roomInfo.canonicalAlias {
|
||||
return canonicalAlias
|
||||
}
|
||||
|
||||
// And finally return whatever the first alternative alias is
|
||||
return roomInfo.alternativeAliases.first
|
||||
}
|
||||
var powerLevels: RoomPowerLevelsProxyProtocol { RoomPowerLevelsProxy(roomInfo.powerLevels) }
|
||||
}
|
||||
|
||||
struct RoomPreviewInfoProxy: BaseRoomInfoProxyProtocol {
|
||||
let roomPreviewInfo: RoomPreviewInfo
|
||||
|
||||
let successor: SuccessorRoom? = nil
|
||||
|
||||
var id: String { roomPreviewInfo.roomId }
|
||||
var displayName: String? { roomPreviewInfo.name }
|
||||
var heroes: [RoomHero] { roomPreviewInfo.heroes ?? [] }
|
||||
@@ -137,12 +74,4 @@ struct RoomPreviewInfoProxy: BaseRoomInfoProxyProtocol {
|
||||
|
||||
var joinRule: JoinRule { roomPreviewInfo.joinRule }
|
||||
var membership: Membership? { roomPreviewInfo.membership }
|
||||
|
||||
/// The room's avatar info for use in a ``RoomAvatarImage``.
|
||||
var avatar: RoomAvatar {
|
||||
if isDirect, avatarURL == nil, heroes.count == 1 {
|
||||
return .heroes(heroes.map(UserProfileProxy.init))
|
||||
}
|
||||
return .room(id: id, name: displayName, avatarURL: avatarURL)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
protocol BaseRoomInfoProxyProtocol {
|
||||
var id: String { get }
|
||||
var displayName: String? { get }
|
||||
var topic: String? { get }
|
||||
var canonicalAlias: String? { get }
|
||||
var avatarURL: URL? { get }
|
||||
var activeMembersCount: Int { get }
|
||||
var joinedMembersCount: Int { get }
|
||||
var isDirect: Bool { get }
|
||||
var isSpace: Bool { get }
|
||||
|
||||
var successor: SuccessorRoom? { get }
|
||||
var heroes: [RoomHero] { get }
|
||||
}
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol RoomInfoProxyProtocol: BaseRoomInfoProxyProtocol {
|
||||
var id: String { get }
|
||||
var creator: String? { get }
|
||||
var displayName: String? { get }
|
||||
var rawName: String? { get }
|
||||
var topic: String? { get }
|
||||
/// The room's avatar URL. Use this for editing and favour ``avatar`` for display.
|
||||
var avatarURL: URL? { get }
|
||||
|
||||
var isEncrypted: Bool { get }
|
||||
var isDirect: Bool { get }
|
||||
var isPublic: Bool { get }
|
||||
|
||||
var isPrivate: Bool { get }
|
||||
|
||||
var isSpace: Bool { get }
|
||||
|
||||
var isFavourite: Bool { get }
|
||||
var canonicalAlias: String? { get }
|
||||
var alternativeAliases: [String] { get }
|
||||
var membership: Membership { get }
|
||||
var inviter: RoomMemberProxyProtocol? { get }
|
||||
|
||||
var activeMembersCount: Int { get }
|
||||
var invitedMembersCount: Int { get }
|
||||
var joinedMembersCount: Int { get }
|
||||
var highlightCount: Int { get }
|
||||
var notificationCount: Int { get }
|
||||
var cachedUserDefinedNotificationMode: RoomNotificationMode? { get }
|
||||
var hasRoomCall: Bool { get }
|
||||
var activeRoomCallParticipants: [String] { get }
|
||||
var isMarkedUnread: Bool { get }
|
||||
var unreadMessagesCount: UInt { get }
|
||||
var unreadNotificationsCount: UInt { get }
|
||||
var unreadMentionsCount: UInt { get }
|
||||
var pinnedEventIDs: Set<String> { get }
|
||||
var joinRule: JoinRule? { get }
|
||||
var historyVisibility: RoomHistoryVisibility { get }
|
||||
|
||||
var powerLevels: RoomPowerLevelsProxyProtocol { get }
|
||||
}
|
||||
|
||||
extension BaseRoomInfoProxyProtocol {
|
||||
/// The room's avatar info for use in a ``RoomAvatarImage``.
|
||||
var avatar: RoomAvatar {
|
||||
guard successor == nil else {
|
||||
return .tombstoned
|
||||
}
|
||||
|
||||
if isDirect, avatarURL == nil, heroes.count == 1 {
|
||||
return .heroes(heroes.map(UserProfileProxy.init))
|
||||
}
|
||||
|
||||
return .room(id: id, name: displayName, avatarURL: avatarURL)
|
||||
}
|
||||
}
|
||||
|
||||
extension RoomInfoProxyProtocol {
|
||||
/// A room might be non public but also not private given the fact that the join rule might be missing or unsupported.
|
||||
var isPrivate: Bool {
|
||||
switch joinRule {
|
||||
case .invite, .knock, .restricted, .knockRestricted:
|
||||
true
|
||||
default:
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the other person left the room in a direct chat
|
||||
var isUserAloneInDirectRoom: Bool {
|
||||
isDirect && activeMembersCount == 1
|
||||
}
|
||||
|
||||
/// Find the first alias that matches the given homeserver
|
||||
/// - Parameters:
|
||||
/// - serverName: the homserver in question
|
||||
/// - useFallback: whether to return any alias if none match
|
||||
func firstAliasMatching(serverName: String?, useFallback: Bool) -> String? {
|
||||
guard let serverName else { return nil }
|
||||
|
||||
// Check if the canonical alias matches the homeserver
|
||||
if let canonicalAlias,
|
||||
canonicalAlias.range(of: serverName) != nil {
|
||||
return canonicalAlias
|
||||
}
|
||||
|
||||
// Otherwise check the alternative aliases and return the first one that matches
|
||||
if let matchingAlternativeAlias = alternativeAliases.filter({ $0.range(of: serverName) != nil }).first {
|
||||
return matchingAlternativeAlias
|
||||
}
|
||||
|
||||
guard useFallback else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Or just return the canonical alias if any
|
||||
if let canonicalAlias {
|
||||
return canonicalAlias
|
||||
}
|
||||
|
||||
// And finally return whatever the first alternative alias is
|
||||
return alternativeAliases.first
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,17 @@ import MatrixRustSDK
|
||||
protocol RoomPowerLevelsProxyProtocol {
|
||||
var values: RoomPowerLevelsValues { get }
|
||||
|
||||
func canOwnUser(sendMessage messageType: MessageLikeEventType) -> Bool
|
||||
func canOwnUser(sendStateEvent event: StateEventType) -> Bool
|
||||
func canOwnUserInvite() -> Bool
|
||||
func canOwnUserRedactOther() -> Bool
|
||||
func canOwnUserRedactOwn() -> Bool
|
||||
func canOwnUserKick() -> Bool
|
||||
func canOwnUserBan() -> Bool
|
||||
func canOwnUserTriggerRoomNotification() -> Bool
|
||||
func canOwnUserPinOrUnpin() -> Bool
|
||||
func canOwnUserJoinCall() -> Bool
|
||||
|
||||
func canUser(userID: String, sendMessage messageType: MessageLikeEventType) -> Result<Bool, RoomProxyError>
|
||||
func canUser(userID: String, sendStateEvent event: StateEventType) -> Result<Bool, RoomProxyError>
|
||||
func canUserInvite(userID: String) -> Result<Bool, RoomProxyError>
|
||||
|
||||
@@ -18,6 +18,46 @@ struct RoomPowerLevelsProxy: RoomPowerLevelsProxyProtocol {
|
||||
powerLevels.values()
|
||||
}
|
||||
|
||||
func canOwnUser(sendMessage messageType: MessageLikeEventType) -> Bool {
|
||||
powerLevels.canOwnUserSendMessage(message: messageType)
|
||||
}
|
||||
|
||||
func canOwnUser(sendStateEvent stateEventType: StateEventType) -> Bool {
|
||||
powerLevels.canOwnUserSendState(stateEvent: stateEventType)
|
||||
}
|
||||
|
||||
func canOwnUserInvite() -> Bool {
|
||||
powerLevels.canOwnUserInvite()
|
||||
}
|
||||
|
||||
func canOwnUserRedactOther() -> Bool {
|
||||
powerLevels.canOwnUserRedactOther()
|
||||
}
|
||||
|
||||
func canOwnUserRedactOwn() -> Bool {
|
||||
powerLevels.canOwnUserRedactOwn()
|
||||
}
|
||||
|
||||
func canOwnUserKick() -> Bool {
|
||||
powerLevels.canOwnUserKick()
|
||||
}
|
||||
|
||||
func canOwnUserBan() -> Bool {
|
||||
powerLevels.canOwnUserBan()
|
||||
}
|
||||
|
||||
func canOwnUserTriggerRoomNotification() -> Bool {
|
||||
powerLevels.canOwnUserTriggerRoomNotification()
|
||||
}
|
||||
|
||||
func canOwnUserPinOrUnpin() -> Bool {
|
||||
powerLevels.canOwnUserPinUnpin()
|
||||
}
|
||||
|
||||
func canOwnUserJoinCall() -> Bool {
|
||||
powerLevels.canOwnUserSendState(stateEvent: .callMember)
|
||||
}
|
||||
|
||||
func canUser(userID: String, sendMessage messageType: MessageLikeEventType) -> Result<Bool, RoomProxyError> {
|
||||
do {
|
||||
return try .success(powerLevels.canUserSendMessage(userId: userID, message: messageType))
|
||||
|
||||
@@ -64,7 +64,7 @@ enum KnockRequestsState {
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
|
||||
var infoPublisher: CurrentValuePublisher<RoomInfoProxy, Never> { get }
|
||||
var infoPublisher: CurrentValuePublisher<RoomInfoProxyProtocol, Never> { get }
|
||||
|
||||
var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { get }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user