Demo-Modus: Persistenz und Kühlbox-Bedienung reparieren, Live Activity aktivieren

Drei vom Nutzer im Testbuild gefundene Fehler:

* DeviceStore.save() schrieb auch im Demo-Modus in UserDefaults. Jede
  Änderung während aktivem Demo-Modus (z.B. Fahrzeuggrafik oder
  Einbaulage ändern) persistierte damit die erfundenen Fahrzeuge/Geräte
  dauerhaft – sie blieben nach dem Deaktivieren des Demo-Modus stehen.
  save() ist jetzt im Demo-Modus ein no-op.

* Die Kühlbox-Schalter (Ein/Aus, Eco/Max, Solltemperatur, Sperre) liefen
  über sendFridgeSettings(), das eine echte Bluetooth-Verbindung
  voraussetzt (`managed`-Eintrag) – im Demo-Modus gibt es die nie, die
  Schalter taten also nichts. Im Demo-Modus wird der angezeigte
  AlpicoolState jetzt direkt geändert, ohne den Umweg über einen
  Stellbefehl.

* Der Live-Activity-Schalter liess sich nicht einschalten, weil dem
  Haupt-App-Target der Info.plist-Schlüssel NSSupportsLiveActivities
  fehlte – Activity.request() wirft ohne ihn, wird aber vom Aufrufer
  stillschweigend abgefangen, sodass der Schalter beim Antippen einfach
  auf Aus zurückspringt.

Build (App + Watch) und Tests grün.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
fototeddy
2026-09-06 22:05:37 +02:00
co-authored by Claude Sonnet 5
parent b956324234
commit eed8f33e30
3 changed files with 41 additions and 0 deletions
@@ -394,6 +394,10 @@ final class BluetoothManager: NSObject {
// MARK: - Kühlbox steuern (vom Hauptthread aufgerufen)
func updateFridgeZoneMode(for device: ConfiguredDevice) {
guard !isDemo else {
applyDemoFridgeChange(for: device.id) { $0.zoneMode = device.fridgeZoneMode }
return
}
let deviceID = device.id, mode = device.fridgeZoneMode
queue.async {
guard let session = self.session(for: deviceID) else { return }
@@ -404,21 +408,51 @@ final class BluetoothManager: NSObject {
}
func setFridgeTarget(_ celsius: Int, zone: AlpicoolState.Zone, for deviceID: UUID) {
guard !isDemo else {
applyDemoFridgeChange(for: deviceID) { state in
switch zone {
case .left: state.leftTarget = celsius
case .right: state.rightTarget = celsius
}
}
return
}
sendFridgeSettings(for: deviceID) { _ in AlpicoolState.setTarget(zone: zone, to: celsius) }
}
func setFridgePower(_ on: Bool, for deviceID: UUID) {
guard !isDemo else {
applyDemoFridgeChange(for: deviceID) { $0.isPoweredOn = on }
return
}
sendFridgeSettings(for: deviceID) { $0.settingsCommand(poweredOn: on) }
}
func setFridgeEco(_ eco: Bool, for deviceID: UUID) {
guard !isDemo else {
applyDemoFridgeChange(for: deviceID) { $0.runMode = eco ? 1 : 0 }
return
}
sendFridgeSettings(for: deviceID) { $0.settingsCommand(eco: eco) }
}
func setFridgeLock(_ locked: Bool, for deviceID: UUID) {
guard !isDemo else {
applyDemoFridgeChange(for: deviceID) { $0.isLocked = locked }
return
}
sendFridgeSettings(for: deviceID) { $0.settingsCommand(locked: locked) }
}
/// Im Demo-Modus gibt es keine Box, die einen Stellbefehl bestätigt die
/// Änderung wird direkt im angezeigten Zustand nachgezogen, auf dem
/// Hauptthread, wo `fridgeStates` lebt.
private func applyDemoFridgeChange(for deviceID: UUID, _ change: (inout AlpicoolState) -> Void) {
var state = fridgeStates[deviceID] ?? DemoData.fridgeState
change(&state)
fridgeStates[deviceID] = state
}
/// Der Einstellungsblock wird aus dem zuletzt empfangenen Zustand gebaut
/// und zwar auf der Funk-Queue, wo dieser Zustand lebt.
///