1// DuressPINSetupView.swift2// SophaxChat3//4// UI for setting the real lock PIN and the duress PIN.5// Two separate structs presented from SettingsView.67import SwiftUI89// MARK: - Real lock PIN setup1011struct LockPINSetupView: View {12@EnvironmentObject var appState: AppState13@Environment(\.dismiss) private var dismiss1415@State private var pin =""16@State private var confirm =""17@State private var errorText: String?18@State private var saved = false1920 var body: some View {21 NavigationStack {22 Form {23 Section {24 Text("Set a numeric PIN as an alternative unlock method alongside biometrics.")25.font(.subheadline)26.foregroundStyle(.secondary)27}2829 Section("New PIN"){30 SecureField("PIN (4–8 digits)", text: $pin)31.keyboardType(.numberPad)32.textContentType(.newPassword)33 SecureField("Confirm PIN", text: $confirm)34.keyboardType(.numberPad)35.textContentType(.newPassword)36}3738 Section {39 Button("Save PIN"){40 savePIN()41}42.disabled(!isValid)43}4445 if let err = errorText {46 Section { Text(err).foregroundStyle(.red)}47}48 if saved {49 Section { Text("Lock PIN saved.").foregroundStyle(.green)}50}51}52.navigationTitle("Set Lock PIN")53.navigationBarTitleDisplayMode(.inline)54.toolbar {55 ToolbarItem(placement:.confirmationAction){56 Button("Done"){ dismiss()}57}58}59}60}6162 private var isValid: Bool {63 let digits = pin.filter(\.isNumber)64 return digits.count >= 4 && digits.count <= 8 && pin == confirm65}6667 private func savePIN(){68 errorText = nil69 saved = false70 do {71 try appState.setRealLockPIN(pin)72 saved = true73 pin =""74 confirm =""75} catch {76 errorText = error.localizedDescription77}78}79}8081// MARK: - Duress PIN setup8283struct DuressPINSetupView: View {84@EnvironmentObject var appState: AppState85@Environment(\.dismiss) private var dismiss8687@State private var pin =""88@State private var confirm =""89@State private var errorText: String?90@State private var saved = false9192 var body: some View {93 NavigationStack {94 Form {95 Section {96 Text("When you enter this PIN on the lock screen, the app appears empty — no messages, no contacts. The real app remains intact.")97.font(.subheadline)98.foregroundStyle(.secondary)99}100101 Section("Duress PIN"){102 SecureField("PIN (4–8 digits, different from lock PIN)", text: $pin)103.keyboardType(.numberPad)104.textContentType(.newPassword)105 SecureField("Confirm PIN", text: $confirm)106.keyboardType(.numberPad)107.textContentType(.newPassword)108}109110 Section {111 Button("Save Duress PIN"){112 savePIN()113}114.disabled(!isValid)115}116117 if let err = errorText {118 Section { Text(err).foregroundStyle(.red)}119}120 if saved {121 Section { Text("Duress PIN saved.").foregroundStyle(.green)}122}123124 Section {125 VStack(alignment:.leading, spacing: 6){126 Label("Entering this PIN will NOT unlock the real app", systemImage:"eye.slash")127 Label("The app shows an empty state with no data", systemImage:"tray")128 Label("Messages are silently dropped until real unlock", systemImage:"bell.slash")129 Label("Must differ from your real lock PIN", systemImage:"exclamationmark.triangle")130}131.font(.caption)132.foregroundStyle(.secondary)133}134}135.navigationTitle("Set Duress PIN")136.navigationBarTitleDisplayMode(.inline)137.toolbar {138 ToolbarItem(placement:.confirmationAction){139 Button("Done"){ dismiss()}140}141}142}143}144145 private var isValid: Bool {146 let digits = pin.filter(\.isNumber)147 guard digits.count >= 4 && digits.count <= 8 && pin == confirm else { return false }148// Must not match the real lock PIN149 return !appState.verifyRealLockPIN(pin)150}151152 private func savePIN(){153 errorText = nil154 saved = false155 do {156 try appState.setDuressPIN(pin)157 saved = true158 pin =""159 confirm =""160} catch {161 errorText = error.localizedDescription162}163}164}