From d3a27d06809e6744c092f9bed4bf0537843d0210 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Fri, 17 Jul 2026 00:27:45 -0500 Subject: Complete v4.5.0: Run Sweep intent, detail deep link, and portfolio widget Finishes the v4.5.0 "Home Screen & Shortcuts reach" scope that the tag shipped partially: - Add RunSweepIntent (opens the app and runs refreshAllTrackedDomains via the in-process router) and expose it in DomainDigShortcuts. - Extend the domaindig:// scheme with `sweep` and `domain` (detail) actions; route .detail to present TrackedDomainDetailView and .sweep to refresh the watchlist. Move DomainDigDeepLink into Shared/ so the widget can build links. - Add a WidgetKit extension (DomainDigWidgetExtension) with small/medium/large Portfolio widgets showing health counts, per-domain status, and certificate countdowns; tapping a domain deep-links into its detail. - Share portfolio state via an App Group (group.net.cleberg.DomainDig): the app writes a DomainDigWidgetData snapshot on launch/foreground and on watchlist changes and reloads timelines; the widget reads the same store. --- DomainDig/DomainViewModel+Widget.swift | 60 ++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 DomainDig/DomainViewModel+Widget.swift (limited to 'DomainDig/DomainViewModel+Widget.swift') diff --git a/DomainDig/DomainViewModel+Widget.swift b/DomainDig/DomainViewModel+Widget.swift new file mode 100644 index 0000000..59b28e0 --- /dev/null +++ b/DomainDig/DomainViewModel+Widget.swift @@ -0,0 +1,60 @@ +import Foundation +import WidgetKit + +extension DomainViewModel { + /// Publishes the current portfolio state to the App Group container so the + /// widget can render it, then asks WidgetKit to refresh its timelines. + func refreshWidgetData() { + let data = portfolioDashboardData + let snapshot = data.snapshot + + let ordered = data.domainStates.sorted { lhs, rhs in + if lhs.trackedDomain.isPinned != rhs.trackedDomain.isPinned { + return lhs.trackedDomain.isPinned + } + return lhs.health.widgetSeverityRank > rhs.health.widgetSeverityRank + } + + let domains = ordered.prefix(6).map { state in + DomainDigWidgetDomain( + domain: state.trackedDomain.domain, + isPinned: state.trackedDomain.isPinned, + status: state.health.widgetStatus, + certDaysRemaining: state.certificateDaysRemaining, + lastChange: state.lastChangeDate + ) + } + + DomainDigWidgetStore.write( + DomainDigWidgetData( + generatedAt: Date(), + totalDomains: snapshot.totalDomains, + healthyCount: snapshot.healthyCount, + warningCount: snapshot.warningCount, + criticalCount: snapshot.criticalCount, + expiringSoonCount: snapshot.expiringSoonCount, + unreachableCount: snapshot.unreachableCount, + domains: Array(domains) + ) + ) + WidgetCenter.shared.reloadAllTimelines() + } +} + +private extension DomainHealth { + var widgetStatus: DomainDigWidgetStatus { + switch self { + case .healthy: return .healthy + case .warning: return .warning + case .critical: return .critical + } + } + + var widgetSeverityRank: Int { + switch self { + case .healthy: return 0 + case .warning: return 1 + case .critical: return 2 + } + } +} -- cgit v1.2.3