Add iPad support, auto-pinning, and comprehensive logging
- 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
This commit is contained in:
@@ -2,46 +2,131 @@ import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@Environment(AppState.self) private var appState
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
|
||||
enum Tab: Hashable {
|
||||
case home, pin, compose, more
|
||||
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 {
|
||||
TabView(selection: $selectedTab) {
|
||||
NavigationStack {
|
||||
HomeView()
|
||||
Group {
|
||||
if horizontalSizeClass == .regular {
|
||||
iPadLayout
|
||||
} else {
|
||||
iPhoneLayout
|
||||
}
|
||||
.tabItem {
|
||||
Label("Home", systemImage: "house.fill")
|
||||
}
|
||||
.overlay {
|
||||
if appState.isLocked {
|
||||
lockScreen
|
||||
}
|
||||
.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 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() }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user