Add source code
This commit is contained in:
parent
69c08de01c
commit
0416d157d7
14 changed files with 1672 additions and 0 deletions
BIN
.DS_Store
vendored
Normal file
BIN
.DS_Store
vendored
Normal file
Binary file not shown.
393
TetheringHider.xcodeproj/project.pbxproj
Normal file
393
TetheringHider.xcodeproj/project.pbxproj
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 77;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0ED897B912F95BB51D023D21 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BC8C459FAEBF6E4430F356C /* ContentView.swift */; };
|
||||
1996632E29C85D523F161D27 /* GlassModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8A1E4DD25148C4B779DD7AB /* GlassModifiers.swift */; };
|
||||
21C59AA87880BE42C84FA5AF /* StatusIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD85A3510C05A9BCF2AE3967 /* StatusIndicator.swift */; };
|
||||
26F71842BA0787F021CAA6B2 /* SystemCommandService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F78DC8BF18792636E9816D1 /* SystemCommandService.swift */; };
|
||||
2771ADA7302CEF6100A74E0C /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 2771ADA6302CEF6100A74E0C /* Localizable.xcstrings */; };
|
||||
46B0F9471E81979738AF3B77 /* TTLControlPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F622A688DC3081B00C57AF0 /* TTLControlPanel.swift */; };
|
||||
C2D263A68C7C3715C72D43E0 /* AuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 303AC637A762389B92B1789E /* AuthenticationService.swift */; };
|
||||
C6FE29A2579CCD7D90FD002A /* TetheringHiderApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BF970E56192546A80A72095 /* TetheringHiderApp.swift */; };
|
||||
F85087C3ACC19F8A9811FF55 /* TTLManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10849CA4224D433EE2901B17 /* TTLManager.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
10849CA4224D433EE2901B17 /* TTLManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TTLManager.swift; sourceTree = "<group>"; };
|
||||
1F622A688DC3081B00C57AF0 /* TTLControlPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TTLControlPanel.swift; sourceTree = "<group>"; };
|
||||
2771ADA6302CEF6100A74E0C /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
|
||||
2BF970E56192546A80A72095 /* TetheringHiderApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TetheringHiderApp.swift; sourceTree = "<group>"; };
|
||||
303AC637A762389B92B1789E /* AuthenticationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationService.swift; sourceTree = "<group>"; };
|
||||
7F78DC8BF18792636E9816D1 /* SystemCommandService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemCommandService.swift; sourceTree = "<group>"; };
|
||||
8BC8C459FAEBF6E4430F356C /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
A8C3A702F0D5E0791E68D310 /* TetheringHider.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TetheringHider.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
E8A1E4DD25148C4B779DD7AB /* GlassModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlassModifiers.swift; sourceTree = "<group>"; };
|
||||
FD85A3510C05A9BCF2AE3967 /* StatusIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusIndicator.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
09EB3BF2D219481B672AD590 /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
10849CA4224D433EE2901B17 /* TTLManager.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0E12970F7ADCC0D0DDFBEA23 /* Services */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
303AC637A762389B92B1789E /* AuthenticationService.swift */,
|
||||
7F78DC8BF18792636E9816D1 /* SystemCommandService.swift */,
|
||||
);
|
||||
path = Services;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B96BAE44BD18AD7208D3DA80 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A8C3A702F0D5E0791E68D310 /* TetheringHider.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF87D2EA1A464B929DA82A81 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8BC8C459FAEBF6E4430F356C /* ContentView.swift */,
|
||||
2771ADA6302CEF6100A74E0C /* Localizable.xcstrings */,
|
||||
E8A1E4DD25148C4B779DD7AB /* GlassModifiers.swift */,
|
||||
FD85A3510C05A9BCF2AE3967 /* StatusIndicator.swift */,
|
||||
1F622A688DC3081B00C57AF0 /* TTLControlPanel.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C0FBE1EA54AD5144BFFD3461 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D61DD9130F3129C572A0794F /* TetheringHider */,
|
||||
B96BAE44BD18AD7208D3DA80 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D61DD9130F3129C572A0794F /* TetheringHider */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
09EB3BF2D219481B672AD590 /* Models */,
|
||||
0E12970F7ADCC0D0DDFBEA23 /* Services */,
|
||||
BF87D2EA1A464B929DA82A81 /* Views */,
|
||||
2BF970E56192546A80A72095 /* TetheringHiderApp.swift */,
|
||||
);
|
||||
path = TetheringHider;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
522E0329F51D9AE0DD4F79C2 /* TetheringHider */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 2DDA7E49014C25A2097D74CA /* Build configuration list for PBXNativeTarget "TetheringHider" */;
|
||||
buildPhases = (
|
||||
0C4BD1F074A5C215A10B53C9 /* Sources */,
|
||||
2771ADA8302CEF6100A74E0C /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = TetheringHider;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = TetheringHider;
|
||||
productReference = A8C3A702F0D5E0791E68D310 /* TetheringHider.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
B2FDB901F8B4728B7FE78327 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastUpgradeCheck = 2660;
|
||||
TargetAttributes = {
|
||||
522E0329F51D9AE0DD4F79C2 = {
|
||||
ProvisioningStyle = Manual;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 91E0115F13D05E40BA171708 /* Build configuration list for PBXProject "TetheringHider" */;
|
||||
developmentRegion = es;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
Base,
|
||||
en,
|
||||
es,
|
||||
);
|
||||
mainGroup = C0FBE1EA54AD5144BFFD3461;
|
||||
minimizedProjectReferenceProxies = 1;
|
||||
preferredProjectObjectVersion = 77;
|
||||
productRefGroup = B96BAE44BD18AD7208D3DA80 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
522E0329F51D9AE0DD4F79C2 /* TetheringHider */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
2771ADA8302CEF6100A74E0C /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2771ADA7302CEF6100A74E0C /* Localizable.xcstrings in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
0C4BD1F074A5C215A10B53C9 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C2D263A68C7C3715C72D43E0 /* AuthenticationService.swift in Sources */,
|
||||
0ED897B912F95BB51D023D21 /* ContentView.swift in Sources */,
|
||||
1996632E29C85D523F161D27 /* GlassModifiers.swift in Sources */,
|
||||
21C59AA87880BE42C84FA5AF /* StatusIndicator.swift in Sources */,
|
||||
26F71842BA0787F021CAA6B2 /* SystemCommandService.swift in Sources */,
|
||||
46B0F9471E81979738AF3B77 /* TTLControlPanel.swift in Sources */,
|
||||
F85087C3ACC19F8A9811FF55 /* TTLManager.swift in Sources */,
|
||||
C6FE29A2579CCD7D90FD002A /* TetheringHiderApp.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
11AA62F6617B022CFD404264 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 6.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
31E4FAB6483D842FA3A3FCDE /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = TetheringHider/TetheringHider.entitlements;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDevelopmentRegion = es;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Tethering Hider";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.tetheringhider.app;
|
||||
PRODUCT_NAME = TetheringHider;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
7EADBBF3285DFAA6D9F296D1 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = TetheringHider/TetheringHider.entitlements;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDevelopmentRegion = es;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Tethering Hider";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.tetheringhider.app;
|
||||
PRODUCT_NAME = TetheringHider;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
B9CCE047ED62954275DBEDE8 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"DEBUG=1",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 6.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
2DDA7E49014C25A2097D74CA /* Build configuration list for PBXNativeTarget "TetheringHider" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
31E4FAB6483D842FA3A3FCDE /* Debug */,
|
||||
7EADBBF3285DFAA6D9F296D1 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Debug;
|
||||
};
|
||||
91E0115F13D05E40BA171708 /* Build configuration list for PBXProject "TetheringHider" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
B9CCE047ED62954275DBEDE8 /* Debug */,
|
||||
11AA62F6617B022CFD404264 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Debug;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = B2FDB901F8B4728B7FE78327 /* Project object */;
|
||||
}
|
||||
7
TetheringHider.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
TetheringHider.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
64
TetheringHider/Models/TTLManager.swift
Normal file
64
TetheringHider/Models/TTLManager.swift
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import Foundation
|
||||
import Observation
|
||||
|
||||
@Observable
|
||||
@MainActor
|
||||
final class TTLManager {
|
||||
private(set) var currentTTL: Int = 64
|
||||
private(set) var currentIPv6HopLimit: Int = 64
|
||||
private(set) var isProtected: Bool = false
|
||||
private(set) var lastError: String?
|
||||
|
||||
var onStatusChanged: (() -> Void)?
|
||||
|
||||
let defaultTTL: Int = 64
|
||||
let targetTTL: Int = 65
|
||||
|
||||
init() {
|
||||
refreshStatus()
|
||||
}
|
||||
|
||||
/// Reads the current system IPv4 TTL value using sysctlbyname (no root required)
|
||||
func readCurrentTTL() -> Int {
|
||||
var ttl: Int32 = 0
|
||||
var size = MemoryLayout<Int32>.size
|
||||
let result = sysctlbyname("net.inet.ip.ttl", &ttl, &size, nil, 0)
|
||||
if result == 0 {
|
||||
return Int(ttl)
|
||||
}
|
||||
return defaultTTL
|
||||
}
|
||||
|
||||
/// Reads the current system IPv6 Hop Limit value using sysctlbyname (no root required)
|
||||
func readCurrentIPv6HopLimit() -> Int {
|
||||
var hlim: Int32 = 0
|
||||
var size = MemoryLayout<Int32>.size
|
||||
let result = sysctlbyname("net.inet6.ip6.hlim", &hlim, &size, nil, 0)
|
||||
if result == 0 {
|
||||
return Int(hlim)
|
||||
}
|
||||
return defaultTTL
|
||||
}
|
||||
|
||||
/// Refreshes current IPv4 TTL, IPv6 Hop Limit, and protection status
|
||||
func refreshStatus() {
|
||||
currentTTL = readCurrentTTL()
|
||||
currentIPv6HopLimit = readCurrentIPv6HopLimit()
|
||||
isProtected = (currentTTL == targetTTL) && (currentIPv6HopLimit == targetTTL)
|
||||
lastError = nil
|
||||
onStatusChanged?()
|
||||
}
|
||||
|
||||
func didSetTTL(_ value: Int) {
|
||||
currentTTL = value
|
||||
currentIPv6HopLimit = value
|
||||
isProtected = value == targetTTL
|
||||
lastError = nil
|
||||
onStatusChanged?()
|
||||
}
|
||||
|
||||
func setError(_ message: String) {
|
||||
lastError = message
|
||||
onStatusChanged?()
|
||||
}
|
||||
}
|
||||
53
TetheringHider/Services/AuthenticationService.swift
Normal file
53
TetheringHider/Services/AuthenticationService.swift
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import Foundation
|
||||
import LocalAuthentication
|
||||
|
||||
enum AuthenticationError: LocalizedError {
|
||||
case biometricsNotAvailable
|
||||
case authenticationFailed(String)
|
||||
case userCancelled
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .biometricsNotAvailable:
|
||||
return "Touch ID is not available on this device."
|
||||
case .authenticationFailed(let reason):
|
||||
return "Authentication failed: \(reason)"
|
||||
case .userCancelled:
|
||||
return "Authentication was cancelled."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AuthenticationService: Sendable {
|
||||
|
||||
/// Attempts biometric authentication (Touch ID) before proceeding.
|
||||
/// Falls back gracefully if Touch ID is not available.
|
||||
/// Returns true if authenticated, false if biometrics unavailable (will proceed to admin dialog).
|
||||
func authenticateWithBiometrics() async throws -> Bool {
|
||||
let context = LAContext()
|
||||
var error: NSError?
|
||||
|
||||
guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
|
||||
// Touch ID not available — skip biometric step, will use admin dialog
|
||||
return true
|
||||
}
|
||||
|
||||
do {
|
||||
let success = try await context.evaluatePolicy(
|
||||
.deviceOwnerAuthenticationWithBiometrics,
|
||||
localizedReason: "Authenticate to modify system TTL value"
|
||||
)
|
||||
return success
|
||||
} catch let authError as LAError {
|
||||
switch authError.code {
|
||||
case .userCancel, .appCancel:
|
||||
throw AuthenticationError.userCancelled
|
||||
case .userFallback:
|
||||
// User chose to use password — allow proceeding to admin dialog
|
||||
return true
|
||||
default:
|
||||
throw AuthenticationError.authenticationFailed(authError.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
56
TetheringHider/Services/SystemCommandService.swift
Normal file
56
TetheringHider/Services/SystemCommandService.swift
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import Foundation
|
||||
|
||||
enum SystemCommandError: LocalizedError {
|
||||
case executionFailed(String)
|
||||
case userCancelledAuth
|
||||
case scriptError(String)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .executionFailed(let reason):
|
||||
return "Command failed: \(reason)"
|
||||
case .userCancelledAuth:
|
||||
return "Administrator authentication was cancelled."
|
||||
case .scriptError(let detail):
|
||||
return "Script error: \(detail)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SystemCommandService: Sendable {
|
||||
|
||||
/// Sets IPv4 TTL, IPv6 Hop Limit, and flushes system DNS cache with administrator privileges.
|
||||
func setTTL(_ value: Int) async throws {
|
||||
let command = "sysctl -w net.inet.ip.ttl=\(value) && sysctl -w net.inet6.ip6.hlim=\(value) && dscacheutil -flushcache && killall -HUP mDNSResponder"
|
||||
try await executeWithAdminPrivileges(command)
|
||||
}
|
||||
|
||||
/// Resets IPv4 TTL and IPv6 Hop Limit to default (64) and flushes DNS cache.
|
||||
func resetTTL() async throws {
|
||||
try await setTTL(64)
|
||||
}
|
||||
|
||||
/// Executes a shell command with administrator privileges via NSAppleScript
|
||||
@MainActor
|
||||
private func executeWithAdminPrivileges(_ command: String) async throws {
|
||||
let scriptSource = "do shell script \"\(command)\" with administrator privileges"
|
||||
|
||||
guard let script = NSAppleScript(source: scriptSource) else {
|
||||
throw SystemCommandError.scriptError("Failed to create AppleScript.")
|
||||
}
|
||||
|
||||
var errorDict: NSDictionary?
|
||||
script.executeAndReturnError(&errorDict)
|
||||
|
||||
if let error = errorDict {
|
||||
let errorNumber = error[NSAppleScript.errorNumber] as? Int ?? -1
|
||||
let errorMessage = error[NSAppleScript.errorMessage] as? String ?? "Unknown error"
|
||||
|
||||
if errorNumber == -128 {
|
||||
throw SystemCommandError.userCancelledAuth
|
||||
}
|
||||
|
||||
throw SystemCommandError.executionFailed(errorMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
8
TetheringHider/TetheringHider.entitlements
Normal file
8
TetheringHider/TetheringHider.entitlements
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
183
TetheringHider/TetheringHiderApp.swift
Normal file
183
TetheringHider/TetheringHiderApp.swift
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
//
|
||||
// TetheringHiderApp.swift
|
||||
//
|
||||
//
|
||||
// Created by Jesús David Chapman Vélez on 12/08/26.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AppKit
|
||||
|
||||
@MainActor
|
||||
final class StatusBarController: NSObject, NSMenuDelegate {
|
||||
private var statusItem: NSStatusItem?
|
||||
private let ttlManager: TTLManager
|
||||
private let authService = AuthenticationService()
|
||||
private let commandService = SystemCommandService()
|
||||
|
||||
init(ttlManager: TTLManager) {
|
||||
self.ttlManager = ttlManager
|
||||
super.init()
|
||||
setupStatusItem()
|
||||
|
||||
self.ttlManager.onStatusChanged = { [weak self] in
|
||||
Task { @MainActor in
|
||||
self?.updateMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setupStatusItem() {
|
||||
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
updateMenu()
|
||||
}
|
||||
|
||||
func updateMenu() {
|
||||
guard let statusItem = statusItem, let button = statusItem.button else { return }
|
||||
|
||||
// Status Bar Icon
|
||||
let imageName = ttlManager.isProtected
|
||||
? "antenna.radiowaves.left.and.right"
|
||||
: "antenna.radiowaves.left.and.right.slash"
|
||||
|
||||
button.image = NSImage(systemSymbolName: imageName, accessibilityDescription: "Tethering Hider")
|
||||
|
||||
let menu = NSMenu()
|
||||
menu.delegate = self
|
||||
|
||||
let statusTitle = ttlManager.isProtected
|
||||
? String(localized: "Tethering Hider — Hotspot Cloaked")
|
||||
: String(localized: "Tethering Hider — Standard Tethering")
|
||||
|
||||
let headerItem = NSMenuItem(title: statusTitle, action: nil, keyEquivalent: "")
|
||||
headerItem.isEnabled = false
|
||||
headerItem.image = NSImage(
|
||||
systemSymbolName: ttlManager.isProtected ? "eye.slash.fill" : "eye.fill",
|
||||
accessibilityDescription: nil
|
||||
)
|
||||
menu.addItem(headerItem)
|
||||
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
let toggleTitle = ttlManager.isProtected
|
||||
? String(localized: "Disable Hotspot Cloak (Set TTL 64)")
|
||||
: String(localized: "Enable Hotspot Cloak (Set TTL 65)")
|
||||
|
||||
let toggleItem = NSMenuItem(title: toggleTitle, action: #selector(toggleAction), keyEquivalent: "")
|
||||
toggleItem.target = self
|
||||
toggleItem.image = NSImage(
|
||||
systemSymbolName: ttlManager.isProtected ? "eye.fill" : "eye.slash.fill",
|
||||
accessibilityDescription: nil
|
||||
)
|
||||
menu.addItem(toggleItem)
|
||||
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
let ipv4Format = String(localized: "IPv4 TTL: %d")
|
||||
let ipv4Item = NSMenuItem(title: String(format: ipv4Format, ttlManager.currentTTL), action: nil, keyEquivalent: "")
|
||||
ipv4Item.isEnabled = false
|
||||
ipv4Item.image = NSImage(systemSymbolName: "network", accessibilityDescription: nil)
|
||||
menu.addItem(ipv4Item)
|
||||
|
||||
let ipv6Format = String(localized: "IPv6 HLIM: %d")
|
||||
let ipv6Item = NSMenuItem(title: String(format: ipv6Format, ttlManager.currentIPv6HopLimit), action: nil, keyEquivalent: "")
|
||||
ipv6Item.isEnabled = false
|
||||
ipv6Item.image = NSImage(systemSymbolName: "globe", accessibilityDescription: nil)
|
||||
menu.addItem(ipv6Item)
|
||||
|
||||
let dnsTitle = String(localized: "DNS Flush: AUTO")
|
||||
let dnsItem = NSMenuItem(title: dnsTitle, action: nil, keyEquivalent: "")
|
||||
dnsItem.isEnabled = false
|
||||
dnsItem.image = NSImage(systemSymbolName: "arrow.triangle.2.circlepath", accessibilityDescription: nil)
|
||||
menu.addItem(dnsItem)
|
||||
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
let aboutTitle = String(localized: "About Tethering Hider...")
|
||||
let aboutItem = NSMenuItem(title: aboutTitle, action: #selector(aboutAction), keyEquivalent: "")
|
||||
aboutItem.target = self
|
||||
aboutItem.image = NSImage(systemSymbolName: "info.circle.fill", accessibilityDescription: nil)
|
||||
menu.addItem(aboutItem)
|
||||
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
let quitTitle = String(localized: "Quit Tethering Hider")
|
||||
let quitItem = NSMenuItem(title: quitTitle, action: #selector(quitAction), keyEquivalent: "q")
|
||||
quitItem.target = self
|
||||
quitItem.image = NSImage(systemSymbolName: "rectangle.portrait.and.arrow.right", accessibilityDescription: nil)
|
||||
menu.addItem(quitItem)
|
||||
|
||||
statusItem.menu = menu
|
||||
}
|
||||
|
||||
func menuWillOpen(_ menu: NSMenu) {
|
||||
ttlManager.refreshStatus()
|
||||
}
|
||||
|
||||
@objc private func toggleAction() {
|
||||
Task { @MainActor in
|
||||
do {
|
||||
let authenticated = try await authService.authenticateWithBiometrics()
|
||||
guard authenticated else { return }
|
||||
|
||||
if ttlManager.isProtected {
|
||||
try await commandService.resetTTL()
|
||||
ttlManager.didSetTTL(ttlManager.defaultTTL)
|
||||
} else {
|
||||
try await commandService.setTTL(ttlManager.targetTTL)
|
||||
ttlManager.didSetTTL(ttlManager.targetTTL)
|
||||
}
|
||||
updateMenu()
|
||||
} catch {
|
||||
ttlManager.setError(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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] = [
|
||||
.credits: NSAttributedString(
|
||||
string: creditsString,
|
||||
attributes: [
|
||||
.font: NSFont.systemFont(ofSize: 11),
|
||||
.foregroundColor: NSColor.secondaryLabelColor
|
||||
]
|
||||
),
|
||||
.applicationName: "Tethering Hider",
|
||||
.version: "1.0.0"
|
||||
]
|
||||
NSApp.orderFrontStandardAboutPanel(options: options)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
@objc private func quitAction() {
|
||||
NSApp.terminate(nil)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
var statusBarController: StatusBarController?
|
||||
let ttlManager = TTLManager()
|
||||
|
||||
func applicationWillFinishLaunching(_ notification: Notification) {
|
||||
NSApp.setActivationPolicy(.accessory)
|
||||
}
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
NSApp.setActivationPolicy(.accessory)
|
||||
self.statusBarController = StatusBarController(ttlManager: ttlManager)
|
||||
}
|
||||
}
|
||||
|
||||
@main
|
||||
struct TetheringHiderApp: App {
|
||||
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||
|
||||
var body: some Scene {
|
||||
Settings {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
218
TetheringHider/Views/ContentView.swift
Normal file
218
TetheringHider/Views/ContentView.swift
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
TetheringHider/Views/GlassModifiers.swift
Normal file
46
TetheringHider/Views/GlassModifiers.swift
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
146
TetheringHider/Views/Localizable.xcstrings
Normal file
146
TetheringHider/Views/Localizable.xcstrings
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
{
|
||||
"sourceLanguage" : "en",
|
||||
"strings" : {
|
||||
"" : {
|
||||
|
||||
},
|
||||
"%@: %@" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "%1$@: %2$@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%lld" : {
|
||||
|
||||
},
|
||||
"About Tethering Hider..." : {
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Acerca de Tethering Hider..."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Evita la detección de anclaje de datos (hotspot) del operador móvil ajustando el TTL de IPv4 a 65, el Hop Limit de IPv6 a 65 y vaciando la caché DNS del sistema.\n\nDiseñado para macOS."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Deactivate Hider" : {
|
||||
|
||||
},
|
||||
"Disable Hotspot Cloak (Set TTL 64)" : {
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Desactivar Ocultación de Anclaje (TTL 64)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DNS Flush: AUTO" : {
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Vaciado DNS: AUTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Enable Hotspot Cloak (Set TTL 65)" : {
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Activar Ocultación de Anclaje (TTL 65)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Error" : {
|
||||
|
||||
},
|
||||
"IPv4 TTL: %d" : {
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "IPv4 TTL: %d"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"IPv6 HLIM: %d" : {
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "IPv6 HLIM: %d"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"OK" : {
|
||||
|
||||
},
|
||||
"Quit Tethering Hider" : {
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Salir de Tethering Hider"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Start minimized in menu bar" : {
|
||||
|
||||
},
|
||||
"Tethering Exposed" : {
|
||||
|
||||
},
|
||||
"Tethering Hidden" : {
|
||||
|
||||
},
|
||||
"TETHERING HIDER" : {
|
||||
|
||||
},
|
||||
"Tethering Hider — Hotspot Cloaked" : {
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Tethering Hider — Ocultación Activa"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Tethering Hider — Standard Tethering" : {
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Tethering Hider — Modo Estándar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
181
TetheringHider/Views/MenuBarView.swift
Normal file
181
TetheringHider/Views/MenuBarView.swift
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
110
TetheringHider/Views/StatusIndicator.swift
Normal file
110
TetheringHider/Views/StatusIndicator.swift
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
207
TetheringHider/Views/TTLControlPanel.swift
Normal file
207
TetheringHider/Views/TTLControlPanel.swift
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
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)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue