forked from fritob/Camper-Monitor
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>
166 lines
7.0 KiB
Swift
166 lines
7.0 KiB
Swift
import SwiftUI
|
||
|
||
/// Einrichtung des Neigungsmessers: Einbaulage und Nullpunkt.
|
||
///
|
||
/// Beides wird einmal eingestellt und danach kaum wieder angefasst. In der
|
||
/// Geräteansicht standen die Knöpfe direkt unter der Libelle – ein Fehlgriff
|
||
/// beim Ablesen verstellte dort den Nullpunkt. Deshalb liegen sie hier.
|
||
///
|
||
/// Die Reihenfolge ist nicht beliebig: erst muss klar sein, welche Achse des
|
||
/// Sensors welche des Fahrzeugs ist, sonst wird der Nullpunkt auf die falsche
|
||
/// Achse gelegt.
|
||
struct LevelSetupView: View {
|
||
let device: ConfiguredDevice
|
||
|
||
@Environment(BluetoothManager.self) private var bluetooth
|
||
@State private var showSensorSetup = false
|
||
@State private var showResetConfirmation = false
|
||
|
||
private var state: LevelState { bluetooth.levelStates[device.id] ?? LevelState() }
|
||
|
||
/// Immer der aktuelle Stand – `device` veraltet, sobald der Assistent
|
||
/// eine neue Einbaulage gespeichert hat.
|
||
@Environment(DeviceStore.self) private var store
|
||
private var currentDevice: ConfiguredDevice {
|
||
store.devices.first { $0.id == device.id } ?? device
|
||
}
|
||
|
||
private let graphicColumns = [GridItem(.adaptive(minimum: 76), spacing: 12)]
|
||
|
||
var body: some View {
|
||
List {
|
||
Section {
|
||
HStack(spacing: 24) {
|
||
reading("Längs", state.pitch)
|
||
reading("Quer", state.roll)
|
||
}
|
||
.frame(maxWidth: .infinity)
|
||
.padding(.vertical, 4)
|
||
}
|
||
|
||
Section {
|
||
Button {
|
||
showSensorSetup = true
|
||
} label: {
|
||
LabeledContent {
|
||
Text(currentDevice.sensorOrientation.summary)
|
||
.font(.caption)
|
||
} label: {
|
||
Label("Einbaulage bestimmen",
|
||
systemImage: "arrow.triangle.2.circlepath")
|
||
}
|
||
}
|
||
.disabled(!state.hasReading)
|
||
} header: {
|
||
Text("Schritt 1 – Einbaulage")
|
||
} footer: {
|
||
Text("Sitzt der Sensor quer oder verdreht im Fahrzeug, meldet er längs "
|
||
+ "und quer vertauscht. Der Assistent klärt das durch zweimaliges "
|
||
+ "Kippen. Danach den Nullpunkt setzen.")
|
||
}
|
||
|
||
Section {
|
||
Button {
|
||
bluetooth.calibrateLevel(for: device.id)
|
||
} label: {
|
||
Label("Aktuelle Lage als eben übernehmen", systemImage: "scope")
|
||
}
|
||
.disabled(!state.hasReading)
|
||
|
||
if !state.isKnownUncalibrated {
|
||
Button(role: .destructive) {
|
||
showResetConfirmation = true
|
||
} label: {
|
||
// Ohne das bliebe das Symbol in der Akzentfarbe, während
|
||
// die Beschriftung rot ist.
|
||
Label("Nullpunkt verwerfen", systemImage: "arrow.uturn.backward")
|
||
.foregroundStyle(.red)
|
||
}
|
||
}
|
||
} header: {
|
||
Text("Schritt 2 – Nullpunkt")
|
||
} footer: {
|
||
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")
|
||
.navigationBarTitleDisplayMode(.inline)
|
||
.sheet(isPresented: $showSensorSetup) {
|
||
SensorSetupView(device: currentDevice)
|
||
}
|
||
.confirmationDialog("Nullpunkt verwerfen?",
|
||
isPresented: $showResetConfirmation,
|
||
titleVisibility: .visible) {
|
||
Button("Verwerfen", role: .destructive) {
|
||
bluetooth.resetLevelCalibration(for: device.id)
|
||
}
|
||
} message: {
|
||
Text("Die Anzeige zeigt danach wieder die Lage des Sensors.")
|
||
}
|
||
}
|
||
|
||
private var calibrationHint: String {
|
||
if state.isCalibrated, let pitchOffset = state.pitchOffset,
|
||
let rollOffset = state.rollOffset {
|
||
return String(format: "Der Nullpunkt liegt bei %.1f° längs und %.1f° quer. "
|
||
+ "Zum Neusetzen das Fahrzeug eben stellen und dann tippen.",
|
||
pitchOffset, rollOffset)
|
||
}
|
||
if state.isKnownUncalibrated {
|
||
return "Noch kein Nullpunkt gesetzt – die Anzeige zeigt die Lage des "
|
||
+ "Sensors, nicht die des Fahrzeugs. Fahrzeug eben stellen, dann tippen."
|
||
}
|
||
// Ältere Firmware gibt die Offsets nicht heraus.
|
||
return "Zum Setzen das Fahrzeug eben stellen und dann tippen. Ob schon ein "
|
||
+ "Nullpunkt gesetzt wurde, meldet dieses Gerät nicht zurück."
|
||
}
|
||
|
||
private func reading(_ title: String, _ value: Double?) -> some View {
|
||
VStack(spacing: 2) {
|
||
Text(value.map { String(format: "%.1f°", $0) } ?? "–")
|
||
.font(.title2.weight(.semibold).monospacedDigit())
|
||
Text(title)
|
||
.font(.caption)
|
||
.foregroundStyle(.secondary)
|
||
}
|
||
}
|
||
}
|