// // 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) } } } }