import SwiftUI import ProxyCore import GRDB struct HomeView: View { @Environment(AppState.self) private var appState @State private var domains: [DomainGroup] = [] @State private var searchText = "" @State private var showClearConfirmation = false @State private var observation: AnyDatabaseCancellable? private let trafficRepo = TrafficRepository() var filteredDomains: [DomainGroup] { if searchText.isEmpty { return domains } return domains.filter { $0.domain.localizedCaseInsensitiveContains(searchText) } } var body: some View { Group { if !appState.isVPNConnected && domains.isEmpty { ContentUnavailableView { Label("VPN Not Connected", systemImage: "bolt.slash") } description: { Text("Enable the VPN to start capturing network traffic.") } actions: { Button("Enable VPN") { Task { await appState.toggleVPN() } } .buttonStyle(.borderedProminent) } } else if domains.isEmpty { ContentUnavailableView { Label("No Traffic", systemImage: "network.slash") } description: { Text("Waiting for network requests. Open Safari or another app to generate traffic.") } } else { List { ForEach(filteredDomains) { group in NavigationLink(value: group) { HStack { Image(systemName: "globe") .foregroundStyle(.secondary) Text(group.domain) .lineLimit(1) Spacer() Text("\(group.requestCount)") .foregroundStyle(.secondary) Image(systemName: "chevron.right") .font(.caption) .foregroundStyle(.tertiary) } } } } } } .searchable(text: $searchText, prompt: "Filter Domains") .navigationTitle("Home") .navigationDestination(for: DomainGroup.self) { group in DomainDetailView(domain: group.domain) } .toolbar { ToolbarItem(placement: .topBarLeading) { Button { showClearConfirmation = true } label: { Image(systemName: "trash") } .disabled(domains.isEmpty) } ToolbarItem(placement: .topBarTrailing) { Button { Task { await appState.toggleVPN() } } label: { Image(systemName: appState.isVPNConnected ? "bolt.fill" : "bolt.slash") .foregroundStyle(appState.isVPNConnected ? .yellow : .secondary) } } } .confirmationDialog("Clear All Domains", isPresented: $showClearConfirmation) { Button("Clear All", role: .destructive) { try? trafficRepo.deleteAll() } } message: { Text("This will permanently delete all captured traffic.") } .task { startObservation() } } private func startObservation() { observation = trafficRepo.observeDomainGroups() .start(in: DatabaseManager.shared.dbPool) { error in print("[HomeView] Observation error: \(error)") } onChange: { newDomains in withAnimation { domains = newDomains } } } }