Deploy Ladill Mini / deploy (push) Successful in 35s
Ship full-featured mobile clients with auth, payments, QRs, push notifications, and iOS polish including Figtree typography, app icons, and QR previews. Co-authored-by: Cursor <cursoragent@cursor.com>
126 lines
3.9 KiB
Swift
126 lines
3.9 KiB
Swift
import SwiftUI
|
|
|
|
struct PinLockView: View {
|
|
let onUnlocked: () -> Void
|
|
@State private var pin = ""
|
|
@State private var error: String?
|
|
|
|
var body: some View {
|
|
PinPadView(title: "Enter PIN", pin: $pin, error: $error) {
|
|
if PinStore.shared.verifyPin(pin) {
|
|
onUnlocked()
|
|
} else {
|
|
error = "Incorrect PIN. Try again."
|
|
pin = ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct PinLockOverlay: View {
|
|
let onUnlocked: () -> Void
|
|
@State private var pin = ""
|
|
@State private var error: String?
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
LadillColors.page.ignoresSafeArea()
|
|
PinPadView(title: "Enter PIN", pin: $pin, error: $error) {
|
|
if PinStore.shared.verifyPin(pin) {
|
|
onUnlocked()
|
|
} else {
|
|
error = "Incorrect PIN. Try again."
|
|
pin = ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct PinSetupView: View {
|
|
let onPinSet: () -> Void
|
|
@State private var pin = ""
|
|
@State private var confirm = ""
|
|
@State private var step = 0
|
|
@State private var error: String?
|
|
|
|
var body: some View {
|
|
PinPadView(title: step == 0 ? "Set a PIN" : "Confirm PIN", pin: step == 0 ? $pin : $confirm, error: $error) {
|
|
if step == 0 {
|
|
step = 1
|
|
} else if pin == confirm {
|
|
PinStore.shared.savePin(pin)
|
|
onPinSet()
|
|
} else {
|
|
error = "PINs don't match."
|
|
confirm = ""
|
|
step = 0
|
|
pin = ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct PinPadView: View {
|
|
let title: String
|
|
@Binding var pin: String
|
|
@Binding var error: String?
|
|
let onComplete: () -> Void
|
|
|
|
var body: some View {
|
|
VStack(spacing: 24) {
|
|
Spacer()
|
|
Image(systemName: "lock.fill")
|
|
.font(.system(size: 32))
|
|
.foregroundStyle(LadillColors.indigo)
|
|
Text(title)
|
|
.font(.ladill(size: 22, weight: .semibold))
|
|
.foregroundStyle(LadillColors.ink)
|
|
|
|
HStack(spacing: 12) {
|
|
ForEach(0..<4, id: \.self) { i in
|
|
Circle()
|
|
.fill(i < pin.count ? LadillColors.indigo : LadillColors.border)
|
|
.frame(width: 14, height: 14)
|
|
}
|
|
}
|
|
|
|
if let error {
|
|
Text(error)
|
|
.font(.ladill(size: 13))
|
|
.foregroundStyle(LadillColors.red)
|
|
}
|
|
|
|
let keys = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "⌫"]
|
|
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 3), spacing: 16) {
|
|
ForEach(keys, id: \.self) { key in
|
|
if key.isEmpty {
|
|
Color.clear.frame(height: 56)
|
|
} else {
|
|
Button {
|
|
if key == "⌫" {
|
|
if !pin.isEmpty { pin.removeLast() }
|
|
} else if pin.count < 4 {
|
|
pin.append(key)
|
|
if pin.count == 4 { onComplete() }
|
|
}
|
|
} label: {
|
|
Text(key)
|
|
.font(.ladill(size: 24, weight: .medium))
|
|
.frame(maxWidth: .infinity)
|
|
.frame(height: 56)
|
|
.background(LadillColors.surface)
|
|
.clipShape(Circle())
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
}
|
|
}
|
|
.padding(.horizontal, 40)
|
|
|
|
Spacer()
|
|
}
|
|
.background(LadillColors.page)
|
|
}
|
|
}
|