diff --git a/CamperMonitor/Views/LevelSetupView.swift b/CamperMonitor/Views/LevelSetupView.swift new file mode 100644 index 0000000..876e317 --- /dev/null +++ b/CamperMonitor/Views/LevelSetupView.swift @@ -0,0 +1,125 @@ +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 + } + + 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) + } + } + .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) + } + } +} diff --git a/CamperMonitor/Views/LevelView.swift b/CamperMonitor/Views/LevelView.swift index 0baa3a8..5151f71 100644 --- a/CamperMonitor/Views/LevelView.swift +++ b/CamperMonitor/Views/LevelView.swift @@ -107,10 +107,8 @@ struct LevelControls: View { @Environment(BluetoothManager.self) private var bluetooth @Environment(DeviceStore.self) private var store - @State private var showResetConfirmation = false @State private var showAssistant = false @AppStorage("levelDisplayStyle") private var displayStyle: LevelDisplayStyle = .bubble - @State private var showSensorSetup = false var body: some View { Section { @@ -165,65 +163,30 @@ struct LevelControls: View { .disabled(!state.hasReading) } + // Einbaulage und Nullpunkt liegen auf einer eigenen Seite. Direkt unter + // der Libelle verstellte ein Fehlgriff beim Ablesen den Nullpunkt. Section { - Button { - showSensorSetup = true + NavigationLink { + LevelSetupView(device: device) } label: { LabeledContent { - Text(device.sensorOrientation.summary) - .font(.caption) + Text(setupSummary).font(.caption) } label: { - Label("Einbaulage", systemImage: "arrow.triangle.2.circlepath") + Label("Neigungsmesser einrichten", systemImage: "slider.horizontal.3") } } - } header: { - Text("Sensor") } footer: { - Text("Sitzt der Sensor quer oder verdreht, meldet er längs und quer " - + "vertauscht. Der Assistent klärt das durch zweimaliges Kippen.") - } - - Section { - Button("Auf aktuelle Lage kalibrieren", systemImage: "scope") { - bluetooth.calibrateLevel(for: device.id) - } - if !state.isKnownUncalibrated { - Button("Kalibrierung zurücksetzen", systemImage: "arrow.uturn.backward", role: .destructive) { - showResetConfirmation = true - } - } - } header: { - Text("Kalibrierung") - } footer: { - if state.isCalibrated, let pitchOffset = state.pitchOffset, - let rollOffset = state.rollOffset { - Text(String(format: "Nullpunkt liegt bei %.1f° längs und %.1f° quer. " - + "Zum Kalibrieren das Fahrzeug eben stellen und dann tippen.", - pitchOffset, rollOffset)) - } else if state.isKnownUncalibrated { - Text("Noch nicht kalibriert – die Anzeige zeigt die Lage des Sensors, " - + "nicht die des Fahrzeugs. Fahrzeug eben stellen und dann kalibrieren.") - } else { - // Ältere Firmware gibt die Offsets nicht heraus. - Text("Zum Kalibrieren das Fahrzeug eben stellen und dann tippen. " - + "Ob bereits kalibriert wurde, meldet dieses Gerät nicht zurück.") - } - } - .sheet(isPresented: $showSensorSetup) { - SensorSetupView(device: device) + Text("Einbaulage des Sensors und Nullpunkt der Anzeige.") } .fullScreenCover(isPresented: $showAssistant) { AlignmentAssistantView(device: device, profile: store.activeProfile) } - .confirmationDialog("Kalibrierung zurücksetzen?", - isPresented: $showResetConfirmation, - titleVisibility: .visible) { - Button("Zurücksetzen", role: .destructive) { - bluetooth.resetLevelCalibration(for: device.id) - } - } message: { - Text("Die Anzeige zeigt danach wieder die Lage des Sensors.") - } + } + + /// Was auf der Einrichtungsseite zu holen ist – der Nullpunkt zuerst, denn + /// ohne ihn stimmt die Anzeige nicht. + private var setupSummary: String { + state.isKnownUncalibrated ? "Nullpunkt fehlt" : device.sensorOrientation.summary } private func reading(_ title: String, _ value: Double?) -> some View {