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 { let deviceID: UUID @Environment(PhoneLink.self) private var link @State private var assistant = AlignmentAssistant() /// Damit die Vibration einmal kommt und nicht im Sekundentakt. @State private var hasAnnouncedTarget = false private var state: LevelState? { link.payload?.devices.first { $0.id == deviceID }?.level } 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: reading, initial: true) { _, reading in guard let reading else { return } assistant.add(pitch: reading.pitch, roll: reading.roll) announceIfLevel() } } /// Ein Wertepaar als Auslöser: nur wenn sich wirklich eine Neigung ändert, /// wird gerechnet – nicht bei jedem Datensatz des iPhones. private var reading: Reading? { guard let state, let pitch = state.pitch, let roll = state.roll else { return nil } return Reading(pitch: pitch, roll: roll) } private struct Reading: 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 } } }