Showing 1 changed files with 99 additions and 42 deletions
+99 -42
USB Meter/Views/ContentView.swift
@@ -16,6 +16,7 @@ struct ContentView: View {
16 16
     private enum SidebarItem: Hashable {
17 17
         case overview
18 18
         case meter(String)
19
+        case debug
19 20
         case bluetoothHelp
20 21
         case deviceChecklist
21 22
         case discoveryChecklist
@@ -120,6 +121,14 @@ struct ContentView: View {
120 121
                     }
121 122
                 }
122 123
             }
124
+
125
+            Section(header: Text("Debug")) {
126
+                NavigationLink(tag: SidebarItem.debug, selection: $selectedSidebarItem) {
127
+                    debugView
128
+                } label: {
129
+                    Label("Debug Info", systemImage: "wrench.and.screwdriver")
130
+                }
131
+            }
123 132
         }
124 133
         .listStyle(SidebarListStyle())
125 134
         .navigationTitle("USB Meters")
@@ -139,6 +148,8 @@ struct ContentView: View {
139 148
             } else {
140 149
                 unavailableMeterDetail
141 150
             }
151
+        case .debug:
152
+            debugView
142 153
         case .bluetoothHelp:
143 154
             appData.bluetoothManager.managerState.helpView
144 155
         case .deviceChecklist:
@@ -246,27 +257,6 @@ struct ContentView: View {
246 257
                 .padding(18)
247 258
                 .meterCard(tint: meter.meterColor, fillOpacity: 0.14, strokeOpacity: 0.22)
248 259
 
249
-                VStack(alignment: .leading, spacing: 10) {
250
-                    HStack {
251
-                        Text("iCloud Debug")
252
-                            .font(.headline)
253
-                        Spacer()
254
-                        Button {
255
-                            UIPasteboard.general.string = offlineDebugText(for: meter, known: known, isConnectedElsewhere: isConnectedElsewhere)
256
-                        } label: {
257
-                            Label("Copy", systemImage: "doc.on.doc")
258
-                                .font(.caption.weight(.semibold))
259
-                        }
260
-                        .buttonStyle(.plain)
261
-                    }
262
-                    Text(offlineDebugText(for: meter, known: known, isConnectedElsewhere: isConnectedElsewhere))
263
-                        .font(.system(.footnote, design: .monospaced))
264
-                        .textSelection(.enabled)
265
-                        .frame(maxWidth: .infinity, alignment: .leading)
266
-                }
267
-                .padding(18)
268
-                .meterCard(tint: .indigo, fillOpacity: 0.14, strokeOpacity: 0.22)
269
-
270 260
                 Text("When this meter appears over BLE on this device, the live Meter View opens automatically from the BT layer and no CloudKit state will override it.")
271 261
                     .font(.footnote)
272 262
                     .foregroundColor(.secondary)
@@ -344,26 +334,6 @@ struct ContentView: View {
344 334
         return value.formatted(date: .abbreviated, time: .standard)
345 335
     }
346 336
 
347
-    private func offlineDebugText(for meter: MeterSidebarEntry, known: KnownMeterCatalogItem, isConnectedElsewhere: Bool) -> String {
348
-        [
349
-            "Local Device ID: \(AppData.myDeviceID)",
350
-            "Local Device Name: \(AppData.myDeviceName)",
351
-            "Now: \(formatDate(Date()))",
352
-            "MAC: \(meter.macAddress)",
353
-            "Display Name: \(meter.displayName)",
354
-            "Model: \(meter.modelSummary)",
355
-            "Connected By Device ID: \(known.connectedByDeviceID ?? "(empty)")",
356
-            "Connected By Device Name: \(known.connectedByDeviceName ?? "(empty)")",
357
-            "Connected At: \(formatDate(known.connectedAt))",
358
-            "Connected Expiry: \(formatDate(known.connectedExpiryAt))",
359
-            "Last Seen At: \(formatDate(known.lastSeenAt))",
360
-            "Last Seen By Device ID: \(known.lastSeenByDeviceID ?? "(empty)")",
361
-            "Last Seen By Device Name: \(known.lastSeenByDeviceName ?? "(empty)")",
362
-            "Last Seen Peripheral Name: \(known.lastSeenPeripheralName ?? "(empty)")",
363
-            "Connected Elsewhere Decision: \(isConnectedElsewhere ? "true (foreign device + valid expiry)" : "false (missing foreign owner or expired claim)")"
364
-        ].joined(separator: "\n")
365
-    }
366
-
367 337
     private var visibleMeterIDs: [String] {
368 338
         visibleMeters.map(\.id)
369 339
     }
@@ -421,7 +391,7 @@ struct ContentView: View {
421 391
             if !shouldShowDiscoveryChecklistEntry {
422 392
                 self.selectedSidebarItem = .overview
423 393
             }
424
-        case .overview:
394
+        case .overview, .debug:
425 395
             break
426 396
         }
427 397
     }
