Atoll/DynamicIsland/models/Constants.swift
Hariharan Mudaliar 5accee5d02
Update onboarding
2026-03-05 16:24:06 +05:30

1160 lines
55 KiB
Swift

/*
* Atoll (DynamicIsland)
* Copyright (C) 2024-2026 Atoll Contributors
*
* Originally from boring.notch project
* Modified and adapted for Atoll (DynamicIsland)
* See NOTICE for details.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import SwiftUI
import Defaults
import Lottie
import Foundation
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
}
struct CustomAppIcon: Codable, Hashable, Equatable, Defaults.Serializable, Identifiable {
let id: UUID
var name: String
var fileName: String
var addedAt: Date
init(id: UUID = UUID(), name: String, fileName: String, addedAt: Date = .now) {
self.id = id
self.name = name
self.fileName = fileName
self.addedAt = addedAt
}
static let iconDirectory: URL = {
let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
let dir = base.appendingPathComponent("DynamicIsland", isDirectory: true)
.appendingPathComponent("AppIcons", isDirectory: true)
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
return dir
}()
var fileURL: URL {
Self.iconDirectory.appendingPathComponent(fileName)
}
}
// 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"
}
}
}
// MARK: - Extension Authorization Models
enum ExtensionPermissionScope: String, CaseIterable, Codable, Defaults.Serializable {
case liveActivities
case lockScreenWidgets
case notchExperiences
var displayName: String {
switch self {
case .liveActivities: return "Live Activities"
case .lockScreenWidgets: return "Lock Screen Widgets"
case .notchExperiences: return "Notch Experiences"
}
}
}
enum ExtensionAuthorizationStatus: String, CaseIterable, Codable, Defaults.Serializable {
case pending
case authorized
case denied
case revoked
var isActive: Bool {
switch self {
case .authorized: return true
case .pending, .denied, .revoked: return false
}
}
}
struct ExtensionAuthorizationEntry: Codable, Defaults.Serializable, Identifiable, Hashable {
let bundleIdentifier: String
var appName: String
var status: ExtensionAuthorizationStatus
var allowedScopes: Set<ExtensionPermissionScope>
var requestedAt: Date
var grantedAt: Date?
var lastActivityAt: Date?
var lastDeniedReason: String?
var notes: String?
var id: String { bundleIdentifier }
init(
bundleIdentifier: String,
appName: String,
status: ExtensionAuthorizationStatus,
allowedScopes: Set<ExtensionPermissionScope> = Set(ExtensionPermissionScope.allCases),
requestedAt: Date = .now,
grantedAt: Date? = nil,
lastActivityAt: Date? = nil,
lastDeniedReason: String? = nil,
notes: String? = nil
) {
self.bundleIdentifier = bundleIdentifier
self.appName = appName
self.status = status
self.allowedScopes = allowedScopes
self.requestedAt = requestedAt
self.grantedAt = grantedAt
self.lastActivityAt = lastActivityAt
self.lastDeniedReason = lastDeniedReason
self.notes = notes
}
var isAuthorized: Bool { status.isActive }
}
struct ExtensionRateLimitRecord: Codable, Defaults.Serializable, Hashable, Identifiable {
let bundleIdentifier: String
var activityTimestamps: [Date]
var widgetTimestamps: [Date]
var notchExperienceTimestamps: [Date]
var id: String { bundleIdentifier }
init(
bundleIdentifier: String,
activityTimestamps: [Date] = [],
widgetTimestamps: [Date] = [],
notchExperienceTimestamps: [Date] = []
) {
self.bundleIdentifier = bundleIdentifier
self.activityTimestamps = activityTimestamps
self.widgetTimestamps = widgetTimestamps
self.notchExperienceTimestamps = notchExperienceTimestamps
}
private enum CodingKeys: String, CodingKey {
case bundleIdentifier
case activityTimestamps
case widgetTimestamps
case notchExperienceTimestamps
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
bundleIdentifier = try container.decode(String.self, forKey: .bundleIdentifier)
activityTimestamps = try container.decodeIfPresent([Date].self, forKey: .activityTimestamps) ?? []
widgetTimestamps = try container.decodeIfPresent([Date].self, forKey: .widgetTimestamps) ?? []
notchExperienceTimestamps = try container.decodeIfPresent([Date].self, forKey: .notchExperienceTimestamps) ?? []
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(bundleIdentifier, forKey: .bundleIdentifier)
try container.encode(activityTimestamps, forKey: .activityTimestamps)
try container.encode(widgetTimestamps, forKey: .widgetTimestamps)
try container.encode(notchExperienceTimestamps, forKey: .notchExperienceTimestamps)
}
}
enum CalendarSelectionState: Codable, Defaults.Serializable {
case all
case selected(Set<String>)
}
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 String(localized: "Popover")
case .panel: return String(localized: "Panel")
case .separateTab: return String(localized: "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 String(localized: "Popover")
case .panel: return String(localized: "Panel")
}
}
var description: String {
switch self {
case .popover: return String(localized: "Shows screen assistant as a dropdown attached to the AI button")
case .panel: return String(localized: "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 }
var localizedName: String {
switch self {
case .nowPlaying: return String(localized: "Now Playing")
case .appleMusic: return String(localized: "Apple Music")
case .spotify: return String(localized: "Spotify")
case .youtubeMusic: return String(localized: "Youtube Music")
}
}
}
// 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 }
var LocalizedName : String {
switch self {
case .standard: return String(localized: "Default")
case .inline: return String(localized: "Inline")
}
}
}
enum CapsLockIndicatorTintMode: String, CaseIterable, Identifiable, Defaults.Serializable {
case green
case accent
case white
var id: String { rawValue }
var displayName: String {
switch self {
case .green:
return String(localized: "Green")
case .accent:
return String(localized: "Accent")
case .white:
return String(localized: "White")
}
}
var color: Color {
switch self {
case .green:
return .green
case .accent:
return .accentColor
case .white:
return .white
}
}
}
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 String(localized: "Track Skip")
case .tenSecond:
return String(localized: "±10 Seconds")
}
}
var description: String {
switch self {
case .track:
return String(localized: "Standard previous/next track controls")
case .tenSecond:
return String(localized: "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 String(localized:"Adaptive gradient")
case .solid: return String(localized:"Solid colour")
}
}
}
enum TimerProgressStyle: String, CaseIterable, Identifiable, Defaults.Serializable {
case bar = "Bar"
case ring = "Ring"
var id: String { rawValue }
var localizedName: String {
switch self {
case .bar: return String(localized:"Bar")
case .ring: return String(localized:"Ring")
}
}
}
enum FocusMonitoringMode: String, CaseIterable, Identifiable, Defaults.Serializable {
case withoutDevTools = "withoutDevTools"
case useDevTools = "useDevTools"
var id: String { rawValue }
var displayName: String {
switch self {
case .withoutDevTools:
return "Use without DevTools"
case .useDevTools:
return "Use DevTools"
}
}
}
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 String(localized: "Ring")
case .digital:
return String(localized: "Digital")
case .minutes:
return String(localized: "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<Bool>("menubarIcon", default: true)
static let showOnAllDisplays = Key<Bool>("showOnAllDisplays", default: false)
static let automaticallySwitchDisplay = Key<Bool>("automaticallySwitchDisplay", default: true)
static let releaseName = Key<String>("releaseName", default: "Bora Bora")
static let hideDynamicIslandFromScreenCapture = Key<Bool>("hideDynamicIslandFromScreenCapture", default: false)
// MARK: Behavior
static let minimumHoverDuration = Key<TimeInterval>("minimumHoverDuration", default: 0.3)
static let enableHaptics = Key<Bool>("enableHaptics", default: true)
static let openNotchOnHover = Key<Bool>("openNotchOnHover", default: true)
static let extendHoverArea = Key<Bool>("extendHoverArea", default: false)
static let externalDisplayStyle = Key<ExternalDisplayStyle>(
"externalDisplayStyle",
default: .notch
)
static let hideNonNotchUntilHover = Key<Bool>("hideNonNotchUntilHover", default: false)
static let notchHeightMode = Key<WindowHeightMode>(
"notchHeightMode",
default: WindowHeightMode.matchRealNotchSize
)
static let nonNotchHeightMode = Key<WindowHeightMode>(
"nonNotchHeightMode",
default: WindowHeightMode.matchMenuBar
)
static let nonNotchHeight = Key<CGFloat>("nonNotchHeight", default: 32)
static let notchHeight = Key<CGFloat>("notchHeight", default: 32)
static let openNotchWidth = Key<CGFloat>("openNotchWidth", default: 640)
//static let openLastTabByDefault = Key<Bool>("openLastTabByDefault", default: false)
// MARK: Appearance
static let showEmojis = Key<Bool>("showEmojis", default: false)
//static let alwaysShowTabs = Key<Bool>("alwaysShowTabs", default: true)
static let showMirror = Key<Bool>("showMirror", default: false)
static let mirrorShape = Key<MirrorShapeEnum>("mirrorShape", default: MirrorShapeEnum.rectangle)
static let settingsIconInNotch = Key<Bool>("settingsIconInNotch", default: true)
static let lightingEffect = Key<Bool>("lightingEffect", default: true)
static let accentColor = Key<Color>("accentColor", default: Color.blue)
static let enableShadow = Key<Bool>("enableShadow", default: true)
static let cornerRadiusScaling = Key<Bool>("cornerRadiusScaling", default: true)
static let useModernCloseAnimation = Key<Bool>("useModernCloseAnimation", default: true)
static let showNotHumanFace = Key<Bool>("showNotHumanFace", default: false)
static let customIdleAnimations = Key<[CustomIdleAnimation]>("customIdleAnimations", default: [])
static let selectedIdleAnimation = Key<CustomIdleAnimation?>("selectedIdleAnimation", default: nil)
static let animationTransformOverrides = Key<[String: AnimationTransformConfig]>("animationTransformOverrides", default: [:])
static let tileShowLabels = Key<Bool>("tileShowLabels", default: false)
static let showCalendar = Key<Bool>("showCalendar", default: true)
static let hideCompletedReminders = Key<Bool>("hideCompletedReminders", default: true)
static let hideAllDayEvents = Key<Bool>("hideAllDayEvents", default: false)
static let sliderColor = Key<SliderColorEnum>(
"sliderUseAlbumArtColor",
default: SliderColorEnum.white
)
static let playerColorTinting = Key<Bool>("playerColorTinting", default: true)
static let useMusicVisualizer = Key<Bool>("useMusicVisualizer", default: true)
static let customVisualizers = Key<[CustomVisualizer]>("customVisualizers", default: [])
static let selectedVisualizer = Key<CustomVisualizer?>("selectedVisualizer", default: nil)
static let customAppIcons = Key<[CustomAppIcon]>("customAppIcons", default: [])
static let selectedAppIconID = Key<String?>("selectedAppIconID", default: nil)
// MARK: Gestures
static let enableGestures = Key<Bool>("enableGestures", default: true)
static let closeGestureEnabled = Key<Bool>("closeGestureEnabled", default: true)
static let gestureSensitivity = Key<CGFloat>("gestureSensitivity", default: 200.0)
static let enableHorizontalMusicGestures = Key<Bool>("enableHorizontalMusicGestures", default: true)
static let musicGestureBehavior = Key<MusicSkipBehavior>("musicGestureBehavior", default: .track)
static let reverseSwipeGestures = Key<Bool>("reverseSwipeGestures", default: false)
static let reverseScrollGestures = Key<Bool>("reverseScrollGestures", default: false)
// MARK: Media playback
static let coloredSpectrogram = Key<Bool>("coloredSpectrogram", default: true)
static let enableSneakPeek = Key<Bool>("enableSneakPeek", default: false)
static let sneakPeekStyles = Key<SneakPeekStyle>("sneakPeekStyles", default: .standard)
static let showSneakPeekOnTrackChange = Key<Bool>("showSneakPeekOnTrackChange", default: true)
static let enableFullscreenMediaDetection = Key<Bool>("enableFullscreenMediaDetection", default: true)
static let enableParallaxEffect = Key<Bool>("enableParallaxEffect", default: true)
static let waitInterval = Key<Double>("waitInterval", default: 3)
static let showShuffleAndRepeat = Key<Bool>("showShuffleAndRepeat", default: true)
static let showMediaOutputControl = Key<Bool>("showMediaOutputControl", default: true)
static let musicAuxLeftControl = Key<MusicAuxiliaryControl>("musicAuxLeftControl", default: .shuffle)
static let musicAuxRightControl = Key<MusicAuxiliaryControl>("musicAuxRightControl", default: .repeatMode)
static let didMigrateMusicAuxControls = Key<Bool>("didMigrateMusicAuxControls", default: false)
static let musicControlSlots = Key<[MusicControlButton]>("musicControlSlots", default: MusicControlButton.defaultLayout)
static let didMigrateMusicControlSlots = Key<Bool>("didMigrateMusicControlSlots", default: false)
static let musicSkipBehavior = Key<MusicSkipBehavior>("musicSkipBehavior", default: .track)
static let musicControlWindowEnabled = Key<Bool>("musicControlWindowEnabled", default: false)
static let showStandardMediaControls = Key<Bool>("showStandardMediaControls", default: true)
static let cachedMusicLiveActivityPreference = Key<Bool?>("cachedMusicLiveActivityPreference", default: nil)
static let cachedLockScreenMediaWidgetPreference = Key<Bool?>("cachedLockScreenMediaWidgetPreference", default: nil)
static let cachedMusicControlWindowPreference = Key<Bool?>("cachedMusicControlWindowPreference", default: nil)
// Enable lock screen media widget (shows the standalone panel when screen is locked)
static let enableLockScreenMediaWidget = Key<Bool>("enableLockScreenMediaWidget", default: true)
static let enableLockScreenWeatherWidget = Key<Bool>("enableLockScreenWeatherWidget", default: true)
static let enableLockScreenFocusWidget = Key<Bool>("enableLockScreenFocusWidget", default: true)
static let enableLockScreenReminderWidget = Key<Bool>("enableLockScreenReminderWidget", default: true)
static let enableLockScreenTimerWidget = Key<Bool>("enableLockScreenTimerWidget", default: true)
static let lockScreenWeatherRefreshInterval = Key<TimeInterval>("lockScreenWeatherRefreshInterval", default: 30 * 60)
static let lockScreenWeatherShowsLocation = Key<Bool>("lockScreenWeatherShowsLocation", default: true)
static let lockScreenWeatherShowsSunrise = Key<Bool>("lockScreenWeatherShowsSunrise", default: true)
static let lockScreenWeatherWidgetStyle = Key<LockScreenWeatherWidgetStyle>("lockScreenWeatherWidgetStyle", default: .inline)
static let lockScreenWeatherTemperatureUnit = Key<LockScreenWeatherTemperatureUnit>("lockScreenWeatherTemperatureUnit", default: .celsius)
static let lockScreenWeatherShowsAQI = Key<Bool>("lockScreenWeatherShowsAQI", default: true)
static let lockScreenWeatherAQIScale = Key<LockScreenWeatherAirQualityScale>("lockScreenWeatherAQIScale", default: .us)
static let lockScreenWeatherUsesGaugeTint = Key<Bool>("lockScreenWeatherUsesGaugeTint", default: false)
static let lockScreenWeatherProviderSource = Key<LockScreenWeatherProviderSource>("lockScreenWeatherProviderSource", default: .openMeteo)
static let lockScreenWeatherVerticalOffset = Key<Double>("lockScreenWeatherVerticalOffset", default: 0)
static let lockScreenMusicVerticalOffset = Key<Double>("lockScreenMusicVerticalOffset", default: 0)
static let lockScreenMusicPanelWidth = Key<Double>("lockScreenMusicPanelWidth", default: 420)
static let lockScreenMusicAlbumParallaxEnabled = Key<Bool>("lockScreenMusicAlbumParallaxEnabled", default: false)
static let lockScreenTimerVerticalOffset = Key<Double>("lockScreenTimerVerticalOffset", default: 0)
static let lockScreenTimerWidgetWidth = Key<Double>("lockScreenTimerWidgetWidth", default: 420)
static let lockScreenGlassStyle = Key<LockScreenGlassStyle>("lockScreenGlassStyle", default: .liquid)
static let lockScreenGlassCustomizationMode = Key<LockScreenGlassCustomizationMode>(
"lockScreenGlassCustomizationMode",
default: .standard
)
static let lockScreenTimerGlassStyle = Key<LockScreenGlassStyle>("lockScreenTimerGlassStyle", default: .frosted)
static let lockScreenTimerGlassCustomizationMode = Key<LockScreenGlassCustomizationMode>(
"lockScreenTimerGlassCustomizationMode",
default: .standard
)
static let lockScreenMusicLiquidGlassVariant = Key<LiquidGlassVariant>(
"lockScreenMusicLiquidGlassVariant",
default: .defaultVariant
)
static let lockScreenTimerLiquidGlassVariant = Key<LiquidGlassVariant>(
"lockScreenTimerLiquidGlassVariant",
default: .defaultVariant
)
static let lockScreenShowAppIcon = Key<Bool>("lockScreenShowAppIcon", default: false)
static let lockScreenPanelShowsBorder = Key<Bool>("lockScreenPanelShowsBorder", default: false)
static let lockScreenPanelUsesBlur = Key<Bool>("lockScreenPanelUsesBlur", default: true)
static let lockScreenTimerWidgetUsesBlur = Key<Bool>("lockScreenTimerWidgetUsesBlur", default: false)
static let lockScreenReminderChipStyle = Key<LockScreenReminderChipStyle>("lockScreenReminderChipStyle", default: .eventColor)
static let lockScreenReminderWidgetHorizontalAlignment = Key<String>("lockScreenReminderWidgetHorizontalAlignment", default: "center")
static let lockScreenReminderWidgetVerticalOffset = Key<Double>("lockScreenReminderWidgetVerticalOffset", default: 0)
static let lockScreenShowCalendarEvent = Key<Bool>("lockScreenShowCalendarEvent", default: true)
static let lockScreenCalendarEventLookaheadWindow = Key<String>("lockScreenCalendarEventLookaheadWindow", default: "3h")
static let lockScreenCalendarSelectionMode = Key<String>("lockScreenCalendarSelectionMode", default: "all")
static let lockScreenSelectedCalendarIDs = Key<Set<String>>("lockScreenSelectedCalendarIDs", default: [])
static let lockScreenShowCalendarCountdown = Key<Bool>("lockScreenShowCalendarCountdown", default: true)
static let lockScreenShowCalendarEventEntireDuration = Key<Bool>("lockScreenShowCalendarEventEntireDuration", default: true)
static let lockScreenShowCalendarEventAfterStartEnabled = Key<Bool>("lockScreenShowCalendarEventAfterStartEnabled", default: false)
static let lockScreenShowCalendarEventAfterStartWindow = Key<String>("lockScreenShowCalendarEventAfterStartWindow", default: "5m")
static let lockScreenShowCalendarTimeRemaining = Key<Bool>("lockScreenShowCalendarTimeRemaining", default: true)
static let lockScreenShowCalendarStartTimeAfterBegins = Key<Bool>("lockScreenShowCalendarStartTimeAfterBegins", default: true)
static let lockScreenWeatherWidgetRowOrder = Key<String>("lockScreenWeatherWidgetRowOrder", default: "weather_calendar_focus")
// MARK: Battery
static let showPowerStatusNotifications = Key<Bool>("showPowerStatusNotifications", default: true)
static let showBatteryIndicator = Key<Bool>("showBatteryIndicator", default: BatteryActivityManager.shared.hasBattery())
static let showBatteryPercentage = Key<Bool>("showBatteryPercentage", default: true)
static let showPowerStatusIcons = Key<Bool>("showPowerStatusIcons", default: true)
static let playLowBatteryAlertSound = Key<Bool>("playLowBatteryAlertSound", default: true)
static let lockScreenBatteryShowsBatteryGauge = Key<Bool>(
"lockScreenWeatherShowsBatteryGauge",
default: BatteryActivityManager.shared.hasBattery()
)
static let lockScreenBatteryUsesLaptopSymbol = Key<Bool>("lockScreenWeatherBatteryUsesLaptopSymbol", default: true)
static let lockScreenBatteryShowsCharging = Key<Bool>("lockScreenWeatherShowsCharging", default: true)
static let lockScreenBatteryShowsChargingPercentage = Key<Bool>("lockScreenWeatherShowsChargingPercentage", default: true)
static let lockScreenBatteryShowsBluetooth = Key<Bool>("lockScreenWeatherShowsBluetooth", default: true)
// MARK: Downloads
static let enableDownloadListener = Key<Bool>("enableDownloadListener", default: true)
static let enableSafariDownloads = Key<Bool>("enableSafariDownloads", default: true)
static let selectedDownloadIndicatorStyle = Key<DownloadIndicatorStyle>("selectedDownloadIndicatorStyle", default: DownloadIndicatorStyle.progress)
static let selectedDownloadIconStyle = Key<DownloadIconStyle>("selectedDownloadIconStyle", default: DownloadIconStyle.onlyAppIcon)
// MARK: HUD
static let inlineHUD = Key<Bool>("inlineHUD", default: true)
static let progressBarStyle = Key<ProgressBarStyle>("progressBarStyle", default: .hierarchical)
// Legacy support - keeping for backward compatibility
static let enableGradient = Key<Bool>("enableGradient", default: false)
static let systemEventIndicatorShadow = Key<Bool>("systemEventIndicatorShadow", default: false)
static let systemEventIndicatorUseAccent = Key<Bool>("systemEventIndicatorUseAccent", default: false)
static let showProgressPercentages = Key<Bool>("showProgressPercentages", default: true)
// MARK: Shelf
static let dynamicShelf = Key<Bool>("dynamicShelf", default: true)
static let openShelfByDefault = Key<Bool>("openShelfByDefault", default: true)
static let quickShareProvider = Key<String>("quickShareProvider", default: "AirDrop")
static let copyOnDrag = Key<Bool>("copyOnDrag", default: false)
static let autoRemoveShelfItems = Key<Bool>("autoRemoveShelfItems", default: false)
static let expandedDragDetection = Key<Bool>("expandedDragDetection", default: true)
// MARK: Calendar
static let calendarSelectionState = Key<CalendarSelectionState>("calendarSelectionState", default: .all)
static let showFullEventTitles = Key<Bool>("showFullEventTitles", default: false)
static let autoScrollToNextEvent = Key<Bool>("autoScrollToNextEvent", default: true)
// MARK: Fullscreen Media Detection
static let alwaysHideInFullscreen = Key<Bool>("alwaysHideInFullscreen", default: false)
static let hideNotchOption = Key<HideNotchOption>("hideNotchOption", default: .nowPlayingOnly)
// MARK: Wobble Animation
static let enableWobbleAnimation = Key<Bool>("enableWobbleAnimation", default: false)
// MARK: Media Controller
static let mediaController = Key<MediaControllerType>("mediaController", default: defaultMediaController)
// MARK: Bluetooth Audio Devices
static let showBluetoothDeviceConnections = Key<Bool>("showBluetoothDeviceConnections", default: true)
static let useColorCodedBatteryDisplay = Key<Bool>("useColorCodedBatteryDisplay", default: true)
static let useColorCodedVolumeDisplay = Key<Bool>("useColorCodedVolumeDisplay", default: true)
static let useSmoothColorGradient = Key<Bool>("useSmoothColorGradient", default: true)
static let useCircularBluetoothBatteryIndicator = Key<Bool>("useCircularBluetoothBatteryIndicator", default: true)
static let showBluetoothBatteryPercentageText = Key<Bool>("showBluetoothBatteryPercentageText", default: false)
static let showBluetoothDeviceNameMarquee = Key<Bool>("showBluetoothDeviceNameMarquee", default: false)
static let useBluetoothHUD3DIcon = Key<Bool>("useBluetoothHUD3DIcon", default: true)
// MARK: Stats Feature
static let enableStatsFeature = Key<Bool>("enableStatsFeature", default: false)
static let autoStartStatsMonitoring = Key<Bool>("autoStartStatsMonitoring", default: true)
static let statsStopWhenNotchCloses = Key<Bool>("statsStopWhenNotchCloses", default: false)
static let statsUpdateInterval = Key<Double>("statsUpdateInterval", default: 1.0)
static let showCpuGraph = Key<Bool>("showCpuGraph", default: true)
static let showMemoryGraph = Key<Bool>("showMemoryGraph", default: true)
static let showGpuGraph = Key<Bool>("showGpuGraph", default: true)
static let showNetworkGraph = Key<Bool>("showNetworkGraph", default: false)
static let showDiskGraph = Key<Bool>("showDiskGraph", default: false)
// MARK: Terminal Feature
static let enableTerminalFeature = Key<Bool>("enableTerminalFeature", default: false)
static let terminalShellPath = Key<String>("terminalShellPath", default: "/bin/zsh")
static let terminalFontSize = Key<Double>("terminalFontSize", default: 12.0)
static let terminalOpacity = Key<Double>("terminalOpacity", default: 1.0)
static let terminalMaxHeightFraction = Key<Double>("terminalMaxHeightFraction", default: 0.4)
static let terminalCursorStyle = Key<String>("terminalCursorStyle", default: "blinkBlock")
static let terminalScrollbackLines = Key<Int>("terminalScrollbackLines", default: 1000)
static let terminalOptionAsMeta = Key<Bool>("terminalOptionAsMeta", default: true)
static let terminalMouseReporting = Key<Bool>("terminalMouseReporting", default: true)
static let terminalBoldAsBright = Key<Bool>("terminalBoldAsBright", default: true)
static let terminalBackgroundColor = Key<Color>("terminalBackgroundColor", default: .black)
static let terminalForegroundColor = Key<Color>("terminalForegroundColor", default: .white)
static let terminalCursorColor = Key<Color>("terminalCursorColor", default: Color(.selectedControlColor))
// MARK: Timer Feature
static let enableTimerFeature = Key<Bool>("enableTimerFeature", default: true)
static let timerDisplayMode = Key<TimerDisplayMode>("timerDisplayMode", default: .tab)
static let timerPresets = Key<[TimerPreset]>("timerPresets", default: TimerPreset.defaultPresets)
static let showTimerPresetsInNotchTab = Key<Bool>("showTimerPresetsInNotchTab", default: true)
static let timerIconColorMode = Key<TimerIconColorMode>("timerIconColorMode", default: .adaptive)
static let timerSolidColor = Key<Color>("timerSolidColor", default: .blue)
static let timerShowsCountdown = Key<Bool>("timerShowsCountdown", default: true)
static let timerShowsLabel = Key<Bool>("timerShowsLabel", default: false)
static let timerShowsProgress = Key<Bool>("timerShowsProgress", default: true)
static let timerProgressStyle = Key<TimerProgressStyle>("timerProgressStyle", default: .bar)
static let mirrorSystemTimer = Key<Bool>("mirrorSystemTimer", default: true)
// MARK: Reminder Live Activity
static let enableReminderLiveActivity = Key<Bool>("enableReminderLiveActivity", default: true)
static let reminderPresentationStyle = Key<ReminderPresentationStyle>("reminderPresentationStyle", default: .ringCountdown)
static let reminderLeadTime = Key<Int>("reminderLeadTime", default: 5)
static let reminderSneakPeekDuration = Key<Double>("reminderSneakPeekDuration", default: 5)
static let timerControlWindowEnabled = Key<Bool>("timerControlWindowEnabled", default: true)
// MARK: ColorPicker Feature
static let enableColorPickerFeature = Key<Bool>("enableColorPickerFeature", default: true)
static let showColorFormats = Key<Bool>("showColorFormats", default: true)
static let colorPickerDisplayMode = Key<ColorPickerDisplayMode>("colorPickerDisplayMode", default: .panel)
static let colorHistorySize = Key<Int>("colorHistorySize", default: 10)
static let showColorPickerIcon = Key<Bool>("showColorPickerIcon", default: true)
// MARK: Clipboard Feature
static let enableClipboardManager = Key<Bool>("enableClipboardManager", default: true)
static let clipboardHistorySize = Key<Int>("clipboardHistorySize", default: 3)
static let showClipboardIcon = Key<Bool>("showClipboardIcon", default: true)
static let clipboardDisplayMode = Key<ClipboardDisplayMode>("clipboardDisplayMode", default: .panel)
// MARK: Screen Assistant Feature
static let enableScreenAssistant = Key<Bool>("enableScreenAssistant", default: true)
static let screenAssistantDisplayMode = Key<ScreenAssistantDisplayMode>("screenAssistantDisplayMode", default: .panel)
static let geminiApiKey = Key<String>("geminiApiKey", default: "")
static let openaiApiKey = Key<String>("openaiApiKey", default: "")
static let claudeApiKey = Key<String>("claudeApiKey", default: "")
static let selectedAIProvider = Key<AIModelProvider>("selectedAIProvider", default: .gemini)
static let selectedAIModel = Key<AIModel?>("selectedAIModel", default: nil)
static let enableThinkingMode = Key<Bool>("enableThinkingMode", default: false)
static let localModelEndpoint = Key<String>("localModelEndpoint", default: "http://localhost:11434")
// MARK: Third-Party Extensions
static let enableThirdPartyExtensions = Key<Bool>("enableThirdPartyExtensions", default: true)
static let enableExtensionLiveActivities = Key<Bool>("enableExtensionLiveActivities", default: true)
static let enableExtensionLockScreenWidgets = Key<Bool>("enableExtensionLockScreenWidgets", default: true)
static let enableExtensionNotchExperiences = Key<Bool>("enableExtensionNotchExperiences", default: true)
static let enableExtensionNotchTabs = Key<Bool>("enableExtensionNotchTabs", default: true)
static let enableExtensionNotchMinimalisticOverrides = Key<Bool>("enableExtensionNotchMinimalisticOverrides", default: true)
static let enableExtensionNotchInteractiveWebViews = Key<Bool>("enableExtensionNotchInteractiveWebViews", default: true)
static let extensionAuthorizationEntries = Key<[ExtensionAuthorizationEntry]>("extensionAuthorizationEntries", default: [])
static let extensionRateLimitRecords = Key<[ExtensionRateLimitRecord]>("extensionRateLimitRecords", default: [])
static let extensionDiagnosticsLoggingEnabled = Key<Bool>("extensionDiagnosticsLoggingEnabled", default: true)
static let extensionLiveActivityCapacity = Key<Int>("extensionLiveActivityCapacity", default: 4)
static let extensionLockScreenWidgetCapacity = Key<Int>("extensionLockScreenWidgetCapacity", default: 4)
static let extensionNotchExperienceCapacity = Key<Int>("extensionNotchExperienceCapacity", default: 2)
// MARK: Keyboard Shortcuts
static let enableShortcuts = Key<Bool>("enableShortcuts", default: true)
// MARK: System HUD Feature
static let enableSystemHUD = Key<Bool>("enableSystemHUD", default: true)
static let enableVolumeHUD = Key<Bool>("enableVolumeHUD", default: true)
static let enableBrightnessHUD = Key<Bool>("enableBrightnessHUD", default: true)
static let enableKeyboardBacklightHUD = Key<Bool>("enableKeyboardBacklightHUD", default: true)
static let systemHUDSensitivity = Key<Int>("systemHUDSensitivity", default: 5)
static let playVolumeChangeFeedback = Key<Bool>("playVolumeChangeFeedback", default: false)
// MARK: Custom OSD Window Feature
static let enableCustomOSD = Key<Bool>("enableCustomOSD", default: false)
static let enableVerticalHUD = Key<Bool>("enableVerticalHUD", default: false)
static let enableCircularHUD = Key<Bool>("enableCircularHUD", default: false)
static let verticalHUDPosition = Key<String>("verticalHUDPosition", default: "right") // "left" or "right"
// Vertical HUD Customization
static let verticalHUDShowValue = Key<Bool>("verticalHUDShowValue", default: true)
static let verticalHUDInteractive = Key<Bool>("verticalHUDInteractive", default: true)
static let verticalHUDHeight = Key<CGFloat>("verticalHUDHeight", default: 160)
static let verticalHUDWidth = Key<CGFloat>("verticalHUDWidth", default: 36)
static let verticalHUDPadding = Key<CGFloat>("verticalHUDPadding", default: 24)
static let verticalHUDUseAccentColor = Key<Bool>("verticalHUDUseAccentColor", default: false)
static let verticalHUDMaterial = Key<OSDMaterial>("verticalHUDMaterial", default: .frosted)
static let verticalHUDLiquidGlassCustomizationMode = Key<LockScreenGlassCustomizationMode>(
"verticalHUDLiquidGlassCustomizationMode",
default: .standard
)
static let verticalHUDLiquidGlassVariant = Key<LiquidGlassVariant>(
"verticalHUDLiquidGlassVariant",
default: .defaultVariant
)
// Circular HUD Customization
static let circularHUDShowValue = Key<Bool>("circularHUDShowValue", default: true)
static let circularHUDSize = Key<CGFloat>("circularHUDSize", default: 65)
static let circularHUDStrokeWidth = Key<CGFloat>("circularHUDStrokeWidth", default: 4)
static let circularHUDUseAccentColor = Key<Bool>("circularHUDUseAccentColor", default: true)
// MARK: BetterDisplay Integration
static let enableBetterDisplayIntegration = Key<Bool>("enableBetterDisplayIntegration", default: false)
static let hasSeenOSDAlphaWarning = Key<Bool>("hasSeenOSDAlphaWarning", default: false)
static let enableOSDVolume = Key<Bool>("enableOSDVolume", default: true)
static let enableOSDBrightness = Key<Bool>("enableOSDBrightness", default: true)
static let enableOSDKeyboardBacklight = Key<Bool>("enableOSDKeyboardBacklight", default: true)
static let osdMaterial = Key<OSDMaterial>("osdMaterial", default: .frosted)
static let osdLiquidGlassCustomizationMode = Key<LockScreenGlassCustomizationMode>(
"osdLiquidGlassCustomizationMode",
default: .standard
)
static let osdLiquidGlassVariant = Key<LiquidGlassVariant>(
"osdLiquidGlassVariant",
default: .defaultVariant
)
static let osdIconColorStyle = Key<OSDIconColorStyle>("osdIconColorStyle", default: .white)
// MARK: Screen Recording Detection Feature
static let enableScreenRecordingDetection = Key<Bool>("enableScreenRecordingDetection", default: true)
static let showRecordingIndicator = Key<Bool>("showRecordingIndicator", default: true)
// Polling removed - now uses event-driven private API detection (CGSIsScreenWatcherPresent)
// static let enableScreenRecordingPolling = Key<Bool>("enableScreenRecordingPolling", default: false)
// MARK: Focus / Do Not Disturb Detection
static let enableDoNotDisturbDetection = Key<Bool>("enableDoNotDisturbDetection", default: true)
static let focusMonitoringMode = Key<FocusMonitoringMode>("focusMonitoringMode", default: .withoutDevTools)
static let showDoNotDisturbIndicator = Key<Bool>("showDoNotDisturbIndicator", default: true)
static let showDoNotDisturbLabel = Key<Bool>("showDoNotDisturbLabel", default: true)
static let focusIndicatorNonPersistent = Key<Bool>("focusIndicatorNonPersistent", default: false)
// MARK: Privacy Indicators (Camera & Microphone Detection)
static let enableCameraDetection = Key<Bool>("enableCameraDetection", default: true)
static let enableMicrophoneDetection = Key<Bool>("enableMicrophoneDetection", default: true)
// MARK: Lock Screen Features
static let enableLockScreenLiveActivity = Key<Bool>("enableLockScreenLiveActivity", default: true)
static let enableLockSounds = Key<Bool>("enableLockSounds", default: true)
// MARK: Caps Lock Indicator
static let enableCapsLockIndicator = Key<Bool>("enableCapsLockIndicator", default: true)
static let capsLockIndicatorUseGreenColor = Key<Bool>("capsLockIndicatorUseGreenColor", default: false) // Legacy toggle
static let capsLockIndicatorTintMode = Key<CapsLockIndicatorTintMode>("capsLockIndicatorTintMode", default: .white)
static let didMigrateCapsLockTintMode = Key<Bool>("didMigrateCapsLockTintMode", default: false)
static let showCapsLockLabel = Key<Bool>("showCapsLockLabel", default: true)
// MARK: ImageService
static let didClearLegacyURLCacheV1 = Key<Bool>("didClearLegacyURLCacheV1", default: false)
// MARK: Minimalistic UI Mode
static let enableMinimalisticUI = Key<Bool>("enableMinimalisticUI", default: false)
// MARK: Lyrics Feature
static let enableLyrics = Key<Bool>("enableLyrics", default: false)
// MARK: Notes Feature
static let enableNotes = Key<Bool>("enableNotes", default: false)
static let enableNotePinning = Key<Bool>("enableNotePinning", default: true)
static let enableNoteSearch = Key<Bool>("enableNoteSearch", default: false)
static let enableNoteColorFiltering = Key<Bool>("enableNoteColorFiltering", default: false)
static let enableCreateFromClipboard = Key<Bool>("enableCreateFromClipboard", default: true)
static let enableNoteCharCount = Key<Bool>("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 migrateCapsLockTintMode() {
guard Defaults[.didMigrateCapsLockTintMode] == false else { return }
let legacyGreen = Defaults[.capsLockIndicatorUseGreenColor]
Defaults[.capsLockIndicatorTintMode] = legacyGreen ? .green : .white
Defaults[.didMigrateCapsLockTintMode] = true
}
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
}
}