summaryrefslogtreecommitdiff
path: root/Shared/DomainDigDeepLink.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-07-17 00:27:45 -0500
committerChristian Cleberg <[email protected]>2026-07-17 00:31:57 -0500
commitd3a27d06809e6744c092f9bed4bf0537843d0210 (patch)
tree1527e935e11561dda4bc8c6cfaabdb92db5be95c /Shared/DomainDigDeepLink.swift
parent685f84ad0a8d2dc7b62125d5d9c3a50b75ae2468 (diff)
downloaddomain-dig-d3a27d06809e6744c092f9bed4bf0537843d0210.tar.gz
domain-dig-d3a27d06809e6744c092f9bed4bf0537843d0210.tar.bz2
domain-dig-d3a27d06809e6744c092f9bed4bf0537843d0210.zip
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.
Diffstat (limited to 'Shared/DomainDigDeepLink.swift')
-rw-r--r--Shared/DomainDigDeepLink.swift71
1 files changed, 71 insertions, 0 deletions
diff --git a/Shared/DomainDigDeepLink.swift b/Shared/DomainDigDeepLink.swift
new file mode 100644
index 0000000..ba1d64b
--- /dev/null
+++ b/Shared/DomainDigDeepLink.swift
@@ -0,0 +1,71 @@
+import Foundation
+
+/// Shared builder/parser for the `domaindig://` URL scheme, used by the intents
+/// and app (to open/route) and by the widget (to deep-link into a domain).
+///
+/// Lives in `Shared/` so it compiles into both the app and the widget target.
+/// It relies only on Foundation and no actor isolation, so it is safe in the
+/// widget extension (`APPLICATION_EXTENSION_API_ONLY`).
+enum DomainDigDeepLink {
+ static let scheme = "domaindig"
+
+ enum Action: Equatable {
+ case inspect(String)
+ case watch(String)
+ case detail(String)
+ case sweep
+
+ var host: String {
+ switch self {
+ case .inspect: return "inspect"
+ case .watch: return "watch"
+ case .detail: return "domain"
+ case .sweep: return "sweep"
+ }
+ }
+
+ /// The domain the action targets, if any. `.sweep` has no domain.
+ var domain: String? {
+ switch self {
+ case let .inspect(domain), let .watch(domain), let .detail(domain):
+ return domain
+ case .sweep:
+ return nil
+ }
+ }
+ }
+
+ static func url(for action: Action) -> URL {
+ var components = URLComponents()
+ components.scheme = scheme
+ components.host = action.host
+ if let domain = action.domain {
+ components.queryItems = [URLQueryItem(name: "domain", value: domain)]
+ }
+ // The scheme and host are fixed and any 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 }
+
+ if url.host() == "sweep" {
+ return .sweep
+ }
+
+ 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)
+ case "domain": return .detail(domain)
+ default: return nil
+ }
+ }
+}