2022-02-14 18:05:21 +02:00
|
|
|
//
|
2022-08-11 08:54:24 +01:00
|
|
|
// Copyright 2022 New Vector Ltd
|
2022-02-14 18:05:21 +02:00
|
|
|
//
|
2022-08-11 08:54:24 +01:00
|
|
|
// 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.
|
2022-02-14 18:05:21 +02:00
|
|
|
//
|
|
|
|
|
2022-07-01 13:56:52 +03:00
|
|
|
import Combine
|
2022-07-27 16:06:40 +03:00
|
|
|
import MatrixRustSDK
|
2022-11-16 15:37:34 +02:00
|
|
|
import SwiftUI
|
2023-01-31 17:48:24 +00:00
|
|
|
import Version
|
2022-02-14 18:05:21 +02:00
|
|
|
|
2022-11-21 19:37:13 +03:00
|
|
|
class AppCoordinator: AppCoordinatorProtocol {
|
2022-07-01 13:56:52 +03:00
|
|
|
private let stateMachine: AppCoordinatorStateMachine
|
2022-12-12 12:31:27 +02:00
|
|
|
private let navigationRootCoordinator: NavigationRootCoordinator
|
2022-06-15 10:10:22 +01:00
|
|
|
private let userSessionStore: UserSessionStoreProtocol
|
2022-11-28 18:42:49 +03:00
|
|
|
/// Common background task to resume long-running tasks in the background.
|
|
|
|
/// When this task expiring, we'll try to suspend the state machine by `suspend` event.
|
|
|
|
private var backgroundTask: BackgroundTaskProtocol?
|
2023-01-25 19:45:01 +02:00
|
|
|
private var isSuspended = false
|
2022-02-14 18:05:21 +02:00
|
|
|
|
2022-09-15 12:41:37 +03:00
|
|
|
private var userSession: UserSessionProtocol! {
|
|
|
|
didSet {
|
2023-01-31 11:51:56 +02:00
|
|
|
userSessionCancellables.removeAll()
|
|
|
|
|
|
|
|
if userSession != nil {
|
2022-11-21 19:37:13 +03:00
|
|
|
configureNotificationManager()
|
2022-09-15 12:41:37 +03:00
|
|
|
observeUserSessionChanges()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-05-26 16:04:48 +03:00
|
|
|
|
2022-09-25 12:34:11 +03:00
|
|
|
private var userSessionFlowCoordinator: UserSessionFlowCoordinator?
|
2022-11-16 15:37:34 +02:00
|
|
|
private var authenticationCoordinator: AuthenticationCoordinator?
|
2022-09-25 12:34:11 +03:00
|
|
|
|
2022-06-06 12:38:07 +03:00
|
|
|
private let bugReportService: BugReportServiceProtocol
|
2022-06-29 13:03:28 +03:00
|
|
|
private let backgroundTaskService: BackgroundTaskServiceProtocol
|
2022-06-06 12:38:07 +03:00
|
|
|
|
2022-12-19 18:29:14 +02:00
|
|
|
private var userSessionCancellables = Set<AnyCancellable>()
|
2022-09-15 12:41:37 +03:00
|
|
|
private var cancellables = Set<AnyCancellable>()
|
2022-11-21 19:37:13 +03:00
|
|
|
private(set) var notificationManager: NotificationManagerProtocol?
|
|
|
|
|
2022-02-14 18:05:21 +02:00
|
|
|
init() {
|
2022-12-12 12:31:27 +02:00
|
|
|
navigationRootCoordinator = NavigationRootCoordinator()
|
2022-12-16 10:02:22 +02:00
|
|
|
|
|
|
|
Self.setupServiceLocator(navigationRootCoordinator: navigationRootCoordinator)
|
|
|
|
Self.setupLogging()
|
|
|
|
|
2022-05-27 13:02:36 +03:00
|
|
|
stateMachine = AppCoordinatorStateMachine()
|
2022-09-12 21:34:53 +03:00
|
|
|
|
2022-12-16 10:02:22 +02:00
|
|
|
bugReportService = BugReportService(withBaseURL: ServiceLocator.shared.settings.bugReportServiceBaseURL, sentryURL: ServiceLocator.shared.settings.bugReportSentryURL)
|
2022-06-06 12:38:07 +03:00
|
|
|
|
2022-12-12 12:31:27 +02:00
|
|
|
navigationRootCoordinator.setRootCoordinator(SplashScreenCoordinator())
|
2022-11-21 19:37:13 +03:00
|
|
|
|
2022-11-28 18:42:49 +03:00
|
|
|
backgroundTaskService = UIKitBackgroundTaskService {
|
|
|
|
UIApplication.shared
|
|
|
|
}
|
2022-11-21 19:37:13 +03:00
|
|
|
|
|
|
|
userSessionStore = UserSessionStore(backgroundTaskService: backgroundTaskService)
|
2022-04-05 10:01:16 +03:00
|
|
|
|
2023-01-31 17:48:24 +00:00
|
|
|
guard let currentVersion = Version(InfoPlistReader(bundle: .main).bundleShortVersionString) else {
|
|
|
|
fatalError("The app's version number **must** use semver for migration purposes.")
|
|
|
|
}
|
|
|
|
|
|
|
|
if let previousVersion = ServiceLocator.shared.settings.lastVersionLaunched.flatMap(Version.init) {
|
|
|
|
performMigrationsIfNecessary(from: previousVersion, to: currentVersion)
|
|
|
|
} else {
|
|
|
|
// The app has been deleted since the previous run. Reset everything.
|
|
|
|
wipeUserData(includingSettings: true)
|
2022-12-16 10:02:22 +02:00
|
|
|
}
|
2023-01-31 17:48:24 +00:00
|
|
|
ServiceLocator.shared.settings.lastVersionLaunched = currentVersion.description
|
2022-05-27 13:02:36 +03:00
|
|
|
|
2022-12-16 10:02:22 +02:00
|
|
|
setupStateMachine()
|
2022-04-05 10:01:16 +03:00
|
|
|
|
2022-11-16 15:37:34 +02:00
|
|
|
Bundle.elementFallbackLanguage = "en"
|
2022-11-28 18:42:49 +03:00
|
|
|
|
2022-12-19 18:29:14 +02:00
|
|
|
observeApplicationState()
|
|
|
|
observeNetworkState()
|
2022-02-14 18:05:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func start() {
|
2023-01-11 15:10:26 +02:00
|
|
|
guard stateMachine.state == .initial else {
|
|
|
|
MXLog.error("Received a start request when already started")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-06-14 17:36:28 +01:00
|
|
|
stateMachine.processEvent(userSessionStore.hasSessions ? .startWithExistingSession : .startWithAuthentication)
|
2022-02-14 18:05:21 +02:00
|
|
|
}
|
2022-09-15 12:41:37 +03:00
|
|
|
|
2022-09-23 15:04:35 +03:00
|
|
|
func stop() {
|
|
|
|
hideLoadingIndicator()
|
|
|
|
}
|
2022-09-25 12:34:11 +03:00
|
|
|
|
2022-11-16 15:37:34 +02:00
|
|
|
func toPresentable() -> AnyView {
|
2023-02-14 11:09:55 +01:00
|
|
|
ServiceLocator.shared.userIndicatorController.toPresentable()
|
2022-11-16 15:37:34 +02:00
|
|
|
}
|
|
|
|
|
2022-05-27 13:02:36 +03:00
|
|
|
// MARK: - Private
|
|
|
|
|
2022-12-16 10:02:22 +02:00
|
|
|
private static func setupServiceLocator(navigationRootCoordinator: NavigationRootCoordinator) {
|
2023-02-14 11:09:55 +01:00
|
|
|
ServiceLocator.shared.register(userIndicatorController: UserIndicatorController(rootCoordinator: navigationRootCoordinator))
|
2022-12-16 10:02:22 +02:00
|
|
|
ServiceLocator.shared.register(appSettings: AppSettings())
|
2022-12-19 18:29:14 +02:00
|
|
|
ServiceLocator.shared.register(networkMonitor: NetworkMonitor())
|
2022-12-16 10:02:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private static func setupLogging() {
|
2022-07-27 16:06:40 +03:00
|
|
|
let loggerConfiguration = MXLogConfiguration()
|
2022-11-21 19:37:13 +03:00
|
|
|
loggerConfiguration.maxLogFilesCount = 10
|
2022-07-27 16:06:40 +03:00
|
|
|
|
|
|
|
#if DEBUG
|
2022-11-24 10:16:59 +00:00
|
|
|
setupTracing(configuration: .debug)
|
2022-07-27 16:06:40 +03:00
|
|
|
loggerConfiguration.logLevel = .debug
|
|
|
|
#else
|
2022-11-24 10:16:59 +00:00
|
|
|
setupTracing(configuration: .release)
|
2022-07-27 16:06:40 +03:00
|
|
|
loggerConfiguration.logLevel = .info
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Avoid redirecting NSLogs to files if we are attached to a debugger.
|
|
|
|
if isatty(STDERR_FILENO) == 0 {
|
|
|
|
loggerConfiguration.redirectLogsToFiles = true
|
|
|
|
}
|
|
|
|
|
|
|
|
MXLog.configure(loggerConfiguration)
|
|
|
|
}
|
|
|
|
|
2023-01-31 17:48:24 +00:00
|
|
|
/// Perform any required migrations for the app to function correctly.
|
|
|
|
private func performMigrationsIfNecessary(from oldVersion: Version, to newVersion: Version) {
|
|
|
|
guard oldVersion != newVersion else { return }
|
|
|
|
|
2023-02-08 19:07:02 +00:00
|
|
|
if oldVersion < Version(1, 0, 20) {
|
|
|
|
// Version 1.0.20 introduced a new format for restoration tokens.
|
|
|
|
// Remove user data to prevent conflict in the crypto store
|
|
|
|
// with the newly generated device ID when signing in again.
|
2023-01-31 17:48:24 +00:00
|
|
|
MXLog.warning("Clearing user data for hardcoded proxy.")
|
|
|
|
wipeUserData()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Clears the keychain, app support directory etc ready for a fresh use.
|
|
|
|
/// - Parameter includingSettings: Whether to additionally wipe the user's app settings too.
|
|
|
|
private func wipeUserData(includingSettings: Bool = false) {
|
|
|
|
if includingSettings {
|
|
|
|
AppSettings.reset()
|
|
|
|
}
|
|
|
|
userSessionStore.reset()
|
|
|
|
}
|
|
|
|
|
2022-09-23 12:21:41 +03:00
|
|
|
// swiftlint:disable:next cyclomatic_complexity
|
2022-05-27 13:02:36 +03:00
|
|
|
private func setupStateMachine() {
|
|
|
|
stateMachine.addTransitionHandler { [weak self] context in
|
2022-10-17 09:56:17 +01:00
|
|
|
guard let self else { return }
|
2022-06-14 17:36:28 +01:00
|
|
|
|
2022-05-27 13:02:36 +03:00
|
|
|
switch (context.fromState, context.event, context.toState) {
|
2022-06-14 17:36:28 +01:00
|
|
|
case (.initial, .startWithAuthentication, .signedOut):
|
2022-06-15 10:10:22 +01:00
|
|
|
self.startAuthentication()
|
2022-09-25 12:34:11 +03:00
|
|
|
case (.signedOut, .succeededSigningIn, .signedIn):
|
|
|
|
self.setupUserSession()
|
2022-06-14 17:36:28 +01:00
|
|
|
case (.initial, .startWithExistingSession, .restoringSession):
|
|
|
|
self.restoreUserSession()
|
|
|
|
case (.restoringSession, .failedRestoringSession, .signedOut):
|
|
|
|
self.showLoginErrorToast()
|
2023-03-02 14:35:23 +02:00
|
|
|
self.presentSplashScreen()
|
2022-09-25 12:34:11 +03:00
|
|
|
case (.restoringSession, .succeededRestoringSession, .signedIn):
|
|
|
|
self.setupUserSession()
|
2022-09-15 12:41:37 +03:00
|
|
|
case (_, .signOut, .signingOut):
|
2023-01-31 11:51:56 +02:00
|
|
|
self.logout(isSoftLogout: false)
|
2022-09-15 12:41:37 +03:00
|
|
|
case (.signingOut, .completedSigningOut, .signedOut):
|
2023-01-31 11:51:56 +02:00
|
|
|
self.tearDownUserSession()
|
2022-09-15 12:41:37 +03:00
|
|
|
self.presentSplashScreen()
|
2023-03-02 14:35:23 +02:00
|
|
|
case (_, .remoteSignOut(let isSoftLogout), .remoteSigningOut):
|
|
|
|
self.logout(isSoftLogout: isSoftLogout)
|
|
|
|
case (.remoteSigningOut(let isSoftLogout), .completedSigningOut, .signedOut):
|
2023-01-31 11:51:56 +02:00
|
|
|
self.tearDownUserSession()
|
2023-03-02 14:35:23 +02:00
|
|
|
self.presentSplashScreen(isSoftLogout: isSoftLogout)
|
2022-05-27 13:02:36 +03:00
|
|
|
default:
|
|
|
|
fatalError("Unknown transition: \(context)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
stateMachine.addErrorHandler { context in
|
|
|
|
fatalError("Failed transition with context: \(context)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-14 17:36:28 +01:00
|
|
|
private func restoreUserSession() {
|
|
|
|
Task {
|
|
|
|
switch await userSessionStore.restoreUserSession() {
|
|
|
|
case .success(let userSession):
|
|
|
|
self.userSession = userSession
|
2022-09-15 12:41:37 +03:00
|
|
|
if userSession.isSoftLogout {
|
|
|
|
stateMachine.processEvent(.remoteSignOut(isSoft: true))
|
|
|
|
} else {
|
|
|
|
stateMachine.processEvent(.succeededRestoringSession)
|
|
|
|
}
|
2022-06-14 17:36:28 +01:00
|
|
|
case .failure:
|
|
|
|
MXLog.error("Failed to restore an existing session.")
|
|
|
|
stateMachine.processEvent(.failedRestoringSession)
|
|
|
|
}
|
2022-05-26 16:04:48 +03:00
|
|
|
}
|
2022-06-14 17:36:28 +01:00
|
|
|
}
|
|
|
|
|
2022-06-15 10:10:22 +01:00
|
|
|
private func startAuthentication() {
|
2022-12-12 12:31:27 +02:00
|
|
|
let authenticationNavigationStackCoordinator = NavigationStackCoordinator()
|
2022-07-27 10:57:16 +01:00
|
|
|
let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore)
|
2022-11-16 15:37:34 +02:00
|
|
|
authenticationCoordinator = AuthenticationCoordinator(authenticationService: authenticationService,
|
2022-12-12 12:31:27 +02:00
|
|
|
navigationStackCoordinator: authenticationNavigationStackCoordinator)
|
2022-11-16 15:37:34 +02:00
|
|
|
authenticationCoordinator?.delegate = self
|
2022-05-26 16:04:48 +03:00
|
|
|
|
2022-11-16 15:37:34 +02:00
|
|
|
authenticationCoordinator?.start()
|
2022-12-12 12:31:27 +02:00
|
|
|
|
|
|
|
navigationRootCoordinator.setRootCoordinator(authenticationNavigationStackCoordinator)
|
2022-02-14 18:05:21 +02:00
|
|
|
}
|
2022-09-15 12:41:37 +03:00
|
|
|
|
|
|
|
private func startAuthenticationSoftLogout() {
|
|
|
|
Task {
|
|
|
|
var displayName = ""
|
|
|
|
if case .success(let name) = await userSession.clientProxy.loadUserDisplayName() {
|
|
|
|
displayName = name
|
|
|
|
}
|
2022-11-16 15:37:34 +02:00
|
|
|
|
2022-09-15 12:41:37 +03:00
|
|
|
let credentials = SoftLogoutCredentials(userId: userSession.userID,
|
|
|
|
homeserverName: userSession.homeserver,
|
|
|
|
userDisplayName: displayName,
|
2023-02-24 14:51:36 +02:00
|
|
|
deviceId: userSession.deviceID)
|
2022-11-16 15:37:34 +02:00
|
|
|
|
2022-09-15 12:41:37 +03:00
|
|
|
let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore)
|
|
|
|
_ = await authenticationService.configure(for: userSession.homeserver)
|
|
|
|
|
|
|
|
let parameters = SoftLogoutCoordinatorParameters(authenticationService: authenticationService,
|
|
|
|
credentials: credentials,
|
|
|
|
keyBackupNeeded: false)
|
|
|
|
let coordinator = SoftLogoutCoordinator(parameters: parameters)
|
|
|
|
coordinator.callback = { result in
|
|
|
|
switch result {
|
|
|
|
case .signedIn(let session):
|
|
|
|
self.userSession = session
|
|
|
|
self.stateMachine.processEvent(.succeededSigningIn)
|
|
|
|
case .clearAllData:
|
2022-09-23 12:21:41 +03:00
|
|
|
// clear user data
|
|
|
|
self.userSessionStore.logout(userSession: self.userSession)
|
|
|
|
self.userSession = nil
|
|
|
|
self.startAuthentication()
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
|
|
|
}
|
2022-11-16 15:37:34 +02:00
|
|
|
|
2022-12-12 12:31:27 +02:00
|
|
|
navigationRootCoordinator.setRootCoordinator(coordinator)
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
|
|
|
}
|
2022-02-14 18:05:21 +02:00
|
|
|
|
2022-09-25 12:34:11 +03:00
|
|
|
private func setupUserSession() {
|
2022-12-12 12:31:27 +02:00
|
|
|
let navigationSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: SplashScreenCoordinator())
|
2022-09-25 12:34:11 +03:00
|
|
|
let userSessionFlowCoordinator = UserSessionFlowCoordinator(userSession: userSession,
|
2022-12-12 12:31:27 +02:00
|
|
|
navigationSplitCoordinator: navigationSplitCoordinator,
|
2022-12-15 15:22:39 +02:00
|
|
|
bugReportService: bugReportService,
|
|
|
|
roomTimelineControllerFactory: RoomTimelineControllerFactory())
|
2022-09-25 12:34:11 +03:00
|
|
|
|
|
|
|
userSessionFlowCoordinator.callback = { [weak self] action in
|
|
|
|
switch action {
|
|
|
|
case .signOut:
|
|
|
|
self?.stateMachine.processEvent(.signOut)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
userSessionFlowCoordinator.start()
|
|
|
|
|
|
|
|
self.userSessionFlowCoordinator = userSessionFlowCoordinator
|
2022-12-12 12:31:27 +02:00
|
|
|
|
|
|
|
navigationRootCoordinator.setRootCoordinator(navigationSplitCoordinator)
|
2022-09-25 12:34:11 +03:00
|
|
|
}
|
|
|
|
|
2023-01-31 11:51:56 +02:00
|
|
|
private func logout(isSoftLogout: Bool) {
|
2023-03-02 14:35:23 +02:00
|
|
|
showLoadingIndicator()
|
|
|
|
|
|
|
|
defer {
|
|
|
|
hideLoadingIndicator()
|
|
|
|
}
|
|
|
|
|
2022-09-21 11:21:58 +03:00
|
|
|
userSession.clientProxy.stopSync()
|
2022-09-25 12:34:11 +03:00
|
|
|
userSessionFlowCoordinator?.stop()
|
2022-09-21 11:21:58 +03:00
|
|
|
|
2023-01-11 15:10:26 +02:00
|
|
|
guard !isSoftLogout else {
|
|
|
|
stateMachine.processEvent(.completedSigningOut)
|
|
|
|
return
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
2022-09-21 11:21:58 +03:00
|
|
|
|
2023-01-11 15:10:26 +02:00
|
|
|
Task {
|
|
|
|
// first log out from the server
|
|
|
|
_ = await userSession.clientProxy.logout()
|
|
|
|
|
|
|
|
// regardless of the result, clear user data
|
|
|
|
userSessionStore.logout(userSession: userSession)
|
|
|
|
|
|
|
|
stateMachine.processEvent(.completedSigningOut)
|
|
|
|
}
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
2023-01-31 11:51:56 +02:00
|
|
|
|
|
|
|
private func tearDownUserSession() {
|
|
|
|
userSession = nil
|
|
|
|
|
|
|
|
userSessionFlowCoordinator = nil
|
|
|
|
|
|
|
|
notificationManager?.delegate = nil
|
|
|
|
notificationManager = nil
|
|
|
|
}
|
2022-09-15 12:41:37 +03:00
|
|
|
|
|
|
|
private func presentSplashScreen(isSoftLogout: Bool = false) {
|
2022-12-12 12:31:27 +02:00
|
|
|
navigationRootCoordinator.setRootCoordinator(SplashScreenCoordinator())
|
2022-11-16 15:37:34 +02:00
|
|
|
|
2022-09-15 12:41:37 +03:00
|
|
|
if isSoftLogout {
|
|
|
|
startAuthenticationSoftLogout()
|
|
|
|
} else {
|
|
|
|
startAuthentication()
|
|
|
|
}
|
2022-02-14 18:05:21 +02:00
|
|
|
}
|
2022-11-21 19:37:13 +03:00
|
|
|
|
|
|
|
private func configureNotificationManager() {
|
2022-12-16 10:02:22 +02:00
|
|
|
guard ServiceLocator.shared.settings.enableNotifications else {
|
2022-11-21 19:37:13 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
guard notificationManager == nil else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let manager = NotificationManager(clientProxy: userSession.clientProxy)
|
|
|
|
if manager.isAvailable {
|
|
|
|
manager.delegate = self
|
|
|
|
notificationManager = manager
|
|
|
|
manager.start()
|
|
|
|
|
|
|
|
if let appDelegate = AppDelegate.shared {
|
|
|
|
appDelegate.callbacks
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { [weak self] callback in
|
|
|
|
switch callback {
|
|
|
|
case .registeredNotifications(let deviceToken):
|
2023-01-05 16:43:41 +00:00
|
|
|
Task { await self?.notificationManager?.register(with: deviceToken) }
|
2022-11-21 19:37:13 +03:00
|
|
|
case .failedToRegisteredNotifications(let error):
|
|
|
|
self?.notificationManager?.registrationFailed(with: error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
|
|
|
} else {
|
2023-01-17 09:28:01 +00:00
|
|
|
MXLog.error("Couldn't register to AppDelegate callbacks")
|
2022-11-21 19:37:13 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-14 18:05:21 +02:00
|
|
|
|
2022-09-15 12:41:37 +03:00
|
|
|
private func observeUserSessionChanges() {
|
|
|
|
userSession.callbacks
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { [weak self] callback in
|
2022-10-17 09:56:17 +01:00
|
|
|
guard let self else { return }
|
2022-09-15 12:41:37 +03:00
|
|
|
switch callback {
|
|
|
|
case .didReceiveAuthError(let isSoftLogout):
|
|
|
|
self.stateMachine.processEvent(.remoteSignOut(isSoft: isSoftLogout))
|
|
|
|
case .updateRestoreTokenNeeded:
|
|
|
|
if let userSession = self.userSession {
|
2022-11-04 13:24:53 +02:00
|
|
|
_ = self.userSessionStore.refreshRestorationToken(for: userSession)
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
2022-09-23 13:21:54 +03:00
|
|
|
}
|
2022-12-19 18:29:14 +02:00
|
|
|
.store(in: &userSessionCancellables)
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
2022-02-14 18:05:21 +02:00
|
|
|
|
2022-07-01 13:56:52 +03:00
|
|
|
// MARK: Toasts and loading indicators
|
|
|
|
|
2022-11-16 15:37:34 +02:00
|
|
|
static let loadingIndicatorIdentifier = "AppCoordinatorLoading"
|
|
|
|
|
2022-07-01 13:56:52 +03:00
|
|
|
private func showLoadingIndicator() {
|
2023-02-14 11:09:55 +01:00
|
|
|
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
|
|
|
|
type: .modal,
|
|
|
|
title: ElementL10n.loading,
|
|
|
|
persistent: true))
|
2022-07-01 13:56:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private func hideLoadingIndicator() {
|
2023-02-14 11:09:55 +01:00
|
|
|
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
|
2022-07-01 13:56:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private func showLoginErrorToast() {
|
2023-02-14 11:09:55 +01:00
|
|
|
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(title: "Failed logging in"))
|
2022-07-01 13:56:52 +03:00
|
|
|
}
|
2022-11-28 18:42:49 +03:00
|
|
|
|
|
|
|
// MARK: - Application State
|
|
|
|
|
|
|
|
private func pause() {
|
|
|
|
userSession?.clientProxy.stopSync()
|
|
|
|
}
|
|
|
|
|
|
|
|
private func resume() {
|
|
|
|
userSession?.clientProxy.startSync()
|
|
|
|
}
|
|
|
|
|
2022-12-19 18:29:14 +02:00
|
|
|
private func observeApplicationState() {
|
2022-11-28 18:42:49 +03:00
|
|
|
NotificationCenter.default.addObserver(self,
|
|
|
|
selector: #selector(applicationWillResignActive),
|
|
|
|
name: UIApplication.willResignActiveNotification,
|
|
|
|
object: nil)
|
|
|
|
NotificationCenter.default.addObserver(self,
|
|
|
|
selector: #selector(applicationDidBecomeActive),
|
|
|
|
name: UIApplication.didBecomeActiveNotification,
|
|
|
|
object: nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc
|
|
|
|
private func applicationWillResignActive() {
|
2023-01-18 17:13:44 +02:00
|
|
|
MXLog.info("Application will resign active")
|
|
|
|
|
2022-11-28 18:42:49 +03:00
|
|
|
guard backgroundTask == nil else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
backgroundTask = backgroundTaskService.startBackgroundTask(withName: "SuspendApp: \(UUID().uuidString)") { [weak self] in
|
|
|
|
self?.pause()
|
|
|
|
|
|
|
|
self?.backgroundTask = nil
|
|
|
|
self?.isSuspended = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc
|
|
|
|
private func applicationDidBecomeActive() {
|
2023-01-18 17:13:44 +02:00
|
|
|
MXLog.info("Application did become active")
|
|
|
|
|
2022-11-28 18:42:49 +03:00
|
|
|
backgroundTask?.stop()
|
|
|
|
backgroundTask = nil
|
|
|
|
|
|
|
|
if isSuspended {
|
|
|
|
isSuspended = false
|
|
|
|
resume()
|
|
|
|
}
|
|
|
|
}
|
2022-12-19 18:29:14 +02:00
|
|
|
|
|
|
|
private func observeNetworkState() {
|
|
|
|
let reachabilityNotificationIdentifier = "io.element.elementx.reachability.notification"
|
|
|
|
ServiceLocator.shared.networkMonitor.reachabilityPublisher.sink { reachable in
|
2023-01-18 17:13:44 +02:00
|
|
|
MXLog.info("Reachability changed to \(reachable)")
|
|
|
|
|
2022-12-19 18:29:14 +02:00
|
|
|
if reachable {
|
2023-02-14 11:09:55 +01:00
|
|
|
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(reachabilityNotificationIdentifier)
|
2022-12-19 18:29:14 +02:00
|
|
|
} else {
|
2023-02-14 11:09:55 +01:00
|
|
|
ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: reachabilityNotificationIdentifier,
|
|
|
|
title: ElementL10n.a11yPresenceOffline,
|
|
|
|
persistent: true))
|
2022-12-19 18:29:14 +02:00
|
|
|
}
|
|
|
|
}.store(in: &cancellables)
|
|
|
|
}
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - AuthenticationCoordinatorDelegate
|
|
|
|
|
|
|
|
extension AppCoordinator: AuthenticationCoordinatorDelegate {
|
|
|
|
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator, didLoginWithSession userSession: UserSessionProtocol) {
|
|
|
|
self.userSession = userSession
|
|
|
|
stateMachine.processEvent(.succeededSigningIn)
|
2022-07-01 13:56:52 +03:00
|
|
|
}
|
2022-02-14 18:05:21 +02:00
|
|
|
}
|
2022-11-21 19:37:13 +03:00
|
|
|
|
|
|
|
// MARK: - NotificationManagerDelegate
|
|
|
|
|
|
|
|
extension AppCoordinator: NotificationManagerDelegate {
|
|
|
|
func authorizationStatusUpdated(_ service: NotificationManagerProtocol, granted: Bool) {
|
|
|
|
if granted {
|
|
|
|
UIApplication.shared.registerForRemoteNotifications()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func shouldDisplayInAppNotification(_ service: NotificationManagerProtocol, content: UNNotificationContent) -> Bool {
|
|
|
|
guard let roomId = content.userInfo[NotificationConstants.UserInfoKey.roomIdentifier] as? String else {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
guard let userSessionFlowCoordinator else {
|
|
|
|
// there is not a user session yet
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return !userSessionFlowCoordinator.isDisplayingRoomScreen(withRoomId: roomId)
|
|
|
|
}
|
|
|
|
|
|
|
|
func notificationTapped(_ service: NotificationManagerProtocol, content: UNNotificationContent) async {
|
2023-01-17 09:28:01 +00:00
|
|
|
MXLog.info("[AppCoordinator] tappedNotification")
|
2022-11-21 19:37:13 +03:00
|
|
|
|
|
|
|
guard let roomId = content.userInfo[NotificationConstants.UserInfoKey.roomIdentifier] as? String else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
userSessionFlowCoordinator?.tryDisplayingRoomScreen(roomId: roomId)
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleInlineReply(_ service: NotificationManagerProtocol, content: UNNotificationContent, replyText: String) async {
|
2023-01-17 09:28:01 +00:00
|
|
|
MXLog.info("[AppCoordinator] handle notification reply")
|
2022-11-21 19:37:13 +03:00
|
|
|
|
|
|
|
guard let roomId = content.userInfo[NotificationConstants.UserInfoKey.roomIdentifier] as? String else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
let roomProxy = await userSession.clientProxy.roomForIdentifier(roomId)
|
|
|
|
switch await roomProxy?.sendMessage(replyText) {
|
|
|
|
case .success:
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
// error or no room proxy
|
2023-01-05 16:43:41 +00:00
|
|
|
await service.showLocalNotification(with: "⚠️ " + ElementL10n.dialogTitleError,
|
|
|
|
subtitle: ElementL10n.a11yErrorSomeMessageNotSent)
|
2022-11-21 19:37:13 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|