summaryrefslogtreecommitdiff
path: root/DomainDigWidget/SweepLiveActivity.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 /DomainDigWidget/SweepLiveActivity.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 'DomainDigWidget/SweepLiveActivity.swift')
-rw-r--r--DomainDigWidget/SweepLiveActivity.swift96
1 files changed, 96 insertions, 0 deletions
diff --git a/DomainDigWidget/SweepLiveActivity.swift b/DomainDigWidget/SweepLiveActivity.swift
new file mode 100644
index 0000000..b99a070
--- /dev/null
+++ b/DomainDigWidget/SweepLiveActivity.swift
@@ -0,0 +1,96 @@
+import ActivityKit
+import SwiftUI
+import WidgetKit
+
+struct SweepLiveActivity: Widget {
+ var body: some WidgetConfiguration {
+ ActivityConfiguration(for: SweepActivityAttributes.self) { context in
+ // Lock Screen / banner presentation.
+ SweepActivityBannerView(context: context)
+ .padding()
+ .activityBackgroundTint(Color(.systemBackground).opacity(0.85))
+ } dynamicIsland: { context in
+ DynamicIsland {
+ DynamicIslandExpandedRegion(.leading) {
+ Label(context.attributes.title, systemImage: "arrow.trianglehead.2.clockwise")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ DynamicIslandExpandedRegion(.trailing) {
+ Text("\(context.state.completed)/\(context.state.total)")
+ .font(.caption)
+ .monospacedDigit()
+ }
+ DynamicIslandExpandedRegion(.bottom) {
+ VStack(alignment: .leading, spacing: 4) {
+ ProgressView(value: context.state.fractionComplete)
+ .tint(.cyan)
+ if let current = context.state.currentDomain {
+ Text(current)
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ .lineLimit(1)
+ }
+ }
+ }
+ } compactLeading: {
+ Image(systemName: "arrow.trianglehead.2.clockwise")
+ .foregroundStyle(.cyan)
+ } compactTrailing: {
+ Text("\(context.state.completed)/\(context.state.total)")
+ .font(.caption2)
+ .monospacedDigit()
+ } minimal: {
+ ProgressView(value: context.state.fractionComplete)
+ .progressViewStyle(.circular)
+ .tint(.cyan)
+ }
+ }
+ }
+}
+
+private struct SweepActivityBannerView: View {
+ let context: ActivityViewContext<SweepActivityAttributes>
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 8) {
+ HStack {
+ Label(context.attributes.title, systemImage: "arrow.trianglehead.2.clockwise")
+ .font(.caption)
+ .fontWeight(.semibold)
+ .foregroundStyle(.secondary)
+ Spacer()
+ Text("\(context.state.completed) of \(context.state.total)")
+ .font(.caption)
+ .monospacedDigit()
+ }
+
+ ProgressView(value: context.state.fractionComplete)
+ .tint(.cyan)
+
+ HStack {
+ if let current = context.state.currentDomain {
+ Text(current)
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ .lineLimit(1)
+ } else {
+ Text(context.state.completed >= context.state.total ? "Finished" : "Working…")
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ }
+ Spacer()
+ if context.state.changed > 0 {
+ Text("\(context.state.changed) changed")
+ .font(.caption2)
+ .foregroundStyle(.orange)
+ }
+ if context.state.warnings > 0 {
+ Text("\(context.state.warnings) warnings")
+ .font(.caption2)
+ .foregroundStyle(.red)
+ }
+ }
+ }
+ }
+}