summaryrefslogtreecommitdiff
path: root/DomainDig/SweepActivityController.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/SweepActivityController.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/SweepActivityController.swift')
-rw-r--r--DomainDig/SweepActivityController.swift63
1 files changed, 63 insertions, 0 deletions
diff --git a/DomainDig/SweepActivityController.swift b/DomainDig/SweepActivityController.swift
new file mode 100644
index 0000000..26fcb2d
--- /dev/null
+++ b/DomainDig/SweepActivityController.swift
@@ -0,0 +1,63 @@
+import ActivityKit
+import Foundation
+
+/// Starts, updates, and ends the sweep Live Activity around a batch run.
+@MainActor
+final class SweepActivityController {
+ static let shared = SweepActivityController()
+
+ private var activity: Activity<SweepActivityAttributes>?
+
+ private init() {}
+
+ func begin(title: String, total: Int) {
+ guard ActivityAuthorizationInfo().areActivitiesEnabled else { return }
+ // A previous activity that never ended (e.g. app killed mid-sweep)
+ // would otherwise linger; replace it.
+ end(changed: 0, warnings: 0, immediately: true)
+
+ let state = SweepActivityAttributes.ContentState(
+ completed: 0,
+ total: total,
+ currentDomain: nil,
+ changed: 0,
+ warnings: 0
+ )
+ activity = try? Activity.request(
+ attributes: SweepActivityAttributes(title: title, startedAt: Date()),
+ content: ActivityContent(state: state, staleDate: nil)
+ )
+ }
+
+ func update(completed: Int, total: Int, currentDomain: String?) {
+ guard let activity else { return }
+ let state = SweepActivityAttributes.ContentState(
+ completed: completed,
+ total: total,
+ currentDomain: currentDomain,
+ changed: 0,
+ warnings: 0
+ )
+ Task {
+ await activity.update(ActivityContent(state: state, staleDate: nil))
+ }
+ }
+
+ func end(changed: Int, warnings: Int, immediately: Bool = false) {
+ guard let activity else { return }
+ self.activity = nil
+ let state = SweepActivityAttributes.ContentState(
+ completed: activity.content.state.total,
+ total: activity.content.state.total,
+ currentDomain: nil,
+ changed: changed,
+ warnings: warnings
+ )
+ Task {
+ await activity.end(
+ ActivityContent(state: state, staleDate: nil),
+ dismissalPolicy: immediately ? .immediate : .after(Date().addingTimeInterval(60))
+ )
+ }
+ }
+}