Initial project setup - Phases 1-3 complete

This commit is contained in:
Trey t
2026-04-06 11:28:40 -05:00
commit c77e506db5
293 changed files with 14233 additions and 0 deletions

104
UI/Home/HomeView.swift Normal file
View File

@@ -0,0 +1,104 @@
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
}
}
}
}