diff --git a/CamperMonitor/Bluetooth/BluetoothManager.swift b/CamperMonitor/Bluetooth/BluetoothManager.swift index 2c2f77b..00965fe 100644 --- a/CamperMonitor/Bluetooth/BluetoothManager.swift +++ b/CamperMonitor/Bluetooth/BluetoothManager.swift @@ -262,6 +262,13 @@ final class BluetoothManager: NSObject { for snapshot in DemoData.snapshots() { snapshots[snapshot.deviceID] = snapshot linkStates[snapshot.deviceID] = .live + // Die Kühlbox wird nur beim Öffnen verbunden – im Vorführbetrieb + // steht sie deshalb wie im Alltag auf „zuletzt gestellt“. + if snapshot.deviceID == DemoData.fridge.id, + let settings = DemoData.fridgeState.settings { + linkStates[snapshot.deviceID] = .idle + snapshots[snapshot.deviceID] = settings.snapshot(deviceID: snapshot.deviceID) + } if let value = snapshot.primaryMetric?.value { history[snapshot.deviceID] = DemoData.history(for: snapshot.deviceID, around: value) } diff --git a/CamperMonitor/Views/DeviceCard.swift b/CamperMonitor/Views/DeviceCard.swift index fdb8e69..11d00f6 100644 --- a/CamperMonitor/Views/DeviceCard.swift +++ b/CamperMonitor/Views/DeviceCard.swift @@ -11,7 +11,9 @@ struct DeviceCard: View { VStack(alignment: .leading, spacing: 12) { header - if let snapshot, let primary = snapshot.primaryMetric { + if isShowingLastSettings, let snapshot { + lastSettings(for: snapshot) + } else if let snapshot, let primary = snapshot.primaryMetric { HStack(alignment: .firstTextBaseline, spacing: 4) { Text(primary.formatted) .font(.system(size: 44, weight: .semibold, design: .rounded)) @@ -54,6 +56,65 @@ struct DeviceCard: View { } } + /// Ob hier der zuletzt gestellte Stand steht statt Messwerten. + private var isShowingLastSettings: Bool { + device.role.connectsOnDemand && linkState != .live + } + + /// Die Kachel der Kühlbox, solange sie nicht verbunden ist. + /// + /// Hier steht kein Messwert, sondern was zuletzt eingestellt war – also + /// gehört „Soll“ dazu. Ohne das läse man die Zahl als Innentemperatur, und + /// genau dieser Irrtum wäre teuer. + private func lastSettings(for snapshot: DeviceSnapshot) -> some View { + HStack(alignment: .top, spacing: 12) { + VStack(alignment: .leading, spacing: 2) { + Text(snapshot.metrics.count > 1 ? "Soll links" : "Soll") + .font(.caption) + .foregroundStyle(.secondary) + HStack(alignment: .firstTextBaseline, spacing: 4) { + Text(snapshot.primaryMetric?.formatted ?? "–") + .font(.system(size: 44, weight: .semibold, design: .rounded)) + Text(snapshot.primaryMetric?.unit ?? "") + .font(.title3.weight(.medium)) + .foregroundStyle(.secondary) + } + } + + if let right = snapshot.metrics.first(where: { $0.key == "target_right" }), + right.value != nil { + VStack(alignment: .leading, spacing: 2) { + Text("Soll rechts") + .font(.caption) + .foregroundStyle(.secondary) + Text(right.formattedWithUnit) + .font(.title2.weight(.semibold)) + .monospacedDigit() + } + } + + Spacer(minLength: 0) + modeBadge(snapshot.state) + } + } + + /// Betriebsart als Schild: Auf der Kachel ist Platz, und ob die Box läuft + /// oder aus ist, ist die zweite Frage nach dem Sollwert. + @ViewBuilder + private func modeBadge(_ state: String?) -> some View { + if let state { + let isOff = state == "Aus" + Text(state) + .font(.title3.weight(.semibold)) + .foregroundStyle(isOff ? Color.secondary : Color.accentColor) + .padding(.horizontal, 14) + .padding(.vertical, 8) + .background(isOff ? Color.secondary.opacity(0.15) + : Color.accentColor.opacity(0.15), + in: .rect(cornerRadius: 12)) + } + } + private func secondaryValues(for snapshot: DeviceSnapshot) -> some View { let others = snapshot.metrics .filter { $0.id != snapshot.primaryMetric?.id && $0.value != nil } @@ -76,7 +137,7 @@ struct DeviceCard: View { @ViewBuilder private var footer: some View { - if device.role.connectsOnDemand, linkState != .live, let snapshot { + if isShowingLastSettings, let snapshot { // Kein Messwert, sondern der zuletzt gestellte Stand. Das gehört // dazugeschrieben, sonst liest man ihn als aktuelle Temperatur. Text(lastSetText(snapshot)) @@ -104,10 +165,11 @@ struct DeviceCard: View { } /// Was unter einem Gerät steht, das nur beim Öffnen verbunden wird. + /// + /// Der Hinweis aufs Verbinden steht vorn: Er erklärt, warum hier kein + /// Messwert steht, und das ist die Frage, die sich zuerst stellt. private func lastSetText(_ snapshot: DeviceSnapshot) -> String { - let age = relativeUpdate(snapshot.timestamp) - guard let state = snapshot.state else { return "Zuletzt gestellt \(age)" } - return "\(state) · zuletzt gestellt \(age)" + "Verbindet erst beim Öffnen · zuletzt gestellt \(relativeUpdate(snapshot.timestamp))" } /// „vor 3 Minuten“ statt einer Uhrzeit – auf der Kachel zählt das Alter.