From 45e4cbcceeeda01568ae49d5bc0c26a13bb06fe9 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Mon, 27 Nov 2023 16:52:33 +0000 Subject: [PATCH] App Lock: Don't rely on the app becoming active at startup. (#2175) --- .../AppLockFlowCoordinator.swift | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/ElementX/Sources/FlowCoordinators/AppLockFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/AppLockFlowCoordinator.swift index ee45e7b4e..3567dae97 100644 --- a/ElementX/Sources/FlowCoordinators/AppLockFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/AppLockFlowCoordinator.swift @@ -34,7 +34,8 @@ class AppLockFlowCoordinator: CoordinatorProtocol { /// States the flow can find itself in enum State: StateType { - /// The initial state before the app has launched. + /// The initial state before the app has launched. If the user hasn't enabled + /// App Lock, then the flow will continue to remain in this state after launch. case initial /// The app is in the foreground and visible to the user. case unlocked @@ -56,6 +57,8 @@ class AppLockFlowCoordinator: CoordinatorProtocol { /// Events that can be triggered on the flow state machine enum Event: EventType { + /// Starts the flow while the app is launching in the background. + case start /// The app is resigning active (going into the app switcher, showing system UI like Face ID, permissions prompt etc). case willResignActive /// The app is now backgrounded and not visible to the user. @@ -99,7 +102,7 @@ class AppLockFlowCoordinator: CoordinatorProtocol { // Set the initial state and start with the placeholder screen as the root view. stateMachine = .init(state: initialState) - showPlaceholder() + configureStateMachine() notificationCenter.publisher(for: UIApplication.willResignActiveNotification) .sink { [weak self] _ in @@ -124,8 +127,6 @@ class AppLockFlowCoordinator: CoordinatorProtocol { self?.stateMachine.tryEvent(isEnabled ? .serviceEnabled : .serviceDisabled) } .store(in: &cancellables) - - configureStateMachine() } func toPresentable() -> AnyView { @@ -139,13 +140,16 @@ class AppLockFlowCoordinator: CoordinatorProtocol { guard let self, appLockService.isEnabled else { return fromState } switch (fromState, event) { + case (.initial, .start): + return .backgrounded + case (.unlocked, .willResignActive): return .appObscured case (.appObscured, .didBecomeActive): return .unlocked case (_, .didEnterBackground): return .backgrounded - case (.backgrounded, .didBecomeActive), (.initial, .didBecomeActive): + case (.backgrounded, .didBecomeActive): guard appLockService.computeNeedsUnlock(didBecomeActiveAt: .now) else { return .unlocked } return isBiometricUnlockAvailable ? .attemptingBiometricUnlock : .attemptingPINUnlock case (.attemptingBiometricUnlock, .didFinishBiometricUnlock(let result)): @@ -183,7 +187,7 @@ class AppLockFlowCoordinator: CoordinatorProtocol { showPlaceholder() case (_, .backgrounded): appLockService.applicationDidEnterBackground() - showPlaceholder() // Double call but just to be safe. + showPlaceholder() // Double call but just to be safe. Useful at app launch. case (_, .attemptingBiometricUnlock): showPlaceholder() // For the unlock background. Triple call but just to be safe. Task { await self.attemptBiometricUnlock() } @@ -203,6 +207,8 @@ class AppLockFlowCoordinator: CoordinatorProtocol { stateMachine.addErrorHandler { context in fatalError("Unexpected transition from `\(context.fromState)` to `\(context.toState)` with event `\(String(describing: context.event))`.") } + + stateMachine.tryEvent(.start) } // MARK: - App unlock