ListlessiOS/Views/SyncDiagnosticsView.swift (3684B)
1import SwiftUI
2
3struct SyncDiagnosticsView: View {
4 @ObservedObject var syncMonitor: CloudKitSyncMonitor
5
6 private static let timestampFormatter: DateFormatter = {
7 let formatter = DateFormatter()
8 formatter.dateStyle = .none
9 formatter.timeStyle = .medium
10 return formatter
11 }()
12
13 var body: some View {
14 List {
15 Section("Status") {
16 row("Transient Banner", syncMonitor.transientErrorMessage ?? "None")
17 row("Last Error Domain", syncMonitor.lastCloudKitErrorDomain ?? "None")
18 row(
19 "Last Error Code",
20 syncMonitor.lastCloudKitErrorCode.map(String.init) ?? "None"
21 )
22 row("Last Error Description", syncMonitor.lastCloudKitErrorDescription ?? "None")
23 row(
24 "Last Success",
25 syncMonitor.lastSuccessfulSyncDate.map(Self.timestampFormatter.string(from:))
26 ?? "None"
27 )
28 }
29
30 Section("Recent Events") {
31 if syncMonitor.recentDiagnostics.isEmpty {
32 Text("No events captured yet.")
33 .foregroundStyle(.secondary)
34 } else {
35 ForEach(syncMonitor.recentDiagnostics.reversed()) { entry in
36 VStack(alignment: .leading, spacing: 4) {
37 Text(
38 "\(Self.timestampFormatter.string(from: entry.timestamp)) [\(entry.level.uppercased())]"
39 )
40 .font(.caption.monospaced())
41 .foregroundStyle(.secondary)
42
43 Text(entry.message)
44 .font(.caption.monospaced())
45 .textSelection(.enabled)
46 }
47 .padding(.vertical, 2)
48 }
49 }
50 }
51 }
52 .navigationTitle("iCloud Diagnostics")
53 .navigationBarTitleDisplayMode(.inline)
54 .toolbar {
55 ToolbarItem(placement: .topBarTrailing) {
56 Button("Copy") {
57 UIPasteboard.general.string = diagnosticDump
58 }
59 }
60 }
61 }
62
63 @ViewBuilder
64 private func row(_ title: String, _ value: String) -> some View {
65 VStack(alignment: .leading, spacing: 4) {
66 Text(title)
67 .font(.caption)
68 .foregroundStyle(.secondary)
69 Text(value)
70 .font(.body.monospaced())
71 .textSelection(.enabled)
72 }
73 .padding(.vertical, 2)
74 }
75
76 private var diagnosticDump: String {
77 var lines: [String] = []
78 lines.append("Transient Banner: \(syncMonitor.transientErrorMessage ?? "None")")
79 lines.append("Last Error Domain: \(syncMonitor.lastCloudKitErrorDomain ?? "None")")
80 lines.append("Last Error Code: \(syncMonitor.lastCloudKitErrorCode.map(String.init) ?? "None")")
81 lines.append("Last Error Description: \(syncMonitor.lastCloudKitErrorDescription ?? "None")")
82 lines.append(
83 "Last Success: \(syncMonitor.lastSuccessfulSyncDate.map(Self.timestampFormatter.string(from:)) ?? "None")"
84 )
85 lines.append("")
86 lines.append("Recent Events:")
87 for entry in syncMonitor.recentDiagnostics {
88 lines.append(
89 "\(Self.timestampFormatter.string(from: entry.timestamp)) [\(entry.level.uppercased())] \(entry.message)"
90 )
91 }
92 return lines.joined(separator: "\n")
93 }
94}