// // 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 var displayName: String { switch self { case .popover: return "Popover" case .panel: return "Panel" } } 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" } } } 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 } } // 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)" : "") } } 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: "alpha 0.0.1") static let hidePanelsFromScreenCapture = Key("hideFromScreenCapture", 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 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: false) 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 enableFullscreenMediaDetection = Key("enableFullscreenMediaDetection", default: true) static let waitInterval = Key("waitInterval", default: 3) static let showShuffleAndRepeat = Key("showShuffleAndRepeat", default: false) // Enable lock screen media widget (shows the standalone panel when screen is locked) static let enableLockScreenMediaWidget = Key("enableLockScreenMediaWidget", default: true) static let lockScreenGlassStyle = Key("lockScreenGlassStyle", default: .liquid) static let lockScreenShowAppIcon = Key("lockScreenShowAppIcon", default: false) static let lockScreenPanelShowsBorder = Key("lockScreenPanelShowsBorder", default: true) static let lockScreenPanelUsesBlur = Key("lockScreenPanelUsesBlur", default: true) // 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) // 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: false) 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) // MARK: Shelf static let dynamicShelf = Key("dynamicShelf", default: true) static let openShelfByDefault = Key("openShelfByDefault", default: true) // MARK: Calendar static let calendarSelectionState = Key("calendarSelectionState", default: .all) // 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: false) static let useSmoothColorGradient = Key("useSmoothColorGradient", default: true) // MARK: Stats Feature static let enableStatsFeature = Key("enableStatsFeature", default: false) static let autoStartStatsMonitoring = Key("autoStartStatsMonitoring", default: true) 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) static let showStatsPanel = Key("showStatsPanel", default: true) // MARK: Timer Feature static let enableTimerFeature = Key("enableTimerFeature", 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: false) 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: 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: 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) // MARK: ImageService static let didClearLegacyURLCacheV1 = Key("didClearLegacyURLCacheV1", default: false) // MARK: Minimalistic UI Mode static let enableMinimalisticUI = Key("enableMinimalisticUI", default: false) // 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 } } }