import SwiftUI /// The main presence screen. /// /// This is the emotional core of the app. A large, tactile "I am here" /// toggle that makes being present feel like an intentional act — not a /// passive background process. Below it, the live feed of silhouettes. struct PresenceView: View { @EnvironmentObject private var vm: PresenceViewModel @EnvironmentObject private var revealFlow: RevealFlow @State private var showReveal = false var body: some View { NavigationStack { ScrollView { VStack(spacing: 24) { presenceToggle radiusPicker encounterFeed } .padding() } .navigationTitle("Proximity") .toolbar { #if DEBUG ToolbarItem(placement: .topBarLeading) { NavigationLink { DebugMenu() } label: { Image(systemName: "hammer") } } #endif } } .onChange(of: revealFlow.phase) { _, phase in if case .mutual = phase { showReveal = true } } .fullScreenCover(isPresented: $showReveal) { RevealView() .environmentObject(revealFlow) } } // MARK: - The presence toggle private var presenceToggle: some View { Button(action: vm.togglePresence) { ZStack { Circle() .fill(vm.isPresent ? Color.green : Color.gray.opacity(0.2)) .frame(width: 200, height: 200) .overlay( Circle() .stroke(vm.isPresent ? Color.green : Color.gray, lineWidth: 4) ) VStack(spacing: 8) { Image(systemName: vm.isPresent ? "dot.radiowaves.left.and.right" : "person.crop.circle.dashed") .font(.system(size: 56)) Text(vm.isPresent ? "I'm Here" : "Tap to be Present") .font(.headline) } .foregroundColor(vm.isPresent ? .white : .secondary) } .scaleEffect(vm.isPresent ? 1.0 : 0.98) .animation(.spring(response: 0.4, dampingFraction: 0.6), value: vm.isPresent) } .buttonStyle(.plain) .accessibilityLabel(vm.isPresent ? "You are present. Tap to go offline." : "Go present") } // MARK: - Radius picker private var radiusPicker: some View { VStack(alignment: .leading, spacing: 8) { Text("How far are you open to?") .font(.subheadline) .foregroundColor(.secondary) Picker("Radius", selection: $vm.radiusTier) { ForEach(DistanceTier.allCases) { tier in Text(tier.title).tag(tier) } } .pickerStyle(.segmented) Text(vm.radiusTier.subtitle) .font(.caption) .foregroundColor(.secondary) } } // MARK: - Encounter feed private var encounterFeed: some View { VStack(alignment: .leading, spacing: 12) { HStack { Text("People you've passed") .font(.headline) Spacer() if let last = vm.lastScan { Text("last scan \(last, style: .time)") .font(.caption) .foregroundColor(.secondary) } } if vm.encounters.isEmpty { emptyState } else { ForEach(vm.encounters) { encounter in SilhouetteRow(encounter: encounter) { // Tap a silhouette → consider it → wave. revealFlow.consider(encounter) Task { await revealFlow.wave() } } } } } } private var emptyState: some View { VStack(spacing: 12) { Image(systemName: "eye.slash") .font(.system(size: 40)) .foregroundColor(.secondary) Text(vm.isPresent ? "Looking for people near you…" : "Go present to start seeing people.") .multilineTextAlignment(.center) .foregroundColor(.secondary) } .frame(maxWidth: .infinity) .padding(.vertical, 40) } }