Implement bank transfer app flows
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import Foundation
|
||||
import Observation
|
||||
|
||||
@Observable
|
||||
final class LockViewModel {
|
||||
private static let maxAttempts = 5
|
||||
private static let lockoutDuration = 30.0
|
||||
|
||||
var pinDigits: [Int] = []
|
||||
var hintMessage = ""
|
||||
var isVerifying = false
|
||||
|
||||
private let autoUnlock: Bool
|
||||
private(set) var pinLength: Int
|
||||
|
||||
init() {
|
||||
self.autoUnlock = UserDefaults.standard.bool(forKey: "auto_unlock_pin")
|
||||
self.pinLength = max(4, UserDefaults.standard.integer(forKey: "pin_length"))
|
||||
}
|
||||
|
||||
// MARK: - Display
|
||||
|
||||
var dotsDisplay: String {
|
||||
let n = pinDigits.count
|
||||
let total = autoUnlock ? pinLength : max(n, 4)
|
||||
return String(repeating: "●", count: n)
|
||||
+ String(repeating: "○", count: max(total - n, 0))
|
||||
}
|
||||
|
||||
// MARK: - Lockout
|
||||
|
||||
var lockoutRemaining: TimeInterval {
|
||||
let fails = UserDefaults.standard.integer(forKey: "lock_fail_count")
|
||||
guard fails >= Self.maxAttempts else { return 0 }
|
||||
let lastFail = UserDefaults.standard.double(forKey: "lock_last_fail_time")
|
||||
let elapsed = Date().timeIntervalSince1970 - lastFail
|
||||
return max(0, Self.lockoutDuration - elapsed)
|
||||
}
|
||||
|
||||
var isLockedOut: Bool { lockoutRemaining > 0 }
|
||||
|
||||
func resetFailures() {
|
||||
UserDefaults.standard.removeObject(forKey: "lock_fail_count")
|
||||
UserDefaults.standard.removeObject(forKey: "lock_last_fail_time")
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func incrementFailures() -> Int {
|
||||
let fails = UserDefaults.standard.integer(forKey: "lock_fail_count") + 1
|
||||
UserDefaults.standard.set(fails, forKey: "lock_fail_count")
|
||||
UserDefaults.standard.set(Date().timeIntervalSince1970, forKey: "lock_last_fail_time")
|
||||
return fails
|
||||
}
|
||||
|
||||
// MARK: - Key handling
|
||||
|
||||
// Returns true when PIN should be submitted
|
||||
func handleKey(_ key: String) -> Bool {
|
||||
guard !isVerifying else { return false }
|
||||
switch key {
|
||||
case "⌫":
|
||||
if !pinDigits.isEmpty { pinDigits.removeLast() }
|
||||
case "✓":
|
||||
if pinDigits.count >= 4 { return true }
|
||||
default:
|
||||
if let d = Int(key), pinDigits.count < 8 {
|
||||
pinDigits.append(d)
|
||||
if autoUnlock && pinDigits.count == pinLength { return true }
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func currentPin() -> String { pinDigits.map(String.init).joined() }
|
||||
func clearPin() { pinDigits = [] }
|
||||
|
||||
func failureHint() -> String {
|
||||
let fails = incrementFailures()
|
||||
let remaining = Self.maxAttempts - fails
|
||||
if remaining <= 0 {
|
||||
return "Too many attempts. Try again in \(Int(Self.lockoutDuration))s."
|
||||
}
|
||||
return "\(remaining) attempt\(remaining == 1 ? "" : "s") remaining"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user