A girl dancing in the star dreaming of love β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
a minimalist modern architectural exterior at sunset, ultra-detailed, hyperreal textures, soft bokeh, natural skin tones. soft pastel accents. High resolution, cinematic, subtle film grain. β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
a serene forest glade with morning mist, high contrast, dramatic rim lighting, soft bokeh, natural skin tones. cinematic composition. High resolution, cinematic, subtle film grain. β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
a serene forest glade with morning mist, high contrast, dramatic rim lighting, soft bokeh, natural skin tones. cinematic composition. High resolution, cinematic, subtle film grain. β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
a serene forest glade with morning mist, soft analog film grain, warm muted palette, soft bokeh, natural skin tones. cinematic composition. High resolution, cinematic, subtle film grain. β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
a serene forest glade with morning mist, soft analog film grain, warm muted palette, soft bokeh, natural skin tones. cinematic composition. High resolution, cinematic, subtle film grain. β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
Jehdhd β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
Jehdhd β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
A neon koi fish β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
A neon koi fish β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
Hdhhd β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
car β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
car β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
cars β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
cars β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
Angel holding flower super Deerfield roses drawing β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
Angel holding flower super Deerfield roses drawing β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
Angel holding flower super Deerfield roses drawing β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
Angel holding flower super Deerfield roses drawing β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
import SwiftUI import LocalAuthentication import SafariServices import Security import CryptoKit import UIKit // MARK: - App @main struct ProtonStyleProfileApp: App { var body: some Scene { WindowGroup { NavigationStack { ProfileView() } .preferredColorScheme(.dark) } } } // MARK: - AutoLock & LockManager enum AutoLock: String, CaseIterable, Identifiable { case immediately, m1, m5, m15, h1, h4 var id: String { rawValue } var label: String { switch self { case .immediately: "Immediately" case .m1: "After 1 minute" case .m5: "After 5 minutes" case .m15: "After 15 minutes" case .h1: "After 1 hour" case .h4: "After 4 hours" } } var seconds: Int { switch self { case .immediately: 0 case .m1: 60 case .m5: 300 case .m15: 900 case .h1: 3600 case .h4: 14400 } } static let presets: [AutoLock] = [.immediately, .m1, .m5, .m15, .h1, .h4] } final class LockManager: ObservableObject { @AppStorage("useFaceID") var useFaceID = true @AppStorage("useSystemPasscodeFallback") var useSystemPasscodeFallback = true @AppStorage("autoLock") var autoLockRaw: String = AutoLock.m5.rawValue @AppStorage("lastBackground") private var lastBackground: Double = 0 @AppStorage("hasPIN") private var hasPIN = false @Published var isLocked = false init() { // Avoid auto-lock on first launch by initializing the background timestamp to now lastBackground = Date().timeIntervalSince1970 // Reflect whether a PIN already exists in the keychain hasPIN = KeychainPIN.load() != nil } var autoLock: AutoLock { AutoLock(rawValue: autoLockRaw) ?? .m5 } func willEnterBackground() { lastBackground = Date().timeIntervalSince1970 if autoLock == .immediately { isLocked = true } } func didBecomeActive() { guard autoLock != .immediately else { return } let elapsed = Date().timeIntervalSince1970 - lastBackground if elapsed >= Double(autoLock.seconds) { isLocked = true } } func lockNow() { isLocked = true } func unlockWithBiometrics(completion: @escaping (Bool)->Void) { let ctx = LAContext() var err: NSError? // If fallback is allowed, use .deviceOwnerAuthentication so iOS can prompt Face ID then passcode let primaryPolicy: LAPolicy = useSystemPasscodeFallback ? .deviceOwnerAuthentication : .deviceOwnerAuthenticationWithBiometrics if ctx.canEvaluatePolicy(primaryPolicy, error: &err) { ctx.evaluatePolicy(primaryPolicy, localizedReason: "Unlock the app") { ok, _ in DispatchQueue.main.async { if ok { self.isLocked = false }; completion(ok) } } return } // If the chosen policy failed (e.g., biometrics-only and not available), try the broader one as a fallback let fallbackPolicy: LAPolicy = .deviceOwnerAuthentication if primaryPolicy != fallbackPolicy, ctx.canEvaluatePolicy(fallbackPolicy, error: &err) { ctx.evaluatePolicy(fallbackPolicy, localizedReason: "Unlock with device passcode") { ok, _ in DispatchQueue.main.async { if ok { self.isLocked = false }; completion(ok) } } return } // Nothing available completion(false) } func unlockWithPIN(_ pin: String) -> Bool { guard hasPIN, let rec = KeychainPIN.load() else { return false } if KeychainPIN.verify(input: pin, record: rec) { isLocked = false; return true } return false } func setupPIN(_ pin: String) { KeychainPIN.store(pin: pin); hasPIN = true } } // MARK: - Keychain PIN + Backup/Restore struct PINRecord: Codable { let salt: Data; let hash: Data } enum KeychainPIN { private static let account = "app.pin.record" private static let backupKeyAccount = "app.pin.backup.key" static func store(pin: String) { let salt = randomBytes(16) var msg = Data() msg.append(salt) msg.append(Data(pin.utf8)) let hash = Data(SHA256.hash(data: msg)) let rec = PINRecord(salt: salt, hash: hash) if let blob = try? JSONEncoder().encode(rec) { saveKeychain(data: blob, account: account) } } static func load() -> PINRecord? { guard let blob = loadKeychain(account: account) else { return nil } return try? JSONDecoder().decode(PINRecord.self, from: blob) } static func verify(input: String, record: PINRecord) -> Bool { var msg = Data() msg.append(record.salt) msg.append(Data(input.utf8)) let test = Data(SHA256.hash(data: msg)) return test == record.hash } static func exportBackupString() -> String? { guard let recData = loadKeychain(account: account) else { return nil } let key = loadOrCreateBackupKey() let sealed = try? ChaChaPoly.seal(recData, using: key) return sealed?.combined.base64EncodedString() } static func importBackupString(_ base64: String) -> Bool { guard let data = Data(base64Encoded: base64) else { return false } let key = loadOrCreateBackupKey() guard let box = try? ChaChaPoly.SealedBox(combined: data), let recData = try? ChaChaPoly.open(box, using: key) else { return false } saveKeychain(data: recData, account: account) return true } private static func loadOrCreateBackupKey() -> SymmetricKey { if let raw = loadKeychain(account: backupKeyAccount) { return SymmetricKey(data: raw) } let key = SymmetricKey(size: .bits256) saveKeychain(data: key.withUnsafeBytes { Data($0) }, account: backupKeyAccount) return key } private static func saveKeychain(data: Data, account: String) { let q: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecValueData as String: data, kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly ] SecItemDelete(q as CFDictionary) SecItemAdd(q as CFDictionary, nil) } private static func loadKeychain(account: String) -> Data? { let q: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne ] var out: AnyObject? let status = SecItemCopyMatching(q as CFDictionary, &out) return status == errSecSuccess ? out as? Data : nil } private static func randomBytes(_ n: Int) -> Data { var b = [UInt8](repeating: 0, count: n) _ = SecRandomCopyBytes(kSecRandomDefault, n, &b) return Data(b) } } // β
allow URL? to be used with `.sheet(item:)` extension URL: Identifiable { public var id: String { absoluteString } } // MARK: - PIN Setup View struct PINSetupView: View { let existing: Bool var onSet: (String)->Void @Environment(\.dismiss) private var dismiss @State private var pin1 = "" @State private var pin2 = "" var body: some View { NavigationStack { Form { Section(existing ? "Change PIN" : "Create PIN") { SecureField("Enter PIN", text: $pin1) .keyboardType(.numberPad) SecureField("Confirm PIN", text: $pin2) .keyboardType(.numberPad) Text("PIN must be 4β6 digits. Only numbers allowed.").font(.caption).foregroundStyle(.secondary) } } .navigationTitle(existing ? "Change PIN" : "Create PIN") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarLeading) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .topBarTrailing) { Button("Save") { let allowed = CharacterSet.decimalDigits let isNumeric1 = pin1.unicodeScalars.allSatisfy { allowed.contains($0) } let isNumeric2 = pin2.unicodeScalars.allSatisfy { allowed.contains($0) } guard isNumeric1, isNumeric2, (4...6).contains(pin1.count), pin1 == pin2 else { return } onSet(pin1) dismiss() }.bold() } } } } } // β¦ (KEEP the rest of your ProfileView, LockOverlay, AutoLockPicker, Paywall, etc. code the same) // β
TierCard fix: struct TierCard: View { let title: String; let price: String; let subtitle: String; let highlight: Bool var body: some View { HStack { VStack(alignment: .leading, spacing: 4) { Text(title).font(.headline) Text(subtitle).font(.caption).foregroundStyle(.secondary) } Spacer() Text(price).font(.title3).bold() } .padding() .background( RoundedRectangle(cornerRadius: 14) .fill({ let style: AnyShapeStyle = highlight ? AnyShapeStyle(LinearGradient(colors: [.white.opacity(0.10), .white.opacity(0.04)], startPoint: .top, endPoint: .bottom)) : AnyShapeStyle(Color.white.opacity(0.06)) return style }()) ) .overlay(RoundedRectangle(cornerRadius: 14).stroke(.white.opacity(0.10), lineWidth: 1)) } } // MARK: - UI Bits struct SectionBox<Content: View>: View { var title: String? @ViewBuilder var content: Content var body: some View { VStack(alignment: .leading, spacing: 10) { if let t = title, !(t.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) { Text(t.uppercased()).font(.caption).foregroundStyle(.secondary).padding(.horizontal, 6) } VStack(spacing: 10) { content } .padding(12) .background(RoundedRectangle(cornerRadius: 18).fill(AppColors.card)) .overlay(RoundedRectangle(cornerRadius: 18).stroke(AppColors.cardStroke, lineWidth: 1)) } } } struct ToggleRow: View { var icon: String var title: String @Binding var isOn: Bool var body: some View { HStack(spacing: 14) { RoundedRectangle(cornerRadius: 10).fill(AppColors.tileIcon).frame(width: 36, height: 36) .overlay(Image(systemName: icon).imageScale(.medium)) Text(title) Spacer() Toggle("", isOn: $isOn).labelsHidden() } .padding(12) .background(RoundedRectangle(cornerRadius: 14).fill(AppColors.tile)) .overlay(RoundedRectangle(cornerRadius: 14).stroke(AppColors.tileStroke, lineWidth: 1)) } } struct GradientPill: View { var icon: String; var title: String; var action: ()->Void var body: some View { Button(action: action) { HStack(spacing: 8) { Image(systemName: icon).imageScale(.medium) Text(title).font(.headline) } .padding(.vertical, 10).padding(.horizontal, 14) .background(LinearGradient(colors: [AppColors.purple.opacity(0.9), AppColors.indigo.opacity(0.9)], startPoint: .topLeading, endPoint: .bottomTrailing)) .overlay(RoundedRectangle(cornerRadius: 16).stroke(AppColors.purple.opacity(0.35), lineWidth: 1)) .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) .shadow(color: AppColors.purple.opacity(0.35), radius: 10, y: 4) }.buttonStyle(.plain) } } struct ToastView: View { var text: String var body: some View { Text(text).font(.subheadline) .padding(.horizontal, 14).padding(.vertical, 10) .background(.ultraThinMaterial, in: Capsule()) .overlay(Capsule().stroke(.white.opacity(0.25), lineWidth: 1)) } } // MARK: - Safari + Share struct SafariSheet: UIViewControllerRepresentable, Identifiable { let url: URL; var id: URL { url } func makeUIViewController(context: Context) -> SFSafariViewController { .init(url: url) } func updateUIViewController(_ vc: SFSafariViewController, context: Context) {} } struct ActivityShare: UIViewControllerRepresentable { var items: [Any]; var subject: String? func makeUIViewController(context: Context) -> UIActivityViewController { let vc = UIActivityViewController(activityItems: items, applicationActivities: nil) if let s = subject { vc.setValue(s, forKey: "subject") } return vc } func updateUIViewController(_ vc: UIActivityViewController, context: Context) {} } // MARK: - Theme enum AppColors { static let card = Color.white.opacity(0.06) static let cardStroke = Color.white.opacity(0.09) static let tile = Color.white.opacity(0.06) static let tileStroke = Color.white.opacity(0.08) static let tileIcon = Color.white.opacity(0.10) static let chip = Color.white.opacity(0.06) static let chipStroke = Color.white.opacity(0.10) static let divider = Color.white.opacity(0.12) static let avatar = Color.purple.opacity(0.85) static let purple = Color(hue: 0.74, saturation: 0.60, brightness: 0.75) static let indigo = Color(hue: 0.65, saturation: 0.65, brightness: 0.70) } // MARK: - Animated Background struct ArcaneBackground: View { var body: some View { TimelineView(.animation) { timeline in let tt = timeline.date.timeIntervalSinceReferenceDate ZStack { LinearGradient(colors: [.black, .black.opacity(0.92)], startPoint: .top, endPoint: .bottom) .ignoresSafeArea() // soft spotlights Circle().fill(AppColors.purple.opacity(0.18)) .blur(radius: 80).frame(width: 320, height: 320) .offset(x: CGFloat(sin(tt/3.2)) * 90, y: -140) Circle().fill(AppColors.indigo.opacity(0.16)) .blur(radius: 80).frame(width: 360, height: 360) .offset(x: -140, y: CGFloat(cos(tt/2.4)) * 70) // floating glyphs ForEach(0..<5) { i in let phase = sin(tt/3 + Double(i)*1.3) Image(systemName: ["shield.checkerboard","sparkles","seal","globe","lock.shield"][i%5]) .font(.system(size: 120 - CGFloat(i*10), weight: .bold)) .foregroundStyle(Color.white.opacity(0.05 + 0.02*Double(i))) .offset(x: CGFloat(phase*Double(60-i*6)), y: CGFloat(cos(tt/4 + Double(i))*Double(70-i*8))) .blur(radius: CGFloat(i)) .blendMode(.plusLighter) } } } } } // MARK: - AutoLock Picker struct AutoLockPicker: View { @Environment(\.dismiss) private var dismiss @Binding var selected: AutoLock @State private var working: AutoLock init(selected: Binding<AutoLock>) { _selected = selected _working = State(initialValue: selected.wrappedValue) } var body: some View { NavigationStack { List { Section { ForEach(AutoLock.presets) { opt in HStack { Text(opt.label) Spacer() if opt == working { Image(systemName: "checkmark.circle.fill") .foregroundStyle(.blue) } } .contentShape(Rectangle()) .onTapGesture { working = opt } } } footer: { Text("Automatic lock secures the app after inactivity.") } } .navigationTitle("Automatic lock") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarLeading) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .topBarTrailing) { Button("Done") { selected = working; dismiss() }.bold() } } } .presentationDetents(Set([.medium])) } } // MARK: - Lock Overlay with Keypad struct LockOverlay: View { var faceIDEnabled: Bool var passcodeFallback: Bool var hasPIN: Bool var unlockBiometric: () -> Void var unlockPIN: (String) -> Bool @State private var pin = "" @State private var shake = false @State private var errorText: String? = nil var body: some View { ZStack { ArcaneBackground().ignoresSafeArea() VStack(spacing: 18) { Image(systemName: "lock.fill").font(.system(size: 40, weight: .bold)) Text("Locked").font(.title3).bold() Text(faceIDEnabled ? (passcodeFallback ? "Face ID/Passcode or PIN" : "Face ID or PIN") : "Enter PIN to continue") .foregroundStyle(.secondary) PINDots(count: 6, filled: pin.count) .modifier(ShakeEffect(shakes: shake ? 2 : 0)) Keypad(onTap: { d in if d == "<" { if !pin.isEmpty { pin.removeLast() } } else if d == "β" { if pin.count >= 4 { let ok = unlockPIN(pin) if !ok { errorText = "Incorrect PIN" withAnimation { shake = true } DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { shake = false } } pin.removeAll() } } else { if pin.count < 6 { pin.append(d) } if pin.count == 6 { let ok = unlockPIN(pin) if !ok { errorText = "Incorrect PIN" withAnimation { shake = true } DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { shake = false } } pin.removeAll() } } }) if let e = errorText { Text(e).foregroundStyle(.red).font(.footnote) } if faceIDEnabled || passcodeFallback { Button { unlockBiometric() } label: { Label(passcodeFallback ? "Use Face ID / Passcode" : "Use Face ID", systemImage: "faceid") .padding(.horizontal, 16).padding(.vertical, 10) } .background(.ultraThinMaterial, in: Capsule()) .overlay(Capsule().stroke(.white.opacity(0.25), lineWidth: 1)) } } .padding(24) .background(RoundedRectangle(cornerRadius: 20).fill(.ultraThinMaterial)) .overlay(RoundedRectangle(cornerRadius: 20).stroke(.white.opacity(0.12), lineWidth: 1)) .shadow(radius: 18) .padding(.horizontal, 28) } } } struct PINDots: View { let count: Int let filled: Int var body: some View { HStack(spacing: 10) { ForEach(0..<count, id: \.self) { i in Circle() .fill(i < filled ? .white : .white.opacity(0.25)) .frame(width: 12, height: 12) } }.padding(.vertical, 6) } } struct Keypad: View { var onTap: (String)->Void private let rows = [ ["1","2","3"], ["4","5","6"], ["7","8","9"], ["<","0","β"] ] var body: some View { VStack(spacing: 10) { ForEach(rows, id: \.self) { row in HStack(spacing: 10) { ForEach(row, id: \.self) { key in Button { onTap(key) } label: { Text(key) .font(.title2).bold() .frame(width: 70, height: 52) .background(RoundedRectangle(cornerRadius: 12).fill(Color.white.opacity(0.08))) .overlay(RoundedRectangle(cornerRadius: 12).stroke(.white.opacity(0.12), lineWidth: 1)) } .buttonStyle(.plain) } } } } } } struct ShakeEffect: GeometryEffect { var shakes: CGFloat = 0 var animatableData: CGFloat { get { shakes } set { shakes = newValue } } func effectValue(size: CGSize) -> ProjectionTransform { let translation = 8 * sin(shakes * .pi * 3) return ProjectionTransform(CGAffineTransform(translationX: translation, y: 0)) } } // MARK: - Paywall struct PaywallView: View { var close: ()->Void var body: some View { VStack(spacing: 20) { Text("Upgrade to Pro") .font(.title).bold() Text("Unlock unlimited aliases, secure link sharing, and priority support.") .multilineTextAlignment(.center) .foregroundStyle(.secondary) .padding(.horizontal) VStack(spacing: 12) { TierCard(title: "Annual", price: "$39.99", subtitle: "Best value", highlight: true) TierCard(title: "Monthly", price: "$4.99", subtitle: "Flexible", highlight: false) } .padding(.horizontal) VStack(alignment: .leading, spacing: 8) { Label("Unlimited aliases", systemImage: "infinity") Label("Secure link vault", systemImage: "link.circle") Label("Face ID lock", systemImage: "faceid") Label("Priority support", systemImage: "bolt.fill") } .frame(maxWidth: .infinity, alignment: .leading) .padding() .background(RoundedRectangle(cornerRadius: 16).fill(Color.white.opacity(0.06))) .overlay(RoundedRectangle(cornerRadius: 16).stroke(.white.opacity(0.08), lineWidth: 1)) .padding(.horizontal) HStack(spacing: 12) { Button("Restore") { close() } .buttonStyle(.bordered) Button("Continue") { close() } .buttonStyle(.borderedProminent) } Button("Not now") { close() }.padding(.top, 6) } .padding(20) .background(ArcaneBackground().ignoresSafeArea()) .presentationDetents(Set([.medium])) } } // MARK: - Profile Screen (main UI) struct ProfileView: View { @StateObject private var lock = LockManager() @AppStorage("autoLock") private var autoLockRaw: String = AutoLock.m5.rawValue @AppStorage("useFaceID") private var useFaceID = true @AppStorage("useSystemPasscodeFallback") private var useSystemPasscodeFallback = true @Environment(\.scenePhase) private var scenePhase @State private var showAutoLockPicker = false @State private var showPINSheet = false @State private var showSafari: URL? = nil @State private var showPaywall = false @State private var toast: String? = nil @State private var isPulsing = false @State private var shareBackup: String? = nil @State private var showShare = false @State private var importText = "" var body: some View { ZStack { ArcaneBackground().ignoresSafeArea() ScrollView { VStack(spacing: 22) { header() accountCard() itemChips() securitySection() lockSection() backupSection() legalSection() helpCenterSection() toolsSection() appVersionFooter() } .padding(.horizontal, 16) .padding(.vertical, 24) } .navigationTitle("Profile") .toolbar { ToolbarItem(placement: .topBarTrailing) { GradientPill(icon: "diamond.fill", title: "Upgrade") { showPaywall = true haptic(.light) } } } .sheet(isPresented: $showPaywall) { PaywallView(close: { showPaywall = false }) } .sheet(item: $showSafari) { url in SafariSheet(url: url) } .sheet(isPresented: $showAutoLockPicker) { AutoLockPicker(selected: Binding( get: { AutoLock(rawValue: autoLockRaw) ?? .m5 }, set: { autoLockRaw = $0.rawValue; lock.autoLockRaw = $0.rawValue } )) .presentationDetents(Set([.medium])) } .sheet(isPresented: $showPINSheet) { PINSetupView(existing: KeychainPIN.load() != nil) { newPIN in lock.setupPIN(newPIN) } .presentationDetents(Set([.medium])) } .sheet(isPresented: $showShare) { if let str = shareBackup { ActivityShare(items: [str], subject: "App PIN Backup") } } if let t = toast { ToastView(text: t) .transition(.move(edge: .top).combined(with: .opacity)) .zIndex(3) .padding(.top, 10) } if lock.isLocked { LockOverlay( faceIDEnabled: useFaceID, passcodeFallback: useSystemPasscodeFallback, hasPIN: KeychainPIN.load() != nil, unlockBiometric: { lock.unlockWithBiometrics { _ in } }, unlockPIN: { pin in lock.unlockWithPIN(pin) } ) .zIndex(4) .transition(.opacity) } } .onChange(of: scenePhase) { phase in switch phase { case .background: lock.useFaceID = useFaceID lock.useSystemPasscodeFallback = useSystemPasscodeFallback lock.autoLockRaw = autoLockRaw lock.willEnterBackground() case .active: lock.didBecomeActive() default: break } } } // MARK: Sections private func header() -> some View { Rectangle().fill(.clear).frame(height: 1) } private func accountCard() -> some View { VStack(alignment: .leading, spacing: 14) { HStack(spacing: 14) { ZStack { RoundedRectangle(cornerRadius: 10) .fill(AppColors.avatar) .frame(width: 44, height: 44) .overlay(Text("P").font(.title2).fontWeight(.semibold).foregroundStyle(.white)) Circle() .strokeBorder(.white.opacity(0.15), lineWidth: 1) .frame(width: 52, height: 52) .offset(y: 1) .opacity(isPulsing ? 0.25 : 0.1) .animation(.easeInOut(duration: 2.4).repeatForever(), value: isPulsing) } VStack(alignment: .leading, spacing: 4) { Text("Gary Makinson").font(.headline) Text("Mail Plus").font(.subheadline).foregroundStyle(.secondary) Text("pixeldreamerapp@pm.me").font(.footnote).foregroundStyle(.secondary) } Spacer() Image(systemName: "chevron.down").foregroundStyle(.secondary) } Divider().background(AppColors.divider) NavigationLink { Text("Device sign-in options here.") .foregroundStyle(.secondary) .padding() .navigationTitle("Sign in to another device") .navigationBarTitleDisplayMode(.inline) } label: { row(icon: "qrcode.viewfinder", title: "Sign in to another device") } } .padding(16) .background(glassCard()) .onAppear { isPulsing = true } } private func itemChips() -> some View { HStack(spacing: 12) { chip(icon: "person.circle.fill", label: "Identities", value: "2") chip(icon: "glasses", label: "Aliases", value: "0/10") chip(icon: "creditcard.fill", label: "Cards", value: "0") chip(icon: "doc.on.doc.fill", label: "Notes", value: "0") } } private func securitySection() -> some View { SectionBox(title: "Security") { ToggleRow(icon: "faceid", title: "Unlock with Face ID", isOn: $lock.useFaceID) .onChange(of: lock.useFaceID) { newVal in haptic(.light) if newVal { biometricProbe() } } row(icon: "lock.clock", title: "Automatic lock") { Text((AutoLock(rawValue: autoLockRaw) ?? .m5).label).foregroundStyle(.secondary) } .onTapGesture { haptic(.soft); showAutoLockPicker = true } ToggleRow(icon: "key.fill", title: "Use system passcode when Face ID fails", isOn: $lock.useSystemPasscodeFallback) .onChange(of: lock.useSystemPasscodeFallback) { _ in haptic(.light) } } } private func lockSection() -> some View { SectionBox(title: "App lock") { row(icon: "lock.fill", title: "Lock now") { Text("Test").foregroundStyle(.secondary) } .onTapGesture { haptic(.medium); lock.lockNow() } row(icon: "number.circle", title: KeychainPIN.load() == nil ? "Set PIN (fallback)" : "Change PIN") .onTapGesture { haptic(.soft); showPINSheet = true } } } private func backupSection() -> some View { SectionBox(title: "PIN backup") { row(icon: "square.and.arrow.up", title: "Export PIN backup") .onTapGesture { haptic(.soft) if let s = KeychainPIN.exportBackupString() { shareBackup = s; showShare = true } else { toast = "No PIN to back up"; dismissToast() } } VStack(alignment: .leading, spacing: 10) { row(icon: "square.and.arrow.down", title: "Import PIN backup") .onTapGesture { haptic(.soft) toast = "Paste the backup code below"; dismissToast() } TextEditor(text: $importText) .frame(height: 80) .padding(8) .background(RoundedRectangle(cornerRadius: 12).fill(Color.white.opacity(0.06))) Button("Restore from code") { if KeychainPIN.importBackupString(importText.trimmingCharacters(in: .whitespacesAndNewlines)) { toast = "PIN restored"; dismissToast() } else { toast = "Invalid backup code"; dismissToast() } } .buttonStyle(.borderedProminent) } } } private func legalSection() -> some View { SectionBox(title: " ") { row(icon: "hand.raised.fill", title: "Privacy policy").onTapGesture { open("https://proton.me/legal/privacy") } row(icon: "doc.text.fill", title: "Terms of service").onTapGesture { open("https://proton.me/legal/terms") } } } private func helpCenterSection() -> some View { SectionBox(title: "Help center") { row(icon: "bubble.left.and.bubble.right.fill", title: "Feedback").onTapGesture { open("https://proton.me/support") } row(icon: "questionmark.circle.fill", title: "How to use Proton Pass") { Image(systemName: "arrow.up.right.square").foregroundStyle(.secondary) } .onTapGesture { open("https://proton.me/pass") } } } private func toolsSection() -> some View { SectionBox(title: "Developer tools") { row(icon: "wand.and.stars", title: "Arcane pulse") .onTapGesture { haptic(.heavy); toast = "β¨ Arcane pulse"; dismissToast() } } } private func appVersionFooter() -> some View { Text("Version 1.17.6 (1) β’ af875d2") .font(.footnote) .foregroundStyle(.secondary) .frame(maxWidth: .infinity) .padding(.top, 4) } // MARK: Small UI helpers private func row(icon: String, title: String, trailing: () -> some View = { EmptyView() }) -> some View { HStack(spacing: 14) { RoundedRectangle(cornerRadius: 10).fill(AppColors.tileIcon).frame(width: 36, height: 36) .overlay(Image(systemName: icon).imageScale(.medium)) Text(title).font(.body) Spacer(); trailing() Image(systemName: "chevron.right").font(.subheadline).foregroundStyle(.secondary) } .contentShape(Rectangle()) .padding(12) .background(RoundedRectangle(cornerRadius: 14).fill(AppColors.tile)) .overlay(RoundedRectangle(cornerRadius: 14).stroke(AppColors.tileStroke, lineWidth: 1)) } private func chip(icon: String, label: String, value: String) -> some View { VStack(alignment: .leading, spacing: 6) { HStack(spacing: 8) { Image(systemName: icon).imageScale(.medium) Text(value).font(.headline) } .padding(.vertical, 10).padding(.horizontal, 12) .background(RoundedRectangle(cornerRadius: 16).fill(AppColors.chip)) .overlay(RoundedRectangle(cornerRadius: 16).stroke(AppColors.chipStroke, lineWidth: 1)) Text(label.uppercased()).font(.caption2).foregroundStyle(.secondary).padding(.horizontal, 6).padding(.top, -4) } } private func glassCard() -> some View { RoundedRectangle(cornerRadius: 18) .fill(.ultraThinMaterial) .overlay(LinearGradient(colors: [.white.opacity(0.08), .clear], startPoint: .top, endPoint: .bottom)) .overlay(RoundedRectangle(cornerRadius: 18).stroke(AppColors.cardStroke, lineWidth: 1)) } private func open(_ url: String) { if let u = URL(string: url) { showSafari = u } } private func biometricProbe() { let ctx = LAContext() var err: NSError? let pol: LAPolicy = lock.useSystemPasscodeFallback ? .deviceOwnerAuthentication : .deviceOwnerAuthenticationWithBiometrics if ctx.canEvaluatePolicy(pol, error: &err) { ctx.evaluatePolicy(pol, localizedReason: "Enable Face ID") { ok, _ in DispatchQueue.main.async { toast = ok ? "Face ID enabled β
" : "Face ID not authorized" if !ok { lock.useFaceID = false } dismissToast() } } } else { toast = "Biometrics unavailable"; lock.useFaceID = false; dismissToast() } } private func haptic(_ style: UIImpactFeedbackGenerator.FeedbackStyle) { #if os(iOS) UIImpactFeedbackGenerator(style: style).impactOccurred() #endif } private func dismissToast() { DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { withAnimation { toast = nil } } } } β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
import SwiftUI import LocalAuthentication import SafariServices import Security import CryptoKit import UIKit // MARK: - App @main struct ProtonStyleProfileApp: App { var body: some Scene { WindowGroup { NavigationStack { ProfileView() } .preferredColorScheme(.dark) } } } // MARK: - AutoLock & LockManager enum AutoLock: String, CaseIterable, Identifiable { case immediately, m1, m5, m15, h1, h4 var id: String { rawValue } var label: String { switch self { case .immediately: "Immediately" case .m1: "After 1 minute" case .m5: "After 5 minutes" case .m15: "After 15 minutes" case .h1: "After 1 hour" case .h4: "After 4 hours" } } var seconds: Int { switch self { case .immediately: 0 case .m1: 60 case .m5: 300 case .m15: 900 case .h1: 3600 case .h4: 14400 } } static let presets: [AutoLock] = [.immediately, .m1, .m5, .m15, .h1, .h4] } final class LockManager: ObservableObject { @AppStorage("useFaceID") var useFaceID = true @AppStorage("useSystemPasscodeFallback") var useSystemPasscodeFallback = true @AppStorage("autoLock") var autoLockRaw: String = AutoLock.m5.rawValue @AppStorage("lastBackground") private var lastBackground: Double = 0 @AppStorage("hasPIN") private var hasPIN = false @Published var isLocked = false init() { // Avoid auto-lock on first launch by initializing the background timestamp to now lastBackground = Date().timeIntervalSince1970 // Reflect whether a PIN already exists in the keychain hasPIN = KeychainPIN.load() != nil } var autoLock: AutoLock { AutoLock(rawValue: autoLockRaw) ?? .m5 } func willEnterBackground() { lastBackground = Date().timeIntervalSince1970 if autoLock == .immediately { isLocked = true } } func didBecomeActive() { guard autoLock != .immediately else { return } let elapsed = Date().timeIntervalSince1970 - lastBackground if elapsed >= Double(autoLock.seconds) { isLocked = true } } func lockNow() { isLocked = true } func unlockWithBiometrics(completion: @escaping (Bool)->Void) { let ctx = LAContext() var err: NSError? // If fallback is allowed, use .deviceOwnerAuthentication so iOS can prompt Face ID then passcode let primaryPolicy: LAPolicy = useSystemPasscodeFallback ? .deviceOwnerAuthentication : .deviceOwnerAuthenticationWithBiometrics if ctx.canEvaluatePolicy(primaryPolicy, error: &err) { ctx.evaluatePolicy(primaryPolicy, localizedReason: "Unlock the app") { ok, _ in DispatchQueue.main.async { if ok { self.isLocked = false }; completion(ok) } } return } // If the chosen policy failed (e.g., biometrics-only and not available), try the broader one as a fallback let fallbackPolicy: LAPolicy = .deviceOwnerAuthentication if primaryPolicy != fallbackPolicy, ctx.canEvaluatePolicy(fallbackPolicy, error: &err) { ctx.evaluatePolicy(fallbackPolicy, localizedReason: "Unlock with device passcode") { ok, _ in DispatchQueue.main.async { if ok { self.isLocked = false }; completion(ok) } } return } // Nothing available completion(false) } func unlockWithPIN(_ pin: String) -> Bool { guard hasPIN, let rec = KeychainPIN.load() else { return false } if KeychainPIN.verify(input: pin, record: rec) { isLocked = false; return true } return false } func setupPIN(_ pin: String) { KeychainPIN.store(pin: pin); hasPIN = true } } // MARK: - Keychain PIN + Backup/Restore struct PINRecord: Codable { let salt: Data; let hash: Data } enum KeychainPIN { private static let account = "app.pin.record" private static let backupKeyAccount = "app.pin.backup.key" static func store(pin: String) { let salt = randomBytes(16) var msg = Data() msg.append(salt) msg.append(Data(pin.utf8)) let hash = Data(SHA256.hash(data: msg)) let rec = PINRecord(salt: salt, hash: hash) if let blob = try? JSONEncoder().encode(rec) { saveKeychain(data: blob, account: account) } } static func load() -> PINRecord? { guard let blob = loadKeychain(account: account) else { return nil } return try? JSONDecoder().decode(PINRecord.self, from: blob) } static func verify(input: String, record: PINRecord) -> Bool { var msg = Data() msg.append(record.salt) msg.append(Data(input.utf8)) let test = Data(SHA256.hash(data: msg)) return test == record.hash } static func exportBackupString() -> String? { guard let recData = loadKeychain(account: account) else { return nil } let key = loadOrCreateBackupKey() let sealed = try? ChaChaPoly.seal(recData, using: key) return sealed?.combined.base64EncodedString() } static func importBackupString(_ base64: String) -> Bool { guard let data = Data(base64Encoded: base64) else { return false } let key = loadOrCreateBackupKey() guard let box = try? ChaChaPoly.SealedBox(combined: data), let recData = try? ChaChaPoly.open(box, using: key) else { return false } saveKeychain(data: recData, account: account) return true } private static func loadOrCreateBackupKey() -> SymmetricKey { if let raw = loadKeychain(account: backupKeyAccount) { return SymmetricKey(data: raw) } let key = SymmetricKey(size: .bits256) saveKeychain(data: key.withUnsafeBytes { Data($0) }, account: backupKeyAccount) return key } private static func saveKeychain(data: Data, account: String) { let q: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecValueData as String: data, kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly ] SecItemDelete(q as CFDictionary) SecItemAdd(q as CFDictionary, nil) } private static func loadKeychain(account: String) -> Data? { let q: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne ] var out: AnyObject? let status = SecItemCopyMatching(q as CFDictionary, &out) return status == errSecSuccess ? out as? Data : nil } private static func randomBytes(_ n: Int) -> Data { var b = [UInt8](repeating: 0, count: n) _ = SecRandomCopyBytes(kSecRandomDefault, n, &b) return Data(b) } } // β
allow URL? to be used with `.sheet(item:)` extension URL: Identifiable { public var id: String { absoluteString } } // MARK: - PIN Setup View struct PINSetupView: View { let existing: Bool var onSet: (String)->Void @Environment(\.dismiss) private var dismiss @State private var pin1 = "" @State private var pin2 = "" var body: some View { NavigationStack { Form { Section(existing ? "Change PIN" : "Create PIN") { SecureField("Enter PIN", text: $pin1) .keyboardType(.numberPad) SecureField("Confirm PIN", text: $pin2) .keyboardType(.numberPad) Text("PIN must be 4β6 digits. Only numbers allowed.").font(.caption).foregroundStyle(.secondary) } } .navigationTitle(existing ? "Change PIN" : "Create PIN") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarLeading) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .topBarTrailing) { Button("Save") { let allowed = CharacterSet.decimalDigits let isNumeric1 = pin1.unicodeScalars.allSatisfy { allowed.contains($0) } let isNumeric2 = pin2.unicodeScalars.allSatisfy { allowed.contains($0) } guard isNumeric1, isNumeric2, (4...6).contains(pin1.count), pin1 == pin2 else { return } onSet(pin1) dismiss() }.bold() } } } } } // β¦ (KEEP the rest of your ProfileView, LockOverlay, AutoLockPicker, Paywall, etc. code the same) // β
TierCard fix: struct TierCard: View { let title: String; let price: String; let subtitle: String; let highlight: Bool var body: some View { HStack { VStack(alignment: .leading, spacing: 4) { Text(title).font(.headline) Text(subtitle).font(.caption).foregroundStyle(.secondary) } Spacer() Text(price).font(.title3).bold() } .padding() .background( RoundedRectangle(cornerRadius: 14) .fill({ let style: AnyShapeStyle = highlight ? AnyShapeStyle(LinearGradient(colors: [.white.opacity(0.10), .white.opacity(0.04)], startPoint: .top, endPoint: .bottom)) : AnyShapeStyle(Color.white.opacity(0.06)) return style }()) ) .overlay(RoundedRectangle(cornerRadius: 14).stroke(.white.opacity(0.10), lineWidth: 1)) } } // MARK: - UI Bits struct SectionBox<Content: View>: View { var title: String? @ViewBuilder var content: Content var body: some View { VStack(alignment: .leading, spacing: 10) { if let t = title, !(t.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) { Text(t.uppercased()).font(.caption).foregroundStyle(.secondary).padding(.horizontal, 6) } VStack(spacing: 10) { content } .padding(12) .background(RoundedRectangle(cornerRadius: 18).fill(AppColors.card)) .overlay(RoundedRectangle(cornerRadius: 18).stroke(AppColors.cardStroke, lineWidth: 1)) } } } struct ToggleRow: View { var icon: String var title: String @Binding var isOn: Bool var body: some View { HStack(spacing: 14) { RoundedRectangle(cornerRadius: 10).fill(AppColors.tileIcon).frame(width: 36, height: 36) .overlay(Image(systemName: icon).imageScale(.medium)) Text(title) Spacer() Toggle("", isOn: $isOn).labelsHidden() } .padding(12) .background(RoundedRectangle(cornerRadius: 14).fill(AppColors.tile)) .overlay(RoundedRectangle(cornerRadius: 14).stroke(AppColors.tileStroke, lineWidth: 1)) } } struct GradientPill: View { var icon: String; var title: String; var action: ()->Void var body: some View { Button(action: action) { HStack(spacing: 8) { Image(systemName: icon).imageScale(.medium) Text(title).font(.headline) } .padding(.vertical, 10).padding(.horizontal, 14) .background(LinearGradient(colors: [AppColors.purple.opacity(0.9), AppColors.indigo.opacity(0.9)], startPoint: .topLeading, endPoint: .bottomTrailing)) .overlay(RoundedRectangle(cornerRadius: 16).stroke(AppColors.purple.opacity(0.35), lineWidth: 1)) .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) .shadow(color: AppColors.purple.opacity(0.35), radius: 10, y: 4) }.buttonStyle(.plain) } } struct ToastView: View { var text: String var body: some View { Text(text).font(.subheadline) .padding(.horizontal, 14).padding(.vertical, 10) .background(.ultraThinMaterial, in: Capsule()) .overlay(Capsule().stroke(.white.opacity(0.25), lineWidth: 1)) } } // MARK: - Safari + Share struct SafariSheet: UIViewControllerRepresentable, Identifiable { let url: URL; var id: URL { url } func makeUIViewController(context: Context) -> SFSafariViewController { .init(url: url) } func updateUIViewController(_ vc: SFSafariViewController, context: Context) {} } struct ActivityShare: UIViewControllerRepresentable { var items: [Any]; var subject: String? func makeUIViewController(context: Context) -> UIActivityViewController { let vc = UIActivityViewController(activityItems: items, applicationActivities: nil) if let s = subject { vc.setValue(s, forKey: "subject") } return vc } func updateUIViewController(_ vc: UIActivityViewController, context: Context) {} } // MARK: - Theme enum AppColors { static let card = Color.white.opacity(0.06) static let cardStroke = Color.white.opacity(0.09) static let tile = Color.white.opacity(0.06) static let tileStroke = Color.white.opacity(0.08) static let tileIcon = Color.white.opacity(0.10) static let chip = Color.white.opacity(0.06) static let chipStroke = Color.white.opacity(0.10) static let divider = Color.white.opacity(0.12) static let avatar = Color.purple.opacity(0.85) static let purple = Color(hue: 0.74, saturation: 0.60, brightness: 0.75) static let indigo = Color(hue: 0.65, saturation: 0.65, brightness: 0.70) } // MARK: - Animated Background struct ArcaneBackground: View { var body: some View { TimelineView(.animation) { timeline in let tt = timeline.date.timeIntervalSinceReferenceDate ZStack { LinearGradient(colors: [.black, .black.opacity(0.92)], startPoint: .top, endPoint: .bottom) .ignoresSafeArea() // soft spotlights Circle().fill(AppColors.purple.opacity(0.18)) .blur(radius: 80).frame(width: 320, height: 320) .offset(x: CGFloat(sin(tt/3.2)) * 90, y: -140) Circle().fill(AppColors.indigo.opacity(0.16)) .blur(radius: 80).frame(width: 360, height: 360) .offset(x: -140, y: CGFloat(cos(tt/2.4)) * 70) // floating glyphs ForEach(0..<5) { i in let phase = sin(tt/3 + Double(i)*1.3) Image(systemName: ["shield.checkerboard","sparkles","seal","globe","lock.shield"][i%5]) .font(.system(size: 120 - CGFloat(i*10), weight: .bold)) .foregroundStyle(Color.white.opacity(0.05 + 0.02*Double(i))) .offset(x: CGFloat(phase*Double(60-i*6)), y: CGFloat(cos(tt/4 + Double(i))*Double(70-i*8))) .blur(radius: CGFloat(i)) .blendMode(.plusLighter) } } } } } // MARK: - AutoLock Picker struct AutoLockPicker: View { @Environment(\.dismiss) private var dismiss @Binding var selected: AutoLock @State private var working: AutoLock init(selected: Binding<AutoLock>) { _selected = selected _working = State(initialValue: selected.wrappedValue) } var body: some View { NavigationStack { List { Section { ForEach(AutoLock.presets) { opt in HStack { Text(opt.label) Spacer() if opt == working { Image(systemName: "checkmark.circle.fill") .foregroundStyle(.blue) } } .contentShape(Rectangle()) .onTapGesture { working = opt } } } footer: { Text("Automatic lock secures the app after inactivity.") } } .navigationTitle("Automatic lock") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarLeading) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .topBarTrailing) { Button("Done") { selected = working; dismiss() }.bold() } } } .presentationDetents(Set([.medium])) } } // MARK: - Lock Overlay with Keypad struct LockOverlay: View { var faceIDEnabled: Bool var passcodeFallback: Bool var hasPIN: Bool var unlockBiometric: () -> Void var unlockPIN: (String) -> Bool @State private var pin = "" @State private var shake = false @State private var errorText: String? = nil var body: some View { ZStack { ArcaneBackground().ignoresSafeArea() VStack(spacing: 18) { Image(systemName: "lock.fill").font(.system(size: 40, weight: .bold)) Text("Locked").font(.title3).bold() Text(faceIDEnabled ? (passcodeFallback ? "Face ID/Passcode or PIN" : "Face ID or PIN") : "Enter PIN to continue") .foregroundStyle(.secondary) PINDots(count: 6, filled: pin.count) .modifier(ShakeEffect(shakes: shake ? 2 : 0)) Keypad(onTap: { d in if d == "<" { if !pin.isEmpty { pin.removeLast() } } else if d == "β" { if pin.count >= 4 { let ok = unlockPIN(pin) if !ok { errorText = "Incorrect PIN" withAnimation { shake = true } DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { shake = false } } pin.removeAll() } } else { if pin.count < 6 { pin.append(d) } if pin.count == 6 { let ok = unlockPIN(pin) if !ok { errorText = "Incorrect PIN" withAnimation { shake = true } DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { shake = false } } pin.removeAll() } } }) if let e = errorText { Text(e).foregroundStyle(.red).font(.footnote) } if faceIDEnabled || passcodeFallback { Button { unlockBiometric() } label: { Label(passcodeFallback ? "Use Face ID / Passcode" : "Use Face ID", systemImage: "faceid") .padding(.horizontal, 16).padding(.vertical, 10) } .background(.ultraThinMaterial, in: Capsule()) .overlay(Capsule().stroke(.white.opacity(0.25), lineWidth: 1)) } } .padding(24) .background(RoundedRectangle(cornerRadius: 20).fill(.ultraThinMaterial)) .overlay(RoundedRectangle(cornerRadius: 20).stroke(.white.opacity(0.12), lineWidth: 1)) .shadow(radius: 18) .padding(.horizontal, 28) } } } struct PINDots: View { let count: Int let filled: Int var body: some View { HStack(spacing: 10) { ForEach(0..<count, id: \.self) { i in Circle() .fill(i < filled ? .white : .white.opacity(0.25)) .frame(width: 12, height: 12) } }.padding(.vertical, 6) } } struct Keypad: View { var onTap: (String)->Void private let rows = [ ["1","2","3"], ["4","5","6"], ["7","8","9"], ["<","0","β"] ] var body: some View { VStack(spacing: 10) { ForEach(rows, id: \.self) { row in HStack(spacing: 10) { ForEach(row, id: \.self) { key in Button { onTap(key) } label: { Text(key) .font(.title2).bold() .frame(width: 70, height: 52) .background(RoundedRectangle(cornerRadius: 12).fill(Color.white.opacity(0.08))) .overlay(RoundedRectangle(cornerRadius: 12).stroke(.white.opacity(0.12), lineWidth: 1)) } .buttonStyle(.plain) } } } } } } struct ShakeEffect: GeometryEffect { var shakes: CGFloat = 0 var animatableData: CGFloat { get { shakes } set { shakes = newValue } } func effectValue(size: CGSize) -> ProjectionTransform { let translation = 8 * sin(shakes * .pi * 3) return ProjectionTransform(CGAffineTransform(translationX: translation, y: 0)) } } // MARK: - Paywall struct PaywallView: View { var close: ()->Void var body: some View { VStack(spacing: 20) { Text("Upgrade to Pro") .font(.title).bold() Text("Unlock unlimited aliases, secure link sharing, and priority support.") .multilineTextAlignment(.center) .foregroundStyle(.secondary) .padding(.horizontal) VStack(spacing: 12) { TierCard(title: "Annual", price: "$39.99", subtitle: "Best value", highlight: true) TierCard(title: "Monthly", price: "$4.99", subtitle: "Flexible", highlight: false) } .padding(.horizontal) VStack(alignment: .leading, spacing: 8) { Label("Unlimited aliases", systemImage: "infinity") Label("Secure link vault", systemImage: "link.circle") Label("Face ID lock", systemImage: "faceid") Label("Priority support", systemImage: "bolt.fill") } .frame(maxWidth: .infinity, alignment: .leading) .padding() .background(RoundedRectangle(cornerRadius: 16).fill(Color.white.opacity(0.06))) .overlay(RoundedRectangle(cornerRadius: 16).stroke(.white.opacity(0.08), lineWidth: 1)) .padding(.horizontal) HStack(spacing: 12) { Button("Restore") { close() } .buttonStyle(.bordered) Button("Continue") { close() } .buttonStyle(.borderedProminent) } Button("Not now") { close() }.padding(.top, 6) } .padding(20) .background(ArcaneBackground().ignoresSafeArea()) .presentationDetents(Set([.medium])) } } // MARK: - Profile Screen (main UI) struct ProfileView: View { @StateObject private var lock = LockManager() @AppStorage("autoLock") private var autoLockRaw: String = AutoLock.m5.rawValue @AppStorage("useFaceID") private var useFaceID = true @AppStorage("useSystemPasscodeFallback") private var useSystemPasscodeFallback = true @Environment(\.scenePhase) private var scenePhase @State private var showAutoLockPicker = false @State private var showPINSheet = false @State private var showSafari: URL? = nil @State private var showPaywall = false @State private var toast: String? = nil @State private var isPulsing = false @State private var shareBackup: String? = nil @State private var showShare = false @State private var importText = "" var body: some View { ZStack { ArcaneBackground().ignoresSafeArea() ScrollView { VStack(spacing: 22) { header() accountCard() itemChips() securitySection() lockSection() backupSection() legalSection() helpCenterSection() toolsSection() appVersionFooter() } .padding(.horizontal, 16) .padding(.vertical, 24) } .navigationTitle("Profile") .toolbar { ToolbarItem(placement: .topBarTrailing) { GradientPill(icon: "diamond.fill", title: "Upgrade") { showPaywall = true haptic(.light) } } } .sheet(isPresented: $showPaywall) { PaywallView(close: { showPaywall = false }) } .sheet(item: $showSafari) { url in SafariSheet(url: url) } .sheet(isPresented: $showAutoLockPicker) { AutoLockPicker(selected: Binding( get: { AutoLock(rawValue: autoLockRaw) ?? .m5 }, set: { autoLockRaw = $0.rawValue; lock.autoLockRaw = $0.rawValue } )) .presentationDetents(Set([.medium])) } .sheet(isPresented: $showPINSheet) { PINSetupView(existing: KeychainPIN.load() != nil) { newPIN in lock.setupPIN(newPIN) } .presentationDetents(Set([.medium])) } .sheet(isPresented: $showShare) { if let str = shareBackup { ActivityShare(items: [str], subject: "App PIN Backup") } } if let t = toast { ToastView(text: t) .transition(.move(edge: .top).combined(with: .opacity)) .zIndex(3) .padding(.top, 10) } if lock.isLocked { LockOverlay( faceIDEnabled: useFaceID, passcodeFallback: useSystemPasscodeFallback, hasPIN: KeychainPIN.load() != nil, unlockBiometric: { lock.unlockWithBiometrics { _ in } }, unlockPIN: { pin in lock.unlockWithPIN(pin) } ) .zIndex(4) .transition(.opacity) } } .onChange(of: scenePhase) { phase in switch phase { case .background: lock.useFaceID = useFaceID lock.useSystemPasscodeFallback = useSystemPasscodeFallback lock.autoLockRaw = autoLockRaw lock.willEnterBackground() case .active: lock.didBecomeActive() default: break } } } // MARK: Sections private func header() -> some View { Rectangle().fill(.clear).frame(height: 1) } private func accountCard() -> some View { VStack(alignment: .leading, spacing: 14) { HStack(spacing: 14) { ZStack { RoundedRectangle(cornerRadius: 10) .fill(AppColors.avatar) .frame(width: 44, height: 44) .overlay(Text("P").font(.title2).fontWeight(.semibold).foregroundStyle(.white)) Circle() .strokeBorder(.white.opacity(0.15), lineWidth: 1) .frame(width: 52, height: 52) .offset(y: 1) .opacity(isPulsing ? 0.25 : 0.1) .animation(.easeInOut(duration: 2.4).repeatForever(), value: isPulsing) } VStack(alignment: .leading, spacing: 4) { Text("Gary Makinson").font(.headline) Text("Mail Plus").font(.subheadline).foregroundStyle(.secondary) Text("pixeldreamerapp@pm.me").font(.footnote).foregroundStyle(.secondary) } Spacer() Image(systemName: "chevron.down").foregroundStyle(.secondary) } Divider().background(AppColors.divider) NavigationLink { Text("Device sign-in options here.") .foregroundStyle(.secondary) .padding() .navigationTitle("Sign in to another device") .navigationBarTitleDisplayMode(.inline) } label: { row(icon: "qrcode.viewfinder", title: "Sign in to another device") } } .padding(16) .background(glassCard()) .onAppear { isPulsing = true } } private func itemChips() -> some View { HStack(spacing: 12) { chip(icon: "person.circle.fill", label: "Identities", value: "2") chip(icon: "glasses", label: "Aliases", value: "0/10") chip(icon: "creditcard.fill", label: "Cards", value: "0") chip(icon: "doc.on.doc.fill", label: "Notes", value: "0") } } private func securitySection() -> some View { SectionBox(title: "Security") { ToggleRow(icon: "faceid", title: "Unlock with Face ID", isOn: $lock.useFaceID) .onChange(of: lock.useFaceID) { newVal in haptic(.light) if newVal { biometricProbe() } } row(icon: "lock.clock", title: "Automatic lock") { Text((AutoLock(rawValue: autoLockRaw) ?? .m5).label).foregroundStyle(.secondary) } .onTapGesture { haptic(.soft); showAutoLockPicker = true } ToggleRow(icon: "key.fill", title: "Use system passcode when Face ID fails", isOn: $lock.useSystemPasscodeFallback) .onChange(of: lock.useSystemPasscodeFallback) { _ in haptic(.light) } } } private func lockSection() -> some View { SectionBox(title: "App lock") { row(icon: "lock.fill", title: "Lock now") { Text("Test").foregroundStyle(.secondary) } .onTapGesture { haptic(.medium); lock.lockNow() } row(icon: "number.circle", title: KeychainPIN.load() == nil ? "Set PIN (fallback)" : "Change PIN") .onTapGesture { haptic(.soft); showPINSheet = true } } } private func backupSection() -> some View { SectionBox(title: "PIN backup") { row(icon: "square.and.arrow.up", title: "Export PIN backup") .onTapGesture { haptic(.soft) if let s = KeychainPIN.exportBackupString() { shareBackup = s; showShare = true } else { toast = "No PIN to back up"; dismissToast() } } VStack(alignment: .leading, spacing: 10) { row(icon: "square.and.arrow.down", title: "Import PIN backup") .onTapGesture { haptic(.soft) toast = "Paste the backup code below"; dismissToast() } TextEditor(text: $importText) .frame(height: 80) .padding(8) .background(RoundedRectangle(cornerRadius: 12).fill(Color.white.opacity(0.06))) Button("Restore from code") { if KeychainPIN.importBackupString(importText.trimmingCharacters(in: .whitespacesAndNewlines)) { toast = "PIN restored"; dismissToast() } else { toast = "Invalid backup code"; dismissToast() } } .buttonStyle(.borderedProminent) } } } private func legalSection() -> some View { SectionBox(title: " ") { row(icon: "hand.raised.fill", title: "Privacy policy").onTapGesture { open("https://proton.me/legal/privacy") } row(icon: "doc.text.fill", title: "Terms of service").onTapGesture { open("https://proton.me/legal/terms") } } } private func helpCenterSection() -> some View { SectionBox(title: "Help center") { row(icon: "bubble.left.and.bubble.right.fill", title: "Feedback").onTapGesture { open("https://proton.me/support") } row(icon: "questionmark.circle.fill", title: "How to use Proton Pass") { Image(systemName: "arrow.up.right.square").foregroundStyle(.secondary) } .onTapGesture { open("https://proton.me/pass") } } } private func toolsSection() -> some View { SectionBox(title: "Developer tools") { row(icon: "wand.and.stars", title: "Arcane pulse") .onTapGesture { haptic(.heavy); toast = "β¨ Arcane pulse"; dismissToast() } } } private func appVersionFooter() -> some View { Text("Version 1.17.6 (1) β’ af875d2") .font(.footnote) .foregroundStyle(.secondary) .frame(maxWidth: .infinity) .padding(.top, 4) } // MARK: Small UI helpers private func row(icon: String, title: String, trailing: () -> some View = { EmptyView() }) -> some View { HStack(spacing: 14) { RoundedRectangle(cornerRadius: 10).fill(AppColors.tileIcon).frame(width: 36, height: 36) .overlay(Image(systemName: icon).imageScale(.medium)) Text(title).font(.body) Spacer(); trailing() Image(systemName: "chevron.right").font(.subheadline).foregroundStyle(.secondary) } .contentShape(Rectangle()) .padding(12) .background(RoundedRectangle(cornerRadius: 14).fill(AppColors.tile)) .overlay(RoundedRectangle(cornerRadius: 14).stroke(AppColors.tileStroke, lineWidth: 1)) } private func chip(icon: String, label: String, value: String) -> some View { VStack(alignment: .leading, spacing: 6) { HStack(spacing: 8) { Image(systemName: icon).imageScale(.medium) Text(value).font(.headline) } .padding(.vertical, 10).padding(.horizontal, 12) .background(RoundedRectangle(cornerRadius: 16).fill(AppColors.chip)) .overlay(RoundedRectangle(cornerRadius: 16).stroke(AppColors.chipStroke, lineWidth: 1)) Text(label.uppercased()).font(.caption2).foregroundStyle(.secondary).padding(.horizontal, 6).padding(.top, -4) } } private func glassCard() -> some View { RoundedRectangle(cornerRadius: 18) .fill(.ultraThinMaterial) .overlay(LinearGradient(colors: [.white.opacity(0.08), .clear], startPoint: .top, endPoint: .bottom)) .overlay(RoundedRectangle(cornerRadius: 18).stroke(AppColors.cardStroke, lineWidth: 1)) } private func open(_ url: String) { if let u = URL(string: url) { showSafari = u } } private func biometricProbe() { let ctx = LAContext() var err: NSError? let pol: LAPolicy = lock.useSystemPasscodeFallback ? .deviceOwnerAuthentication : .deviceOwnerAuthenticationWithBiometrics if ctx.canEvaluatePolicy(pol, error: &err) { ctx.evaluatePolicy(pol, localizedReason: "Enable Face ID") { ok, _ in DispatchQueue.main.async { toast = ok ? "Face ID enabled β
" : "Face ID not authorized" if !ok { lock.useFaceID = false } dismissToast() } } } else { toast = "Biometrics unavailable"; lock.useFaceID = false; dismissToast() } } private func haptic(_ style: UIImpactFeedbackGenerator.FeedbackStyle) { #if os(iOS) UIImpactFeedbackGenerator(style: style).impactOccurred() #endif } private func dismissToast() { DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { withAnimation { toast = nil } } } } β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
Angel holding flower super Deerfield roses drawing β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
Angel holding flower super Deerfield roses drawing β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
angel fcrying β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
angel fcrying β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
angel fcrying β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
angel fcrying β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
angel fcrying β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style suepr detail fine pen β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
angel fcrying β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style suepr detail fine pen β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
Angle holding. Shen wit devil β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
Angle holding. Shen wit devil β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
A girl holding a flower with a demon holding a Chinese doll out of her her face super detailed drawing β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
A girl holding a flower with a demon holding a Chinese doll out of her her face super detailed drawing β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
angel ram β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
angel ram β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
A girl holding a flower with a demon holding a Chinese doll out of her her face super detailed drawing β highly detailed graphite drawing, fine shading, β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
A girl holding a flower with a demon holding a Chinese doll out of her her face super detailed drawing β highly detailed graphite drawing, fine shading, β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
Ahfoe β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
Ahfoe β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
Ahfoe β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
Ahfoe β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
: Drawn of a man you suffering from like mad depression, holding a rose in his hand while heβs like flying out of his fucking face and then thereβs like blood dripping down like part of his neck super detailed like Japanese fine detail pointillism 0.53 mm beyond realistic, hyper realism, fucking high tech artist extraordinary β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
: Drawn of a man you suffering from like mad depression, holding a rose in his hand while heβs like flying out of his fucking face and then thereβs like blood dripping down like part of his neck super detailed like Japanese fine detail pointillism 0.53 mm beyond realistic, hyper realism, fucking high tech artist extraordinary β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
: Drawn of a man you suffering from like mad depression, holding a rose in his hand while heβs like flying out of his fucking face and then thereβs like blood dripping down like part of his neck super detailed like Japanese fine detail pointillism 0.53 mm beyond realistic, hyper realism, fucking high tech artist extraordinary β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style front view, not too bloody, but like super realistic, with him, like looking at the rose and all unlike distress of β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
: Drawn of a man you suffering from like mad depression, holding a rose in his hand while heβs like flying out of his fucking face and then thereβs like blood dripping down like part of his neck super detailed like Japanese fine detail pointillism 0.53 mm beyond realistic, hyper realism, fucking high tech artist extraordinary β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style front view, not too bloody, but like super realistic, with him, like looking at the rose and all unlike distress of β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
a minimalist modern architectural exterior at sunset, ultra-detailed, hyperreal textures, soft bokeh, natural skin tones. soft pastel accents. High resolution, cinematic, subtle film grain. β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style
A girl dancing in the star dreaming of love β highly detailed graphite drawing, fine shading, soft texture, realistic pencil sketch style