@@ -573,6 +543,93 @@ struct ContentView: View {
573 543
         .padding(16)
574 544
         .meterCard(tint: tint, fillOpacity: 0.14, strokeOpacity: 0.20)
575 545
     }
546
+
547
+    private var debugView: some View {
548
+        ScrollView {
549
+            VStack(alignment: .leading, spacing: 14) {
550
+                VStack(alignment: .leading, spacing: 8) {
551
+                    Text("Debug Information")
552
+                        .font(.system(.title2, design: .rounded).weight(.bold))
553
+                    Text("System and CloudKit details for troubleshooting.")
554
+                        .font(.footnote)
555
+                        .foregroundColor(.secondary)
556
+                }
557
+                .frame(maxWidth: .infinity, alignment: .leading)
558
+                .padding(18)
559
+                .meterCard(tint: .purple, fillOpacity: 0.16, strokeOpacity: 0.22)
560
+
561
+                debugCard(title: "Device Info", content: [
562
+                    "Device ID: \(AppData.myDeviceID)",
563
+                    "Device Name: \(AppData.myDeviceName)"
564
+                ])
565
+
566
+                debugCard(title: "CloudKit Status", content: [
567
+                    "Container: iCloud.ro.xdev.USB-Meter",
568
+                    "Total Meters: \(appData.knownMetersByMAC.count)",
569
+                    "Live Meters: \(appData.meters.count)"
570
+                ])
571
+
572
+                if !appData.knownMetersByMAC.isEmpty {
573
+                    VStack(alignment: .leading, spacing: 10) {
574
+                        Text("Connected Meters")
575
+                            .font(.headline)
576
+                        ForEach(Array(appData.knownMetersByMAC.values), id: \.macAddress) { known in
577
+                            meterDebugCard(for: known)
578
+                        }
579
+                    }
580
+                    .frame(maxWidth: .infinity, alignment: .leading)
581
+                    .padding(18)
582
+                    .meterCard(tint: .cyan, fillOpacity: 0.14, strokeOpacity: 0.22)
583
+                }
584
+            }
585
+            .padding()
586
+        }
587
+        .background(
588
+            LinearGradient(
589
+                colors: [.purple.opacity(0.08), Color.clear],
590
+                startPoint: .topLeading,
591
+                endPoint: .bottomTrailing
592
+            )
593
+            .ignoresSafeArea()
594
+        )
595
+        .navigationBarTitle(Text("Debug Info"), displayMode: .inline)
596
+    }
597
+
598
+    private func debugCard(title: String, content: [String]) -> some View {
599
+        VStack(alignment: .leading, spacing: 8) {
600
+            Text(title)
601
+                .font(.headline)
602
+            VStack(alignment: .leading, spacing: 4) {
603
+                ForEach(content, id: \.self) { line in
604
+                    Text(line)
605
+                        .font(.system(.footnote, design: .monospaced))
606
+                        .textSelection(.enabled)
607
+                }
608
+            }
609
+        }
610
+        .frame(maxWidth: .infinity, alignment: .leading)
611
+        .padding(12)
612
+        .meterCard(tint: .purple, fillOpacity: 0.08, strokeOpacity: 0.16)
613
+    }
614
+
615
+    private func meterDebugCard(for known: KnownMeterCatalogItem) -> some View {
616
+        VStack(alignment: .leading, spacing: 6) {
617
+            Text(known.displayName)
618
+                .font(.subheadline.weight(.semibold))
619
+            VStack(alignment: .leading, spacing: 2) {
620
+                Text(known.macAddress)
621
+                    .font(.system(.caption, design: .monospaced))
622
+                if let connectedBy = known.connectedByDeviceName, !connectedBy.isEmpty {
623
+                    Text("Connected: \(connectedBy)")
624
+                        .font(.system(.caption2, design: .default))
625
+                        .foregroundColor(.secondary)
626
+                }
627
+            }
628
+        }
629
+        .frame(maxWidth: .infinity, alignment: .leading)
630
+        .padding(10)
631
+        .meterCard(tint: .cyan, fillOpacity: 0.08, strokeOpacity: 0.12)
632
+    }
576 633
 }
577 634
 
578 635
 private struct DiscoveryChecklistView: View {