import SwiftUI /// Nivellierung auf der Uhr: Libelle, Zahlen, und was zu tun ist. struct WatchLevelView: View { /// Nur für den Einstieg – angezeigt wird immer der Stand aus dem letzten /// Datensatz, sonst bliebe die Ansicht auf dem Wert vom Öffnen stehen. let device: WatchDevice @Environment(PhoneLink.self) private var link @State private var isCalibrating = false private var current: WatchDevice { link.payload?.levelDevice ?? device } private var state: LevelState { current.level ?? LevelState() } var body: some View { ScrollView { VStack(spacing: 10) { WatchBubble(pitch: state.pitch, roll: state.roll) .frame(maxWidth: 110) HStack(spacing: 14) { angle("Längs", state.pitch) angle("Quer", state.roll) } Text(state.instruction ?? "Keine Werte") .font(.footnote.weight(.medium)) .multilineTextAlignment(.center) .foregroundStyle(state.isLevel ? .green : .primary) if let wedges = wedgeText { Text(wedges) .font(.caption2) .foregroundStyle(.secondary) .multilineTextAlignment(.center) } NavigationLink { WatchAlignView(deviceID: current.id) } label: { Label("Ausrichten", systemImage: "car.side.rear.and.collision.and.car.side.front") } .buttonStyle(.borderedProminent) Button("Kalibrieren", systemImage: "scope") { isCalibrating = true } .font(.footnote) if state.isKnownUncalibrated { Text("Noch nicht kalibriert – die Anzeige zeigt die Lage des " + "Sensors, nicht die des Fahrzeugs.") .font(.caption2) .foregroundStyle(.orange) .multilineTextAlignment(.center) } } .padding(.horizontal, 4) } .navigationTitle("Nivellierung") .confirmationDialog("Auf aktuelle Lage kalibrieren?", isPresented: $isCalibrating, titleVisibility: .visible) { Button("Kalibrieren") { link.send(.calibrateLevel(device: current.id)) } Button("Abbrechen", role: .cancel) {} } message: { Text("Nur sinnvoll, wenn das Fahrzeug jetzt eben steht.") } } private func angle(_ title: String, _ value: Double?) -> some View { VStack(spacing: 0) { Text(value.map { String(format: "%.1f°", $0) } ?? "–") .font(.title3.weight(.semibold)) .monospacedDigit() Text(title) .font(.caption2) .foregroundStyle(.secondary) } } /// Wie hoch die Auffahrkeile sein müssen – reine Geometrie, und nur zu /// haben, wenn im Fahrzeugprofil Spurweite und Radstand stehen. private var wedgeText: String? { guard let profile = link.payload?.profile else { return nil } var parts: [String] = [] if let roll = state.roll, let width = profile.trackWidth, let wedge = LevelingWedge.across(roll: roll, trackWidth: width) { parts.append(String(format: "%.0f cm %@", wedge.heightInCentimetres, wedge.side.text)) } if let pitch = state.pitch, let base = profile.wheelbase, let wedge = LevelingWedge.along(pitch: pitch, wheelbase: base) { parts.append(String(format: "%.0f cm %@", wedge.heightInCentimetres, wedge.side.text)) } return parts.isEmpty ? nil : "Keile: " + parts.joined(separator: ", ") } }