tethering-hider/TetheringHider/Views/TTLControlPanel.swift

207 lines
7.2 KiB
Swift
Raw Normal View History

2026-08-12 23:44:59 -05:00
import SwiftUI
struct TTLControlPanel: View {
let currentTTL: Int
let currentIPv6HopLimit: Int
let targetTTL: Int
let defaultTTL: Int
let isProtected: Bool
let isProcessing: Bool
let onActivate: () -> Void
let onDeactivate: () -> Void
@AppStorage("startMinimized") private var startMinimized = false
@Environment(\.colorScheme) private var colorScheme
@Namespace private var controlNamespace
@State private var isHovering = false
private var accentColor: Color {
if colorScheme == .light {
return isProtected
? Color(hue: 0.38, saturation: 0.85, brightness: 0.40)
: Color(hue: 0.05, saturation: 0.90, brightness: 0.45)
} else {
return isProtected
? Color(hue: 0.38, saturation: 0.70, brightness: 0.80)
: Color(hue: 0.06, saturation: 0.70, brightness: 0.85)
}
}
private var buttonTint: Color {
if colorScheme == .light {
return isProtected
? Color(hue: 0.05, saturation: 0.85, brightness: isHovering ? 0.60 : 0.50)
: Color(hue: 0.38, saturation: 0.85, brightness: isHovering ? 0.50 : 0.42)
} else {
return isProtected
? Color(hue: 0.06, saturation: 0.65, brightness: isHovering ? 0.90 : 0.80)
: Color(hue: 0.38, saturation: 0.65, brightness: isHovering ? 0.90 : 0.80)
}
}
var body: some View {
VStack(spacing: 12) {
ttlInfoCard
protocolBadges
actionButton
settingsCard
}
}
private var ttlInfoCard: some View {
HStack(spacing: 20) {
ttlValueDisplay(
label: "CURRENT",
value: currentTTL,
highlight: isProtected
)
Image(systemName: "arrow.right")
.font(.system(size: 14, weight: .medium))
.foregroundStyle(.tertiary)
ttlValueDisplay(
label: "TARGET",
value: targetTTL,
highlight: true
)
}
.padding(.horizontal, 24)
.padding(.vertical, 12)
.frame(maxWidth: .infinity)
.background {
RoundedRectangle(cornerRadius: 16)
.fill(.ultraThinMaterial)
.overlay {
RoundedRectangle(cornerRadius: 16)
.stroke(accentColor.opacity(0.15), lineWidth: 0.5)
}
}
.modifier(GlassCardModifier(tintColor: accentColor))
}
private func ttlValueDisplay(label: String, value: Int, highlight: Bool) -> some View {
VStack(spacing: 4) {
Text(label)
.font(.system(size: 10, weight: .semibold, design: .rounded))
.tracking(2)
.foregroundStyle(.secondary)
Text("\(value)")
.font(.system(size: 30, weight: .bold, design: .monospaced))
.foregroundStyle(highlight ? accentColor : .primary)
.contentTransition(.numericText())
.animation(.spring(duration: 0.5), value: value)
}
}
// Protocol Badges
private var protocolBadges: some View {
HStack(spacing: 8) {
badgeView(title: "IPv4 TTL", value: "\(currentTTL)", active: currentTTL == targetTTL)
badgeView(title: "IPv6 HLIM", value: "\(currentIPv6HopLimit)", active: currentIPv6HopLimit == targetTTL)
badgeView(title: "DNS FLUSH", value: "AUTO", active: isProtected)
}
}
private func badgeView(title: String, value: String, active: Bool) -> some View {
HStack(spacing: 4) {
Circle()
.fill(active ? Color.green : Color.orange)
.frame(width: 5, height: 5)
Text("\(title): \(value)")
.font(.system(size: 9, weight: .medium, design: .monospaced))
.foregroundStyle(.secondary)
}
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(
Capsule()
.fill(.ultraThinMaterial)
.overlay(
Capsule().stroke(active ? Color.green.opacity(0.3) : Color.orange.opacity(0.2), lineWidth: 0.5)
)
)
}
// Action Button
private var actionButton: some View {
Button {
if isProtected {
onDeactivate()
} else {
onActivate()
}
} label: {
HStack(spacing: 10) {
if isProcessing {
ProgressView()
.controlSize(.small)
.tint(.white)
} else {
Image(systemName: isProtected ? "antenna.radiowaves.left.and.right.slash" : "antenna.radiowaves.left.and.right")
.font(.system(size: 16, weight: .semibold))
.symbolEffect(.bounce, value: isHovering)
.contentTransition(.symbolEffect(.replace.magic(fallback: .replace)))
}
Text(isProtected ? "Deactivate Hider" : "Activate Hider")
.font(.system(size: 15, weight: .semibold, design: .rounded))
.contentTransition(.numericText())
}
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
}
.modifier(GlassButtonModifier())
.tint(buttonTint)
.scaleEffect(isHovering ? 1.025 : 1.0)
.shadow(
color: (isProtected
? Color(hue: 0.06, saturation: 0.8, brightness: colorScheme == .light ? 0.5 : 0.9)
: Color(hue: 0.38, saturation: 0.8, brightness: colorScheme == .light ? 0.4 : 0.9)
).opacity(isHovering ? (colorScheme == .light ? 0.35 : 0.45) : 0.0),
radius: isHovering ? 12 : 0,
x: 0,
y: isHovering ? 4 : 0
)
.onHover { hovering in
withAnimation(.spring(response: 0.35, dampingFraction: 0.7)) {
isHovering = hovering
}
}
.disabled(isProcessing)
.animation(.easeInOut(duration: 0.3), value: isProtected)
.animation(.easeInOut(duration: 0.3), value: isProcessing)
}
// Settings Card
private var settingsCard: some View {
HStack {
Label("Start minimized in menu bar", systemImage: "macwindow.on.rectangle")
.font(.system(size: 11, weight: .medium))
.foregroundStyle(.secondary)
Spacer()
Toggle("", isOn: $startMinimized)
.toggleStyle(.switch)
.controlSize(.small)
.tint(accentColor)
}
.padding(.horizontal, 12)
.padding(.vertical, 7)
.background(
RoundedRectangle(cornerRadius: 10)
.fill(.ultraThinMaterial)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(accentColor.opacity(0.12), lineWidth: 0.5)
)
)
}
}