Implement bank transfer app flows

This commit is contained in:
2026-06-08 00:03:57 +05:00
parent ef877217ad
commit 9b281d48a7
127 changed files with 8008 additions and 90 deletions
@@ -0,0 +1,180 @@
import LocalAuthentication
import SwiftUI
struct SecuritySetupView: View {
var onComplete: () -> Void
var changeMode: Bool = false
private enum Step { case enterPin, confirmPin, done }
@State private var step: Step = .enterPin
@State private var pinDigits: [Int] = []
@State private var firstPin = ""
@State private var hintText = ""
@State private var biometricsAvailable = false
@State private var biometricsEnabled = UserDefaults.standard.bool(forKey: "biometrics_enabled")
var body: some View {
VStack(spacing: 28) {
Spacer()
switch step {
case .enterPin, .confirmPin:
pinSetupContent
case .done:
doneContent
}
Spacer()
}
.animation(.easeInOut(duration: 0.2), value: step)
.task {
let ctx = LAContext()
biometricsAvailable = ctx.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
}
}
// MARK: - PIN setup
@ViewBuilder
private var pinSetupContent: some View {
VStack(spacing: 8) {
Image(systemName: "lock.fill")
.font(.system(size: 40))
.foregroundStyle(.secondary)
Text(step == .enterPin ? "Create a PIN" : "Confirm PIN")
.font(.title2).bold()
Text("Use 48 digits")
.font(.caption)
.foregroundStyle(.secondary)
}
Text(dotsDisplay)
.font(.system(size: 30, design: .monospaced))
.kerning(6)
.frame(height: 44)
.animation(.spring(response: 0.2), value: pinDigits.count)
if !hintText.isEmpty {
Text(hintText)
.font(.callout)
.foregroundStyle(.red)
.transition(.opacity)
}
NumpadView { key in handleKey(key) }
}
// MARK: - Done screen
@ViewBuilder
private var doneContent: some View {
VStack(spacing: 16) {
Image(systemName: "checkmark.shield.fill")
.font(.system(size: 64))
.foregroundStyle(.green)
Text("PIN Created")
.font(.title2).bold()
Text("Your app is now protected.")
.foregroundStyle(.secondary)
}
if biometricsAvailable {
VStack(spacing: 4) {
let label = biometricLabel()
Toggle(isOn: $biometricsEnabled) {
Label("Use \(label) to unlock", systemImage: biometricIcon())
}
.padding(.horizontal, 32)
.tint(.accentColor)
.onChange(of: biometricsEnabled) {
UserDefaults.standard.set(biometricsEnabled, forKey: "biometrics_enabled")
}
Text("Recommended for quick access")
.font(.caption)
.foregroundStyle(.secondary)
}
}
Button("Continue") { onComplete() }
.buttonStyle(.borderedProminent)
.controlSize(.large)
.padding(.horizontal, 40)
}
// MARK: - Logic
private var dotsDisplay: String {
let n = pinDigits.count
let total = max(n, 4)
return String(repeating: "", count: n)
+ String(repeating: "", count: total - n)
}
private func handleKey(_ key: String) {
withAnimation { hintText = "" }
switch key {
case "":
if !pinDigits.isEmpty { pinDigits.removeLast() }
case "":
if pinDigits.count >= 4 { submitPin() }
default:
if let d = Int(key), pinDigits.count < 8 {
pinDigits.append(d)
}
}
}
private func submitPin() {
let entered = pinDigits.map(String.init).joined()
switch step {
case .enterPin:
firstPin = entered
pinDigits = []
step = .confirmPin
case .confirmPin:
if entered == firstPin {
savePin(entered)
withAnimation { step = .done }
} else {
withAnimation { hintText = "PINs don't match — try again" }
pinDigits = []
}
case .done:
break
}
}
private func savePin(_ pin: String) {
let salt = PinHash.generateSalt()
let hash = PinHash.hash(pin, salt: salt)
try? CredentialStore.shared.save(salt.base64EncodedString(), forKey: CredentialStore.Keys.securityHashSalt)
try? CredentialStore.shared.save(hash, forKey: CredentialStore.Keys.securityHash)
UserDefaults.standard.set("pin", forKey: "security_method")
UserDefaults.standard.set(pin.count, forKey: "pin_length")
UserDefaults.standard.set(true, forKey: "auto_unlock_pin")
}
private func biometricLabel() -> String {
let ctx = LAContext()
_ = ctx.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
return ctx.biometryType == .faceID ? "Face ID" : "Touch ID"
}
private func biometricIcon() -> String {
let ctx = LAContext()
_ = ctx.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
return ctx.biometryType == .faceID ? "faceid" : "touchid"
}
}
#Preview {
SecuritySetupView(onComplete: {})
}