Made system-tray only for this app.

This commit is contained in:
Jesús David Chapman Vélez 2026-08-13 10:35:10 -05:00
commit b2d6f95f70
11 changed files with 80 additions and 841 deletions

View file

@ -1,3 +1,10 @@
//
// TetheringHiderApp.swift
//
//
// Created by Jesús David Chapman Vélez on 12/08/26.
//
import Foundation
enum SystemCommandError: LocalizedError {

View file

@ -1,10 +1,3 @@
//
// TetheringHiderApp.swift
//
//
// Created by Jesús David Chapman Vélez on 12/08/26.
//
import SwiftUI
import AppKit
@ -45,6 +38,7 @@ final class StatusBarController: NSObject, NSMenuDelegate {
let menu = NSMenu()
menu.delegate = self
// 1. Status Header with Cloak/Visible Icon
let statusTitle = ttlManager.isProtected
? String(localized: "Tethering Hider — Hotspot Cloaked")
: String(localized: "Tethering Hider — Standard Tethering")
@ -59,6 +53,7 @@ final class StatusBarController: NSObject, NSMenuDelegate {
menu.addItem(NSMenuItem.separator())
// 2. Toggle Action with Cloak/Uncloak Icon
let toggleTitle = ttlManager.isProtected
? String(localized: "Disable Hotspot Cloak (Set TTL 64)")
: String(localized: "Enable Hotspot Cloak (Set TTL 65)")
@ -73,6 +68,7 @@ final class StatusBarController: NSObject, NSMenuDelegate {
menu.addItem(NSMenuItem.separator())
// 3. Info Items with Network & DNS Icons
let ipv4Format = String(localized: "IPv4 TTL: %d")
let ipv4Item = NSMenuItem(title: String(format: ipv4Format, ttlManager.currentTTL), action: nil, keyEquivalent: "")
ipv4Item.isEnabled = false
@ -93,6 +89,7 @@ final class StatusBarController: NSObject, NSMenuDelegate {
menu.addItem(NSMenuItem.separator())
// 4. About Panel Option
let aboutTitle = String(localized: "About Tethering Hider...")
let aboutItem = NSMenuItem(title: aboutTitle, action: #selector(aboutAction), keyEquivalent: "")
aboutItem.target = self
@ -101,6 +98,7 @@ final class StatusBarController: NSObject, NSMenuDelegate {
menu.addItem(NSMenuItem.separator())
// 5. Quit Option with Standard macOS Exit Icon
let quitTitle = String(localized: "Quit Tethering Hider")
let quitItem = NSMenuItem(title: quitTitle, action: #selector(quitAction), keyEquivalent: "q")
quitItem.target = self
@ -136,7 +134,7 @@ final class StatusBarController: NSObject, NSMenuDelegate {
@objc private func aboutAction() {
let creditsString = String(localized: "Bypasses mobile carrier hotspot tethering detection by adjusting outgoing IPv4 TTL to 65, IPv6 Hop Limit to 65, and flushing system DNS caches.\n\nDesigned for macOS.")
let options: [NSApplication.AboutPanelOptionKey: Any] = [
var options: [NSApplication.AboutPanelOptionKey: Any] = [
.credits: NSAttributedString(
string: creditsString,
attributes: [
@ -147,6 +145,11 @@ final class StatusBarController: NSObject, NSMenuDelegate {
.applicationName: "Tethering Hider",
.version: "1.0.0"
]
if let appIcon = NSImage(named: NSImage.applicationIconName) ?? NSApplication.shared.applicationIconImage {
options[.applicationIcon] = appIcon
}
NSApp.orderFrontStandardAboutPanel(options: options)
NSApp.activate(ignoringOtherApps: true)
}

View file

@ -1,218 +0,0 @@
//
// 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)
}
}
}
}

View file

@ -1,46 +0,0 @@
import SwiftUI
struct GlassCardModifier: ViewModifier {
let tintColor: Color
func body(content: Content) -> some View {
if #available(macOS 26, *) {
content
.glassEffect(.regular.tint(tintColor.opacity(0.15)), in: .rect(cornerRadius: 16))
} else {
content
}
}
}
struct GlassButtonModifier: ViewModifier {
var isActive: Bool = false
func body(content: Content) -> some View {
if #available(macOS 26, *) {
content
.buttonStyle(.glassProminent)
} else {
content
.buttonStyle(.borderedProminent)
}
}
}
struct GlassInteractiveModifier: ViewModifier {
var cornerRadius: CGFloat = 20
var tintColor: Color = .clear
func body(content: Content) -> some View {
if #available(macOS 26, *) {
content
.glassEffect(
.regular.tint(tintColor.opacity(0.1)).interactive(),
in: .rect(cornerRadius: cornerRadius)
)
} else {
content
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: cornerRadius))
}
}
}

View file

@ -1,22 +1,6 @@
{
"sourceLanguage" : "en",
"strings" : {
"" : {
},
"%@: %@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "%1$@: %2$@"
}
}
}
},
"%lld" : {
},
"About Tethering Hider..." : {
"localizations" : {
"es" : {
@ -26,9 +10,6 @@
}
}
}
},
"Activate Hider" : {
},
"Bypasses mobile carrier hotspot tethering detection by adjusting outgoing IPv4 TTL to 65, IPv6 Hop Limit to 65, and flushing system DNS caches.\n\nDesigned for macOS." : {
"localizations" : {
@ -39,9 +20,6 @@
}
}
}
},
"Deactivate Hider" : {
},
"Disable Hotspot Cloak (Set TTL 64)" : {
"localizations" : {
@ -72,9 +50,6 @@
}
}
}
},
"Error" : {
},
"IPv4 TTL: %d" : {
"localizations" : {
@ -95,9 +70,6 @@
}
}
}
},
"OK" : {
},
"Quit Tethering Hider" : {
"localizations" : {
@ -108,18 +80,6 @@
}
}
}
},
"Start minimized in menu bar" : {
},
"Tethering Exposed" : {
},
"Tethering Hidden" : {
},
"TETHERING HIDER" : {
},
"Tethering Hider — Hotspot Cloaked" : {
"localizations" : {

View file

@ -1,181 +0,0 @@
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)
}
}
}
}

