diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index d1bc2bc40..5d72ad872 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -325,7 +325,6 @@ 804C15D8ADE0EA7A5268F58A /* OverridableAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */; }; 80D00A7C62AAB44F54725C43 /* PermalinkBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */; }; 80DEA2A4B20F9E279EAE6B2B /* UserProfile+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD01F7FC2BBAC7351948595 /* UserProfile+Mock.swift */; }; - 8196A2E71ACC902DD69F24EE /* UserNotificationControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DE6C5C756E1393202BA95CD /* UserNotificationControllerTests.swift */; }; 81A7C020CB5F6232242A8414 /* UserSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F36C0A6D59717193F49EA986 /* UserSessionTests.swift */; }; 8285FF4B2C2331758C437FF7 /* ReportContentScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 713B48DBF65DE4B0DD445D66 /* ReportContentScreenViewModelProtocol.swift */; }; 828EA5009557C2B9DCD4CA0F /* UserDiscoverySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */; }; @@ -426,6 +425,7 @@ A14A9419105A1CD42F0511C4 /* UserIndicatorModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43005941B3A2C9671E23C85 /* UserIndicatorModalView.swift */; }; A17FAD2EBC53E17B5FD384DB /* InviteUsersScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */; }; A1D4033881320C9EB88196E6 /* ServerConfirmationScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE846DDA83BFD7EC5C03760B /* ServerConfirmationScreenUITests.swift */; }; + A1DF0E1E526A981ED6D5DF44 /* UserIndicatorControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */; }; A216C83ADCF32BA5EF8A6FBC /* InviteUsersViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845DDBDE5A0887E73D38B826 /* InviteUsersViewModelTests.swift */; }; A23B8B27A1436A1049EEF68E /* InfoPlistReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */; }; A2434D4DFB49A68E5CD0F53C /* MediaLoaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02406480C351B8C6E0682C /* MediaLoaderProtocol.swift */; }; @@ -775,7 +775,6 @@ 0C88046D6A070D9827181C4D /* OnboardingUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingUITests.swift; sourceTree = ""; }; 0D0B159AFFBBD8ECFD0E37FA /* LoginScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenModels.swift; sourceTree = ""; }; 0D8F620C8B314840D8602E3F /* NSE.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = NSE.appex; sourceTree = BUILT_PRODUCTS_DIR; }; - 0DE6C5C756E1393202BA95CD /* UserNotificationControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationControllerTests.swift; sourceTree = ""; }; 0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStore.swift; sourceTree = ""; }; 0F19DBE940499D3E3DD405D8 /* RoomMemberDetailsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenUITests.swift; sourceTree = ""; }; 0F5567A7EF6F2AB9473236F6 /* DocumentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentPicker.swift; sourceTree = ""; }; @@ -835,6 +834,7 @@ 227AC5D71A4CE43512062243 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = ""; }; 23AA3F4B285570805CB0CCDD /* MapTiler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTiler.swift; sourceTree = ""; }; 24227FF9A2797F6EA7F69CDD /* HomeScreenInvitesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenInvitesButton.swift; sourceTree = ""; }; + 2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorControllerTests.swift; sourceTree = ""; }; 248649EBA5BC33DB93698734 /* SessionVerificationControllerProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxyMock.swift; sourceTree = ""; }; 24DEE0682C95F897B6C7CB0D /* ServerConfirmationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenViewModel.swift; sourceTree = ""; }; 24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelProtocol.swift; sourceTree = ""; }; @@ -2363,7 +2363,7 @@ 2AB2C848BB9A7A9B618B7B89 /* TextBasedRoomTimelineTests.swift */, 1734A445A58ED855B977A0A8 /* TracingConfigurationTests.swift */, EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */, - 0DE6C5C756E1393202BA95CD /* UserNotificationControllerTests.swift */, + 2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */, BA241DEEF7C8A7181C0AEDC9 /* UserPreferenceTests.swift */, 53280D2292E6C9C7821773FD /* UserSession */, 70C5B842301AC281DF374E41 /* Extensions */, @@ -3874,8 +3874,8 @@ AF33B9044498211C3D82F1E1 /* UNTextInputNotificationResponse+Creator.swift in Sources */, 8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */, E313BDD2B8813144139B2E00 /* UserDiscoveryServiceTest.swift in Sources */, + A1DF0E1E526A981ED6D5DF44 /* UserIndicatorControllerTests.swift in Sources */, 08248D02BACA75CDC3B39A96 /* UserNotificationCenterSpy.swift in Sources */, - 8196A2E71ACC902DD69F24EE /* UserNotificationControllerTests.swift in Sources */, 04F17DE71A50206336749BAC /* UserPreferenceTests.swift in Sources */, 81A7C020CB5F6232242A8414 /* UserSessionTests.swift in Sources */, 99F8DA4CCC6772EE5FE68E24 /* ViewModelContext.swift in Sources */, diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 5f9990236..bba8cc489 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -52,8 +52,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, private var appDelegateObserver: AnyCancellable? private var userSessionObserver: AnyCancellable? + private var clientProxyObserver: AnyCancellable? private var networkMonitorObserver: AnyCancellable? - private var initialSyncObserver: AnyCancellable? private var backgroundRefreshSyncObserver: AnyCancellable? let notificationManager: NotificationManagerProtocol @@ -355,15 +355,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, showLoadingIndicator() - defer { - hideLoadingIndicator() - } - stopSync() userSessionFlowCoordinator?.stop() guard !isSoft else { stateMachine.processEvent(.completedSigningOut(isSoft: isSoft)) + hideLoadingIndicator() return } @@ -380,6 +377,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, ServiceLocator.shared.analytics.resetConsentState() stateMachine.processEvent(.completedSigningOut(isSoft: isSoft)) + + hideLoadingIndicator() } } @@ -519,6 +518,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, backgroundAppRefreshTask?.setTaskCompleted(success: true) backgroundAppRefreshTask = nil + + clientProxyObserver = nil } private func startSync() { @@ -532,15 +533,18 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, let identifier = "StaleDataIndicator" - ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: identifier, type: .toast(progress: .indeterminate), title: L10n.commonSyncing, persistent: true)) - - initialSyncObserver = userSession.clientProxy + clientProxyObserver = userSession.clientProxy .callbacks .receive(on: DispatchQueue.main) - .filter(\.isSyncUpdate) - .sink { [weak self] _ in - ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(identifier) - self?.initialSyncObserver?.cancel() + .sink { action in + switch action { + case .startedUpdating: + ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: identifier, type: .toast(progress: .indeterminate), title: L10n.commonSyncing, persistent: true)) + case .receivedSyncUpdate: + ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(identifier) + default: + break + } } } diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 76c4b5052..2717f06d9 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -1210,19 +1210,19 @@ class UserIndicatorControllerMock: UserIndicatorControllerProtocol { //MARK: - submitIndicator - var submitIndicatorCallsCount = 0 - var submitIndicatorCalled: Bool { - return submitIndicatorCallsCount > 0 + var submitIndicatorDelayCallsCount = 0 + var submitIndicatorDelayCalled: Bool { + return submitIndicatorDelayCallsCount > 0 } - var submitIndicatorReceivedIndicator: UserIndicator? - var submitIndicatorReceivedInvocations: [UserIndicator] = [] - var submitIndicatorClosure: ((UserIndicator) -> Void)? + var submitIndicatorDelayReceivedArguments: (indicator: UserIndicator, delay: Duration?)? + var submitIndicatorDelayReceivedInvocations: [(indicator: UserIndicator, delay: Duration?)] = [] + var submitIndicatorDelayClosure: ((UserIndicator, Duration?) -> Void)? - func submitIndicator(_ indicator: UserIndicator) { - submitIndicatorCallsCount += 1 - submitIndicatorReceivedIndicator = indicator - submitIndicatorReceivedInvocations.append(indicator) - submitIndicatorClosure?(indicator) + func submitIndicator(_ indicator: UserIndicator, delay: Duration?) { + submitIndicatorDelayCallsCount += 1 + submitIndicatorDelayReceivedArguments = (indicator: indicator, delay: delay) + submitIndicatorDelayReceivedInvocations.append((indicator: indicator, delay: delay)) + submitIndicatorDelayClosure?(indicator, delay) } //MARK: - retractIndicatorWithId diff --git a/ElementX/Sources/Mocks/UserIndicatorControllerMock.swift b/ElementX/Sources/Mocks/UserIndicatorControllerMock.swift index ae80cd71c..77da87fcc 100644 --- a/ElementX/Sources/Mocks/UserIndicatorControllerMock.swift +++ b/ElementX/Sources/Mocks/UserIndicatorControllerMock.swift @@ -20,7 +20,7 @@ import Foundation extension UserIndicatorControllerMock { static var `default`: UserIndicatorControllerMock { let mock = UserIndicatorControllerMock() - mock.submitIndicatorClosure = { _ in } + mock.submitIndicatorDelayClosure = { _, _ in } mock.retractIndicatorWithIdClosure = { _ in } mock.retractAllIndicatorsClosure = { } return mock diff --git a/ElementX/Sources/Other/UserIndicator/UserIndicatorController.swift b/ElementX/Sources/Other/UserIndicator/UserIndicatorController.swift index 61c9abb29..dd1473f6e 100644 --- a/ElementX/Sources/Other/UserIndicator/UserIndicatorController.swift +++ b/ElementX/Sources/Other/UserIndicator/UserIndicatorController.swift @@ -21,6 +21,7 @@ class UserIndicatorController: ObservableObject, UserIndicatorControllerProtocol private var dismisalTimer: Timer? private var displayTimes = [String: Date]() + private var delayedIndicators = Set() var nonPersistentDisplayDuration = 2.5 var minimumDisplayDuration = 0.5 @@ -51,12 +52,27 @@ class UserIndicatorController: ObservableObject, UserIndicatorControllerProtocol ) } - func submitIndicator(_ indicator: UserIndicator) { + func submitIndicator(_ indicator: UserIndicator, delay: Duration?) { if let index = indicatorQueue.firstIndex(where: { $0.id == indicator.id }) { indicatorQueue[index] = indicator } else { - retractIndicatorWithId(indicator.id) - indicatorQueue.append(indicator) + if let delay { + delayedIndicators.insert(indicator.id) + Timer.scheduledTimer(withTimeInterval: Double(delay.components.seconds), repeats: false) { [weak self] _ in + guard let self else { return } + + guard delayedIndicators.contains(indicator.id) else { + return + } + + retractIndicatorWithId(indicator.id) + indicatorQueue.append(indicator) + delayedIndicators.remove(indicator.id) + } + } else { + retractIndicatorWithId(indicator.id) + indicatorQueue.append(indicator) + } } displayTimes[indicator.id] = .now @@ -69,6 +85,8 @@ class UserIndicatorController: ObservableObject, UserIndicatorControllerProtocol } func retractIndicatorWithId(_ id: String) { + delayedIndicators.remove(id) + guard let displayTime = displayTimes[id], abs(displayTime.timeIntervalSinceNow) <= minimumDisplayDuration else { indicatorQueue.removeAll { $0.id == id } return diff --git a/ElementX/Sources/Other/UserIndicator/UserIndicatorControllerProtocol.swift b/ElementX/Sources/Other/UserIndicator/UserIndicatorControllerProtocol.swift index 520794768..5dfef0d7a 100644 --- a/ElementX/Sources/Other/UserIndicator/UserIndicatorControllerProtocol.swift +++ b/ElementX/Sources/Other/UserIndicator/UserIndicatorControllerProtocol.swift @@ -18,19 +18,14 @@ import Foundation // sourcery: AutoMockable protocol UserIndicatorControllerProtocol: CoordinatorProtocol { - func submitIndicator(_ indicator: UserIndicator) + func submitIndicator(_ indicator: UserIndicator, delay: Duration?) func retractIndicatorWithId(_ id: String) func retractAllIndicators() var alertInfo: AlertInfo? { get set } } extension UserIndicatorControllerProtocol { - /// Allows to submit a delayed indicator, this returns a Task so that it's also possible to cancel the action - func submitIndicator(_ indicator: UserIndicator, delay: Duration) -> Task { - Task { @MainActor in - try? await Task.sleep(for: delay) - guard !Task.isCancelled else { return } - submitIndicator(indicator) - } + func submitIndicator(_ indicator: UserIndicator) { + submitIndicator(indicator, delay: nil) } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenUserMenuButton.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenUserMenuButton.swift index 99faf9e6e..063296cc5 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenUserMenuButton.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenUserMenuButton.swift @@ -19,7 +19,7 @@ import SwiftUI struct HomeScreenUserMenuButton: View { @State private var showingLogoutConfirmation = false - let context: HomeScreenViewModel.Context + @ObservedObject var context: HomeScreenViewModel.Context var body: some View { Menu { diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift index 62ae7f3ed..054566805 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift @@ -26,7 +26,6 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr private let roomType: InviteUsersScreenRoomType private weak var userIndicatorController: UserIndicatorControllerProtocol? private let actionsSubject: PassthroughSubject = .init() - @CancellableTask private var showLoaderTask: Task? var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() @@ -165,11 +164,10 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr private let userIndicatorID = UUID().uuidString private func showLoader() { - showLoaderTask = userIndicatorController?.submitIndicator(UserIndicator(id: userIndicatorID, type: .modal, title: L10n.commonLoading, persistent: true), delay: .milliseconds(200)) + userIndicatorController?.submitIndicator(UserIndicator(id: userIndicatorID, type: .modal, title: L10n.commonLoading, persistent: true), delay: .milliseconds(200)) } private func hideLoader() { - showLoaderTask = nil userIndicatorController?.retractIndicatorWithId(userIndicatorID) } } diff --git a/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift b/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift index 18a325513..19fbc6ed2 100644 --- a/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift @@ -21,7 +21,6 @@ typealias RoomMembersListScreenViewModelType = StateStoreViewModel? var callback: ((RoomMembersListScreenViewModelAction) -> Void)? @@ -109,11 +108,10 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe private let userIndicatorID = UUID().uuidString private func showLoader() { - showLoaderTask = ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: userIndicatorID, type: .modal, title: L10n.commonLoading, persistent: true), delay: .milliseconds(200)) + ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: userIndicatorID, type: .modal, title: L10n.commonLoading, persistent: true), delay: .milliseconds(200)) } private func hideLoader() { - showLoaderTask = nil ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(userIndicatorID) } } diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index f2b9e2144..b056b0ac6 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -30,7 +30,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol private let roomProxy: RoomProxyProtocol private let timelineController: RoomTimelineControllerProtocol private unowned let userIndicatorController: UserIndicatorControllerProtocol - private var loadingTask: Task? init(timelineController: RoomTimelineControllerProtocol, mediaProvider: MediaProviderProtocol, @@ -512,9 +511,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol private func handleTappedUser(userID: String) async { // This is generally fast but it could take some time for rooms with thousands of users on first load // Show a loader only if it takes more than 0.1 seconds - loadingTask = showLoadingIndicator(with: .milliseconds(100)) + showLoadingIndicator(with: .milliseconds(100)) let result = await roomProxy.getMember(userID: userID) - loadingTask?.cancel() hideLoadingIndicator() switch result { @@ -544,7 +542,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol private static let loadingIndicatorIdentifier = "RoomScreenLoadingIndicator" - private func showLoadingIndicator(with delay: Duration) -> Task { + private func showLoadingIndicator(with delay: Duration) { userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, type: .modal(progress: .indeterminate, interactiveDismissDisabled: true), title: L10n.commonLoading, diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index d0a81951b..29d2b7c97 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -19,26 +19,6 @@ import Foundation import MatrixRustSDK import UIKit -private class WeakClientProxyWrapper: ClientDelegate { - private weak var clientProxy: ClientProxy? - - init(clientProxy: ClientProxy) { - self.clientProxy = clientProxy - } - - // MARK: - ClientDelegate - - func didReceiveAuthError(isSoftLogout: Bool) { - MXLog.error("Received authentication error, softlogout=\(isSoftLogout)") - clientProxy?.didReceiveAuthError(isSoftLogout: isSoftLogout) - } - - func didRefreshTokens() { - MXLog.info("The session has updated tokens.") - clientProxy?.updateRestorationToken() - } -} - class ClientProxy: ClientProxyProtocol { private let client: ClientProtocol private let backgroundTaskService: BackgroundTaskServiceProtocol @@ -67,10 +47,8 @@ class ClientProxy: ClientProxyProtocol { private var visibleRoomsListProxyStateObservationToken: AnyCancellable? deinit { - // These need to be inlined instead of using stopSync() - // as we can't call async methods safely from deinit client.setDelegate(delegate: nil) - try? roomListService?.stopSync() + stopSync() } let callbacks = PassthroughSubject() @@ -82,8 +60,11 @@ class ClientProxy: ClientProxyProtocol { mediaLoader = MediaLoader(client: client, clientQueue: clientQueue) - let delegate = WeakClientProxyWrapper(clientProxy: self) - client.setDelegate(delegate: delegate) + client.setDelegate(delegate: ClientDelegateWrapper { [weak self] isSoftLogout in + self?.callbacks.send(.receivedAuthError(isSoftLogout: isSoftLogout)) + } tokenRefreshCallback: { [weak self] in + self?.callbacks.send(.updateRestorationToken) + }) await configureRoomListService() @@ -391,19 +372,25 @@ class ClientProxy: ClientProxyProtocol { MXLog.info("Received room list update: \(state)") // Restart the room list sync on every error for now - if state == .terminated { + if state == .error { self.restartSync() } + // The invites are available only when entering `running` if state == .running { - self.callbacks.send(.receivedSyncUpdate) - 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) } } + + // Anything that's not `running` is interpreted as "Loading data" + if state == .running { + self.callbacks.send(.receivedSyncUpdate) + } else { + self.callbacks.send(.startedUpdating) + } }) roomSummaryProvider = RoomSummaryProvider(roomListService: roomListService, @@ -434,14 +421,6 @@ class ClientProxy: ClientProxyProtocol { return (nil, nil) } } - - fileprivate func updateRestorationToken() { - callbacks.send(.updateRestorationToken) - } - - fileprivate func didReceiveAuthError(isSoftLogout: Bool) { - callbacks.send(.receivedAuthError(isSoftLogout: isSoftLogout)) - } } extension ClientProxy: MediaLoaderProtocol { @@ -469,3 +448,26 @@ private class RoomListStateListenerProxy: RoomListStateListener { onUpdateClosure(state) } } + +private class ClientDelegateWrapper: ClientDelegate { + private let authErrorCallback: (Bool) -> Void + private let tokenRefreshCallback: () -> Void + + init(authErrorCallback: @escaping (Bool) -> Void, + tokenRefreshCallback: @escaping () -> Void) { + self.authErrorCallback = authErrorCallback + self.tokenRefreshCallback = tokenRefreshCallback + } + + // MARK: - ClientDelegate + + func didReceiveAuthError(isSoftLogout: Bool) { + MXLog.error("Received authentication error, softlogout=\(isSoftLogout)") + authErrorCallback(isSoftLogout) + } + + func didRefreshTokens() { + MXLog.info("The session has updated tokens.") + tokenRefreshCallback() + } +} diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index 3599cd4d6..9426ec697 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -19,6 +19,7 @@ import Foundation import MatrixRustSDK enum ClientProxyCallback { + case startedUpdating case receivedSyncUpdate case receivedAuthError(isSoftLogout: Bool) case updateRestorationToken diff --git a/Tools/Sources/BuildSDK.swift b/Tools/Sources/BuildSDK.swift index 211a34efe..c316a900f 100644 --- a/Tools/Sources/BuildSDK.swift +++ b/Tools/Sources/BuildSDK.swift @@ -25,7 +25,7 @@ struct BuildSDK: ParsableCommand { var target: Target? @Option(help: "The profile to use when building the SDK. Omit this option to build in debug mode.") - var profile: Profile = .debug + var profile: Profile = .reldbg enum Error: LocalizedError { case rustupOutputFailure diff --git a/UnitTests/Sources/RoomScreenViewModelTests.swift b/UnitTests/Sources/RoomScreenViewModelTests.swift index a5a95d13f..8635f8264 100644 --- a/UnitTests/Sources/RoomScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomScreenViewModelTests.swift @@ -185,7 +185,7 @@ class RoomScreenViewModelTests: XCTestCase { // Test viewModel.context.send(viewAction: .tappedOnUser(userID: "bob")) await Task.yield() - XCTAssertFalse(userIndicatorControllerMock.submitIndicatorCalled) + XCTAssertFalse(userIndicatorControllerMock.submitIndicatorDelayCalled) XCTAssert(roomProxyMock.getMemberUserIDCallsCount == 1) XCTAssertEqual(roomProxyMock.getMemberUserIDReceivedUserID, "bob") } @@ -217,7 +217,7 @@ class RoomScreenViewModelTests: XCTestCase { // Test viewModel.context.send(viewAction: .tappedOnUser(userID: "bob")) try? await Task.sleep(for: .milliseconds(300)) - XCTAssert(userIndicatorControllerMock.submitIndicatorCallsCount == 1) + XCTAssert(userIndicatorControllerMock.submitIndicatorDelayCallsCount == 1) XCTAssert(userIndicatorControllerMock.retractIndicatorWithIdCallsCount == 1) XCTAssert(roomProxyMock.getMemberUserIDCallsCount == 1) XCTAssertEqual(roomProxyMock.getMemberUserIDReceivedUserID, "bob") diff --git a/UnitTests/Sources/UserNotificationControllerTests.swift b/UnitTests/Sources/UserIndicatorControllerTests.swift similarity index 100% rename from UnitTests/Sources/UserNotificationControllerTests.swift rename to UnitTests/Sources/UserIndicatorControllerTests.swift