Devices and chargers are independent entities. A meter can monitor any device or charger without explicit associations. Remove the artifact associations: Changes: - Remove 'Devices' and 'Chargers' cards from MeterDetailView - Update chargedDevices() and chargers() to filter only by sessions, not by lastAssociatedMeterMAC - Devices/chargers now only appear in meter views if they have active sessions The lastAssociatedMeterMAC attribute in the data model remains for now (may be cleaned up in migration) but is no longer used in filtering logic. This simplifies the model: meters monitor devices/chargers through sessions, not through explicit associations.
@@ -228,8 +228,7 @@ final class AppData : ObservableObject {
|
||
| 228 | 228 |
guard chargedDevice.isCharger == false else {
|
| 229 | 229 |
return false |
| 230 | 230 |
} |
| 231 |
- return chargedDevice.lastAssociatedMeterMAC == normalizedMAC |
|
| 232 |
- || chargedDevice.sessions.contains(where: { $0.meterMACAddress == normalizedMAC })
|
|
| 231 |
+ return chargedDevice.sessions.contains(where: { $0.meterMACAddress == normalizedMAC })
|
|
| 233 | 232 |
} |
| 234 | 233 |
} |
| 235 | 234 |
|
@@ -239,8 +238,7 @@ final class AppData : ObservableObject {
|
||
| 239 | 238 |
guard chargedDevice.isCharger else {
|
| 240 | 239 |
return false |
| 241 | 240 |
} |
| 242 |
- return chargedDevice.lastAssociatedMeterMAC == normalizedMAC |
|
| 243 |
- || chargedDevice.sessions.contains(where: { $0.meterMACAddress == normalizedMAC })
|
|
| 241 |
+ return chargedDevice.sessions.contains(where: { $0.meterMACAddress == normalizedMAC })
|
|
| 244 | 242 |
} |
| 245 | 243 |
} |
| 246 | 244 |
|
@@ -14,8 +14,6 @@ struct MeterDetailView: View {
|
||
| 14 | 14 |
headerCard |
| 15 | 15 |
statusCard |
| 16 | 16 |
identifiersCard |
| 17 |
- chargedDevicesCard |
|
| 18 |
- chargersCard |
|
| 19 | 17 |
} |
| 20 | 18 |
.padding() |
| 21 | 19 |
} |
@@ -112,98 +110,6 @@ struct MeterDetailView: View {
|
||
| 112 | 110 |
.meterCard(tint: .secondary, fillOpacity: 0.12, strokeOpacity: 0.18, cornerRadius: 18) |
| 113 | 111 |
} |
| 114 | 112 |
|
| 115 |
- private var chargedDevicesCard: some View {
|
|
| 116 |
- let chargedDevices = appData.chargedDevices(for: meterSummary.macAddress) |
|
| 117 |
- |
|
| 118 |
- return VStack(alignment: .leading, spacing: 10) {
|
|
| 119 |
- HStack(spacing: 8) {
|
|
| 120 |
- Text("Devices")
|
|
| 121 |
- .font(.headline) |
|
| 122 |
- ContextInfoButton( |
|
| 123 |
- title: "Devices", |
|
| 124 |
- message: "Link devices to this meter from Charge Record to keep capacity learning and charge curves tied to the right hardware." |
|
| 125 |
- ) |
|
| 126 |
- } |
|
| 127 |
- |
|
| 128 |
- if chargedDevices.isEmpty {
|
|
| 129 |
- Text("No devices linked yet.")
|
|
| 130 |
- .font(.caption) |
|
| 131 |
- .foregroundColor(.secondary) |
|
| 132 |
- } else {
|
|
| 133 |
- ForEach(chargedDevices.prefix(3)) { chargedDevice in
|
|
| 134 |
- NavigationLink(destination: ChargedDeviceDetailView(chargedDeviceID: chargedDevice.id)) {
|
|
| 135 |
- HStack(spacing: 12) {
|
|
| 136 |
- ChargedDeviceQRCodeView(qrIdentifier: chargedDevice.qrIdentifier, side: 52) |
|
| 137 |
- |
|
| 138 |
- VStack(alignment: .leading, spacing: 4) {
|
|
| 139 |
- ChargedDeviceIdentityLabelView( |
|
| 140 |
- chargedDevice: chargedDevice, |
|
| 141 |
- iconPointSize: 15 |
|
| 142 |
- ) |
|
| 143 |
- .font(.subheadline.weight(.semibold)) |
|
| 144 |
- Text(chargedDevice.estimatedBatteryCapacityWh.map { "\($0.format(decimalDigits: 2)) Wh" } ?? "Capacity: learning")
|
|
| 145 |
- .font(.caption) |
|
| 146 |
- .foregroundColor(.secondary) |
|
| 147 |
- } |
|
| 148 |
- |
|
| 149 |
- Spacer() |
|
| 150 |
- } |
|
| 151 |
- } |
|
| 152 |
- .buttonStyle(.plain) |
|
| 153 |
- } |
|
| 154 |
- } |
|
| 155 |
- } |
|
| 156 |
- .frame(maxWidth: .infinity, alignment: .leading) |
|
| 157 |
- .padding(18) |
|
| 158 |
- .meterCard(tint: .orange, fillOpacity: 0.12, strokeOpacity: 0.18, cornerRadius: 18) |
|
| 159 |
- } |
|
| 160 |
- |
|
| 161 |
- private var chargersCard: some View {
|
|
| 162 |
- let chargers = appData.chargers(for: meterSummary.macAddress) |
|
| 163 |
- |
|
| 164 |
- return VStack(alignment: .leading, spacing: 10) {
|
|
| 165 |
- HStack(spacing: 8) {
|
|
| 166 |
- Text("Chargers")
|
|
| 167 |
- .font(.headline) |
|
| 168 |
- ContextInfoButton( |
|
| 169 |
- title: "Chargers", |
|
| 170 |
- message: "Link chargers to this meter for wireless sessions so the app can keep charger-specific learning and efficiency data separate." |
|
| 171 |
- ) |
|
| 172 |
- } |
|
| 173 |
- |
|
| 174 |
- if chargers.isEmpty {
|
|
| 175 |
- Text("No chargers linked yet.")
|
|
| 176 |
- .font(.caption) |
|
| 177 |
- .foregroundColor(.secondary) |
|
| 178 |
- } else {
|
|
| 179 |
- ForEach(chargers.prefix(3)) { charger in
|
|
| 180 |
- NavigationLink(destination: ChargedDeviceDetailView(chargedDeviceID: charger.id)) {
|
|
| 181 |
- HStack(spacing: 12) {
|
|
| 182 |
- ChargedDeviceQRCodeView(qrIdentifier: charger.qrIdentifier, side: 52) |
|
| 183 |
- |
|
| 184 |
- VStack(alignment: .leading, spacing: 4) {
|
|
| 185 |
- ChargedDeviceIdentityLabelView( |
|
| 186 |
- chargedDevice: charger, |
|
| 187 |
- iconPointSize: 15 |
|
| 188 |
- ) |
|
| 189 |
- .font(.subheadline.weight(.semibold)) |
|
| 190 |
- Text(charger.chargerMaximumPowerWatts.map { "Max power: \($0.format(decimalDigits: 2)) W" } ?? "Wireless charger")
|
|
| 191 |
- .font(.caption) |
|
| 192 |
- .foregroundColor(.secondary) |
|
| 193 |
- } |
|
| 194 |
- |
|
| 195 |
- Spacer() |
|
| 196 |
- } |
|
| 197 |
- } |
|
| 198 |
- .buttonStyle(.plain) |
|
| 199 |
- } |
|
| 200 |
- } |
|
| 201 |
- } |
|
| 202 |
- .frame(maxWidth: .infinity, alignment: .leading) |
|
| 203 |
- .padding(18) |
|
| 204 |
- .meterCard(tint: .pink, fillOpacity: 0.12, strokeOpacity: 0.18, cornerRadius: 18) |
|
| 205 |
- } |
|
| 206 |
- |
|
| 207 | 113 |
private func infoRow(label: String, value: String) -> some View {
|
| 208 | 114 |
HStack {
|
| 209 | 115 |
Text(label) |