Update settings search

This commit is contained in:
Hariharan Mudaliar 2025-11-29 14:04:38 +05:30
commit 5790b96cfe
3 changed files with 730 additions and 361 deletions

View file

@ -730,6 +730,18 @@
}
}
},
"%@ — %@" : {
"comment" : "A suggestion in the sidebar that includes both the title of the suggestion and the name of the tab it belongs to.",
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "%1$@ — %2$@"
}
}
}
},
"%@ · %@" : {
"comment" : "A secondary line of text that provides additional information about the air quality index, including its scale label and category name.",
"isCommentAutoGenerated" : true,
@ -5410,6 +5422,10 @@
}
}
},
"Collect the latest crash report to share with the developer when reporting lock screen or overlay issues." : {
"comment" : "A footer for the \"Diagnostics\" section of the lock screen settings.",
"isCommentAutoGenerated" : true
},
"Color Details" : {
"localizations" : {
"fr" : {
@ -5741,6 +5757,14 @@
},
"Controls how often system metrics refresh while monitoring is active." : {
},
"Controls the optional timer widget that floats above the media panel. Blur adds a frosted finish behind the compact view." : {
"comment" : "A footer for the \"Timer Widget\" section in the Lock Screen Settings view.",
"isCommentAutoGenerated" : true
},
"Controls whether Dynamic Island mirrors lock/unlock events with its own live activity and audible chimes." : {
"comment" : "A footer for the \"Live Activity & Feedback\" section in the Lock Screen Settings.",
"isCommentAutoGenerated" : true
},
"Copy event link" : {
"comment" : "A tooltip for a button that copies the link of a reminder event to the clipboard.",
@ -6675,6 +6699,10 @@
}
}
},
"Diagnostics" : {
"comment" : "A section header in the Lock Screen Settings view, describing diagnostics-related functionality.",
"isCommentAutoGenerated" : true
},
"Disable" : {
"localizations" : {
"ar" : {
@ -7536,6 +7564,10 @@
"comment" : "A message displayed below the section header in the IdleAnimationsSettingsSection, explaining that users can customize animations by enabling this feature.",
"isCommentAutoGenerated" : true
},
"Enable and style the media controls that appear above the system clock when the screen is locked." : {
"comment" : "A description of the media panel settings in the lock screen settings view.",
"isCommentAutoGenerated" : true
},
"Enable Built-in System HUD" : {
"localizations" : {
"fr" : {
@ -7990,6 +8022,10 @@
},
"Enable stats monitoring in Settings to view system performance data." : {
},
"Enable the weather capsule and configure its layout, provider, units, and optional battery/AQI indicators." : {
"comment" : "A footer for the \"Weather Widget\" section in the Lock Screen Settings view, describing its purpose.",
"isCommentAutoGenerated" : true
},
"Enable Thinking Mode" : {
@ -10629,6 +10665,10 @@
}
}
},
"Layout" : {
"comment" : "A label for the segment control that lets the user choose the layout of the weather widget.",
"isCommentAutoGenerated" : true
},
"License key" : {
"localizations" : {
"fr" : {
@ -10666,6 +10706,10 @@
"comment" : "A label indicating that the music is live.",
"isCommentAutoGenerated" : true
},
"Live Activity & Feedback" : {
"comment" : "The header for the \"Live Activity & Feedback\" section in the lock screen settings.",
"isCommentAutoGenerated" : true
},
"Live Monitoring" : {
"extractionState" : "stale",
"localizations" : {
@ -10713,7 +10757,7 @@
}
}
},
"Lock screen material" : {
"Lock Screen Integration" : {
},
"Lock screen panel preview" : {
@ -11038,112 +11082,9 @@
"Mac battery at %d percent" : {
"comment" : "Mac battery gauge accessibility label"
},
"MacBook icon is available in the circular layout." : {
},
"Made ❤️ by Ebullioscopic" : {
},
"Made with 🫶🏻 by not so boring not.people" : {
"extractionState" : "stale",
"localizations" : {
"ar" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Made with 🫶🏻 by not so boring not.people"
}
},
"cs" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Vyrobeno s 🫶🏻 ne tak nudnými ne lidmi."
}
},
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Gemacht mit 🫶🏻 durch nicht so boring not.people"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Made with 🫶🏻 by not so boring not.people"
}
},
"en-GB" : {
"stringUnit" : {
"state" : "translated",
"value" : "Made with 🫶🏻 by not so boring not.people"
}
},
"es" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Hecho con 🫶🏻 no tan aburrido no.people"
}
},
"fr" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Fait avec 🫶🏻 par des personnes pas si ennuyeuses - boring not.people"
}
},
"hu" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Made with 🫶🏻 by not so boring not.people"
}
},
"it" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Realizzato col 🫶🏻 da not so boring not.people"
}
},
"ko" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "지루하지 않은 비사람들이 🫶🏻 마음을 담아 만들었습니다"
}
},
"pl" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Zrobione z 🫶🏻 przez niezbyt nudnych ludzi"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Made with 🫶🏻 by not so boring not.people"
}
},
"ru" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Made with 🫶🏻 by not so boring not.people"
}
},
"tr" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Made with 🫶🏻 by not so boring not.people"
}
},
"uk" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Made with 🫶🏻 by not so boring not.people"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "由 not.people 团队用心制作 🫶🏻"
}
}
}
"Made with ❤️ by Ebullioscopic" : {
"comment" : "A footer text that credits the developer of the app.",
"isCommentAutoGenerated" : true
},
"Manage indicator preferences from Settings → Privacy. No manual microphone toggle is required." : {
"comment" : "A description of how to manage the microphone privacy indicator preferences.",
@ -11352,6 +11293,10 @@
}
}
},
"Material" : {
"comment" : "A picker that lets the user select the material of the lock screen glass.",
"isCommentAutoGenerated" : true
},
"Max Capacity: %lld%%" : {
"localizations" : {
"ar" : {
@ -11858,6 +11803,10 @@
"comment" : "Accessibility label describing the control that opens the media output selector.",
"isCommentAutoGenerated" : true
},
"Media Panel" : {
"comment" : "A section header in the lock screen settings view.",
"isCommentAutoGenerated" : true
},
"Media playback live activity" : {
"localizations" : {
"ar" : {
@ -12738,6 +12687,9 @@
}
}
}
},
"Mirrors the toggle found under Lock Screen settings so timer-specific workflows can enable or disable the widget without switching tabs." : {
},
"Monitor clipboard changes and keep a history of recent copies. Use Cmd+Shift+V to quickly access clipboard history." : {
@ -15958,6 +15910,10 @@
}
}
},
"Search Settings" : {
"comment" : "A placeholder text for a search bar in the settings view.",
"isCommentAutoGenerated" : true
},
"Search..." : {
"localizations" : {
"fr" : {
@ -17245,9 +17201,6 @@
},
"Show timer name" : {
},
"Shows a lock icon in the Dynamic Island when the screen is locked. Use the toggles above to control the lock screen media panel and weather capsule." : {
},
"Shows green camera icon and yellow microphone icon when in use. Uses event-driven CoreAudio and CoreMediaIO APIs." : {
@ -18456,9 +18409,16 @@
},
"Temperature unit" : {
},
"The MacBook icon is available only in the circular layout." : {
"comment" : "A message displayed below a toggle that lets the user choose whether to show a MacBook icon in the battery gauge of the weather widget. The message explains that the MacBook icon is only available when the weather widget is in a",
"isCommentAutoGenerated" : true
},
"TheDynamicIsland" : {
},
"These controls mirror the Lock Screen tab so you can tune the media overlay while focusing on playback settings." : {
},
"These controls sit beside the notch while a timer runs. They require the timer name to stay hidden for spacing." : {
@ -18637,6 +18597,10 @@
},
"Timer tint" : {
},
"Timer Widget" : {
"comment" : "A section header for the timer widget settings.",
"isCommentAutoGenerated" : true
},
"Toggle Notch Open:" : {
"localizations" : {
@ -19920,6 +19884,10 @@
},
"Weather data provider" : {
},
"Weather Widget" : {
"comment" : "A section header in the Lock Screen Settings view.",
"isCommentAutoGenerated" : true
},
"Weather: %@ %@" : {
"comment" : "Weather description and temperature",
@ -20154,9 +20122,6 @@
},
"When enabled, the Stats tab will display real-time system performance graphs. This feature requires system permissions and may use additional battery." : {
},
"Widget layout" : {
},
"Width:" : {
"localizations" : {

View file

@ -15,9 +15,106 @@ import SwiftUI
import SwiftUIIntrospect
import UniformTypeIdentifiers
private enum SettingsTab: String, CaseIterable, Identifiable {
case general
case appearance
case lockScreen
case media
case timer
case calendar
case hud
case battery
case stats
case clipboard
case screenAssistant
case colorPicker
case downloads
case shelf
case shortcuts
case extensions
case about
var id: String { rawValue }
var title: String {
switch self {
case .general: return "General"
case .appearance: return "Appearance"
case .lockScreen: return "Lock Screen"
case .media: return "Media"
case .timer: return "Timer"
case .calendar: return "Calendar"
case .hud: return "HUDs"
case .battery: return "Battery"
case .stats: return "Stats"
case .clipboard: return "Clipboard"
case .screenAssistant: return "Screen Assistant"
case .colorPicker: return "Color Picker"
case .downloads: return "Downloads"
case .shelf: return "Shelf"
case .shortcuts: return "Shortcuts"
case .extensions: return "Extensions"
case .about: return "About"
}
}
var systemImage: String {
switch self {
case .general: return "gear"
case .appearance: return "paintpalette"
case .lockScreen: return "lock.laptopcomputer"
case .media: return "play.laptopcomputer"
case .timer: return "timer"
case .calendar: return "calendar"
case .hud: return "dial.medium.fill"
case .battery: return "battery.100.bolt"
case .stats: return "chart.xyaxis.line"
case .clipboard: return "clipboard"
case .screenAssistant: return "brain.head.profile"
case .colorPicker: return "eyedropper"
case .downloads: return "square.and.arrow.down"
case .shelf: return "books.vertical"
case .shortcuts: return "keyboard"
case .extensions: return "puzzlepiece.extension"
case .about: return "info.circle"
}
}
var tint: Color {
switch self {
case .general: return Color.blue
case .appearance: return Color.purple
case .lockScreen: return Color.orange
case .media: return Color.green
case .timer: return Color.red
case .calendar: return Color.cyan
case .hud: return Color.indigo
case .battery: return Color.yellow
case .stats: return Color.teal
case .clipboard: return Color.mint
case .screenAssistant: return Color.pink
case .colorPicker: return Color.accentColor
case .downloads: return Color.gray
case .shelf: return Color.brown
case .shortcuts: return Color.orange
case .extensions: return Color.blue
case .about: return Color.secondary
}
}
}
private struct SettingsSearchEntry: Identifiable {
let tab: SettingsTab
let title: String
let keywords: [String]
var id: String { "\(tab.rawValue)-\(title)" }
}
struct SettingsView: View {
@StateObject var extensionManager = DynamicIslandExtensionManager()
@State private var selectedTab = "General"
@State private var selectedTab: SettingsTab = .general
@State private var searchText: String = ""
@Default(.enableMinimalisticUI) var enableMinimalisticUI
let updaterController: SPUStandardUpdaterController?
@ -28,111 +125,40 @@ struct SettingsView: View {
var body: some View {
NavigationSplitView {
List(selection: $selectedTab) {
NavigationLink(value: "General") {
Label("General", systemImage: "gear")
}
NavigationLink(value: "Appearance") {
Label("Appearance", systemImage: "eye")
}
NavigationLink(value: "Media") {
Label("Media", systemImage: "play.laptopcomputer")
}
NavigationLink(value: "Calendar") {
Label("Calendar", systemImage: "calendar")
}
NavigationLink(value: "HUD") {
Label("HUDs", systemImage: "dial.medium.fill")
}
NavigationLink(value: "Battery") {
Label("Battery", systemImage: "battery.100.bolt")
}
if !enableMinimalisticUI {
NavigationLink(value: "Timer") {
Label("Timer", systemImage: "timer")
}
NavigationLink(value: "Stats") {
Label("Stats", systemImage: "chart.xyaxis.line")
}
NavigationLink(value: "Clipboard") {
Label("Clipboard", systemImage: "clipboard")
}
NavigationLink(value: "ScreenAssistant") {
Label("Screen Assistant", systemImage: "brain.head.profile")
}
NavigationLink(value: "ColorPicker") {
Label("Color Picker", systemImage: "eyedropper")
List(filteredTabs, selection: selectionBinding) { tab in
NavigationLink(value: tab) {
HStack(spacing: 10) {
sidebarIcon(for: tab)
Text(tab.title)
.foregroundStyle(Color.primary)
}
.padding(.vertical, 4)
}
if extensionManager.installedExtensions
.contains(where: { $0.bundleIdentifier == downloadManagerExtension }) {
NavigationLink(value: "Downloads") {
Label("Downloads", systemImage: "square.and.arrow.down")
}
}
if !enableMinimalisticUI {
NavigationLink(value: "Shelf") {
Label("Shelf", systemImage: "books.vertical")
}
}
NavigationLink(value: "Shortcuts") {
Label("Shortcuts", systemImage: "keyboard")
}
NavigationLink(value: "Extensions") {
Label("Extensions", systemImage: "puzzlepiece.extension")
}
NavigationLink(value: "About") {
Label("About", systemImage: "info.circle")
}
.tag(tab)
.listRowInsets(EdgeInsets(top: 4, leading: 12, bottom: 4, trailing: 10))
}
.listStyle(SidebarListStyle())
.frame(minWidth: 205)
.toolbar(removing: .sidebarToggle)
.navigationSplitViewColumnWidth(200)
} detail: {
Group {
switch selectedTab {
case "General":
GeneralSettings()
case "Appearance":
Appearance()
case "Media":
Media()
case "Calendar":
CalendarSettings()
case "HUD":
HUD()
case "Battery":
Charge()
case "Timer":
TimerSettings()
case "Stats":
StatsSettings()
case "Clipboard":
ClipboardSettings()
case "ScreenAssistant":
ScreenAssistantSettings()
case "ColorPicker":
ColorPickerSettings()
case "Downloads":
Downloads()
case "Shelf":
Shelf()
case "Shortcuts":
Shortcuts()
case "Extensions":
Extensions()
case "About":
if let controller = updaterController {
About(updaterController: controller)
} else {
// Fallback with a default controller
About(updaterController: SPUStandardUpdaterController(startingUpdater: false, updaterDelegate: nil, userDriverDelegate: nil))
.navigationSplitViewColumnWidth(min: 220, ideal: 240, max: 360)
.searchable(text: $searchText, placement: .sidebar, prompt: Text("Search Settings"))
.searchSuggestions {
ForEach(searchSuggestions) { suggestion in
Button {
selectedTab = suggestion.tab
} label: {
Label {
Text("\(suggestion.title) — \(suggestion.tab.title)")
} icon: {
Image(systemName: suggestion.tab.systemImage)
}
}
default:
GeneralSettings()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.environment(\.defaultMinListRowHeight, 44)
} detail: {
detailView(for: resolvedSelection)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.navigationSplitViewStyle(.balanced)
.toolbar(removing: .sidebarToggle)
@ -145,7 +171,311 @@ struct SettingsView: View {
.environmentObject(extensionManager)
.formStyle(.grouped)
.frame(width: 700)
.background(Color(NSColor.windowBackgroundColor))
.onChange(of: searchText) { _, newValue in
let matches = tabsMatchingSearch(newValue)
guard let firstMatch = matches.first else { return }
if !matches.contains(resolvedSelection) {
selectedTab = firstMatch
}
}
.background {
Group {
if #available(macOS 26.0, *) {
RoundedRectangle(cornerRadius: 18, style: .continuous)
.glassEffect(
.clear
.tint(Color.white.opacity(0.1))
.interactive(),
in: .rect(cornerRadius: 18)
)
} else {
ZStack {
Color(NSColor.windowBackgroundColor)
RoundedRectangle(cornerRadius: 18, style: .continuous)
.fill(.ultraThinMaterial)
}
}
}
.ignoresSafeArea()
}
}
private var resolvedSelection: SettingsTab {
availableTabs.contains(selectedTab) ? selectedTab : (availableTabs.first ?? .general)
}
private var filteredTabs: [SettingsTab] {
tabsMatchingSearch(searchText)
}
private var selectionBinding: Binding<SettingsTab> {
Binding(
get: { resolvedSelection },
set: { selectedTab = $0 }
)
}
@ViewBuilder
private func sidebarIcon(for tab: SettingsTab) -> some View {
RoundedRectangle(cornerRadius: 8, style: .continuous)
.fill(tab.tint)
.frame(width: 26, height: 26)
.overlay {
Image(systemName: tab.systemImage)
.font(.system(size: 13, weight: .semibold))
.foregroundStyle(Color.white)
}
}
private var availableTabs: [SettingsTab] {
let ordered: [SettingsTab] = [
.general,
.appearance,
.lockScreen,
.media,
.timer,
.calendar,
.hud,
.battery,
.stats,
.clipboard,
.screenAssistant,
.colorPicker,
.downloads,
.shelf,
.shortcuts,
.extensions,
.about
]
return ordered.filter { isTabVisible($0) }
}
private func tabsMatchingSearch(_ query: String) -> [SettingsTab] {
let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return availableTabs }
let entryMatches = searchEntries(matching: trimmed)
let matchingTabs = Set(entryMatches.map(\.tab))
return availableTabs.filter { tab in
tab.title.localizedCaseInsensitiveContains(trimmed) || matchingTabs.contains(tab)
}
}
private var searchSuggestions: [SettingsSearchEntry] {
Array(searchEntries(matching: searchText).prefix(8))
}
private func searchEntries(matching query: String) -> [SettingsSearchEntry] {
let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return [] }
return settingsSearchIndex
.filter { availableTabs.contains($0.tab) }
.filter { entry in
entry.title.localizedCaseInsensitiveContains(trimmed) ||
entry.keywords.contains { $0.localizedCaseInsensitiveContains(trimmed) }
}
}
private var settingsSearchIndex: [SettingsSearchEntry] {
[
// General
SettingsSearchEntry(tab: .general, title: "Enable Minimalistic UI", keywords: ["minimalistic", "ui mode", "general"]),
SettingsSearchEntry(tab: .general, title: "Menubar icon", keywords: ["menu bar", "status bar", "icon"]),
SettingsSearchEntry(tab: .general, title: "Launch at login", keywords: ["autostart", "startup"]),
SettingsSearchEntry(tab: .general, title: "Show on all displays", keywords: ["multi-display", "external monitor"]),
SettingsSearchEntry(tab: .general, title: "Show on a specific display", keywords: ["preferred screen", "display picker"]),
SettingsSearchEntry(tab: .general, title: "Automatically switch displays", keywords: ["auto switch", "displays"]),
SettingsSearchEntry(tab: .general, title: "Hide Dynamic Island during screenshots & recordings", keywords: ["privacy", "screenshot", "recording"]),
SettingsSearchEntry(tab: .general, title: "Enable Screen Recording Detection", keywords: ["screen recording", "indicator"]),
SettingsSearchEntry(tab: .general, title: "Show Recording Indicator", keywords: ["recording indicator", "red dot"]),
SettingsSearchEntry(tab: .general, title: "Enable Focus Detection", keywords: ["focus", "do not disturb", "dnd"]),
SettingsSearchEntry(tab: .general, title: "Show Focus Indicator", keywords: ["focus icon", "moon"]),
SettingsSearchEntry(tab: .general, title: "Show Focus Label", keywords: ["focus label", "text"]),
SettingsSearchEntry(tab: .general, title: "Enable Camera Detection", keywords: ["camera", "privacy indicator"]),
SettingsSearchEntry(tab: .general, title: "Enable Microphone Detection", keywords: ["microphone", "privacy"]),
SettingsSearchEntry(tab: .general, title: "Enable gestures", keywords: ["gestures", "trackpad"]),
SettingsSearchEntry(tab: .general, title: "Close gesture", keywords: ["pinch", "swipe"]),
SettingsSearchEntry(tab: .general, title: "Extend hover area", keywords: ["hover", "cursor"]),
SettingsSearchEntry(tab: .general, title: "Enable haptics", keywords: ["haptic", "feedback"]),
SettingsSearchEntry(tab: .general, title: "Open notch on hover", keywords: ["hover to open", "auto open"]),
SettingsSearchEntry(tab: .general, title: "Non-notch display height", keywords: ["display height", "menu bar size"]),
// Battery (Charge)
SettingsSearchEntry(tab: .battery, title: "Show battery indicator", keywords: ["battery hud", "charge"]),
SettingsSearchEntry(tab: .battery, title: "Show battery percentage", keywords: ["battery percent"]),
SettingsSearchEntry(tab: .battery, title: "Show power status notifications", keywords: ["notifications", "power"]),
SettingsSearchEntry(tab: .battery, title: "Show power status icons", keywords: ["power icons", "charging icon"]),
// Downloads
SettingsSearchEntry(tab: .downloads, title: "Show download progress", keywords: ["downloads", "progress"]),
SettingsSearchEntry(tab: .downloads, title: "Enable Safari Downloads", keywords: ["safari", "browser"]),
SettingsSearchEntry(tab: .downloads, title: "Download indicator style", keywords: ["indicator", "style"]),
SettingsSearchEntry(tab: .downloads, title: "Download icon style", keywords: ["icon", "style"]),
// HUDs
SettingsSearchEntry(tab: .hud, title: "Show Bluetooth device connections", keywords: ["bluetooth", "hud"]),
SettingsSearchEntry(tab: .hud, title: "Use circular battery indicator", keywords: ["battery", "circular"]),
SettingsSearchEntry(tab: .hud, title: "Show battery percentage text in HUD", keywords: ["battery text"]),
SettingsSearchEntry(tab: .hud, title: "Scroll device name in HUD", keywords: ["marquee", "device name"]),
SettingsSearchEntry(tab: .hud, title: "Color-coded battery display", keywords: ["color", "battery"]),
SettingsSearchEntry(tab: .hud, title: "Color-coded volume display", keywords: ["volume", "color"]),
SettingsSearchEntry(tab: .hud, title: "Smooth color transitions", keywords: ["gradient", "smooth"]),
SettingsSearchEntry(tab: .hud, title: "Show percentages beside progress bars", keywords: ["percentages", "progress"]),
SettingsSearchEntry(tab: .hud, title: "HUD style", keywords: ["inline", "compact"]),
SettingsSearchEntry(tab: .hud, title: "Progressbar style", keywords: ["progress", "style"]),
SettingsSearchEntry(tab: .hud, title: "Enable glowing effect", keywords: ["glow", "indicator"]),
SettingsSearchEntry(tab: .hud, title: "Use accent color", keywords: ["accent", "color"]),
// Media
SettingsSearchEntry(tab: .media, title: "Music Source", keywords: ["media source", "controller"]),
SettingsSearchEntry(tab: .media, title: "Skip buttons", keywords: ["skip", "controls", "±10"]),
SettingsSearchEntry(tab: .media, title: "Sneak Peek Style", keywords: ["sneak peek", "preview"]),
SettingsSearchEntry(tab: .media, title: "Enable lyrics", keywords: ["lyrics", "song text"]),
// Calendar
SettingsSearchEntry(tab: .calendar, title: "Show calendar", keywords: ["calendar", "events"]),
SettingsSearchEntry(tab: .calendar, title: "Enable reminder live activity", keywords: ["reminder", "live activity"]),
SettingsSearchEntry(tab: .calendar, title: "Countdown style", keywords: ["reminder countdown"]),
SettingsSearchEntry(tab: .calendar, title: "Show lock screen reminder", keywords: ["lock screen", "reminder widget"]),
SettingsSearchEntry(tab: .calendar, title: "Chip color", keywords: ["reminder chip", "color"]),
// Shelf
SettingsSearchEntry(tab: .shelf, title: "Enable shelf", keywords: ["shelf", "dock"]),
SettingsSearchEntry(tab: .shelf, title: "Open shelf tab by default if items added", keywords: ["auto open", "shelf tab"]),
// Appearance
SettingsSearchEntry(tab: .appearance, title: "Settings icon in notch", keywords: ["settings button", "toolbar"]),
SettingsSearchEntry(tab: .appearance, title: "Enable window shadow", keywords: ["shadow", "appearance"]),
SettingsSearchEntry(tab: .appearance, title: "Corner radius scaling", keywords: ["corner radius", "shape"]),
SettingsSearchEntry(tab: .appearance, title: "Use simpler close animation", keywords: ["close animation", "notch"]),
SettingsSearchEntry(tab: .appearance, title: "Enable colored spectrograms", keywords: ["spectrogram", "audio"]),
SettingsSearchEntry(tab: .appearance, title: "Enable blur effect behind album art", keywords: ["blur", "album art"]),
SettingsSearchEntry(tab: .appearance, title: "Slider color", keywords: ["slider", "accent"]),
SettingsSearchEntry(tab: .appearance, title: "Enable Dynamic mirror", keywords: ["mirror", "reflection"]),
SettingsSearchEntry(tab: .appearance, title: "Mirror shape", keywords: ["mirror shape", "circle", "rectangle"]),
SettingsSearchEntry(tab: .appearance, title: "Show cool face animation while inactivity", keywords: ["face animation", "idle"]),
// Lock Screen
SettingsSearchEntry(tab: .lockScreen, title: "Enable lock screen live activity", keywords: ["lock screen", "live activity"]),
SettingsSearchEntry(tab: .lockScreen, title: "Play lock/unlock sounds", keywords: ["chime", "sound"]),
SettingsSearchEntry(tab: .lockScreen, title: "Material", keywords: ["glass", "frosted", "liquid"]),
SettingsSearchEntry(tab: .lockScreen, title: "Show lock screen media panel", keywords: ["media panel", "lock screen media"]),
SettingsSearchEntry(tab: .lockScreen, title: "Show media app icon", keywords: ["app icon", "media"]),
SettingsSearchEntry(tab: .lockScreen, title: "Show panel border", keywords: ["panel border"]),
SettingsSearchEntry(tab: .lockScreen, title: "Enable media panel blur", keywords: ["blur", "media panel"]),
SettingsSearchEntry(tab: .lockScreen, title: "Show lock screen timer", keywords: ["timer widget", "lock screen timer"]),
SettingsSearchEntry(tab: .lockScreen, title: "Enable timer blur", keywords: ["timer blur"]),
SettingsSearchEntry(tab: .lockScreen, title: "Show lock screen weather", keywords: ["weather widget"]),
SettingsSearchEntry(tab: .lockScreen, title: "Layout", keywords: ["inline", "circular", "weather layout"]),
SettingsSearchEntry(tab: .lockScreen, title: "Weather data provider", keywords: ["wttr", "open meteo"]),
SettingsSearchEntry(tab: .lockScreen, title: "Temperature unit", keywords: ["celsius", "fahrenheit"]),
SettingsSearchEntry(tab: .lockScreen, title: "Show location label", keywords: ["location", "weather"]),
SettingsSearchEntry(tab: .lockScreen, title: "Show charging status", keywords: ["charging", "weather"]),
SettingsSearchEntry(tab: .lockScreen, title: "Show charging percentage", keywords: ["charging percentage"]),
SettingsSearchEntry(tab: .lockScreen, title: "Show battery indicator", keywords: ["battery gauge", "weather"]),
SettingsSearchEntry(tab: .lockScreen, title: "Use MacBook icon when on battery", keywords: ["laptop icon", "battery"]),
SettingsSearchEntry(tab: .lockScreen, title: "Show Bluetooth battery", keywords: ["bluetooth", "gauge"]),
SettingsSearchEntry(tab: .lockScreen, title: "Show AQI widget", keywords: ["air quality", "aqi"]),
SettingsSearchEntry(tab: .lockScreen, title: "Air quality scale", keywords: ["aqi", "scale"]),
SettingsSearchEntry(tab: .lockScreen, title: "Use colored gauges", keywords: ["gauge tint", "monochrome"]),
// Shortcuts
SettingsSearchEntry(tab: .shortcuts, title: "Enable global keyboard shortcuts", keywords: ["keyboard", "shortcut"]),
// Timer
SettingsSearchEntry(tab: .timer, title: "Enable timer feature", keywords: ["timer", "enable"]),
SettingsSearchEntry(tab: .timer, title: "Mirror macOS Clock timers", keywords: ["system timer", "clock app"]),
SettingsSearchEntry(tab: .timer, title: "Show lock screen timer widget", keywords: ["lock screen", "timer widget"]),
SettingsSearchEntry(tab: .timer, title: "Enable timer blur", keywords: ["timer blur", "lock screen"]),
SettingsSearchEntry(tab: .timer, title: "Timer tint", keywords: ["timer colour", "preset"]),
SettingsSearchEntry(tab: .timer, title: "Solid colour", keywords: ["timer colour", "custom"]),
SettingsSearchEntry(tab: .timer, title: "Progress style", keywords: ["progress", "bar", "ring"]),
SettingsSearchEntry(tab: .timer, title: "Accent colour", keywords: ["accent", "timer"]),
// Stats
SettingsSearchEntry(tab: .stats, title: "Enable system stats monitoring", keywords: ["stats", "monitoring"]),
SettingsSearchEntry(tab: .stats, title: "Stop monitoring after closing the notch", keywords: ["stats", "auto stop"]),
SettingsSearchEntry(tab: .stats, title: "CPU Usage", keywords: ["cpu", "graph"]),
SettingsSearchEntry(tab: .stats, title: "Memory Usage", keywords: ["memory", "ram"]),
SettingsSearchEntry(tab: .stats, title: "GPU Usage", keywords: ["gpu", "graphics"]),
SettingsSearchEntry(tab: .stats, title: "Network Activity", keywords: ["network", "graph"]),
SettingsSearchEntry(tab: .stats, title: "Disk I/O", keywords: ["disk", "io"]),
// Clipboard
SettingsSearchEntry(tab: .clipboard, title: "Enable Clipboard Manager", keywords: ["clipboard", "manager"]),
SettingsSearchEntry(tab: .clipboard, title: "Show Clipboard Icon", keywords: ["icon", "clipboard"]),
SettingsSearchEntry(tab: .clipboard, title: "Display Mode", keywords: ["list", "grid", "clipboard"]),
SettingsSearchEntry(tab: .clipboard, title: "History Size", keywords: ["history", "clipboard"]),
// Screen Assistant
SettingsSearchEntry(tab: .screenAssistant, title: "Enable Screen Assistant", keywords: ["screen assistant", "ai"]),
SettingsSearchEntry(tab: .screenAssistant, title: "Display Mode", keywords: ["screen assistant", "mode"]),
// Color Picker
SettingsSearchEntry(tab: .colorPicker, title: "Enable Color Picker", keywords: ["color picker", "eyedropper"]),
SettingsSearchEntry(tab: .colorPicker, title: "Show Color Picker Icon", keywords: ["color icon", "toolbar"]),
SettingsSearchEntry(tab: .colorPicker, title: "Display Mode", keywords: ["color", "list"]),
SettingsSearchEntry(tab: .colorPicker, title: "History Size", keywords: ["color history"]),
SettingsSearchEntry(tab: .colorPicker, title: "Show All Color Formats", keywords: ["hex", "hsl", "color formats"])
]
}
private func isTabVisible(_ tab: SettingsTab) -> Bool {
switch tab {
case .timer, .stats, .clipboard, .screenAssistant, .colorPicker, .shelf:
return !enableMinimalisticUI
case .downloads:
return extensionManager.installedExtensions.contains { $0.bundleIdentifier == downloadManagerExtension }
default:
return true
}
}
@ViewBuilder
private func detailView(for tab: SettingsTab) -> some View {
switch tab {
case .general:
GeneralSettings()
case .appearance:
Appearance()
case .lockScreen:
LockScreenSettings()
case .media:
Media()
case .timer:
TimerSettings()
case .calendar:
CalendarSettings()
case .hud:
HUD()
case .battery:
Charge()
case .stats:
StatsSettings()
case .clipboard:
ClipboardSettings()
case .screenAssistant:
ScreenAssistantSettings()
case .colorPicker:
ColorPickerSettings()
case .downloads:
Downloads()
case .shelf:
Shelf()
case .shortcuts:
Shortcuts()
case .extensions:
Extensions()
case .about:
if let controller = updaterController {
About(updaterController: controller)
} else {
About(updaterController: SPUStandardUpdaterController(startingUpdater: false, updaterDelegate: nil, userDriverDelegate: nil))
}
}
}
}
@ -175,24 +505,6 @@ struct GeneralSettings: View {
@Default(.showDoNotDisturbIndicator) var showDoNotDisturbIndicator
@Default(.showDoNotDisturbLabel) var showDoNotDisturbLabel
@Default(.enableMinimalisticUI) var enableMinimalisticUI
@Default(.lockScreenGlassStyle) var lockScreenGlassStyle
@Default(.lockScreenShowAppIcon) var lockScreenShowAppIcon
@Default(.lockScreenPanelShowsBorder) var lockScreenPanelShowsBorder
@Default(.lockScreenPanelUsesBlur) var lockScreenPanelUsesBlur
@Default(.enableLockScreenTimerWidget) var enableLockScreenTimerWidget
@Default(.enableLockScreenWeatherWidget) var enableLockScreenWeatherWidget
@Default(.lockScreenWeatherShowsLocation) var lockScreenWeatherShowsLocation
@Default(.lockScreenWeatherShowsCharging) var lockScreenWeatherShowsCharging
@Default(.lockScreenWeatherShowsChargingPercentage) var lockScreenWeatherShowsChargingPercentage
@Default(.lockScreenWeatherShowsBluetooth) var lockScreenWeatherShowsBluetooth
@Default(.lockScreenWeatherShowsBatteryGauge) var lockScreenWeatherShowsBatteryGauge
@Default(.lockScreenWeatherWidgetStyle) var lockScreenWeatherWidgetStyle
@Default(.lockScreenWeatherTemperatureUnit) var lockScreenWeatherTemperatureUnit
@Default(.lockScreenWeatherShowsAQI) var lockScreenWeatherShowsAQI
@Default(.lockScreenWeatherAQIScale) var lockScreenWeatherAQIScale
@Default(.lockScreenWeatherUsesGaugeTint) var lockScreenWeatherUsesGaugeTint
@Default(.lockScreenWeatherProviderSource) var lockScreenWeatherProviderSource
@Default(.lockScreenWeatherBatteryUsesLaptopSymbol) var lockScreenWeatherBatteryUsesLaptopSymbol
var body: some View {
Form {
@ -355,97 +667,6 @@ struct GeneralSettings: View {
Text("Shows green camera icon and yellow microphone icon when in use. Uses event-driven CoreAudio and CoreMediaIO APIs.")
}
Section {
Defaults.Toggle("Enable Lock Screen Live Activity", key: .enableLockScreenLiveActivity)
Defaults.Toggle("Play lock/unlock sounds", key: .enableLockSounds)
if #available(macOS 26.0, *) {
Picker("Lock screen material", selection: $lockScreenGlassStyle) {
ForEach(LockScreenGlassStyle.allCases) { style in
Text(style.rawValue).tag(style)
}
}
} else {
Picker("Lock screen material", selection: $lockScreenGlassStyle) {
ForEach(LockScreenGlassStyle.allCases) { style in
Text(style.rawValue).tag(style)
}
}
.disabled(true)
Text("Liquid Glass requires macOS 26 or later.")
.font(.caption)
.foregroundStyle(.secondary)
}
Defaults.Toggle("Show media app icon", key: .lockScreenShowAppIcon)
Defaults.Toggle("Show panel border", key: .lockScreenPanelShowsBorder)
Defaults.Toggle("Enable blur", key: .lockScreenPanelUsesBlur)
Defaults.Toggle("Show lock screen media panel", key: .enableLockScreenMediaWidget)
Defaults.Toggle("Show lock screen weather", key: .enableLockScreenWeatherWidget)
Defaults.Toggle("Show lock screen timer", key: .enableLockScreenTimerWidget)
Defaults.Toggle("Enable timer blur", key: .lockScreenTimerWidgetUsesBlur)
.disabled(!enableLockScreenTimerWidget)
if enableLockScreenWeatherWidget {
Picker("Widget layout", selection: $lockScreenWeatherWidgetStyle) {
ForEach(LockScreenWeatherWidgetStyle.allCases) { style in
Text(style.rawValue).tag(style)
}
}
.pickerStyle(.segmented)
Picker("Weather data provider", selection: $lockScreenWeatherProviderSource) {
ForEach(LockScreenWeatherProviderSource.allCases) { source in
Text(source.displayName).tag(source)
}
}
.pickerStyle(.segmented)
Picker("Temperature unit", selection: $lockScreenWeatherTemperatureUnit) {
ForEach(LockScreenWeatherTemperatureUnit.allCases) { unit in
Text(unit.rawValue).tag(unit)
}
}
.pickerStyle(.segmented)
Defaults.Toggle("Show location label", key: .lockScreenWeatherShowsLocation)
.disabled(lockScreenWeatherWidgetStyle == .circular)
Defaults.Toggle("Show charging status", key: .lockScreenWeatherShowsCharging)
if lockScreenWeatherShowsCharging {
Defaults.Toggle("Show charging percentage", key: .lockScreenWeatherShowsChargingPercentage)
}
Defaults.Toggle("Show battery indicator", key: .lockScreenWeatherShowsBatteryGauge)
if lockScreenWeatherShowsBatteryGauge {
Defaults.Toggle("Use MacBook icon when on battery", key: .lockScreenWeatherBatteryUsesLaptopSymbol)
.disabled(lockScreenWeatherWidgetStyle != .circular)
if lockScreenWeatherWidgetStyle != .circular {
Text("MacBook icon is available in the circular layout.")
.font(.caption)
.foregroundStyle(.secondary)
}
}
Defaults.Toggle("Show Bluetooth battery", key: .lockScreenWeatherShowsBluetooth)
Defaults.Toggle("Show AQI widget", key: .lockScreenWeatherShowsAQI)
.disabled(!lockScreenWeatherProviderSource.supportsAirQuality)
if lockScreenWeatherShowsAQI && lockScreenWeatherProviderSource.supportsAirQuality {
Picker("Air quality scale", selection: $lockScreenWeatherAQIScale) {
ForEach(LockScreenWeatherAirQualityScale.allCases) { scale in
Text(scale.displayName).tag(scale)
}
}
.pickerStyle(.segmented)
}
if !lockScreenWeatherProviderSource.supportsAirQuality {
Text("Air quality requires the Open Meteo provider.")
.font(.caption)
.foregroundStyle(.secondary)
}
Defaults.Toggle("Use colored gauges", key: .lockScreenWeatherUsesGaugeTint)
}
Button("Copy Latest Crash Report") {
copyLatestCrashReport()
}
} header: {
Text("Lock Screen")
} footer: {
Text("Shows a lock icon in the Dynamic Island when the screen is locked. Use the toggles above to control the lock screen media panel and weather capsule.")
}
Section {
Picker(selection: $notchHeightMode, label:
Text("Notch display height")) {
@ -524,43 +745,6 @@ struct GeneralSettings: View {
}
}
private func copyLatestCrashReport() {
let crashReportsPath = NSString(string: "~/Library/Logs/DiagnosticReports").expandingTildeInPath
let fileManager = FileManager.default
do {
let files = try fileManager.contentsOfDirectory(atPath: crashReportsPath)
let crashFiles = files.filter { $0.contains("DynamicIsland") && $0.hasSuffix(".crash") }
guard let latestCrash = crashFiles.sorted(by: >).first else {
let alert = NSAlert()
alert.messageText = "No Crash Reports Found"
alert.informativeText = "No crash reports found for DynamicIsland"
alert.alertStyle = .informational
alert.runModal()
return
}
let crashPath = (crashReportsPath as NSString).appendingPathComponent(latestCrash)
let crashContent = try String(contentsOfFile: crashPath, encoding: .utf8)
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(crashContent, forType: .string)
let alert = NSAlert()
alert.messageText = "Crash Report Copied"
alert.informativeText = "Crash report '\(latestCrash)' has been copied to clipboard"
alert.alertStyle = .informational
alert.runModal()
} catch {
let alert = NSAlert()
alert.messageText = "Error"
alert.informativeText = "Failed to read crash reports: \(error.localizedDescription)"
alert.alertStyle = .warning
alert.runModal()
}
}
@ViewBuilder
func gestureControls() -> some View {
Section {
@ -864,6 +1048,7 @@ struct Media: View {
@Default(.musicAuxRightControl) private var musicAuxRightControl
@Default(.musicSkipBehavior) private var musicSkipBehavior
@Default(.musicControlWindowEnabled) private var musicControlWindowEnabled
@Default(.enableLockScreenMediaWidget) private var enableLockScreenMediaWidget
@State private var previousLeftAuxControl: MusicAuxiliaryControl = Defaults[.musicAuxLeftControl]
@State private var previousRightAuxControl: MusicAuxiliaryControl = Defaults[.musicAuxRightControl]
@ -976,6 +1161,20 @@ struct Media: View {
Text("Media playback live activity")
}
Section {
Defaults.Toggle("Show lock screen media panel", key: .enableLockScreenMediaWidget)
Defaults.Toggle("Show media app icon", key: .lockScreenShowAppIcon)
.disabled(!enableLockScreenMediaWidget)
Defaults.Toggle("Show panel border", key: .lockScreenPanelShowsBorder)
.disabled(!enableLockScreenMediaWidget)
Defaults.Toggle("Enable media panel blur", key: .lockScreenPanelUsesBlur)
.disabled(!enableLockScreenMediaWidget)
} header: {
Text("Lock Screen Integration")
} footer: {
Text("These controls mirror the Lock Screen tab so you can tune the media overlay while focusing on playback settings.")
}
Picker(selection: $hideNotchOption, label:
HStack {
Text("Hide DynamicIsland Options")
@ -1370,7 +1569,7 @@ struct About: View {
}
VStack(spacing: 0) {
Divider()
Text("Made ❤️ by Ebullioscopic")
Text("Made with ❤️ by Ebullioscopic")
.foregroundStyle(.secondary)
.padding(.top, 5)
.padding(.bottom, 7)
@ -1552,8 +1751,6 @@ struct Appearance: View {
Text("General")
}
LockScreenPositioningControls()
Section {
Defaults.Toggle("Enable colored spectrograms", key: .coloredSpectrogram)
Defaults
@ -1811,6 +2008,162 @@ struct Appearance: View {
}
}
struct LockScreenSettings: View {
@Default(.lockScreenGlassStyle) private var lockScreenGlassStyle
@Default(.enableLockScreenMediaWidget) private var enableLockScreenMediaWidget
@Default(.enableLockScreenTimerWidget) private var enableLockScreenTimerWidget
@Default(.enableLockScreenWeatherWidget) private var enableLockScreenWeatherWidget
@Default(.lockScreenWeatherWidgetStyle) private var lockScreenWeatherWidgetStyle
@Default(.lockScreenWeatherProviderSource) private var lockScreenWeatherProviderSource
@Default(.lockScreenWeatherTemperatureUnit) private var lockScreenWeatherTemperatureUnit
@Default(.lockScreenWeatherShowsCharging) private var lockScreenWeatherShowsCharging
@Default(.lockScreenWeatherShowsBatteryGauge) private var lockScreenWeatherShowsBatteryGauge
@Default(.lockScreenWeatherShowsAQI) private var lockScreenWeatherShowsAQI
@Default(.lockScreenWeatherAQIScale) private var lockScreenWeatherAQIScale
var body: some View {
Form {
Section {
Defaults.Toggle("Enable lock screen live activity", key: .enableLockScreenLiveActivity)
Defaults.Toggle("Play lock/unlock sounds", key: .enableLockSounds)
} header: {
Text("Live Activity & Feedback")
} footer: {
Text("Controls whether Dynamic Island mirrors lock/unlock events with its own live activity and audible chimes.")
}
Section {
if #available(macOS 26.0, *) {
Picker("Material", selection: $lockScreenGlassStyle) {
ForEach(LockScreenGlassStyle.allCases) { style in
Text(style.rawValue).tag(style)
}
}
} else {
Picker("Material", selection: $lockScreenGlassStyle) {
ForEach(LockScreenGlassStyle.allCases) { style in
Text(style.rawValue).tag(style)
}
}
.disabled(true)
Text("Liquid Glass requires macOS 26 or later.")
.font(.caption)
.foregroundStyle(.secondary)
}
Defaults.Toggle("Show lock screen media panel", key: .enableLockScreenMediaWidget)
Defaults.Toggle("Show media app icon", key: .lockScreenShowAppIcon)
.disabled(!enableLockScreenMediaWidget)
Defaults.Toggle("Show panel border", key: .lockScreenPanelShowsBorder)
.disabled(!enableLockScreenMediaWidget)
Defaults.Toggle("Enable media panel blur", key: .lockScreenPanelUsesBlur)
.disabled(!enableLockScreenMediaWidget)
} header: {
Text("Media Panel")
} footer: {
Text("Enable and style the media controls that appear above the system clock when the screen is locked.")
}
Section {
Defaults.Toggle("Show lock screen timer", key: .enableLockScreenTimerWidget)
Defaults.Toggle("Enable timer blur", key: .lockScreenTimerWidgetUsesBlur)
.disabled(!enableLockScreenTimerWidget)
} header: {
Text("Timer Widget")
} footer: {
Text("Controls the optional timer widget that floats above the media panel. Blur adds a frosted finish behind the compact view.")
}
Section {
Defaults.Toggle("Show lock screen weather", key: .enableLockScreenWeatherWidget)
if enableLockScreenWeatherWidget {
Picker("Layout", selection: $lockScreenWeatherWidgetStyle) {
ForEach(LockScreenWeatherWidgetStyle.allCases) { style in
Text(style.rawValue).tag(style)
}
}
.pickerStyle(.segmented)
Picker("Weather data provider", selection: $lockScreenWeatherProviderSource) {
ForEach(LockScreenWeatherProviderSource.allCases) { source in
Text(source.displayName).tag(source)
}
}
.pickerStyle(.segmented)
Picker("Temperature unit", selection: $lockScreenWeatherTemperatureUnit) {
ForEach(LockScreenWeatherTemperatureUnit.allCases) { unit in
Text(unit.rawValue).tag(unit)
}
}
.pickerStyle(.segmented)
Defaults.Toggle("Show location label", key: .lockScreenWeatherShowsLocation)
.disabled(lockScreenWeatherWidgetStyle == .circular)
Defaults.Toggle("Show charging status", key: .lockScreenWeatherShowsCharging)
if lockScreenWeatherShowsCharging {
Defaults.Toggle("Show charging percentage", key: .lockScreenWeatherShowsChargingPercentage)
}
Defaults.Toggle("Show battery indicator", key: .lockScreenWeatherShowsBatteryGauge)
if lockScreenWeatherShowsBatteryGauge {
Defaults.Toggle("Use MacBook icon when on battery", key: .lockScreenWeatherBatteryUsesLaptopSymbol)
.disabled(lockScreenWeatherWidgetStyle != .circular)
if lockScreenWeatherWidgetStyle != .circular {
Text("The MacBook icon is available only in the circular layout.")
.font(.caption)
.foregroundStyle(.secondary)
}
}
Defaults.Toggle("Show Bluetooth battery", key: .lockScreenWeatherShowsBluetooth)
Defaults.Toggle("Show AQI widget", key: .lockScreenWeatherShowsAQI)
.disabled(!lockScreenWeatherProviderSource.supportsAirQuality)
if lockScreenWeatherShowsAQI && lockScreenWeatherProviderSource.supportsAirQuality {
Picker("Air quality scale", selection: $lockScreenWeatherAQIScale) {
ForEach(LockScreenWeatherAirQualityScale.allCases) { scale in
Text(scale.displayName).tag(scale)
}
}
.pickerStyle(.segmented)
}
if !lockScreenWeatherProviderSource.supportsAirQuality {
Text("Air quality requires the Open Meteo provider.")
.font(.caption)
.foregroundStyle(.secondary)
}
Defaults.Toggle("Use colored gauges", key: .lockScreenWeatherUsesGaugeTint)
}
} header: {
Text("Weather Widget")
} footer: {
Text("Enable the weather capsule and configure its layout, provider, units, and optional battery/AQI indicators.")
}
LockScreenPositioningControls()
Section {
Button("Copy Latest Crash Report") {
copyLatestCrashReport()
}
} header: {
Text("Diagnostics")
} footer: {
Text("Collect the latest crash report to share with the developer when reporting lock screen or overlay issues.")
}
}
.navigationTitle("Lock Screen")
}
}
private struct LockScreenPositioningControls: View {
@Default(.lockScreenWeatherVerticalOffset) private var weatherOffset
@Default(.lockScreenMusicVerticalOffset) private var musicOffset
@ -2163,6 +2516,43 @@ private struct LockScreenPositioningPreview: View {
}
}
private func copyLatestCrashReport() {
let crashReportsPath = NSString(string: "~/Library/Logs/DiagnosticReports").expandingTildeInPath
let fileManager = FileManager.default
do {
let files = try fileManager.contentsOfDirectory(atPath: crashReportsPath)
let crashFiles = files.filter { $0.contains("DynamicIsland") && $0.hasSuffix(".crash") }
guard let latestCrash = crashFiles.sorted(by: >).first else {
let alert = NSAlert()
alert.messageText = "No Crash Reports Found"
alert.informativeText = "No crash reports found for DynamicIsland"
alert.alertStyle = .informational
alert.runModal()
return
}
let crashPath = (crashReportsPath as NSString).appendingPathComponent(latestCrash)
let crashContent = try String(contentsOfFile: crashPath, encoding: .utf8)
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(crashContent, forType: .string)
let alert = NSAlert()
alert.messageText = "Crash Report Copied"
alert.informativeText = "Crash report '\(latestCrash)' has been copied to clipboard"
alert.alertStyle = .informational
alert.runModal()
} catch {
let alert = NSAlert()
alert.messageText = "Error"
alert.informativeText = "Failed to read crash reports: \(error.localizedDescription)"
alert.alertStyle = .warning
alert.runModal()
}
}
struct Shortcuts: View {
@Default(.enableTimerFeature) var enableTimerFeature
@Default(.enableClipboardManager) var enableClipboardManager
@ -2376,6 +2766,7 @@ struct TimerSettings: View {
@Default(.timerProgressStyle) private var progressStyle
@Default(.timerControlWindowEnabled) private var controlWindowEnabled
@Default(.mirrorSystemTimer) private var mirrorSystemTimer
@Default(.enableLockScreenTimerWidget) private var enableLockScreenTimerWidget
@AppStorage("customTimerDuration") private var customTimerDuration: Double = 600
@State private var customHours: Int = 0
@State private var customMinutes: Int = 10
@ -2401,6 +2792,16 @@ struct TimerSettings: View {
if enableTimerFeature {
Group {
Section {
Defaults.Toggle("Show lock screen timer widget", key: .enableLockScreenTimerWidget)
Defaults.Toggle("Enable timer blur", key: .lockScreenTimerWidgetUsesBlur)
.disabled(!enableLockScreenTimerWidget)
} header: {
Text("Lock Screen Integration")
} footer: {
Text("Mirrors the toggle found under Lock Screen settings so timer-specific workflows can enable or disable the widget without switching tabs.")
}
Section {
VStack(alignment: .leading, spacing: 12) {
Text("Default Custom Timer")