tethering-hider/TetheringHider/Views/ContentView.swift

218 lines
8.5 KiB
Swift
Raw Normal View History

2026-08-12 23:44:59 -05:00
//
// ContentView.swift
//
//
// Created by Jesús David Chapman Vélez on 12/08/26.
//
import SwiftUI
struct ContentView: View {
@Environment(TTLManager.self) private var ttlManager
@AppStorage("startMinimized") private var startMinimized = false
@State private var hasHandledInitialLaunch = false
@State private var isProcessing = false
@State private var showError = false
@State private var errorMessage = ""
@Namespace private var shieldNamespace
private let authService = AuthenticationService()
private let commandService = SystemCommandService()
@Environment(\.colorScheme) private var colorScheme
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 {
ZStack {
// Background gradient
backgroundGradient
VStack(spacing: 0) {
// Header with glass
headerSection
.padding(.horizontal, 20)
.padding(.vertical, 10)
.modifier(GlassInteractiveModifier(cornerRadius: 14, tintColor: accentColor))
.padding(.top, 14)
Spacer(minLength: 12)
// Central antenna indicator
StatusIndicator(
isProtected: ttlManager.isProtected,
isProcessing: isProcessing
)
.padding(.vertical, 10)
Spacer(minLength: 12)
// Control panel
TTLControlPanel(
currentTTL: ttlManager.currentTTL,
currentIPv6HopLimit: ttlManager.currentIPv6HopLimit,
targetTTL: ttlManager.targetTTL,
defaultTTL: ttlManager.defaultTTL,
isProtected: ttlManager.isProtected,
isProcessing: isProcessing,
onActivate: activateProtection,
onDeactivate: deactivateProtection
)
.padding(.horizontal, 24)
.padding(.bottom, 24)
}
}
.frame(width: 400, height: 580)
.alert("Error", isPresented: $showError) {
Button("OK", role: .cancel) { }
} message: {
Text(errorMessage)
}
.task {
ttlManager.refreshStatus()
}
}
private var backgroundGradient: some View {
MeshGradient(
width: 3, height: 3,
points: [
[0.0, 0.0], [0.5, 0.0], [1.0, 0.0],
[0.0, 0.5], [0.5, 0.5], [1.0, 0.5],
[0.0, 1.0], [0.5, 1.0], [1.0, 1.0]
],
colors: meshColors
)
.ignoresSafeArea()
.animation(.easeInOut(duration: 1.2), value: ttlManager.isProtected)
.animation(.easeInOut(duration: 0.5), value: colorScheme)
}
private var meshColors: [Color] {
if colorScheme == .light {
return ttlManager.isProtected ? [
Color(hue: 0.44, saturation: 0.22, brightness: 0.95),
Color(hue: 0.48, saturation: 0.18, brightness: 0.98),
Color(hue: 0.52, saturation: 0.22, brightness: 0.94),
Color(hue: 0.40, saturation: 0.16, brightness: 0.96),
Color(hue: 0.46, saturation: 0.26, brightness: 0.93),
Color(hue: 0.50, saturation: 0.18, brightness: 0.97),
Color(hue: 0.42, saturation: 0.20, brightness: 0.95),
Color(hue: 0.45, saturation: 0.16, brightness: 0.98),
Color(hue: 0.51, saturation: 0.22, brightness: 0.94)
] : [
Color(hue: 0.04, saturation: 0.18, brightness: 0.96),
Color(hue: 0.07, saturation: 0.14, brightness: 0.98),
Color(hue: 0.09, saturation: 0.18, brightness: 0.95),
Color(hue: 0.02, saturation: 0.14, brightness: 0.97),
Color(hue: 0.05, saturation: 0.24, brightness: 0.94),
Color(hue: 0.08, saturation: 0.16, brightness: 0.98),
Color(hue: 0.03, saturation: 0.18, brightness: 0.95),
Color(hue: 0.06, saturation: 0.14, brightness: 0.98),
Color(hue: 0.08, saturation: 0.18, brightness: 0.94)
]
} else {
return ttlManager.isProtected ? [
Color(hue: 0.45, saturation: 0.4, brightness: 0.08),
Color(hue: 0.50, saturation: 0.3, brightness: 0.10),
Color(hue: 0.55, saturation: 0.4, brightness: 0.08),
Color(hue: 0.40, saturation: 0.3, brightness: 0.12),
Color(hue: 0.48, saturation: 0.5, brightness: 0.14),
Color(hue: 0.52, saturation: 0.3, brightness: 0.10),
Color(hue: 0.42, saturation: 0.4, brightness: 0.06),
Color(hue: 0.47, saturation: 0.3, brightness: 0.10),
Color(hue: 0.53, saturation: 0.4, brightness: 0.08)
] : [
Color(hue: 0.02, saturation: 0.3, brightness: 0.08),
Color(hue: 0.05, saturation: 0.2, brightness: 0.10),
Color(hue: 0.08, saturation: 0.3, brightness: 0.08),
Color(hue: 0.00, saturation: 0.2, brightness: 0.12),
Color(hue: 0.04, saturation: 0.4, brightness: 0.14),
Color(hue: 0.07, saturation: 0.2, brightness: 0.10),
Color(hue: 0.01, saturation: 0.3, brightness: 0.06),
Color(hue: 0.03, saturation: 0.2, brightness: 0.10),
Color(hue: 0.06, saturation: 0.3, brightness: 0.08)
]
}
}
// MARK: - Header
private var headerSection: some View {
VStack(spacing: 6) {
Text("TETHERING HIDER")
.font(.system(size: 14, weight: .bold, design: .rounded))
.tracking(4)
.foregroundStyle(.secondary)
Text(ttlManager.isProtected ? "Tethering Hidden" : "Tethering Exposed")
.font(.system(size: 13, weight: .medium))
.foregroundStyle(ttlManager.isProtected
? Color(hue: 0.38, saturation: 0.7, brightness: 0.85)
: Color(hue: 0.06, saturation: 0.7, brightness: 0.90)
)
.contentTransition(.numericText())
.animation(.easeInOut, value: ttlManager.isProtected)
}
}
private func activateProtection() {
Task {
isProcessing = true
defer { isProcessing = false }
do {
// Authenticate with Touch ID
let authenticated = try await authService.authenticateWithBiometrics()
guard authenticated else { return }
// Execute privileged command
try await commandService.setTTL(ttlManager.targetTTL)
// Refresh status
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)
}
}
}
}