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
|
|
|
//
|
|
|
|
|
2024-05-02 17:21:09 +02:00
|
|
|
import AnalyticsEvents
|
2023-05-15 13:06:25 +03:00
|
|
|
import BackgroundTasks
|
2022-07-01 13:56:52 +03:00
|
|
|
import Combine
|
2024-05-27 12:49:24 +03:00
|
|
|
import Intents
|
2022-07-27 16:06:40 +03:00
|
|
|
import MatrixRustSDK
|
2024-06-28 18:15:57 +03:00
|
|
|
import Sentry
|
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
|
|
|
|
2024-04-22 18:10:24 +03:00
|
|
|
class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDelegate, NotificationManagerDelegate, SecureWindowManagerDelegate {
|
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
|
2024-04-19 13:59:15 +03:00
|
|
|
private let appMediator: AppMediator
|
2023-09-05 17:21:53 +01:00
|
|
|
private let appSettings: AppSettings
|
2023-09-27 11:35:41 -07:00
|
|
|
private let appDelegate: AppDelegate
|
2024-06-27 18:18:17 +01:00
|
|
|
private let appHooks: AppHooks
|
2024-05-17 15:13:57 +03:00
|
|
|
private let elementCallService: ElementCallServiceProtocol
|
2023-09-27 11:35:41 -07:00
|
|
|
|
2023-08-08 15:34:35 +03:00
|
|
|
/// Common background task to continue long-running tasks in the background.
|
2024-04-22 18:10:24 +03:00
|
|
|
private var backgroundTask: UIBackgroundTaskIdentifier?
|
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 {
|
2024-07-16 17:09:16 +03:00
|
|
|
configureElementCallService()
|
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
|
|
|
|
2024-03-13 11:24:48 +02:00
|
|
|
private var authenticationFlowCoordinator: AuthenticationFlowCoordinator?
|
2023-10-11 13:59:47 +01:00
|
|
|
private let appLockFlowCoordinator: AppLockFlowCoordinator
|
2023-12-13 09:51:57 +01:00
|
|
|
// periphery:ignore - used to avoid deallocation
|
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?
|
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
|
|
|
|
2024-04-22 18:10:24 +03:00
|
|
|
let windowManager: SecureWindowManagerProtocol
|
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) {
|
2024-06-27 18:18:17 +01:00
|
|
|
let appHooks = AppHooks()
|
|
|
|
appHooks.configure()
|
|
|
|
|
2024-01-18 14:24:15 +01:00
|
|
|
windowManager = WindowManager(appDelegate: appDelegate)
|
2024-08-08 18:29:39 +02:00
|
|
|
let networkMonitor = NetworkMonitor()
|
|
|
|
appMediator = AppMediator(windowManager: windowManager, networkMonitor: networkMonitor)
|
2024-04-19 13:59:15 +03:00
|
|
|
|
2024-07-19 17:05:54 +01:00
|
|
|
let appSettings = appHooks.appSettingsHook.configure(AppSettings())
|
2023-08-23 19:15:38 +03:00
|
|
|
|
2024-05-08 10:54:54 +01:00
|
|
|
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 {
|
2024-03-21 14:01:23 +02:00
|
|
|
AppSettings.resetAllSettings()
|
2023-08-23 14:58:44 +03:00
|
|
|
}
|
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
|
2024-06-27 18:18:17 +01:00
|
|
|
self.appHooks = appHooks
|
2023-09-11 12:31:31 +03:00
|
|
|
appRouteURLParser = AppRouteURLParser(appSettings: appSettings)
|
2023-09-05 17:21:53 +01:00
|
|
|
|
2024-05-17 15:13:57 +03:00
|
|
|
elementCallService = ElementCallService()
|
|
|
|
|
2022-12-12 12:31:27 +02:00
|
|
|
navigationRootCoordinator = NavigationRootCoordinator()
|
2022-12-16 10:02:22 +02:00
|
|
|
|
2023-04-18 09:33:32 +02:00
|
|
|
stateMachine = AppCoordinatorStateMachine()
|
|
|
|
|
2022-12-12 12:31:27 +02:00
|
|
|
navigationRootCoordinator.setRootCoordinator(SplashScreenCoordinator())
|
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)
|
2024-08-08 18:29:39 +02:00
|
|
|
userSessionStore = UserSessionStore(keychainController: keychainController, appSettings: appSettings, appHooks: appHooks, networkMonitor: networkMonitor)
|
2023-10-11 13:59:47 +01:00
|
|
|
|
|
|
|
let appLockService = AppLockService(keychainController: keychainController, appSettings: appSettings)
|
2023-10-25 12:03:37 +01:00
|
|
|
let appLockNavigationCoordinator = NavigationRootCoordinator()
|
2023-10-11 13:59:47 +01:00
|
|
|
appLockFlowCoordinator = AppLockFlowCoordinator(appLockService: appLockService,
|
2023-10-25 12:03:37 +01:00
|
|
|
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
|
|
|
|
2024-07-17 14:47:51 +03:00
|
|
|
Self.setupServiceLocator(appSettings: appSettings, appHooks: appHooks)
|
2024-07-25 15:56:45 +01:00
|
|
|
Self.setupSentry(appSettings: appSettings)
|
2024-07-17 14:47:51 +03:00
|
|
|
|
2024-07-25 15:56:45 +01:00
|
|
|
ServiceLocator.shared.analytics.signpost.start()
|
2024-07-17 14:47:51 +03:00
|
|
|
ServiceLocator.shared.analytics.startIfEnabled()
|
|
|
|
|
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()
|
2024-05-17 15:13:57 +03:00
|
|
|
|
2024-07-17 14:47:51 +03:00
|
|
|
appSettings.$analyticsConsentState
|
|
|
|
.dropFirst() // Called above before configuring the ServiceLocator
|
|
|
|
.sink { _ in
|
|
|
|
Self.setupSentry(appSettings: appSettings)
|
2024-06-28 18:15:57 +03:00
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
|
|
|
|
2024-06-24 18:51:43 +02:00
|
|
|
elementCallService.actions
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { [weak self] action in
|
|
|
|
switch action {
|
2024-06-27 14:07:44 +03:00
|
|
|
case .startCall(let roomID):
|
2024-06-24 18:51:43 +02:00
|
|
|
self?.handleAppRoute(.call(roomID: roomID))
|
2024-06-27 14:33:16 +03:00
|
|
|
default:
|
|
|
|
break
|
2024-06-24 18:51:43 +02:00
|
|
|
}
|
2024-05-17 15:13:57 +03:00
|
|
|
}
|
2024-06-24 18:51:43 +02:00
|
|
|
.store(in: &cancellables)
|
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
|
|
|
|
}
|
|
|
|
|
2024-03-21 14:01:23 +02:00
|
|
|
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(
|
2023-11-09 15:10:50 +02:00
|
|
|
navigationRootCoordinator.toPresentable()
|
2023-06-23 09:56:22 +03:00
|
|
|
.environment(\.analyticsService, ServiceLocator.shared.analytics)
|
2023-11-15 10:48:00 +02:00
|
|
|
.onReceive(appSettings.$appAppearance) { [weak self] appAppearance in
|
|
|
|
guard let self else { return }
|
|
|
|
|
|
|
|
windowManager.windows.forEach { window in
|
|
|
|
// Unfortunately .preferredColorScheme doesn't propagate properly throughout the app when changed
|
|
|
|
window.overrideUserInterfaceStyle = appAppearance.interfaceStyle
|
|
|
|
}
|
|
|
|
}
|
2023-06-23 09:56:22 +03:00
|
|
|
)
|
2022-11-16 15:37:34 +02:00
|
|
|
}
|
2023-05-15 13:06:25 +03:00
|
|
|
|
2024-04-08 16:44:03 +01:00
|
|
|
func handleDeepLink(_ url: URL, isExternalURL: Bool) -> 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 {
|
2024-03-13 11:24:48 +02:00
|
|
|
authenticationFlowCoordinator?.handleOIDCRedirectURL(url)
|
2023-09-28 14:12:57 +01:00
|
|
|
}
|
2023-09-07 12:06:18 +03:00
|
|
|
case .genericCallLink(let url):
|
|
|
|
if let userSessionFlowCoordinator {
|
|
|
|
userSessionFlowCoordinator.handleAppRoute(route, animated: true)
|
|
|
|
} else {
|
2024-08-19 17:21:25 +01:00
|
|
|
presentCallScreen(genericCallLink: url)
|
2023-09-07 12:06:18 +03:00
|
|
|
}
|
2024-04-15 11:08:00 +01:00
|
|
|
case .userProfile(let userID):
|
|
|
|
if isExternalURL {
|
2024-04-22 18:05:18 +03:00
|
|
|
handleAppRoute(route)
|
2024-04-15 11:08:00 +01:00
|
|
|
} else {
|
2024-04-22 18:05:18 +03:00
|
|
|
handleAppRoute(.roomMemberDetails(userID: userID))
|
2024-04-15 11:08:00 +01:00
|
|
|
}
|
2024-05-09 12:25:54 +01:00
|
|
|
case .room(let roomID, let via):
|
2024-04-08 16:44:03 +01:00
|
|
|
if isExternalURL {
|
2024-04-22 18:05:18 +03:00
|
|
|
handleAppRoute(route)
|
2024-04-08 16:44:03 +01:00
|
|
|
} else {
|
2024-05-09 12:25:54 +01:00
|
|
|
handleAppRoute(.childRoom(roomID: roomID, via: via))
|
2024-04-22 18:05:18 +03:00
|
|
|
}
|
|
|
|
case .roomAlias(let alias):
|
|
|
|
if isExternalURL {
|
|
|
|
handleAppRoute(route)
|
|
|
|
} else {
|
|
|
|
handleAppRoute(.childRoomAlias(alias))
|
2024-04-08 16:44:03 +01:00
|
|
|
}
|
2024-05-09 12:25:54 +01:00
|
|
|
case .event(let eventID, let roomID, let via):
|
2024-04-25 12:50:06 +01:00
|
|
|
if isExternalURL {
|
|
|
|
handleAppRoute(route)
|
|
|
|
} else {
|
2024-05-09 12:25:54 +01:00
|
|
|
handleAppRoute(.childEvent(eventID: eventID, roomID: roomID, via: via))
|
2024-04-25 12:50:06 +01:00
|
|
|
}
|
2024-05-09 12:25:54 +01:00
|
|
|
case .eventOnRoomAlias(let eventID, let alias):
|
2024-04-25 12:50:06 +01:00
|
|
|
if isExternalURL {
|
|
|
|
handleAppRoute(route)
|
|
|
|
} else {
|
2024-05-09 12:25:54 +01:00
|
|
|
handleAppRoute(.childEventOnRoomAlias(eventID: eventID, alias: alias))
|
2024-04-25 12:50:06 +01:00
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
2024-05-27 12:49:24 +03:00
|
|
|
func handleUserActivity(_ userActivity: NSUserActivity) {
|
|
|
|
// `INStartVideoCallIntent` is to be replaced with `INStartCallIntent`
|
|
|
|
// but calls from Recents still send it ¯\_(ツ)_/¯
|
|
|
|
guard let intent = userActivity.interaction?.intent as? INStartVideoCallIntent,
|
|
|
|
let contact = intent.contacts?.first,
|
|
|
|
let roomIdentifier = contact.personHandle?.value else {
|
|
|
|
MXLog.error("Failed retrieving information from userActivity: \(userActivity)")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
MXLog.info("Starting call in room: \(roomIdentifier)")
|
|
|
|
handleAppRoute(AppRoute.call(roomID: roomIdentifier))
|
|
|
|
}
|
|
|
|
|
2024-03-13 11:24:48 +02:00
|
|
|
// MARK: - AuthenticationFlowCoordinatorDelegate
|
2023-05-15 13:06:25 +03:00
|
|
|
|
2024-03-13 11:24:48 +02:00
|
|
|
func authenticationFlowCoordinator(didLoginWithSession userSession: UserSessionProtocol) {
|
2023-05-15 13:06:25 +03:00
|
|
|
self.userSession = userSession
|
2024-03-13 11:24:48 +02:00
|
|
|
authenticationFlowCoordinator = nil
|
2023-05-15 13:06:25 +03:00
|
|
|
stateMachine.processEvent(.createdUserSession)
|
|
|
|
}
|
|
|
|
|
2023-10-11 13:59:47 +01:00
|
|
|
// MARK: - WindowManagerDelegate
|
|
|
|
|
2024-04-22 18:10:24 +03:00
|
|
|
func windowManagerDidConfigureWindows(_ windowManager: SecureWindowManagerProtocol) {
|
2023-10-11 13:59:47 +01:00
|
|
|
windowManager.alternateWindow.rootViewController = UIHostingController(rootView: appLockFlowCoordinator.toPresentable())
|
2023-11-09 15:10:50 +02:00
|
|
|
ServiceLocator.shared.userIndicatorController.window = windowManager.overlayWindow
|
2023-10-11 13:59:47 +01:00
|
|
|
}
|
|
|
|
|
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-12-13 09:51:57 +01:00
|
|
|
func shouldDisplayInAppNotification(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
|
|
|
}
|
|
|
|
|
2023-12-13 09:51:57 +01:00
|
|
|
func notificationTapped(content: UNNotificationContent) async {
|
2023-05-15 13:06:25 +03:00
|
|
|
MXLog.info("[AppCoordinator] tappedNotification")
|
|
|
|
|
|
|
|
guard let roomID = content.roomID,
|
|
|
|
content.receiverID != nil else {
|
|
|
|
return
|
|
|
|
}
|
2022-11-16 15:37:34 +02:00
|
|
|
|
2024-05-09 12:25:54 +01:00
|
|
|
handleAppRoute(.room(roomID: roomID, via: []))
|
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)
|
2024-01-12 12:58:36 +01:00
|
|
|
switch await roomProxy?.timeline.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
|
|
|
|
|
2024-07-01 13:53:24 +01:00
|
|
|
private static func setupServiceLocator(appSettings: AppSettings, appHooks: AppHooks) {
|
2023-11-09 15:10:50 +02:00
|
|
|
ServiceLocator.shared.register(userIndicatorController: UserIndicatorController())
|
2023-08-23 14:58:44 +03:00
|
|
|
ServiceLocator.shared.register(appSettings: appSettings)
|
2023-09-05 17:21:53 +01:00
|
|
|
ServiceLocator.shared.register(bugReportService: BugReportService(withBaseURL: appSettings.bugReportServiceBaseURL,
|
|
|
|
applicationId: appSettings.bugReportApplicationId,
|
2023-08-01 17:28:56 +03:00
|
|
|
sdkGitSHA: sdkGitSha(),
|
2024-07-01 13:53:24 +01:00
|
|
|
maxUploadSize: appSettings.bugReportMaxUploadSize,
|
|
|
|
appHooks: appHooks))
|
2024-05-02 17:21:09 +02:00
|
|
|
let posthogAnalyticsClient = PostHogAnalyticsClient()
|
2024-05-30 14:10:51 +03:00
|
|
|
posthogAnalyticsClient.updateSuperProperties(AnalyticsEvent.SuperProperties(appPlatform: .EXI, cryptoSDK: .Rust, cryptoSDKVersion: sdkGitSha()))
|
2024-05-02 17:21:09 +02:00
|
|
|
ServiceLocator.shared.register(analytics: AnalyticsService(client: posthogAnalyticsClient,
|
2024-05-30 14:10:51 +03:00
|
|
|
appSettings: appSettings))
|
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
|
|
|
}
|
|
|
|
}
|
2024-03-21 14:01:23 +02:00
|
|
|
|
|
|
|
if oldVersion < Version(1, 6, 0) {
|
|
|
|
MXLog.info("Migrating to v1.6.0, marking identity confirmation onboarding as ran.")
|
|
|
|
if !userSessionStore.userIDs.isEmpty {
|
|
|
|
appSettings.hasRunIdentityConfirmationOnboarding = true
|
|
|
|
appSettings.hasRunNotificationPermissionsOnboarding = true
|
|
|
|
}
|
|
|
|
}
|
2024-04-24 10:31:37 +02:00
|
|
|
|
|
|
|
if oldVersion < Version(1, 6, 7) {
|
|
|
|
RustTracing.deleteLogFiles()
|
|
|
|
MXLog.info("Migrating to v1.6.7, log files have been wiped")
|
|
|
|
}
|
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 {
|
2024-03-21 14:01:23 +02:00
|
|
|
AppSettings.resetAllSettings()
|
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):
|
2024-03-21 14:01:23 +02:00
|
|
|
setupUserSession(isNewLogin: true)
|
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):
|
2024-03-21 14:01:23 +02:00
|
|
|
setupUserSession(isNewLogin: false)
|
|
|
|
|
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() {
|
2024-05-29 17:07:17 +02:00
|
|
|
let encryptionKeyProvider = EncryptionKeyProvider()
|
2024-06-24 15:05:00 +01:00
|
|
|
let authenticationService = AuthenticationService(userSessionStore: userSessionStore,
|
|
|
|
encryptionKeyProvider: encryptionKeyProvider,
|
2024-07-18 09:47:37 +01:00
|
|
|
appSettings: appSettings,
|
|
|
|
appHooks: appHooks)
|
2024-06-24 15:05:00 +01:00
|
|
|
let qrCodeLoginService = QRCodeLoginService(encryptionKeyProvider: encryptionKeyProvider,
|
|
|
|
userSessionStore: userSessionStore,
|
2024-07-18 09:47:37 +01:00
|
|
|
appSettings: appSettings,
|
|
|
|
appHooks: appHooks)
|
2024-05-29 17:07:17 +02:00
|
|
|
|
2024-03-13 11:24:48 +02:00
|
|
|
authenticationFlowCoordinator = AuthenticationFlowCoordinator(authenticationService: authenticationService,
|
2024-05-29 17:07:17 +02:00
|
|
|
qrCodeLoginService: qrCodeLoginService,
|
2024-03-13 11:24:48 +02:00
|
|
|
bugReportService: ServiceLocator.shared.bugReportService,
|
|
|
|
navigationRootCoordinator: navigationRootCoordinator,
|
2024-04-19 13:59:15 +03:00
|
|
|
appMediator: appMediator,
|
2024-03-13 11:24:48 +02:00
|
|
|
appSettings: appSettings,
|
|
|
|
analytics: ServiceLocator.shared.analytics,
|
2024-04-22 18:10:24 +03:00
|
|
|
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
2024-03-13 11:24:48 +02:00
|
|
|
authenticationFlowCoordinator?.delegate = self
|
|
|
|
|
|
|
|
authenticationFlowCoordinator?.start()
|
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 {
|
2024-05-27 14:47:55 +03:00
|
|
|
let credentials = SoftLogoutScreenCredentials(userID: userSession.clientProxy.userID,
|
|
|
|
homeserverName: userSession.clientProxy.homeserver,
|
2024-02-28 13:21:54 +02:00
|
|
|
userDisplayName: userSession.clientProxy.userDisplayNamePublisher.value ?? "",
|
2024-05-27 14:47:55 +03:00
|
|
|
deviceID: userSession.clientProxy.deviceID)
|
2022-11-16 15:37:34 +02:00
|
|
|
|
2024-06-24 15:05:00 +01:00
|
|
|
let authenticationService = AuthenticationService(userSessionStore: userSessionStore,
|
|
|
|
encryptionKeyProvider: EncryptionKeyProvider(),
|
2024-07-18 09:47:37 +01:00
|
|
|
appSettings: appSettings,
|
|
|
|
appHooks: appHooks)
|
2024-05-27 14:47:55 +03:00
|
|
|
_ = await authenticationService.configure(for: userSession.clientProxy.homeserver)
|
2022-09-15 12:41:37 +03:00
|
|
|
|
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
|
|
|
|
2024-03-21 14:01:23 +02:00
|
|
|
private func setupUserSession(isNewLogin: Bool) {
|
2023-05-17 17:37:16 +03:00
|
|
|
guard let userSession else {
|
|
|
|
fatalError("User session not setup")
|
|
|
|
}
|
|
|
|
|
2022-09-25 12:34:11 +03:00
|
|
|
let userSessionFlowCoordinator = UserSessionFlowCoordinator(userSession: userSession,
|
2024-03-13 11:24:48 +02:00
|
|
|
navigationRootCoordinator: navigationRootCoordinator,
|
2023-10-19 12:26:34 +01:00
|
|
|
appLockService: appLockFlowCoordinator.appLockService,
|
2023-04-18 09:33:32 +02:00
|
|
|
bugReportService: ServiceLocator.shared.bugReportService,
|
2024-05-17 15:13:57 +03:00
|
|
|
elementCallService: elementCallService,
|
2023-06-30 13:27:49 +01:00
|
|
|
roomTimelineControllerFactory: RoomTimelineControllerFactory(),
|
2024-04-19 13:59:15 +03:00
|
|
|
appMediator: appMediator,
|
2023-09-05 17:21:53 +01:00
|
|
|
appSettings: appSettings,
|
2024-07-19 17:05:54 +01:00
|
|
|
appHooks: appHooks,
|
2024-03-21 14:01:23 +02:00
|
|
|
analytics: ServiceLocator.shared.analytics,
|
|
|
|
notificationManager: notificationManager,
|
|
|
|
isNewLogin: isNewLogin)
|
2022-09-25 12:34:11 +03:00
|
|
|
|
2024-03-21 14:01:23 +02:00
|
|
|
userSessionFlowCoordinator.actionsPublisher
|
2023-09-14 12:53:33 +03:00
|
|
|
.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
|
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
|
|
|
|
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-11-13 10:31:45 +02:00
|
|
|
// The user will log out, clear any existing notifications and unregister from receving new ones
|
2023-09-05 15:35:04 +03:00
|
|
|
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
|
|
|
|
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
|
|
|
|
UIApplication.shared.applicationIconBadgeNumber = 0
|
|
|
|
|
2023-11-13 10:31:45 +02:00
|
|
|
unregisterForRemoteNotifications()
|
|
|
|
|
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
|
|
|
|
2024-03-21 14:01:23 +02:00
|
|
|
AppSettings.resetSessionSpecificSettings()
|
|
|
|
|
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
|
|
|
}
|
2024-07-16 17:09:16 +03:00
|
|
|
|
|
|
|
private func configureElementCallService() {
|
|
|
|
guard let userSession else {
|
|
|
|
fatalError("User session not setup")
|
|
|
|
}
|
|
|
|
|
|
|
|
elementCallService.setClientProxy(userSession.clientProxy)
|
|
|
|
}
|
2024-08-19 17:21:25 +01:00
|
|
|
|
|
|
|
private func presentCallScreen(genericCallLink url: URL) {
|
|
|
|
let configuration = ElementCallConfiguration(genericCallLink: url)
|
|
|
|
|
|
|
|
let callScreenCoordinator = CallScreenCoordinator(parameters: .init(elementCallService: elementCallService,
|
|
|
|
configuration: configuration,
|
|
|
|
elementCallPictureInPictureEnabled: false,
|
|
|
|
appHooks: appHooks))
|
|
|
|
|
|
|
|
callScreenCoordinator.actions
|
|
|
|
.sink { [weak self] action in
|
|
|
|
guard let self else { return }
|
|
|
|
switch action {
|
|
|
|
case .pictureInPictureStarted, .pictureInPictureStopped:
|
|
|
|
// Don't allow PiP when signed out - the user could login at which point we'd
|
|
|
|
// need to hand over the call from here to the user session flow coordinator.
|
|
|
|
MXLog.error("Picture in Picture not supported before login.")
|
|
|
|
case .dismiss:
|
|
|
|
navigationRootCoordinator.setOverlayCoordinator(nil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
|
|
|
|
|
|
|
navigationRootCoordinator.setOverlayCoordinator(callScreenCoordinator, animated: false)
|
|
|
|
}
|
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
|
|
|
|
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
|
|
|
}
|
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"
|
2024-08-08 18:29:39 +02:00
|
|
|
appMediator.networkMonitor
|
2023-05-31 18:16:32 +03:00
|
|
|
.reachabilityPublisher
|
2024-06-11 17:17:46 +03:00
|
|
|
.sink { reachability in
|
2023-08-08 15:09:05 +03:00
|
|
|
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 {
|
2024-04-19 13:59:15 +03:00
|
|
|
userSessionFlowCoordinator.handleAppRoute(appRoute, animated: appMediator.appState == .active)
|
2023-05-15 13:06:25 +03:00
|
|
|
} 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()
|
|
|
|
|
2024-05-27 14:47:55 +03:00
|
|
|
let userID = userSession.clientProxy.userID
|
2023-05-15 13:06:25 +03:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-17 14:47:51 +03:00
|
|
|
private static func setupSentry(appSettings: AppSettings) {
|
|
|
|
let options: Options = .init()
|
|
|
|
|
|
|
|
#if DEBUG
|
|
|
|
options.enabled = false
|
|
|
|
#else
|
|
|
|
options.enabled = appSettings.analyticsConsentState == .optedIn
|
|
|
|
#endif
|
|
|
|
|
|
|
|
options.dsn = appSettings.bugReportSentryURL.absoluteString
|
|
|
|
|
|
|
|
if AppSettings.isDevelopmentBuild {
|
|
|
|
options.environment = "development"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sentry swizzling shows up quite often as the heaviest stack trace when profiling
|
|
|
|
// We don't need any of the features it powers (see docs)
|
|
|
|
options.enableSwizzling = false
|
|
|
|
|
|
|
|
// WatchdogTermination is currently the top issue but we've had zero complaints
|
|
|
|
// so it might very well just all be false positives
|
|
|
|
options.enableWatchdogTerminationTracking = false
|
|
|
|
|
|
|
|
// Disabled as it seems to report a lot of false positives
|
|
|
|
options.enableAppHangTracking = false
|
|
|
|
|
|
|
|
// Most of the network requests are made Rust side, this is useless
|
|
|
|
options.enableNetworkBreadcrumbs = false
|
|
|
|
|
|
|
|
// Doesn't seem to work at all well with SwiftUI
|
|
|
|
options.enableAutoBreadcrumbTracking = false
|
|
|
|
|
|
|
|
// Experimental. Stitches stack traces of asynchronous code together
|
|
|
|
options.swiftAsyncStacktraces = true
|
|
|
|
|
|
|
|
// Uniform sample rate: 1.0 captures 100% of transactions
|
|
|
|
// In Production you will probably want a smaller number such as 0.5 for 50%
|
|
|
|
if AppSettings.isDevelopmentBuild {
|
|
|
|
options.sampleRate = 1.0
|
|
|
|
options.tracesSampleRate = 1.0
|
|
|
|
options.profilesSampleRate = 1.0
|
|
|
|
} else {
|
|
|
|
options.sampleRate = 0.5
|
|
|
|
options.tracesSampleRate = 0.5
|
|
|
|
options.profilesSampleRate = 0.5
|
|
|
|
}
|
|
|
|
|
|
|
|
// This callback is only executed once during the entire run of the program to avoid
|
|
|
|
// multiple callbacks if there are multiple crash events to send (see method documentation)
|
|
|
|
options.onCrashedLastRun = { event in
|
|
|
|
MXLog.error("Sentry detected a crash in the previous run: \(event.eventId.sentryIdString)")
|
|
|
|
ServiceLocator.shared.bugReportService.lastCrashEventID = event.eventId.sentryIdString
|
2024-06-28 18:15:57 +03:00
|
|
|
}
|
2024-07-17 14:47:51 +03:00
|
|
|
|
|
|
|
SentrySDK.start(options: options)
|
|
|
|
|
|
|
|
MXLog.info("SentrySDK started")
|
2024-06-28 18:15:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private func teardownSentry() {
|
|
|
|
SentrySDK.close()
|
|
|
|
MXLog.info("SentrySDK stopped")
|
|
|
|
}
|
|
|
|
|
2022-07-01 13:56:52 +03:00
|
|
|
// MARK: Toasts and loading indicators
|
|
|
|
|
2024-03-21 14:01:23 +02:00
|
|
|
private static let loadingIndicatorIdentifier = "\(AppCoordinator.self)-Loading"
|
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
|
|
|
|
2024-08-06 13:23:29 +03:00
|
|
|
let serverName = String(userSession.clientProxy.userIDServerName ?? "Unknown")
|
2024-06-28 18:15:57 +03:00
|
|
|
|
|
|
|
ServiceLocator.shared.analytics.signpost.beginFirstSync(serverName: serverName)
|
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)
|
2024-08-08 18:29:39 +02:00
|
|
|
.sink { [weak self] state in
|
2023-09-06 16:10:36 +03:00
|
|
|
let toastIdentifier = "StaleDataIndicator"
|
|
|
|
|
|
|
|
switch state {
|
|
|
|
case .loading:
|
2024-08-08 18:29:39 +02:00
|
|
|
if self?.appMediator.networkMonitor.reachabilityPublisher.value == .reachable {
|
2023-09-06 16:10:36 +03:00
|
|
|
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
|
|
|
|
}
|
2024-04-22 18:10:24 +03:00
|
|
|
|
|
|
|
backgroundTask = appMediator.beginBackgroundTask { [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
|
|
|
|
2024-04-22 18:10:24 +03:00
|
|
|
if let backgroundTask {
|
|
|
|
appMediator.endBackgroundTask(backgroundTask)
|
|
|
|
self.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")
|
|
|
|
|
2024-04-22 18:10:24 +03:00
|
|
|
if let backgroundTask {
|
|
|
|
appMediator.endBackgroundTask(backgroundTask)
|
|
|
|
self.backgroundTask = nil
|
|
|
|
}
|
2022-11-28 18:42:49 +03:00
|
|
|
|
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
|
2024-02-28 13:21:54 +02:00
|
|
|
.actionsPublisher
|
2023-05-15 13:06:25 +03:00
|
|
|
.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
|
|
|
}
|
|
|
|
}
|