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:
fototeddy
2026-09-06 21:25:28 +02:00
co-authored by Claude Sonnet 5
parent 83ea85f3b8
commit 303a9735d0
79 changed files with 190 additions and 190 deletions
+115
View File
@@ -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
}
}
}