import SwiftUI struct ProfilesScreen: View { @EnvironmentObject private var app: AppModel @State private var profiles: [ViewerProfile] = [] @State private var error: String? @State private var pinTarget: ViewerProfile? @State private var pinDigits = "" @State private var pinError: String? @State private var loading = true @State private var selecting = false private let cols = [GridItem(.adaptive(minimum: 110), spacing: 20)] var body: some View { VStack(spacing: 28) { Text("Wie kijkt er?") .font(McFonts.title) .foregroundStyle(McColors.text) .padding(.top, 48) if loading { ProgressView().tint(.white) } else if let error, profiles.isEmpty { Text(error).foregroundStyle(McColors.danger) Button("Opnieuw") { Task { await load() } } .buttonStyle(McPrimaryButtonStyle()) } else { if let error { Text(error) .font(McFonts.caption) .foregroundStyle(McColors.danger) .padding(.horizontal) } LazyVGrid(columns: cols, spacing: 24) { ForEach(profiles) { profile in Button { Task { await select(profile) } } label: { VStack(spacing: 10) { Text(ProfileAvatars.emoji(for: profile.avatarId)) .font(.system(size: 48)) .frame(width: 96, height: 96) .background( LinearGradient( colors: [McColors.surfaceSoft, McColors.surface], startPoint: .topLeading, endPoint: .bottomTrailing ), in: Circle() ) .overlay(Circle().stroke(McColors.accent.opacity(0.25), lineWidth: 1)) Text(profile.name) .font(McFonts.headline) .foregroundStyle(McColors.text) .lineLimit(1) if profile.hasPin == true { Image(systemName: "lock.fill") .font(.caption2) .foregroundStyle(McColors.textDim) } } } .buttonStyle(.plain) .disabled(selecting) } } .padding(.horizontal, 24) } Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity) .background( LinearGradient(colors: [Color(red: 0.08, green: 0.03, blue: 0.04), McColors.bg], startPoint: .top, endPoint: .bottom) .ignoresSafeArea() ) .task { await load() } .sheet(item: $pinTarget) { profile in PinPadSheet( profileName: profile.name, avatarId: profile.avatarId, digits: $pinDigits, error: pinError, busy: selecting, onCancel: { pinTarget = nil pinDigits = "" pinError = nil }, onSubmit: { code in Task { await submitPin(for: profile, code: code) } } ) .presentationDetents([.height(420)]) .presentationDragIndicator(.visible) } } private func load() async { loading = true error = nil do { let payload = try await app.api.listProfiles() profiles = payload.profiles loading = false } catch { self.error = error.localizedDescription loading = false } } private func select(_ profile: ViewerProfile, pin: String? = nil) async { if profile.hasPin == true, pin == nil { pinDigits = "" pinError = nil pinTarget = profile return } selecting = true error = nil do { let selected = try await app.api.selectProfile(profileId: profile.id, pin: pin) pinTarget = nil pinDigits = "" pinError = nil selecting = false app.didSelectProfile(selected) } catch { selecting = false if pinTarget != nil { pinError = error.localizedDescription pinDigits = "" } else { self.error = error.localizedDescription } } } private func submitPin(for profile: ViewerProfile, code: String) async { let digits = code.filter(\.isNumber) guard digits.count == 4 else { pinError = "PIN moet 4 cijfers zijn" return } await select(profile, pin: String(digits)) } } struct PinPadSheet: View { let profileName: String let avatarId: String? @Binding var digits: String let error: String? let busy: Bool let onCancel: () -> Void let onSubmit: (String) -> Void private let keys = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "⌫"] var body: some View { VStack(spacing: 18) { HStack { Button("Annuleer", action: onCancel) .foregroundStyle(McColors.textDim) Spacer() } .padding(.horizontal) Text(ProfileAvatars.emoji(for: avatarId)) .font(.system(size: 40)) Text("PIN voor \(profileName)") .font(McFonts.headline) .foregroundStyle(McColors.text) HStack(spacing: 12) { ForEach(0..<4, id: \.self) { i in Circle() .fill(i < digits.count ? McColors.accent : McColors.surfaceSoft) .frame(width: 14, height: 14) } } .padding(.vertical, 4) if let error { Text(error) .font(McFonts.caption) .foregroundStyle(McColors.danger) } LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 12), count: 3), spacing: 12) { ForEach(keys, id: \.self) { key in if key.isEmpty { Color.clear.frame(height: 52) } else { Button { tap(key) } label: { Text(key) .font(.system(size: 24, weight: .semibold, design: .rounded)) .foregroundStyle(McColors.text) .frame(maxWidth: .infinity) .frame(height: 52) .background(McColors.surface, in: RoundedRectangle(cornerRadius: 14, style: .continuous)) } .buttonStyle(.plain) .disabled(busy) } } } .padding(.horizontal, 28) if busy { ProgressView().tint(.white) } } .padding(.top, 16) .padding(.bottom, 24) .background(McColors.bgElevated.ignoresSafeArea()) } private func tap(_ key: String) { if key == "⌫" { if !digits.isEmpty { digits.removeLast() } return } guard digits.count < 4 else { return } digits.append(key) if digits.count == 4 { onSubmit(digits) } } }