Replace individual RoomProxy properties with a stored RoomInfo. (#3445)

* Store RoomInfo updates in JoinedRoomProxy and read from them directly.

* Remove all RoomProxy properties that were reading from the RoomInfo.
This commit is contained in:
Doug
2024-10-28 12:29:31 +00:00
committed by GitHub
parent 7c28d9709f
commit 7c75498b4d
29 changed files with 353 additions and 580 deletions
@@ -696,7 +696,7 @@ class ClientProxy: ClientProxyProtocol {
for roomID in roomIdentifiers {
guard case let .joined(roomProxy) = await roomForIdentifier(roomID),
roomProxy.isDirect,
roomProxy.infoPublisher.value.isDirect,
let members = await roomProxy.members() else {
continue
}
@@ -869,15 +869,15 @@ class ClientProxy: ClientProxyProtocol {
switch roomListItem.membership() {
case .invited:
return try .invited(InvitedRoomProxy(roomListItem: roomListItem,
room: roomListItem.invitedRoom()))
return try await .invited(InvitedRoomProxy(roomListItem: roomListItem,
room: roomListItem.invitedRoom()))
case .knocked:
if appSettings.knockingEnabled {
return try .knocked(KnockedRoomProxy(roomListItem: roomListItem,
room: roomListItem.invitedRoom()))
return try await .knocked(KnockedRoomProxy(roomListItem: roomListItem,
room: roomListItem.invitedRoom()))
} else {
return try .invited(InvitedRoomProxy(roomListItem: roomListItem,
room: roomListItem.invitedRoom()))
return try await .invited(InvitedRoomProxy(roomListItem: roomListItem,
room: roomListItem.invitedRoom()))
}
case .joined:
if roomListItem.isTimelineInitialized() == false {
@@ -291,16 +291,11 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
// it from what we have. If the call is running before subscribing then wait
// for it to change to `false` otherwise wait for it to turn `true` before
// changing to `false`
let isCallOngoing = roomProxy.hasOngoingCall
let isCallOngoing = roomProxy.infoPublisher.value.hasRoomCall
roomProxy
.actionsPublisher
.compactMap { action -> (Bool, [String])? in
switch action {
case .roomInfoUpdate:
return (roomProxy.hasOngoingCall, roomProxy.activeRoomCallParticipants)
}
}
.infoPublisher
.compactMap { ($0.hasRoomCall, $0.activeRoomCallParticipants) }
.removeDuplicates { $0 == $1 }
.dropFirst(isCallOngoing ? 0 : 1)
.sink { [weak self] hasOngoingCall, activeRoomCallParticipants in
@@ -17,68 +17,15 @@ class InvitedRoomProxy: InvitedRoomProxyProtocol {
// multiple times over FFI
lazy var id: String = room.id()
var canonicalAlias: String? {
room.canonicalAlias()
}
var ownUserID: String { room.ownUserId() }
var ownUserID: String {
room.ownUserId()
}
var name: String? {
roomListItem.displayName()
}
var topic: String? {
room.topic()
}
var avatarURL: URL? {
roomListItem.avatarUrl().flatMap(URL.init(string:))
}
var avatar: RoomAvatar {
if isDirect, avatarURL == nil {
let heroes = room.heroes()
if heroes.count == 1 {
return .heroes(heroes.map(UserProfileProxy.init))
}
}
return .room(id: id, name: name, avatarURL: avatarURL)
}
var isDirect: Bool {
room.isDirect()
}
var isPublic: Bool {
room.isPublic()
}
var isSpace: Bool {
room.isSpace()
}
var joinedMembersCount: Int {
Int(room.joinedMembersCount())
}
var activeMembersCount: Int {
Int(room.activeMembersCount())
}
var inviter: RoomMemberProxyProtocol? {
get async {
await (try? roomListItem.roomInfo().inviter).map(RoomMemberProxy.init)
}
}
let info: RoomInfoProxy
init(roomListItem: RoomListItemProtocol,
room: RoomProtocol) {
room: RoomProtocol) async throws {
self.roomListItem = roomListItem
self.room = room
info = try await RoomInfoProxy(roomInfo: room.roomInfo())
}
func acceptInvitation() async -> Result<Void, RoomProxyError> {
@@ -62,6 +62,11 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
private var identityStatusChangesObservationToken: TaskHandle?
private var subscribedForUpdates = false
private let infoSubject: CurrentValueSubject<RoomInfoProxy, Never>
var infoPublisher: CurrentValuePublisher<RoomInfoProxy, Never> {
infoSubject.asCurrentValuePublisher()
}
private let membersSubject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> {
@@ -77,95 +82,17 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
var identityStatusChangesPublisher: CurrentValuePublisher<[IdentityStatusChange], Never> {
identityStatusChangesSubject.asCurrentValuePublisher()
}
private let actionsSubject = PassthroughSubject<JoinedRoomProxyAction, Never>()
var actionsPublisher: AnyPublisher<JoinedRoomProxyAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
// A room identifier is constant and lazy stops it from being fetched
// multiple times over FFI
lazy var id: String = room.id()
var canonicalAlias: String? {
room.canonicalAlias()
}
var ownUserID: String {
room.ownUserId()
}
var name: String? {
roomListItem.displayName()
}
var topic: String? {
room.topic()
}
var avatarURL: URL? {
roomListItem.avatarUrl().flatMap(URL.init(string:))
}
var avatar: RoomAvatar {
if isDirect, avatarURL == nil {
let heroes = room.heroes()
if heroes.count == 1 {
return .heroes(heroes.map(UserProfileProxy.init))
}
}
return .room(id: id, name: name, avatarURL: avatarURL)
}
var isDirect: Bool {
room.isDirect()
}
var isPublic: Bool {
room.isPublic()
}
var isSpace: Bool {
room.isSpace()
}
var joinedMembersCount: Int {
Int(room.joinedMembersCount())
}
var activeMembersCount: Int {
Int(room.activeMembersCount())
}
var ownUserID: String { room.ownUserId() }
var info: RoomInfoProxy { infoSubject.value }
var isEncrypted: Bool {
(try? room.isEncrypted()) ?? false
}
var isFavourite: Bool {
get async {
await (try? room.roomInfo().isFavourite) ?? false
}
}
var pinnedEventIDs: Set<String> {
get async {
guard let pinnedEventIDs = try? await room.roomInfo().pinnedEventIds else {
return []
}
return .init(pinnedEventIDs)
}
}
var hasOngoingCall: Bool {
room.hasActiveRoomCall()
}
var activeRoomCallParticipants: [String] {
room.activeRoomCallParticipants()
}
init(roomListService: RoomListServiceProtocol,
roomListItem: RoomListItemProtocol,
room: RoomProtocol) async throws {
@@ -173,6 +100,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
self.roomListItem = roomListItem
self.room = room
infoSubject = try await .init(RoomInfoProxy(roomInfo: room.roomInfo()))
timeline = try await TimelineProxy(timeline: room.timeline(), kind: .live)
Task {
@@ -210,9 +138,9 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
return
}
roomInfoObservationToken = room.subscribeToRoomInfoUpdates(listener: RoomInfoUpdateListener { [weak self] in
roomInfoObservationToken = room.subscribeToRoomInfoUpdates(listener: RoomInfoUpdateListener { [weak self] roomInfo in
MXLog.info("Received room info update")
self?.actionsSubject.send(.roomInfoUpdate)
self?.infoSubject.send(.init(roomInfo: roomInfo))
})
}
@@ -730,14 +658,14 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
}
private final class RoomInfoUpdateListener: RoomInfoListener {
private let onUpdateClosure: () -> Void
private let onUpdateClosure: (RoomInfo) -> Void
init(_ onUpdateClosure: @escaping () -> Void) {
init(_ onUpdateClosure: @escaping (RoomInfo) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func call(roomInfo: RoomInfo) {
onUpdateClosure()
onUpdateClosure(roomInfo)
}
}
@@ -17,62 +17,15 @@ class KnockedRoomProxy: KnockedRoomProxyProtocol {
// multiple times over FFI
lazy var id: String = room.id()
var canonicalAlias: String? {
room.canonicalAlias()
}
var ownUserID: String { room.ownUserId() }
var ownUserID: String {
room.ownUserId()
}
var name: String? {
roomListItem.displayName()
}
var topic: String? {
room.topic()
}
var avatarURL: URL? {
roomListItem.avatarUrl().flatMap(URL.init(string:))
}
var avatar: RoomAvatar {
if isDirect, avatarURL == nil {
let heroes = room.heroes()
if heroes.count == 1 {
return .heroes(heroes.map(UserProfileProxy.init))
}
}
return .room(id: id, name: name, avatarURL: avatarURL)
}
var isDirect: Bool {
room.isDirect()
}
var isPublic: Bool {
room.isPublic()
}
var isSpace: Bool {
room.isSpace()
}
var joinedMembersCount: Int {
Int(room.joinedMembersCount())
}
var activeMembersCount: Int {
Int(room.activeMembersCount())
}
let info: RoomInfoProxy
init(roomListItem: RoomListItemProtocol,
room: RoomProtocol) {
room: RoomProtocol) async throws {
self.roomListItem = roomListItem
self.room = room
info = try await RoomInfoProxy(roomInfo: room.roomInfo())
}
func cancelKnock() async -> Result<Void, RoomProxyError> {
@@ -0,0 +1,56 @@
//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import Foundation
import MatrixRustSDK
struct RoomInfoProxy {
let roomInfo: RoomInfo
var id: String { roomInfo.id }
var creator: String? { roomInfo.creator }
var displayName: String? { roomInfo.displayName }
var rawName: String? { roomInfo.rawName }
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 {
if isDirect, avatarURL == nil {
if heroes.count == 1 {
return .heroes(heroes.map(UserProfileProxy.init))
}
}
return .room(id: id, name: displayName, avatarURL: avatarURL)
}
var isDirect: Bool { roomInfo.isDirect }
var isPublic: Bool { roomInfo.isPublic }
var isSpace: Bool { roomInfo.isSpace }
var isTombstoned: Bool { roomInfo.isTombstoned }
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 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 }
var hasRoomCall: Bool { roomInfo.hasRoomCall }
var activeRoomCallParticipants: [String] { roomInfo.activeRoomCallParticipants }
var isMarkedUnread: Bool { roomInfo.isMarkedUnread }
var unreadMessagesCount: UInt { UInt(roomInfo.numUnreadMessages) }
var unreadNotificationsCount: UInt { UInt(roomInfo.numUnreadNotifications) }
var unreadMentionsCount: UInt { UInt(roomInfo.numUnreadMentions) }
var pinnedEventIDs: Set<String> { Set(roomInfo.pinnedEventIds) }
}
@@ -28,37 +28,19 @@ enum RoomProxyType {
// sourcery: AutoMockable
protocol RoomProxyProtocol {
var id: String { get }
var canonicalAlias: String? { get }
var ownUserID: String { get }
var name: String? { get }
var topic: String? { get }
/// The room's avatar info for use in a ``RoomAvatarImage``.
var avatar: RoomAvatar { get }
/// The room's avatar URL. Use this for editing and favour ``avatar`` for display.
var avatarURL: URL? { get }
var isPublic: Bool { get }
var isDirect: Bool { get }
var isSpace: Bool { get }
var joinedMembersCount: Int { get }
var activeMembersCount: Int { get }
}
// sourcery: AutoMockable
protocol InvitedRoomProxyProtocol: RoomProxyProtocol {
var inviter: RoomMemberProxyProtocol? { get async }
var info: RoomInfoProxy { get }
func rejectInvitation() async -> Result<Void, RoomProxyError>
func acceptInvitation() async -> Result<Void, RoomProxyError>
}
// sourcery: AutoMockable
protocol KnockedRoomProxyProtocol: RoomProxyProtocol {
var info: RoomInfoProxy { get }
func cancelKnock() async -> Result<Void, RoomProxyError>
}
@@ -69,11 +51,8 @@ enum JoinedRoomProxyAction: Equatable {
// sourcery: AutoMockable
protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
var isEncrypted: Bool { get }
var isFavourite: Bool { get async }
var pinnedEventIDs: Set<String> { get async }
var hasOngoingCall: Bool { get }
var activeRoomCallParticipants: [String] { get }
var infoPublisher: CurrentValuePublisher<RoomInfoProxy, Never> { get }
var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { get }
@@ -81,8 +60,6 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
var identityStatusChangesPublisher: CurrentValuePublisher<[IdentityStatusChange], Never> { get }
var actionsPublisher: AnyPublisher<JoinedRoomProxyAction, Never> { get }
var timeline: TimelineProxyProtocol { get }
var pinnedEventsTimeline: TimelineProxyProtocol? { get async }
@@ -176,21 +153,15 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
extension JoinedRoomProxyProtocol {
var details: RoomDetails {
RoomDetails(id: id,
name: name,
avatar: avatar,
canonicalAlias: canonicalAlias,
name: infoPublisher.value.displayName,
avatar: infoPublisher.value.avatar,
canonicalAlias: infoPublisher.value.canonicalAlias,
isEncrypted: isEncrypted,
isPublic: isPublic)
}
// Avoids to duplicate the same logic around in the app
// Probably this should be done in rust.
var roomTitle: String {
name ?? "Unknown room 💥"
isPublic: infoPublisher.value.isPublic)
}
var isEncryptedOneToOneRoom: Bool {
isDirect && isEncrypted && activeMembersCount <= 2
infoPublisher.value.isDirect && isEncrypted && infoPublisher.value.activeMembersCount <= 2
}
func members() async -> [RoomMemberProxyProtocol]? {
@@ -326,7 +326,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
switch paginationState.backward {
case .timelineEndReached:
if timelineKind != .pinned, !roomProxy.isEncryptedOneToOneRoom {
let timelineStart = TimelineStartRoomTimelineItem(name: roomProxy.name)
let timelineStart = TimelineStartRoomTimelineItem(name: roomProxy.infoPublisher.value.displayName)
newTimelineItems.insert(timelineStart, at: 0)
}
case .paginating: