summaryrefslogtreecommitdiff
path: root/DomainDig/LocalNotificationService.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-21 21:53:16 -0500
committerChristian Cleberg <[email protected]>2026-04-21 21:53:16 -0500
commit1f58092dcb67cded75a850db364f3b46d86181f3 (patch)
tree662731251ed1022b4a1585ec74379f233ca6705a /DomainDig/LocalNotificationService.swift
parent2e90ee4ceca73d5126118920b486149cb227c4e9 (diff)
downloaddomain-dig-1f58092dcb67cded75a850db364f3b46d86181f3.tar.gz
domain-dig-1f58092dcb67cded75a850db364f3b46d86181f3.tar.bz2
domain-dig-1f58092dcb67cded75a850db364f3b46d86181f3.zip
feat(v2.2.0): add foreground monitoring, notifications, and smarter diffs
- implement “Check All” sweep for tracked domains - add local notifications for changes and certificate expiration - introduce change severity filtering (low/medium/high) - enhance change summaries and diff readability - add certificate expiration tracking and alerts - improve watchlist indicators for changes - add sweep summary screen - optimize performance for larger watchlists
Diffstat (limited to 'DomainDig/LocalNotificationService.swift')
-rw-r--r--DomainDig/LocalNotificationService.swift89
1 files changed, 89 insertions, 0 deletions
diff --git a/DomainDig/LocalNotificationService.swift b/DomainDig/LocalNotificationService.swift
new file mode 100644
index 0000000..450d844
--- /dev/null
+++ b/DomainDig/LocalNotificationService.swift
@@ -0,0 +1,89 @@
+import Foundation
+import UserNotifications
+
+@MainActor
+final class LocalNotificationService {
+ static let shared = LocalNotificationService()
+
+ private init() {}
+
+ func configureForegroundPresentation() {
+ UNUserNotificationCenter.current().delegate = NotificationCenterDelegate.shared
+ }
+
+ func requestAuthorizationIfNeeded() async -> Bool {
+ let center = UNUserNotificationCenter.current()
+ let settings = await center.notificationSettings()
+
+ switch settings.authorizationStatus {
+ case .authorized, .provisional, .ephemeral:
+ return true
+ case .notDetermined:
+ return (try? await center.requestAuthorization(options: [.alert, .badge, .sound])) ?? false
+ case .denied:
+ return false
+ @unknown default:
+ return false
+ }
+ }
+
+ func notifyDomainEvent(domain: String, message: String, severity: ChangeSeverity) async {
+ await schedule(
+ identifier: "domain-change-\(domain)",
+ title: domain,
+ body: message,
+ interruptionLevel: severity == .high ? .timeSensitive : .active
+ )
+ }
+
+ func notifyCertificateWarning(domain: String, daysRemaining: Int) async {
+ await schedule(
+ identifier: "cert-warning-\(domain)",
+ title: domain,
+ body: "Certificate expires in \(daysRemaining) days",
+ interruptionLevel: .timeSensitive
+ )
+ }
+
+ func notifySweepComplete(summary: BatchSweepSummary) async {
+ let body = "\(summary.changedDomains) changed, \(summary.warningDomains) warnings, \(summary.unchangedDomains) unchanged"
+ await schedule(
+ identifier: "sweep-complete",
+ title: summary.source == .watchlistRefresh ? "Check All Complete" : "Batch Complete",
+ body: body,
+ interruptionLevel: .active
+ )
+ }
+
+ private func schedule(
+ identifier: String,
+ title: String,
+ body: String,
+ interruptionLevel: UNNotificationInterruptionLevel
+ ) async {
+ let content = UNMutableNotificationContent()
+ content.title = title
+ content.body = body
+ content.sound = .default
+ content.interruptionLevel = interruptionLevel
+
+ let request = UNNotificationRequest(
+ identifier: identifier,
+ content: content,
+ trigger: UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
+ )
+
+ try? await UNUserNotificationCenter.current().add(request)
+ }
+}
+
+private final class NotificationCenterDelegate: NSObject, UNUserNotificationCenterDelegate {
+ static let shared = NotificationCenterDelegate()
+
+ func userNotificationCenter(
+ _ center: UNUserNotificationCenter,
+ willPresent notification: UNNotification
+ ) async -> UNNotificationPresentationOptions {
+ [.banner, .list, .sound]
+ }
+}