From 822202e37d2210a8d551b229a28f03e779ed5eec Mon Sep 17 00:00:00 2001 From: Benjamin Erhart Date: Tue, 14 Apr 2026 18:57:37 +0200 Subject: [PATCH] Issue #3: First implementation of a DoH configuration setting. --- dns/HomeView.swift | 11 +++---- dns/ViewModel.swift | 79 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/dns/HomeView.swift b/dns/HomeView.swift index d95d302..765a48c 100644 --- a/dns/HomeView.swift +++ b/dns/HomeView.swift @@ -39,7 +39,6 @@ struct HomeView: View { @EnvironmentObject private var viewModel: ViewModel - @State private var isEnabled = false @State private var serviceStatus: ServiceStatus = .operational private let falsePositiveURL = URL(string: "https://www.sr2.uk/contact")! @@ -58,14 +57,14 @@ struct HomeView: View { VStack(alignment: .leading, spacing: 4) { Text("DNS Protection") .font(.headline) - Text(isEnabled ? "Active" : "Inactive") + Text(viewModel.isDnsEnabled ? "Active" : "Inactive") .font(.caption) - .foregroundStyle(isEnabled ? .green : .secondary) + .foregroundStyle(viewModel.isDnsEnabled ? .green : .secondary) } Spacer() - Toggle("", isOn: $isEnabled) + Toggle("", isOn: $viewModel.isDnsEnabled) .labelsHidden() .tint(.green) } @@ -95,7 +94,7 @@ struct HomeView: View { } // Status section - if isEnabled { + if viewModel.isDnsEnabled { Section { HStack { Label("Status", systemImage: "checkmark.circle.fill") @@ -213,7 +212,7 @@ struct HomeView: View { } } .navigationTitle("SR2® Cloud DNS") - .animation(.default, value: isEnabled) + .animation(.default, value: viewModel.isDnsEnabled) } } } diff --git a/dns/ViewModel.swift b/dns/ViewModel.swift index 4c82680..4ec1ada 100644 --- a/dns/ViewModel.swift +++ b/dns/ViewModel.swift @@ -7,10 +7,89 @@ import Foundation import Combine +import NetworkExtension +import OSLog class ViewModel: NSObject, ObservableObject { + // TODO: Store this in UserDefaults @Published var blocklist: BlocklistOption = .secure + // TODO: Store this in UserDefaults + @Published + var isDnsEnabled = false { + didSet { + if !isProgrammaticChange { + toggleDns() + } + else { + // Reset, so next one is recognized as coming from the user again. + isProgrammaticChange = false + } + } + } + + private var isProgrammaticChange = false + + + private let manager = NEDNSSettingsManager.shared() + + private let log = Logger(subsystem: String(describing: ViewModel.self), category: String(describing: ViewModel.self)) + + + func toggleDns() { + Task { + if isDnsEnabled { + do { + try await manager.loadFromPreferences() + } + catch { + log.error("Error loading preferences: \(error)") + + delayedToggle(false) + + return + } + + let settings = NEDNSOverHTTPSSettings(servers: [blocklist.ipv4, blocklist.ipv6]) + settings.serverURL = URL(string: "https://\(blocklist.server)") + settings.matchDomains = [""] + + + manager.dnsSettings = settings + manager.localizedDescription = blocklist.description + + do { + try await manager.saveToPreferences() + } + catch { + log.error("Error storing preferences: \(error)") + + delayedToggle(false) + } + } + else { + do { + try await manager.removeFromPreferences() + } + catch { + log.error("Error removing preferences: \(error)") + + delayedToggle(true) + } + } + } + } + + private func delayedToggle(_ enabled: Bool) { + Task { + try? await Task.sleep(nanoseconds: 500_000_000) + + await MainActor.run { + isProgrammaticChange = true + isDnsEnabled = enabled + } + } + } }