// // Constants.swift // DynamicIsland // // Created by Richard Kunkli on 2024. 10. 17.. // Modified by Hariharan Mudaliar import SwiftUI import Defaults import Lottie private let availableDirectories = FileManager .default .urls(for: .documentDirectory, in: .userDomainMask) let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let bundleIdentifier = Bundle.main.bundleIdentifier! let appVersion = "\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "") (\(Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? ""))" let temporaryDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first! let spacing: CGFloat = 16 struct CustomVisualizer: Codable, Hashable, Equatable, Defaults.Serializable { let UUID: UUID var name: String var url: URL var speed: CGFloat = 1.0 } // MARK: - Custom Idle Animation Models struct CustomIdleAnimation: Codable, Hashable, Equatable, Defaults.Serializable, Identifiable { let id: UUID var name: String var source: AnimationSource var speed: CGFloat = 1.0 var isBuiltIn: Bool = false // Track if it's bundled vs user-added init(id: UUID = UUID(), name: String, source: AnimationSource, speed: CGFloat = 1.0, isBuiltIn: Bool = false) { self.id = id self.name = name self.source = source self.speed = speed self.isBuiltIn = isBuiltIn } /// Get the effective transform config (override or default) func getTransformConfig() -> AnimationTransformConfig { let override = Defaults[.animationTransformOverrides][id.uuidString] if let override = override { print("📋 [CustomIdleAnimation] Found override for '\(name)': \(override)") } else { print("📋 [CustomIdleAnimation] No override for '\(name)', using default") } return override ?? .default } } struct AnimationTransformConfig: Codable, Hashable, Equatable, Defaults.Serializable { var scale: CGFloat = 1.0 var offsetX: CGFloat = 0 var offsetY: CGFloat = 0 var cropWidth: CGFloat = 30 var cropHeight: CGFloat = 20 var rotation: CGFloat = 0 var opacity: CGFloat = 1.0 var paddingBottom: CGFloat = 0 // Allow adjustment to fill notch from bottom var expandWithAnimation: Bool = false // Whether notch should expand horizontally with animation var loopMode: AnimationLoopMode = .loop // Loop mode for animation static let `default` = AnimationTransformConfig() } enum AnimationLoopMode: String, Codable, CaseIterable { case loop = "Loop" case playOnce = "Play Once" case autoReverse = "Auto Reverse" var lottieLoopMode: LottieLoopMode { switch self { case .loop: return .loop case .playOnce: return .playOnce case .autoReverse: return .autoReverse } } } enum AnimationSource: Codable, Hashable, Equatable { case lottieFile(URL) // Local file (in app support or bundle) case lottieURL(URL) // Remote URL case builtInFace // Original MinimalFaceFeatures var displayType: String { switch self { case .lottieFile: return "Local" case .lottieURL: return "Remote" case .builtInFace: return "Built-in" } } } enum CalendarSelectionState: Codable, Defaults.Serializable { case all case selected(Set) } enum ClipboardDisplayMode: String, CaseIterable, Codable, Defaults.Serializable { case popover = "popover" // Traditional popover attached to button case panel = "panel" // Floating panel near notch case separateTab = "separateTab" // Separate tab in Dynamic Island var displayName: String { switch self { case .popover: return "Popover" case .panel: return "Panel" case .separateTab: return "Separate Tab" } } var description: String { switch self { case .popover: return "Shows clipboard as a dropdown attached to the clipboard button" case .panel: return "Shows clipboard in a floating panel near the notch" case .separateTab: return "Shows copied items in a separate tab within the Dynamic Island (merges with Notes if enabled)" } } } enum ScreenAssistantDisplayMode: String, CaseIterable, Codable, Defaults.Serializable { case popover = "popover" // Traditional popover attached to button case panel = "panel" // Floating panel near notch var displayName: String { switch self { case .popover: return "Popover" case .panel: return "Panel" } } var description: String { switch self { case .popover: return "Shows screen assistant as a dropdown attached to the AI button" case .panel: return "Shows screen assistant in a floating panel near the notch" } } } enum ColorPickerDisplayMode: String, CaseIterable, Codable, Defaults.Serializable { case popover = "popover" // Traditional popover attached to button case panel = "panel" // Floating panel near notch var displayName: String { switch self { case .popover: return "Popover" case .panel: return "Panel" } } var description: String { switch self { case .popover: return "Shows color picker as a dropdown attached to the color picker button" case .panel: return "Shows color picker in a floating panel near the notch" } } } enum HideNotchOption: String, Defaults.Serializable { case always case nowPlayingOnly case never } // Define notification names at file scope extension Notification.Name { static let mediaControllerChanged = Notification.Name("mediaControllerChanged") } // Media controller types for selection in settings enum MediaControllerType: String, CaseIterable, Identifiable, Defaults.Serializable { case nowPlaying = "Now Playing" case appleMusic = "Apple Music" case spotify = "Spotify" case youtubeMusic = "Youtube Music" var id: String { self.rawValue } } // Sneak peek styles for selection in settings enum SneakPeekStyle: String, CaseIterable, Identifiable, Defaults.Serializable { case standard = "Default" case inline = "Inline" var id: String { self.rawValue } } enum ProgressBarStyle: String, CaseIterable, Identifiable, Defaults.Serializable { case hierarchical = "Hierarchical" case gradient = "Gradient" case segmented = "Segmented" var id: String { self.rawValue } } enum MusicAuxiliaryControl: String, CaseIterable, Identifiable, Defaults.Serializable { case shuffle case repeatMode case mediaOutput case lyrics var id: String { rawValue } var displayName: String { switch self { case .shuffle: return "Shuffle" case .repeatMode: return "Repeat" case .mediaOutput: return "Media Output" case .lyrics: return "Lyrics" } } var symbolName: String { switch self { case .shuffle: return "shuffle" case .repeatMode: return "repeat" case .mediaOutput: return "laptopcomputer" case .lyrics: return "quote.bubble" } } static func alternative( excluding control: MusicAuxiliaryControl, preferring candidate: MusicAuxiliaryControl? = nil ) -> MusicAuxiliaryControl { if let candidate, candidate != control { return candidate } return allCases.first { $0 != control } ?? .shuffle } } enum MusicSkipBehavior: String, CaseIterable, Identifiable, Defaults.Serializable { case track case tenSecond var id: String { rawValue } var displayName: String { switch self { case .track: return "Track Skip" case .tenSecond: return "±10 Seconds" } } var description: String { switch self { case .track: return "Standard previous/next track controls" case .tenSecond: return "Skip forward or backward by ten seconds" } } } enum TimerIconColorMode: String, CaseIterable, Identifiable, Defaults.Serializable { case adaptive = "Adaptive" case solid = "Solid" var id: String { rawValue } var displayName: String { switch self { case .adaptive: return "Adaptive gradient" case .solid: return "Solid colour" } } } enum TimerProgressStyle: String, CaseIterable, Identifiable, Defaults.Serializable { case bar = "Bar" case ring = "Ring" var id: String { rawValue } } enum ReminderPresentationStyle: String, CaseIterable, Identifiable, Defaults.Serializable { case ringCountdown = "Ring" case digital = "Digital" case minutes = "Minutes" var id: String { rawValue } var displayName: String { switch self { case .ringCountdown: return "Ring" case .digital: return "Digital" case .minutes: return "Minutes" } } } // AI Model types for screen assistant enum AIModelProvider: String, CaseIterable, Identifiable, Defaults.Serializable { case gemini = "Gemini" case openai = "OpenAI GPT" case claude = "Claude" case local = "Local Model" var id: String { self.rawValue } var displayName: String { return self.rawValue } var description: String { switch self { case .gemini: return "Google's Gemini AI with multimodal capabilities" case .openai: return "OpenAI's GPT models with advanced reasoning" case .claude: return "Anthropic's Claude with strong analytical skills" case .local: return "Local AI model (Ollama or similar)" } } var supportedModels: [AIModel] { switch self { case .gemini: return [ // Gemini 2.5 Models (Latest) AIModel(id: "gemini-2.5-pro", name: "Gemini 2.5 Pro", supportsThinking: true), AIModel(id: "gemini-2.5-flash", name: "Gemini 2.5 Flash", supportsThinking: true), AIModel(id: "gemini-2.5-flash-lite", name: "Gemini 2.5 Flash-Lite", supportsThinking: false), AIModel(id: "gemini-2.5-flash-live", name: "Gemini 2.5 Flash Live", supportsThinking: false), AIModel(id: "gemini-2.5-flash-native-audio", name: "Gemini 2.5 Flash Native Audio", supportsThinking: true), // Gemini 2.0 Models AIModel(id: "gemini-2.0-flash", name: "Gemini 2.0 Flash", supportsThinking: false), AIModel(id: "gemini-2.0-flash-lite", name: "Gemini 2.0 Flash-Lite", supportsThinking: false), AIModel(id: "gemini-2.0-flash-live", name: "Gemini 2.0 Flash Live", supportsThinking: false), // Legacy 1.5 Models (for compatibility) AIModel(id: "gemini-1.5-pro", name: "Gemini 1.5 Pro", supportsThinking: false), AIModel(id: "gemini-1.5-flash", name: "Gemini 1.5 Flash", supportsThinking: false) ] case .openai: return [ AIModel(id: "gpt-4o", name: "GPT-4o", supportsThinking: false), AIModel(id: "gpt-4o-mini", name: "GPT-4o Mini", supportsThinking: false), AIModel(id: "o1-preview", name: "o1 Preview", supportsThinking: true), AIModel(id: "o1-mini", name: "o1 Mini", supportsThinking: true) ] case .claude: return [ AIModel(id: "claude-3-5-sonnet", name: "Claude 3.5 Sonnet", supportsThinking: false), AIModel(id: "claude-3-haiku", name: "Claude 3 Haiku", supportsThinking: false) ] case .local: return [ AIModel(id: "llama3.2", name: "Llama 3.2", supportsThinking: false), AIModel(id: "qwen2.5", name: "Qwen 2.5", supportsThinking: false) ] } } } struct AIModel: Codable, Identifiable, Defaults.Serializable { let id: String let name: String let supportsThinking: Bool var displayName: String { return name + (supportsThinking ? " (Thinking)" : "") } } struct NoteItem: Codable, Identifiable, Defaults.Serializable, Hashable { var id: UUID = UUID() var title: String var content: String var creationDate: Date var colorIndex: Int // 0: Yellow, 1: Blue, 2: Red, 3: Green var isPinned: Bool = false var imageFileName: String? = nil // Store filename instead of raw data // Internal property for migration private enum CodingKeys: String, CodingKey { case id, title, content, creationDate, colorIndex, isPinned, imageFileName, imageData } init(id: UUID = UUID(), title: String, content: String, creationDate: Date, colorIndex: Int, isPinned: Bool = false, imageFileName: String? = nil) { self.id = id self.title = title self.content = content self.creationDate = creationDate self.colorIndex = colorIndex self.isPinned = isPinned self.imageFileName = imageFileName } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(UUID.self, forKey: .id) title = try container.decode(String.self, forKey: .title) content = try container.decode(String.self, forKey: .content) creationDate = try container.decode(Date.self, forKey: .creationDate) colorIndex = try container.decode(Int.self, forKey: .colorIndex) isPinned = try container.decode(Bool.self, forKey: .isPinned) // Migration logic: if imageData exists but imageFileName doesn't, save it to disk if let data = try container.decodeIfPresent(Data.self, forKey: .imageData) { let fileName = "note_image_\(id.uuidString).png" let fileURL = NoteItem.noteImageDataDirectory.appendingPathComponent(fileName) try? data.write(to: fileURL) imageFileName = fileName } else { imageFileName = try container.decodeIfPresent(String.self, forKey: .imageFileName) } } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(title, forKey: .title) try container.encode(content, forKey: .content) try container.encode(creationDate, forKey: .creationDate) try container.encode(colorIndex, forKey: .colorIndex) try container.encode(isPinned, forKey: .isPinned) try container.encode(imageFileName, forKey: .imageFileName) } static let colors: [Color] = [.yellow, .blue, .red, .green, .purple, .orange] // Directory for storing note image files static let noteImageDataDirectory: URL = { let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let notesDir = documentsPath.appendingPathComponent("NoteImages") try? FileManager.default.createDirectory(at: notesDir, withIntermediateDirectories: true) return notesDir }() var color: Color { if colorIndex >= 0 && colorIndex < NoteItem.colors.count { return NoteItem.colors[colorIndex] } return .yellow } // Helper to get image data from file func getImageData() -> Data? { guard let fileName = imageFileName else { return nil } let fileURL = NoteItem.noteImageDataDirectory.appendingPathComponent(fileName) return try? Data(contentsOf: fileURL) } } extension Defaults.Keys { // MARK: General static let menubarIcon = Key("menubarIcon", default: true) static let showOnAllDisplays = Key("showOnAllDisplays", default: false) static let automaticallySwitchDisplay = Key("automaticallySwitchDisplay", default: true) static let releaseName = Key("releaseName", default: "Maldives") static let hideDynamicIslandFromScreenCapture = Key("hideDynamicIslandFromScreenCapture", default: false) // MARK: Behavior static let minimumHoverDuration = Key("minimumHoverDuration", default: 0.3) static let enableHaptics = Key("enableHaptics", default: true) static let openNotchOnHover = Key("openNotchOnHover", default: true) static let extendHoverArea = Key("extendHoverArea", default: false) static let notchHeightMode = Key( "notchHeightMode", default: WindowHeightMode.matchRealNotchSize ) static let nonNotchHeightMode = Key( "nonNotchHeightMode", default: WindowHeightMode.matchMenuBar ) static let nonNotchHeight = Key("nonNotchHeight", default: 32) static let notchHeight = Key("notchHeight", default: 32) static let openNotchWidth = Key("openNotchWidth", default: 640) //static let openLastTabByDefault = Key("openLastTabByDefault", default: false) // MARK: Appearance static let showEmojis = Key("showEmojis", default: false) //static let alwaysShowTabs = Key("alwaysShowTabs", default: true) static let showMirror = Key("showMirror", default: false) static let mirrorShape = Key("mirrorShape", default: MirrorShapeEnum.rectangle) static let settingsIconInNotch = Key("settingsIconInNotch", default: true) static let lightingEffect = Key("lightingEffect", default: true) static let accentColor = Key("accentColor", default: Color.blue) static let enableShadow = Key("enableShadow", default: true) static let cornerRadiusScaling = Key("cornerRadiusScaling", default: true) static let useModernCloseAnimation = Key("useModernCloseAnimation", default: true) static let showNotHumanFace = Key("showNotHumanFace", default: false) static let customIdleAnimations = Key<[CustomIdleAnimation]>("customIdleAnimations", default: []) static let selectedIdleAnimation = Key("selectedIdleAnimation", default: nil) static let animationTransformOverrides = Key<[String: AnimationTransformConfig]>("animationTransformOverrides", default: [:]) static let tileShowLabels = Key("tileShowLabels", default: false) static let showCalendar = Key("showCalendar", default: true) static let hideCompletedReminders = Key("hideCompletedReminders", default: true) static let hideAllDayEvents = Key("hideAllDayEvents", default: false) static let sliderColor = Key( "sliderUseAlbumArtColor", default: SliderColorEnum.white ) static let playerColorTinting = Key("playerColorTinting", default: true) static let useMusicVisualizer = Key("useMusicVisualizer", default: true) static let customVisualizers = Key<[CustomVisualizer]>("customVisualizers", default: []) static let selectedVisualizer = Key("selectedVisualizer", default: nil) // MARK: Gestures static let enableGestures = Key("enableGestures", default: true) static let closeGestureEnabled = Key("closeGestureEnabled", default: true) static let gestureSensitivity = Key("gestureSensitivity", default: 200.0) // MARK: Media playback static let coloredSpectrogram = Key("coloredSpectrogram", default: true) static let enableSneakPeek = Key("enableSneakPeek", default: false) static let sneakPeekStyles = Key("sneakPeekStyles", default: .standard) static let showSneakPeekOnTrackChange = Key("showSneakPeekOnTrackChange", default: true) static let enableFullscreenMediaDetection = Key("enableFullscreenMediaDetection", default: true) static let waitInterval = Key("waitInterval", default: 3) static let showShuffleAndRepeat = Key("showShuffleAndRepeat", default: true) static let showMediaOutputControl = Key("showMediaOutputControl", default: false) static let musicAuxLeftControl = Key("musicAuxLeftControl", default: .shuffle) static let musicAuxRightControl = Key("musicAuxRightControl", default: .repeatMode) static let didMigrateMusicAuxControls = Key("didMigrateMusicAuxControls", default: false) static let musicControlSlots = Key<[MusicControlButton]>("musicControlSlots", default: MusicControlButton.defaultLayout) static let didMigrateMusicControlSlots = Key("didMigrateMusicControlSlots", default: false) static let musicSkipBehavior = Key("musicSkipBehavior", default: .track) static let musicControlWindowEnabled = Key("musicControlWindowEnabled", default: false) // Enable lock screen media widget (shows the standalone panel when screen is locked) static let enableLockScreenMediaWidget = Key("enableLockScreenMediaWidget", default: true) static let enableLockScreenWeatherWidget = Key("enableLockScreenWeatherWidget", default: true) static let enableLockScreenFocusWidget = Key("enableLockScreenFocusWidget", default: true) static let enableLockScreenReminderWidget = Key("enableLockScreenReminderWidget", default: true) static let enableLockScreenTimerWidget = Key("enableLockScreenTimerWidget", default: true) static let lockScreenWeatherRefreshInterval = Key("lockScreenWeatherRefreshInterval", default: 30 * 60) static let lockScreenWeatherShowsLocation = Key("lockScreenWeatherShowsLocation", default: true) static let lockScreenWeatherShowsCharging = Key("lockScreenWeatherShowsCharging", default: true) static let lockScreenWeatherShowsChargingPercentage = Key("lockScreenWeatherShowsChargingPercentage", default: true) static let lockScreenWeatherShowsBluetooth = Key("lockScreenWeatherShowsBluetooth", default: true) static let lockScreenWeatherShowsBatteryGauge = Key("lockScreenWeatherShowsBatteryGauge", default: true) static let lockScreenWeatherBatteryUsesLaptopSymbol = Key("lockScreenWeatherBatteryUsesLaptopSymbol", default: true) static let lockScreenWeatherWidgetStyle = Key("lockScreenWeatherWidgetStyle", default: .circular) static let lockScreenWeatherTemperatureUnit = Key("lockScreenWeatherTemperatureUnit", default: .celsius) static let lockScreenWeatherShowsAQI = Key("lockScreenWeatherShowsAQI", default: true) static let lockScreenWeatherAQIScale = Key("lockScreenWeatherAQIScale", default: .us) static let lockScreenWeatherUsesGaugeTint = Key("lockScreenWeatherUsesGaugeTint", default: false) static let lockScreenWeatherProviderSource = Key("lockScreenWeatherProviderSource", default: .openMeteo) static let lockScreenWeatherVerticalOffset = Key("lockScreenWeatherVerticalOffset", default: 0) static let lockScreenMusicVerticalOffset = Key("lockScreenMusicVerticalOffset", default: 0) static let lockScreenTimerVerticalOffset = Key("lockScreenTimerVerticalOffset", default: 0) static let lockScreenGlassStyle = Key("lockScreenGlassStyle", default: .liquid) static let lockScreenShowAppIcon = Key("lockScreenShowAppIcon", default: false) static let lockScreenPanelShowsBorder = Key("lockScreenPanelShowsBorder", default: false) static let lockScreenPanelUsesBlur = Key("lockScreenPanelUsesBlur", default: true) static let lockScreenTimerWidgetUsesBlur = Key("lockScreenTimerWidgetUsesBlur", default: false) static let lockScreenReminderChipStyle = Key("lockScreenReminderChipStyle", default: .eventColor) // MARK: Battery static let showPowerStatusNotifications = Key("showPowerStatusNotifications", default: true) static let showBatteryIndicator = Key("showBatteryIndicator", default: true) static let showBatteryPercentage = Key("showBatteryPercentage", default: true) static let showPowerStatusIcons = Key("showPowerStatusIcons", default: true) static let playLowBatteryAlertSound = Key("playLowBatteryAlertSound", default: true) // MARK: Downloads static let enableDownloadListener = Key("enableDownloadListener", default: true) static let enableSafariDownloads = Key("enableSafariDownloads", default: true) static let selectedDownloadIndicatorStyle = Key("selectedDownloadIndicatorStyle", default: DownloadIndicatorStyle.progress) static let selectedDownloadIconStyle = Key("selectedDownloadIconStyle", default: DownloadIconStyle.onlyAppIcon) // MARK: HUD static let inlineHUD = Key("inlineHUD", default: true) static let progressBarStyle = Key("progressBarStyle", default: .hierarchical) // Legacy support - keeping for backward compatibility static let enableGradient = Key("enableGradient", default: false) static let systemEventIndicatorShadow = Key("systemEventIndicatorShadow", default: false) static let systemEventIndicatorUseAccent = Key("systemEventIndicatorUseAccent", default: false) static let showProgressPercentages = Key("showProgressPercentages", default: true) // MARK: Shelf static let dynamicShelf = Key("dynamicShelf", default: true) static let openShelfByDefault = Key("openShelfByDefault", default: true) static let quickShareProvider = Key("quickShareProvider", default: "AirDrop") static let copyOnDrag = Key("copyOnDrag", default: false) static let autoRemoveShelfItems = Key("autoRemoveShelfItems", default: false) static let expandedDragDetection = Key("expandedDragDetection", default: true) // MARK: Calendar static let calendarSelectionState = Key("calendarSelectionState", default: .all) static let showFullEventTitles = Key("showFullEventTitles", default: false) static let autoScrollToNextEvent = Key("autoScrollToNextEvent", default: true) // MARK: Fullscreen Media Detection static let alwaysHideInFullscreen = Key("alwaysHideInFullscreen", default: false) static let hideNotchOption = Key("hideNotchOption", default: .nowPlayingOnly) // MARK: Wobble Animation static let enableWobbleAnimation = Key("enableWobbleAnimation", default: false) // MARK: Media Controller static let mediaController = Key("mediaController", default: defaultMediaController) // MARK: Bluetooth Audio Devices static let showBluetoothDeviceConnections = Key("showBluetoothDeviceConnections", default: true) static let useColorCodedBatteryDisplay = Key("useColorCodedBatteryDisplay", default: true) static let useColorCodedVolumeDisplay = Key("useColorCodedVolumeDisplay", default: true) static let useSmoothColorGradient = Key("useSmoothColorGradient", default: true) static let useCircularBluetoothBatteryIndicator = Key("useCircularBluetoothBatteryIndicator", default: true) static let showBluetoothBatteryPercentageText = Key("showBluetoothBatteryPercentageText", default: false) static let showBluetoothDeviceNameMarquee = Key("showBluetoothDeviceNameMarquee", default: false) // MARK: Stats Feature static let enableStatsFeature = Key("enableStatsFeature", default: false) static let autoStartStatsMonitoring = Key("autoStartStatsMonitoring", default: true) static let statsStopWhenNotchCloses = Key("statsStopWhenNotchCloses", default: false) static let statsUpdateInterval = Key("statsUpdateInterval", default: 1.0) static let showCpuGraph = Key("showCpuGraph", default: true) static let showMemoryGraph = Key("showMemoryGraph", default: true) static let showGpuGraph = Key("showGpuGraph", default: true) static let showNetworkGraph = Key("showNetworkGraph", default: false) static let showDiskGraph = Key("showDiskGraph", default: false) // MARK: Timer Feature static let enableTimerFeature = Key("enableTimerFeature", default: true) static let timerDisplayMode = Key("timerDisplayMode", default: .tab) static let timerPresets = Key<[TimerPreset]>("timerPresets", default: TimerPreset.defaultPresets) static let showTimerPresetsInNotchTab = Key("showTimerPresetsInNotchTab", default: true) static let timerIconColorMode = Key("timerIconColorMode", default: .adaptive) static let timerSolidColor = Key("timerSolidColor", default: .blue) static let timerShowsCountdown = Key("timerShowsCountdown", default: true) static let timerShowsLabel = Key("timerShowsLabel", default: false) static let timerShowsProgress = Key("timerShowsProgress", default: true) static let timerProgressStyle = Key("timerProgressStyle", default: .bar) static let mirrorSystemTimer = Key("mirrorSystemTimer", default: true) // MARK: Reminder Live Activity static let enableReminderLiveActivity = Key("enableReminderLiveActivity", default: true) static let reminderPresentationStyle = Key("reminderPresentationStyle", default: .ringCountdown) static let reminderLeadTime = Key("reminderLeadTime", default: 5) static let reminderSneakPeekDuration = Key("reminderSneakPeekDuration", default: 5) static let timerControlWindowEnabled = Key("timerControlWindowEnabled", default: true) // MARK: ColorPicker Feature static let enableColorPickerFeature = Key("enableColorPickerFeature", default: true) static let showColorFormats = Key("showColorFormats", default: true) static let colorPickerDisplayMode = Key("colorPickerDisplayMode", default: .panel) static let colorHistorySize = Key("colorHistorySize", default: 10) static let showColorPickerIcon = Key("showColorPickerIcon", default: true) // MARK: Clipboard Feature static let enableClipboardManager = Key("enableClipboardManager", default: true) static let clipboardHistorySize = Key("clipboardHistorySize", default: 3) static let showClipboardIcon = Key("showClipboardIcon", default: true) static let clipboardDisplayMode = Key("clipboardDisplayMode", default: .panel) // MARK: Screen Assistant Feature static let enableScreenAssistant = Key("enableScreenAssistant", default: true) static let screenAssistantDisplayMode = Key("screenAssistantDisplayMode", default: .panel) static let geminiApiKey = Key("geminiApiKey", default: "") static let openaiApiKey = Key("openaiApiKey", default: "") static let claudeApiKey = Key("claudeApiKey", default: "") static let selectedAIProvider = Key("selectedAIProvider", default: .gemini) static let selectedAIModel = Key("selectedAIModel", default: nil) static let enableThinkingMode = Key("enableThinkingMode", default: false) static let localModelEndpoint = Key("localModelEndpoint", default: "http://localhost:11434") // MARK: Keyboard Shortcuts static let enableShortcuts = Key("enableShortcuts", default: true) // MARK: System HUD Feature static let enableSystemHUD = Key("enableSystemHUD", default: true) static let enableVolumeHUD = Key("enableVolumeHUD", default: true) static let enableBrightnessHUD = Key("enableBrightnessHUD", default: true) static let enableKeyboardBacklightHUD = Key("enableKeyboardBacklightHUD", default: true) static let systemHUDSensitivity = Key("systemHUDSensitivity", default: 5) // MARK: Custom OSD Window Feature static let enableCustomOSD = Key("enableCustomOSD", default: false) static let hasSeenOSDAlphaWarning = Key("hasSeenOSDAlphaWarning", default: false) static let enableOSDVolume = Key("enableOSDVolume", default: true) static let enableOSDBrightness = Key("enableOSDBrightness", default: true) static let enableOSDKeyboardBacklight = Key("enableOSDKeyboardBacklight", default: true) static let osdMaterial = Key("osdMaterial", default: .frosted) static let osdIconColorStyle = Key("osdIconColorStyle", default: .white) // MARK: Screen Recording Detection Feature static let enableScreenRecordingDetection = Key("enableScreenRecordingDetection", default: true) static let showRecordingIndicator = Key("showRecordingIndicator", default: true) // Polling removed - now uses event-driven private API detection (CGSIsScreenWatcherPresent) // static let enableScreenRecordingPolling = Key("enableScreenRecordingPolling", default: false) // MARK: Focus / Do Not Disturb Detection static let enableDoNotDisturbDetection = Key("enableDoNotDisturbDetection", default: true) static let showDoNotDisturbIndicator = Key("showDoNotDisturbIndicator", default: true) static let showDoNotDisturbLabel = Key("showDoNotDisturbLabel", default: true) static let focusIndicatorNonPersistent = Key("focusIndicatorNonPersistent", default: false) // MARK: Privacy Indicators (Camera & Microphone Detection) static let enableCameraDetection = Key("enableCameraDetection", default: true) static let enableMicrophoneDetection = Key("enableMicrophoneDetection", default: true) // MARK: Lock Screen Features static let enableLockScreenLiveActivity = Key("enableLockScreenLiveActivity", default: true) static let enableLockSounds = Key("enableLockSounds", default: true) // MARK: ImageService static let didClearLegacyURLCacheV1 = Key("didClearLegacyURLCacheV1", default: false) // MARK: Minimalistic UI Mode static let enableMinimalisticUI = Key("enableMinimalisticUI", default: false) // MARK: Lyrics Feature static let enableLyrics = Key("enableLyrics", default: true) // MARK: Notes Feature static let enableNotes = Key("enableNotes", default: false) static let enableNotePinning = Key("enableNotePinning", default: true) static let enableNoteSearch = Key("enableNoteSearch", default: false) static let enableNoteColorFiltering = Key("enableNoteColorFiltering", default: false) static let enableCreateFromClipboard = Key("enableCreateFromClipboard", default: true) static let enableNoteCharCount = Key("enableNoteCharCount", default: true) static let savedNotes = Key<[NoteItem]>("savedNotes", default: []) // Helper to determine the default media controller based on macOS version static var defaultMediaController: MediaControllerType { if #available(macOS 15.4, *) { return .appleMusic } else { return .nowPlaying } } // Migration helper to convert from legacy enableGradient Boolean to new ProgressBarStyle enum static func migrateProgressBarStyle() { // Check if migration is needed by seeing if the old Boolean was set to gradient let wasGradientEnabled = Defaults[.enableGradient] // Only migrate if we're still using the default hierarchical value but gradient was enabled if wasGradientEnabled && Defaults[.progressBarStyle] == .hierarchical { Defaults[.progressBarStyle] = .gradient } } static func migrateMusicAuxControls() { if Defaults[.didMigrateMusicAuxControls] == false { if Defaults[.showMediaOutputControl] { Defaults[.musicAuxRightControl] = .mediaOutput } Defaults[.didMigrateMusicAuxControls] = true } normalizeMusicAuxControls() } static func migrateMusicControlSlots() { guard Defaults[.didMigrateMusicControlSlots] == false else { return } let allowMediaOutput = Defaults[.showMediaOutputControl] let baseLayout: [MusicControlButton] if Defaults[.showShuffleAndRepeat] { var slots = MusicControlButton.defaultLayout let left = MusicControlButton(auxiliaryControl: Defaults[.musicAuxLeftControl]) let right = MusicControlButton(auxiliaryControl: Defaults[.musicAuxRightControl]) slots[0] = left slots[4] = right baseLayout = slots } else { baseLayout = MusicControlButton.minimalLayout } Defaults[.musicControlSlots] = baseLayout.normalized(allowingMediaOutput: allowMediaOutput) Defaults[.didMigrateMusicControlSlots] = true } private static func normalizeMusicAuxControls() { guard Defaults[.musicAuxLeftControl] == Defaults[.musicAuxRightControl] else { return } let current = Defaults[.musicAuxLeftControl] let fallback = MusicAuxiliaryControl.alternative(excluding: current) Defaults[.musicAuxRightControl] = fallback } }