mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-28 23:54:32 +00:00
Update SettingsView
This commit is contained in:
parent
022fa187e3
commit
1b5925090a
4 changed files with 2177 additions and 92 deletions
|
|
@ -22,10 +22,24 @@
|
||||||
C61F69420FB64DA98516CFBC /* MediaRemoteAdapter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F4C1DD09EC134302938E84F6 /* MediaRemoteAdapter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
C61F69420FB64DA98516CFBC /* MediaRemoteAdapter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F4C1DD09EC134302938E84F6 /* MediaRemoteAdapter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
B933CDB48C2F42C2887A9836 /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 12;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
C61F69420FB64DA98516CFBC /* MediaRemoteAdapter.framework in Embed Frameworks */,
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
7E81303D2E3FB40500E08F3F /* mediaremote-adapter.pl */ = {isa = PBXFileReference; lastKnownFileType = text.script.perl; path = "mediaremote-adapter.pl"; sourceTree = "<group>"; };
|
7E81303D2E3FB40500E08F3F /* mediaremote-adapter.pl */ = {isa = PBXFileReference; lastKnownFileType = text.script.perl; path = "mediaremote-adapter.pl"; sourceTree = "<group>"; };
|
||||||
7E81303E2E3FB40500E08F3F /* NowPlayingTestClient */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = NowPlayingTestClient; sourceTree = "<group>"; };
|
7E81303E2E3FB40500E08F3F /* NowPlayingTestClient */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = NowPlayingTestClient; sourceTree = "<group>"; };
|
||||||
F4C1DD09EC134302938E84F6 /* MediaRemoteAdapter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaRemoteAdapter.framework; path = "Frameworks/MediaRemoteAdapter.framework"; sourceTree = SOURCE_ROOT; };
|
F4C1DD09EC134302938E84F6 /* MediaRemoteAdapter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaRemoteAdapter.framework; path = Frameworks/MediaRemoteAdapter.framework; sourceTree = SOURCE_ROOT; };
|
||||||
F54406E72CD4987A00C7AFD9 /* DynamicIsland.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DynamicIsland.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
F54406E72CD4987A00C7AFD9 /* DynamicIsland.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DynamicIsland.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
|
@ -57,20 +71,6 @@
|
||||||
};
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
|
||||||
B933CDB48C2F42C2887A9836 /* Embed Frameworks */ = {
|
|
||||||
isa = PBXCopyFilesBuildPhase;
|
|
||||||
buildActionMask = 12;
|
|
||||||
dstPath = "";
|
|
||||||
dstSubfolderSpec = 10;
|
|
||||||
files = (
|
|
||||||
C61F69420FB64DA98516CFBC /* MediaRemoteAdapter.framework in Embed Frameworks */,
|
|
||||||
);
|
|
||||||
name = "Embed Frameworks";
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
7E81303F2E3FB40500E08F3F /* mediaremote-adapter */ = {
|
7E81303F2E3FB40500E08F3F /* mediaremote-adapter */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
|
|
@ -348,6 +348,7 @@
|
||||||
);
|
);
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_NSAppleEventsUsageDescription = "Need for Spotify and Apple Music";
|
INFOPLIST_KEY_NSAppleEventsUsageDescription = "Need for Spotify and Apple Music";
|
||||||
|
INFOPLIST_KEY_NSCalendarsUsageDescription = "Need for Calendar Mode";
|
||||||
INFOPLIST_KEY_NSCameraUsageDescription = "We need your camera for Mirror mode";
|
INFOPLIST_KEY_NSCameraUsageDescription = "We need your camera for Mirror mode";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
|
@ -381,6 +382,7 @@
|
||||||
);
|
);
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_NSAppleEventsUsageDescription = "Need for Spotify and Apple Music";
|
INFOPLIST_KEY_NSAppleEventsUsageDescription = "Need for Spotify and Apple Music";
|
||||||
|
INFOPLIST_KEY_NSCalendarsUsageDescription = "Need for Calendar Mode";
|
||||||
INFOPLIST_KEY_NSCameraUsageDescription = "We need your camera for Mirror mode";
|
INFOPLIST_KEY_NSCameraUsageDescription = "We need your camera for Mirror mode";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,6 @@
|
||||||
},
|
},
|
||||||
"%lld" : {
|
"%lld" : {
|
||||||
|
|
||||||
},
|
|
||||||
"%lld items" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"%lld%%" : {
|
"%lld%%" : {
|
||||||
|
|
||||||
|
|
@ -52,6 +49,18 @@
|
||||||
},
|
},
|
||||||
"• New feature 1" : {
|
"• New feature 1" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"3 items" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"5 items" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"7 items" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"10 items" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"About" : {
|
"About" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -76,6 +85,9 @@
|
||||||
},
|
},
|
||||||
"Access Denied" : {
|
"Access Denied" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Actions" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Activate" : {
|
"Activate" : {
|
||||||
|
|
||||||
|
|
@ -241,11 +253,14 @@
|
||||||
},
|
},
|
||||||
"Check for Updates…" : {
|
"Check for Updates…" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Choose a custom sound file that will play when the timer completes. Supported formats: MP3, M4A, WAV, AIFF." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Choose a Music Source" : {
|
"Choose a Music Source" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Choose which performance graphs to display. 1-3 graphs use horizontal layout, 4+ graphs use compressed grid layout." : {
|
"Choose File" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Circle" : {
|
"Circle" : {
|
||||||
|
|
@ -253,15 +268,18 @@
|
||||||
},
|
},
|
||||||
"Clear" : {
|
"Clear" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Clear Clipboard History" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Clear Data" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Clipboard" : {
|
"Clipboard" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Clipboard History" : {
|
"Clipboard History" : {
|
||||||
|
|
||||||
},
|
|
||||||
"Clipboard history will be tracked and accessible via the header button." : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Clipboard Manager" : {
|
"Clipboard Manager" : {
|
||||||
|
|
||||||
|
|
@ -278,15 +296,27 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Configure how many recent clipboard items to remember (minimum 3)." : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Continue" : {
|
"Continue" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Controls" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Copy something to get started" : {
|
"Copy something to get started" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"CPU Usage" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Current custom timer: %@" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Current History" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Current Items" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Custom" : {
|
"Custom" : {
|
||||||
|
|
||||||
|
|
@ -310,15 +340,27 @@
|
||||||
},
|
},
|
||||||
"Custom notch size - %.0f" : {
|
"Custom notch size - %.0f" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Custom Timer" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Custom Timer Duration" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Custom vizualizers (Lottie)" : {
|
"Custom vizualizers (Lottie)" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Custom: %@" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Customize in Settings" : {
|
"Customize in Settings" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Default" : {
|
"Default" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Default: dynamic.m4a" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Description" : {
|
"Description" : {
|
||||||
|
|
||||||
|
|
@ -328,6 +370,12 @@
|
||||||
},
|
},
|
||||||
"Disabled" : {
|
"Disabled" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Disk Read" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Disk Write" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Download" : {
|
"Download" : {
|
||||||
|
|
||||||
|
|
@ -373,6 +421,9 @@
|
||||||
},
|
},
|
||||||
"dynamic.island" : {
|
"dynamic.island" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Each graph can be individually enabled or disabled. Network activity shows download/upload speeds, and disk I/O shows read/write speeds." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Easily copy, store, and manage your most-used content. Upgrade now for advanced features like multi-item storage and quick access!" : {
|
"Easily copy, store, and manage your most-used content. Upgrade now for advanced features like multi-item storage and quick access!" : {
|
||||||
|
|
||||||
|
|
@ -413,6 +464,9 @@
|
||||||
},
|
},
|
||||||
"Enable music live activity" : {
|
"Enable music live activity" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Enable or disable the timer functionality in the Dynamic Island. The live activity toggle controls whether timer progress is shown in the expanded view." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Enable sneak peek" : {
|
"Enable sneak peek" : {
|
||||||
|
|
||||||
|
|
@ -423,13 +477,7 @@
|
||||||
"Enable timer feature in Settings to use this tab" : {
|
"Enable timer feature in Settings to use this tab" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Enable to access timer functionality in the Dynamic Island." : {
|
"Enable timer live activity" : {
|
||||||
|
|
||||||
},
|
|
||||||
"Enable to monitor CPU, Memory, GPU, Network and Disk usage." : {
|
|
||||||
|
|
||||||
},
|
|
||||||
"Enable to track clipboard history and access recent items." : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Enhance your experience with HUDs" : {
|
"Enhance your experience with HUDs" : {
|
||||||
|
|
@ -491,6 +539,9 @@
|
||||||
},
|
},
|
||||||
"Got it!" : {
|
"Got it!" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"GPU Usage" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Gradient" : {
|
"Gradient" : {
|
||||||
|
|
||||||
|
|
@ -510,7 +561,7 @@
|
||||||
"High" : {
|
"High" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"History size" : {
|
"History Size" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"https://github.com/th-ch/youtube-music" : {
|
"https://github.com/th-ch/youtube-music" : {
|
||||||
|
|
@ -541,6 +592,9 @@
|
||||||
},
|
},
|
||||||
"Installed extensions" : {
|
"Installed extensions" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Last Updated" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Later" : {
|
"Later" : {
|
||||||
|
|
||||||
|
|
@ -550,6 +604,9 @@
|
||||||
},
|
},
|
||||||
"Live" : {
|
"Live" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Live Performance Data" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Lottie JSON URL" : {
|
"Lottie JSON URL" : {
|
||||||
|
|
||||||
|
|
@ -620,6 +677,9 @@
|
||||||
},
|
},
|
||||||
"Medium" : {
|
"Medium" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Memory Usage" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Menubar icon" : {
|
"Menubar icon" : {
|
||||||
"extractionState" : "stale",
|
"extractionState" : "stale",
|
||||||
|
|
@ -640,12 +700,21 @@
|
||||||
},
|
},
|
||||||
"Minimum hover duration" : {
|
"Minimum hover duration" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Minutes" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Mirror" : {
|
"Mirror" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Mirror shape" : {
|
"Mirror shape" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Monitor clipboard changes and keep a history of recent copies. Use Cmd+Shift+V to quickly access clipboard history." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Monitoring Status" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"More" : {
|
"More" : {
|
||||||
|
|
||||||
|
|
@ -661,6 +730,12 @@
|
||||||
},
|
},
|
||||||
"Need Restart" : {
|
"Need Restart" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Network Download" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Network Upload" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Never hide" : {
|
"Never hide" : {
|
||||||
|
|
||||||
|
|
@ -812,6 +887,9 @@
|
||||||
},
|
},
|
||||||
"Remember last tab" : {
|
"Remember last tab" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Reset to Default" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Restart" : {
|
"Restart" : {
|
||||||
|
|
||||||
|
|
@ -835,6 +913,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Seconds" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Select Calendars" : {
|
"Select Calendars" : {
|
||||||
|
|
||||||
|
|
@ -847,6 +928,9 @@
|
||||||
},
|
},
|
||||||
"Selected animation" : {
|
"Selected animation" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Set a custom duration for the timer. This will be used when you press the 'Custom' button in the timer interface." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Settings" : {
|
"Settings" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -944,6 +1028,15 @@
|
||||||
},
|
},
|
||||||
"Start" : {
|
"Start" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Start Demo Timer:" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Start Monitoring" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Starts a 5-minute demo timer to test the timer live activity feature." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Stats" : {
|
"Stats" : {
|
||||||
|
|
||||||
|
|
@ -951,10 +1044,10 @@
|
||||||
"Stats Disabled" : {
|
"Stats Disabled" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Stats tab will appear in the notch when enabled." : {
|
"Stop" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Stop" : {
|
"Stop Monitoring" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Stopped" : {
|
"Stopped" : {
|
||||||
|
|
@ -973,11 +1066,14 @@
|
||||||
"System features" : {
|
"System features" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"System Monitoring" : {
|
"The clipboard icon appears in the header next to the settings gear when enabled. Use the global shortcut Cmd+Shift+V or click the icon to access history." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"TheDynamicIsland" : {
|
"TheDynamicIsland" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"This will permanently delete all stored clipboard history. The clipboard button is located to the left of the settings gear in the header." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Time to Full Charge: %lld min" : {
|
"Time to Full Charge: %lld min" : {
|
||||||
|
|
||||||
|
|
@ -988,7 +1084,10 @@
|
||||||
"Timer Disabled" : {
|
"Timer Disabled" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Timer tab will appear in the notch when enabled." : {
|
"Timer Feature" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Timer Sound" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Toggle Notch Open:" : {
|
"Toggle Notch Open:" : {
|
||||||
|
|
@ -1011,6 +1110,9 @@
|
||||||
},
|
},
|
||||||
"unmuted" : {
|
"unmuted" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Update" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Upgrade to Pro" : {
|
"Upgrade to Pro" : {
|
||||||
|
|
||||||
|
|
@ -1046,6 +1148,12 @@
|
||||||
},
|
},
|
||||||
"What's New" : {
|
"What's New" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"When enabled, the Stats tab will display real-time system performance graphs. This feature requires system permissions and may use additional battery." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"With %lld graphs enabled, the Dynamic Island will expand horizontally to accommodate all graphs in a single row." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"You can now enjoy the app. If you want to tweak things further, you can always visit the settings." : {
|
"You can now enjoy the app. If you want to tweak things further, you can always visit the settings." : {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import LottieUI
|
||||||
import Sparkle
|
import Sparkle
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SwiftUIIntrospect
|
import SwiftUIIntrospect
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
@StateObject var extensionManager = DynamicIslandExtensionManager()
|
@StateObject var extensionManager = DynamicIslandExtensionManager()
|
||||||
|
|
@ -1126,6 +1127,16 @@ struct Shortcuts: View {
|
||||||
Section {
|
Section {
|
||||||
KeyboardShortcuts.Recorder("Toggle Notch Open:", name: .toggleNotchOpen)
|
KeyboardShortcuts.Recorder("Toggle Notch Open:", name: .toggleNotchOpen)
|
||||||
}
|
}
|
||||||
|
Section {
|
||||||
|
KeyboardShortcuts.Recorder("Start Demo Timer:", name: .startDemoTimer)
|
||||||
|
} header: {
|
||||||
|
Text("Timer")
|
||||||
|
} footer: {
|
||||||
|
Text("Starts a 5-minute demo timer to test the timer live activity feature.")
|
||||||
|
.multilineTextAlignment(.trailing)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Shortcuts")
|
.navigationTitle("Shortcuts")
|
||||||
}
|
}
|
||||||
|
|
@ -1178,62 +1189,318 @@ func warningBadge(_ text: String, _ description: String) -> some View {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TimerSettings: View {
|
struct TimerSettings: View {
|
||||||
|
@ObservedObject private var coordinator = DynamicIslandViewCoordinator.shared
|
||||||
|
@Default(.enableTimerFeature) var enableTimerFeature
|
||||||
|
@AppStorage("customTimerDuration") private var customTimerDuration: Double = 600 // 10 minutes default
|
||||||
|
@State private var customMinutes: Int = 10
|
||||||
|
@State private var customSeconds: Int = 0
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section {
|
||||||
Defaults.Toggle("Enable timer feature", key: .enableTimerFeature)
|
Defaults.Toggle("Enable timer feature", key: .enableTimerFeature)
|
||||||
.onChange(of: Defaults[.enableTimerFeature]) { _, newValue in
|
|
||||||
if !newValue {
|
if enableTimerFeature {
|
||||||
// Stop any running timers when feature is disabled
|
Toggle(
|
||||||
TimerManager.shared.stopTimer()
|
"Enable timer live activity",
|
||||||
|
isOn: $coordinator.timerLiveActivityEnabled
|
||||||
|
)
|
||||||
|
.animation(.easeInOut, value: coordinator.timerLiveActivityEnabled)
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text("Timer Feature")
|
||||||
|
} footer: {
|
||||||
|
Text("Enable or disable the timer functionality in the Dynamic Island. The live activity toggle controls whether timer progress is shown in the expanded view.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if enableTimerFeature {
|
||||||
|
Section {
|
||||||
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
|
Text("Custom Timer Duration")
|
||||||
|
.font(.headline)
|
||||||
|
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
VStack {
|
||||||
|
Text("Minutes")
|
||||||
|
.font(.system(size: 14, weight: .medium))
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
|
Picker(selection: $customMinutes, label: Text("Minutes")) {
|
||||||
|
ForEach(0...59, id: \.self) { minute in
|
||||||
|
Text("\(minute)").tag(minute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.menu)
|
||||||
|
.frame(width: 80, height: 40)
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack {
|
||||||
|
Text("Seconds")
|
||||||
|
.font(.system(size: 14, weight: .medium))
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
|
Picker(selection: $customSeconds, label: Text("Seconds")) {
|
||||||
|
ForEach(0...59, id: \.self) { second in
|
||||||
|
Text("\(second)").tag(second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.menu)
|
||||||
|
.frame(width: 80, height: 40)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Text("Current custom timer: \(customTimerDisplayText)")
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
Spacer()
|
||||||
|
Button("Update") {
|
||||||
|
customTimerDuration = Double(customMinutes * 60 + customSeconds)
|
||||||
|
}
|
||||||
|
.disabled(customMinutes == 0 && customSeconds == 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text("Timer")
|
Text("Custom Timer")
|
||||||
} footer: {
|
} footer: {
|
||||||
if Defaults[.enableTimerFeature] {
|
Text("Set a custom duration for the timer. This will be used when you press the 'Custom' button in the timer interface.")
|
||||||
Text("Timer tab will appear in the notch when enabled.")
|
}
|
||||||
} else {
|
|
||||||
Text("Enable to access timer functionality in the Dynamic Island.")
|
Section {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
HStack {
|
||||||
|
Text("Timer Sound")
|
||||||
|
.font(.system(size: 16, weight: .medium))
|
||||||
|
Spacer()
|
||||||
|
Button("Choose File") {
|
||||||
|
selectCustomTimerSound()
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let customTimerSoundPath = UserDefaults.standard.string(forKey: "customTimerSoundPath") {
|
||||||
|
Text("Custom: \(URL(fileURLWithPath: customTimerSoundPath).lastPathComponent)")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
} else {
|
||||||
|
Text("Default: dynamic.m4a")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button("Reset to Default") {
|
||||||
|
UserDefaults.standard.removeObject(forKey: "customTimerSoundPath")
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
.disabled(UserDefaults.standard.string(forKey: "customTimerSoundPath") == nil)
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text("Timer Sound")
|
||||||
|
} footer: {
|
||||||
|
Text("Choose a custom sound file that will play when the timer completes. Supported formats: MP3, M4A, WAV, AIFF.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Timer")
|
.navigationTitle("Timer")
|
||||||
|
.onAppear {
|
||||||
|
let totalMinutes = Int(customTimerDuration) / 60
|
||||||
|
customMinutes = totalMinutes
|
||||||
|
customSeconds = Int(customTimerDuration) % 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var customTimerDisplayText: String {
|
||||||
|
let totalMinutes = Int(customTimerDuration) / 60
|
||||||
|
let seconds = Int(customTimerDuration) % 60
|
||||||
|
let hours = totalMinutes / 60
|
||||||
|
let minutes = totalMinutes % 60
|
||||||
|
|
||||||
|
if hours > 0 {
|
||||||
|
return "\(hours):\(String(format: "%02d", minutes)):\(String(format: "%02d", seconds))"
|
||||||
|
} else if minutes > 0 {
|
||||||
|
return "\(minutes):\(String(format: "%02d", seconds))"
|
||||||
|
} else {
|
||||||
|
return "\(seconds)s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func selectCustomTimerSound() {
|
||||||
|
let panel = NSOpenPanel()
|
||||||
|
panel.title = "Select Timer Sound"
|
||||||
|
panel.allowedContentTypes = [.audio]
|
||||||
|
panel.allowsMultipleSelection = false
|
||||||
|
panel.canChooseDirectories = false
|
||||||
|
panel.canChooseFiles = true
|
||||||
|
|
||||||
|
if panel.runModal() == .OK {
|
||||||
|
if let url = panel.url {
|
||||||
|
UserDefaults.standard.set(url.path, forKey: "customTimerSoundPath")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StatsSettings: View {
|
struct StatsSettings: View {
|
||||||
|
@ObservedObject var statsManager = StatsManager.shared
|
||||||
|
@Default(.enableStatsFeature) var enableStatsFeature
|
||||||
|
@Default(.showCpuGraph) var showCpuGraph
|
||||||
|
@Default(.showMemoryGraph) var showMemoryGraph
|
||||||
|
@Default(.showGpuGraph) var showGpuGraph
|
||||||
|
@Default(.showNetworkGraph) var showNetworkGraph
|
||||||
|
@Default(.showDiskGraph) var showDiskGraph
|
||||||
|
|
||||||
|
var enabledGraphsCount: Int {
|
||||||
|
[showCpuGraph, showMemoryGraph, showGpuGraph, showNetworkGraph, showDiskGraph].filter { $0 }.count
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section {
|
||||||
Defaults.Toggle("Enable stats feature", key: .enableStatsFeature)
|
Defaults.Toggle("Enable system stats monitoring", key: .enableStatsFeature)
|
||||||
.onChange(of: Defaults[.enableStatsFeature]) { _, newValue in
|
.onChange(of: enableStatsFeature) { _, newValue in
|
||||||
if !newValue {
|
if newValue {
|
||||||
// Stop monitoring when feature is disabled
|
statsManager.startMonitoring()
|
||||||
StatsManager.shared.stopMonitoring()
|
} else {
|
||||||
|
statsManager.stopMonitoring()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text("System Monitoring")
|
Text("General")
|
||||||
} footer: {
|
} footer: {
|
||||||
if Defaults[.enableStatsFeature] {
|
Text("When enabled, the Stats tab will display real-time system performance graphs. This feature requires system permissions and may use additional battery.")
|
||||||
Text("Stats tab will appear in the notch when enabled.")
|
.multilineTextAlignment(.trailing)
|
||||||
} else {
|
.foregroundStyle(.secondary)
|
||||||
Text("Enable to monitor CPU, Memory, GPU, Network and Disk usage.")
|
.font(.caption)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if Defaults[.enableStatsFeature] {
|
if enableStatsFeature {
|
||||||
Section {
|
Section {
|
||||||
Defaults.Toggle("Show CPU graph", key: .showCpuGraph)
|
Defaults.Toggle("CPU Usage", key: .showCpuGraph)
|
||||||
Defaults.Toggle("Show Memory graph", key: .showMemoryGraph)
|
Defaults.Toggle("Memory Usage", key: .showMemoryGraph)
|
||||||
Defaults.Toggle("Show GPU graph", key: .showGpuGraph)
|
Defaults.Toggle("GPU Usage", key: .showGpuGraph)
|
||||||
Defaults.Toggle("Show Network graph", key: .showNetworkGraph)
|
Defaults.Toggle("Network Activity", key: .showNetworkGraph)
|
||||||
Defaults.Toggle("Show Disk graph", key: .showDiskGraph)
|
Defaults.Toggle("Disk I/O", key: .showDiskGraph)
|
||||||
} header: {
|
} header: {
|
||||||
Text("Graph Visibility")
|
Text("Graph Visibility")
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("Choose which performance graphs to display. 1-3 graphs use horizontal layout, 4+ graphs use compressed grid layout.")
|
if enabledGraphsCount >= 4 {
|
||||||
|
Text("With \(enabledGraphsCount) graphs enabled, the Dynamic Island will expand horizontally to accommodate all graphs in a single row.")
|
||||||
|
.multilineTextAlignment(.trailing)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.font(.caption)
|
||||||
|
} else {
|
||||||
|
Text("Each graph can be individually enabled or disabled. Network activity shows download/upload speeds, and disk I/O shows read/write speeds.")
|
||||||
|
.multilineTextAlignment(.trailing)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
HStack {
|
||||||
|
Text("Monitoring Status")
|
||||||
|
Spacer()
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
Circle()
|
||||||
|
.fill(statsManager.isMonitoring ? .green : .red)
|
||||||
|
.frame(width: 8, height: 8)
|
||||||
|
Text(statsManager.isMonitoring ? "Active" : "Stopped")
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if statsManager.isMonitoring {
|
||||||
|
if showCpuGraph {
|
||||||
|
HStack {
|
||||||
|
Text("CPU Usage")
|
||||||
|
Spacer()
|
||||||
|
Text(statsManager.cpuUsageString)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if showMemoryGraph {
|
||||||
|
HStack {
|
||||||
|
Text("Memory Usage")
|
||||||
|
Spacer()
|
||||||
|
Text(statsManager.memoryUsageString)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if showGpuGraph {
|
||||||
|
HStack {
|
||||||
|
Text("GPU Usage")
|
||||||
|
Spacer()
|
||||||
|
Text(statsManager.gpuUsageString)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if showNetworkGraph {
|
||||||
|
HStack {
|
||||||
|
Text("Network Download")
|
||||||
|
Spacer()
|
||||||
|
Text(String(format: "%.1f MB/s", statsManager.networkDownload))
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Text("Network Upload")
|
||||||
|
Spacer()
|
||||||
|
Text(String(format: "%.1f MB/s", statsManager.networkUpload))
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if showDiskGraph {
|
||||||
|
HStack {
|
||||||
|
Text("Disk Read")
|
||||||
|
Spacer()
|
||||||
|
Text(String(format: "%.1f MB/s", statsManager.diskRead))
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Text("Disk Write")
|
||||||
|
Spacer()
|
||||||
|
Text(String(format: "%.1f MB/s", statsManager.diskWrite))
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Text("Last Updated")
|
||||||
|
Spacer()
|
||||||
|
Text(statsManager.lastUpdated, style: .relative)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text("Live Performance Data")
|
||||||
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
HStack {
|
||||||
|
Button(statsManager.isMonitoring ? "Stop Monitoring" : "Start Monitoring") {
|
||||||
|
if statsManager.isMonitoring {
|
||||||
|
statsManager.stopMonitoring()
|
||||||
|
} else {
|
||||||
|
statsManager.startMonitoring()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
.foregroundColor(statsManager.isMonitoring ? .red : .blue)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button("Clear Data") {
|
||||||
|
statsManager.clearHistory()
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
.disabled(statsManager.isMonitoring)
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text("Controls")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1242,55 +1509,128 @@ struct StatsSettings: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ClipboardSettings: View {
|
struct ClipboardSettings: View {
|
||||||
|
@ObservedObject var clipboardManager = ClipboardManager.shared
|
||||||
|
@Default(.enableClipboardManager) var enableClipboardManager
|
||||||
|
@Default(.clipboardHistorySize) var clipboardHistorySize
|
||||||
|
@Default(.showClipboardIcon) var showClipboardIcon
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section {
|
||||||
Defaults.Toggle("Enable clipboard manager", key: .enableClipboardManager)
|
Defaults.Toggle("Enable Clipboard Manager", key: .enableClipboardManager)
|
||||||
.onChange(of: Defaults[.enableClipboardManager]) { _, newValue in
|
.onChange(of: enableClipboardManager) { _, enabled in
|
||||||
if newValue {
|
if enabled {
|
||||||
ClipboardManager.shared.startMonitoring()
|
clipboardManager.startMonitoring()
|
||||||
} else {
|
} else {
|
||||||
ClipboardManager.shared.stopMonitoring()
|
clipboardManager.stopMonitoring()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text("Clipboard Manager")
|
Text("Clipboard Manager")
|
||||||
} footer: {
|
} footer: {
|
||||||
if Defaults[.enableClipboardManager] {
|
Text("Monitor clipboard changes and keep a history of recent copies. Use Cmd+Shift+V to quickly access clipboard history.")
|
||||||
Text("Clipboard history will be tracked and accessible via the header button.")
|
|
||||||
} else {
|
|
||||||
Text("Enable to track clipboard history and access recent items.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if Defaults[.enableClipboardManager] {
|
if enableClipboardManager {
|
||||||
Section {
|
Section {
|
||||||
Defaults.Toggle("Show clipboard icon", key: .showClipboardIcon)
|
Defaults.Toggle("Show Clipboard Icon", key: .showClipboardIcon)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
HStack {
|
||||||
HStack {
|
Text("History Size")
|
||||||
Text("History size")
|
Spacer()
|
||||||
Spacer()
|
Picker("History Size", selection: $clipboardHistorySize) {
|
||||||
Text("\(Defaults[.clipboardHistorySize]) items")
|
Text("3 items").tag(3)
|
||||||
.foregroundStyle(.secondary)
|
Text("5 items").tag(5)
|
||||||
|
Text("7 items").tag(7)
|
||||||
|
Text("10 items").tag(10)
|
||||||
}
|
}
|
||||||
Slider(
|
.pickerStyle(.menu)
|
||||||
value: Binding(
|
.frame(width: 100)
|
||||||
get: { Double(Defaults[.clipboardHistorySize]) },
|
}
|
||||||
set: { Defaults[.clipboardHistorySize] = Int($0) }
|
|
||||||
),
|
HStack {
|
||||||
in: 3...10,
|
Text("Current Items")
|
||||||
step: 1
|
Spacer()
|
||||||
)
|
Text("\(clipboardManager.clipboardHistory.count)")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Text("Monitoring Status")
|
||||||
|
Spacer()
|
||||||
|
Text(clipboardManager.isMonitoring ? "Active" : "Stopped")
|
||||||
|
.foregroundColor(clipboardManager.isMonitoring ? .green : .secondary)
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text("Settings")
|
Text("Settings")
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("Configure how many recent clipboard items to remember (minimum 3).")
|
Text("The clipboard icon appears in the header next to the settings gear when enabled. Use the global shortcut Cmd+Shift+V or click the icon to access history.")
|
||||||
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
Button("Clear Clipboard History") {
|
||||||
|
clipboardManager.clearHistory()
|
||||||
|
}
|
||||||
|
.foregroundColor(.red)
|
||||||
|
.disabled(clipboardManager.clipboardHistory.isEmpty)
|
||||||
|
} header: {
|
||||||
|
Text("Actions")
|
||||||
|
} footer: {
|
||||||
|
Text("This will permanently delete all stored clipboard history. The clipboard button is located to the left of the settings gear in the header.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !clipboardManager.clipboardHistory.isEmpty {
|
||||||
|
Section {
|
||||||
|
ForEach(clipboardManager.clipboardHistory) { item in
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: item.type.icon)
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
.frame(width: 16)
|
||||||
|
Text(item.type.displayName)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
Spacer()
|
||||||
|
Text(timeAgoString(from: item.timestamp))
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
Text(item.preview)
|
||||||
|
.font(.system(.body, design: .monospaced))
|
||||||
|
.lineLimit(2)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 2)
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text("Current History")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.formStyle(.grouped)
|
||||||
.navigationTitle("Clipboard")
|
.navigationTitle("Clipboard")
|
||||||
|
.onAppear {
|
||||||
|
if enableClipboardManager && !clipboardManager.isMonitoring {
|
||||||
|
clipboardManager.startMonitoring()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func timeAgoString(from date: Date) -> String {
|
||||||
|
let interval = Date().timeIntervalSince(date)
|
||||||
|
|
||||||
|
if interval < 60 {
|
||||||
|
return "Just now"
|
||||||
|
} else if interval < 3600 {
|
||||||
|
let minutes = Int(interval / 60)
|
||||||
|
return "\(minutes)m ago"
|
||||||
|
} else if interval < 86400 {
|
||||||
|
let hours = Int(interval / 3600)
|
||||||
|
return "\(hours)h ago"
|
||||||
|
} else {
|
||||||
|
let days = Int(interval / 86400)
|
||||||
|
return "\(days)d ago"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
1635
SettingsView.swift.md
Normal file
1635
SettingsView.swift.md
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue