2026-04-14 18:20:30 +02:00
|
|
|
//
|
|
|
|
|
// ViewModel.swift
|
|
|
|
|
// dns
|
|
|
|
|
//
|
|
|
|
|
// Created by Benjamin Erhart on 14.04.26.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
import Combine
|
2026-04-14 18:57:37 +02:00
|
|
|
import NetworkExtension
|
|
|
|
|
import OSLog
|
2026-04-14 18:20:30 +02:00
|
|
|
|
|
|
|
|
class ViewModel: NSObject, ObservableObject {
|
|
|
|
|
|
2026-04-15 14:13:47 +02:00
|
|
|
// MARK: Public Properties
|
|
|
|
|
|
2026-04-14 18:20:30 +02:00
|
|
|
@Published
|
2026-04-15 13:40:29 +02:00
|
|
|
var blocklist: BlocklistOption = .secure {
|
|
|
|
|
didSet {
|
|
|
|
|
Settings.blocklist = blocklist
|
|
|
|
|
|
2026-04-15 13:47:36 +02:00
|
|
|
if isDnsEnabled {
|
|
|
|
|
toggleDns()
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
isProgrammaticChange = false
|
|
|
|
|
}
|
2026-04-15 13:40:29 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-14 18:20:30 +02:00
|
|
|
|
2026-04-14 18:57:37 +02:00
|
|
|
@Published
|
|
|
|
|
var isDnsEnabled = false {
|
|
|
|
|
didSet {
|
2026-04-15 13:40:29 +02:00
|
|
|
toggleDns()
|
2026-04-14 18:57:37 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 14:13:47 +02:00
|
|
|
@Published
|
|
|
|
|
var summaryStatus: SummaryStatus = .pending
|
2026-04-14 18:57:37 +02:00
|
|
|
|
|
|
|
|
|
2026-04-15 14:13:47 +02:00
|
|
|
// MARK: Private Properties
|
|
|
|
|
|
|
|
|
|
private var isProgrammaticChange = false
|
|
|
|
|
|
2026-04-14 18:57:37 +02:00
|
|
|
private let manager = NEDNSSettingsManager.shared()
|
|
|
|
|
|
|
|
|
|
private let log = Logger(subsystem: String(describing: ViewModel.self), category: String(describing: ViewModel.self))
|
|
|
|
|
|
|
|
|
|
|
2026-04-15 13:41:38 +02:00
|
|
|
override init() {
|
|
|
|
|
super.init()
|
|
|
|
|
|
|
|
|
|
isProgrammaticChange = true
|
|
|
|
|
blocklist = Settings.blocklist
|
|
|
|
|
|
|
|
|
|
Task {
|
|
|
|
|
do {
|
|
|
|
|
try await manager.loadFromPreferences()
|
|
|
|
|
}
|
|
|
|
|
catch {
|
|
|
|
|
log.error("Error loading preferences: \(error)")
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 15:36:36 +02:00
|
|
|
if manager.isEnabled, let settings = manager.dnsSettings {
|
2026-04-15 13:41:38 +02:00
|
|
|
for dnsServer in BlocklistOption.allCases {
|
|
|
|
|
if settings.servers.contains(dnsServer.ipv4) {
|
|
|
|
|
await MainActor.run {
|
|
|
|
|
isProgrammaticChange = true
|
|
|
|
|
blocklist = dnsServer
|
|
|
|
|
isProgrammaticChange = true
|
|
|
|
|
isDnsEnabled = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-15 14:13:47 +02:00
|
|
|
|
|
|
|
|
Task {
|
|
|
|
|
await fetchServerStatus()
|
|
|
|
|
}
|
2026-04-15 13:41:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-04-15 14:13:47 +02:00
|
|
|
// MARK: Public Methods
|
|
|
|
|
|
2026-04-14 18:57:37 +02:00
|
|
|
func toggleDns() {
|
2026-04-15 13:40:29 +02:00
|
|
|
guard !isProgrammaticChange else {
|
|
|
|
|
// Reset, so next one is recognized as coming from the user again.
|
|
|
|
|
isProgrammaticChange = false
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 18:57:37 +02:00
|
|
|
Task {
|
|
|
|
|
if isDnsEnabled {
|
2026-04-15 13:38:25 +02:00
|
|
|
manager.dnsSettings = blocklist.settings
|
2026-04-14 18:57:37 +02:00
|
|
|
manager.localizedDescription = blocklist.description
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
try await manager.saveToPreferences()
|
|
|
|
|
}
|
|
|
|
|
catch {
|
|
|
|
|
log.error("Error storing preferences: \(error)")
|
|
|
|
|
|
|
|
|
|
delayedToggle(false)
|
|
|
|
|
}
|
2026-04-15 15:36:36 +02:00
|
|
|
|
|
|
|
|
if !manager.isEnabled {
|
|
|
|
|
delayedToggle(false)
|
|
|
|
|
}
|
2026-04-14 18:57:37 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
do {
|
|
|
|
|
try await manager.removeFromPreferences()
|
|
|
|
|
}
|
|
|
|
|
catch {
|
|
|
|
|
log.error("Error removing preferences: \(error)")
|
|
|
|
|
|
|
|
|
|
delayedToggle(true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 14:13:47 +02:00
|
|
|
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
|
|
|
|
|
|
2026-04-14 18:57:37 +02:00
|
|
|
private func delayedToggle(_ enabled: Bool) {
|
|
|
|
|
Task {
|
|
|
|
|
try? await Task.sleep(nanoseconds: 500_000_000)
|
|
|
|
|
|
|
|
|
|
await MainActor.run {
|
|
|
|
|
isProgrammaticChange = true
|
|
|
|
|
isDnsEnabled = enabled
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-14 18:20:30 +02:00
|
|
|
}
|