diff options
Diffstat (limited to 'DomainDig')
| -rw-r--r-- | DomainDig/DomainViewModel+Monitoring.swift | 174 | ||||
| -rw-r--r-- | DomainDig/DomainViewModel.swift | 165 |
2 files changed, 176 insertions, 163 deletions
diff --git a/DomainDig/DomainViewModel+Monitoring.swift b/DomainDig/DomainViewModel+Monitoring.swift new file mode 100644 index 0000000..57f297b --- /dev/null +++ b/DomainDig/DomainViewModel+Monitoring.swift @@ -0,0 +1,174 @@ +import Foundation +import SwiftUI +import UserNotifications + +/// Monitoring configuration surface of `DomainViewModel`: notification +/// authorization, the monitoring settings mutators (scope, interval, adaptivity, +/// sensitivity, quiet hours, alert filter/toggle, per-domain selection), the +/// manual run, and the per-domain status labels. +/// +/// Settings mutations persist through `persistMonitoringSettings(...)` and +/// `sanitizeMonitoringSelection()`, which remain on the main type because the +/// tracked-domain lifecycle (add/delete/clear) calls them too. +extension DomainViewModel { + func refreshMonitoringAuthorizationStatus() async { + let settings = await UNUserNotificationCenter.current().notificationSettings() + monitoringNotificationStatus = settings.authorizationStatus + } + + func setMonitoringEnabled(_ isEnabled: Bool) { + guard !isEnabled || FeatureAccessService.hasAccess(to: .automatedMonitoring) else { + monitoringSettings.isEnabled = false + upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring) + return + } + + monitoringSettings.isEnabled = isEnabled + persistMonitoringSettings(localActivationConfirmed: isEnabled) + monitoringStatusMessage = DomainMonitoringScheduler.shared.syncSchedule() + } + + func setMonitoringScope(_ scope: MonitoringScope) { + monitoringSettings.scope = scope + persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) + } + + func setMonitoringBaseInterval(_ baseInterval: MonitoringBaseInterval) { + guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else { + upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring) + return + } + monitoringSettings.baseInterval = baseInterval.interval + persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) + monitoringStatusMessage = DomainMonitoringScheduler.shared.syncSchedule() + } + + func setMonitoringAdaptiveEnabled(_ isEnabled: Bool) { + guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else { + upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring) + return + } + monitoringSettings.adaptiveEnabled = isEnabled + persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) + monitoringStatusMessage = DomainMonitoringScheduler.shared.syncSchedule() + } + + func setMonitoringSensitivity(_ sensitivity: MonitoringSensitivity) { + guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else { + upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring) + return + } + monitoringSettings.sensitivity = sensitivity + persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) + monitoringStatusMessage = DomainMonitoringScheduler.shared.syncSchedule() + } + + func setMonitoringQuietHours(startHour: Int, endHour: Int, isEnabled: Bool) { + guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else { + upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring) + return + } + monitoringSettings.quietHours = isEnabled ? QuietHours(startHour: startHour, endHour: endHour) : nil + persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) + } + + func setMonitoringAlertFilter(_ filter: MonitoringAlertFilter) { + guard FeatureAccessService.hasAccess(to: .localAlerts) else { + monitoringSettings.alertsEnabled = false + upgradePrompt = FeatureAccessService.upgradePrompt(for: .localAlerts) + return + } + monitoringSettings.alertFilter = filter + persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) + } + + func setMonitoringAlertsEnabled(_ isEnabled: Bool) { + guard !isEnabled || FeatureAccessService.hasAccess(to: .localAlerts) else { + monitoringSettings.alertsEnabled = false + upgradePrompt = FeatureAccessService.upgradePrompt(for: .localAlerts) + return + } + monitoringSettings.alertsEnabled = isEnabled + persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) + } + + func setMonitoringSelection(for trackedDomain: TrackedDomain, isSelected: Bool) { + if isSelected { + if !monitoringSettings.selectedDomainIDs.contains(trackedDomain.id) { + monitoringSettings.selectedDomainIDs.append(trackedDomain.id) + } + } else { + monitoringSettings.selectedDomainIDs.removeAll { $0 == trackedDomain.id } + } + persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) + } + + func toggleMonitoring(for trackedDomain: TrackedDomain) { + guard canEdit(trackedDomain) else { return } + guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else { + upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring) + return + } + guard let index = trackedDomains.firstIndex(where: { $0.id == trackedDomain.id }) else { return } + trackedDomains[index].monitoringEnabled.toggle() + trackedDomains[index].updatedAt = Date() + MonitoringStorage.saveTrackedDomains(trackedDomains) + CloudSyncService.shared.scheduleSyncIfNeeded() + sanitizeMonitoringSelection() + } + + func requestMonitoringNotificationAuthorization() async { + let granted = await LocalNotificationService.shared.requestAuthorizationIfNeeded() + monitoringSettings.alertsEnabled = granted + persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) + await refreshMonitoringAuthorizationStatus() + } + + func runMonitoringNow() { + guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else { + upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring) + return + } + guard !monitoringRunInProgress else { return } + + monitoringRunInProgress = true + monitoringStatusMessage = nil + + Task { [weak self] in + guard let self else { return } + let outcome = await DomainMonitoringService.shared.performMonitoring( + trigger: .manual, + requireEnabledSetting: false + ) + await MainActor.run { + self.refreshMonitoringState() + self.monitoringRunInProgress = false + self.monitoringStatusMessage = outcome.message + } + } + } + + func monitoringIntervalLabel(for trackedDomain: TrackedDomain) -> String { + let state = DomainMonitoringScheduler.normalizedMonitoringState(for: trackedDomain, settings: monitoringSettings) + return Self.intervalLabel(for: state.currentInterval) + } + + func monitoringStatusLabel(for trackedDomain: TrackedDomain) -> String { + let state = trackedDomain.monitoringState + if let lastChangeDate = state.lastChangeDate, + Date().timeIntervalSince(lastChangeDate) <= 6 * 60 * 60 { + return "Recently Changed" + } + if state.consecutiveStableChecks >= 2 { + return "Stable" + } + return trackedDomain.monitoringEnabled ? "Active" : "Paused" + } + + private static func intervalLabel(for interval: TimeInterval) -> String { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = interval < 3600 ? [.minute] : [.hour, .minute] + formatter.unitsStyle = .full + return formatter.string(from: interval) ?? "Unknown" + } +} diff --git a/DomainDig/DomainViewModel.swift b/DomainDig/DomainViewModel.swift index fdf6b84..3814704 100644 --- a/DomainDig/DomainViewModel.swift +++ b/DomainDig/DomainViewModel.swift @@ -1100,167 +1100,6 @@ final class DomainViewModel { await refreshMonitoringAuthorizationStatus() } - func refreshMonitoringAuthorizationStatus() async { - let settings = await UNUserNotificationCenter.current().notificationSettings() - monitoringNotificationStatus = settings.authorizationStatus - } - - func setMonitoringEnabled(_ isEnabled: Bool) { - guard !isEnabled || FeatureAccessService.hasAccess(to: .automatedMonitoring) else { - monitoringSettings.isEnabled = false - upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring) - return - } - - monitoringSettings.isEnabled = isEnabled - persistMonitoringSettings(localActivationConfirmed: isEnabled) - monitoringStatusMessage = DomainMonitoringScheduler.shared.syncSchedule() - } - - func setMonitoringScope(_ scope: MonitoringScope) { - monitoringSettings.scope = scope - persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) - } - - func setMonitoringBaseInterval(_ baseInterval: MonitoringBaseInterval) { - guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else { - upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring) - return - } - monitoringSettings.baseInterval = baseInterval.interval - persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) - monitoringStatusMessage = DomainMonitoringScheduler.shared.syncSchedule() - } - - func setMonitoringAdaptiveEnabled(_ isEnabled: Bool) { - guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else { - upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring) - return - } - monitoringSettings.adaptiveEnabled = isEnabled - persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) - monitoringStatusMessage = DomainMonitoringScheduler.shared.syncSchedule() - } - - func setMonitoringSensitivity(_ sensitivity: MonitoringSensitivity) { - guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else { - upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring) - return - } - monitoringSettings.sensitivity = sensitivity - persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) - monitoringStatusMessage = DomainMonitoringScheduler.shared.syncSchedule() - } - - func setMonitoringQuietHours(startHour: Int, endHour: Int, isEnabled: Bool) { - guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else { - upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring) - return - } - monitoringSettings.quietHours = isEnabled ? QuietHours(startHour: startHour, endHour: endHour) : nil - persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) - } - - func setMonitoringAlertFilter(_ filter: MonitoringAlertFilter) { - guard FeatureAccessService.hasAccess(to: .localAlerts) else { - monitoringSettings.alertsEnabled = false - upgradePrompt = FeatureAccessService.upgradePrompt(for: .localAlerts) - return - } - monitoringSettings.alertFilter = filter - persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) - } - - func setMonitoringAlertsEnabled(_ isEnabled: Bool) { - guard !isEnabled || FeatureAccessService.hasAccess(to: .localAlerts) else { - monitoringSettings.alertsEnabled = false - upgradePrompt = FeatureAccessService.upgradePrompt(for: .localAlerts) - return - } - monitoringSettings.alertsEnabled = isEnabled - persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) - } - - func setMonitoringSelection(for trackedDomain: TrackedDomain, isSelected: Bool) { - if isSelected { - if !monitoringSettings.selectedDomainIDs.contains(trackedDomain.id) { - monitoringSettings.selectedDomainIDs.append(trackedDomain.id) - } - } else { - monitoringSettings.selectedDomainIDs.removeAll { $0 == trackedDomain.id } - } - persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) - } - - func toggleMonitoring(for trackedDomain: TrackedDomain) { - guard canEdit(trackedDomain) else { return } - guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else { - upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring) - return - } - guard let index = trackedDomains.firstIndex(where: { $0.id == trackedDomain.id }) else { return } - trackedDomains[index].monitoringEnabled.toggle() - trackedDomains[index].updatedAt = Date() - MonitoringStorage.saveTrackedDomains(trackedDomains) - CloudSyncService.shared.scheduleSyncIfNeeded() - sanitizeMonitoringSelection() - } - - func requestMonitoringNotificationAuthorization() async { - let granted = await LocalNotificationService.shared.requestAuthorizationIfNeeded() - monitoringSettings.alertsEnabled = granted - persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled) - await refreshMonitoringAuthorizationStatus() - } - - func runMonitoringNow() { - guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else { - upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring) - return - } - guard !monitoringRunInProgress else { return } - - monitoringRunInProgress = true - monitoringStatusMessage = nil - - Task { [weak self] in - guard let self else { return } - let outcome = await DomainMonitoringService.shared.performMonitoring( - trigger: .manual, - requireEnabledSetting: false - ) - await MainActor.run { - self.refreshMonitoringState() - self.monitoringRunInProgress = false - self.monitoringStatusMessage = outcome.message - } - } - } - - func monitoringIntervalLabel(for trackedDomain: TrackedDomain) -> String { - let state = DomainMonitoringScheduler.normalizedMonitoringState(for: trackedDomain, settings: monitoringSettings) - return Self.intervalLabel(for: state.currentInterval) - } - - func monitoringStatusLabel(for trackedDomain: TrackedDomain) -> String { - let state = trackedDomain.monitoringState - if let lastChangeDate = state.lastChangeDate, - Date().timeIntervalSince(lastChangeDate) <= 6 * 60 * 60 { - return "Recently Changed" - } - if state.consecutiveStableChecks >= 2 { - return "Stable" - } - return trackedDomain.monitoringEnabled ? "Active" : "Paused" - } - - private static func intervalLabel(for interval: TimeInterval) -> String { - let formatter = DateComponentsFormatter() - formatter.allowedUnits = interval < 3600 ? [.minute] : [.hour, .minute] - formatter.unitsStyle = .full - return formatter.string(from: interval) ?? "Unknown" - } - func rerunLookup(from entry: HistoryEntry, useSnapshotResolver: Bool) { if useSnapshotResolver { UserDefaults.standard.set(entry.resolverURLString, forKey: DNSResolverOption.userDefaultsKey) @@ -2590,13 +2429,13 @@ final class DomainViewModel { return existingMax + 1 } - private func persistMonitoringSettings(localActivationConfirmed: Bool = false) { + func persistMonitoringSettings(localActivationConfirmed: Bool = false) { monitoringSettings = MonitoringStorage.sanitizeSettings(monitoringSettings, trackedDomains: trackedDomains) MonitoringStorage.saveSettings(monitoringSettings) CloudSyncService.shared.markMonitoringSettingsChanged(localActivationConfirmed: localActivationConfirmed) } - private func sanitizeMonitoringSelection() { + func sanitizeMonitoringSelection() { monitoringSettings = MonitoringStorage.sanitizeSettings(monitoringSettings, trackedDomains: trackedDomains) MonitoringStorage.saveSettings(monitoringSettings) CloudSyncService.shared.markMonitoringSettingsChanged(localActivationConfirmed: false) |
