diff options
| author | Christian Cleberg <[email protected]> | 2026-07-17 08:36:05 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-07-17 10:12:36 -0500 |
| commit | b4b94d54cbed9334a28cdcd70d3a8a0fd06e04ef (patch) | |
| tree | 8fa4b408729da32ec2c547081ec2bba13b893fa2 /DomainDig/LocalNotificationService.swift | |
| parent | 065e1e77ff00e8f9c8785393b5eba6c3e0bb2bb1 (diff) | |
| download | domain-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.swift | 65 |
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 + } + } } |
