aboutsummaryrefslogtreecommitdiff
path: root/DomainDig/LocalNotificationService.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-07-17 08:36:05 -0500
committerChristian Cleberg <[email protected]>2026-07-17 10:12:36 -0500
commitb4b94d54cbed9334a28cdcd70d3a8a0fd06e04ef (patch)
tree8fa4b408729da32ec2c547081ec2bba13b893fa2 /DomainDig/LocalNotificationService.swift
parent065e1e77ff00e8f9c8785393b5eba6c3e0bb2bb1 (diff)
downloaddomain-dig-b4b94d54cbed9334a28cdcd70d3a8a0fd06e04ef.tar.gz
domain-dig-b4b94d54cbed9334a28cdcd70d3a8a0fd06e04ef.tar.bz2
domain-dig-b4b94d54cbed9334a28cdcd70d3a8a0fd06e04ef.zip
Implement v4.6.0: sweep Live Activity, share extension, iPad split view, actionable notifications
- Sweep Live Activity: SweepActivityAttributes (Shared/), lock-screen + Dynamic Island UI in the widget extension, driven by SweepActivityController wired into the batch pipeline (begin/update/end); NSSupportsLiveActivities in Info.plist. - Share extension (DomainDigShareExtension): accepts a web URL from the share sheet, extracts the host, and hands it to the app via the App Group inbox (DomainDigShareInbox); the app consumes it on activation and inspects it. - iPad layout: RootTabView uses NavigationSplitView in the regular size class and the tab bar in compact. - Actionable notifications: per-domain threadIdentifier grouping, a Re-inspect action, and tap routing into the domain detail via the intent router. - Bump version to 4.6.0 (build 38) across app, widget, and share targets.
Diffstat (limited to 'DomainDig/LocalNotificationService.swift')
-rw-r--r--DomainDig/LocalNotificationService.swift65
1 files changed, 59 insertions, 6 deletions
diff --git a/DomainDig/LocalNotificationService.swift b/DomainDig/LocalNotificationService.swift
index eef0567..f417619 100644
--- a/DomainDig/LocalNotificationService.swift
+++ b/DomainDig/LocalNotificationService.swift
@@ -7,8 +7,27 @@ final class LocalNotificationService {
private init() {}
+ static let domainUserInfoKey = "domain"
+ static let domainCategoryIdentifier = "domain-event"
+ static let reinspectActionIdentifier = "reinspect"
+
func configureForegroundPresentation() {
- UNUserNotificationCenter.current().delegate = NotificationCenterDelegate.shared
+ let center = UNUserNotificationCenter.current()
+ center.delegate = NotificationCenterDelegate.shared
+
+ let reinspect = UNNotificationAction(
+ identifier: Self.reinspectActionIdentifier,
+ title: "Re-inspect",
+ options: []
+ )
+ center.setNotificationCategories([
+ UNNotificationCategory(
+ identifier: Self.domainCategoryIdentifier,
+ actions: [reinspect],
+ intentIdentifiers: [],
+ options: []
+ )
+ ])
}
func requestAuthorizationIfNeeded() async -> Bool {
@@ -44,7 +63,8 @@ final class LocalNotificationService {
identifier: "domain-change-\(domain)",
title: domain,
body: message,
- interruptionLevel: severity == .high ? .timeSensitive : .active
+ interruptionLevel: severity == .high ? .timeSensitive : .active,
+ domain: domain
)
}
@@ -53,7 +73,8 @@ final class LocalNotificationService {
identifier: "cert-warning-\(domain)",
title: domain,
body: "Certificate expires in \(daysRemaining) days",
- interruptionLevel: .timeSensitive
+ interruptionLevel: .timeSensitive,
+ domain: domain
)
}
@@ -74,7 +95,8 @@ final class LocalNotificationService {
identifier: "monitoring-\(domain)-\(UUID().uuidString)",
title: domain,
body: message,
- interruptionLevel: interruptionLevel
+ interruptionLevel: interruptionLevel,
+ domain: domain
)
}
@@ -103,7 +125,8 @@ final class LocalNotificationService {
identifier: "monitoring-summary-\(domain)-\(UUID().uuidString)",
title: domain,
body: body,
- interruptionLevel: interruptionLevel
+ interruptionLevel: interruptionLevel,
+ domain: domain
)
}
@@ -127,13 +150,20 @@ final class LocalNotificationService {
identifier: String,
title: String,
body: String,
- interruptionLevel: UNNotificationInterruptionLevel
+ interruptionLevel: UNNotificationInterruptionLevel,
+ domain: String? = nil
) async {
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = .default
content.interruptionLevel = interruptionLevel
+ if let domain {
+ // Group alerts per domain and let taps/actions route back into it.
+ content.threadIdentifier = domain
+ content.userInfo = [Self.domainUserInfoKey: domain]
+ content.categoryIdentifier = Self.domainCategoryIdentifier
+ }
let request = UNNotificationRequest(
identifier: identifier,
@@ -154,4 +184,27 @@ private final class NotificationCenterDelegate: NSObject, UNUserNotificationCent
) async -> UNNotificationPresentationOptions {
[.banner, .list, .sound]
}
+
+ func userNotificationCenter(
+ _ center: UNUserNotificationCenter,
+ didReceive response: UNNotificationResponse
+ ) async {
+ let userInfo = response.notification.request.content.userInfo
+ guard let domain = userInfo[LocalNotificationService.domainUserInfoKey] as? String,
+ !domain.isEmpty
+ else { return }
+
+ let action: DomainDigDeepLink.Action
+ switch response.actionIdentifier {
+ case LocalNotificationService.reinspectActionIdentifier:
+ action = .inspect(domain)
+ default:
+ // Default tap: open the tracked domain's detail.
+ action = .detail(domain)
+ }
+
+ await MainActor.run {
+ DomainDigIntentRouter.shared.pendingAction = action
+ }
+ }
}