218 lines
8.5 KiB
Swift
218 lines
8.5 KiB
Swift
|
|
//
|
||
|
|
// 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)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|