diff --git a/.swiftlint.yml b/.swiftlint.yml index a9bc271df..ec611b419 100755 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -3,7 +3,6 @@ disabled_rules: - unused_setter_value - redundant_discardable_let - identifier_name - - unhandled_throwing_task opt_in_rules: - force_unwrapping diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 06f305a92..f53c0b339 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -5004,7 +5004,7 @@ repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = "1.0.81-alpha"; + version = "1.0.82-alpha"; }; }; 96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 301dd429e..1f4354273 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -11,7 +11,7 @@ { "identity" : "compound-ios", "kind" : "remoteSourceControl", - "location" : "https://github.com/vector-im/compound-ios", + "location" : "https://github.com/vector-im/compound-ios.git", "state" : { "revision" : "d1a28b8a311e33ddb517d10391037f1547a3c7b6" } @@ -111,8 +111,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-rust-components-swift", "state" : { - "revision" : "eb26d0b26995a36df31b3bd965088ccfbe612779", - "version" : "1.0.81-alpha" + "revision" : "fbac557ed23d326f4838f2cd4a902c6b54851311", + "version" : "1.0.82-alpha" } }, { diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index bba8cc489..3c4f05c6d 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -533,13 +533,19 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, let identifier = "StaleDataIndicator" + func showLoadingIndicator() { + ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: identifier, type: .toast(progress: .indeterminate), title: L10n.commonSyncing, persistent: true)) + } + + showLoadingIndicator() + clientProxyObserver = userSession.clientProxy .callbacks .receive(on: DispatchQueue.main) .sink { action in switch action { case .startedUpdating: - ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: identifier, type: .toast(progress: .indeterminate), title: L10n.commonSyncing, persistent: true)) + showLoadingIndicator() case .receivedSyncUpdate: ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(identifier) default: diff --git a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift index c08939e3b..43b625858 100644 --- a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift @@ -454,46 +454,46 @@ class SDKClientMock: SDKClientProtocol { restoreSessionSessionReceivedInvocations.append(`session`) try restoreSessionSessionClosure?(`session`) } - //MARK: - `roomList` + //MARK: - `roomListService` - public var roomListThrowableError: Error? - public var roomListCallsCount = 0 - public var roomListCalled: Bool { - return roomListCallsCount > 0 + public var roomListServiceThrowableError: Error? + public var roomListServiceCallsCount = 0 + public var roomListServiceCalled: Bool { + return roomListServiceCallsCount > 0 } - public var roomListReturnValue: RoomList! - public var roomListClosure: (() throws -> RoomList)? + public var roomListServiceReturnValue: RoomListService! + public var roomListServiceClosure: (() throws -> RoomListService)? - public func `roomList`() throws -> RoomList { - if let error = roomListThrowableError { + public func `roomListService`() throws -> RoomListService { + if let error = roomListServiceThrowableError { throw error } - roomListCallsCount += 1 - if let roomListClosure = roomListClosure { - return try roomListClosure() + roomListServiceCallsCount += 1 + if let roomListServiceClosure = roomListServiceClosure { + return try roomListServiceClosure() } else { - return roomListReturnValue + return roomListServiceReturnValue } } - //MARK: - `roomListWithEncryption` + //MARK: - `roomListServiceWithEncryption` - public var roomListWithEncryptionThrowableError: Error? - public var roomListWithEncryptionCallsCount = 0 - public var roomListWithEncryptionCalled: Bool { - return roomListWithEncryptionCallsCount > 0 + public var roomListServiceWithEncryptionThrowableError: Error? + public var roomListServiceWithEncryptionCallsCount = 0 + public var roomListServiceWithEncryptionCalled: Bool { + return roomListServiceWithEncryptionCallsCount > 0 } - public var roomListWithEncryptionReturnValue: RoomList! - public var roomListWithEncryptionClosure: (() throws -> RoomList)? + public var roomListServiceWithEncryptionReturnValue: RoomListService! + public var roomListServiceWithEncryptionClosure: (() throws -> RoomListService)? - public func `roomListWithEncryption`() throws -> RoomList { - if let error = roomListWithEncryptionThrowableError { + public func `roomListServiceWithEncryption`() throws -> RoomListService { + if let error = roomListServiceWithEncryptionThrowableError { throw error } - roomListWithEncryptionCallsCount += 1 - if let roomListWithEncryptionClosure = roomListWithEncryptionClosure { - return try roomListWithEncryptionClosure() + roomListServiceWithEncryptionCallsCount += 1 + if let roomListServiceWithEncryptionClosure = roomListServiceWithEncryptionClosure { + return try roomListServiceWithEncryptionClosure() } else { - return roomListWithEncryptionReturnValue + return roomListServiceWithEncryptionReturnValue } } //MARK: - `rooms` diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index cac5c2ac5..a61e5578c 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -70,14 +70,13 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol return } - Publishers.CombineLatest(roomSummaryProvider.statePublisher, - roomSummaryProvider.roomListPublisher) + roomSummaryProvider.statePublisher .receive(on: DispatchQueue.main) - .sink { [weak self] state, rooms in + .sink { [weak self] state in guard let self else { return } - let isLoadingData = state == .notLoaded - let hasNoRooms = (state == .fullyLoaded && rooms.count == 0) + let isLoadingData = !state.isLoaded + let hasNoRooms = (state.isLoaded && state.totalNumberOfRooms == 0) var roomListMode = self.state.roomListMode if isLoadingData { @@ -94,7 +93,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol self.state.roomListMode = roomListMode - MXLog.info("Received visibleRoomsSummaryProvider update, setting view room list mode to \"\(self.state.roomListMode)\"") + MXLog.info("Received room summary provider update, setting view room list mode to \"\(self.state.roomListMode)\"") // Delay user profile detail loading until after the initial room list loads if roomListMode == .rooms { diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index 2a39d89cf..177706596 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -381,7 +381,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol return .init(actions: actions, debugActions: debugActions) } - // swiftlint:disable:next cyclomatic_complexity function_body_length + // swiftlint:disable:next cyclomatic_complexity private func processTimelineItemMenuAction(_ action: TimelineItemMenuAction, itemID: String) { guard let timelineItem = timelineController.timelineItems.first(where: { $0.id == itemID }), let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else { diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 29d2b7c97..06c5b1d82 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -26,7 +26,7 @@ class ClientProxy: ClientProxyProtocol { private let mediaLoader: MediaLoaderProtocol private let clientQueue: DispatchQueue - private var roomListService: RoomList? + private var roomListService: RoomListService? private var roomListStateUpdateTaskHandle: TaskHandle? var roomSummaryProvider: RoomSummaryProviderProtocol? @@ -227,8 +227,8 @@ class ClientProxy: ClientProxyProtocol { return nil } - if roomSummaryProvider.statePublisher.value != .fullyLoaded { - _ = await roomSummaryProvider.statePublisher.values.first(where: { $0 == .fullyLoaded }) + if !roomSummaryProvider.statePublisher.value.isLoaded { + _ = await roomSummaryProvider.statePublisher.values.first(where: { $0.isLoaded }) } (roomListItem, room) = await Task.dispatch(on: clientQueue) { @@ -366,7 +366,7 @@ class ClientProxy: ClientProxyProtocol { } do { - let roomListService = try client.roomList() + let roomListService = try client.roomListServiceWithEncryption() roomListStateUpdateTaskHandle = roomListService.state(listener: RoomListStateListenerProxy { [weak self] state in guard let self else { return } MXLog.info("Received room list update: \(state)") @@ -379,9 +379,12 @@ class ClientProxy: ClientProxyProtocol { // The invites are available only when entering `running` if state == .running { Task { - // Subscribe to invites later as the underlying SlidingSync list is only added when entering AllRooms - await self.inviteSummaryProvider?.subscribeIfNecessary(entriesFunction: roomListService.invites(listener:), - entriesLoadingStateFunction: nil) + do { + // Subscribe to invites later as the underlying SlidingSync list is only added when entering AllRooms + try await self.inviteSummaryProvider?.setRoomList(roomListService.invites()) + } catch { + MXLog.error("Failed configuring invites room list with error: \(error)") + } } } @@ -397,8 +400,7 @@ class ClientProxy: ClientProxyProtocol { eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)), name: "AllRooms") - await roomSummaryProvider?.subscribeIfNecessary(entriesFunction: roomListService.entries(listener:), - entriesLoadingStateFunction: roomListService.entriesLoadingState(listener:)) + try await roomSummaryProvider?.setRoomList(roomListService.allRooms()) inviteSummaryProvider = RoomSummaryProvider(roomListService: roomListService, eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)), @@ -437,14 +439,14 @@ extension ClientProxy: MediaLoaderProtocol { } } -private class RoomListStateListenerProxy: RoomListStateListener { - private let onUpdateClosure: (RoomListState) -> Void +private class RoomListStateListenerProxy: RoomListServiceStateListener { + private let onUpdateClosure: (RoomListServiceState) -> Void - init(_ onUpdateClosure: @escaping (RoomListState) -> Void) { + init(_ onUpdateClosure: @escaping (RoomListServiceState) -> Void) { self.onUpdateClosure = onUpdateClosure } - func onUpdate(state: RoomListState) { + func onUpdate(state: RoomListServiceState) { onUpdateClosure(state) } } diff --git a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift index 595629395..1a07170a8 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift @@ -38,12 +38,11 @@ class MockRoomSummaryProvider: RoomSummaryProviderProtocol { statePublisher = .init(.notLoaded) case .loaded(let rooms): roomListPublisher = .init(rooms) - statePublisher = .init(.fullyLoaded) + statePublisher = .init(.loaded(totalNumberOfRooms: UInt(rooms.count))) } } - func subscribeIfNecessary(entriesFunction: EntriesFunction, - entriesLoadingStateFunction: LoadingStateFunction?) async { } + func setRoomList(_ roomList: RoomList) { } func updateVisibleRange(_ range: Range) { } } diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift index 58bdc957d..aeceb478f 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift @@ -19,12 +19,14 @@ import Foundation import MatrixRustSDK class RoomSummaryProvider: RoomSummaryProviderProtocol { - private let roomListService: RoomListProtocol + private let roomListService: RoomListServiceProtocol private let eventStringBuilder: RoomEventStringBuilder private let name: String private let serialDispatchQueue: DispatchQueue + private var roomList: RoomListProtocol? + private var cancellables = Set() private var listUpdatesTaskHandle: TaskHandle? private var stateUpdatesTaskHandle: TaskHandle? @@ -49,7 +51,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { } } - init(roomListService: RoomListProtocol, + init(roomListService: RoomListServiceProtocol, eventStringBuilder: RoomEventStringBuilder, name: String) { self.roomListService = roomListService @@ -63,37 +65,35 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { .store(in: &cancellables) } - func subscribeIfNecessary(entriesFunction: EntriesFunction, - entriesLoadingStateFunction: LoadingStateFunction?) async { + func setRoomList(_ roomList: RoomList) { guard listUpdatesTaskHandle == nil, stateUpdatesTaskHandle == nil else { return } + self.roomList = roomList + do { - let listUpdatesSubscriptionResult = try await entriesFunction(RoomListEntriesListenerProxy { [weak self] update in + let listUpdatesSubscriptionResult = try roomList.entries(listener: RoomListEntriesListenerProxy { [weak self] update in guard let self else { return } MXLog.verbose("\(name): Received list update") diffPublisher.send(update) }) - + listUpdatesTaskHandle = listUpdatesSubscriptionResult.entriesStream - + rooms = listUpdatesSubscriptionResult.entries.map { roomListEntry in buildSummaryForRoomListEntry(roomListEntry) } - if let entriesLoadingStateFunction { - let stateUpdatesSubscriptionResult = try await entriesLoadingStateFunction(RoomListStateObserver { [weak self] state in - guard let self else { return } - MXLog.info("\(name): Received state update: \(state)") - stateSubject.send(RoomSummaryProviderState(slidingSyncState: state)) - }) - - stateSubject.send(RoomSummaryProviderState(slidingSyncState: stateUpdatesSubscriptionResult.entriesLoadingState)) - - stateUpdatesTaskHandle = stateUpdatesSubscriptionResult.entriesLoadingStateStream - } + let stateUpdatesSubscriptionResult = try roomList.loadingState(listener: RoomListStateObserver { [weak self] state in + guard let self else { return } + MXLog.info("\(name): Received state update: \(state)") + stateSubject.send(RoomSummaryProviderState(roomListState: state)) + }) + stateUpdatesTaskHandle = stateUpdatesSubscriptionResult.stateStream + + stateSubject.send(RoomSummaryProviderState(roomListState: stateUpdatesSubscriptionResult.state)) } catch { MXLog.error("Failed setting up room list entry listener with error: \(error)") } @@ -266,16 +266,12 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { } extension RoomSummaryProviderState { - init(slidingSyncState: SlidingSyncListLoadingState) { - switch slidingSyncState { + init(roomListState: RoomListLoadingState) { + switch roomListState { case .notLoaded: self = .notLoaded - case .preloaded: - self = .preloaded - case .partiallyLoaded: - self = .partiallyLoaded - case .fullyLoaded: - self = .fullyLoaded + case .loaded(let maximumNumberOfRooms): + self = .loaded(totalNumberOfRooms: UInt(maximumNumberOfRooms ?? 0)) } } } @@ -314,14 +310,14 @@ private class RoomListEntriesListenerProxy: RoomListEntriesListener { } } -private class RoomListStateObserver: SlidingSyncListStateObserver { - private let onUpdateClosure: (SlidingSyncListLoadingState) -> Void +private class RoomListStateObserver: RoomListLoadingStateListener { + private let onUpdateClosure: (RoomListLoadingState) -> Void - init(_ onUpdateClosure: @escaping (SlidingSyncListLoadingState) -> Void) { + init(_ onUpdateClosure: @escaping (RoomListLoadingState) -> Void) { self.onUpdateClosure = onUpdateClosure } - func didReceiveUpdate(newState: SlidingSyncListLoadingState) { - onUpdateClosure(newState) + func onUpdate(state: RoomListLoadingState) { + onUpdateClosure(state) } } diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift index a87684254..966f47962 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift @@ -20,9 +20,25 @@ import MatrixRustSDK enum RoomSummaryProviderState { case notLoaded - case preloaded - case partiallyLoaded - case fullyLoaded + case loaded(totalNumberOfRooms: UInt) + + var isLoaded: Bool { + switch self { + case .loaded: + return true + default: + return false + } + } + + var totalNumberOfRooms: UInt? { + switch self { + case .loaded(let totalNumberOfRooms): + return totalNumberOfRooms + default: + return nil + } + } } enum RoomSummary: CustomStringConvertible { @@ -61,19 +77,15 @@ enum RoomSummary: CustomStringConvertible { } protocol RoomSummaryProviderProtocol { - typealias EntriesFunction = (RoomListEntriesListener) async throws -> RoomListEntriesResult - typealias LoadingStateFunction = (SlidingSyncListStateObserver) async throws -> RoomListEntriesLoadingStateResult - /// Publishes the currently available room summaries var roomListPublisher: CurrentValuePublisher<[RoomSummary], Never> { get } /// Publishes the current state the summary provider is finding itself in var statePublisher: CurrentValuePublisher { get } - /// A separate subscription method is needed instead of running this in the constructor because the invites list is added later on the Rust side. + /// This is outside of the constructor because the invites list is added later on the Rust side. /// Wanted to be able to build the InvitesSummaryProvider directly instead of having to inform the HomeScreenViewModel about it later - func subscribeIfNecessary(entriesFunction: EntriesFunction, - entriesLoadingStateFunction: LoadingStateFunction?) async + func setRoomList(_ roomList: RoomList) func updateVisibleRange(_ range: Range) } diff --git a/project.yml b/project.yml index d2a369623..0c9baabf4 100644 --- a/project.yml +++ b/project.yml @@ -44,7 +44,7 @@ include: packages: MatrixRustSDK: url: https://github.com/matrix-org/matrix-rust-components-swift - exactVersion: 1.0.81-alpha + exactVersion: 1.0.82-alpha # path: ../matrix-rust-sdk DesignKit: path: DesignKit