first commit
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
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 {
|
||||
// Ohne ScrollView: Beim Rangieren schaut man kurz hin, da darf nichts
|
||||
// ausserhalb des Bildes liegen. Alles ist deshalb so bemessen, dass es
|
||||
// auch auf der kleinsten Uhr auf einen Blick passt.
|
||||
VStack(spacing: 3) {
|
||||
if let current = assistant.current {
|
||||
Text(String(format: "%.1f°", current.deviation))
|
||||
.font(.system(size: 30, weight: .semibold, design: .rounded))
|
||||
.monospacedDigit()
|
||||
.foregroundStyle(assistant.hasReachedTarget ? .green : .primary)
|
||||
.contentTransition(.numericText())
|
||||
} else {
|
||||
ProgressView()
|
||||
.padding(.vertical, 6)
|
||||
}
|
||||
|
||||
Label(assistant.trend.text, systemImage: assistant.trend.symbol)
|
||||
.font(.caption2.weight(.medium))
|
||||
.foregroundStyle(trendColor)
|
||||
|
||||
Text(assistant.advice)
|
||||
.font(.system(size: 11))
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(2)
|
||||
.minimumScaleFactor(0.8)
|
||||
|
||||
if let state {
|
||||
HStack(spacing: 8) {
|
||||
WatchBubble(pitch: state.pitch, roll: state.roll)
|
||||
.frame(width: 40, height: 40)
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
Text(String(format: "Längs %.1f°", state.pitch ?? 0))
|
||||
Text(String(format: "Quer %.1f°", state.roll ?? 0))
|
||||
}
|
||||
.font(.system(size: 11))
|
||||
.monospacedDigit()
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
assistant.reset()
|
||||
hasAnnouncedTarget = false
|
||||
} label: {
|
||||
Label("Von vorn", systemImage: "arrow.counterclockwise")
|
||||
.font(.system(size: 10))
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.horizontal, 2)
|
||||
.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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user