summaryrefslogtreecommitdiff
path: root/DomainDig/ContentView.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-23 23:12:34 -0500
committerChristian Cleberg <[email protected]>2026-04-23 23:12:34 -0500
commitb35f1b432fc24fbab1fa05593c9a0bca7e4d95a6 (patch)
tree099cea2cc4324331a719ed2cad8c0815033b5471 /DomainDig/ContentView.swift
parentf045a5d339a796f40a9e958436ebc6f5314bdab8 (diff)
downloaddomain-dig-b35f1b432fc24fbab1fa05593c9a0bca7e4d95a6.tar.gz
domain-dig-b35f1b432fc24fbab1fa05593c9a0bca7e4d95a6.tar.bz2
domain-dig-b35f1b432fc24fbab1fa05593c9a0bca7e4d95a6.zip
feat(v3.6.0): add iCloud sharing for workflows and tracked domains
- implement CloudKit sharing (CKShare) for TrackedDomain and DomainWorkflow - support read-only and editable permissions - add Share actions and shared state indicators in UI - handle shared record ownership and participant roles - implement deterministic conflict resolution to prevent duplication - ensure compatibility with existing iCloud sync layer - maintain local-first behavior with graceful offline handling - remove debug logging for domain availability/rdap messages notes: - no custom backend or accounts introduced - sharing is Apple ecosystem only (iCloud-based) - large history data remains local-only
Diffstat (limited to 'DomainDig/ContentView.swift')
-rw-r--r--DomainDig/ContentView.swift59
1 files changed, 58 insertions, 1 deletions
diff --git a/DomainDig/ContentView.swift b/DomainDig/ContentView.swift
index e1d0a99..0386211 100644
--- a/DomainDig/ContentView.swift
+++ b/DomainDig/ContentView.swift
@@ -2421,6 +2421,7 @@ struct SettingsView: View {
@Environment(\.appDensity) private var appDensity
@Bindable var viewModel: DomainViewModel
@State private var purchaseService = PurchaseService.shared
+ @State private var cloudSyncService = CloudSyncService.shared
@AppStorage(DNSResolverOption.userDefaultsKey)
private var storedResolverURL = DNSResolverOption.defaultURLString
@AppStorage(AppDensity.userDefaultsKey)
@@ -2480,6 +2481,43 @@ struct SettingsView: View {
}
}
+ Section("iCloud Sync") {
+ Toggle(
+ "Enable iCloud Sync",
+ isOn: Binding(
+ get: { cloudSyncService.isEnabled },
+ set: { cloudSyncService.setSyncEnabled($0) }
+ )
+ )
+
+ LabeledContent("Status", value: cloudSyncService.status.title)
+ LabeledContent(
+ "Last Sync",
+ value: cloudSyncService.lastSyncDate?.formatted(date: .abbreviated, time: .shortened) ?? "Not yet synced"
+ )
+
+ Button(cloudSyncService.status == .syncing ? "Syncing…" : "Sync Now") {
+ Task {
+ await cloudSyncService.syncNow(trigger: .manual)
+ }
+ }
+ .disabled(!cloudSyncService.isEnabled || cloudSyncService.status == .syncing)
+
+ Text("iCloud Sync stores DomainDig data in your private iCloud account. DomainDig does not operate a sync server. Disabling sync keeps local data on this device.")
+ .font(appDensity.font(.caption, design: .default))
+ .foregroundStyle(.secondary)
+
+ Text(cloudSyncService.detailMessage)
+ .font(appDensity.font(.caption, design: .default))
+ .foregroundStyle(.secondary)
+
+ if let lastErrorMessage = cloudSyncService.lastErrorMessage {
+ Text(lastErrorMessage)
+ .font(appDensity.font(.caption, design: .default))
+ .foregroundStyle(.red)
+ }
+ }
+
Section("Monitoring") {
Toggle(
"Enable Background Monitoring",
@@ -2713,7 +2751,7 @@ struct SettingsView: View {
Section("About") {
LabeledContent("Version", value: appVersion)
- LabeledContent("Storage", value: "Local-only")
+ LabeledContent("Storage", value: cloudSyncService.isEnabled ? "Local-first + iCloud" : "Local-only")
LabeledContent("Backup Schema", value: "v\(DomainDigBackup.currentSchemaVersion)")
}
}
@@ -2817,18 +2855,37 @@ struct SettingsView: View {
Task {
await viewModel.refreshUsageCredits()
await viewModel.refreshMonitoringAuthorizationStatus()
+ await cloudSyncService.refreshAvailability()
}
}
.onChange(of: resolverOption) { _, newValue in
guard let presetURL = newValue.urlString else {
storedResolverURL = customResolverURL.trimmingCharacters(in: .whitespacesAndNewlines)
+ viewModel.persistCurrentAppSettings(
+ resolverURLString: storedResolverURL,
+ appDensityRawValue: storedDensity
+ )
return
}
storedResolverURL = presetURL
+ viewModel.persistCurrentAppSettings(
+ resolverURLString: storedResolverURL,
+ appDensityRawValue: storedDensity
+ )
}
.onChange(of: customResolverURL) { _, newValue in
guard resolverOption == .custom else { return }
storedResolverURL = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
+ viewModel.persistCurrentAppSettings(
+ resolverURLString: storedResolverURL,
+ appDensityRawValue: storedDensity
+ )
+ }
+ .onChange(of: storedDensity) { _, newValue in
+ viewModel.persistCurrentAppSettings(
+ resolverURLString: storedResolverURL,
+ appDensityRawValue: newValue
+ )
}
}