InvestmentTrackerApp/PortfolioJournal/Views/Security/AppLockView.swift

125 lines
4.1 KiB
Swift

import SwiftUI
struct AppLockView: View {
@Binding var isUnlocked: Bool
@AppStorage("faceIdEnabled") private var faceIdEnabled = false
@AppStorage("pinEnabled") private var pinEnabled = false
@State private var pin = ""
@State private var errorMessage: String?
@State private var didAttemptBiometrics = false
@FocusState private var pinFocused: Bool
var body: some View {
ZStack {
Color(.systemBackground)
.ignoresSafeArea()
VStack(spacing: 20) {
Spacer()
Image("BrandMark")
.resizable()
.scaledToFit()
.frame(width: 64, height: 64)
Text("Locked")
.font(.title.weight(.bold))
Text("Unlock Portfolio Journal to view your data.")
.font(.subheadline)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 30)
if faceIdEnabled {
Button {
authenticateWithBiometrics()
} label: {
Label("Unlock with Face ID", systemImage: "faceid")
.font(.headline)
.frame(maxWidth: .infinity)
.padding()
.background(Color.appPrimary)
.foregroundColor(.white)
.cornerRadius(AppConstants.UI.cornerRadius)
}
.padding(.horizontal, 24)
}
if pinEnabled {
VStack(spacing: 10) {
SecureField("4-digit PIN", text: $pin)
.keyboardType(.numberPad)
.textContentType(.oneTimeCode)
.multilineTextAlignment(.center)
.font(.title3.weight(.semibold))
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(12)
.focused($pinFocused)
.onChange(of: pin) { _, newValue in
pin = String(newValue.filter(\.isNumber).prefix(4))
errorMessage = nil
if pin.count == 4 {
validatePin()
}
}
Button("Unlock") {
validatePin()
}
.font(.subheadline.weight(.semibold))
.disabled(pin.count < 4)
}
.padding(.horizontal, 24)
}
if let errorMessage {
Text(errorMessage)
.font(.caption)
.foregroundColor(.negativeRed)
}
Spacer()
}
}
.onAppear {
if faceIdEnabled && !didAttemptBiometrics {
didAttemptBiometrics = true
authenticateWithBiometrics()
} else if pinEnabled {
pinFocused = true
}
}
}
private func authenticateWithBiometrics() {
AppLockService.authenticate(reason: "Unlock your portfolio") { success in
if success {
isUnlocked = true
} else if pinEnabled {
pinFocused = true
} else {
errorMessage = "Face ID failed. Please try again."
}
}
}
private func validatePin() {
guard let storedPin = KeychainService.readPin() else {
errorMessage = "PIN not set."
pin = ""
return
}
if pin == storedPin {
isUnlocked = true
pin = ""
errorMessage = nil
} else {
errorMessage = "Incorrect PIN."
pin = ""
}
}
}