Add source code
This commit is contained in:
parent
69c08de01c
commit
0416d157d7
14 changed files with 1672 additions and 0 deletions
181
TetheringHider/Views/MenuBarView.swift
Normal file
181
TetheringHider/Views/MenuBarView.swift
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
import SwiftUI
|
||||
|
||||
struct MenuBarView: View {
|
||||
@Environment(TTLManager.self) private var ttlManager
|
||||
@Environment(\.openWindow) private var openWindow
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
@State private var isProcessing = false
|
||||
@State private var showError = false
|
||||
@State private var errorMessage = ""
|
||||
|
||||
private let authService = AuthenticationService()
|
||||
private let commandService = SystemCommandService()
|
||||
|
||||
private var accentColor: Color {
|
||||
if colorScheme == .light {
|
||||
return ttlManager.isProtected
|
||||
? Color(hue: 0.38, saturation: 0.85, brightness: 0.40)
|
||||
: Color(hue: 0.05, saturation: 0.90, brightness: 0.45)
|
||||
} else {
|
||||
return ttlManager.isProtected
|
||||
? Color(hue: 0.38, saturation: 0.70, brightness: 0.85)
|
||||
: Color(hue: 0.06, saturation: 0.70, brightness: 0.90)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 14) {
|
||||
// Header
|
||||
HStack(spacing: 10) {
|
||||
Image(systemName: ttlManager.isProtected ? "antenna.radiowaves.left.and.right" : "antenna.radiowaves.left.and.right.slash")
|
||||
.font(.system(size: 20, weight: .medium))
|
||||
.foregroundStyle(accentColor)
|
||||
.symbolEffect(.bounce, value: ttlManager.isProtected)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Tethering Hider")
|
||||
.font(.system(size: 13, weight: .bold, design: .rounded))
|
||||
|
||||
Text(ttlManager.isProtected ? "Protected (TTL 65)" : "Exposed (TTL 64)")
|
||||
.font(.system(size: 11, weight: .medium))
|
||||
.foregroundStyle(accentColor)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
// Toggle Switch
|
||||
Toggle("", isOn: Binding(
|
||||
get: { ttlManager.isProtected },
|
||||
set: { newValue in
|
||||
if newValue {
|
||||
activateProtection()
|
||||
} else {
|
||||
deactivateProtection()
|
||||
}
|
||||
}
|
||||
))
|
||||
.toggleStyle(.switch)
|
||||
.disabled(isProcessing)
|
||||
.tint(accentColor)
|
||||
}
|
||||
.padding(.horizontal, 14)
|
||||
.padding(.vertical, 10)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(.ultraThinMaterial)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.stroke(accentColor.opacity(0.2), lineWidth: 0.5)
|
||||
)
|
||||
)
|
||||
.modifier(GlassCardModifier(tintColor: accentColor))
|
||||
|
||||
// Status Badges
|
||||
HStack(spacing: 6) {
|
||||
badgeItem(title: "IPv4", val: "\(ttlManager.currentTTL)", active: ttlManager.currentTTL == 65)
|
||||
badgeItem(title: "IPv6", val: "\(ttlManager.currentIPv6HopLimit)", active: ttlManager.currentIPv6HopLimit == 65)
|
||||
badgeItem(title: "DNS", val: "FLUSH", active: ttlManager.isProtected)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.opacity(0.3)
|
||||
|
||||
// Action Links
|
||||
VStack(spacing: 6) {
|
||||
Button {
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
openWindow(id: "mainWindow")
|
||||
} label: {
|
||||
Label("Open Dashboard", systemImage: "macwindow")
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
.background(RoundedRectangle(cornerRadius: 6).fill(Color.primary.opacity(0.05)))
|
||||
|
||||
Button(role: .destructive) {
|
||||
NSApp.terminate(nil)
|
||||
} label: {
|
||||
Label("Quit Tethering Hider", systemImage: "power")
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.foregroundStyle(.red)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
}
|
||||
}
|
||||
.padding(14)
|
||||
.frame(width: 280)
|
||||
.alert("Error", isPresented: $showError) {
|
||||
Button("OK", role: .cancel) { }
|
||||
} message: {
|
||||
Text(errorMessage)
|
||||
}
|
||||
.task {
|
||||
ttlManager.refreshStatus()
|
||||
}
|
||||
}
|
||||
|
||||
private func badgeItem(title: String, val: String, active: Bool) -> some View {
|
||||
HStack(spacing: 3) {
|
||||
Circle()
|
||||
.fill(active ? Color.green : Color.orange)
|
||||
.frame(width: 4, height: 4)
|
||||
Text("\(title): \(val)")
|
||||
.font(.system(size: 9, weight: .medium, design: .monospaced))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 3)
|
||||
.background(Capsule().fill(.ultraThinMaterial))
|
||||
}
|
||||
|
||||
private func activateProtection() {
|
||||
Task {
|
||||
isProcessing = true
|
||||
defer { isProcessing = false }
|
||||
|
||||
do {
|
||||
let authenticated = try await authService.authenticateWithBiometrics()
|
||||
guard authenticated else { return }
|
||||
|
||||
try await commandService.setTTL(ttlManager.targetTTL)
|
||||
ttlManager.didSetTTL(ttlManager.targetTTL)
|
||||
} catch is AuthenticationError {
|
||||
} catch SystemCommandError.userCancelledAuth {
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
showError = true
|
||||
ttlManager.setError(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func deactivateProtection() {
|
||||
Task {
|
||||
isProcessing = true
|
||||
defer { isProcessing = false }
|
||||
|
||||
do {
|
||||
let authenticated = try await authService.authenticateWithBiometrics()
|
||||
guard authenticated else { return }
|
||||
|
||||
try await commandService.resetTTL()
|
||||
ttlManager.didSetTTL(ttlManager.defaultTTL)
|
||||
} catch is AuthenticationError {
|
||||
} catch SystemCommandError.userCancelledAuth {
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
showError = true
|
||||
ttlManager.setError(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue