Add support for Face ID/Touch ID to app lock. (#1966)

* Fix biometrics with low grace period and backgrounding before unlocked.

* Trigger permissions alert when enabling Face ID.
This commit is contained in:
Doug
2023-10-27 10:09:12 +01:00
committed by GitHub
parent 2eb8945824
commit 11563c6297
27 changed files with 598 additions and 92 deletions
@@ -32,6 +32,8 @@ class AppLockFlowCoordinator: CoordinatorProtocol {
let userIndicatorController: UserIndicatorController
let navigationCoordinator: NavigationRootCoordinator
/// A task used to await biometric unlock before showing the PIN screen.
@CancellableTask private var unlockTask: Task<Void, Never>?
private var cancellables: Set<AnyCancellable> = []
private let actionsSubject: PassthroughSubject<AppLockFlowCoordinatorAction, Never> = .init()
@@ -44,6 +46,9 @@ class AppLockFlowCoordinator: CoordinatorProtocol {
self.userIndicatorController = userIndicatorController
self.navigationCoordinator = navigationCoordinator
// Set the initial background state.
showPlaceholder()
appLockService.disabledPublisher
.sink { [weak self] _ in
// When the service is disabled via a force logout, we need to remove the activity indicator.
@@ -71,6 +76,8 @@ class AppLockFlowCoordinator: CoordinatorProtocol {
// MARK: - App unlock
private func applicationDidEnterBackground() {
unlockTask = nil
guard appLockService.isEnabled else { return }
appLockService.applicationDidEnterBackground()
@@ -80,12 +87,33 @@ class AppLockFlowCoordinator: CoordinatorProtocol {
private func applicationWillEnterForeground() {
guard appLockService.isEnabled else { return }
if appLockService.computeNeedsUnlock(willEnterForegroundAt: .now) {
showUnlockScreen()
} else {
guard appLockService.computeNeedsUnlock(willEnterForegroundAt: .now) else {
// Reveal the app again if within the grace period.
actionsSubject.send(.unlockApp)
return
}
// Show the relevant unlock mechanism.
unlockTask = Task { [weak self] in
guard let self else { return }
await startUnlockFlow()
}
}
/// Runs the unlock flow, showing Touch ID/Face ID if available, transitioning to PIN unlock if it fails or isn't available.
private func startUnlockFlow() async {
if appLockService.biometricUnlockEnabled, appLockService.biometricUnlockTrusted {
showPlaceholder() // For the unlock background.
if await appLockService.unlockWithBiometrics() {
actionsSubject.send(.unlockApp)
return
}
}
guard !Task.isCancelled else { return }
showUnlockScreen()
}
/// Displays the unlock flow with the app's placeholder view to hide obscure the view hierarchy in the app switcher.
@@ -140,6 +140,9 @@ class AppLockSetupFlowCoordinator: FlowCoordinatorProtocol {
showBiometricsPrompt()
case (.createPIN, .settings):
navigationStackCoordinator.setSheetCoordinator(nil)
// The above is fine for change pin, but not create PIN when biometrics are unavailable.
// Need to track the two flows differently to call the alternative below.
// showSettings()
case (.biometricsPrompt, .settings):
showSettings()
case (.settings, .createPIN):