View file

@ -1,110 +0,0 @@
import SwiftUI
struct StatusIndicator: View {
let isProtected: Bool
let isProcessing: Bool
@Environment(\.colorScheme) private var colorScheme
@State private var pulseScale: CGFloat = 1.0
@State private var glowOpacity: Double = 0.4
@State private var rotationAngle: Double = 0
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.75, brightness: 0.80)
: Color(hue: 0.06, saturation: 0.75, brightness: 0.85)
}
}
private var statusText: String {
if isProcessing { return "Processing..." }
return isProtected ? "PROTECTED" : "EXPOSED"
}
var body: some View {
VStack(spacing: 20) {
ZStack {
ForEach(0..<3, id: \.self) { ring in
Circle()
.stroke(
accentColor.opacity(colorScheme == .light ? (0.28 - Double(ring) * 0.07) : (0.15 - Double(ring) * 0.04)),
lineWidth: colorScheme == .light ? 2.0 : 1.5
)
.frame(
width: 140 + CGFloat(ring) * 22,
height: 140 + CGFloat(ring) * 22
)
.scaleEffect(pulseScale + CGFloat(ring) * 0.02)
}
Circle()
.fill(
RadialGradient(
colors: [
accentColor.opacity(0.25),
accentColor.opacity(0.08),
.clear
],
center: .center,
startRadius: 20,
endRadius: 75
)
)
.frame(width: 130, height: 130)
.scaleEffect(pulseScale)
// Antenna icon
Image(systemName: isProtected ? "antenna.radiowaves.left.and.right" : "antenna.radiowaves.left.and.right.slash")
.font(.system(size: 52, weight: .light))
.foregroundStyle(accentColor)
.symbolEffect(.bounce, value: isProtected)
.contentTransition(.symbolEffect(.replace.magic(fallback: .replace)))
.shadow(color: accentColor.opacity(0.5), radius: 16)
// Processing spinner
if isProcessing {
Circle()
.trim(from: 0, to: 0.7)
.stroke(
accentColor,
style: StrokeStyle(lineWidth: 2, lineCap: .round)
)
.frame(width: 125, height: 125)
.rotationEffect(.degrees(rotationAngle))
}
}
.animation(.easeInOut(duration: 0.8), value: isProtected)
.animation(.easeInOut(duration: 0.4), value: isProcessing)
// Status text
Text(statusText)
.font(.system(size: 16, weight: .semibold, design: .rounded))
.tracking(3)
.foregroundStyle(accentColor)
.contentTransition(.numericText())
.animation(.easeInOut, value: statusText)
}
.onAppear {
withAnimation(
.easeInOut(duration: 2.0)
.repeatForever(autoreverses: true)
) {
pulseScale = 1.04
glowOpacity = 0.7
}
withAnimation(
.linear(duration: 1.0)
.repeatForever(autoreverses: false)
) {
rotationAngle = 360
}
}
}
}

View file

@ -1,207 +0,0 @@
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)
)
)
}
}