- Adaptive iPhone/iPad layout with NavigationSplitView sidebar - Auto-detect SSL-pinned domains, fall back to passthrough - Certificate install via local HTTP server (Safari profile flow) - App Group-backed CA, per-domain leaf cert LRU cache - DB-backed config repository, Darwin notification throttling - Rules engine, breakpoint rules, pinned domain tracking - os.Logger instrumentation across tunnel/proxy/mitm/capture/cert/rules/db/ipc/ui - Fix dyld framework embed, race conditions, thread safety
133 lines
3.9 KiB
Swift
133 lines
3.9 KiB
Swift
import SwiftUI
|
|
|
|
struct ContentView: View {
|
|
@Environment(AppState.self) private var appState
|
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
|
|
|
enum Tab: String, Hashable, CaseIterable {
|
|
case home = "Home"
|
|
case pin = "Pin"
|
|
case compose = "Compose"
|
|
case more = "More"
|
|
|
|
var icon: String {
|
|
switch self {
|
|
case .home: return "house.fill"
|
|
case .pin: return "pin.fill"
|
|
case .compose: return "square.and.pencil"
|
|
case .more: return "ellipsis.circle.fill"
|
|
}
|
|
}
|
|
}
|
|
|
|
@State private var selectedTab: Tab = .home
|
|
|
|
var body: some View {
|
|
Group {
|
|
if horizontalSizeClass == .regular {
|
|
iPadLayout
|
|
} else {
|
|
iPhoneLayout
|
|
}
|
|
}
|
|
.overlay {
|
|
if appState.isLocked {
|
|
lockScreen
|
|
}
|
|
}
|
|
}
|
|
|
|
private var iPhoneLayout: some View {
|
|
TabView(selection: $selectedTab) {
|
|
NavigationStack { HomeView() }
|
|
.tabItem { Label("Home", systemImage: "house.fill") }
|
|
.tag(Tab.home)
|
|
|
|
NavigationStack { PinView() }
|
|
.tabItem { Label("Pin", systemImage: "pin.fill") }
|
|
.tag(Tab.pin)
|
|
|
|
NavigationStack { ComposeListView() }
|
|
.tabItem { Label("Compose", systemImage: "square.and.pencil") }
|
|
.tag(Tab.compose)
|
|
|
|
NavigationStack { MoreView() }
|
|
.tabItem { Label("More", systemImage: "ellipsis.circle.fill") }
|
|
.tag(Tab.more)
|
|
}
|
|
}
|
|
|
|
private var iPadLayout: some View {
|
|
NavigationSplitView {
|
|
iPadSidebar
|
|
} detail: {
|
|
iPadDetail
|
|
}
|
|
}
|
|
|
|
private var iPadSidebar: some View {
|
|
VStack(spacing: 4) {
|
|
ForEach(Tab.allCases, id: \.self) { tab in
|
|
iPadSidebarButton(tab: tab)
|
|
}
|
|
Spacer()
|
|
}
|
|
.padding(.top, 8)
|
|
.navigationTitle("Proxy")
|
|
}
|
|
|
|
private func iPadSidebarButton(tab: Tab) -> some View {
|
|
Button {
|
|
selectedTab = tab
|
|
} label: {
|
|
Label(tab.rawValue, systemImage: tab.icon)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding(.horizontal, 20)
|
|
.padding(.vertical, 14)
|
|
.background(
|
|
selectedTab == tab ? Color.accentColor.opacity(0.12) : Color.clear,
|
|
in: RoundedRectangle(cornerRadius: 10)
|
|
)
|
|
.foregroundStyle(selectedTab == tab ? Color.accentColor : Color.primary)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.padding(.horizontal, 12)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var iPadDetail: some View {
|
|
NavigationStack {
|
|
switch selectedTab {
|
|
case .home: HomeView()
|
|
case .pin: PinView()
|
|
case .compose: ComposeListView()
|
|
case .more: MoreView()
|
|
}
|
|
}
|
|
}
|
|
|
|
private var lockScreen: some View {
|
|
ZStack {
|
|
Color(.systemBackground).ignoresSafeArea()
|
|
VStack(spacing: 24) {
|
|
Image(systemName: "lock.fill")
|
|
.font(.system(size: 48))
|
|
.foregroundStyle(.secondary)
|
|
Text("Unlock Proxy")
|
|
.font(.title2.weight(.semibold))
|
|
Button {
|
|
appState.authenticate()
|
|
} label: {
|
|
Text("Unlock")
|
|
.font(.headline)
|
|
.frame(maxWidth: 300)
|
|
.padding()
|
|
.background(.blue, in: RoundedRectangle(cornerRadius: 12))
|
|
.foregroundStyle(.white)
|
|
}
|
|
}
|
|
}
|
|
.onAppear { appState.authenticate() }
|
|
}
|
|
}
|