Require PIN to disable Face ID
When toggling Face ID off, users must now verify their PIN first. Adds a PinVerifyView component for PIN verification. Fixes #23 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ struct SettingsView: View {
|
||||
@State private var showingBiometricAlert = false
|
||||
@State private var showingPinRequiredAlert = false
|
||||
@State private var showingPinDisableAlert = false
|
||||
@State private var showingPinVerifyForFaceId = false
|
||||
@State private var showingRestartAlert = false
|
||||
@State private var didLoadCloudSync = false
|
||||
@State private var backupToRestore: BackupRecord?
|
||||
@@ -163,6 +164,13 @@ struct SettingsView: View {
|
||||
_ = KeychainService.savePin(pin)
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showingPinVerifyForFaceId) {
|
||||
PinVerifyView(title: "Enter PIN to Disable Face ID") { success in
|
||||
if success {
|
||||
faceIdEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
didLoadCloudSync = true
|
||||
if viewModel.backupsEnabled {
|
||||
@@ -495,6 +503,9 @@ struct SettingsView: View {
|
||||
faceIdEnabled = false
|
||||
showingPinRequiredAlert = true
|
||||
}
|
||||
} else {
|
||||
faceIdEnabled = true
|
||||
showingPinVerifyForFaceId = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -860,6 +871,62 @@ struct PinSetupView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct PinVerifyView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
let title: String
|
||||
let onResult: (Bool) -> Void
|
||||
|
||||
@State private var pin = ""
|
||||
@State private var errorMessage: String?
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section {
|
||||
SecureField("Enter PIN", text: $pin)
|
||||
.keyboardType(.numberPad)
|
||||
.onChange(of: pin) { _, newValue in
|
||||
pin = String(newValue.filter(\.isNumber).prefix(4))
|
||||
}
|
||||
} header: {
|
||||
Text("4-Digit PIN")
|
||||
}
|
||||
|
||||
if let errorMessage {
|
||||
Text(errorMessage)
|
||||
.font(.caption)
|
||||
.foregroundColor(.negativeRed)
|
||||
}
|
||||
}
|
||||
.navigationTitle(title)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel") {
|
||||
onResult(false)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Verify") { verifyPin() }
|
||||
.disabled(pin.count < 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
.presentationDetents([.medium])
|
||||
}
|
||||
|
||||
private func verifyPin() {
|
||||
guard let savedPin = KeychainService.readPin(), pin == savedPin else {
|
||||
errorMessage = "Incorrect PIN."
|
||||
pin = ""
|
||||
return
|
||||
}
|
||||
onResult(true)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SettingsView(iapService: IAPService())
|
||||
.environmentObject(AccountStore(iapService: IAPService()))
|
||||
|
||||
Reference in New Issue
Block a user