From eed8f33e3007d7b207ea3a6a0d5699d318b70dd8 Mon Sep 17 00:00:00 2001 From: fototeddy Date: Sun, 6 Sep 2026 22:05:37 +0200 Subject: [PATCH] =?UTF-8?q?Demo-Modus:=20Persistenz=20und=20K=C3=BChlbox-B?= =?UTF-8?q?edienung=20reparieren,=20Live=20Activity=20aktivieren?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- VanControl.xcodeproj/project.pbxproj | 2 ++ VanControl/Bluetooth/BluetoothManager.swift | 34 +++++++++++++++++++++ VanControl/Store/DeviceStore.swift | 5 +++ 3 files changed, 41 insertions(+) diff --git a/VanControl.xcodeproj/project.pbxproj b/VanControl.xcodeproj/project.pbxproj index fb791fb..701f30a 100644 --- a/VanControl.xcodeproj/project.pbxproj +++ b/VanControl.xcodeproj/project.pbxproj @@ -507,6 +507,7 @@ INFOPLIST_FILE = "Config/VanControl-Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = "VanControl Pro"; INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Zum Auslesen von Victron-Geraeten und dem Daly BMS per Bluetooth."; + INFOPLIST_KEY_NSSupportsLiveActivities = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; @@ -538,6 +539,7 @@ INFOPLIST_FILE = "Config/VanControl-Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = "VanControl Pro"; INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Zum Auslesen von Victron-Geraeten und dem Daly BMS per Bluetooth."; + INFOPLIST_KEY_NSSupportsLiveActivities = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; diff --git a/VanControl/Bluetooth/BluetoothManager.swift b/VanControl/Bluetooth/BluetoothManager.swift index 4915543..c7a4e9a 100644 --- a/VanControl/Bluetooth/BluetoothManager.swift +++ b/VanControl/Bluetooth/BluetoothManager.swift @@ -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. /// diff --git a/VanControl/Store/DeviceStore.swift b/VanControl/Store/DeviceStore.swift index a0a0cb2..7fbcf81 100644 --- a/VanControl/Store/DeviceStore.swift +++ b/VanControl/Store/DeviceStore.swift @@ -244,7 +244,12 @@ final class DeviceStore { } } + /// Im Demo-Modus wird nichts geschrieben – sonst überlebten die erfundenen + /// Fahrzeuge und Geräte das Ausschalten des Demo-Modus, weil z.B. das + /// Ändern der Fahrzeuggrafik oder der Einbaulage ganz normal `save()` + /// aufruft. private func save() { + guard !DemoData.isEnabled else { return } let defaults = UserDefaults.standard if let data = try? JSONEncoder().encode(devices) { defaults.set(data, forKey: Self.devicesKey)