Introduce a new RoomProxyType and treat rooms differently based on their membership state

This commit is contained in:
Stefan Ceriu
2024-08-20 16:13:27 +03:00
committed by Stefan Ceriu
parent e0ba9925e7
commit e281a3f242
97 changed files with 3755 additions and 3474 deletions
@@ -443,23 +443,13 @@ class ClientProxy: ClientProxyProtocol {
return result
}
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? {
guard let roomListService else {
MXLog.error("Failed retrieving room, room list service not set up")
return nil
}
func roomForIdentifier(_ identifier: String) async -> RoomProxyType? {
// Try fetching the room from the cold cache (if available) first
var (roomListItem, room) = await roomTupleForIdentifier(identifier)
if let roomListItem, let room {
return await RoomProxy(roomListService: roomListService,
roomListItem: roomListItem,
room: room)
if let room = await buildRoomForIdentifier(identifier) {
return room
}
// Else wait for the visible rooms list to go into fully loaded
guard let roomSummaryProvider else {
MXLog.error("Rooms summary provider not setup yet")
return nil
@@ -469,21 +459,7 @@ class ClientProxy: ClientProxyProtocol {
_ = await roomSummaryProvider.statePublisher.values.first(where: { $0.isLoaded })
}
(roomListItem, room) = await roomTupleForIdentifier(identifier)
guard let roomListItem else {
MXLog.error("Invalid roomListItem for identifier \(identifier)")
return nil
}
guard let room else {
MXLog.error("Invalid roomListItem fullRoom for identifier \(identifier)")
return nil
}
return await RoomProxy(roomListService: roomListService,
roomListItem: roomListItem,
room: room)
return await buildRoomForIdentifier(identifier)
}
func roomPreviewForIdentifier(_ identifier: String, via: [String]) async -> Result<RoomPreviewDetails, ClientProxyError> {
@@ -703,9 +679,9 @@ class ClientProxy: ClientProxyProtocol {
var users: OrderedSet<UserProfileProxy> = []
for roomID in roomIdentifiers {
guard let room = await roomForIdentifier(roomID),
room.isDirect,
let members = await room.members() else {
guard case let .joined(roomProxy) = await roomForIdentifier(roomID),
roomProxy.isDirect,
let members = await roomProxy.members() else {
continue
}
@@ -868,19 +844,46 @@ class ClientProxy: ClientProxyProtocol {
return .exclude(eventTypes: stateEventFilters.map { FilterTimelineEventType.state(eventType: $0) })
}()
private func roomTupleForIdentifier(_ identifier: String) async -> (RoomListItem?, Room?) {
do {
let roomListItem = try roomListService?.room(roomId: identifier)
if roomListItem?.isTimelineInitialized() == false {
try await roomListItem?.initTimeline(eventTypeFilter: eventFilters, internalIdPrefix: nil)
private func buildRoomForIdentifier(_ identifier: String) async -> RoomProxyType? {
guard let roomListService else {
MXLog.error("Failed retrieving room, room list service not set up")
return nil
}
guard let roomListItem = try? roomListService.room(roomId: identifier) else {
MXLog.error("Failed retrieving room, invalid room list item")
return nil
}
if roomListItem.isTimelineInitialized() == false {
do {
try await roomListItem.initTimeline(eventTypeFilter: eventFilters, internalIdPrefix: nil)
} catch {
MXLog.error("Failed initializing room timeline with error: \(error)")
return nil
}
}
guard let room = try? roomListItem.fullRoom() else {
MXLog.error("Failed retrieving room, invalid room list item full room")
return nil
}
switch room.membership() {
case .invited:
return .invited(InvitedRoomProxy(roomListItem: roomListItem,
room: room))
case .joined:
guard let roomProxy = await JoinedRoomProxy(roomListService: roomListService,
roomListItem: roomListItem,
room: room) else {
return nil
}
let fullRoom = try roomListItem?.fullRoom()
return (roomListItem, fullRoom)
} catch {
MXLog.error("Failed retrieving/initialising room with identifier: \(identifier)")
return (nil, nil)
return .joined(roomProxy)
case .left:
return .left
}
}
@@ -146,7 +146,7 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
func uploadMedia(_ media: MediaInfo) async -> Result<String, ClientProxyError>
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol?
func roomForIdentifier(_ identifier: String) async -> RoomProxyType?
func roomPreviewForIdentifier(_ identifier: String, via: [String]) async -> Result<RoomPreviewDetails, ClientProxyError>
@@ -19,11 +19,11 @@ import Foundation
import MatrixRustSDK
final class ComposerDraftService: ComposerDraftServiceProtocol {
private let roomProxy: RoomProxyProtocol
private let roomProxy: JoinedRoomProxyProtocol
private let timelineItemfactory: RoomTimelineItemFactoryProtocol
private var volatileDraft: ComposerDraftProxy?
init(roomProxy: RoomProxyProtocol, timelineItemfactory: RoomTimelineItemFactoryProtocol) {
init(roomProxy: JoinedRoomProxyProtocol, timelineItemfactory: RoomTimelineItemFactoryProtocol) {
self.roomProxy = roomProxy
self.timelineItemfactory = timelineItemfactory
}
@@ -25,7 +25,7 @@ private enum GenericCallLinkQueryParameters {
struct ElementCallConfiguration {
enum Kind {
case genericCallLink(URL)
case roomCall(roomProxy: RoomProxyProtocol,
case roomCall(roomProxy: JoinedRoomProxyProtocol,
clientProxy: ClientProxyProtocol,
clientID: String,
elementCallBaseURL: URL,
@@ -62,7 +62,7 @@ struct ElementCallConfiguration {
}
/// Creates a configuration for an internal room call.
init(roomProxy: RoomProxyProtocol,
init(roomProxy: JoinedRoomProxyProtocol,
clientProxy: ClientProxyProtocol,
clientID: String,
elementCallBaseURL: URL,
@@ -276,7 +276,7 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
return
}
guard let roomProxy = await clientProxy.roomForIdentifier(incomingCallID.roomID) else {
guard case let .joined(roomProxy) = await clientProxy.roomForIdentifier(incomingCallID.roomID) else {
return
}
@@ -18,9 +18,9 @@ import Foundation
class PollInteractionHandler: PollInteractionHandlerProtocol {
let analyticsService: AnalyticsService
let roomProxy: RoomProxyProtocol
let roomProxy: JoinedRoomProxyProtocol
init(analyticsService: AnalyticsService, roomProxy: RoomProxyProtocol) {
init(analyticsService: AnalyticsService, roomProxy: JoinedRoomProxyProtocol) {
self.analyticsService = analyticsService
self.roomProxy = roomProxy
}
@@ -0,0 +1,111 @@
//
// Copyright 2024 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import MatrixRustSDK
import UIKit
class InvitedRoomProxy: InvitedRoomProxyProtocol {
private let roomListItem: RoomListItemProtocol
private let room: RoomProtocol
// 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 inviter: RoomMemberProxyProtocol? {
get async {
await (try? roomListItem.roomInfo().inviter).map(RoomMemberProxy.init)
}
}
init(roomListItem: RoomListItemProtocol,
room: RoomProtocol) {
self.roomListItem = roomListItem
self.room = room
}
func acceptInvitation() async -> Result<Void, RoomProxyError> {
do {
try await room.join()
return .success(())
} catch {
MXLog.error("Failed accepting invitation with error: \(error)")
return .failure(.sdkError(error))
}
}
func rejectInvitation() async -> Result<Void, RoomProxyError> {
do {
return try await .success(room.leave())
} catch {
MXLog.error("Failed rejecting invitiation with error: \(error)")
return .failure(.sdkError(error))
}
}
}
@@ -19,7 +19,7 @@ import Foundation
import MatrixRustSDK
import UIKit
class RoomProxy: RoomProxyProtocol {
class JoinedRoomProxy: JoinedRoomProxyProtocol {
private let roomListService: RoomListServiceProtocol
private let roomListItem: RoomListItemProtocol
private let room: RoomProtocol
@@ -77,13 +77,19 @@ class RoomProxy: RoomProxyProtocol {
typingMembersSubject.asCurrentValuePublisher()
}
private let actionsSubject = PassthroughSubject<RoomProxyAction, Never>()
var actionsPublisher: AnyPublisher<RoomProxyAction, Never> {
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()
}
@@ -96,14 +102,20 @@ class RoomProxy: RoomProxyProtocol {
room.topic()
}
var membership: Membership {
room.membership()
var avatarURL: URL? {
roomListItem.avatarUrl().flatMap(URL.init(string:))
}
var inviter: RoomMemberProxyProtocol? {
get async {
await (try? roomListItem.roomInfo().inviter).map(RoomMemberProxy.init)
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 {
@@ -118,6 +130,14 @@ class RoomProxy: RoomProxyProtocol {
room.isSpace()
}
var joinedMembersCount: Int {
Int(room.joinedMembersCount())
}
var activeMembersCount: Int {
Int(room.activeMembersCount())
}
var isEncrypted: Bool {
(try? room.isEncrypted()) ?? false
}
@@ -145,34 +165,6 @@ class RoomProxy: RoomProxyProtocol {
room.activeRoomCallParticipants()
}
var canonicalAlias: String? {
room.canonicalAlias()
}
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 joinedMembersCount: Int {
Int(room.joinedMembersCount())
}
var activeMembersCount: Int {
Int(room.activeMembersCount())
}
init?(roomListService: RoomListServiceProtocol,
roomListItem: RoomListItemProtocol,
room: RoomProtocol) async {
@@ -315,25 +307,6 @@ class RoomProxy: RoomProxyProtocol {
}
}
func rejectInvitation() async -> Result<Void, RoomProxyError> {
do {
return try await .success(room.leave())
} catch {
MXLog.error("Failed rejecting invitiation with error: \(error)")
return .failure(.sdkError(error))
}
}
func acceptInvitation() async -> Result<Void, RoomProxyError> {
do {
try await room.join()
return .success(())
} catch {
MXLog.error("Failed accepting invitation with error: \(error)")
return .failure(.sdkError(error))
}
}
func invite(userID: String) async -> Result<Void, RoomProxyError> {
do {
MXLog.info("Inviting user \(userID)")
@@ -26,46 +26,62 @@ enum RoomProxyError: Error {
case eventNotFound
}
enum RoomProxyAction {
case roomInfoUpdate
enum RoomProxyType {
case joined(JoinedRoomProxyProtocol)
case invited(InvitedRoomProxyProtocol)
case left
}
// sourcery: AutoMockable
protocol RoomProxyProtocol {
var id: String { get }
var isDirect: Bool { get }
var isPublic: Bool { get }
var isSpace: Bool { get }
var isEncrypted: Bool { get }
var isFavourite: Bool { get async }
var pinnedEventIDs: Set<String> { get async }
var membership: Membership { get }
var inviter: RoomMemberProxyProtocol? { get async }
var hasOngoingCall: Bool { get }
var activeRoomCallParticipants: [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 }
func rejectInvitation() async -> Result<Void, RoomProxyError>
func acceptInvitation() async -> Result<Void, RoomProxyError>
}
enum JoinedRoomProxyAction {
case roomInfoUpdate
}
// 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 membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { get }
var typingMembersPublisher: CurrentValuePublisher<[String], Never> { get }
var joinedMembersCount: Int { get }
var activeMembersCount: Int { get }
var actionsPublisher: AnyPublisher<RoomProxyAction, Never> { get }
var actionsPublisher: AnyPublisher<JoinedRoomProxyAction, Never> { get }
var timeline: TimelineProxyProtocol { get }
@@ -87,10 +103,6 @@ protocol RoomProxyProtocol {
func getMember(userID: String) async -> Result<RoomMemberProxyProtocol, RoomProxyError>
func rejectInvitation() async -> Result<Void, RoomProxyError>
func acceptInvitation() async -> Result<Void, RoomProxyError>
func invite(userID: String) async -> Result<Void, RoomProxyError>
func setName(_ name: String) async -> Result<Void, RoomProxyError>
@@ -155,7 +167,7 @@ protocol RoomProxyProtocol {
func clearDraft() async -> Result<Void, RoomProxyError>
}
extension RoomProxyProtocol {
extension JoinedRoomProxyProtocol {
var details: RoomDetails {
RoomDetails(id: id,
name: name,
@@ -26,7 +26,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
/// An array of timeline items that will be appended in order when ``simulateIncomingItems()`` is called.
var incomingItems: [RoomTimelineItemProtocol] = []
var roomProxy: RoomProxyProtocol?
var roomProxy: JoinedRoomProxyProtocol?
var roomID: String { roomProxy?.id ?? "MockRoomIdentifier" }
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
@@ -20,7 +20,7 @@ import MatrixRustSDK
import UIKit
class RoomTimelineController: RoomTimelineControllerProtocol {
private let roomProxy: RoomProxyProtocol
private let roomProxy: JoinedRoomProxyProtocol
private let liveTimelineProvider: RoomTimelineProviderProtocol
private let timelineItemFactory: RoomTimelineItemFactoryProtocol
private let appSettings: AppSettings
@@ -41,7 +41,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
roomProxy.id
}
init(roomProxy: RoomProxyProtocol,
init(roomProxy: JoinedRoomProxyProtocol,
timelineProxy: TimelineProxyProtocol,
initialFocussedEventID: String?,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
@@ -17,7 +17,7 @@
import Foundation
struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
func buildRoomTimelineController(roomProxy: RoomProxyProtocol,
func buildRoomTimelineController(roomProxy: JoinedRoomProxyProtocol,
initialFocussedEventID: String?,
timelineItemFactory: RoomTimelineItemFactoryProtocol) -> RoomTimelineControllerProtocol {
RoomTimelineController(roomProxy: roomProxy,
@@ -27,7 +27,7 @@ struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
appSettings: ServiceLocator.shared.settings)
}
func buildRoomPinnedTimelineController(roomProxy: RoomProxyProtocol,
func buildRoomPinnedTimelineController(roomProxy: JoinedRoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol) async -> RoomTimelineControllerProtocol? {
guard let pinnedEventsTimeline = await roomProxy.pinnedEventsTimeline else {
return nil
@@ -18,10 +18,10 @@ import Foundation
@MainActor
protocol RoomTimelineControllerFactoryProtocol {
func buildRoomTimelineController(roomProxy: RoomProxyProtocol,
func buildRoomTimelineController(roomProxy: JoinedRoomProxyProtocol,
initialFocussedEventID: String?,
timelineItemFactory: RoomTimelineItemFactoryProtocol) -> RoomTimelineControllerProtocol
func buildRoomPinnedTimelineController(roomProxy: RoomProxyProtocol,
func buildRoomPinnedTimelineController(roomProxy: JoinedRoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol) async -> RoomTimelineControllerProtocol?
}
@@ -154,7 +154,7 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
return .success(waveformData)
}
func sendVoiceMessage(inRoom roomProxy: RoomProxyProtocol, audioConverter: AudioConverterProtocol) async -> Result<Void, VoiceMessageRecorderError> {
func sendVoiceMessage(inRoom roomProxy: JoinedRoomProxyProtocol, audioConverter: AudioConverterProtocol) async -> Result<Void, VoiceMessageRecorderError> {
guard let url = audioRecorder.audioFileURL else {
return .failure(VoiceMessageRecorderError.missingRecordingFile)
}
@@ -47,7 +47,7 @@ protocol VoiceMessageRecorderProtocol {
func seekPlayback(to progress: Double) async
func deleteRecording() async
func sendVoiceMessage(inRoom roomProxy: RoomProxyProtocol, audioConverter: AudioConverterProtocol) async -> Result<Void, VoiceMessageRecorderError>
func sendVoiceMessage(inRoom roomProxy: JoinedRoomProxyProtocol, audioConverter: AudioConverterProtocol) async -> Result<Void, VoiceMessageRecorderError>
}
// sourcery: AutoMockable