forked from fritob/Camper-Monitor
App-Namen im iOS-Projekt auf VanControl vereinheitlichen
CamperMonitor (Haupt-Repo) und VanAligneiOS (aus dem gemergten solar-integration-Branch) liefen unter zwei verschiedenen internen Namen, obwohl die App nach aussen längst einheitlich "VanControl Pro" heisst. Jetzt durchgängig VanControl: - Ordner: CamperMonitor/, CamperMonitorWatch/, CamperMonitorComplication/, VanAligneiOSWidget/ → VanControl/, VanControlWatch/, VanControlComplication/, VanControlWidget/ - Xcode-Projekt: CamperMonitor.xcodeproj → VanControl.xcodeproj, alle Targets/Schemes/Produktnamen entsprechend umbenannt - Bundle-Identifier auf Wunsch mitgeändert: de.s0.fototeddy.VanControl* (App noch nicht veröffentlicht); dabei auch die WKCompanionAppBundleIdentifier-Werte korrigiert, die noch das alte de.fritob-Präfix statt des tatsächlichen de.s0.fototeddy-Präfixes trugen - Swift-Dateien/Typen: CamperMonitorApp → VanControlApp, VanAligneiOSWidget* → VanControlWidget* - Config/*-Info.plist umbenannt, README.md/Tools/README.md/run-tests.sh auf die neuen Pfade angepasst Bewusst unverändert: firmware/vanalign und alle Bezüge auf "VanAlign" als Namen der Neigungsmesser-Hardware (eigenständiges Produkt, kein App-Name) sowie der komplette Android/-Ordner. Build (App, Watch, Debug) und Protokoll-Testlauf grün. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
83ea85f3b8
commit
303a9735d0
@@ -0,0 +1,201 @@
|
||||
import SwiftUI
|
||||
|
||||
/// Führt durch die Einrichtung der Einbaulage des Neigungsmessers.
|
||||
///
|
||||
/// Der Sensor kann quer, gedreht oder kopfüber sitzen. Statt die Lage aus einer
|
||||
/// Liste raten zu lassen, wird sie gemessen: zweimal kippen, einmal um jede
|
||||
/// Achse, und aus der Reaktion ergibt sich die Zuordnung.
|
||||
struct SensorSetupView: View {
|
||||
let device: ConfiguredDevice
|
||||
|
||||
@Environment(DeviceStore.self) private var store
|
||||
@Environment(BluetoothManager.self) private var bluetooth
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
private enum Step {
|
||||
case intro
|
||||
case tiltNose
|
||||
case settle
|
||||
case tiltSide
|
||||
case done(SensorOrientation)
|
||||
case failed(OrientationDetection.Failure)
|
||||
}
|
||||
|
||||
@State private var step: Step = .intro
|
||||
/// Ruhelage, auf die beide Kippbewegungen bezogen werden.
|
||||
@State private var reference = OrientationDetection.Reading(pitch: 0, roll: 0)
|
||||
@State private var noseChange: OrientationDetection.Reading?
|
||||
|
||||
private var state: LevelState { bluetooth.levelStates[device.id] ?? LevelState() }
|
||||
|
||||
private var live: OrientationDetection.Reading? {
|
||||
guard let pitch = state.rawPitch, let roll = state.rawRoll else { return nil }
|
||||
return OrientationDetection.Reading(pitch: pitch, roll: roll)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ScrollView {
|
||||
VStack(spacing: 24) {
|
||||
content
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.background(Color(.systemGroupedBackground))
|
||||
.navigationTitle("Einbaulage")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Abbrechen") { dismiss() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var content: some View {
|
||||
switch step {
|
||||
case .intro:
|
||||
card(icon: "arrow.triangle.2.circlepath",
|
||||
title: "Einbaulage bestimmen",
|
||||
text: "Sitzt der Sensor quer oder verdreht im Fahrzeug, meldet er "
|
||||
+ "die Neigung vertauscht. Um das zu klären, wird er gleich "
|
||||
+ "zweimal gekippt.\n\nBaue ihn dazu so ein oder halte ihn so, "
|
||||
+ "wie er später sitzen soll. Er muss nicht angeschraubt sein – "
|
||||
+ "nur die Ausrichtung muss stimmen.")
|
||||
liveReadout
|
||||
Button("Los geht’s") {
|
||||
reference = live ?? .init(pitch: 0, roll: 0)
|
||||
step = .tiltNose
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.disabled(live == nil)
|
||||
|
||||
case .tiltNose:
|
||||
card(icon: "arrow.down.forward",
|
||||
title: "Schritt 1 von 2: nach vorne kippen",
|
||||
text: "Kippe den Sensor so, als würde das Fahrzeug **vorne "
|
||||
+ "abwärts** stehen – die Front also nach unten.\n\n"
|
||||
+ "Deutlich kippen, etwa eine Handbreit, und in dieser Lage "
|
||||
+ "halten. Dann weiter.")
|
||||
liveReadout
|
||||
Button("Weiter") { finishNoseStep() }
|
||||
.buttonStyle(.borderedProminent)
|
||||
.disabled(live == nil)
|
||||
|
||||
case .settle:
|
||||
card(icon: "equal.circle",
|
||||
title: "Zurück in die Ruhelage",
|
||||
text: "Stelle den Sensor wieder so hin wie am Anfang und lass "
|
||||
+ "ihn kurz ruhen.\n\nVon hier aus wird die zweite Bewegung "
|
||||
+ "gemessen.")
|
||||
liveReadout
|
||||
Button("Weiter") {
|
||||
reference = live ?? reference
|
||||
step = .tiltSide
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.disabled(live == nil)
|
||||
|
||||
case .tiltSide:
|
||||
card(icon: "arrow.down.left",
|
||||
title: "Schritt 2 von 2: nach links kippen",
|
||||
text: "Kippe den Sensor jetzt so, als würde das Fahrzeug **nach "
|
||||
+ "links** hängen – die linke Seite also nach unten.\n\n"
|
||||
+ "Wieder deutlich kippen und in dieser Lage halten.")
|
||||
liveReadout
|
||||
HStack {
|
||||
Button("Zurück") { step = .settle }
|
||||
.buttonStyle(.bordered)
|
||||
Button("Fertig") { finishSideStep() }
|
||||
.buttonStyle(.borderedProminent)
|
||||
.disabled(live == nil)
|
||||
}
|
||||
|
||||
case .done(let orientation):
|
||||
card(icon: "checkmark.circle.fill",
|
||||
title: "Einbaulage erkannt",
|
||||
text: "Ergebnis: **\(orientation.summary)**.\n\nDie Anzeige rechnet "
|
||||
+ "die Werte des Sensors ab jetzt auf die Achsen des Fahrzeugs "
|
||||
+ "um. Vergiss nicht, anschliessend im ebenen Stand zu "
|
||||
+ "kalibrieren.")
|
||||
Button("Übernehmen") { save(orientation) }
|
||||
.buttonStyle(.borderedProminent)
|
||||
|
||||
case .failed(let failure):
|
||||
card(icon: "exclamationmark.triangle.fill",
|
||||
title: "Das hat nicht geklappt",
|
||||
text: failure.message)
|
||||
Button("Nochmal versuchen") { step = .intro }
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
}
|
||||
|
||||
private func card(icon: String, title: String, text: String) -> some View {
|
||||
VStack(spacing: 12) {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 44))
|
||||
.foregroundStyle(.tint)
|
||||
Text(title)
|
||||
.font(.title3.weight(.semibold))
|
||||
.multilineTextAlignment(.center)
|
||||
Text(.init(text))
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(Color(.secondarySystemGroupedBackground), in: .rect(cornerRadius: 16))
|
||||
}
|
||||
|
||||
/// Was der Sensor gerade meldet – ohne Umrechnung, denn die wird hier ja
|
||||
/// erst bestimmt.
|
||||
private var liveReadout: some View {
|
||||
HStack(spacing: 16) {
|
||||
value("Achse A", state.rawPitch)
|
||||
value("Achse B", state.rawRoll)
|
||||
}
|
||||
}
|
||||
|
||||
private func value(_ title: String, _ reading: Double?) -> some View {
|
||||
VStack(spacing: 4) {
|
||||
Text(reading.map { String(format: "%.1f°", $0) } ?? "–")
|
||||
.font(.title3.weight(.semibold).monospacedDigit())
|
||||
Text(title)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 10)
|
||||
.background(Color(.secondarySystemGroupedBackground), in: .rect(cornerRadius: 12))
|
||||
}
|
||||
|
||||
// MARK: - Ablauf
|
||||
|
||||
private func finishNoseStep() {
|
||||
guard let live else { return }
|
||||
noseChange = live - reference
|
||||
// Der Bezug bleibt die Ruhelage. Von der gekippten Lage aus zu messen
|
||||
// wäre falsch: die zweite Messung enthielte dann das Zurückkippen aus
|
||||
// der ersten, und beide Achsen schlügen aus.
|
||||
step = .settle
|
||||
}
|
||||
|
||||
private func finishSideStep() {
|
||||
guard let live, let nose = noseChange else { return }
|
||||
let side = live - reference
|
||||
switch OrientationDetection.orientation(nose: nose, side: side) {
|
||||
case .success(let orientation): step = .done(orientation)
|
||||
case .failure(let failure): step = .failed(failure)
|
||||
}
|
||||
}
|
||||
|
||||
private func save(_ orientation: SensorOrientation) {
|
||||
var updated = device
|
||||
updated.sensorOrientation = orientation
|
||||
store.update(updated)
|
||||
bluetooth.updateSensorOrientation(for: updated)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user