Beim ursprünglichen Merge übersehene Dateien nachziehen

Drei Dateien aus solar-integration wurden beim Zusammenführen mit
dev_watch übersehen, weil sie fälschlich als "identisch zwischen den
Branches" eingestuft wurden:

* DemoData.isEnabled kannte nur noch den Umgebungsvariablen-Weg
  (CAMPER_DEMO=1). Der Schalter in den Einstellungen – der einzige Weg,
  Demo-Daten auf einem lokal installierten Build ohne Xcode-Verbindung
  einzuschalten – fehlte ganz, SettingsView.swift war nie zusammengeführt
  worden.
* LevelSetupView.swift fehlte der komplette Abschnitt "Fahrzeuggrafik" –
  die Kachel-Auswahl zwischen den VehicleGraphicStyle-Varianten
  (Vanster/California). Das Feld existierte im Modell und wurde beim
  Rendern gelesen, war aber nirgends einstellbar.
* DeviceCard.swift zeigte die Nivellierungs-Kachel wie jedes andere
  Gerät (Hauptwert + Nebenwerte) statt der grossen Front/Heck- und
  Links/Rechts-Anzeige mit Richtungsbuchstaben.

Anlass: Im Testbuild fehlte deshalb sowohl der Demo-Modus als auch die
Fahrzeuggrafik-Auswahl. Als Konsequenz systematisch alle beim Merge als
"bereits identisch" eingestuften Dateien erneut gegengeprüft – keine
weiteren Lücken gefunden.

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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
fototeddy
2026-09-06 21:48:40 +02:00
co-authored by Claude Sonnet 5
parent ed026ce580
commit b956324234
5 changed files with 172 additions and 19 deletions
+82 -14
View File
@@ -9,9 +9,21 @@ iOS-App, die per Bluetooth LE die Energieanlage im Wohnmobil ausliest:
| Batterie-BMS (Bulltron/Daly, WattCycle) | GATT-Verbindung, alle 5 s abgefragt | SoC, Spannung, Strom, Restkapazität, alle Einzelzellspannungen, Zelldifferenz, Temperaturen, Zyklen, MOSFET-Status | | Batterie-BMS (Bulltron/Daly, WattCycle) | GATT-Verbindung, alle 5 s abgefragt | SoC, Spannung, Strom, Restkapazität, alle Einzelzellspannungen, Zelldifferenz, Temperaturen, Zyklen, MOSFET-Status |
| Kompressor-Kühlbox (Alpicool und Baugleiche) | GATT-Verbindung, lesen **und** stellen | Ist- und Solltemperatur je Zone, Betriebsart, Kompressorstatus, Bordspannung, Batterieanzeige | | Kompressor-Kühlbox (Alpicool und Baugleiche) | GATT-Verbindung, lesen **und** stellen | Ist- und Solltemperatur je Zone, Betriebsart, Kompressorstatus, Bordspannung, Batterieanzeige |
| Neigungsmesser (VanAlign Pro) | GATT-Verbindung | Längs- und Querneigung als Libelle, Kalibrierung aus der App | | Neigungsmesser (VanAlign Pro) | GATT-Verbindung | Längs- und Querneigung als Libelle, Kalibrierung aus der App |
| Votronic-Solarladeregler (über eigene ESP32-Bridge) | GATT-Verbindung, im Takt abgefragt | PV-Leistung, PV-Spannung/-Strom, Batteriespannung, Reglertemperatur, Lade-/Reglerstatus |
Ein Victron SmartShunt/BMV wird ebenfalls unterstützt, falls später einer dazukommt. Ein Victron SmartShunt/BMV wird ebenfalls unterstützt, falls später einer dazukommt.
Victron- und Votronic-Solarladeregler sind zwei getrennte Geräterollen, absichtlich
nicht zusammengelegt: Der Victron-Regler meldet sich passiv und verschlüsselt im
Advertisement, der Votronic-Regler hängt über eine eigene ESP32-Bridge (siehe
unten) an einer GATT-Verbindung grundverschiedene Übertragungswege für
grundverschiedene Hardware. In Code und Einstellungen tragen beide deshalb
konsequent `victron`/`votronic` im Namen.
Der Neigungsmesser zeigt sich zusätzlich als **Live Activity** auf Sperrbildschirm,
Dynamic Island und seit iOS 26 automatisch im CarPlay-Dashboard, siehe
[Live Activity](#live-activity).
Dazu gehört eine **Apple-Watch-App**: Übersicht, Nivellierung samt Dazu gehört eine **Apple-Watch-App**: Übersicht, Nivellierung samt
Ausrichtungs-Assistent mit Vibration und die Steuerung der Kühlbox am Ausrichtungs-Assistent mit Vibration und die Steuerung der Kühlbox am
Handgelenk. Den Neigungsmesser funkt die Uhr selbst an, alles Übrige kommt Handgelenk. Den Neigungsmesser funkt die Uhr selbst an, alles Übrige kommt
@@ -19,7 +31,9 @@ Handgelenk. Den Neigungsmesser funkt die Uhr selbst an, alles Übrige kommt
[Apple Watch](#apple-watch). [Apple Watch](#apple-watch).
Die Firmware des Neigungsmessers liegt mit im Projekt, siehe Die Firmware des Neigungsmessers liegt mit im Projekt, siehe
[firmware/vanalign](firmware/vanalign/README.md). [firmware/vanalign](firmware/vanalign/README.md). Der Votronic-Solarregler hängt
an einer zweiten, unabhängigen ESP32-Bridge Firmware und BLE-Dienst dafür
liegen als `firmware/vanalign/esp32_ble_solar.yaml` im selben Ordner.
## Bauen und installieren ## Bauen und installieren
@@ -29,10 +43,12 @@ open /Users/fritob/GIT/Camper-Management/VanControl.xcodeproj
Dann in Xcode: Dann in Xcode:
1. Target `VanControl`**Signing & Capabilities** → dein Apple-Team auswählen. 1. Bei allen vier Targets `VanControl`, `VanControlWatch`,
Die Bundle-ID `de.fritob.VanControl` ggf. anpassen, falls sie schon vergeben ist. `VanControlComplication`, `VanControlLiveActivityExtension` unter
Dasselbe beim Target `VanControlWatch`; dessen Bundle-ID muss die des **Signing & Capabilities** dein Apple-Team auswählen. Die Bundle-IDs ggf.
iPhones mit angehängtem `.watchkitapp` bleiben. anpassen, falls sie schon vergeben sind; die der Watch-App muss die des
iPhones mit angehängtem `.watchkitapp` bleiben, die der Komplikation
zusätzlich `.levelwidget`.
2. iPhone per Kabel anschließen, oben als Ziel wählen, ⌘R. 2. iPhone per Kabel anschließen, oben als Ziel wählen, ⌘R.
Das Schema `VanControl` baut die Watch-App mit und bettet sie ein. Dafür Das Schema `VanControl` baut die Watch-App mit und bettet sie ein. Dafür
@@ -181,10 +197,17 @@ hinweg gemerkt:
In beiden Fällen: grün heißt eben (bis 0,5°), orange bis zwei Grad, darüber In beiden Fällen: grün heißt eben (bis 0,5°), orange bis zwei Grad, darüber
rot. Dazu steht in Worten, welche Seite höher steht. rot. Dazu steht in Worten, welche Seite höher steht.
Die Fahrzeugzeichnungen stammen aus dem Ursprungsprojekt VanAlign Pro. Sie Die Fahrzeugansicht kennt mehrere Grafikstile (aktuell Vanster und
liegen als Schablonen im Asset-Katalog und werden je nach Abweichung California), einstellbar je Fahrzeugprofil. Die Zeichnungen stammen aus dem
eingefärbt; die Helligkeit des Originals wurde dafür in Deckkraft übersetzt, Ursprungsprojekt VanAlign Pro. Sie liegen als Schablonen im Asset-Katalog und
damit Fenster und Konturen beim Einfärben erhalten bleiben. werden je nach Abweichung eingefärbt; die Helligkeit des Originals wurde
dafür in Deckkraft übersetzt, damit Fenster und Konturen beim Einfärben
erhalten bleiben.
Im Querformat rücken Libelle bzw. Fahrzeugansicht und die übrigen Angaben
nebeneinander statt untereinander gedacht fürs Handy in der Halterung beim
Rangieren, wo man beiläufig hinschaut statt zu scrollen. Das gilt auch für
den Ausrichtungs-Assistenten weiter unten.
**Vor der ersten Nutzung die Einbaulage bestimmen.** Sitzt der Sensor quer, **Vor der ersten Nutzung die Einbaulage bestimmen.** Sitzt der Sensor quer,
gedreht oder kopfüber, meldet er längs und quer vertauscht oder mit falschem gedreht oder kopfüber, meldet er längs und quer vertauscht oder mit falschem
@@ -273,6 +296,27 @@ Längsneigung bei 3,50 m Radstand dazu, braucht die tiefste Ecke 16,1 cm, ihre
Nachbarn 9,2 und 7,0 cm, und das höchststehende Rad bleibt liegen. Die Maße Nachbarn 9,2 und 7,0 cm, und das höchststehende Rad bleibt liegen. Die Maße
stehen im Fahrzeugprofil, erreichbar über das ⓘ in der Fahrzeugliste. stehen im Fahrzeugprofil, erreichbar über das ⓘ in der Fahrzeugliste.
### Live Activity
In der Nivellierungs-Ansicht lässt sich eine Live Activity einschalten, die
die Neigung auch bei gesperrtem Bildschirm zeigt: Sperrbildschirm, Dynamic
Island und seit iOS 26, ohne eigenes CarPlay-Ziel nötig automatisch im
CarPlay-Dashboard. Der Schalter ist deaktiviert, solange keine Messwerte
anliegen, sonst startete die Anzeige gleich mit einem veralteten Stand.
Die Aktualisierung ist bewusst auf höchstens einmal je Sekunde gedrosselt.
Der Neigungsmesser liefert deutlich öfter, und stiesse jede Messung sofort
eine Aktualisierung an, drosselt iOS das von sich aus zunehmend stärker
sichtbar vor allem in CarPlay, dessen Dashboard ohnehin zurückhaltender
aktualisiert als Sperrbildschirm oder Dynamic Island. Innerhalb der
Sperrfrist eingehende Messwerte werden nicht verworfen, sondern der
jeweils neueste für ihr Ende vorgemerkt.
Ein kurzer Verbindungsabbruch beendet die Aktivität nicht sofort erst nach
zwölf Sekunden ohne Verbindung, und meldet sich das Gerät vorher zurück,
läuft sie unverändert weiter. War sie wegen einer längeren Trennung wirklich
beendet, startet sie bei der nächsten Verbindung automatisch neu.
## Apple Watch ## Apple Watch
Die Uhr zeigt dasselbe wie das Dashboard, nur auf das eingedampft, wofür man Die Uhr zeigt dasselbe wie das Dashboard, nur auf das eingedampft, wofür man
@@ -392,8 +436,9 @@ und die Prüfsummen beider Daly-Dialekte.
## Aufbau ## Aufbau
``` ```
firmware/vanalign/ Firmware des Neigungsmessers (ESPHome) firmware/vanalign/ Firmware der beiden ESP32 im Fahrzeug (ESPHome)
├── esp32_ble.yaml Neigungsmessung und Bluetooth-Schnittstelle ├── esp32_ble.yaml Neigungsmessung und Bluetooth-Schnittstelle
├── esp32_ble_solar.yaml Zweiter ESP32: Votronic-Solarregler über BLE
└── experimente/ Nicht für den Betrieb nötig └── experimente/ Nicht für den Betrieb nötig
Shared/ In iPhone- und Watch-App übersetzt Shared/ In iPhone- und Watch-App übersetzt
@@ -404,15 +449,22 @@ Shared/ In iPhone- und Watch-App übersetzt
│ ├── ConfiguredDevice.swift Eingerichtetes Gerät, Rolle, Transportart │ ├── ConfiguredDevice.swift Eingerichtetes Gerät, Rolle, Transportart
│ ├── DeviceSnapshot.swift Messwerte in Anzeigeform │ ├── DeviceSnapshot.swift Messwerte in Anzeigeform
│ ├── LevelState.swift Neigung, Toleranz und Klartext dazu │ ├── LevelState.swift Neigung, Toleranz und Klartext dazu
── VictronCodes.swift Klartexte für Zustands-/Fehlercodes ── VictronCodes.swift Klartexte für Zustands-/Fehlercodes
│ └── VehicleGraphicStyle.swift Grafikstile der Fahrzeugansicht (Vanster/California)
├── Bluetooth/ ├── Bluetooth/
│ ├── VanAlignProtocol.swift Neigungsmesser: Winkel und Kalibrierung │ ├── VanAlignProtocol.swift Neigungsmesser: Winkel und Kalibrierung
── LevelSession.swift Verbindung zum Neigungsmesser (iPhone und Uhr) ── LevelSession.swift Verbindung zum Neigungsmesser (iPhone und Uhr)
│ ├── VotronicSolarESPProtocol.swift Votronic-Solarregler: Charakteristiken, Statusbits
│ └── VotronicSolarESPSession.swift GATT-Verbindung zur Solar-Bridge, im Takt abgefragt
├── VehicleTilt.swift Überhöhung und Farben der Fahrzeugansicht ├── VehicleTilt.swift Überhöhung und Farben der Fahrzeugansicht
├── WheelLiftPlan.swift Keilhöhen als Draufsicht auf die vier Räder ├── WheelLiftPlan.swift Keilhöhen als Draufsicht auf die vier Räder
└── WatchLink/ └── WatchLink/
└── WatchLink.swift Datensatz und Befehle zwischen iPhone und Uhr └── WatchLink.swift Datensatz und Befehle zwischen iPhone und Uhr
SharedActivity/ In Haupt-App und Live-Activity-Extension übersetzt
├── LevelActivityAttributes.swift Inhalt der Live Activity (ActivityKit)
└── LevelDirectionFormatting.swift "H 1.8°" statt Vorzeichen, für App und Extension gleich
VanControl/ VanControl/
├── Bluetooth/ ├── Bluetooth/
│ ├── BluetoothManager.swift Zentraler Scan, Verbindungen, Verlauf │ ├── BluetoothManager.swift Zentraler Scan, Verbindungen, Verlauf
@@ -425,8 +477,11 @@ VanControl/
│ ├── WattCycleProtocol.swift WattCycle, Freischaltung und Auswertung │ ├── WattCycleProtocol.swift WattCycle, Freischaltung und Auswertung
│ ├── AlpicoolProtocol.swift Kühlboxen: Auswertung und Stellbefehle │ ├── AlpicoolProtocol.swift Kühlboxen: Auswertung und Stellbefehle
│ └── BMSSession.swift GATT-Verbindung, Protokollerkennung, Abfrage │ └── BMSSession.swift GATT-Verbindung, Protokollerkennung, Abfrage
├── LiveActivity/
│ └── LevelActivityManager.swift Startet/aktualisiert/beendet die Live Activity, drosselt auf 1 Hz
├── Store/ ├── Store/
│ ├── DeviceStore.swift Geräteliste, Persistenz │ ├── DeviceStore.swift Geräteliste, Persistenz
│ ├── DemoData.swift Erfundene Werte für den Demo-Modus
│ └── KeychainStore.swift Victron-Schlüssel │ └── KeychainStore.swift Victron-Schlüssel
├── Watch/ ├── Watch/
│ └── PhoneWatchLink.swift Sendet den Stand, nimmt Befehle der Uhr an │ └── PhoneWatchLink.swift Sendet den Stand, nimmt Befehle der Uhr an
@@ -436,9 +491,11 @@ VanControl/
├── DeviceDetailView.swift Alle Werte, Verlauf, Zellspannungen, Diagnose ├── DeviceDetailView.swift Alle Werte, Verlauf, Zellspannungen, Diagnose
├── ProfilesView.swift Fahrzeuge anlegen und verwalten ├── ProfilesView.swift Fahrzeuge anlegen und verwalten
├── FridgeControls.swift Bedienelemente der Kühlbox ├── FridgeControls.swift Bedienelemente der Kühlbox
├── LevelView.swift Libelle und Kalibrierung ├── LevelView.swift Libelle, Kalibrierung, Live-Activity-Schalter
├── LevelSetupView.swift Einbaulage und Nullpunkt
├── AlignmentAssistantView.swift Ausrichtungs-Assistent fürs Rangieren ├── AlignmentAssistantView.swift Ausrichtungs-Assistent fürs Rangieren
├── VehicleTiltView.swift Neigung am Fahrzeug dargestellt ├── VehicleTiltView.swift Neigung am Fahrzeug dargestellt
├── VictronKeyView.swift Verschlüsselungsschlüssel eintragen
├── SettingsView.swift App-Einstellungen ├── SettingsView.swift App-Einstellungen
├── SensorSetupView.swift Assistent für die Einbaulage ├── SensorSetupView.swift Assistent für die Einbaulage
└── AddDeviceView.swift Scannen und Einrichten └── AddDeviceView.swift Scannen und Einrichten
@@ -457,8 +514,16 @@ VanControlWatch/ watchOS-App
├── WatchDeviceDetailView.swift Alle Werte eines Geräts ├── WatchDeviceDetailView.swift Alle Werte eines Geräts
└── WatchFridgeControls.swift Kühlbox stellen └── WatchFridgeControls.swift Kühlbox stellen
VanControlComplication/ Zifferblatt-Komplikation (watchOS)
└── LevelComplication.swift Tippt auf die Nivellierung, ohne eigenen Messwert
VanControlLiveActivity/ Live-Activity-Extension (WidgetKit)
├── VanControlLiveActivityBundle.swift Einstieg der Extension
└── VanControlLiveActivity.swift Ansicht für Sperrbildschirm, Dynamic Island, CarPlay
Config/ Config/
── VanControl-Info.plist Nur der Hintergrundbetrieb für die Uhr ── VanControl-Info.plist Nur der Hintergrundbetrieb für die Uhr
└── VanControlComplication-Info.plist Anzeigename der Komplikation
``` ```
## Bekannte Grenzen ## Bekannte Grenzen
@@ -478,6 +543,9 @@ Config/
die iPhone-App zu schliessen dann gehört der Sensor der Uhr. die iPhone-App zu schliessen dann gehört der Sensor der Uhr.
* **Auf der Uhr muss beim ersten Start Bluetooth erlaubt werden**, sonst bleibt * **Auf der Uhr muss beim ersten Start Bluetooth erlaubt werden**, sonst bleibt
die Nivellierung dort leer. die Nivellierung dort leer.
* **Die Live Activity aktualisiert höchstens einmal je Sekunde.** Öfter
drosselt iOS lokale Aktualisierungen ohnehin von sich aus, zunehmend
stärker sichtbar vor allem in CarPlay. Siehe [Live Activity](#live-activity).
* **Das Modbus-Registerlayout des neuen Daly-Protokolls variiert zwischen * **Das Modbus-Registerlayout des neuen Daly-Protokolls variiert zwischen
Firmwareständen.** Das klassische `A5`-Protokoll und das JBD-Protokoll sind Firmwareständen.** Das klassische `A5`-Protokoll und das JBD-Protokoll sind
gut dokumentiert; falls dein BMS Modbus spricht und Werte unplausibel gut dokumentiert; falls dein BMS Modbus spricht und Werte unplausibel
+17 -5
View File
@@ -3,18 +3,30 @@ import Foundation
/// Füllt die App mit erfundenen Messwerten, damit sich die Ansichten ohne /// Füllt die App mit erfundenen Messwerten, damit sich die Ansichten ohne
/// Fahrzeug und ohne Bluetooth prüfen lassen. /// Fahrzeug und ohne Bluetooth prüfen lassen.
/// ///
/// Nur in Debug-Builds und nur, wenn beim Start `CAMPER_DEMO=1` gesetzt ist: /// Nur in Debug-Builds, auf zwei Wegen einzuschalten:
/// ///
/// xcrun simctl launch --terminate-running-process \ /// * Umgebungsvariable `CAMPER_DEMO=1` beim Start praktisch im Simulator:
/// booted de.fritob.VanControl ///
/// # mit: SIMCTL_CHILD_CAMPER_DEMO=1 davor /// xcrun simctl launch --terminate-running-process \
/// booted de.fritob.VanControl
/// # mit: SIMCTL_CHILD_CAMPER_DEMO=1 davor
///
/// * Schalter in den Einstellungen (`SettingsView`) der einzige Weg auf
/// einem lokal installierten Build ohne Xcode-Verbindung, da sich dort
/// keine Umgebungsvariable setzen lässt. Wirkt erst nach einem Neustart der
/// App, weil `DeviceStore` und `BluetoothManager` den Stand nur beim Start
/// lesen.
/// ///
/// Im normalen Betrieb wird hiervon nichts ausgeführt. /// Im normalen Betrieb wird hiervon nichts ausgeführt.
enum DemoData { enum DemoData {
/// Schlüssel für den Einstellungen-Schalter, siehe `SettingsView`.
static let enabledKey = "demoModeEnabled"
static var isEnabled: Bool { static var isEnabled: Bool {
#if DEBUG #if DEBUG
return ProcessInfo.processInfo.environment["CAMPER_DEMO"] == "1" if ProcessInfo.processInfo.environment["CAMPER_DEMO"] == "1" { return true }
return UserDefaults.standard.bool(forKey: enabledKey)
#else #else
return false return false
#endif #endif
+17
View File
@@ -13,6 +13,8 @@ struct DeviceCard: View {
if isShowingLastSettings, let snapshot { if isShowingLastSettings, let snapshot {
lastSettings(for: snapshot) lastSettings(for: snapshot)
} else if let snapshot, snapshot.primaryMetric != nil, device.role == .leveling {
levelReadout(for: snapshot)
} else if let snapshot, let primary = snapshot.primaryMetric { } else if let snapshot, let primary = snapshot.primaryMetric {
HStack(alignment: .firstTextBaseline, spacing: 4) { HStack(alignment: .firstTextBaseline, spacing: 4) {
Text(primary.formatted) Text(primary.formatted)
@@ -115,6 +117,21 @@ struct DeviceCard: View {
} }
} }
/// Für die Nivellierung: Front/Heck und links/rechts nebeneinander, groß,
/// ohne "Längsneigung"/"Querneigung"-Beschriftung die Richtung steht ja
/// schon im vorangestellten Buchstaben.
private func levelReadout(for snapshot: DeviceSnapshot) -> some View {
let pitch = snapshot.metrics.first(where: { $0.key == "pitch" })?.value
let roll = snapshot.metrics.first(where: { $0.key == "roll" })?.value
return HStack(spacing: 24) {
Text(LevelDirectionFormatting.pitchTile(pitch))
Text(LevelDirectionFormatting.rollTile(roll))
}
.font(.system(size: 36, weight: .semibold, design: .rounded))
.contentTransition(.numericText())
.foregroundStyle(snapshot.isStale ? .secondary : .primary)
}
private func secondaryValues(for snapshot: DeviceSnapshot) -> some View { private func secondaryValues(for snapshot: DeviceSnapshot) -> some View {
let others = snapshot.metrics let others = snapshot.metrics
.filter { $0.id != snapshot.primaryMetric?.id && $0.value != nil } .filter { $0.id != snapshot.primaryMetric?.id && $0.value != nil }
+40
View File
@@ -25,6 +25,8 @@ struct LevelSetupView: View {
store.devices.first { $0.id == device.id } ?? device store.devices.first { $0.id == device.id } ?? device
} }
private let graphicColumns = [GridItem(.adaptive(minimum: 76), spacing: 12)]
var body: some View { var body: some View {
List { List {
Section { Section {
@@ -80,6 +82,44 @@ struct LevelSetupView: View {
} footer: { } footer: {
Text(calibrationHint) Text(calibrationHint)
} }
Section {
LazyVGrid(columns: graphicColumns, spacing: 12) {
ForEach(VehicleGraphicStyle.allCases) { style in
Button {
var updated = currentDevice
updated.vehicleGraphicStyle = style
store.update(updated)
} label: {
VStack(spacing: 4) {
Image(style.sideImageName)
.resizable()
.scaledToFit()
.padding(10)
.frame(width: 60, height: 60)
.background(currentDevice.vehicleGraphicStyle == style
? Color.accentColor.opacity(0.18)
: Color.secondary.opacity(0.08),
in: .rect(cornerRadius: 12))
.foregroundStyle(currentDevice.vehicleGraphicStyle == style
? Color.accentColor : Color.primary)
Text(style.title)
.font(.caption2)
.foregroundStyle(currentDevice.vehicleGraphicStyle == style
? Color.accentColor : Color.secondary)
}
}
.buttonStyle(.plain)
.accessibilityLabel(style.title)
}
}
.padding(.vertical, 4)
} header: {
Text("Fahrzeuggrafik")
} footer: {
Text("Wird in der Fahrzeug-Darstellung des Neigungsmessers verwendet. "
+ "Weitere Grafiken folgen.")
}
} }
.navigationTitle("Neigungsmesser einrichten") .navigationTitle("Neigungsmesser einrichten")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
+16
View File
@@ -4,12 +4,28 @@ import SwiftUI
/// betrifft, steht bei diesem Gerät. /// betrifft, steht bei diesem Gerät.
struct SettingsView: View { struct SettingsView: View {
@AppStorage(AppSettings.showDiagnosticsKey) private var showDiagnostics = false @AppStorage(AppSettings.showDiagnosticsKey) private var showDiagnostics = false
#if DEBUG
@AppStorage(DemoData.enabledKey) private var demoModeEnabled = false
#endif
@Environment(PhoneWatchLink.self) private var watch @Environment(PhoneWatchLink.self) private var watch
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
var body: some View { var body: some View {
NavigationStack { NavigationStack {
List { List {
#if DEBUG
Section {
Toggle("Demo-Modus", isOn: $demoModeEnabled)
} header: {
Text("Entwicklung")
} footer: {
Text("Füllt die App mit erfundenen Fahrzeugen und Messwerten, um "
+ "Ansichten ohne echte Sensoren zu prüfen. Wirkt erst nach "
+ "einem Neustart der App einmal beenden (im App-Umschalter "
+ "nach oben wischen) und wieder öffnen.")
}
#endif
Section { Section {
Toggle("Diagnose anzeigen", isOn: $showDiagnostics) Toggle("Diagnose anzeigen", isOn: $showDiagnostics)
} header: { } header: {