Made system-tray only for this app.
This commit is contained in:
parent
3992a4f9d0
commit
b2d6f95f70
11 changed files with 80 additions and 841 deletions
|
|
@ -7,12 +7,9 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* 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 */; };
|
26F71842BA0787F021CAA6B2 /* SystemCommandService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F78DC8BF18792636E9816D1 /* SystemCommandService.swift */; };
|
||||||
2771ADA7302CEF6100A74E0C /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 2771ADA6302CEF6100A74E0C /* Localizable.xcstrings */; };
|
2771ADA7302CEF6100A74E0C /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 2771ADA6302CEF6100A74E0C /* Localizable.xcstrings */; };
|
||||||
46B0F9471E81979738AF3B77 /* TTLControlPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F622A688DC3081B00C57AF0 /* TTLControlPanel.swift */; };
|
27B058A1302E147A00DC87F2 /* appicon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 27B058A0302E147A00DC87F2 /* appicon.icon */; };
|
||||||
C2D263A68C7C3715C72D43E0 /* AuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 303AC637A762389B92B1789E /* AuthenticationService.swift */; };
|
C2D263A68C7C3715C72D43E0 /* AuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 303AC637A762389B92B1789E /* AuthenticationService.swift */; };
|
||||||
C6FE29A2579CCD7D90FD002A /* TetheringHiderApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BF970E56192546A80A72095 /* TetheringHiderApp.swift */; };
|
C6FE29A2579CCD7D90FD002A /* TetheringHiderApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BF970E56192546A80A72095 /* TetheringHiderApp.swift */; };
|
||||||
F85087C3ACC19F8A9811FF55 /* TTLManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10849CA4224D433EE2901B17 /* TTLManager.swift */; };
|
F85087C3ACC19F8A9811FF55 /* TTLManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10849CA4224D433EE2901B17 /* TTLManager.swift */; };
|
||||||
|
|
@ -20,15 +17,12 @@
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
10849CA4224D433EE2901B17 /* TTLManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TTLManager.swift; sourceTree = "<group>"; };
|
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>"; };
|
2771ADA6302CEF6100A74E0C /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
|
||||||
|
27B058A0302E147A00DC87F2 /* appicon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = appicon.icon; sourceTree = "<group>"; };
|
||||||
2BF970E56192546A80A72095 /* TetheringHiderApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TetheringHiderApp.swift; 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>"; };
|
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>"; };
|
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; };
|
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 */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
|
@ -60,11 +54,7 @@
|
||||||
BF87D2EA1A464B929DA82A81 /* Views */ = {
|
BF87D2EA1A464B929DA82A81 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
8BC8C459FAEBF6E4430F356C /* ContentView.swift */,
|
|
||||||
2771ADA6302CEF6100A74E0C /* Localizable.xcstrings */,
|
2771ADA6302CEF6100A74E0C /* Localizable.xcstrings */,
|
||||||
E8A1E4DD25148C4B779DD7AB /* GlassModifiers.swift */,
|
|
||||||
FD85A3510C05A9BCF2AE3967 /* StatusIndicator.swift */,
|
|
||||||
1F622A688DC3081B00C57AF0 /* TTLControlPanel.swift */,
|
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -72,6 +62,7 @@
|
||||||
C0FBE1EA54AD5144BFFD3461 = {
|
C0FBE1EA54AD5144BFFD3461 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
27B058A0302E147A00DC87F2 /* appicon.icon */,
|
||||||
D61DD9130F3129C572A0794F /* TetheringHider */,
|
D61DD9130F3129C572A0794F /* TetheringHider */,
|
||||||
B96BAE44BD18AD7208D3DA80 /* Products */,
|
B96BAE44BD18AD7208D3DA80 /* Products */,
|
||||||
);
|
);
|
||||||
|
|
@ -116,7 +107,7 @@
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = YES;
|
BuildIndependentTargetsInParallel = YES;
|
||||||
LastUpgradeCheck = 2660;
|
LastUpgradeCheck = 1600;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
522E0329F51D9AE0DD4F79C2 = {
|
522E0329F51D9AE0DD4F79C2 = {
|
||||||
ProvisioningStyle = Manual;
|
ProvisioningStyle = Manual;
|
||||||
|
|
@ -149,6 +140,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
2771ADA7302CEF6100A74E0C /* Localizable.xcstrings in Resources */,
|
2771ADA7302CEF6100A74E0C /* Localizable.xcstrings in Resources */,
|
||||||
|
27B058A1302E147A00DC87F2 /* appicon.icon in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
@ -160,11 +152,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
C2D263A68C7C3715C72D43E0 /* AuthenticationService.swift in Sources */,
|
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 */,
|
26F71842BA0787F021CAA6B2 /* SystemCommandService.swift in Sources */,
|
||||||
46B0F9471E81979738AF3B77 /* TTLControlPanel.swift in Sources */,
|
|
||||||
F85087C3ACC19F8A9811FF55 /* TTLManager.swift in Sources */,
|
F85087C3ACC19F8A9811FF55 /* TTLManager.swift in Sources */,
|
||||||
C6FE29A2579CCD7D90FD002A /* TetheringHiderApp.swift in Sources */,
|
C6FE29A2579CCD7D90FD002A /* TetheringHiderApp.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
|
@ -177,8 +165,7 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_APPICON_NAME = appicon;
|
||||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
|
@ -211,12 +198,10 @@
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "-";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
|
@ -230,7 +215,6 @@
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
|
@ -241,16 +225,16 @@
|
||||||
31E4FAB6483D842FA3A3FCDE /* Debug */ = {
|
31E4FAB6483D842FA3A3FCDE /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = appicon;
|
||||||
CODE_SIGN_ENTITLEMENTS = TetheringHider/TetheringHider.entitlements;
|
CODE_SIGN_ENTITLEMENTS = TetheringHider/TetheringHider.entitlements;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_CFBundleDevelopmentRegion = es;
|
INFOPLIST_KEY_CFBundleDevelopmentRegion = es;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Tethering Hider";
|
INFOPLIST_KEY_CFBundleDisplayName = "Tethering Hider";
|
||||||
|
INFOPLIST_KEY_CFBundleIconName = appicon;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
|
@ -269,16 +253,16 @@
|
||||||
7EADBBF3285DFAA6D9F296D1 /* Release */ = {
|
7EADBBF3285DFAA6D9F296D1 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = appicon;
|
||||||
CODE_SIGN_ENTITLEMENTS = TetheringHider/TetheringHider.entitlements;
|
CODE_SIGN_ENTITLEMENTS = TetheringHider/TetheringHider.entitlements;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_CFBundleDevelopmentRegion = es;
|
INFOPLIST_KEY_CFBundleDevelopmentRegion = es;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Tethering Hider";
|
INFOPLIST_KEY_CFBundleDisplayName = "Tethering Hider";
|
||||||
|
INFOPLIST_KEY_CFBundleIconName = appicon;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
|
@ -298,8 +282,7 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_APPICON_NAME = appicon;
|
||||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
|
@ -332,12 +315,10 @@
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "-";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
|
@ -358,7 +339,6 @@
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
|
//
|
||||||
|
// TetheringHiderApp.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Jesús David Chapman Vélez on 12/08/26.
|
||||||
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum SystemCommandError: LocalizedError {
|
enum SystemCommandError: LocalizedError {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,3 @@
|
||||||
//
|
|
||||||
// TetheringHiderApp.swift
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Created by Jesús David Chapman Vélez on 12/08/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
|
|
@ -45,6 +38,7 @@ final class StatusBarController: NSObject, NSMenuDelegate {
|
||||||
let menu = NSMenu()
|
let menu = NSMenu()
|
||||||
menu.delegate = self
|
menu.delegate = self
|
||||||
|
|
||||||
|
// 1. Status Header with Cloak/Visible Icon
|
||||||
let statusTitle = ttlManager.isProtected
|
let statusTitle = ttlManager.isProtected
|
||||||
? String(localized: "Tethering Hider — Hotspot Cloaked")
|
? String(localized: "Tethering Hider — Hotspot Cloaked")
|
||||||
: String(localized: "Tethering Hider — Standard Tethering")
|
: String(localized: "Tethering Hider — Standard Tethering")
|
||||||
|
|
@ -59,6 +53,7 @@ final class StatusBarController: NSObject, NSMenuDelegate {
|
||||||
|
|
||||||
menu.addItem(NSMenuItem.separator())
|
menu.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
|
// 2. Toggle Action with Cloak/Uncloak Icon
|
||||||
let toggleTitle = ttlManager.isProtected
|
let toggleTitle = ttlManager.isProtected
|
||||||
? String(localized: "Disable Hotspot Cloak (Set TTL 64)")
|
? String(localized: "Disable Hotspot Cloak (Set TTL 64)")
|
||||||
: String(localized: "Enable Hotspot Cloak (Set TTL 65)")
|
: String(localized: "Enable Hotspot Cloak (Set TTL 65)")
|
||||||
|
|
@ -73,6 +68,7 @@ final class StatusBarController: NSObject, NSMenuDelegate {
|
||||||
|
|
||||||
menu.addItem(NSMenuItem.separator())
|
menu.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
|
// 3. Info Items with Network & DNS Icons
|
||||||
let ipv4Format = String(localized: "IPv4 TTL: %d")
|
let ipv4Format = String(localized: "IPv4 TTL: %d")
|
||||||
let ipv4Item = NSMenuItem(title: String(format: ipv4Format, ttlManager.currentTTL), action: nil, keyEquivalent: "")
|
let ipv4Item = NSMenuItem(title: String(format: ipv4Format, ttlManager.currentTTL), action: nil, keyEquivalent: "")
|
||||||
ipv4Item.isEnabled = false
|
ipv4Item.isEnabled = false
|
||||||
|
|
@ -93,6 +89,7 @@ final class StatusBarController: NSObject, NSMenuDelegate {
|
||||||
|
|
||||||
menu.addItem(NSMenuItem.separator())
|
menu.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
|
// 4. About Panel Option
|
||||||
let aboutTitle = String(localized: "About Tethering Hider...")
|
let aboutTitle = String(localized: "About Tethering Hider...")
|
||||||
let aboutItem = NSMenuItem(title: aboutTitle, action: #selector(aboutAction), keyEquivalent: "")
|
let aboutItem = NSMenuItem(title: aboutTitle, action: #selector(aboutAction), keyEquivalent: "")
|
||||||
aboutItem.target = self
|
aboutItem.target = self
|
||||||
|
|
@ -101,6 +98,7 @@ final class StatusBarController: NSObject, NSMenuDelegate {
|
||||||
|
|
||||||
menu.addItem(NSMenuItem.separator())
|
menu.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
|
// 5. Quit Option with Standard macOS Exit Icon
|
||||||
let quitTitle = String(localized: "Quit Tethering Hider")
|
let quitTitle = String(localized: "Quit Tethering Hider")
|
||||||
let quitItem = NSMenuItem(title: quitTitle, action: #selector(quitAction), keyEquivalent: "q")
|
let quitItem = NSMenuItem(title: quitTitle, action: #selector(quitAction), keyEquivalent: "q")
|
||||||
quitItem.target = self
|
quitItem.target = self
|
||||||
|
|
@ -136,7 +134,7 @@ final class StatusBarController: NSObject, NSMenuDelegate {
|
||||||
|
|
||||||
@objc private func aboutAction() {
|
@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 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(
|
.credits: NSAttributedString(
|
||||||
string: creditsString,
|
string: creditsString,
|
||||||
attributes: [
|
attributes: [
|
||||||
|
|
@ -147,6 +145,11 @@ final class StatusBarController: NSObject, NSMenuDelegate {
|
||||||
.applicationName: "Tethering Hider",
|
.applicationName: "Tethering Hider",
|
||||||
.version: "1.0.0"
|
.version: "1.0.0"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if let appIcon = NSImage(named: NSImage.applicationIconName) ?? NSApplication.shared.applicationIconImage {
|
||||||
|
options[.applicationIcon] = appIcon
|
||||||
|
}
|
||||||
|
|
||||||
NSApp.orderFrontStandardAboutPanel(options: options)
|
NSApp.orderFrontStandardAboutPanel(options: options)
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +1,6 @@
|
||||||
{
|
{
|
||||||
"sourceLanguage" : "en",
|
"sourceLanguage" : "en",
|
||||||
"strings" : {
|
"strings" : {
|
||||||
"" : {
|
|
||||||
|
|
||||||
},
|
|
||||||
"%@: %@" : {
|
|
||||||
"localizations" : {
|
|
||||||
"en" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "new",
|
|
||||||
"value" : "%1$@: %2$@"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"%lld" : {
|
|
||||||
|
|
||||||
},
|
|
||||||
"About Tethering Hider..." : {
|
"About Tethering Hider..." : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"es" : {
|
"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." : {
|
"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" : {
|
"localizations" : {
|
||||||
|
|
@ -39,9 +20,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Deactivate Hider" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Disable Hotspot Cloak (Set TTL 64)" : {
|
"Disable Hotspot Cloak (Set TTL 64)" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -72,9 +50,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Error" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"IPv4 TTL: %d" : {
|
"IPv4 TTL: %d" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -95,9 +70,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"OK" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Quit Tethering Hider" : {
|
"Quit Tethering Hider" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -108,18 +80,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Start minimized in menu bar" : {
|
|
||||||
|
|
||||||
},
|
|
||||||
"Tethering Exposed" : {
|
|
||||||
|
|
||||||
},
|
|
||||||
"Tethering Hidden" : {
|
|
||||||
|
|
||||||
},
|
|
||||||
"TETHERING HIDER" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Tethering Hider — Hotspot Cloaked" : {
|
"Tethering Hider — Hotspot Cloaked" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
appicon.icon/Assets/tethering-hidder.png
Normal file
BIN
appicon.icon/Assets/tethering-hidder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
51
appicon.icon/icon.json
Normal file
51
appicon.icon/icon.json
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"fill" : {
|
||||||
|
"linear-gradient" : [
|
||||||
|
"srgb:0.92143,0.92145,0.92144,1.00000",
|
||||||
|
"srgb:0.92143,0.92145,0.92144,1.00000"
|
||||||
|
],
|
||||||
|
"orientation" : {
|
||||||
|
"start" : {
|
||||||
|
"x" : 0.5,
|
||||||
|
"y" : 0
|
||||||
|
},
|
||||||
|
"stop" : {
|
||||||
|
"x" : 0.5,
|
||||||
|
"y" : 0.7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"groups" : [
|
||||||
|
{
|
||||||
|
"layers" : [
|
||||||
|
{
|
||||||
|
"fill" : "automatic",
|
||||||
|
"image-name" : "tethering-hidder.png",
|
||||||
|
"name" : "tethering-hidder",
|
||||||
|
"opacity" : 0.8,
|
||||||
|
"position" : {
|
||||||
|
"scale" : 1,
|
||||||
|
"translation-in-points" : [
|
||||||
|
16.71898342510258,
|
||||||
|
9.266469244762675
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"shadow" : {
|
||||||
|
"kind" : "neutral",
|
||||||
|
"opacity" : 0.5
|
||||||
|
},
|
||||||
|
"translucency" : {
|
||||||
|
"enabled" : true,
|
||||||
|
"value" : 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"supported-platforms" : {
|
||||||
|
"circles" : [
|
||||||
|
"watchOS"
|
||||||
|
],
|
||||||
|
"squares" : "shared"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue