import Defaults import Foundation import SwiftUI import SwiftyJSON final class ImportExportSettingsModel: ObservableObject { static let shared = ImportExportSettingsModel() static var exportFile: URL { YatteeApp.settingsExportDirectory .appendingPathComponent("Yattee Settings from \(Constants.deviceName).\(settingsExtension)") } static var settingsExtension: String { "yatteesettings" } enum ExportGroup: String, Identifiable, CaseIterable { case browsingSettings case playerSettings case controlsSettings case qualitySettings case historySettings case sponsorBlockSettings case advancedSettings case locationsSettings case instances case accounts case accountsUnencryptedPasswords case recentlyOpened case otherData static var settingsGroups: [Self] { [.browsingSettings, .playerSettings, .controlsSettings, .qualitySettings, .historySettings, .sponsorBlockSettings, .advancedSettings] } static var locationsGroups: [Self] { [.locationsSettings, .instances, .accounts, .accountsUnencryptedPasswords] } static var otherGroups: [Self] { [.recentlyOpened, .otherData] } var id: RawValue { rawValue } var label: String { switch self { case .browsingSettings: return "Browsing" case .playerSettings: return "Player" case .controlsSettings: return "Controls" case .qualitySettings: return "Quality" case .historySettings: return "History" case .sponsorBlockSettings: return "SponsorBlock" case .locationsSettings: return "Public Locations" case .instances: return "Custom Locations" case .accounts: return "Accounts" case .accountsUnencryptedPasswords: return "Accounts passwords (unencrypted)" case .advancedSettings: return "Advanced" case .recentlyOpened: return "Recents" case .otherData: return "Other data" } } } @Published var selectedExportGroups = Set() static var defaultExportGroups = Set([ .browsingSettings, .playerSettings, .controlsSettings, .qualitySettings, .historySettings, .sponsorBlockSettings, .locationsSettings, .instances, .accounts, .advancedSettings ]) @Published var isExportInProgress = false private var navigation = NavigationModel.shared private var settings = SettingsModel.shared func toggleExportGroupSelection(_ group: ExportGroup) { if isGroupSelected(group) { selectedExportGroups.remove(group) } else { selectedExportGroups.insert(group) } removeNotEnabledSelectedGroups() } func reset() { isExportInProgress = false selectedExportGroups = Self.defaultExportGroups } func reset(_ model: ImportSettingsFileModel? = nil) { reset() guard let model else { return } selectedExportGroups = selectedExportGroups.filter { model.isGroupIncludedInFile($0) } } func exportAction() { DispatchQueue.global(qos: .background).async { [weak self] in var writingOptions: JSONSerialization.WritingOptions = [] #if DEBUG writingOptions.insert(.prettyPrinted) writingOptions.insert(.sortedKeys) #endif try? self?.jsonForExport?.rawString(options: writingOptions)?.write(to: Self.exportFile, atomically: true, encoding: String.Encoding.utf8) #if os(macOS) DispatchQueue.main.async { [weak self] in self?.isExportInProgress = false } NSWorkspace.shared.selectFile(Self.exportFile.path, inFileViewerRootedAtPath: YatteeApp.settingsExportDirectory.path) #endif } } private var jsonForExport: JSON? { [ "metadata": metadataJSON, "browsingSettings": selectedExportGroups.contains(.browsingSettings) ? BrowsingSettingsGroupExporter().exportJSON : JSON(), "playerSettings": selectedExportGroups.contains(.playerSettings) ? PlayerSettingsGroupExporter().exportJSON : JSON(), "controlsSettings": selectedExportGroups.contains(.controlsSettings) ? ConstrolsSettingsGroupExporter().exportJSON : JSON(), "qualitySettings": selectedExportGroups.contains(.qualitySettings) ? QualitySettingsGroupExporter().exportJSON : JSON(), "historySettings": selectedExportGroups.contains(.historySettings) ? HistorySettingsGroupExporter().exportJSON : JSON(), "sponsorBlockSettings": selectedExportGroups.contains(.sponsorBlockSettings) ? SponsorBlockSettingsGroupExporter().exportJSON : JSON(), "locationsSettings": LocationsSettingsGroupExporter( includePublicInstances: isGroupSelected(.locationsSettings), includeInstances: isGroupSelected(.instances), includeAccounts: isGroupSelected(.accounts), includeAccountsUnencryptedPasswords: isGroupSelected(.accountsUnencryptedPasswords) ).exportJSON, "advancedSettings": selectedExportGroups.contains(.advancedSettings) ? AdvancedSettingsGroupExporter().exportJSON : JSON(), "recentlyOpened": selectedExportGroups.contains(.recentlyOpened) ? RecentlyOpenedExporter().exportJSON : JSON(), "otherData": selectedExportGroups.contains(.otherData) ? OtherDataSettingsGroupExporter().exportJSON : JSON() ] } private var metadataJSON: JSON { [ "build": YatteeApp.build, "timestamp": "\(Date().timeIntervalSince1970)", "platform": Constants.platform ] } func isGroupSelected(_ group: ExportGroup) -> Bool { selectedExportGroups.contains(group) } func isGroupEnabled(_ group: ExportGroup) -> Bool { switch group { case .accounts: return selectedExportGroups.contains(.instances) case .accountsUnencryptedPasswords: return selectedExportGroups.contains(.instances) && selectedExportGroups.contains(.accounts) default: return true } } func removeNotEnabledSelectedGroups() { selectedExportGroups = selectedExportGroups.filter { isGroupEnabled($0) } } var isExportAvailable: Bool { !selectedExportGroups.isEmpty && !isExportInProgress } }