diff --git a/ElementX/Sources/Mocks/ClientProxyMock.swift b/ElementX/Sources/Mocks/ClientProxyMock.swift index b57060f6e..615ee090c 100644 --- a/ElementX/Sources/Mocks/ClientProxyMock.swift +++ b/ElementX/Sources/Mocks/ClientProxyMock.swift @@ -35,6 +35,7 @@ extension ClientProxyMock { roomSummaryProvider = configuration.roomSummaryProvider alternateRoomSummaryProvider = RoomSummaryProviderMock(.init()) + staticRoomSummaryProvider = RoomSummaryProviderMock(.init()) roomDirectorySearchProxyReturnValue = configuration.roomDirectorySearchProxy diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index b73c4b2ec..aa652948e 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -2268,12 +2268,13 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable { var underlyingIgnoredUsersPublisher: CurrentValuePublisher<[String]?, Never>! var pusherNotificationClientIdentifier: String? var roomSummaryProvider: RoomSummaryProviderProtocol? + var alternateRoomSummaryProvider: RoomSummaryProviderProtocol? + var staticRoomSummaryProvider: StaticRoomSummaryProviderProtocol? var roomsToAwait: Set { get { return underlyingRoomsToAwait } set(value) { underlyingRoomsToAwait = value } } var underlyingRoomsToAwait: Set! - var alternateRoomSummaryProvider: RoomSummaryProviderProtocol? var notificationSettings: NotificationSettingsProxyProtocol { get { return underlyingNotificationSettings } set(value) { underlyingNotificationSettings = value } @@ -13589,58 +13590,17 @@ class RoomProxyMock: RoomProxyProtocol, @unchecked Sendable { } class RoomSummaryProviderMock: RoomSummaryProviderProtocol, @unchecked Sendable { - var roomListPublisher: CurrentValuePublisher<[RoomSummary], Never> { - get { return underlyingRoomListPublisher } - set(value) { underlyingRoomListPublisher = value } - } - var underlyingRoomListPublisher: CurrentValuePublisher<[RoomSummary], Never>! var statePublisher: CurrentValuePublisher { get { return underlyingStatePublisher } set(value) { underlyingStatePublisher = value } } var underlyingStatePublisher: CurrentValuePublisher! - - //MARK: - setRoomList - - var setRoomListUnderlyingCallsCount = 0 - var setRoomListCallsCount: Int { - get { - if Thread.isMainThread { - return setRoomListUnderlyingCallsCount - } else { - var returnValue: Int? = nil - DispatchQueue.main.sync { - returnValue = setRoomListUnderlyingCallsCount - } - - return returnValue! - } - } - set { - if Thread.isMainThread { - setRoomListUnderlyingCallsCount = newValue - } else { - DispatchQueue.main.sync { - setRoomListUnderlyingCallsCount = newValue - } - } - } + var roomListPublisher: CurrentValuePublisher<[RoomSummary], Never> { + get { return underlyingRoomListPublisher } + set(value) { underlyingRoomListPublisher = value } } - var setRoomListCalled: Bool { - return setRoomListCallsCount > 0 - } - var setRoomListReceivedRoomList: RoomList? - var setRoomListReceivedInvocations: [RoomList] = [] - var setRoomListClosure: ((RoomList) -> Void)? + var underlyingRoomListPublisher: CurrentValuePublisher<[RoomSummary], Never>! - func setRoomList(_ roomList: RoomList) { - setRoomListCallsCount += 1 - setRoomListReceivedRoomList = roomList - DispatchQueue.main.async { - self.setRoomListReceivedInvocations.append(roomList) - } - setRoomListClosure?(roomList) - } //MARK: - updateVisibleRange var updateVisibleRangeUnderlyingCallsCount = 0 @@ -13723,6 +13683,47 @@ class RoomSummaryProviderMock: RoomSummaryProviderProtocol, @unchecked Sendable } setFilterClosure?(filter) } + //MARK: - setRoomList + + var setRoomListUnderlyingCallsCount = 0 + var setRoomListCallsCount: Int { + get { + if Thread.isMainThread { + return setRoomListUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = setRoomListUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + setRoomListUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + setRoomListUnderlyingCallsCount = newValue + } + } + } + } + var setRoomListCalled: Bool { + return setRoomListCallsCount > 0 + } + var setRoomListReceivedRoomList: RoomList? + var setRoomListReceivedInvocations: [RoomList] = [] + var setRoomListClosure: ((RoomList) -> Void)? + + func setRoomList(_ roomList: RoomList) { + setRoomListCallsCount += 1 + setRoomListReceivedRoomList = roomList + DispatchQueue.main.async { + self.setRoomListReceivedInvocations.append(roomList) + } + setRoomListClosure?(roomList) + } } class SecureBackupControllerMock: SecureBackupControllerProtocol, @unchecked Sendable { var recoveryState: CurrentValuePublisher { @@ -14595,6 +14596,55 @@ class SessionVerificationControllerProxyMock: SessionVerificationControllerProxy } } } +class StaticRoomSummaryProviderMock: StaticRoomSummaryProviderProtocol, @unchecked Sendable { + var roomListPublisher: CurrentValuePublisher<[RoomSummary], Never> { + get { return underlyingRoomListPublisher } + set(value) { underlyingRoomListPublisher = value } + } + var underlyingRoomListPublisher: CurrentValuePublisher<[RoomSummary], Never>! + + //MARK: - setRoomList + + var setRoomListUnderlyingCallsCount = 0 + var setRoomListCallsCount: Int { + get { + if Thread.isMainThread { + return setRoomListUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = setRoomListUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + setRoomListUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + setRoomListUnderlyingCallsCount = newValue + } + } + } + } + var setRoomListCalled: Bool { + return setRoomListCallsCount > 0 + } + var setRoomListReceivedRoomList: RoomList? + var setRoomListReceivedInvocations: [RoomList] = [] + var setRoomListClosure: ((RoomList) -> Void)? + + func setRoomList(_ roomList: RoomList) { + setRoomListCallsCount += 1 + setRoomListReceivedRoomList = roomList + DispatchQueue.main.async { + self.setRoomListReceivedInvocations.append(roomList) + } + setRoomListClosure?(roomList) + } +} class TimelineControllerFactoryMock: TimelineControllerFactoryProtocol, @unchecked Sendable { //MARK: - buildTimelineController diff --git a/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenCoordinator.swift b/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenCoordinator.swift index 6c0916a0f..59817c342 100644 --- a/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenCoordinator.swift +++ b/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenCoordinator.swift @@ -51,4 +51,8 @@ class GlobalSearchScreenCoordinator: CoordinatorProtocol { func toPresentable() -> AnyView { AnyView(GlobalSearchScreen(context: viewModel.context)) } + + func stop() { + viewModel.stop() + } } diff --git a/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenViewModel.swift b/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenViewModel.swift index c3e7341fc..7479e75ab 100644 --- a/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenViewModel.swift +++ b/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenViewModel.swift @@ -47,6 +47,11 @@ class GlobalSearchScreenViewModel: GlobalSearchScreenViewModelType, GlobalSearch updateRooms(with: roomSummaryProvider.roomListPublisher.value) } + func stop() { + // This is a shared provider so we should reset the filtering when we are done with the view + roomSummaryProvider.setFilter(.all(filters: [])) + } + // MARK: - Public override func process(viewAction: GlobalSearchScreenViewAction) { @@ -55,7 +60,6 @@ class GlobalSearchScreenViewModel: GlobalSearchScreenViewModelType, GlobalSearch switch viewAction { case .dismiss: actionsSubject.send(.dismiss) - roomSummaryProvider.setFilter(.all(filters: [])) // This is a shared provider case .select(let roomID): actionsSubject.send(.select(roomID: roomID)) case .reachedTop: diff --git a/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenViewModelProtocol.swift b/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenViewModelProtocol.swift index 8190dcf3d..e919256fd 100644 --- a/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenViewModelProtocol.swift +++ b/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenViewModelProtocol.swift @@ -11,4 +11,6 @@ import Combine protocol GlobalSearchScreenViewModelProtocol { var actions: AnyPublisher { get } var context: GlobalSearchScreenViewModelType.Context { get } + + func stop() } diff --git a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift index ba6f1ad5a..fb83838d5 100644 --- a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift @@ -140,7 +140,7 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo roomInfo = invitedRoomProxy.info case .knocked(let knockedRoomProxy): roomInfo = knockedRoomProxy.info - if let roomSummaryProvider = clientProxy.alternateRoomSummaryProvider { + if let roomSummaryProvider = clientProxy.staticRoomSummaryProvider { membershipStateChangeCancellable = roomSummaryProvider.roomListPublisher .compactMap { summaries -> Void? in guard let roomSummary = summaries.first(where: { $0.id == roomInfo?.id }), diff --git a/ElementX/Sources/Screens/MessageForwardingScreen/MessageForwardingScreenCoordinator.swift b/ElementX/Sources/Screens/MessageForwardingScreen/MessageForwardingScreenCoordinator.swift index 47c6165d2..055a1fa84 100644 --- a/ElementX/Sources/Screens/MessageForwardingScreen/MessageForwardingScreenCoordinator.swift +++ b/ElementX/Sources/Screens/MessageForwardingScreen/MessageForwardingScreenCoordinator.swift @@ -53,4 +53,8 @@ final class MessageForwardingScreenCoordinator: CoordinatorProtocol { func toPresentable() -> AnyView { AnyView(MessageForwardingScreen(context: viewModel.context)) } + + func stop() { + viewModel.stop() + } } diff --git a/ElementX/Sources/Screens/MessageForwardingScreen/MessageForwardingScreenViewModel.swift b/ElementX/Sources/Screens/MessageForwardingScreen/MessageForwardingScreenViewModel.swift index e07c4c650..f7e62d420 100644 --- a/ElementX/Sources/Screens/MessageForwardingScreen/MessageForwardingScreenViewModel.swift +++ b/ElementX/Sources/Screens/MessageForwardingScreen/MessageForwardingScreenViewModel.swift @@ -60,7 +60,6 @@ class MessageForwardingScreenViewModel: MessageForwardingScreenViewModelType, Me switch viewAction { case .cancel: actionsSubject.send(.dismiss) - roomSummaryProvider.setFilter(.all(filters: [])) case .send: Task { await forward() } case .selectRoom(let roomID): @@ -72,6 +71,11 @@ class MessageForwardingScreenViewModel: MessageForwardingScreenViewModelType, Me } } + func stop() { + // This is a shared provider so we should reset the filtering when we are done with the view + roomSummaryProvider.setFilter(.all(filters: [])) + } + // MARK: - Private private func updateRooms() { diff --git a/ElementX/Sources/Screens/MessageForwardingScreen/MessageForwardingScreenViewModelProtocol.swift b/ElementX/Sources/Screens/MessageForwardingScreen/MessageForwardingScreenViewModelProtocol.swift index ece932a83..111a61343 100644 --- a/ElementX/Sources/Screens/MessageForwardingScreen/MessageForwardingScreenViewModelProtocol.swift +++ b/ElementX/Sources/Screens/MessageForwardingScreen/MessageForwardingScreenViewModelProtocol.swift @@ -11,4 +11,6 @@ import Combine protocol MessageForwardingScreenViewModelProtocol { var actions: AnyPublisher { get } var context: MessageForwardingScreenViewModelType.Context { get } + + func stop() } diff --git a/ElementX/Sources/Screens/RoomSelectionScreen/RoomSelectionScreenCoordinator.swift b/ElementX/Sources/Screens/RoomSelectionScreen/RoomSelectionScreenCoordinator.swift index 8863b32fd..41631bbcf 100644 --- a/ElementX/Sources/Screens/RoomSelectionScreen/RoomSelectionScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomSelectionScreen/RoomSelectionScreenCoordinator.swift @@ -45,6 +45,10 @@ final class RoomSelectionScreenCoordinator: CoordinatorProtocol { } .store(in: &cancellables) } + + func stop() { + viewModel.stop() + } func toPresentable() -> AnyView { AnyView(RoomSelectionScreen(context: viewModel.context)) diff --git a/ElementX/Sources/Screens/RoomSelectionScreen/RoomSelectionScreenViewModel.swift b/ElementX/Sources/Screens/RoomSelectionScreen/RoomSelectionScreenViewModel.swift index 8eba1b878..19fa388ed 100644 --- a/ElementX/Sources/Screens/RoomSelectionScreen/RoomSelectionScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomSelectionScreen/RoomSelectionScreenViewModel.swift @@ -54,7 +54,6 @@ class RoomSelectionScreenViewModel: RoomSelectionScreenViewModelType, RoomSelect switch viewAction { case .cancel: actionsSubject.send(.dismiss) - roomSummaryProvider.setFilter(.all(filters: [])) case .confirm: guard let selectedRoomID = state.selectedRoomID else { return @@ -70,6 +69,11 @@ class RoomSelectionScreenViewModel: RoomSelectionScreenViewModelType, RoomSelect } } + func stop() { + // This is a shared provider so we should reset the filtering when we are done with the view + roomSummaryProvider.setFilter(.all(filters: [])) + } + // MARK: - Private private func updateRooms() { diff --git a/ElementX/Sources/Screens/RoomSelectionScreen/RoomSelectionScreenViewModelProtocol.swift b/ElementX/Sources/Screens/RoomSelectionScreen/RoomSelectionScreenViewModelProtocol.swift index 8bc9bb421..da868990a 100644 --- a/ElementX/Sources/Screens/RoomSelectionScreen/RoomSelectionScreenViewModelProtocol.swift +++ b/ElementX/Sources/Screens/RoomSelectionScreen/RoomSelectionScreenViewModelProtocol.swift @@ -11,4 +11,6 @@ import Combine protocol RoomSelectionScreenViewModelProtocol { var actionsPublisher: AnyPublisher { get } var context: RoomSelectionScreenViewModelType.Context { get } + + func stop() } diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index c54019ef1..d702e4777 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -46,6 +46,8 @@ class ClientProxy: ClientProxyProtocol { private(set) var roomSummaryProvider: RoomSummaryProviderProtocol? private(set) var alternateRoomSummaryProvider: RoomSummaryProviderProtocol? + private(set) var staticRoomSummaryProvider: StaticRoomSummaryProviderProtocol? + let notificationSettings: NotificationSettingsProxyProtocol let secureBackupController: SecureBackupControllerProtocol @@ -523,14 +525,32 @@ class ClientProxy: ClientProxyProtocol { func roomSummaryForIdentifier(_ identifier: String) -> RoomSummary? { // the alternate room summary provider is not impacted by filtering - alternateRoomSummaryProvider?.roomListPublisher.value.first { $0.id == identifier } + guard let provider = staticRoomSummaryProvider else { + MXLog.verbose("Missing room summary provider") + return nil + } + + guard let roomSummary = provider.roomListPublisher.value.first(where: { $0.id == identifier }) else { + MXLog.verbose("Missing room summary, count: \(provider.roomListPublisher.value.count)") + return nil + } + + return roomSummary } func roomSummaryForAlias(_ alias: String) -> RoomSummary? { // the alternate room summary provider is not impacted by filtering - alternateRoomSummaryProvider?.roomListPublisher.value.first { roomSummary in - roomSummary.canonicalAlias == alias || roomSummary.alternativeAliases.contains(alias) + guard let provider = staticRoomSummaryProvider else { + MXLog.verbose("Missing room summary provider") + return nil } + + guard let roomSummary = provider.roomListPublisher.value.first(where: { $0.canonicalAlias == alias || $0.alternativeAliases.contains(alias) }) else { + MXLog.verbose("Missing room summary, count: \(provider.roomListPublisher.value.count)") + return nil + } + + return roomSummary } func loadUserDisplayName() async -> Result { @@ -846,6 +866,14 @@ class ClientProxy: ClientProxyProtocol { notificationSettings: notificationSettings, appSettings: appSettings) try await alternateRoomSummaryProvider?.setRoomList(roomListService.allRooms()) + + staticRoomSummaryProvider = RoomSummaryProvider(roomListService: roomListService, + eventStringBuilder: eventStringBuilder, + name: "StaticAllRooms", + roomListPageSize: .max, + notificationSettings: notificationSettings, + appSettings: appSettings) + try await staticRoomSummaryProvider?.setRoomList(roomListService.allRooms()) self.syncService = syncService self.roomListService = roomListService diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index ff690dc8b..a4492ee3b 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -96,11 +96,16 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { var roomSummaryProvider: RoomSummaryProviderProtocol? { get } - var roomsToAwait: Set { get set } - /// Used for listing rooms that shouldn't be affected by the main `roomSummaryProvider` filtering + /// But can still be filtered by queries, since this may be shared across multiple views, remember to reset + /// The filtering state when you are done with it var alternateRoomSummaryProvider: RoomSummaryProviderProtocol? { get } + /// Used for listing rooms, can't be filtered nor its state observed + var staticRoomSummaryProvider: StaticRoomSummaryProviderProtocol? { get } + + var roomsToAwait: Set { get set } + var notificationSettings: NotificationSettingsProxyProtocol { get } var secureBackupController: SecureBackupControllerProtocol { get } diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift index d56f4b1d5..14e949ca9 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift @@ -17,7 +17,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { private let notificationSettings: NotificationSettingsProxyProtocol private let appSettings: AppSettings - private let roomListPageSize = 200 + private let roomListPageSize: UInt32 private let serialDispatchQueue: DispatchQueue @@ -59,6 +59,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { eventStringBuilder: RoomEventStringBuilder, name: String, shouldUpdateVisibleRange: Bool = false, + roomListPageSize: UInt32 = 200, notificationSettings: NotificationSettingsProxyProtocol, appSettings: AppSettings) { self.roomListService = roomListService @@ -68,6 +69,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { self.shouldUpdateVisibleRange = shouldUpdateVisibleRange self.notificationSettings = notificationSettings self.appSettings = appSettings + self.roomListPageSize = roomListPageSize diffsPublisher .receive(on: serialDispatchQueue) diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift index 3bf2a521b..219eb37fa 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift @@ -42,15 +42,18 @@ enum RoomSummaryProviderFilter: Equatable { } // sourcery: AutoMockable -protocol RoomSummaryProviderProtocol { +protocol StaticRoomSummaryProviderProtocol { /// Publishes the currently available room summaries var roomListPublisher: CurrentValuePublisher<[RoomSummary], Never> { get } + func setRoomList(_ roomList: RoomList) +} + +// sourcery: AutoMockable +protocol RoomSummaryProviderProtocol: StaticRoomSummaryProviderProtocol { /// Publishes the current state the summary provider is finding itself in var statePublisher: CurrentValuePublisher { get } - - func setRoomList(_ roomList: RoomList) - + func updateVisibleRange(_ range: Range) func setFilter(_ filter: RoomSummaryProviderFilter)