mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Add Shortcut Toggle
This commit is contained in:
parent
b4f1953439
commit
5e206ec20b
6 changed files with 225 additions and 233 deletions
|
|
@ -203,6 +203,8 @@ struct ContentView: View {
|
|||
}
|
||||
}
|
||||
.frame(maxWidth: dynamicNotchSize.width, maxHeight: dynamicNotchSize.height, alignment: .top)
|
||||
.animation(.easeInOut(duration: 0.4), value: dynamicNotchSize)
|
||||
.animation(.easeInOut(duration: 0.4), value: coordinator.currentView)
|
||||
.shadow(color: ((vm.notchState == .open || isHovering) && Defaults[.enableShadow]) ? .black.opacity(0.6) : .clear, radius: Defaults[.cornerRadiusScaling] ? 10 : 5)
|
||||
.background(dragDetector)
|
||||
.environmentObject(vm)
|
||||
|
|
@ -309,6 +311,7 @@ struct ContentView: View {
|
|||
|
||||
ZStack {
|
||||
if vm.notchState == .open {
|
||||
Group {
|
||||
switch coordinator.currentView {
|
||||
case .home:
|
||||
NotchHomeView(albumArtNamespace: albumArtNamespace)
|
||||
|
|
@ -320,7 +323,14 @@ struct ContentView: View {
|
|||
NotchStatsView()
|
||||
}
|
||||
}
|
||||
.transition(.asymmetric(
|
||||
insertion: .scale(scale: 0.8).combined(with: .opacity).animation(.easeInOut(duration: 0.4)),
|
||||
removal: .scale(scale: 0.8).combined(with: .opacity).animation(.easeInOut(duration: 0.4))
|
||||
))
|
||||
.id(coordinator.currentView) // Force SwiftUI to treat each view as unique
|
||||
}
|
||||
}
|
||||
.animation(.easeInOut(duration: 0.4), value: coordinator.currentView)
|
||||
.zIndex(1)
|
||||
.allowsHitTesting(vm.notchState == .open)
|
||||
.blur(radius: abs(gestureProgress) > 0.3 ? min(abs(gestureProgress), 8) : 0)
|
||||
|
|
|
|||
|
|
@ -373,6 +373,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
|
||||
KeyboardShortcuts.onKeyDown(for: .toggleSneakPeek) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
// Only execute if shortcuts are enabled
|
||||
guard Defaults[.enableShortcuts] else { return }
|
||||
|
||||
self.coordinator.toggleSneakPeek(
|
||||
status: !self.coordinator.sneakPeek.show,
|
||||
type: .music,
|
||||
|
|
@ -383,6 +387,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
KeyboardShortcuts.onKeyDown(for: .toggleNotchOpen) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
// Only execute if shortcuts are enabled
|
||||
guard Defaults[.enableShortcuts] else { return }
|
||||
|
||||
let mouseLocation = NSEvent.mouseLocation
|
||||
|
||||
var viewModel = self.vm
|
||||
|
|
@ -418,6 +425,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
|
||||
KeyboardShortcuts.onKeyDown(for: .startDemoTimer) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
// Only execute if shortcuts are enabled
|
||||
guard Defaults[.enableShortcuts] else { return }
|
||||
|
||||
// Only start timer if the timer feature is enabled
|
||||
guard Defaults[.enableTimerFeature] else { return }
|
||||
|
||||
// Start a 5-minute demo timer
|
||||
TimerManager.shared.startDemoTimer(duration: 300)
|
||||
}
|
||||
|
|
@ -425,6 +439,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
KeyboardShortcuts.onKeyDown(for: .clipboardHistoryPanel) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
// Only execute if shortcuts are enabled
|
||||
guard Defaults[.enableShortcuts] else { return }
|
||||
|
||||
// Only open clipboard if the feature is enabled
|
||||
guard Defaults[.enableClipboardManager] else { return }
|
||||
|
||||
|
|
|
|||
|
|
@ -277,9 +277,15 @@
|
|||
},
|
||||
"Clipboard" : {
|
||||
|
||||
},
|
||||
"Clipboard feature is disabled" : {
|
||||
|
||||
},
|
||||
"Clipboard History" : {
|
||||
|
||||
},
|
||||
"Clipboard History:" : {
|
||||
|
||||
},
|
||||
"Clipboard Manager" : {
|
||||
|
||||
|
|
@ -444,6 +450,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Enable global keyboard shortcuts above to customize your shortcuts." : {
|
||||
|
||||
},
|
||||
"Enable graph visibility in Settings → Stats to view performance data." : {
|
||||
|
||||
|
|
@ -592,6 +601,9 @@
|
|||
},
|
||||
"Installed extensions" : {
|
||||
|
||||
},
|
||||
"Keyboard shortcuts are disabled" : {
|
||||
|
||||
},
|
||||
"Last Updated" : {
|
||||
|
||||
|
|
@ -727,6 +739,9 @@
|
|||
},
|
||||
"Name" : {
|
||||
|
||||
},
|
||||
"Navigation" : {
|
||||
|
||||
},
|
||||
"Need Restart" : {
|
||||
|
||||
|
|
@ -818,6 +833,9 @@
|
|||
},
|
||||
"Open System Settings" : {
|
||||
|
||||
},
|
||||
"Opens the clipboard history panel. Default is Cmd+Shift+V (similar to Windows+V on PC). Only works when clipboard feature is enabled." : {
|
||||
|
||||
},
|
||||
"Pause" : {
|
||||
|
||||
|
|
@ -1035,7 +1053,7 @@
|
|||
"Start Monitoring" : {
|
||||
|
||||
},
|
||||
"Starts a 5-minute demo timer to test the timer live activity feature." : {
|
||||
"Starts a 5-minute demo timer to test the timer live activity feature. Only works when timer feature is enabled." : {
|
||||
|
||||
},
|
||||
"Stats" : {
|
||||
|
|
@ -1086,6 +1104,9 @@
|
|||
},
|
||||
"Timer Feature" : {
|
||||
|
||||
},
|
||||
"Timer feature is disabled" : {
|
||||
|
||||
},
|
||||
"Timer Sound" : {
|
||||
|
||||
|
|
@ -1095,6 +1116,9 @@
|
|||
},
|
||||
"Toggle Sneak Peek:" : {
|
||||
|
||||
},
|
||||
"Toggle the Dynamic Island open or closed from anywhere." : {
|
||||
|
||||
},
|
||||
"Transform your notch truly yours" : {
|
||||
|
||||
|
|
@ -1148,6 +1172,9 @@
|
|||
},
|
||||
"What's New" : {
|
||||
|
||||
},
|
||||
"When disabled, all keyboard shortcuts will be inactive. You can still use the UI controls." : {
|
||||
|
||||
},
|
||||
"When enabled, the Stats tab will display real-time system performance graphs. This feature requires system permissions and may use additional battery." : {
|
||||
|
||||
|
|
|
|||
|
|
@ -118,142 +118,31 @@ struct NotchStatsView: View {
|
|||
return graphs
|
||||
}
|
||||
|
||||
// New vertical expansion layout system
|
||||
// Simplified and more consistent layout system
|
||||
@ViewBuilder
|
||||
var statsGridLayout: some View {
|
||||
let graphCount = availableGraphs.count
|
||||
|
||||
switch graphCount {
|
||||
case 1...3:
|
||||
// Single row for 1-3 graphs
|
||||
HStack(spacing: 12) {
|
||||
// Use LazyVGrid for all cases - more predictable layout behavior
|
||||
LazyVGrid(
|
||||
columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: min(graphCount, 3)),
|
||||
spacing: 12
|
||||
) {
|
||||
ForEach(0..<graphCount, id: \.self) { index in
|
||||
graphView(for: availableGraphs[index])
|
||||
.frame(maxWidth: .infinity)
|
||||
UnifiedStatsCard(graphData: availableGraphs[index])
|
||||
.transition(.asymmetric(
|
||||
insertion: .scale.combined(with: .opacity),
|
||||
removal: .scale.combined(with: .opacity)
|
||||
insertion: .scale.combined(with: .opacity).animation(.easeInOut(duration: 0.4)),
|
||||
removal: .scale.combined(with: .opacity).animation(.easeInOut(duration: 0.4))
|
||||
))
|
||||
}
|
||||
}
|
||||
.animation(.easeInOut(duration: 0.25), value: graphCount)
|
||||
|
||||
case 4:
|
||||
// 2x2 quadrants for 4 graphs
|
||||
VStack(spacing: 12) {
|
||||
HStack(spacing: 12) {
|
||||
graphView(for: availableGraphs[0])
|
||||
.frame(maxWidth: .infinity)
|
||||
.transition(.asymmetric(
|
||||
insertion: .scale.combined(with: .opacity),
|
||||
removal: .scale.combined(with: .opacity)
|
||||
))
|
||||
graphView(for: availableGraphs[1])
|
||||
.frame(maxWidth: .infinity)
|
||||
.transition(.asymmetric(
|
||||
insertion: .scale.combined(with: .opacity),
|
||||
removal: .scale.combined(with: .opacity)
|
||||
))
|
||||
}
|
||||
HStack(spacing: 12) {
|
||||
graphView(for: availableGraphs[2])
|
||||
.frame(maxWidth: .infinity)
|
||||
.transition(.asymmetric(
|
||||
insertion: .scale.combined(with: .opacity),
|
||||
removal: .scale.combined(with: .opacity)
|
||||
))
|
||||
graphView(for: availableGraphs[3])
|
||||
.frame(maxWidth: .infinity)
|
||||
.transition(.asymmetric(
|
||||
insertion: .scale.combined(with: .opacity),
|
||||
removal: .scale.combined(with: .opacity)
|
||||
))
|
||||
}
|
||||
}
|
||||
.animation(.easeInOut(duration: 0.25), value: graphCount)
|
||||
|
||||
case 5:
|
||||
// 3 on top, 2 on bottom (taking half space each)
|
||||
VStack(spacing: 12) {
|
||||
// Top row: 3 graphs
|
||||
HStack(spacing: 12) {
|
||||
graphView(for: availableGraphs[0])
|
||||
.frame(maxWidth: .infinity)
|
||||
.transition(.asymmetric(
|
||||
insertion: .scale.combined(with: .opacity),
|
||||
removal: .scale.combined(with: .opacity)
|
||||
))
|
||||
graphView(for: availableGraphs[1])
|
||||
.frame(maxWidth: .infinity)
|
||||
.transition(.asymmetric(
|
||||
insertion: .scale.combined(with: .opacity),
|
||||
removal: .scale.combined(with: .opacity)
|
||||
))
|
||||
graphView(for: availableGraphs[2])
|
||||
.frame(maxWidth: .infinity)
|
||||
.transition(.asymmetric(
|
||||
insertion: .scale.combined(with: .opacity),
|
||||
removal: .scale.combined(with: .opacity)
|
||||
))
|
||||
}
|
||||
// Bottom row: 2 graphs centered
|
||||
HStack(spacing: 12) {
|
||||
Spacer()
|
||||
graphView(for: availableGraphs[3])
|
||||
.frame(maxWidth: .infinity)
|
||||
.transition(.asymmetric(
|
||||
insertion: .scale.combined(with: .opacity),
|
||||
removal: .scale.combined(with: .opacity)
|
||||
))
|
||||
graphView(for: availableGraphs[4])
|
||||
.frame(maxWidth: .infinity)
|
||||
.transition(.asymmetric(
|
||||
insertion: .scale.combined(with: .opacity),
|
||||
removal: .scale.combined(with: .opacity)
|
||||
))
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.animation(.easeInOut(duration: 0.25), value: graphCount)
|
||||
|
||||
default:
|
||||
// Fallback for more than 5 graphs (shouldn't happen with current settings)
|
||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: min(3, graphCount)), spacing: 12) {
|
||||
ForEach(0..<graphCount, id: \.self) { index in
|
||||
graphView(for: availableGraphs[index])
|
||||
.transition(.asymmetric(
|
||||
insertion: .scale.combined(with: .opacity),
|
||||
removal: .scale.combined(with: .opacity)
|
||||
))
|
||||
}
|
||||
}
|
||||
.animation(.easeInOut(duration: 0.25), value: graphCount)
|
||||
}
|
||||
.animation(.easeInOut(duration: 0.4), value: graphCount)
|
||||
}
|
||||
|
||||
// Helper function to create graph views
|
||||
// Helper function to create graph views using unified component
|
||||
@ViewBuilder
|
||||
func graphView(for graphData: GraphData) -> some View {
|
||||
if let singleData = graphData as? SingleGraphData {
|
||||
StatCard(
|
||||
title: singleData.title,
|
||||
value: singleData.value,
|
||||
data: singleData.data,
|
||||
color: singleData.color,
|
||||
icon: singleData.icon
|
||||
)
|
||||
} else if let dualData = graphData as? DualGraphData {
|
||||
DualStatCard(
|
||||
title: dualData.title,
|
||||
positiveValue: dualData.positiveValue,
|
||||
negativeValue: dualData.negativeValue,
|
||||
positiveData: dualData.positiveData,
|
||||
negativeData: dualData.negativeData,
|
||||
positiveColor: dualData.positiveColor,
|
||||
negativeColor: dualData.negativeColor,
|
||||
icon: dualData.icon
|
||||
)
|
||||
}
|
||||
UnifiedStatsCard(graphData: graphData)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
|
@ -297,17 +186,17 @@ struct NotchStatsView: View {
|
|||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding()
|
||||
} else {
|
||||
// Stats content with vertical expansion layout
|
||||
// Stats content with improved animation coordination
|
||||
ZStack(alignment: .topTrailing) {
|
||||
VStack(spacing: 12) {
|
||||
// New vertical expansion layout
|
||||
// Simplified consistent layout
|
||||
statsGridLayout
|
||||
}
|
||||
.padding(16)
|
||||
.animation(.easeInOut(duration: 0.25), value: availableGraphs.count)
|
||||
.animation(.easeInOut(duration: 0.4), value: availableGraphs.count)
|
||||
.transition(.asymmetric(
|
||||
insertion: .scale.combined(with: .opacity),
|
||||
removal: .scale.combined(with: .opacity)
|
||||
insertion: .scale.combined(with: .opacity).animation(.easeInOut(duration: 0.4)),
|
||||
removal: .scale.combined(with: .opacity).animation(.easeInOut(duration: 0.4))
|
||||
))
|
||||
|
||||
// Live indicator and controls in top-right corner
|
||||
|
|
@ -365,25 +254,24 @@ struct NotchStatsView: View {
|
|||
.onDisappear {
|
||||
// Keep monitoring running when tab is not visible
|
||||
}
|
||||
.animation(.easeInOut(duration: 0.4), value: enableStatsFeature)
|
||||
.animation(.easeInOut(duration: 0.4), value: availableGraphs.count)
|
||||
}
|
||||
}
|
||||
|
||||
struct StatCard: View {
|
||||
let title: String
|
||||
let value: String
|
||||
let data: [Double]
|
||||
let color: Color
|
||||
let icon: String
|
||||
// Unified Stats Card Component - handles both single and dual data types
|
||||
struct UnifiedStatsCard: View {
|
||||
let graphData: GraphData
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 6) {
|
||||
// Header - more compact
|
||||
// Header - consistent across all card types
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: icon)
|
||||
.foregroundColor(color)
|
||||
Image(systemName: graphData.icon)
|
||||
.foregroundColor(graphData.color)
|
||||
.font(.caption2)
|
||||
|
||||
Text(title)
|
||||
Text(graphData.title)
|
||||
.font(.caption2)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.secondary)
|
||||
|
|
@ -391,76 +279,47 @@ struct StatCard: View {
|
|||
Spacer()
|
||||
}
|
||||
|
||||
// Current value - larger and more prominent
|
||||
Text(value)
|
||||
// Values section - adapts based on graph type
|
||||
Group {
|
||||
if let singleData = graphData as? SingleGraphData {
|
||||
Text(singleData.value)
|
||||
.font(.title3)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.primary)
|
||||
.frame(height: 22) // Match the height of dual values in DualStatCard
|
||||
|
||||
// Mini graph - use more height
|
||||
MiniGraph(data: data, color: color)
|
||||
.frame(height: 50)
|
||||
}
|
||||
.padding(10)
|
||||
.background(Color(NSColor.controlBackgroundColor).opacity(0.5))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
struct DualStatCard: View {
|
||||
let title: String
|
||||
let positiveValue: String
|
||||
let negativeValue: String
|
||||
let positiveData: [Double]
|
||||
let negativeData: [Double]
|
||||
let positiveColor: Color
|
||||
let negativeColor: Color
|
||||
let icon: String
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 6) {
|
||||
// Header
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: icon)
|
||||
.foregroundColor(positiveColor)
|
||||
.font(.caption2)
|
||||
|
||||
Text(title)
|
||||
.font(.caption2)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
// Dual values - horizontal layout to match single value height
|
||||
HStack(spacing: 8) {
|
||||
Text(positiveValue)
|
||||
} else if let dualData = graphData as? DualGraphData {
|
||||
HStack(spacing: 6) {
|
||||
Text(dualData.positiveValue)
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(positiveColor)
|
||||
.foregroundColor(dualData.positiveColor)
|
||||
|
||||
Text("•")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Text(negativeValue)
|
||||
Text(dualData.negativeValue)
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(negativeColor)
|
||||
.foregroundColor(dualData.negativeColor)
|
||||
}
|
||||
.frame(height: 22) // Match the height of .title3 text in StatCard
|
||||
}
|
||||
}
|
||||
.frame(height: 22) // Fixed height for consistent card sizing
|
||||
|
||||
// Dual quadrant graph
|
||||
// Graph section - adapts based on graph type
|
||||
Group {
|
||||
if let singleData = graphData as? SingleGraphData {
|
||||
MiniGraph(data: singleData.data, color: singleData.color)
|
||||
} else if let dualData = graphData as? DualGraphData {
|
||||
DualQuadrantGraph(
|
||||
positiveData: positiveData,
|
||||
negativeData: negativeData,
|
||||
positiveColor: positiveColor,
|
||||
negativeColor: negativeColor
|
||||
positiveData: dualData.positiveData,
|
||||
negativeData: dualData.negativeData,
|
||||
positiveColor: dualData.positiveColor,
|
||||
negativeColor: dualData.negativeColor
|
||||
)
|
||||
.frame(height: 50)
|
||||
}
|
||||
}
|
||||
.frame(height: 50) // Fixed height for consistent card sizing
|
||||
}
|
||||
.padding(10)
|
||||
.background(Color(NSColor.controlBackgroundColor).opacity(0.5))
|
||||
|
|
|
|||
|
|
@ -1112,10 +1112,27 @@ struct Appearance: View {
|
|||
}
|
||||
|
||||
struct Shortcuts: View {
|
||||
@Default(.enableTimerFeature) var enableTimerFeature
|
||||
@Default(.enableClipboardManager) var enableClipboardManager
|
||||
@Default(.enableShortcuts) var enableShortcuts
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
Defaults.Toggle("Enable global keyboard shortcuts", key: .enableShortcuts)
|
||||
} header: {
|
||||
Text("General")
|
||||
} footer: {
|
||||
Text("When disabled, all keyboard shortcuts will be inactive. You can still use the UI controls.")
|
||||
.multilineTextAlignment(.trailing)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
if enableShortcuts {
|
||||
Section {
|
||||
KeyboardShortcuts.Recorder("Toggle Sneak Peek:", name: .toggleSneakPeek)
|
||||
.disabled(!enableShortcuts)
|
||||
} header: {
|
||||
Text("Media")
|
||||
} footer: {
|
||||
|
|
@ -1124,19 +1141,78 @@ struct Shortcuts: View {
|
|||
.foregroundStyle(.secondary)
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
Section {
|
||||
KeyboardShortcuts.Recorder("Toggle Notch Open:", name: .toggleNotchOpen)
|
||||
}
|
||||
Section {
|
||||
KeyboardShortcuts.Recorder("Start Demo Timer:", name: .startDemoTimer)
|
||||
.disabled(!enableShortcuts)
|
||||
} header: {
|
||||
Text("Timer")
|
||||
Text("Navigation")
|
||||
} footer: {
|
||||
Text("Starts a 5-minute demo timer to test the timer live activity feature.")
|
||||
Text("Toggle the Dynamic Island open or closed from anywhere.")
|
||||
.multilineTextAlignment(.trailing)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
Section {
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
KeyboardShortcuts.Recorder("Start Demo Timer:", name: .startDemoTimer)
|
||||
.disabled(!enableShortcuts || !enableTimerFeature)
|
||||
if !enableTimerFeature {
|
||||
Text("Timer feature is disabled")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.top, 2)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
} header: {
|
||||
Text("Timer")
|
||||
} footer: {
|
||||
Text("Starts a 5-minute demo timer to test the timer live activity feature. Only works when timer feature is enabled.")
|
||||
.multilineTextAlignment(.trailing)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
Section {
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
KeyboardShortcuts.Recorder("Clipboard History:", name: .clipboardHistoryPanel)
|
||||
.disabled(!enableShortcuts || !enableClipboardManager)
|
||||
if !enableClipboardManager {
|
||||
Text("Clipboard feature is disabled")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.top, 2)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
} header: {
|
||||
Text("Clipboard")
|
||||
} footer: {
|
||||
Text("Opens the clipboard history panel. Default is Cmd+Shift+V (similar to Windows+V on PC). Only works when clipboard feature is enabled.")
|
||||
.multilineTextAlignment(.trailing)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.caption)
|
||||
}
|
||||
} else {
|
||||
Section {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Keyboard shortcuts are disabled")
|
||||
.font(.headline)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
Text("Enable global keyboard shortcuts above to customize your shortcuts.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Shortcuts")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@ extension Defaults.Keys {
|
|||
static let clipboardHistorySize = Key<Int>("clipboardHistorySize", default: 3)
|
||||
static let showClipboardIcon = Key<Bool>("showClipboardIcon", default: true)
|
||||
|
||||
// MARK: Keyboard Shortcuts
|
||||
static let enableShortcuts = Key<Bool>("enableShortcuts", default: true)
|
||||
|
||||
// Helper to determine the default media controller based on macOS version
|
||||
static var defaultMediaController: MediaControllerType {
|
||||
if #available(macOS 15.4, *) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue