## Issues Fixed 1. ❌ Title: "Connected Meters" → misleading (showed all known meters) ✅ Now: "Known Meters" 2. ❌ Content: Only MAC + device name (static, useless) ✅ Now: Complete state + timestamps ## New Debug Card Content Each meter card now shows: **Status Badge:** - Live on this device (green) - Connected elsewhere (indigo) - Offline (gray) **Connection Metadata:** - Device: hostname holding connection - Since: connectedAt timestamp - Expires: connectedExpiryAt (with warning if expired) **Discovery Metadata:** - Last seen: lastSeenAt timestamp - Seen by: lastSeenByDeviceName **Static Info:** - Display name - MAC address - Model type ## Result Debug view now provides complete troubleshooting info: - Who has the meter connected - When connection was established - When connection expires - Last BT discovery info ✅ Build: Mac Catalyst SUCCESS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@@ -571,7 +571,7 @@ struct ContentView: View {
|
||
| 571 | 571 |
|
| 572 | 572 |
if !appData.knownMetersByMAC.isEmpty {
|
| 573 | 573 |
VStack(alignment: .leading, spacing: 10) {
|
| 574 |
- Text("Connected Meters")
|
|
| 574 |
+ Text("Known Meters")
|
|
| 575 | 575 |
.font(.headline) |
| 576 | 576 |
ForEach(Array(appData.knownMetersByMAC.values), id: \.macAddress) { known in
|
| 577 | 577 |
meterDebugCard(for: known) |
@@ -613,22 +613,92 @@ struct ContentView: View {
|
||
| 613 | 613 |
} |
| 614 | 614 |
|
| 615 | 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) |
|
| 616 |
+ let isLive = appData.meters.values.contains { $0.btSerial.macAddress.description == known.macAddress }
|
|
| 617 |
+ let connectedElsewhere = isConnectedElsewhere(known) |
|
| 618 |
+ let statusText: String = {
|
|
| 619 |
+ if isLive {
|
|
| 620 |
+ return "Live on this device" |
|
| 621 |
+ } else if connectedElsewhere {
|
|
| 622 |
+ return "Connected elsewhere" |
|
| 623 |
+ } else {
|
|
| 624 |
+ return "Offline" |
|
| 625 |
+ } |
|
| 626 |
+ }() |
|
| 627 |
+ let statusColor: Color = {
|
|
| 628 |
+ if isLive {
|
|
| 629 |
+ return .green |
|
| 630 |
+ } else if connectedElsewhere {
|
|
| 631 |
+ return .indigo |
|
| 632 |
+ } else {
|
|
| 633 |
+ return .secondary |
|
| 634 |
+ } |
|
| 635 |
+ }() |
|
| 636 |
+ |
|
| 637 |
+ return VStack(alignment: .leading, spacing: 8) {
|
|
| 638 |
+ HStack {
|
|
| 639 |
+ Circle() |
|
| 640 |
+ .fill(statusColor) |
|
| 641 |
+ .frame(width: 8, height: 8) |
|
| 642 |
+ Text(known.displayName) |
|
| 643 |
+ .font(.subheadline.weight(.semibold)) |
|
| 644 |
+ Spacer() |
|
| 645 |
+ Text(statusText) |
|
| 646 |
+ .font(.caption2) |
|
| 647 |
+ .foregroundColor(statusColor) |
|
| 648 |
+ } |
|
| 649 |
+ |
|
| 650 |
+ Text(known.macAddress) |
|
| 651 |
+ .font(.system(.caption, design: .monospaced)) |
|
| 652 |
+ .foregroundColor(.secondary) |
|
| 653 |
+ |
|
| 654 |
+ if let modelType = known.modelType {
|
|
| 655 |
+ Text("Model: \(modelType)")
|
|
| 656 |
+ .font(.caption2) |
|
| 657 |
+ .foregroundColor(.secondary) |
|
| 658 |
+ } |
|
| 659 |
+ |
|
| 660 |
+ Divider() |
|
| 661 |
+ |
|
| 662 |
+ if let connectedBy = known.connectedByDeviceName, !connectedBy.isEmpty {
|
|
| 663 |
+ VStack(alignment: .leading, spacing: 3) {
|
|
| 664 |
+ Text("Connection:")
|
|
| 665 |
+ .font(.caption.weight(.semibold)) |
|
| 666 |
+ Text("Device: \(connectedBy)")
|
|
| 667 |
+ .font(.system(.caption2, design: .monospaced)) |
|
| 668 |
+ if let connectedAt = known.connectedAt {
|
|
| 669 |
+ Text("Since: \(formatDate(connectedAt))")
|
|
| 670 |
+ .font(.system(.caption2, design: .monospaced)) |
|
| 671 |
+ } |
|
| 672 |
+ if let expiry = known.connectedExpiryAt {
|
|
| 673 |
+ let expired = expiry < Date() |
|
| 674 |
+ Text("Expires: \(formatDate(expiry)) \(expired ? "(expired)" : "")")
|
|
| 675 |
+ .font(.system(.caption2, design: .monospaced)) |
|
| 676 |
+ .foregroundColor(expired ? .orange : .secondary) |
|
| 677 |
+ } |
|
| 678 |
+ } |
|
| 679 |
+ } else {
|
|
| 680 |
+ Text("Not connected")
|
|
| 681 |
+ .font(.caption2) |
|
| 682 |
+ .foregroundColor(.secondary) |
|
| 683 |
+ } |
|
| 684 |
+ |
|
| 685 |
+ if let lastSeenAt = known.lastSeenAt {
|
|
| 686 |
+ Divider() |
|
| 687 |
+ VStack(alignment: .leading, spacing: 3) {
|
|
| 688 |
+ Text("Discovery:")
|
|
| 689 |
+ .font(.caption.weight(.semibold)) |
|
| 690 |
+ Text("Last seen: \(formatDate(lastSeenAt))")
|
|
| 691 |
+ .font(.system(.caption2, design: .monospaced)) |
|
| 692 |
+ if let seenBy = known.lastSeenByDeviceName, !seenBy.isEmpty {
|
|
| 693 |
+ Text("Seen by: \(seenBy)")
|
|
| 694 |
+ .font(.system(.caption2, design: .monospaced)) |
|
| 695 |
+ } |
|
| 626 | 696 |
} |
| 627 | 697 |
} |
| 628 | 698 |
} |
| 629 | 699 |
.frame(maxWidth: .infinity, alignment: .leading) |
| 630 |
- .padding(10) |
|
| 631 |
- .meterCard(tint: .cyan, fillOpacity: 0.08, strokeOpacity: 0.12) |
|
| 700 |
+ .padding(12) |
|
| 701 |
+ .meterCard(tint: statusColor, fillOpacity: 0.08, strokeOpacity: 0.14) |
|
| 632 | 702 |
} |
| 633 | 703 |
} |
| 634 | 704 |
|