import Foundation import CoreData import Kingfisher class SettingsRepository: PSettingsRepository { private let coreDataManager = CoreDataManager.shared private let userDefault = UserDefaults.standard private let keychainHelper = KeychainHelper.shared var hasFinishedSetup: Bool { get { return userDefault.value(forKey: "hasFinishedSetup") as? Bool ?? false } set { userDefault.set(newValue, forKey: "hasFinishedSetup") } } func saveSettings(_ settings: Settings) async throws { // Save credentials to keychain if let endpoint = settings.endpoint, !endpoint.isEmpty { keychainHelper.saveEndpoint(endpoint) } if let username = settings.username, !username.isEmpty { keychainHelper.saveUsername(username) } if let password = settings.password, !password.isEmpty { keychainHelper.savePassword(password) } if let token = settings.token, !token.isEmpty { keychainHelper.saveToken(token) } // Save UI preferences to Core Data let context = coreDataManager.context return try await withCheckedThrowingContinuation { continuation in context.perform { do { let fetchRequest: NSFetchRequest = SettingEntity.fetchRequest() let existingSettings = try context.fetch(fetchRequest).first ?? SettingEntity(context: context) if let fontFamily = settings.fontFamily { existingSettings.fontFamily = fontFamily.rawValue } if let fontSize = settings.fontSize { existingSettings.fontSize = fontSize.rawValue } if let enableTTS = settings.enableTTS { existingSettings.enableTTS = enableTTS } if let theme = settings.theme { existingSettings.theme = theme.rawValue } if let urlOpener = settings.urlOpener { existingSettings.urlOpener = urlOpener.rawValue } if let cardLayoutStyle = settings.cardLayoutStyle { existingSettings.cardLayoutStyle = cardLayoutStyle.rawValue } if let tagSortOrder = settings.tagSortOrder { existingSettings.tagSortOrder = tagSortOrder.rawValue } try context.save() continuation.resume() } catch { continuation.resume(throwing: error) } } } } func loadSettings() async throws -> Settings? { let context = coreDataManager.context return try await withCheckedThrowingContinuation { continuation in context.perform { do { let fetchRequest: NSFetchRequest = SettingEntity.fetchRequest() fetchRequest.fetchLimit = 1 let settingEntities = try context.fetch(fetchRequest) let settingEntity = settingEntities.first // Load credentials from keychain only let endpoint = self.keychainHelper.loadEndpoint() let username = self.keychainHelper.loadUsername() let password = self.keychainHelper.loadPassword() let token = self.keychainHelper.loadToken() // Load UI preferences from Core Data let settings = Settings( endpoint: endpoint, username: username, password: password, token: token, fontFamily: FontFamily(rawValue: settingEntity?.fontFamily ?? FontFamily.system.rawValue), fontSize: FontSize(rawValue: settingEntity?.fontSize ?? FontSize.medium.rawValue), enableTTS: settingEntity?.enableTTS, theme: Theme(rawValue: settingEntity?.theme ?? Theme.system.rawValue), cardLayoutStyle: CardLayoutStyle(rawValue: settingEntity?.cardLayoutStyle ?? CardLayoutStyle.magazine.rawValue), tagSortOrder: TagSortOrder(rawValue: settingEntity?.tagSortOrder ?? TagSortOrder.byCount.rawValue), urlOpener: UrlOpener(rawValue: settingEntity?.urlOpener ?? UrlOpener.inAppBrowser.rawValue) ) continuation.resume(returning: settings) } catch { continuation.resume(throwing: error) } } } } func clearSettings() async throws { // Clear credentials from keychain keychainHelper.clearCredentials() // Also clear from Core Data let context = coreDataManager.context return try await withCheckedThrowingContinuation { continuation in context.perform { do { let fetchRequest: NSFetchRequest = SettingEntity.fetchRequest() let settingEntities = try context.fetch(fetchRequest) for settingEntity in settingEntities { context.delete(settingEntity) } try context.save() continuation.resume() } catch { continuation.resume(throwing: error) } } } } func saveToken(_ token: String) async throws { // Save to keychain only keychainHelper.saveToken(token) // Wenn ein Token gespeichert wird, Setup als abgeschlossen markieren if !token.isEmpty { self.hasFinishedSetup = true // Notification senden, dass sich der Setup-Status geändert hat DispatchQueue.main.async { NotificationCenter.default.post(name: .setupStatusChanged, object: nil) } } } func saveServerSettings(endpoint: String, username: String, password: String, token: String) async throws { keychainHelper.saveEndpoint(endpoint) keychainHelper.saveUsername(username) keychainHelper.savePassword(password) keychainHelper.saveToken(token) if !token.isEmpty { self.hasFinishedSetup = true DispatchQueue.main.async { NotificationCenter.default.post(name: .setupStatusChanged, object: nil) } } } func saveUsername(_ username: String) async throws { keychainHelper.saveUsername(username) } func savePassword(_ password: String) async throws { keychainHelper.savePassword(password) } func saveHasFinishedSetup(_ hasFinishedSetup: Bool) async throws { return try await withCheckedThrowingContinuation { continuation in self.hasFinishedSetup = hasFinishedSetup // Notification senden, dass sich der Setup-Status geändert hat DispatchQueue.main.async { NotificationCenter.default.post(name: .setupStatusChanged, object: nil) } continuation.resume() } } func saveCardLayoutStyle(_ cardLayoutStyle: CardLayoutStyle) async throws { let context = coreDataManager.context return try await withCheckedThrowingContinuation { continuation in context.perform { do { let fetchRequest: NSFetchRequest = SettingEntity.fetchRequest() let existingSettings = try context.fetch(fetchRequest).first ?? SettingEntity(context: context) existingSettings.cardLayoutStyle = cardLayoutStyle.rawValue try context.save() continuation.resume() } catch { continuation.resume(throwing: error) } } } } func loadCardLayoutStyle() async throws -> CardLayoutStyle { let context = coreDataManager.context return try await withCheckedThrowingContinuation { continuation in context.perform { do { let fetchRequest: NSFetchRequest = SettingEntity.fetchRequest() fetchRequest.fetchLimit = 1 let settingEntities = try context.fetch(fetchRequest) let settingEntity = settingEntities.first let cardLayoutStyle = CardLayoutStyle(rawValue: settingEntity?.cardLayoutStyle ?? CardLayoutStyle.magazine.rawValue) ?? .magazine continuation.resume(returning: cardLayoutStyle) } catch { continuation.resume(throwing: error) } } } } func saveTagSortOrder(_ tagSortOrder: TagSortOrder) async throws { let context = coreDataManager.context return try await withCheckedThrowingContinuation { continuation in context.perform { do { let fetchRequest: NSFetchRequest = SettingEntity.fetchRequest() let existingSettings = try context.fetch(fetchRequest).first ?? SettingEntity(context: context) existingSettings.tagSortOrder = tagSortOrder.rawValue try context.save() continuation.resume() } catch { continuation.resume(throwing: error) } } } } func loadTagSortOrder() async throws -> TagSortOrder { let context = coreDataManager.context return try await withCheckedThrowingContinuation { continuation in context.perform { do { let fetchRequest: NSFetchRequest = SettingEntity.fetchRequest() fetchRequest.fetchLimit = 1 let settingEntities = try context.fetch(fetchRequest) let settingEntity = settingEntities.first let tagSortOrder = TagSortOrder(rawValue: settingEntity?.tagSortOrder ?? TagSortOrder.byCount.rawValue) ?? .byCount continuation.resume(returning: tagSortOrder) } catch { continuation.resume(throwing: error) } } } } // MARK: - Offline Settings private let logger = Logger.data func loadOfflineSettings() async throws -> OfflineSettings { let context = coreDataManager.context return try await withCheckedThrowingContinuation { continuation in context.perform { do { let fetchRequest: NSFetchRequest = SettingEntity.fetchRequest() fetchRequest.fetchLimit = 1 let settingEntities = try context.fetch(fetchRequest) let settingEntity = settingEntities.first let settings = OfflineSettings( enabled: settingEntity?.offlineEnabled ?? false, maxUnreadArticles: settingEntity?.offlineMaxUnreadArticles ?? 20, saveImages: settingEntity?.offlineSaveImages ?? true, lastSyncDate: settingEntity?.offlineLastSyncDate ) self.logger.debug("Loaded offline settings: enabled=\(settings.enabled), max=\(settings.maxUnreadArticlesInt)") continuation.resume(returning: settings) } catch { self.logger.error("Failed to load offline settings: \(error.localizedDescription)") continuation.resume(throwing: error) } } } } func saveOfflineSettings(_ settings: OfflineSettings) async throws { let context = coreDataManager.context return try await withCheckedThrowingContinuation { continuation in context.perform { do { let fetchRequest: NSFetchRequest = SettingEntity.fetchRequest() let existingSettings = try context.fetch(fetchRequest).first ?? SettingEntity(context: context) existingSettings.offlineEnabled = settings.enabled existingSettings.offlineMaxUnreadArticles = settings.maxUnreadArticles existingSettings.offlineSaveImages = settings.saveImages existingSettings.offlineLastSyncDate = settings.lastSyncDate try context.save() self.logger.info("Saved offline settings: enabled=\(settings.enabled), max=\(settings.maxUnreadArticlesInt)") continuation.resume() } catch { self.logger.error("Failed to save offline settings: \(error.localizedDescription)") continuation.resume(throwing: error) } } } } // MARK: - Cache Settings private let maxCacheSizeKey = "KingfisherMaxCacheSize" func getCacheSize() async throws -> UInt { return try await withCheckedThrowingContinuation { continuation in KingfisherManager.shared.cache.calculateDiskStorageSize { result in switch result { case .success(let size): continuation.resume(returning: size) case .failure(let error): continuation.resume(throwing: error) } } } } func getMaxCacheSize() async throws -> UInt { if let savedSize = userDefault.object(forKey: maxCacheSizeKey) as? UInt { return savedSize } else { // Default: 200 MB let defaultBytes = UInt(200 * 1024 * 1024) userDefault.set(defaultBytes, forKey: maxCacheSizeKey) return defaultBytes } } func updateMaxCacheSize(_ sizeInBytes: UInt) async throws { KingfisherManager.shared.cache.diskStorage.config.sizeLimit = sizeInBytes userDefault.set(sizeInBytes, forKey: maxCacheSizeKey) logger.info("Updated max cache size to \(sizeInBytes) bytes") } func clearCache() async throws { return try await withCheckedThrowingContinuation { continuation in KingfisherManager.shared.cache.clearDiskCache { KingfisherManager.shared.cache.clearMemoryCache() self.logger.info("Cache cleared successfully") continuation.resume() } } } }