diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 7750f14ab..586fd4662 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -159,6 +159,8 @@ class AppCoordinator: AppCoordinatorProtocol { self.logout(isSoft: isSoft) case (.signingOut, .completedSigningOut(let isSoft), .signedOut): self.presentSplashScreen(isSoftLogout: isSoft) + case (.signedIn, .clearCache, .initial): + clearCache() default: fatalError("Unknown transition: \(context)") } @@ -238,6 +240,8 @@ class AppCoordinator: AppCoordinatorProtocol { switch action { case .signOut: self?.stateMachine.processEvent(.signOut(isSoft: false)) + case .clearCache: + self?.stateMachine.processEvent(.clearCache) } } @@ -428,6 +432,25 @@ class AppCoordinator: AppCoordinatorProtocol { storedAppRoute = appRoute } } + + private func clearCache() { + showLoadingIndicator() + + defer { + hideLoadingIndicator() + } + + navigationRootCoordinator.setRootCoordinator(SplashScreenCoordinator()) + + userSession.clientProxy.stopSync() + userSessionFlowCoordinator?.stop() + + userSessionStore.clearCacheFor(userSession: userSession) + + tearDownUserSession() + + stateMachine.processEvent(.startWithExistingSession) + } } // MARK: - AuthenticationCoordinatorDelegate diff --git a/ElementX/Sources/Application/AppCoordinatorStateMachine.swift b/ElementX/Sources/Application/AppCoordinatorStateMachine.swift index 7183d14d5..c974f488f 100644 --- a/ElementX/Sources/Application/AppCoordinatorStateMachine.swift +++ b/ElementX/Sources/Application/AppCoordinatorStateMachine.swift @@ -52,6 +52,9 @@ class AppCoordinatorStateMachine { case signOut(isSoft: Bool) /// Signing out completed case completedSigningOut(isSoft: Bool) + + /// Request cache clearing + case clearCache } private let stateMachine: StateMachine @@ -71,6 +74,8 @@ class AppCoordinatorStateMachine { stateMachine.addRoutes(event: .startWithExistingSession, transitions: [.initial => .restoringSession]) stateMachine.addRoutes(event: .createdUserSession, transitions: [.restoringSession => .signedIn]) stateMachine.addRoutes(event: .failedRestoringSession, transitions: [.restoringSession => .signedOut]) + + stateMachine.addRoutes(event: .clearCache, transitions: [.signedIn => .initial]) // Transitions with associated values need to be handled through `addRouteMapping` stateMachine.addRouteMapping { event, fromState, _ in diff --git a/ElementX/Sources/Screens/DeveloperOptionsScreen/DeveloperOptionsScreenCoordinator.swift b/ElementX/Sources/Screens/DeveloperOptionsScreen/DeveloperOptionsScreenCoordinator.swift index c967b4e57..1b42d3054 100644 --- a/ElementX/Sources/Screens/DeveloperOptionsScreen/DeveloperOptionsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/DeveloperOptionsScreen/DeveloperOptionsScreenCoordinator.swift @@ -16,11 +16,23 @@ import SwiftUI +enum DeveloperOptionsScreenCoordinatorAction { + case clearCache +} + final class DeveloperOptionsScreenCoordinator: CoordinatorProtocol { - private let viewModel: DeveloperOptionsScreenViewModelProtocol + private var viewModel: DeveloperOptionsScreenViewModelProtocol + + var callback: ((DeveloperOptionsScreenCoordinatorAction) -> Void)? init() { viewModel = DeveloperOptionsScreenViewModel() + viewModel.callback = { [weak self] action in + switch action { + case .clearCache: + self?.callback?(.clearCache) + } + } } func toPresentable() -> AnyView { diff --git a/ElementX/Sources/Screens/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index 17dcc8e82..f757d73a3 100644 --- a/ElementX/Sources/Screens/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -16,7 +16,9 @@ import Foundation -enum DeveloperOptionsScreenViewModelAction { } +enum DeveloperOptionsScreenViewModelAction { + case clearCache +} struct DeveloperOptionsScreenViewState: BindableState { var bindings: DeveloperOptionsScreenViewStateBindings @@ -34,4 +36,5 @@ enum DeveloperOptionsScreenViewAction { case changedStartChatFlowEnabled case changedStartChatUserSuggestionsEnabled case changedInvitesFlowEnabled + case clearCache } diff --git a/ElementX/Sources/Screens/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift b/ElementX/Sources/Screens/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift index a34683a20..c5923ea22 100644 --- a/ElementX/Sources/Screens/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift +++ b/ElementX/Sources/Screens/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift @@ -45,6 +45,8 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve ServiceLocator.shared.settings.startChatUserSuggestionsEnabled = state.bindings.startChatUserSuggestionsEnabled case .changedInvitesFlowEnabled: ServiceLocator.shared.settings.invitesFlowEnabled = state.bindings.invitesFlowEnabled + case .clearCache: + callback?(.clearCache) } } } diff --git a/ElementX/Sources/Screens/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index accdc5c28..589eea6f8 100644 --- a/ElementX/Sources/Screens/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -59,9 +59,16 @@ struct DeveloperOptionsScreen: View { Text("🥳") .frame(maxWidth: .infinity) } - .buttonStyle(FormButtonStyle()) } - .formSectionStyle() + + Section { + Button(role: .destructive) { + context.send(viewAction: .clearCache) + } label: { + Text("Clear cache") + .frame(maxWidth: .infinity) + } + } } .overlay(effectsView) .scrollContentBackground(.hidden) diff --git a/ElementX/Sources/Screens/SettingsScreen/SettingsScreenCoordinator.swift b/ElementX/Sources/Screens/SettingsScreen/SettingsScreenCoordinator.swift index ed7534590..aae17b0a6 100644 --- a/ElementX/Sources/Screens/SettingsScreen/SettingsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/SettingsScreen/SettingsScreenCoordinator.swift @@ -26,6 +26,7 @@ struct SettingsScreenCoordinatorParameters { enum SettingsScreenCoordinatorAction { case dismiss case logout + case clearCache } final class SettingsScreenCoordinator: CoordinatorProtocol { @@ -114,6 +115,14 @@ final class SettingsScreenCoordinator: CoordinatorProtocol { private func presentDeveloperOptions() { let coordinator = DeveloperOptionsScreenCoordinator() + + coordinator.callback = { [weak self] action in + switch action { + case .clearCache: + self?.callback?(.clearCache) + } + } + parameters.navigationStackCoordinator?.push(coordinator) } diff --git a/ElementX/Sources/Services/UserSession/UserSessionFlowCoordinator.swift b/ElementX/Sources/Services/UserSession/UserSessionFlowCoordinator.swift index d50c2dcef..091132da3 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionFlowCoordinator.swift @@ -19,6 +19,7 @@ import SwiftUI enum UserSessionFlowCoordinatorAction { case signOut + case clearCache } class UserSessionFlowCoordinator: CoordinatorProtocol { @@ -304,6 +305,8 @@ class UserSessionFlowCoordinator: CoordinatorProtocol { case .logout: self.navigationSplitCoordinator.setSheetCoordinator(nil) self.callback?(.signOut) + case .clearCache: + self.callback?(.clearCache) } } diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSession/UserSessionStore.swift index d9c8d95f7..eb5d6d1a1 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStore.swift @@ -21,6 +21,7 @@ import MatrixRustSDK class UserSessionStore: UserSessionStoreProtocol { private let keychainController: KeychainControllerProtocol private let backgroundTaskService: BackgroundTaskServiceProtocol + private let cachesFolderName = "matrix-sdk-state" /// Whether or not there are sessions in the store. var hasSessions: Bool { !keychainController.restorationTokens().isEmpty } @@ -90,6 +91,11 @@ class UserSessionStore: UserSessionStoreProtocol { deleteSessionDirectory(for: userID) } + func clearCacheFor(userSession: UserSessionProtocol) { + let userID = userSession.clientProxy.userID + deleteCachesFolder(for: userID) + } + // MARK: - Private private func buildUserSessionWithClient(_ clientProxy: ClientProxyProtocol) -> UserSessionProtocol { @@ -140,9 +146,15 @@ class UserSessionStore: UserSessionStoreProtocol { } private func deleteSessionDirectory(for userID: String) { - // Rust sanitises the user ID replacing invalid characters with an _ - let sanitisedUserID = userID.replacingOccurrences(of: ":", with: "_") - let url = baseDirectory.appendingPathComponent(sanitisedUserID) + do { + try FileManager.default.removeItem(at: basePath(for: userID)) + } catch { + MXLog.failure("Failed deleting the session data: \(error)") + } + } + + private func deleteCachesFolder(for userID: String) { + let url = basePath(for: userID).appendingPathComponent(cachesFolderName) do { try FileManager.default.removeItem(at: url) @@ -150,4 +162,11 @@ class UserSessionStore: UserSessionStoreProtocol { MXLog.failure("Failed deleting the session data: \(error)") } } + + #warning("We should move this and the caches folder path to the rust side") + private func basePath(for userID: String) -> URL { + // Rust sanitises the user ID replacing invalid characters with an _ + let sanitisedUserID = userID.replacingOccurrences(of: ":", with: "_") + return baseDirectory.appendingPathComponent(sanitisedUserID) + } } diff --git a/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift b/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift index 714335c50..b07958ebc 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift @@ -45,4 +45,7 @@ protocol UserSessionStoreProtocol { /// Logs out of the specified session. func logout(userSession: UserSessionProtocol) + + /// Clears our all the matrix sdk state data for the specified session + func clearCacheFor(userSession: UserSessionProtocol) }