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/DomainDigIntents.swift | 78 ++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 51 deletions(-) (limited to 'DomainDig/DomainDigIntents.swift') diff --git a/DomainDig/DomainDigIntents.swift b/DomainDig/DomainDigIntents.swift index 44a1dc4..c0c73cc 100644 --- a/DomainDig/DomainDigIntents.swift +++ b/DomainDig/DomainDigIntents.swift @@ -124,6 +124,24 @@ struct AddToWatchlistIntent: AppIntent { } } +/// App Intent that opens DomainDig and re-inspects every tracked domain. It runs +/// through the existing view-model batch path (`refreshAllTrackedDomains`), which +/// enforces the batch feature gate and surfaces the paywall when needed. +struct RunSweepIntent: AppIntent { + static var title: LocalizedStringResource = "Run Watchlist Sweep" + static var description = IntentDescription( + "Open DomainDig and re-inspect every domain on your watchlist." + ) + + static var openAppWhenRun = true + + @MainActor + func perform() async throws -> some IntentResult { + DomainDigIntentRouter.shared.pendingAction = .sweep + return .result() + } +} + /// In-process hand-off from an `openAppWhenRun` intent to the running SwiftUI /// layer. `RootTabView` observes `pendingAction` and performs it. @MainActor @@ -134,57 +152,6 @@ final class DomainDigIntentRouter { private init() {} } -/// Shared builder/parser for the `domaindig://` URL scheme, used by both the -/// intents (to open the app) and the app (to route incoming links). -enum DomainDigDeepLink { - static let scheme = "domaindig" - - enum Action: Equatable { - case inspect(String) - case watch(String) - - var host: String { - switch self { - case .inspect: return "inspect" - case .watch: return "watch" - } - } - - var domain: String { - switch self { - case let .inspect(domain), let .watch(domain): return domain - } - } - } - - static func url(for action: Action) -> URL { - var components = URLComponents() - components.scheme = scheme - components.host = action.host - components.queryItems = [URLQueryItem(name: "domain", value: action.domain)] - // The scheme and host are fixed and the domain is percent-encoded by - // URLComponents, so this is always a valid URL. - return components.url! - } - - static func action(from url: URL) -> Action? { - guard url.scheme == scheme else { return nil } - - let domain = URLComponents(url: url, resolvingAgainstBaseURL: false)? - .queryItems? - .first { $0.name == "domain" }? - .value? - .trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - guard !domain.isEmpty else { return nil } - - switch url.host() { - case "inspect": return .inspect(domain) - case "watch": return .watch(domain) - default: return nil - } - } -} - /// Exposes DomainDig intents to Spotlight and Siri with invocation phrases. struct DomainDigShortcuts: AppShortcutsProvider { static var appShortcuts: [AppShortcut] { @@ -206,5 +173,14 @@ struct DomainDigShortcuts: AppShortcutsProvider { shortTitle: "Add to Watchlist", systemImageName: "plus.circle" ) + AppShortcut( + intent: RunSweepIntent(), + phrases: [ + "Run a sweep with \(.applicationName)", + "Sweep my \(.applicationName) watchlist" + ], + shortTitle: "Run Sweep", + systemImageName: "arrow.trianglehead.2.clockwise" + ) } } -- cgit v1.2.3