import Foundation protocol AppSettingsStoreType { func load() -> AppSettings func save(_ settings: AppSettings) } struct UserDefaultsAppSettingsStore: AppSettingsStoreType { private let defaults: UserDefaults init(defaults: UserDefaults = .standard) { self.defaults = defaults } func load() -> AppSettings { AppSettings( display: .init( showOnAllDisplays: bool(NotchSettings.Keys.showOnAllDisplays, default: NotchSettings.Defaults.showOnAllDisplays), showMenuBarIcon: bool(NotchSettings.Keys.showMenuBarIcon, default: NotchSettings.Defaults.showMenuBarIcon), launchAtLogin: bool(NotchSettings.Keys.launchAtLogin, default: NotchSettings.Defaults.launchAtLogin), notchHeightMode: integer(NotchSettings.Keys.notchHeightMode, default: NotchSettings.Defaults.notchHeightMode), notchHeight: double(NotchSettings.Keys.notchHeight, default: NotchSettings.Defaults.notchHeight), nonNotchHeightMode: integer(NotchSettings.Keys.nonNotchHeightMode, default: NotchSettings.Defaults.nonNotchHeightMode), nonNotchHeight: double(NotchSettings.Keys.nonNotchHeight, default: NotchSettings.Defaults.nonNotchHeight), openWidth: double(NotchSettings.Keys.openWidth, default: NotchSettings.Defaults.openWidth), openHeight: double(NotchSettings.Keys.openHeight, default: NotchSettings.Defaults.openHeight) ), behavior: .init( openNotchOnHover: bool(NotchSettings.Keys.openNotchOnHover, default: NotchSettings.Defaults.openNotchOnHover), minimumHoverDuration: double(NotchSettings.Keys.minimumHoverDuration, default: NotchSettings.Defaults.minimumHoverDuration), enableGestures: bool(NotchSettings.Keys.enableGestures, default: NotchSettings.Defaults.enableGestures), gestureSensitivity: double(NotchSettings.Keys.gestureSensitivity, default: NotchSettings.Defaults.gestureSensitivity) ), appearance: .init( enableShadow: bool(NotchSettings.Keys.enableShadow, default: NotchSettings.Defaults.enableShadow), shadowRadius: double(NotchSettings.Keys.shadowRadius, default: NotchSettings.Defaults.shadowRadius), shadowOpacity: double(NotchSettings.Keys.shadowOpacity, default: NotchSettings.Defaults.shadowOpacity), cornerRadiusScaling: bool(NotchSettings.Keys.cornerRadiusScaling, default: NotchSettings.Defaults.cornerRadiusScaling), notchOpacity: double(NotchSettings.Keys.notchOpacity, default: NotchSettings.Defaults.notchOpacity), blurRadius: double(NotchSettings.Keys.blurRadius, default: NotchSettings.Defaults.blurRadius) ), animation: .init( openSpringResponse: double(NotchSettings.Keys.openSpringResponse, default: NotchSettings.Defaults.openSpringResponse), openSpringDamping: double(NotchSettings.Keys.openSpringDamping, default: NotchSettings.Defaults.openSpringDamping), closeSpringResponse: double(NotchSettings.Keys.closeSpringResponse, default: NotchSettings.Defaults.closeSpringResponse), closeSpringDamping: double(NotchSettings.Keys.closeSpringDamping, default: NotchSettings.Defaults.closeSpringDamping), hoverSpringResponse: double(NotchSettings.Keys.hoverSpringResponse, default: NotchSettings.Defaults.hoverSpringResponse), hoverSpringDamping: double(NotchSettings.Keys.hoverSpringDamping, default: NotchSettings.Defaults.hoverSpringDamping), resizeAnimationDuration: double(NotchSettings.Keys.resizeAnimationDuration, default: NotchSettings.Defaults.resizeAnimationDuration) ), terminal: .init( fontSize: double(NotchSettings.Keys.terminalFontSize, default: NotchSettings.Defaults.terminalFontSize), shellPath: string(NotchSettings.Keys.terminalShell, default: NotchSettings.Defaults.terminalShell), themeRawValue: string(NotchSettings.Keys.terminalTheme, default: NotchSettings.Defaults.terminalTheme), sizePresetsJSON: string(NotchSettings.Keys.terminalSizePresets, default: NotchSettings.Defaults.terminalSizePresets) ), hotkeys: .init( toggle: hotkey(NotchSettings.Keys.hotkeyToggle, default: .cmdReturn), newTab: hotkey(NotchSettings.Keys.hotkeyNewTab, default: .cmdT), closeTab: hotkey(NotchSettings.Keys.hotkeyCloseTab, default: .cmdW), nextTab: hotkey(NotchSettings.Keys.hotkeyNextTab, default: .cmdShiftRB), previousTab: hotkey(NotchSettings.Keys.hotkeyPreviousTab, default: .cmdShiftLB), nextWorkspace: hotkey(NotchSettings.Keys.hotkeyNextWorkspace, default: .cmdShiftDown), previousWorkspace: hotkey(NotchSettings.Keys.hotkeyPreviousWorkspace, default: .cmdShiftUp), detachTab: hotkey(NotchSettings.Keys.hotkeyDetachTab, default: .cmdD) ) ) } func save(_ settings: AppSettings) { defaults.set(settings.display.showOnAllDisplays, forKey: NotchSettings.Keys.showOnAllDisplays) defaults.set(settings.display.showMenuBarIcon, forKey: NotchSettings.Keys.showMenuBarIcon) defaults.set(settings.display.launchAtLogin, forKey: NotchSettings.Keys.launchAtLogin) defaults.set(settings.display.notchHeightMode, forKey: NotchSettings.Keys.notchHeightMode) defaults.set(settings.display.notchHeight, forKey: NotchSettings.Keys.notchHeight) defaults.set(settings.display.nonNotchHeightMode, forKey: NotchSettings.Keys.nonNotchHeightMode) defaults.set(settings.display.nonNotchHeight, forKey: NotchSettings.Keys.nonNotchHeight) defaults.set(settings.display.openWidth, forKey: NotchSettings.Keys.openWidth) defaults.set(settings.display.openHeight, forKey: NotchSettings.Keys.openHeight) defaults.set(settings.behavior.openNotchOnHover, forKey: NotchSettings.Keys.openNotchOnHover) defaults.set(settings.behavior.minimumHoverDuration, forKey: NotchSettings.Keys.minimumHoverDuration) defaults.set(settings.behavior.enableGestures, forKey: NotchSettings.Keys.enableGestures) defaults.set(settings.behavior.gestureSensitivity, forKey: NotchSettings.Keys.gestureSensitivity) defaults.set(settings.appearance.enableShadow, forKey: NotchSettings.Keys.enableShadow) defaults.set(settings.appearance.shadowRadius, forKey: NotchSettings.Keys.shadowRadius) defaults.set(settings.appearance.shadowOpacity, forKey: NotchSettings.Keys.shadowOpacity) defaults.set(settings.appearance.cornerRadiusScaling, forKey: NotchSettings.Keys.cornerRadiusScaling) defaults.set(settings.appearance.notchOpacity, forKey: NotchSettings.Keys.notchOpacity) defaults.set(settings.appearance.blurRadius, forKey: NotchSettings.Keys.blurRadius) defaults.set(settings.animation.openSpringResponse, forKey: NotchSettings.Keys.openSpringResponse) defaults.set(settings.animation.openSpringDamping, forKey: NotchSettings.Keys.openSpringDamping) defaults.set(settings.animation.closeSpringResponse, forKey: NotchSettings.Keys.closeSpringResponse) defaults.set(settings.animation.closeSpringDamping, forKey: NotchSettings.Keys.closeSpringDamping) defaults.set(settings.animation.hoverSpringResponse, forKey: NotchSettings.Keys.hoverSpringResponse) defaults.set(settings.animation.hoverSpringDamping, forKey: NotchSettings.Keys.hoverSpringDamping) defaults.set(settings.animation.resizeAnimationDuration, forKey: NotchSettings.Keys.resizeAnimationDuration) defaults.set(settings.terminal.fontSize, forKey: NotchSettings.Keys.terminalFontSize) defaults.set(settings.terminal.shellPath, forKey: NotchSettings.Keys.terminalShell) defaults.set(settings.terminal.themeRawValue, forKey: NotchSettings.Keys.terminalTheme) defaults.set(settings.terminal.sizePresetsJSON, forKey: NotchSettings.Keys.terminalSizePresets) defaults.set(settings.hotkeys.toggle.toJSON(), forKey: NotchSettings.Keys.hotkeyToggle) defaults.set(settings.hotkeys.newTab.toJSON(), forKey: NotchSettings.Keys.hotkeyNewTab) defaults.set(settings.hotkeys.closeTab.toJSON(), forKey: NotchSettings.Keys.hotkeyCloseTab) defaults.set(settings.hotkeys.nextTab.toJSON(), forKey: NotchSettings.Keys.hotkeyNextTab) defaults.set(settings.hotkeys.previousTab.toJSON(), forKey: NotchSettings.Keys.hotkeyPreviousTab) defaults.set(settings.hotkeys.nextWorkspace.toJSON(), forKey: NotchSettings.Keys.hotkeyNextWorkspace) defaults.set(settings.hotkeys.previousWorkspace.toJSON(), forKey: NotchSettings.Keys.hotkeyPreviousWorkspace) defaults.set(settings.hotkeys.detachTab.toJSON(), forKey: NotchSettings.Keys.hotkeyDetachTab) } private func bool(_ key: String, default defaultValue: Bool) -> Bool { guard defaults.object(forKey: key) != nil else { return defaultValue } return defaults.bool(forKey: key) } private func double(_ key: String, default defaultValue: Double) -> Double { guard defaults.object(forKey: key) != nil else { return defaultValue } return defaults.double(forKey: key) } private func integer(_ key: String, default defaultValue: Int) -> Int { guard defaults.object(forKey: key) != nil else { return defaultValue } return defaults.integer(forKey: key) } private func string(_ key: String, default defaultValue: String) -> String { defaults.string(forKey: key) ?? defaultValue } private func hotkey(_ key: String, default defaultValue: HotkeyBinding) -> HotkeyBinding { guard let json = defaults.string(forKey: key), let binding = HotkeyBinding.fromJSON(json) else { return defaultValue } return binding } }