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
@@ -46,11 +46,7 @@ class AppLockScreenViewModel: AppLockScreenViewModelType, AppLockScreenViewModel
switch viewAction {
case .submitPINCode:
guard appLockService.unlock(with: state.bindings.pinCode) else {
handleInvalidPIN()
return
}
actionsSubject.send(.appUnlocked)
submitPINCode()
case .clearPINCode:
state.bindings.pinCode = ""
case .forgotPIN:
@@ -60,6 +56,14 @@ class AppLockScreenViewModel: AppLockScreenViewModelType, AppLockScreenViewModel
// MARK: - Private
private func submitPINCode() {
guard appLockService.unlock(with: state.bindings.pinCode) else {
handleInvalidPIN()
return
}
actionsSubject.send(.appUnlocked)
}
private func handleForgotPIN() {
state.bindings.alertInfo = .init(id: .confirmResetPIN,
title: L10n.screenAppLockSignoutAlertTitle,
@@ -39,13 +39,34 @@ class AppLockSetupBiometricsScreenViewModel: AppLockSetupBiometricsScreenViewMod
switch viewAction {
case .allow:
MXLog.info("Enable biometric unlock.")
appLockService.biometricUnlockEnabled = true
actionsSubject.send(.continue)
Task { await enableBiometricUnlock() }
case .skip:
MXLog.info("Disable biometric unlock.")
appLockService.biometricUnlockEnabled = false
actionsSubject.send(.continue)
disableBiometricUnlock()
}
}
// MARK: - Private
private func enableBiometricUnlock() async {
guard case .success = appLockService.enableBiometricUnlock() else {
MXLog.error("Enabling biometric unlock failed.")
return
}
MXLog.info("Biometric unlock enabled.")
// Attempt unlock to trigger Face ID permissions alert.
if appLockService.biometryType == .faceID,
await !appLockService.unlockWithBiometrics() {
MXLog.info("Confirmation failed. Disabling biometric unlock.")
appLockService.disableBiometricUnlock()
}
actionsSubject.send(.continue)
}
private func disableBiometricUnlock() {
appLockService.disableBiometricUnlock()
MXLog.info("Biometric unlock disabled.")
actionsSubject.send(.continue)
}
}
@@ -30,8 +30,8 @@ struct AppLockSetupSettingsScreenViewState: BindableState {
let biometryType: LABiometryType
var bindings: AppLockSetupSettingsScreenViewStateBindings
var supportsBiometry: Bool { biometryType != .none }
var enableBiometryTitle: String { L10n.screenAppLockSetupBiometricUnlockAllowTitle(biometryType.localizedString) }
var supportsBiometrics: Bool { biometryType != .none }
var enableBiometricsTitle: String { L10n.screenAppLockSetupBiometricUnlockAllowTitle(biometryType.localizedString) }
}
struct AppLockSetupSettingsScreenViewStateBindings {
@@ -45,12 +45,34 @@ class AppLockSetupSettingsScreenViewModel: AppLockSetupSettingsScreenViewModelTy
case .disable:
showRemovePINAlert()
case .enableBiometricsChanged:
appLockService.biometricUnlockEnabled = state.bindings.enableBiometrics
Task { await toggleBiometrics() }
}
}
// MARK: - Private
private func toggleBiometrics() async {
if state.bindings.enableBiometrics {
guard case .success = appLockService.enableBiometricUnlock() else {
MXLog.error("Enabling biometric unlock failed.")
state.bindings.enableBiometrics = false
return
}
MXLog.info("Biometric unlock enabled.")
// Attempt unlock to trigger Face ID permissions alert.
if appLockService.biometryType == .faceID,
await !appLockService.unlockWithBiometrics() {
MXLog.info("Confirmation failed. Disabling biometric unlock.")
state.bindings.enableBiometrics = false
appLockService.disableBiometricUnlock()
}
} else {
appLockService.disableBiometricUnlock()
MXLog.info("Biometric unlock disabled.")
}
}
/// Shows a confirmation alert to the user before removing their PIN code.
private func showRemovePINAlert() {
state.bindings.alertInfo = .init(id: .confirmRemovePINCode,
@@ -32,9 +32,9 @@ struct AppLockSetupSettingsScreen: View {
}
}
if context.viewState.supportsBiometry {
if context.viewState.supportsBiometrics {
Section {
ListRow(label: .plain(title: context.viewState.enableBiometryTitle),
ListRow(label: .plain(title: context.viewState.enableBiometricsTitle),
kind: .toggle($context.enableBiometrics))
.onChange(of: context.enableBiometrics) { _ in
context.send(viewAction: .enableBiometricsChanged)
@@ -22,7 +22,7 @@ extension LABiometryType {
var systemSymbol: SFSymbol {
switch self {
case .none:
MXLog.error("Invalid presentation: Biometry not supported.")
MXLog.error("Invalid presentation: Biometrics not supported.")
return .viewfinder
case .touchID:
return .touchid
@@ -40,7 +40,7 @@ extension LABiometryType {
var localizedString: String {
switch self {
case .none:
MXLog.error("Invalid presentation: Biometry not supported.")
MXLog.error("Invalid presentation: Biometrics not supported.")
return L10n.screenAppLockBiometricUnlock
case .touchID:
return L10n.commonTouchIdIos