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
|
|
|
//
|
|
|
|
|
2023-05-15 13:06:25 +03:00
|
|
|
import BackgroundTasks
|
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
|
|
|
|
2023-10-11 13:59:47 +01:00
|
|
|
class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, NotificationManagerDelegate, WindowManagerDelegate {
|
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
|
2023-09-05 17:21:53 +01:00
|
|
|
private let appSettings: AppSettings
|
2023-09-27 11:35:41 -07:00
|
|
|
private let appDelegate: AppDelegate
|
|
|
|
|
2023-08-08 15:34:35 +03:00
|
|
|
/// Common background task to continue long-running tasks in the background.
|
2022-11-28 18:42:49 +03:00
|
|
|
private var backgroundTask: BackgroundTaskProtocol?
|
2023-05-19 18:22:27 +03:00
|
|
|
|
2023-01-25 19:45:01 +02:00
|
|
|
private var isSuspended = false
|
2022-02-14 18:05:21 +02:00
|
|
|
|
2023-05-17 17:37:16 +03:00
|
|
|
private var userSession: UserSessionProtocol? {
|
2022-09-15 12:41:37 +03:00
|
|
|
didSet {
|
2023-05-10 19:26:59 +03:00
|
|
|
userSessionObserver?.cancel()
|
2023-01-31 11:51:56 +02:00
|
|
|
if userSession != nil {
|
2022-11-21 19:37:13 +03:00
|
|
|
configureNotificationManager()
|
2022-09-15 12:41:37 +03:00
|
|
|
observeUserSessionChanges()
|
2023-05-10 19:26:59 +03:00
|
|
|
startSync()
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-05-26 16:04:48 +03:00
|
|
|
|
2022-11-16 15:37:34 +02:00
|
|
|
private var authenticationCoordinator: AuthenticationCoordinator?
|
2023-10-11 13:59:47 +01:00
|
|
|
private let appLockFlowCoordinator: AppLockFlowCoordinator
|
2023-10-30 15:52:25 +00:00
|
|
|
private var appLockSetupFlowCoordinator: AppLockSetupFlowCoordinator?
|
2023-10-11 13:59:47 +01:00
|
|
|
private var userSessionFlowCoordinator: UserSessionFlowCoordinator?
|
2023-09-28 14:12:57 +01:00
|
|
|
private var softLogoutCoordinator: SoftLogoutScreenCoordinator?
|
2022-09-25 12:34:11 +03:00
|
|
|
|
2022-06-29 13:03:28 +03:00
|
|
|
private let backgroundTaskService: BackgroundTaskServiceProtocol
|
2022-06-06 12:38:07 +03:00
|
|
|
|
2023-05-10 19:26:59 +03:00
|
|
|
private var appDelegateObserver: AnyCancellable?
|
|
|
|
private var userSessionObserver: AnyCancellable?
|
2023-06-22 15:04:20 +03:00
|
|
|
private var clientProxyObserver: AnyCancellable?
|
2023-09-14 12:53:33 +03:00
|
|
|
private var cancellables = Set<AnyCancellable>()
|
2023-05-10 19:26:59 +03:00
|
|
|
|
2023-10-11 13:59:47 +01:00
|
|
|
let windowManager = WindowManager()
|
2023-04-14 14:24:48 +02:00
|
|
|
let notificationManager: NotificationManagerProtocol
|
|
|
|
|
2023-09-11 12:31:31 +03:00
|
|
|
private let appRouteURLParser: AppRouteURLParser
|
2023-04-14 14:24:48 +02:00
|
|
|
@Consumable private var storedAppRoute: AppRoute?
|
2022-11-21 19:37:13 +03:00
|
|
|
|
2023-09-27 11:35:41 -07:00
|
|
|
init(appDelegate: AppDelegate) {
|
2023-09-08 14:34:59 +03:00
|
|
|
Self.setupEnvironmentVariables()
|
|
|
|
|
2023-08-23 14:58:44 +03:00
|
|
|
let appSettings = AppSettings()
|
2023-08-23 19:15:38 +03:00
|
|
|
|
|
|
|
if appSettings.otlpTracingEnabled {
|
|
|
|
MXLog.configure(logLevel: appSettings.logLevel, otlpConfiguration: .init(url: appSettings.otlpTracingURL,
|
|
|
|
username: appSettings.otlpTracingUsername,
|
|
|
|
password: appSettings.otlpTracingPassword))
|
|
|
|
} else {
|
|
|
|
MXLog.configure(logLevel: appSettings.logLevel)
|
|
|
|
}
|
2023-08-23 14:58:44 +03:00
|
|
|
|
2023-10-31 13:24:59 +00:00
|
|
|
let appName = InfoPlistReader.main.bundleDisplayName
|
|
|
|
let appVersion = InfoPlistReader.main.bundleShortVersionString
|
|
|
|
let appBuild = InfoPlistReader.main.bundleVersion
|
|
|
|
MXLog.info("\(appName) \(appVersion) (\(appBuild))")
|
|
|
|
|
2023-08-23 14:58:44 +03:00
|
|
|
if ProcessInfo.processInfo.environment["RESET_APP_SETTINGS"].map(Bool.init) == true {
|
|
|
|
AppSettings.reset()
|
|
|
|
}
|
2023-04-25 14:48:20 +03:00
|
|
|
|
2023-09-27 11:35:41 -07:00
|
|
|
self.appDelegate = appDelegate
|
2023-09-05 17:21:53 +01:00
|
|
|
self.appSettings = appSettings
|
2023-09-11 12:31:31 +03:00
|
|
|
appRouteURLParser = AppRouteURLParser(appSettings: appSettings)
|
2023-09-05 17:21:53 +01:00
|
|
|
|
2022-12-12 12:31:27 +02:00
|
|
|
navigationRootCoordinator = NavigationRootCoordinator()
|
2022-12-16 10:02:22 +02:00
|
|
|
|
2023-08-23 14:58:44 +03:00
|
|
|
Self.setupServiceLocator(navigationRootCoordinator: navigationRootCoordinator, appSettings: appSettings)
|
2023-09-08 14:34:59 +03:00
|
|
|
|
2023-04-18 09:33:32 +02:00
|
|
|
ServiceLocator.shared.analytics.startIfEnabled()
|
|
|
|
|
|
|
|
stateMachine = AppCoordinatorStateMachine()
|
|
|
|
|
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
|
|
|
|
2023-10-11 13:59:47 +01:00
|
|
|
let keychainController = KeychainController(service: .sessions,
|
|
|
|
accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier)
|
|
|
|
userSessionStore = UserSessionStore(keychainController: keychainController,
|
|
|
|
backgroundTaskService: backgroundTaskService)
|
|
|
|
|
|
|
|
let appLockService = AppLockService(keychainController: keychainController, appSettings: appSettings)
|
2023-10-25 12:03:37 +01:00
|
|
|
let appLockNavigationCoordinator = NavigationRootCoordinator()
|
|
|
|
let appLockFlowUserIndicatorController = UserIndicatorController(rootCoordinator: appLockNavigationCoordinator)
|
2023-10-11 13:59:47 +01:00
|
|
|
appLockFlowCoordinator = AppLockFlowCoordinator(appLockService: appLockService,
|
2023-10-25 12:03:37 +01:00
|
|
|
userIndicatorController: appLockFlowUserIndicatorController,
|
|
|
|
navigationCoordinator: appLockNavigationCoordinator)
|
2023-10-11 13:59:47 +01:00
|
|
|
|
2023-06-23 09:56:22 +03:00
|
|
|
notificationManager = NotificationManager(notificationCenter: UNUserNotificationCenter.current(),
|
2023-09-05 17:21:53 +01:00
|
|
|
appSettings: appSettings)
|
2023-10-11 13:59:47 +01:00
|
|
|
|
|
|
|
windowManager.delegate = self
|
|
|
|
|
2023-04-14 14:24:48 +02:00
|
|
|
notificationManager.delegate = self
|
|
|
|
notificationManager.start()
|
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.")
|
|
|
|
}
|
|
|
|
|
2023-09-05 17:21:53 +01:00
|
|
|
if let previousVersion = appSettings.lastVersionLaunched.flatMap(Version.init) {
|
2023-01-31 17:48:24 +00:00
|
|
|
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-09-05 17:21:53 +01:00
|
|
|
appSettings.lastVersionLaunched = currentVersion.description
|
2023-05-03 16:28:07 +02:00
|
|
|
|
2022-12-16 10:02:22 +02:00
|
|
|
setupStateMachine()
|
2022-11-28 18:42:49 +03:00
|
|
|
|
2022-12-19 18:29:14 +02:00
|
|
|
observeApplicationState()
|
|
|
|
observeNetworkState()
|
2023-10-11 13:59:47 +01:00
|
|
|
observeAppLockChanges()
|
2023-05-15 13:06:25 +03:00
|
|
|
|
|
|
|
registerBackgroundAppRefresh()
|
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
|
|
|
|
}
|
|
|
|
|
2023-10-30 15:52:25 +00:00
|
|
|
guard userSessionStore.hasSessions else {
|
|
|
|
stateMachine.processEvent(.startWithAuthentication)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-10-31 08:55:30 +00:00
|
|
|
if appSettings.appLockIsMandatory, !appLockFlowCoordinator.appLockService.isEnabled {
|
2023-10-30 15:52:25 +00:00
|
|
|
stateMachine.processEvent(.startWithAppLockSetup)
|
|
|
|
} else {
|
|
|
|
stateMachine.processEvent(.startWithExistingSession)
|
|
|
|
}
|
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-06-23 09:56:22 +03:00
|
|
|
AnyView(
|
|
|
|
ServiceLocator.shared.userIndicatorController.toPresentable()
|
|
|
|
.environment(\.analyticsService, ServiceLocator.shared.analytics)
|
|
|
|
)
|
2022-11-16 15:37:34 +02:00
|
|
|
}
|
2023-05-15 13:06:25 +03:00
|
|
|
|
2023-09-11 12:31:31 +03:00
|
|
|
func handleDeepLink(_ url: URL) -> Bool {
|
2023-09-05 17:21:53 +01:00
|
|
|
// Parse into an AppRoute to redirect these in a type safe way.
|
|
|
|
|
2023-09-11 12:31:31 +03:00
|
|
|
if let route = appRouteURLParser.route(from: url) {
|
2023-09-07 12:06:18 +03:00
|
|
|
switch route {
|
2023-09-28 14:12:57 +01:00
|
|
|
case .oidcCallback(let url):
|
|
|
|
if stateMachine.state == .softLogout {
|
|
|
|
softLogoutCoordinator?.handleOIDCRedirectURL(url)
|
|
|
|
} else {
|
|
|
|
authenticationCoordinator?.handleOIDCRedirectURL(url)
|
|
|
|
}
|
2023-09-07 12:06:18 +03:00
|
|
|
case .genericCallLink(let url):
|
|
|
|
if let userSessionFlowCoordinator {
|
|
|
|
userSessionFlowCoordinator.handleAppRoute(route, animated: true)
|
|
|
|
} else {
|
|
|
|
navigationRootCoordinator.setSheetCoordinator(GenericCallLinkCoordinator(parameters: .init(url: url)))
|
|
|
|
}
|
2023-10-04 15:47:57 +02:00
|
|
|
case .roomMemberDetails, .room:
|
2023-10-03 10:11:05 +02:00
|
|
|
userSessionFlowCoordinator?.handleAppRoute(route, animated: true)
|
2023-09-07 12:06:18 +03:00
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2023-09-11 12:31:31 +03:00
|
|
|
return true
|
2023-09-07 12:06:18 +03:00
|
|
|
}
|
|
|
|
|
2023-09-11 12:31:31 +03:00
|
|
|
return false
|
2023-09-05 17:21:53 +01:00
|
|
|
}
|
|
|
|
|
2023-05-15 13:06:25 +03:00
|
|
|
// MARK: - AuthenticationCoordinatorDelegate
|
|
|
|
|
|
|
|
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator, didLoginWithSession userSession: UserSessionProtocol) {
|
|
|
|
self.userSession = userSession
|
2023-09-28 14:12:57 +01:00
|
|
|
self.authenticationCoordinator = nil
|
2023-05-15 13:06:25 +03:00
|
|
|
stateMachine.processEvent(.createdUserSession)
|
|
|
|
}
|
|
|
|
|
2023-10-11 13:59:47 +01:00
|
|
|
// MARK: - WindowManagerDelegate
|
|
|
|
|
|
|
|
func windowManagerDidConfigureWindows(_ windowManager: WindowManager) {
|
|
|
|
windowManager.alternateWindow.rootViewController = UIHostingController(rootView: appLockFlowCoordinator.toPresentable())
|
|
|
|
}
|
|
|
|
|
2023-05-15 13:06:25 +03:00
|
|
|
// MARK: - NotificationManagerDelegate
|
|
|
|
|
2023-07-10 19:26:49 +02:00
|
|
|
func registerForRemoteNotifications() {
|
|
|
|
UIApplication.shared.registerForRemoteNotifications()
|
2023-05-15 13:06:25 +03:00
|
|
|
}
|
|
|
|
|
2023-07-10 19:26:49 +02:00
|
|
|
func unregisterForRemoteNotifications() {
|
|
|
|
UIApplication.shared.unregisterForRemoteNotifications()
|
|
|
|
}
|
|
|
|
|
2023-05-15 13:06:25 +03:00
|
|
|
func shouldDisplayInAppNotification(_ service: NotificationManagerProtocol, content: UNNotificationContent) -> Bool {
|
2023-07-18 11:25:07 +01:00
|
|
|
guard let roomID = content.roomID else {
|
2023-05-15 13:06:25 +03:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
guard let userSessionFlowCoordinator else {
|
|
|
|
// there is not a user session yet
|
|
|
|
return false
|
|
|
|
}
|
2023-07-18 11:25:07 +01:00
|
|
|
return !userSessionFlowCoordinator.isDisplayingRoomScreen(withRoomID: roomID)
|
2023-05-15 13:06:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func notificationTapped(_ service: NotificationManagerProtocol, content: UNNotificationContent) async {
|
|
|
|
MXLog.info("[AppCoordinator] tappedNotification")
|
|
|
|
|
|
|
|
guard let roomID = content.roomID,
|
|
|
|
content.receiverID != nil else {
|
|
|
|
return
|
|
|
|
}
|
2022-11-16 15:37:34 +02:00
|
|
|
|
2023-05-15 13:06:25 +03:00
|
|
|
// Handle here the account switching when available
|
2023-06-19 10:03:32 +02:00
|
|
|
|
|
|
|
switch content.categoryIdentifier {
|
|
|
|
case NotificationConstants.Category.message:
|
|
|
|
handleAppRoute(.room(roomID: roomID))
|
|
|
|
case NotificationConstants.Category.invite:
|
|
|
|
handleAppRoute(.invites)
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
2023-05-15 13:06:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func handleInlineReply(_ service: NotificationManagerProtocol, content: UNNotificationContent, replyText: String) async {
|
2023-05-17 17:37:16 +03:00
|
|
|
guard let userSession else {
|
|
|
|
fatalError("User session not setup")
|
|
|
|
}
|
|
|
|
|
2023-05-15 13:06:25 +03:00
|
|
|
MXLog.info("[AppCoordinator] handle notification reply")
|
|
|
|
|
2023-07-18 11:25:07 +01:00
|
|
|
guard let roomID = content.userInfo[NotificationConstants.UserInfoKey.roomIdentifier] as? String else {
|
2023-05-15 13:06:25 +03:00
|
|
|
return
|
|
|
|
}
|
2023-07-18 11:25:07 +01:00
|
|
|
let roomProxy = await userSession.clientProxy.roomForIdentifier(roomID)
|
2023-10-20 15:51:25 +02:00
|
|
|
switch await roomProxy?.sendMessage(replyText,
|
|
|
|
html: nil,
|
|
|
|
intentionalMentions: .empty) {
|
2023-05-15 13:06:25 +03:00
|
|
|
case .success:
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
// error or no room proxy
|
|
|
|
await service.showLocalNotification(with: "⚠️ " + L10n.commonError,
|
|
|
|
subtitle: L10n.errorSomeMessagesHaveNotBeenSent)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-27 13:02:36 +03:00
|
|
|
// MARK: - Private
|
|
|
|
|
2023-09-08 14:34:59 +03:00
|
|
|
private static func setupEnvironmentVariables() {
|
|
|
|
setenv("RUST_BACKTRACE", "1", 1)
|
|
|
|
}
|
|
|
|
|
2023-08-23 14:58:44 +03:00
|
|
|
private static func setupServiceLocator(navigationRootCoordinator: NavigationRootCoordinator, appSettings: AppSettings) {
|
2023-02-14 11:09:55 +01:00
|
|
|
ServiceLocator.shared.register(userIndicatorController: UserIndicatorController(rootCoordinator: navigationRootCoordinator))
|
2023-08-23 14:58:44 +03:00
|
|
|
ServiceLocator.shared.register(appSettings: appSettings)
|
2022-12-19 18:29:14 +02:00
|
|
|
ServiceLocator.shared.register(networkMonitor: NetworkMonitor())
|
2023-09-05 17:21:53 +01:00
|
|
|
ServiceLocator.shared.register(bugReportService: BugReportService(withBaseURL: appSettings.bugReportServiceBaseURL,
|
|
|
|
sentryURL: appSettings.bugReportSentryURL,
|
|
|
|
applicationId: appSettings.bugReportApplicationId,
|
2023-08-01 17:28:56 +03:00
|
|
|
sdkGitSHA: sdkGitSha(),
|
2023-09-05 17:21:53 +01:00
|
|
|
maxUploadSize: appSettings.bugReportMaxUploadSize))
|
2023-06-23 09:56:22 +03:00
|
|
|
ServiceLocator.shared.register(analytics: AnalyticsService(client: PostHogAnalyticsClient(),
|
2023-09-05 17:21:53 +01:00
|
|
|
appSettings: appSettings,
|
2023-06-23 09:56:22 +03:00
|
|
|
bugReportService: ServiceLocator.shared.bugReportService))
|
2022-12-16 10:02:22 +02:00
|
|
|
}
|
|
|
|
|
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-10-31 13:24:59 +00:00
|
|
|
MXLog.info("The app was upgraded from \(oldVersion) to \(newVersion)")
|
|
|
|
|
2023-03-31 14:42:05 +03:00
|
|
|
if oldVersion < Version(1, 1, 0) {
|
2023-06-30 13:27:49 +01:00
|
|
|
MXLog.info("Migrating to v1.1.0, signing out the user.")
|
2023-03-31 14:42:05 +03:00
|
|
|
// Version 1.1.0 switched the Rust crypto store to SQLite
|
2023-10-27 10:09:12 +01:00
|
|
|
// There are no migrations in place so we need to sign the user out
|
2023-01-31 17:48:24 +00:00
|
|
|
wipeUserData()
|
|
|
|
}
|
2023-06-30 13:27:49 +01:00
|
|
|
|
|
|
|
if oldVersion < Version(1, 1, 7) {
|
2023-10-31 13:24:59 +00:00
|
|
|
MXLog.info("Migrating to v1.1.7, marking accounts as migrated.")
|
2023-06-30 13:27:49 +01:00
|
|
|
for userID in userSessionStore.userIDs {
|
2023-09-05 17:21:53 +01:00
|
|
|
appSettings.migratedAccounts[userID] = true
|
2023-06-30 13:27:49 +01:00
|
|
|
}
|
|
|
|
}
|
2023-01-31 17:48:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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()
|
2023-10-27 10:09:12 +01:00
|
|
|
appLockFlowCoordinator.appLockService.disable()
|
2023-01-31 17:48:24 +00:00
|
|
|
}
|
|
|
|
userSessionStore.reset()
|
|
|
|
}
|
|
|
|
|
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):
|
2023-10-30 15:52:25 +00:00
|
|
|
startAuthentication()
|
2023-03-06 14:58:50 +02:00
|
|
|
case (.signedOut, .createdUserSession, .signedIn):
|
2023-10-30 15:52:25 +00:00
|
|
|
setupUserSession()
|
2022-06-14 17:36:28 +01:00
|
|
|
case (.initial, .startWithExistingSession, .restoringSession):
|
2023-10-30 15:52:25 +00:00
|
|
|
restoreUserSession()
|
2022-06-14 17:36:28 +01:00
|
|
|
case (.restoringSession, .failedRestoringSession, .signedOut):
|
2023-10-30 15:52:25 +00:00
|
|
|
showLoginErrorToast()
|
|
|
|
presentSplashScreen()
|
2023-03-06 14:58:50 +02:00
|
|
|
case (.restoringSession, .createdUserSession, .signedIn):
|
2023-10-30 15:52:25 +00:00
|
|
|
setupUserSession()
|
|
|
|
|
|
|
|
case (.initial, .startWithAppLockSetup, .mandatoryAppLockSetup):
|
|
|
|
startMandatoryAppLockSetup()
|
|
|
|
case (.mandatoryAppLockSetup, .appLockSetupComplete, .restoringSession):
|
|
|
|
restoreUserSession()
|
|
|
|
|
2023-05-11 16:46:36 +01:00
|
|
|
case (.signingOut, .signOut, .signingOut):
|
|
|
|
// We can ignore signOut when already in the process of signing out,
|
|
|
|
// such as the SDK sending an authError due to token invalidation.
|
|
|
|
break
|
2023-10-25 12:03:37 +01:00
|
|
|
case (_, .signOut(let isSoft, _), .signingOut):
|
2023-10-30 15:52:25 +00:00
|
|
|
logout(isSoft: isSoft)
|
2023-10-25 12:03:37 +01:00
|
|
|
case (.signingOut(_, let disableAppLock), .completedSigningOut, .signedOut):
|
2023-10-30 15:52:25 +00:00
|
|
|
presentSplashScreen(isSoftLogout: false, disableAppLock: disableAppLock)
|
2023-10-25 12:03:37 +01:00
|
|
|
case (.signingOut(_, let disableAppLock), .showSoftLogout, .softLogout):
|
2023-10-30 15:52:25 +00:00
|
|
|
presentSplashScreen(isSoftLogout: true, disableAppLock: disableAppLock)
|
2023-05-05 19:41:21 +03:00
|
|
|
case (.signedIn, .clearCache, .initial):
|
2023-10-30 15:52:25 +00:00
|
|
|
clearCache()
|
2022-05-27 13:02:36 +03:00
|
|
|
default:
|
|
|
|
fatalError("Unknown transition: \(context)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
stateMachine.addErrorHandler { context in
|
2023-09-01 13:35:27 +03:00
|
|
|
if context.fromState == context.toState {
|
|
|
|
MXLog.error("Failed transition from equal states: \(context.fromState)")
|
|
|
|
} else {
|
|
|
|
fatalError("Failed transition with context: \(context)")
|
|
|
|
}
|
2022-05-27 13:02:36 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-14 17:36:28 +01:00
|
|
|
private func restoreUserSession() {
|
|
|
|
Task {
|
|
|
|
switch await userSessionStore.restoreUserSession() {
|
|
|
|
case .success(let userSession):
|
|
|
|
self.userSession = userSession
|
2023-03-06 14:58:50 +02:00
|
|
|
stateMachine.processEvent(.createdUserSession)
|
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()
|
2023-09-05 17:21:53 +01:00
|
|
|
let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore, appSettings: appSettings)
|
2022-11-16 15:37:34 +02:00
|
|
|
authenticationCoordinator = AuthenticationCoordinator(authenticationService: authenticationService,
|
2023-06-23 09:56:22 +03:00
|
|
|
navigationStackCoordinator: authenticationNavigationStackCoordinator,
|
2023-09-05 17:21:53 +01:00
|
|
|
appSettings: appSettings,
|
2023-06-23 09:56:22 +03:00
|
|
|
analytics: ServiceLocator.shared.analytics,
|
2023-10-25 12:03:37 +01:00
|
|
|
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
|
|
|
appLockService: appLockFlowCoordinator.appLockService)
|
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() {
|
2023-05-17 17:37:16 +03:00
|
|
|
guard let userSession else {
|
|
|
|
fatalError("User session not setup")
|
|
|
|
}
|
|
|
|
|
2022-09-15 12:41:37 +03:00
|
|
|
Task {
|
2023-06-26 18:26:20 +01:00
|
|
|
let credentials = SoftLogoutScreenCredentials(userID: userSession.userID,
|
2023-04-25 12:13:03 +03:00
|
|
|
homeserverName: userSession.homeserver,
|
2023-09-15 16:20:33 +03:00
|
|
|
userDisplayName: userSession.clientProxy.userDisplayName.value ?? "",
|
2023-06-26 18:26:20 +01:00
|
|
|
deviceID: userSession.deviceID)
|
2022-11-16 15:37:34 +02:00
|
|
|
|
2023-09-05 17:21:53 +01:00
|
|
|
let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore, appSettings: appSettings)
|
2022-09-15 12:41:37 +03:00
|
|
|
_ = await authenticationService.configure(for: userSession.homeserver)
|
|
|
|
|
2023-04-25 12:13:03 +03:00
|
|
|
let parameters = SoftLogoutScreenCoordinatorParameters(authenticationService: authenticationService,
|
|
|
|
credentials: credentials,
|
2023-06-23 09:56:22 +03:00
|
|
|
keyBackupNeeded: false,
|
|
|
|
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
2023-04-25 12:13:03 +03:00
|
|
|
let coordinator = SoftLogoutScreenCoordinator(parameters: parameters)
|
2023-09-28 14:12:57 +01:00
|
|
|
self.softLogoutCoordinator = coordinator
|
2023-09-14 12:53:33 +03:00
|
|
|
coordinator.actions
|
|
|
|
.sink { [weak self] action in
|
|
|
|
guard let self else { return }
|
|
|
|
|
|
|
|
switch action {
|
|
|
|
case .signedIn(let session):
|
|
|
|
self.userSession = session
|
2023-09-28 14:12:57 +01:00
|
|
|
self.softLogoutCoordinator = nil
|
2023-09-14 12:53:33 +03:00
|
|
|
stateMachine.processEvent(.createdUserSession)
|
|
|
|
case .clearAllData:
|
2023-09-28 14:12:57 +01:00
|
|
|
self.softLogoutCoordinator = nil
|
2023-10-25 12:03:37 +01:00
|
|
|
stateMachine.processEvent(.signOut(isSoft: false, disableAppLock: false))
|
2023-09-14 12:53:33 +03:00
|
|
|
}
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
2023-09-14 12:53:33 +03:00
|
|
|
.store(in: &cancellables)
|
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() {
|
2023-05-17 17:37:16 +03:00
|
|
|
guard let userSession else {
|
|
|
|
fatalError("User session not setup")
|
|
|
|
}
|
|
|
|
|
2023-06-23 11:01:09 +01:00
|
|
|
let navigationSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator())
|
2022-09-25 12:34:11 +03:00
|
|
|
let userSessionFlowCoordinator = UserSessionFlowCoordinator(userSession: userSession,
|
2022-12-12 12:31:27 +02:00
|
|
|
navigationSplitCoordinator: navigationSplitCoordinator,
|
2023-10-19 12:26:34 +01:00
|
|
|
appLockService: appLockFlowCoordinator.appLockService,
|
2023-04-18 09:33:32 +02:00
|
|
|
bugReportService: ServiceLocator.shared.bugReportService,
|
2023-06-30 13:27:49 +01:00
|
|
|
roomTimelineControllerFactory: RoomTimelineControllerFactory(),
|
2023-09-05 17:21:53 +01:00
|
|
|
appSettings: appSettings,
|
2023-07-11 10:32:24 +01:00
|
|
|
analytics: ServiceLocator.shared.analytics)
|
2022-09-25 12:34:11 +03:00
|
|
|
|
2023-09-14 12:53:33 +03:00
|
|
|
userSessionFlowCoordinator.actions
|
|
|
|
.sink { [weak self] action in
|
|
|
|
guard let self else { return }
|
|
|
|
|
|
|
|
switch action {
|
2023-10-24 18:38:41 +03:00
|
|
|
case .logout:
|
2023-10-25 12:03:37 +01:00
|
|
|
stateMachine.processEvent(.signOut(isSoft: false, disableAppLock: false))
|
2023-09-14 12:53:33 +03:00
|
|
|
case .clearCache:
|
|
|
|
stateMachine.processEvent(.clearCache)
|
2023-10-27 14:08:06 +01:00
|
|
|
case .forceLogout:
|
|
|
|
stateMachine.processEvent(.signOut(isSoft: false, disableAppLock: true))
|
2023-09-14 12:53:33 +03:00
|
|
|
}
|
2022-09-25 12:34:11 +03:00
|
|
|
}
|
2023-09-14 12:53:33 +03:00
|
|
|
.store(in: &cancellables)
|
2022-09-25 12:34:11 +03:00
|
|
|
|
|
|
|
userSessionFlowCoordinator.start()
|
|
|
|
|
|
|
|
self.userSessionFlowCoordinator = userSessionFlowCoordinator
|
2022-12-12 12:31:27 +02:00
|
|
|
|
|
|
|
navigationRootCoordinator.setRootCoordinator(navigationSplitCoordinator)
|
2023-04-14 14:24:48 +02:00
|
|
|
|
|
|
|
if let storedAppRoute {
|
2023-04-18 15:08:39 +02:00
|
|
|
userSessionFlowCoordinator.handleAppRoute(storedAppRoute, animated: false)
|
2023-04-14 14:24:48 +02:00
|
|
|
}
|
2022-09-25 12:34:11 +03:00
|
|
|
}
|
|
|
|
|
2023-10-30 15:52:25 +00:00
|
|
|
/// Used to add a PIN code to an existing session that somehow missed out mandatory PIN setup.
|
|
|
|
private func startMandatoryAppLockSetup() {
|
|
|
|
MXLog.info("Mandatory App Lock enabled but no PIN is set. Showing the setup flow.")
|
|
|
|
|
|
|
|
let navigationCoordinator = NavigationStackCoordinator()
|
|
|
|
let coordinator = AppLockSetupFlowCoordinator(presentingFlow: .onboarding,
|
|
|
|
appLockService: appLockFlowCoordinator.appLockService,
|
|
|
|
navigationStackCoordinator: navigationCoordinator)
|
|
|
|
coordinator.actions.sink { [weak self] action in
|
|
|
|
guard let self else { return }
|
|
|
|
switch action {
|
|
|
|
case .complete:
|
|
|
|
stateMachine.processEvent(.appLockSetupComplete)
|
|
|
|
appLockSetupFlowCoordinator = nil
|
|
|
|
case .forceLogout:
|
|
|
|
fatalError("Creating a PIN shouldn't be able to fail in this way")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
|
|
|
|
|
|
|
appLockSetupFlowCoordinator = coordinator
|
|
|
|
navigationRootCoordinator.setRootCoordinator(navigationCoordinator)
|
|
|
|
coordinator.start()
|
|
|
|
}
|
|
|
|
|
2023-03-06 14:58:50 +02:00
|
|
|
private func logout(isSoft: Bool) {
|
2023-05-17 17:37:16 +03:00
|
|
|
guard let userSession else {
|
|
|
|
fatalError("User session not setup")
|
|
|
|
}
|
|
|
|
|
2023-03-02 14:35:23 +02:00
|
|
|
showLoadingIndicator()
|
|
|
|
|
2023-05-19 10:28:30 +02:00
|
|
|
stopSync()
|
2022-09-25 12:34:11 +03:00
|
|
|
userSessionFlowCoordinator?.stop()
|
2022-09-21 11:21:58 +03:00
|
|
|
|
2023-03-06 14:58:50 +02:00
|
|
|
guard !isSoft else {
|
2023-09-28 14:12:57 +01:00
|
|
|
stateMachine.processEvent(.showSoftLogout)
|
2023-06-22 15:04:20 +03:00
|
|
|
hideLoadingIndicator()
|
2023-01-11 15:10:26 +02:00
|
|
|
return
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
2022-09-21 11:21:58 +03:00
|
|
|
|
2023-09-05 15:35:04 +03:00
|
|
|
// The user will log out, clear any existing notifications
|
|
|
|
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
|
|
|
|
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
|
|
|
|
UIApplication.shared.applicationIconBadgeNumber = 0
|
|
|
|
|
2023-01-11 15:10:26 +02:00
|
|
|
Task {
|
2023-08-30 09:22:22 +01:00
|
|
|
// First log out from the server
|
|
|
|
let accountLogoutURL = await userSession.clientProxy.logout()
|
2023-01-11 15:10:26 +02:00
|
|
|
|
2023-08-30 09:22:22 +01:00
|
|
|
// Regardless of the result, clear user data
|
2023-01-11 15:10:26 +02:00
|
|
|
userSessionStore.logout(userSession: userSession)
|
2023-03-06 14:58:50 +02:00
|
|
|
tearDownUserSession()
|
2023-01-11 15:10:26 +02:00
|
|
|
|
2023-08-30 09:22:22 +01:00
|
|
|
// Reset analytics
|
2023-04-21 17:05:39 +02:00
|
|
|
ServiceLocator.shared.analytics.optOut()
|
|
|
|
ServiceLocator.shared.analytics.resetConsentState()
|
2023-04-18 09:33:32 +02:00
|
|
|
|
2023-09-28 14:12:57 +01:00
|
|
|
stateMachine.processEvent(.completedSigningOut)
|
2023-06-22 15:04:20 +03:00
|
|
|
|
2023-08-30 09:22:22 +01:00
|
|
|
// Handle OIDC's RP-Initiated Logout if needed. Don't fallback to an ASWebAuthenticationSession
|
|
|
|
// as it looks weird to show an alert to the user asking them to sign in to their provider.
|
|
|
|
if let accountLogoutURL, UIApplication.shared.canOpenURL(accountLogoutURL) {
|
|
|
|
await UIApplication.shared.open(accountLogoutURL)
|
|
|
|
}
|
|
|
|
|
2023-06-22 15:04:20 +03:00
|
|
|
hideLoadingIndicator()
|
2023-01-11 15:10:26 +02:00
|
|
|
}
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
2023-01-31 11:51:56 +02:00
|
|
|
|
|
|
|
private func tearDownUserSession() {
|
2023-06-19 10:35:01 +03:00
|
|
|
ServiceLocator.shared.userIndicatorController.retractAllIndicators()
|
|
|
|
|
2023-01-31 11:51:56 +02:00
|
|
|
userSession = nil
|
|
|
|
|
|
|
|
userSessionFlowCoordinator = nil
|
2022-09-15 12:41:37 +03:00
|
|
|
|
2023-04-28 14:36:16 +02:00
|
|
|
notificationManager.setUserSession(nil)
|
2023-04-14 14:24:48 +02:00
|
|
|
}
|
|
|
|
|
2023-10-25 12:03:37 +01:00
|
|
|
private func presentSplashScreen(isSoftLogout: Bool = false, disableAppLock: 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()
|
|
|
|
}
|
2023-10-25 12:03:37 +01:00
|
|
|
|
|
|
|
if disableAppLock {
|
|
|
|
Task {
|
|
|
|
// Ensure the navigation stack has settled.
|
|
|
|
try? await Task.sleep(for: .milliseconds(500))
|
|
|
|
appLockFlowCoordinator.appLockService.disable()
|
|
|
|
windowManager.switchToMain()
|
|
|
|
}
|
|
|
|
}
|
2022-02-14 18:05:21 +02:00
|
|
|
}
|
2022-11-21 19:37:13 +03:00
|
|
|
|
|
|
|
private func configureNotificationManager() {
|
2023-04-28 14:36:16 +02:00
|
|
|
notificationManager.setUserSession(userSession)
|
2023-04-14 14:24:48 +02:00
|
|
|
notificationManager.requestAuthorization()
|
|
|
|
|
2023-09-27 11:35:41 -07:00
|
|
|
appDelegateObserver = appDelegate.callbacks
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { [weak self] callback in
|
|
|
|
switch callback {
|
|
|
|
case .registeredNotifications(let deviceToken):
|
|
|
|
Task { await self?.notificationManager.register(with: deviceToken) }
|
|
|
|
case .failedToRegisteredNotifications(let error):
|
|
|
|
self?.notificationManager.registrationFailed(with: error)
|
2023-04-14 14:24:48 +02:00
|
|
|
}
|
2023-09-27 11:35:41 -07:00
|
|
|
}
|
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() {
|
2023-05-17 17:37:16 +03:00
|
|
|
guard let userSession else {
|
|
|
|
fatalError("User session not setup")
|
|
|
|
}
|
|
|
|
|
2023-05-10 19:26:59 +03:00
|
|
|
userSessionObserver = userSession.callbacks
|
2022-09-15 12:41:37 +03:00
|
|
|
.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):
|
2023-10-25 12:03:37 +01:00
|
|
|
stateMachine.processEvent(.signOut(isSoft: isSoftLogout, disableAppLock: false))
|
2022-09-15 12:41:37 +03:00
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
2022-09-23 13:21:54 +03:00
|
|
|
}
|
2022-09-15 12:41:37 +03:00
|
|
|
}
|
2022-02-14 18:05:21 +02:00
|
|
|
|
2023-05-15 13:06:25 +03:00
|
|
|
private func observeNetworkState() {
|
|
|
|
let reachabilityNotificationIdentifier = "io.element.elementx.reachability.notification"
|
2023-09-14 12:53:33 +03:00
|
|
|
ServiceLocator.shared.networkMonitor
|
2023-05-31 18:16:32 +03:00
|
|
|
.reachabilityPublisher
|
|
|
|
.removeDuplicates()
|
2023-08-08 15:09:05 +03:00
|
|
|
.sink { reachability in
|
|
|
|
MXLog.info("Reachability changed to \(reachability)")
|
2023-05-31 18:16:32 +03:00
|
|
|
|
2023-08-08 15:09:05 +03:00
|
|
|
if reachability == .reachable {
|
2023-05-31 18:16:32 +03:00
|
|
|
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(reachabilityNotificationIdentifier)
|
|
|
|
} else {
|
|
|
|
ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: reachabilityNotificationIdentifier,
|
|
|
|
title: L10n.commonOffline,
|
|
|
|
persistent: true))
|
|
|
|
}
|
2023-05-15 13:06:25 +03:00
|
|
|
}
|
2023-09-14 12:53:33 +03:00
|
|
|
.store(in: &cancellables)
|
2023-05-15 13:06:25 +03:00
|
|
|
}
|
2023-05-31 18:16:32 +03:00
|
|
|
|
2023-10-11 13:59:47 +01:00
|
|
|
private func observeAppLockChanges() {
|
|
|
|
appLockFlowCoordinator.actions.sink { [weak self] action in
|
|
|
|
guard let self else { return }
|
|
|
|
switch action {
|
|
|
|
case .lockApp:
|
|
|
|
windowManager.switchToAlternate()
|
|
|
|
case .unlockApp:
|
|
|
|
windowManager.switchToMain()
|
2023-10-25 12:03:37 +01:00
|
|
|
case .forceLogout:
|
|
|
|
stateMachine.processEvent(.signOut(isSoft: false, disableAppLock: true))
|
2023-10-11 13:59:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
|
|
|
}
|
|
|
|
|
2023-05-15 13:06:25 +03:00
|
|
|
private func handleAppRoute(_ appRoute: AppRoute) {
|
|
|
|
if let userSessionFlowCoordinator {
|
|
|
|
userSessionFlowCoordinator.handleAppRoute(appRoute, animated: UIApplication.shared.applicationState == .active)
|
|
|
|
} else {
|
|
|
|
storedAppRoute = appRoute
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func clearCache() {
|
2023-05-17 17:37:16 +03:00
|
|
|
guard let userSession else {
|
|
|
|
fatalError("User session not setup")
|
|
|
|
}
|
|
|
|
|
2023-05-15 13:06:25 +03:00
|
|
|
showLoadingIndicator()
|
|
|
|
|
2023-07-21 14:22:09 +01:00
|
|
|
navigationRootCoordinator.setRootCoordinator(PlaceholderScreenCoordinator())
|
2023-05-15 13:06:25 +03:00
|
|
|
|
2023-05-19 10:28:30 +02:00
|
|
|
stopSync()
|
2023-05-15 13:06:25 +03:00
|
|
|
userSessionFlowCoordinator?.stop()
|
|
|
|
|
|
|
|
let userID = userSession.userID
|
|
|
|
tearDownUserSession()
|
|
|
|
|
|
|
|
// Allow for everything to deallocate properly
|
|
|
|
Task {
|
2023-05-12 18:41:11 +02:00
|
|
|
try? await Task.sleep(for: .seconds(2))
|
2023-05-15 13:06:25 +03:00
|
|
|
userSessionStore.clearCache(for: userID)
|
|
|
|
stateMachine.processEvent(.startWithExistingSession)
|
|
|
|
hideLoadingIndicator()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-01 13:56:52 +03:00
|
|
|
// MARK: Toasts and loading indicators
|
|
|
|
|
2023-05-04 16:09:29 +03:00
|
|
|
private static let loadingIndicatorIdentifier = "AppCoordinatorLoading"
|
2022-11-16 15:37:34 +02:00
|
|
|
|
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,
|
2023-03-27 18:18:59 +01:00
|
|
|
title: L10n.commonLoading,
|
2023-02-14 11:09:55 +01:00
|
|
|
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
|
|
|
|
|
2023-05-10 19:26:59 +03:00
|
|
|
private func stopSync() {
|
2023-08-16 13:24:43 +03:00
|
|
|
userSession?.clientProxy.stopSync()
|
2023-06-22 15:04:20 +03:00
|
|
|
clientProxyObserver = nil
|
2022-11-28 18:42:49 +03:00
|
|
|
}
|
|
|
|
|
2023-05-10 19:26:59 +03:00
|
|
|
private func startSync() {
|
2023-08-22 15:53:27 +01:00
|
|
|
guard let userSession else { return }
|
2023-05-17 17:37:16 +03:00
|
|
|
|
2023-08-24 13:26:37 +03:00
|
|
|
ServiceLocator.shared.analytics.signpost.beginFirstSync()
|
2023-05-17 17:37:16 +03:00
|
|
|
userSession.clientProxy.startSync()
|
2023-05-10 19:26:59 +03:00
|
|
|
|
2023-08-25 09:53:57 +03:00
|
|
|
guard clientProxyObserver == nil else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-06-22 15:04:20 +03:00
|
|
|
clientProxyObserver = userSession.clientProxy
|
2023-09-06 16:10:36 +03:00
|
|
|
.loadingStatePublisher
|
|
|
|
.removeDuplicates()
|
2023-05-10 19:26:59 +03:00
|
|
|
.receive(on: DispatchQueue.main)
|
2023-09-06 16:10:36 +03:00
|
|
|
.sink { state in
|
|
|
|
let toastIdentifier = "StaleDataIndicator"
|
|
|
|
|
|
|
|
switch state {
|
|
|
|
case .loading:
|
|
|
|
if ServiceLocator.shared.networkMonitor.reachabilityPublisher.value == .reachable {
|
|
|
|
ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: toastIdentifier, type: .toast(progress: .indeterminate), title: L10n.commonSyncing, persistent: true))
|
|
|
|
}
|
|
|
|
case .notLoading:
|
2023-08-24 13:26:37 +03:00
|
|
|
ServiceLocator.shared.analytics.signpost.endFirstSync()
|
2023-09-06 16:10:36 +03:00
|
|
|
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(toastIdentifier)
|
2023-06-22 15:04:20 +03:00
|
|
|
}
|
2023-05-10 19:26:59 +03:00
|
|
|
}
|
2022-11-28 18:42:49 +03:00
|
|
|
}
|
|
|
|
|
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)
|
2023-06-22 19:23:33 +02:00
|
|
|
|
|
|
|
NotificationCenter.default.addObserver(self,
|
|
|
|
selector: #selector(applicationWillTerminate),
|
|
|
|
name: UIApplication.willTerminateNotification,
|
|
|
|
object: nil)
|
2023-08-22 12:14:23 +02:00
|
|
|
NotificationCenter.default.addObserver(self,
|
|
|
|
selector: #selector(didChangeContentSizeCategory),
|
|
|
|
name: UIContentSizeCategory.didChangeNotification,
|
|
|
|
object: nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc
|
|
|
|
private func didChangeContentSizeCategory() {
|
2023-09-29 16:49:20 +02:00
|
|
|
AttributedStringBuilder.invalidateCaches()
|
2023-06-22 19:23:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@objc
|
|
|
|
private func applicationWillTerminate() {
|
2023-08-25 09:53:57 +03:00
|
|
|
stopSync()
|
2022-11-28 18:42:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@objc
|
|
|
|
private func applicationWillResignActive() {
|
2023-01-18 17:13:44 +02:00
|
|
|
MXLog.info("Application will resign active")
|
2023-05-19 10:28:30 +02:00
|
|
|
|
2022-11-28 18:42:49 +03:00
|
|
|
guard backgroundTask == nil else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
backgroundTask = backgroundTaskService.startBackgroundTask(withName: "SuspendApp: \(UUID().uuidString)") { [weak self] in
|
2023-05-19 18:22:27 +03:00
|
|
|
guard let self else { return }
|
|
|
|
|
2023-08-25 09:53:57 +03:00
|
|
|
stopSync()
|
2023-05-24 14:33:56 +03:00
|
|
|
|
|
|
|
backgroundTask?.stop()
|
|
|
|
backgroundTask = nil
|
2022-11-28 18:42:49 +03:00
|
|
|
}
|
2023-05-19 10:28:30 +02:00
|
|
|
|
|
|
|
isSuspended = true
|
|
|
|
|
2023-05-15 13:06:25 +03:00
|
|
|
// This does seem to work if scheduled from the background task above
|
|
|
|
// Schedule it here instead but with an earliest being date of 30 seconds
|
2023-08-08 15:34:35 +03:00
|
|
|
scheduleBackgroundAppRefresh()
|
2022-11-28 18:42:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@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
|
|
|
|
|
2023-08-16 13:24:43 +03:00
|
|
|
if isSuspended {
|
2023-05-10 19:26:59 +03:00
|
|
|
startSync()
|
2022-11-28 18:42:49 +03:00
|
|
|
}
|
2023-05-19 10:28:30 +02:00
|
|
|
|
|
|
|
isSuspended = false
|
2022-11-28 18:42:49 +03:00
|
|
|
}
|
2022-12-19 18:29:14 +02:00
|
|
|
|
2023-05-15 13:06:25 +03:00
|
|
|
// MARK: Background app refresh
|
|
|
|
|
|
|
|
private func registerBackgroundAppRefresh() {
|
2023-09-05 17:21:53 +01:00
|
|
|
let result = BGTaskScheduler.shared.register(forTaskWithIdentifier: appSettings.backgroundAppRefreshTaskIdentifier, using: .main) { [weak self] task in
|
2023-05-15 13:06:25 +03:00
|
|
|
guard let task = task as? BGAppRefreshTask else {
|
|
|
|
MXLog.error("Invalid background app refresh configuration")
|
|
|
|
return
|
2022-12-19 18:29:14 +02:00
|
|
|
}
|
2023-05-15 13:06:25 +03:00
|
|
|
|
|
|
|
self?.handleBackgroundAppRefresh(task)
|
2023-05-10 19:26:59 +03:00
|
|
|
}
|
2023-05-15 13:06:25 +03:00
|
|
|
|
|
|
|
MXLog.info("Register background app refresh with result: \(result)")
|
2023-04-14 14:24:48 +02:00
|
|
|
}
|
2023-05-05 19:41:21 +03:00
|
|
|
|
2023-05-15 13:06:25 +03:00
|
|
|
private func scheduleBackgroundAppRefresh() {
|
2023-09-05 17:21:53 +01:00
|
|
|
let request = BGAppRefreshTaskRequest(identifier: appSettings.backgroundAppRefreshTaskIdentifier)
|
2023-05-05 19:41:21 +03:00
|
|
|
|
2023-05-15 13:06:25 +03:00
|
|
|
// We have other background tasks that keep the app alive
|
|
|
|
request.earliestBeginDate = Date(timeIntervalSinceNow: 30)
|
2023-05-05 19:41:21 +03:00
|
|
|
|
2023-05-15 13:06:25 +03:00
|
|
|
do {
|
|
|
|
try BGTaskScheduler.shared.submit(request)
|
|
|
|
MXLog.info("Successfully scheduled background app refresh task")
|
|
|
|
} catch {
|
|
|
|
MXLog.error("Failed scheduling background app refresh with error :\(error)")
|
2023-05-12 18:24:20 +03:00
|
|
|
}
|
2023-05-05 19:41:21 +03:00
|
|
|
}
|
2023-05-15 13:06:25 +03:00
|
|
|
|
2023-08-08 15:34:35 +03:00
|
|
|
private var backgroundRefreshSyncObserver: AnyCancellable?
|
2023-05-15 13:06:25 +03:00
|
|
|
private func handleBackgroundAppRefresh(_ task: BGAppRefreshTask) {
|
|
|
|
MXLog.info("Started background app refresh")
|
|
|
|
|
|
|
|
// This is important for the app to keep refreshing in the background
|
|
|
|
scheduleBackgroundAppRefresh()
|
|
|
|
|
2023-08-08 15:34:35 +03:00
|
|
|
task.expirationHandler = {
|
2023-05-15 13:06:25 +03:00
|
|
|
MXLog.info("Background app refresh task expired")
|
2023-08-08 15:34:35 +03:00
|
|
|
task.setTaskCompleted(success: true)
|
2022-11-21 19:37:13 +03:00
|
|
|
}
|
2023-05-15 13:06:25 +03:00
|
|
|
|
2023-05-19 18:22:27 +03:00
|
|
|
guard let userSession else {
|
2023-08-08 15:34:35 +03:00
|
|
|
return
|
2023-05-19 18:22:27 +03:00
|
|
|
}
|
|
|
|
|
2023-08-16 13:24:43 +03:00
|
|
|
startSync()
|
2023-05-15 13:06:25 +03:00
|
|
|
|
|
|
|
// Be a good citizen, run for a max of 10 SS responses or 10 seconds
|
|
|
|
// An SS request will time out after 30 seconds if no new data is available
|
2023-05-19 18:22:27 +03:00
|
|
|
backgroundRefreshSyncObserver = userSession.clientProxy
|
2023-05-15 13:06:25 +03:00
|
|
|
.callbacks
|
|
|
|
.filter(\.isSyncUpdate)
|
|
|
|
.collect(.byTimeOrCount(DispatchQueue.main, .seconds(10), 10))
|
|
|
|
.sink(receiveValue: { [weak self] _ in
|
2023-08-08 15:34:35 +03:00
|
|
|
guard let self else { return }
|
|
|
|
|
2023-05-15 13:06:25 +03:00
|
|
|
MXLog.info("Background app refresh finished")
|
2023-08-08 15:34:35 +03:00
|
|
|
backgroundRefreshSyncObserver?.cancel()
|
|
|
|
|
|
|
|
task.setTaskCompleted(success: true)
|
2023-05-15 13:06:25 +03:00
|
|
|
})
|
2022-11-21 19:37:13 +03:00
|
|
|
}
|
|
|
|
}
|