// // ViewModel.swift // dns // // Created by Benjamin Erhart on 14.04.26. // import Foundation import Combine import NetworkExtension import OSLog class ViewModel: NSObject, ObservableObject { // MARK: Public Properties @Published var blocklist: BlocklistOption = .secure { didSet { Settings.blocklist = blocklist if isDnsEnabled { toggleDns() } else { isProgrammaticChange = false } } } @Published var isDnsEnabled = false { didSet { toggleDns() } } @Published var summaryStatus: SummaryStatus = .pending // MARK: Private Properties private var isProgrammaticChange = false private let manager = NEDNSSettingsManager.shared() private let log = Logger(subsystem: String(describing: ViewModel.self), category: String(describing: ViewModel.self)) override init() { super.init() isProgrammaticChange = true blocklist = Settings.blocklist Task { do { try await manager.loadFromPreferences() } catch { log.error("Error loading preferences: \(error)") return } if manager.isEnabled, let settings = manager.dnsSettings { for dnsServer in BlocklistOption.allCases { if settings.servers.contains(dnsServer.ipv4) { await MainActor.run { isProgrammaticChange = true blocklist = dnsServer isProgrammaticChange = true isDnsEnabled = true } break } } } } Task { await fetchServerStatus() } } // MARK: Public Methods func toggleDns() { guard !isProgrammaticChange else { // Reset, so next one is recognized as coming from the user again. isProgrammaticChange = false return } Task { if isDnsEnabled { manager.dnsSettings = blocklist.settings manager.localizedDescription = blocklist.description do { try await manager.saveToPreferences() } catch { log.error("Error storing preferences: \(error)") delayedToggle(false) } if !manager.isEnabled { delayedToggle(false) } } else { do { try await manager.removeFromPreferences() } catch { log.error("Error removing preferences: \(error)") delayedToggle(true) } } } } func fetchServerStatus() async { do { let (data, _) = try await URLSession.shared.data(for: .init(url: .init(string: "https://status.sr2.uk/index.json")!)) let status = try JSONDecoder().decode(Status.self, from: data) summaryStatus = status.summaryStatus } catch { log.error("Error while checking status: \(error)") } } // MARK: Private Methods private func delayedToggle(_ enabled: Bool) { Task { try? await Task.sleep(nanoseconds: 500_000_000) await MainActor.run { isProgrammaticChange = true isDnsEnabled = enabled } } } }