import SwiftUI import WatchKit /// Der Ausrichtungs-Assistent am Handgelenk – der eigentliche Grund für die /// Uhr: Beim Rangieren hat man das iPhone nicht in der Hand, aber den Arm am /// Lenkrad. Ein Blick nach unten genügt, und die Rückmeldung „steht eben“ kommt /// zusätzlich als Vibration, ganz ohne Blick. /// /// Gerechnet wird mit demselben `AlignmentAssistant` wie am iPhone: kein /// Ortsbezug, nur der zeitliche Verlauf der Neigung. Was besser und was /// schlechter wird, steckt vollständig darin. struct WatchAlignView: View { @Environment(PhoneLink.self) private var link @Environment(WatchLevelRadio.self) private var radio @State private var assistant = AlignmentAssistant() /// Damit die Vibration einmal kommt und nicht im Sekundentakt. @State private var hasAnnouncedTarget = false private var reading: WatchLevelReading { WatchLevelReading(radio: radio, phone: link) } private var state: LevelState? { reading.hasReading ? reading.state : nil } var body: some View { ScrollView { VStack(spacing: 8) { if let current = assistant.current { Text(String(format: "%.1f°", current.deviation)) .font(.system(size: 40, weight: .semibold, design: .rounded)) .monospacedDigit() .foregroundStyle(assistant.hasReachedTarget ? .green : .primary) .contentTransition(.numericText()) } else { ProgressView() .padding(.vertical, 12) } Label(assistant.trend.text, systemImage: assistant.trend.symbol) .font(.footnote.weight(.medium)) .foregroundStyle(trendColor) Text(assistant.advice) .font(.caption) .multilineTextAlignment(.center) .foregroundStyle(.secondary) if let state { HStack(spacing: 12) { WatchBubble(pitch: state.pitch, roll: state.roll) .frame(width: 56, height: 56) VStack(alignment: .leading, spacing: 2) { Text(String(format: "Längs %.1f°", state.pitch ?? 0)) Text(String(format: "Quer %.1f°", state.roll ?? 0)) } .font(.caption2) .monospacedDigit() .foregroundStyle(.secondary) } .padding(.top, 2) } Button("Von vorn messen", systemImage: "arrow.counterclockwise") { assistant.reset() hasAnnouncedTarget = false } .font(.caption2) .padding(.top, 4) } .padding(.horizontal, 4) } .navigationTitle("Ausrichten") .onChange(of: tilt, initial: true) { _, tilt in guard let tilt else { return } assistant.add(pitch: tilt.pitch, roll: tilt.roll) announceIfLevel() } } /// Ein Wertepaar als Auslöser: gerechnet wird nur, wenn sich die Neigung /// wirklich ändert – nicht bei jeder Aktualisierung der Anzeige. private var tilt: Tilt? { guard let state, let pitch = state.pitch, let roll = state.roll else { return nil } return Tilt(pitch: pitch, roll: roll) } private struct Tilt: Equatable { let pitch: Double let roll: Double } private var trendColor: Color { switch assistant.trend { case .improving: return .green case .worsening: return .orange default: return .secondary } } /// Die Vibration ist der Punkt: Sie sagt „anhalten“, ohne dass jemand /// hinschauen muss. Erneut gemeldet wird erst, wenn das Fahrzeug die /// Toleranz zwischendurch wieder verlassen hat. private func announceIfLevel() { if assistant.hasReachedTarget { guard !hasAnnouncedTarget else { return } hasAnnouncedTarget = true WKInterfaceDevice.current().play(.success) } else { hasAnnouncedTarget = false } } }