import AppKit import SwiftUI // Needed for ObservableObject, Published, etc. import Combine // Needed for objectWillChange class SettingsManager: ObservableObject { static let shared = SettingsManager() private var isInitializing = false // NIEUWE FLAG var screenshotFolder: String? { get { UserDefaults.standard.string(forKey: SettingsKey.screenshotFolder) } set { objectWillChange.send() UserDefaults.standard.set(newValue, forKey: SettingsKey.screenshotFolder) } } var thumbnailTimer: Int { get { UserDefaults.standard.integer(forKey: SettingsKey.thumbnailTimer) } set { objectWillChange.send(); UserDefaults.standard.set(newValue, forKey: SettingsKey.thumbnailTimer) } } @Published var closeAfterDrag: Bool = false { didSet { UserDefaults.standard.set(closeAfterDrag, forKey: SettingsKey.closeAfterDrag) if !isInitializing { NotificationCenter.default.post(name: .closeAfterDragSettingChanged, object: nil) } } } var showFolderButton: Bool { get { UserDefaults.standard.bool(forKey: SettingsKey.showFolderButton) } set { objectWillChange.send(); UserDefaults.standard.set(newValue, forKey: SettingsKey.showFolderButton) } } @Published var closeAfterSave: Bool = false { didSet { UserDefaults.standard.set(closeAfterSave, forKey: SettingsKey.closeAfterSave) if !isInitializing { NotificationCenter.default.post(name: .closeAfterSaveSettingChanged, object: nil) } } } @Published var playSoundOnCapture: Bool = true { didSet { UserDefaults.standard.set(playSoundOnCapture, forKey: SettingsKey.playSoundOnCapture) if !isInitializing { // CONTROLEER FLAG NotificationCenter.default.post(name: .playSoundOnCaptureSettingChanged, object: nil) } } } // NEW: Properties for filename format var filenamePrefix: String { get { UserDefaults.standard.string(forKey: SettingsKey.filenamePrefix) ?? "Schermafbeelding" } // Default prefix set { objectWillChange.send(); UserDefaults.standard.set(newValue, forKey: SettingsKey.filenamePrefix) } } var filenameFormatPreset: FilenameFormatPreset { get { let rawValue = UserDefaults.standard.integer(forKey: SettingsKey.filenameFormatPreset) return FilenameFormatPreset(rawValue: rawValue) ?? .macOSStyle // Default naar macOS style } set { objectWillChange.send(); UserDefaults.standard.set(newValue.rawValue, forKey: SettingsKey.filenameFormatPreset) } } var filenameCustomFormat: String { get { UserDefaults.standard.string(forKey: SettingsKey.filenameCustomFormat) ?? "{YYYY}-{MM}-{DD}_{hh}.{mm}.{ss}" } // Default custom format set { objectWillChange.send(); UserDefaults.standard.set(newValue, forKey: SettingsKey.filenameCustomFormat) } } // NEW: Properties for action enables var isRenameActionEnabled: Bool { get { let value = UserDefaults.standard.object(forKey: SettingsKey.isRenameActionEnabled) as? Bool ?? true return value } set { objectWillChange.send() UserDefaults.standard.set(newValue, forKey: SettingsKey.isRenameActionEnabled) } } var isStashActionEnabled: Bool { get { let value = UserDefaults.standard.object(forKey: SettingsKey.isStashActionEnabled) as? Bool ?? true return value } set { objectWillChange.send() UserDefaults.standard.set(newValue, forKey: SettingsKey.isStashActionEnabled) } } var isOCRActionEnabled: Bool { get { let value = UserDefaults.standard.object(forKey: SettingsKey.isOCRActionEnabled) as? Bool ?? true return value } set { objectWillChange.send() UserDefaults.standard.set(newValue, forKey: SettingsKey.isOCRActionEnabled) } } var isClipboardActionEnabled: Bool { get { let value = UserDefaults.standard.object(forKey: SettingsKey.isClipboardActionEnabled) as? Bool ?? true return value } set { objectWillChange.send() UserDefaults.standard.set(newValue, forKey: SettingsKey.isClipboardActionEnabled) } } var isBackgroundRemoveActionEnabled: Bool { get { let value = UserDefaults.standard.object(forKey: SettingsKey.isBackgroundRemoveActionEnabled) as? Bool ?? true return value } set { objectWillChange.send() UserDefaults.standard.set(newValue, forKey: SettingsKey.isBackgroundRemoveActionEnabled) } } var isCancelActionEnabled: Bool { get { let value = UserDefaults.standard.object(forKey: SettingsKey.isCancelActionEnabled) as? Bool ?? true return value } set { objectWillChange.send() UserDefaults.standard.set(newValue, forKey: SettingsKey.isCancelActionEnabled) } } var isRemoveActionEnabled: Bool { get { let value = UserDefaults.standard.object(forKey: SettingsKey.isRemoveActionEnabled) as? Bool ?? true return value } set { objectWillChange.send() UserDefaults.standard.set(newValue, forKey: SettingsKey.isRemoveActionEnabled) } } var isAction3Enabled: Bool { get { let value = UserDefaults.standard.object(forKey: SettingsKey.isAction3Enabled) as? Bool ?? false return value } set { objectWillChange.send() UserDefaults.standard.set(newValue, forKey: SettingsKey.isAction3Enabled) } } var isAction4Enabled: Bool { get { let value = UserDefaults.standard.object(forKey: SettingsKey.isAction4Enabled) as? Bool ?? false return value } set { objectWillChange.send() UserDefaults.standard.set(newValue, forKey: SettingsKey.isAction4Enabled) } } // NEW: Properties for Delete and Cancel Drag actions var isDeleteActionEnabled: Bool { get { UserDefaults.standard.object(forKey: SettingsKey.isDeleteActionEnabled) as? Bool ?? true } // Default AAN set { objectWillChange.send(); UserDefaults.standard.set(newValue, forKey: SettingsKey.isDeleteActionEnabled) } } var isCancelDragActionEnabled: Bool { get { UserDefaults.standard.object(forKey: SettingsKey.isCancelDragActionEnabled) as? Bool ?? true } // Default AAN set { objectWillChange.send(); UserDefaults.standard.set(newValue, forKey: SettingsKey.isCancelDragActionEnabled) } } // ๐ŸŽจ Background Removal Method Preference var preferredBackgroundRemovalMethod: BackgroundRemovalMethod { get { let rawValue = UserDefaults.standard.string(forKey: SettingsKey.preferredBackgroundRemovalMethod) ?? "auto" return BackgroundRemovalMethod(rawValue: rawValue) ?? .auto } set { objectWillChange.send() UserDefaults.standard.set(newValue.rawValue, forKey: SettingsKey.preferredBackgroundRemovalMethod) } } // NEW: Property for automatic startup @Published var startAppOnLogin: Bool = false { didSet { UserDefaults.standard.set(startAppOnLogin, forKey: SettingsKey.startAppOnLogin) if !isInitializing { NotificationCenter.default.post(name: .startAppOnLoginSettingChanged, object: nil) } } } // NEW: Property for automatic screenshot saving var autoSaveScreenshot: Bool { get { UserDefaults.standard.object(forKey: SettingsKey.autoSaveScreenshot) as? Bool ?? false } // Default false (handmatig beslissen) set { objectWillChange.send() UserDefaults.standard.set(newValue, forKey: SettingsKey.autoSaveScreenshot) // Optioneel: post een notificatie als directe actie in ScreenshotApp nodig is // NotificationCenter.default.post(name: .autoSaveScreenshotChanged, object: nil) } } // VOEG DEZE TOE static let thumbnailFixedSizeKey = "thumbnailFixedSize" @Published var thumbnailFixedSize: ThumbnailFixedSize = .medium { didSet { UserDefaults.standard.set(thumbnailFixedSize.rawValue, forKey: SettingsManager.thumbnailFixedSizeKey) if !isInitializing { NotificationCenter.default.post(name: .thumbnailSizeSettingChanged, object: nil) } } } // NEW: Property for stash always on top @Published var stashAlwaysOnTop: Bool = false { didSet { UserDefaults.standard.set(stashAlwaysOnTop, forKey: SettingsKey.stashAlwaysOnTop) if !isInitializing { NotificationCenter.default.post(name: .stashAlwaysOnTopSettingChanged, object: nil) } } } // ๐Ÿ”ฅ๐Ÿ’Ž MEGA NIEUWE STASH PREVIEW SIZE SETTING! ๐Ÿ’Ž๐Ÿ”ฅ @Published var stashPreviewSize: StashPreviewSize = .medium { didSet { UserDefaults.standard.set(stashPreviewSize.rawValue, forKey: SettingsKey.stashPreviewSize) if !isInitializing { NotificationCenter.default.post(name: .stashPreviewSizeChanged, object: nil) } } } // ๐Ÿ”ฅ๐Ÿ’ฅโšก HYPERMODE STASH GRID CONFIGURATION! โšก๐Ÿ’ฅ๐Ÿ”ฅ @Published var stashGridMode: StashGridMode = .fixedColumns { didSet { UserDefaults.standard.set(stashGridMode.rawValue, forKey: SettingsKey.stashGridMode) if !isInitializing { NotificationCenter.default.post(name: .stashGridModeChanged, object: nil) NotificationCenter.default.post(name: .stashGridConfigChanged, object: nil) } } } @Published var stashMaxColumns: Int = 2 { didSet { // Clamp to 1-5 range for HYPERMODE safety! let clampedValue = max(1, min(5, stashMaxColumns)) if clampedValue != stashMaxColumns { stashMaxColumns = clampedValue return } UserDefaults.standard.set(stashMaxColumns, forKey: SettingsKey.stashMaxColumns) if !isInitializing { NotificationCenter.default.post(name: .stashGridConfigChanged, object: nil) } } } @Published var stashMaxRows: Int = 1 { didSet { // Clamp to 1-5 range for HYPERMODE safety! let clampedValue = max(1, min(5, stashMaxRows)) if clampedValue != stashMaxRows { stashMaxRows = clampedValue return } UserDefaults.standard.set(stashMaxRows, forKey: SettingsKey.stashMaxRows) if !isInitializing { NotificationCenter.default.post(name: .stashGridConfigChanged, object: nil) } } } // ๐Ÿ”ฅ NIEUW: Persistent stash setting @Published var persistentStash: Bool = false { didSet { UserDefaults.standard.set(persistentStash, forKey: SettingsKey.persistentStash) if !isInitializing { NotificationCenter.default.post(name: .persistentStashChanged, object: nil) } } } // ๐Ÿ”„ UPDATE SETTINGS @Published var automaticUpdates: Bool = true { didSet { UserDefaults.standard.set(automaticUpdates, forKey: SettingsKey.automaticUpdates) } } @Published var includePreReleases: Bool = false { didSet { UserDefaults.standard.set(includePreReleases, forKey: SettingsKey.includePreReleases) } } // NEW: Property for hiding desktop icons during screenshots @Published var hideDesktopIconsDuringScreenshot: Bool = false { didSet { UserDefaults.standard.set(hideDesktopIconsDuringScreenshot, forKey: SettingsKey.hideDesktopIconsDuringScreenshot) if !isInitializing { NotificationCenter.default.post(name: .hideDesktopIconsSettingChanged, object: nil) } print("Setting updated: hideDesktopIconsDuringScreenshot = \(hideDesktopIconsDuringScreenshot)") } } // NEW: Property for hiding desktop widgets during screenshots @Published var hideDesktopWidgetsDuringScreenshot: Bool = false { didSet { UserDefaults.standard.set(hideDesktopWidgetsDuringScreenshot, forKey: SettingsKey.hideDesktopWidgetsDuringScreenshot) if !isInitializing { NotificationCenter.default.post(name: .hideDesktopWidgetsSettingChanged, object: nil) } print("Setting updated: hideDesktopWidgetsDuringScreenshot = \(hideDesktopWidgetsDuringScreenshot)") } } // ๐Ÿ”Š NEW: Sound volume and type settings @Published var screenshotSoundVolume: Float = 0.1 { didSet { UserDefaults.standard.set(screenshotSoundVolume, forKey: SettingsKey.screenshotSoundVolume) } } @Published var screenshotSoundType: ScreenshotSoundType = .pop { didSet { if let data = try? JSONEncoder().encode(screenshotSoundType) { UserDefaults.standard.set(data, forKey: SettingsKey.screenshotSoundType) } } } // ๐Ÿ—‚๏ธ NEW: Cache management settings @Published var cacheRetentionTime: CacheRetentionTime = .oneWeek { didSet { if let data = try? JSONEncoder().encode(cacheRetentionTime) { UserDefaults.standard.set(data, forKey: SettingsKey.cacheRetentionTime) } // ๐Ÿงช NIEUW: Send notification for cache retention time changes (except during initialization) if !isInitializing { NotificationCenter.default.post(name: NSNotification.Name("cacheRetentionTimeChanged"), object: nil) } } } // NEW: Property for first launch completion var hasCompletedFirstLaunch: Bool { get { UserDefaults.standard.bool(forKey: SettingsKey.hasCompletedFirstLaunch) } set { objectWillChange.send() UserDefaults.standard.set(newValue, forKey: SettingsKey.hasCompletedFirstLaunch) } } var windowCaptureIncludeCursor: Bool { get { UserDefaults.standard.bool(forKey: SettingsKey.windowCaptureIncludeCursor) } set { objectWillChange.send() UserDefaults.standard.set(newValue, forKey: SettingsKey.windowCaptureIncludeCursor) if !isInitializing { NotificationCenter.default.post(name: .windowCaptureSettingChanged, object: nil) } } } // NEW: Property for clean desktop screenshots // VERWIJDERD: cleanDesktopScreenshots property - feature disabled @Published var saveAfterEdit: Bool = false { didSet { UserDefaults.standard.set(saveAfterEdit, forKey: SettingsKey.saveAfterEdit) NotificationCenter.default.post(name: .saveAfterEditSettingChanged, object: nil) } } var thumbnailDisplayScreen: ThumbnailDisplayScreen { get { let rawValue = UserDefaults.standard.string(forKey: SettingsKey.thumbnailDisplayScreen) ?? ThumbnailDisplayScreen.automatic.rawValue return ThumbnailDisplayScreen(rawValue: rawValue) ?? .automatic } set { objectWillChange.send() UserDefaults.standard.set(newValue.rawValue, forKey: SettingsKey.thumbnailDisplayScreen) } } static let actionOrderKey = "actionOrder" @Published var actionOrder: [ActionType] = [] { didSet { let orderStrings = actionOrder.map { $0.rawValue } UserDefaults.standard.set(orderStrings, forKey: SettingsManager.actionOrderKey) } } // NEW: Keyboard shortcut properties @Published var useCustomShortcut: Bool = false { didSet { UserDefaults.standard.set(useCustomShortcut, forKey: SettingsKey.useCustomShortcut) if !isInitializing { // Post notification for hotkey change NotificationCenter.default.post(name: .shortcutSettingChanged, object: nil) } } } @Published var customShortcutModifiers: UInt = 0 { didSet { UserDefaults.standard.set(customShortcutModifiers, forKey: SettingsKey.customShortcutModifiers) if !isInitializing && useCustomShortcut { NotificationCenter.default.post(name: .shortcutSettingChanged, object: nil) } } } @Published var customShortcutKey: UInt16 = 0 { didSet { UserDefaults.standard.set(customShortcutKey, forKey: SettingsKey.customShortcutKey) if !isInitializing && useCustomShortcut { NotificationCenter.default.post(name: .shortcutSettingChanged, object: nil) } } } private init() { isInitializing = true // ZET FLAG // Laad ALLE properties hier uit UserDefaults self.screenshotFolder = UserDefaults.standard.string(forKey: SettingsKey.screenshotFolder) // Kan nil zijn self.filenamePrefix = UserDefaults.standard.string(forKey: SettingsKey.filenamePrefix) ?? "Schermafbeelding" let presetRaw = UserDefaults.standard.integer(forKey: SettingsKey.filenameFormatPreset) self.filenameFormatPreset = FilenameFormatPreset(rawValue: presetRaw) ?? .macOSStyle // Als de opgeslagen rawValue 0 was en .macOSStyle is 0, dan is dit onnodig dubbel. // Overweeg: if UserDefaults.standard.object(forKey: SettingsKey.filenameFormatPreset) != nil { ... laden ... } else { default } self.filenameCustomFormat = UserDefaults.standard.string(forKey: SettingsKey.filenameCustomFormat) ?? "{YYYY}-{MM}-{DD}_{hh}.{mm}.{ss}" self.saveAfterEdit = UserDefaults.standard.bool(forKey: SettingsKey.saveAfterEdit) // Default false als key niet bestaat self.playSoundOnCapture = UserDefaults.standard.object(forKey: SettingsKey.playSoundOnCapture) as? Bool ?? true self.thumbnailTimer = UserDefaults.standard.integer(forKey: SettingsKey.thumbnailTimer) self.closeAfterDrag = UserDefaults.standard.bool(forKey: SettingsKey.closeAfterDrag) let thumbFixedSizeKeyString = SettingsManager.thumbnailFixedSizeKey let loadedFixedSizeRawValue = UserDefaults.standard.string(forKey: thumbFixedSizeKeyString) if let loadedSize = loadedFixedSizeRawValue.flatMap(ThumbnailFixedSize.init) { if self.thumbnailFixedSize != loadedSize { self.thumbnailFixedSize = loadedSize } } else { UserDefaults.standard.set(ThumbnailFixedSize.medium.rawValue, forKey: thumbFixedSizeKeyString) } self.showFolderButton = UserDefaults.standard.object(forKey: SettingsKey.showFolderButton) as? Bool ?? true self.startAppOnLogin = UserDefaults.standard.object(forKey: SettingsKey.startAppOnLogin) as? Bool ?? false self.autoSaveScreenshot = UserDefaults.standard.object(forKey: SettingsKey.autoSaveScreenshot) as? Bool ?? false self.closeAfterSave = UserDefaults.standard.bool(forKey: SettingsKey.closeAfterSave) // Stash window border etc. ook hier initialiseren: // self.stashWindowBorderWidth = UserDefaults.standard.object(forKey: SettingsKey.stashWindowBorderWidth) == nil ? 1.0 : CGFloat(UserDefaults.standard.float(forKey: SettingsKey.stashWindowBorderWidth)) self.isRenameActionEnabled = UserDefaults.standard.object(forKey: SettingsKey.isRenameActionEnabled) as? Bool ?? true self.isStashActionEnabled = UserDefaults.standard.object(forKey: SettingsKey.isStashActionEnabled) as? Bool ?? true self.isOCRActionEnabled = UserDefaults.standard.object(forKey: SettingsKey.isOCRActionEnabled) as? Bool ?? true self.isClipboardActionEnabled = UserDefaults.standard.object(forKey: SettingsKey.isClipboardActionEnabled) as? Bool ?? true self.isBackgroundRemoveActionEnabled = UserDefaults.standard.object(forKey: SettingsKey.isBackgroundRemoveActionEnabled) as? Bool ?? true self.isCancelActionEnabled = UserDefaults.standard.object(forKey: SettingsKey.isCancelActionEnabled) as? Bool ?? true self.isRemoveActionEnabled = UserDefaults.standard.object(forKey: SettingsKey.isRemoveActionEnabled) as? Bool ?? true self.isDeleteActionEnabled = UserDefaults.standard.object(forKey: SettingsKey.isDeleteActionEnabled) as? Bool ?? true self.isCancelDragActionEnabled = UserDefaults.standard.object(forKey: SettingsKey.isCancelDragActionEnabled) as? Bool ?? true self.stashAlwaysOnTop = UserDefaults.standard.object(forKey: SettingsKey.stashAlwaysOnTop) as? Bool ?? false self.hideDesktopIconsDuringScreenshot = UserDefaults.standard.object(forKey: SettingsKey.hideDesktopIconsDuringScreenshot) as? Bool ?? false self.hideDesktopWidgetsDuringScreenshot = UserDefaults.standard.object(forKey: SettingsKey.hideDesktopWidgetsDuringScreenshot) as? Bool ?? false // ๐Ÿ”ฅ๐Ÿ’Ž MEGA NIEUWE STASH PREVIEW SIZE INIT! ๐Ÿ’Ž๐Ÿ”ฅ let stashPreviewSizeRaw = UserDefaults.standard.string(forKey: SettingsKey.stashPreviewSize) ?? StashPreviewSize.medium.rawValue self.stashPreviewSize = StashPreviewSize(rawValue: stashPreviewSizeRaw) ?? .medium // ๐Ÿ”ฅ๐Ÿ’ฅโšก HYPERMODE STASH GRID INIT! โšก๐Ÿ’ฅ๐Ÿ”ฅ let stashGridModeRaw = UserDefaults.standard.string(forKey: SettingsKey.stashGridMode) ?? StashGridMode.fixedColumns.rawValue self.stashGridMode = StashGridMode(rawValue: stashGridModeRaw) ?? .fixedColumns self.stashMaxColumns = UserDefaults.standard.object(forKey: SettingsKey.stashMaxColumns) as? Int ?? 2 // HYPERMODE SAFETY: Clamp to 1-5 range! self.stashMaxColumns = max(1, min(5, self.stashMaxColumns)) self.stashMaxRows = UserDefaults.standard.object(forKey: SettingsKey.stashMaxRows) as? Int ?? 1 // HYPERMODE SAFETY: Clamp to 1-5 range! self.stashMaxRows = max(1, min(5, self.stashMaxRows)) // ๐Ÿ”ฅ NIEUW: Persistent stash init self.persistentStash = UserDefaults.standard.object(forKey: SettingsKey.persistentStash) as? Bool ?? false // ๐Ÿ”„ UPDATE SETTINGS INIT self.automaticUpdates = UserDefaults.standard.object(forKey: SettingsKey.automaticUpdates) as? Bool ?? true self.includePreReleases = UserDefaults.standard.object(forKey: SettingsKey.includePreReleases) as? Bool ?? false // ๐ŸŽน CUSTOM SHORTCUT SETTINGS INIT self.useCustomShortcut = UserDefaults.standard.object(forKey: SettingsKey.useCustomShortcut) as? Bool ?? false self.customShortcutModifiers = UserDefaults.standard.object(forKey: SettingsKey.customShortcutModifiers) as? UInt ?? 0 self.customShortcutKey = UserDefaults.standard.object(forKey: SettingsKey.customShortcutKey) as? UInt16 ?? 0 // ๐Ÿ”Š SOUND SETTINGS INIT self.screenshotSoundVolume = UserDefaults.standard.object(forKey: SettingsKey.screenshotSoundVolume) as? Float ?? 0.1 if let soundTypeData = UserDefaults.standard.data(forKey: SettingsKey.screenshotSoundType), let soundType = try? JSONDecoder().decode(ScreenshotSoundType.self, from: soundTypeData) { self.screenshotSoundType = soundType } else { self.screenshotSoundType = .pop } // ๐Ÿ—‚๏ธ CACHE MANAGEMENT INIT if let cacheRetentionData = UserDefaults.standard.data(forKey: SettingsKey.cacheRetentionTime), let retentionTime = try? JSONDecoder().decode(CacheRetentionTime.self, from: cacheRetentionData) { self.cacheRetentionTime = retentionTime } else { self.cacheRetentionTime = .oneWeek } // Initialize thumbnailDisplayScreen let _ = UserDefaults.standard.string(forKey: SettingsKey.thumbnailDisplayScreen) ?? ThumbnailDisplayScreen.automatic.rawValue // No need to set it since it's a computed property // Load action order with migration for new actions if let savedOrder = UserDefaults.standard.stringArray(forKey: SettingsManager.actionOrderKey) { var loadedOrder = savedOrder.compactMap { ActionType(rawValue: $0) } // Migration: ensure all new actions are included let allActions = ActionType.allCases for action in allActions { if !loadedOrder.contains(action) { loadedOrder.append(action) } } actionOrder = loadedOrder } else { actionOrder = ActionType.allCases } // Initialize hasCompletedFirstLaunch self.hasCompletedFirstLaunch = UserDefaults.standard.object(forKey: SettingsKey.hasCompletedFirstLaunch) as? Bool ?? false isInitializing = false // RESET FLAG } private enum CodingKeys: String, CodingKey { case screenshotFolder case filenamePrefix case filenameFormatPreset case filenameCustomFormat case saveAfterEdit case playSoundOnCapture case thumbnailTimer case closeAfterDrag case thumbnailFixedSize case showFolderButton case startAppOnLogin case autoSaveScreenshot case closeAfterSave } required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) screenshotFolder = try container.decodeIfPresent(String.self, forKey: .screenshotFolder) filenamePrefix = try container.decodeIfPresent(String.self, forKey: .filenamePrefix) ?? "Screenshot" filenameCustomFormat = try container.decodeIfPresent(String.self, forKey: .filenameCustomFormat) ?? "{YYYY}-{MM}-{DD}_{hh}.{mm}.{ss}" saveAfterEdit = try container.decodeIfPresent(Bool.self, forKey: .saveAfterEdit) ?? false playSoundOnCapture = try container.decodeIfPresent(Bool.self, forKey: .playSoundOnCapture) ?? true thumbnailTimer = try container.decodeIfPresent(Int.self, forKey: .thumbnailTimer) ?? 0 closeAfterDrag = try container.decodeIfPresent(Bool.self, forKey: .closeAfterDrag) ?? false let presetRaw = try container.decodeIfPresent(Int.self, forKey: .filenameFormatPreset) ?? FilenameFormatPreset.macOSStyle.rawValue filenameFormatPreset = FilenameFormatPreset(rawValue: presetRaw) ?? .macOSStyle thumbnailFixedSize = try container.decodeIfPresent(ThumbnailFixedSize.self, forKey: .thumbnailFixedSize) ?? .medium showFolderButton = try container.decodeIfPresent(Bool.self, forKey: .showFolderButton) ?? true startAppOnLogin = try container.decodeIfPresent(Bool.self, forKey: .startAppOnLogin) ?? false autoSaveScreenshot = try container.decodeIfPresent(Bool.self, forKey: .autoSaveScreenshot) ?? false closeAfterSave = try container.decodeIfPresent(Bool.self, forKey: .closeAfterSave) ?? false } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(screenshotFolder, forKey: .screenshotFolder) try container.encode(filenamePrefix, forKey: .filenamePrefix) try container.encode(filenameFormatPreset.rawValue, forKey: .filenameFormatPreset) try container.encode(filenameCustomFormat, forKey: .filenameCustomFormat) try container.encode(saveAfterEdit, forKey: .saveAfterEdit) try container.encode(playSoundOnCapture, forKey: .playSoundOnCapture) try container.encode(thumbnailTimer, forKey: .thumbnailTimer) try container.encode(closeAfterDrag, forKey: .closeAfterDrag) try container.encode(thumbnailFixedSize, forKey: .thumbnailFixedSize) try container.encode(showFolderButton, forKey: .showFolderButton) try container.encode(startAppOnLogin, forKey: .startAppOnLogin) try container.encode(autoSaveScreenshot, forKey: .autoSaveScreenshot) try container.encode(closeAfterSave, forKey: .closeAfterSave) } func resetToDefaults() { objectWillChange.send() // Stuur eenmalig aan het begin // Wijs defaults direct toe aan de properties; de didSets zorgen voor opslaan en notificaties. screenshotFolder = defaultScreenshotFolder() filenamePrefix = "Schermafbeelding" filenameFormatPreset = .macOSStyle filenameCustomFormat = "{YYYY}-{MM}-{DD}_{hh}.{mm}.{ss}" saveAfterEdit = false playSoundOnCapture = true thumbnailTimer = 0 closeAfterDrag = false thumbnailFixedSize = .medium showFolderButton = true startAppOnLogin = false autoSaveScreenshot = false closeAfterSave = false isRenameActionEnabled = true isStashActionEnabled = true isOCRActionEnabled = true isClipboardActionEnabled = true isCancelActionEnabled = true isRemoveActionEnabled = true isDeleteActionEnabled = true isCancelDragActionEnabled = true stashAlwaysOnTop = false hideDesktopIconsDuringScreenshot = false // ๐Ÿ”ฅ๐Ÿ’Ž MEGA NIEUWE RESET VOOR STASH PREVIEW SIZE! ๐Ÿ’Ž๐Ÿ”ฅ stashPreviewSize = .medium // ๐Ÿ”ฅ๐Ÿ’ฅโšก HYPERMODE STASH GRID RESET! โšก๐Ÿ’ฅ๐Ÿ”ฅ stashGridMode = .fixedColumns stashMaxColumns = 2 stashMaxRows = 1 // ๐Ÿ”ฅ NIEUW: Reset persistent stash persistentStash = false // ๐Ÿ”„ RESET UPDATE SETTINGS automaticUpdates = true includePreReleases = false // ๐ŸŽน RESET CUSTOM SHORTCUT SETTINGS useCustomShortcut = false customShortcutModifiers = 0 customShortcutKey = 0 // Verwijder de saveSettings() aanroep. // Verwijder de individuele NotificationCenter.default.post calls hier; didSets handelen dat af. } private func defaultScreenshotFolder() -> String? { // Voorbeeld: probeer ~/Pictures/Screenshots, anders nil if let picturesURL = FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask).first { let screenshotsURL = picturesURL.appendingPathComponent("Screenshots") // Maak de map als hij niet bestaat (optioneel) // try? FileManager.default.createDirectory(at: screenshotsURL, withIntermediateDirectories: true, attributes: nil) return screenshotsURL.path } return nil } func resetActionOrder() { actionOrder = ActionType.allCases } func moveAction(_ action: ActionType, direction: Int) { guard let currentIndex = actionOrder.firstIndex(of: action) else { return } let newIndex = currentIndex + direction guard newIndex >= 0 && newIndex < actionOrder.count else { return } actionOrder.swapAt(currentIndex, newIndex) } // saveSettings() is waarschijnlijk niet nodig als @Published didSets goed werken. // Als je het toch wilt: /* func saveSettings() { UserDefaults.standard.set(screenshotFolder, forKey: SettingsKey.screenshotFolder) // ... etc. voor alle settings ... UserDefaults.standard.synchronize() // Optioneel, gebeurt periodiek } */ } // MARK: - Settings Window UI (Nieuwe structuur met TabView) struct SettingsSnapshot { var screenshotFolder: String? var thumbnailTimer: Int var closeAfterDrag: Bool var thumbnailFixedSize: ThumbnailFixedSize var showFolderButton: Bool var closeAfterSave: Bool var playSoundOnCapture: Bool var filenamePrefix: String var filenameFormatPreset: FilenameFormatPreset var filenameCustomFormat: String var startAppOnLogin: Bool var autoSaveScreenshot: Bool var thumbnailDisplayScreen: ThumbnailDisplayScreen var stashAlwaysOnTop: Bool var hideDesktopIconsDuringScreenshot: Bool static func captureCurrent() -> SettingsSnapshot { let s = SettingsManager.shared return SettingsSnapshot( screenshotFolder: s.screenshotFolder, thumbnailTimer: s.thumbnailTimer, closeAfterDrag: s.closeAfterDrag, thumbnailFixedSize: s.thumbnailFixedSize, showFolderButton: s.showFolderButton, closeAfterSave: s.closeAfterSave, playSoundOnCapture: s.playSoundOnCapture, filenamePrefix: s.filenamePrefix, filenameFormatPreset: s.filenameFormatPreset, filenameCustomFormat: s.filenameCustomFormat, startAppOnLogin: s.startAppOnLogin, autoSaveScreenshot: s.autoSaveScreenshot, thumbnailDisplayScreen: s.thumbnailDisplayScreen, stashAlwaysOnTop: s.stashAlwaysOnTop, hideDesktopIconsDuringScreenshot: s.hideDesktopIconsDuringScreenshot ) } func apply(to manager: SettingsManager = SettingsManager.shared) { manager.screenshotFolder = screenshotFolder manager.thumbnailTimer = thumbnailTimer manager.closeAfterDrag = closeAfterDrag manager.thumbnailFixedSize = thumbnailFixedSize manager.showFolderButton = showFolderButton manager.closeAfterSave = closeAfterSave manager.playSoundOnCapture = playSoundOnCapture manager.filenamePrefix = filenamePrefix manager.filenameFormatPreset = filenameFormatPreset manager.filenameCustomFormat = filenameCustomFormat manager.startAppOnLogin = startAppOnLogin manager.autoSaveScreenshot = autoSaveScreenshot manager.thumbnailDisplayScreen = thumbnailDisplayScreen manager.stashAlwaysOnTop = stashAlwaysOnTop manager.hideDesktopIconsDuringScreenshot = hideDesktopIconsDuringScreenshot } } // MARK: - Cache Manager struct CacheManager { static let shared = CacheManager() private init() {} // Get thumbnail directory path private var thumbnailDirectory: URL { let appSupportDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! let shotScreenDirectory = appSupportDirectory.appendingPathComponent("ShotScreen") let thumbnailsDirectory = shotScreenDirectory.appendingPathComponent("Thumbnails") return thumbnailsDirectory } // Calculate cache size in MB func getCacheSize() -> Double { do { let contents = try FileManager.default.contentsOfDirectory(at: thumbnailDirectory, includingPropertiesForKeys: [.fileSizeKey], options: []) var totalSize: Int64 = 0 for fileURL in contents { // Skip thumbnail restoration directory from cache size calculation if fileURL.lastPathComponent == "thumbnail_restoration" { continue } do { let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey]) if let fileSize = resourceValues.fileSize { totalSize += Int64(fileSize) } } catch { print("โš ๏ธ Error getting file size for \(fileURL.lastPathComponent): \(error)") } } // Convert bytes to MB return Double(totalSize) / (1024 * 1024) } catch { print("โš ๏ธ Error calculating cache size: \(error)") return 0.0 } } // Get cache file count func getCacheFileCount() -> Int { do { let contents = try FileManager.default.contentsOfDirectory(at: thumbnailDirectory, includingPropertiesForKeys: [.isDirectoryKey], options: []) var fileCount = 0 for fileURL in contents { // Skip thumbnail restoration directory if fileURL.lastPathComponent == "thumbnail_restoration" { continue } // Only count PNG files, not directories let resourceValues = try? fileURL.resourceValues(forKeys: [.isDirectoryKey]) let isDirectory = resourceValues?.isDirectory ?? false if !isDirectory && fileURL.pathExtension == "png" { fileCount += 1 } } return fileCount } catch { print("โš ๏ธ Error getting cache file count: \(error)") return 0 } } // Clear all cache (except active thumbnails) func clearCache(preserveActiveThumbnails: Bool = true) -> (deletedFiles: Int, savedSpace: Double) { do { let contents = try FileManager.default.contentsOfDirectory(at: thumbnailDirectory, includingPropertiesForKeys: [.fileSizeKey, .creationDateKey], options: []) var deletedFiles = 0 var savedSpace: Int64 = 0 let activeThumbnailPath = getActiveThumbnailPath() for fileURL in contents { // Skip if this is the active thumbnail and we want to preserve it if preserveActiveThumbnails && fileURL.path == activeThumbnailPath { print("๐Ÿ”’ Preserving active thumbnail: \(fileURL.lastPathComponent)") continue } // Skip thumbnail restoration directory completely if fileURL.lastPathComponent == "thumbnail_restoration" { print("๐Ÿ”’ Preserving thumbnail restoration directory: \(fileURL.lastPathComponent)") continue } // Skip thumbnail restoration backup files if fileURL.lastPathComponent.contains("latest_backup") { print("๐Ÿ”’ Preserving thumbnail restoration backup file: \(fileURL.lastPathComponent)") continue } do { let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey]) let fileSize = resourceValues.fileSize ?? 0 try FileManager.default.removeItem(at: fileURL) deletedFiles += 1 savedSpace += Int64(fileSize) print("๐Ÿ—‘๏ธ Deleted cache file: \(fileURL.lastPathComponent)") } catch { print("โš ๏ธ Failed to delete \(fileURL.lastPathComponent): \(error)") } } let savedSpaceMB = Double(savedSpace) / (1024 * 1024) print("โœ… Cache cleanup complete: \(deletedFiles) files deleted, \(String(format: "%.1f", savedSpaceMB)) MB freed") return (deletedFiles, savedSpaceMB) } catch { print("โŒ Error during cache cleanup: \(error)") return (0, 0.0) } } // Clean cache based on retention time func cleanupOldCache() { let retentionTime = SettingsManager.shared.cacheRetentionTime // Don't cleanup if retention is set to forever guard let maxAge = retentionTime.timeInterval else { print("๐Ÿ—‚๏ธ Cache retention set to forever - no automatic cleanup") return } do { let contents = try FileManager.default.contentsOfDirectory(at: thumbnailDirectory, includingPropertiesForKeys: [.creationDateKey, .fileSizeKey], options: []) let cutoffDate = Date().addingTimeInterval(-maxAge) let activeThumbnailPath = getActiveThumbnailPath() print("๐Ÿงช CLEANUP: Checking \(contents.count) files. Cutoff time: \(cutoffDate). Max age: \(Int(maxAge))s") var deletedFiles = 0 var savedSpace: Int64 = 0 var checkedFiles = 0 for fileURL in contents { // Skip active thumbnail if fileURL.path == activeThumbnailPath { print("๐Ÿงช CLEANUP: Skipping active thumbnail: \(fileURL.lastPathComponent)") continue } // Skip thumbnail restoration directory completely if fileURL.lastPathComponent == "thumbnail_restoration" { continue } // Skip thumbnail restoration backup files if fileURL.lastPathComponent.contains("latest_backup") { continue } do { let resourceValues = try fileURL.resourceValues(forKeys: [.creationDateKey, .fileSizeKey]) if let creationDate = resourceValues.creationDate { checkedFiles += 1 let age = Date().timeIntervalSince(creationDate) print("๐Ÿงช CLEANUP: \(fileURL.lastPathComponent) - age: \(Int(age))s, created: \(creationDate)") if creationDate < cutoffDate { let fileSize = resourceValues.fileSize ?? 0 try FileManager.default.removeItem(at: fileURL) deletedFiles += 1 savedSpace += Int64(fileSize) print("๐Ÿ—‘๏ธ DELETED: \(fileURL.lastPathComponent) (was \(Int(age))s old)") } else { print("โœ… KEEPING: \(fileURL.lastPathComponent) (only \(Int(age))s old)") } } } catch { print("โš ๏ธ Error processing \(fileURL.lastPathComponent): \(error)") } } if deletedFiles > 0 { let savedSpaceMB = Double(savedSpace) / (1024 * 1024) print("โœ… Auto cache cleanup: \(deletedFiles) old files deleted, \(String(format: "%.1f", savedSpaceMB)) MB freed") } else if checkedFiles > 0 { print("๐Ÿงช CLEANUP: No files old enough to delete (\(checkedFiles) files checked)") } else { print("๐Ÿงช CLEANUP: No cache files found to check") } } catch { print("โŒ Error during automatic cache cleanup: \(error)") } } // Get active thumbnail path (to avoid deleting currently open thumbnail) private func getActiveThumbnailPath() -> String? { // Try to get the active thumbnail URL from ScreenshotApp if let app = NSApp.delegate as? ScreenshotApp, let tempURL = app.getTempURL() { return tempURL.path } return nil } }