initial version
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
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 = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user