forked from fritob/Camper-Monitor
Eigene Build-Konfiguration "CarPlay" samt Schema, damit der Versuchsbau neben der TestFlight-App stehen kann: eigener Name, eigene Bundle-ID, persoenliches Team, manuelles Signing. Debug und Release bleiben unberuehrt. Wichtig dabei: Die Signing-Einstellungen stehen ausschliesslich in den Target-Konfigurationen. Steht CODE_SIGN_STYLE auf Projektebene, schreibt der naechste xcodebuild-Lauf alle uebrigen Konfigurationen auf Manual um und loescht ihr Team – der TestFlight-Build waere damit hin. Der CarPlay-Code haengt an `CARPLAY_DEMO` und existiert im regulaeren Build nicht. Die Szene zeichnet in das gelieferte CPWindow; das CPMapTemplate darueber bleibt leer, weil es auf dem Stellplatz nichts zu tippen gibt. Die Anzeige sperrt sich oberhalb von 5 km/h selbst – erzwungen, nicht zugesagt. Die Groessen sind auf die 400x240 Punkte ausgelegt, die CarPlay liefert, nicht auf die 800x480 Pixel. In Pixeln gedachte Schriftgroessen sind auf dem Fahrzeugdisplay doppelt zu gross und schneiden den Text ab. Bekannt: Unter iOS 26.5 stuerzt Apples CarPlayTemplateUIHost beim Aufbau der Navigationsleiste ab (`_updateShareButtonVisibility` fragt seinen destinationSharingDelegate nach `vehicleSupportsDestinationSharing`, den dieser nicht kennt). Beides ist privates API und von der App aus nicht erreichbar; unter iOS 18 laeuft es. Szene und Root-Template werden vorher in beiden Faellen akzeptiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
58 lines
2.4 KiB
Swift
58 lines
2.4 KiB
Swift
import SwiftUI
|
||
|
||
@main
|
||
struct VanControlApp: App {
|
||
@State private var store: DeviceStore
|
||
@State private var bluetooth: BluetoothManager
|
||
@State private var watch: PhoneWatchLink
|
||
@State private var levelActivity: LevelActivityManager
|
||
@Environment(\.scenePhase) private var scenePhase
|
||
|
||
init() {
|
||
let store = DeviceStore()
|
||
let bluetooth = BluetoothManager(store: store)
|
||
let levelActivity = LevelActivityManager()
|
||
bluetooth.activityManager = levelActivity
|
||
_store = State(initialValue: store)
|
||
_bluetooth = State(initialValue: bluetooth)
|
||
_watch = State(initialValue: PhoneWatchLink(store: store, bluetooth: bluetooth))
|
||
_levelActivity = State(initialValue: levelActivity)
|
||
}
|
||
|
||
var body: some Scene {
|
||
WindowGroup {
|
||
DashboardView()
|
||
.environment(store)
|
||
.environment(bluetooth)
|
||
.environment(watch)
|
||
.environment(levelActivity)
|
||
.task { watch.activate() }
|
||
.carPlayPreviewOverlay()
|
||
}
|
||
.onChange(of: scenePhase) { _, phase in
|
||
// Im Hintergrund darf ohne Service-Filter ohnehin nicht gescannt
|
||
// werden, also Funk sparen und beim Zurückkommen neu starten.
|
||
//
|
||
// Ausnahme: Solange jemand auf die Uhr schaut, läuft es weiter. Beim
|
||
// Rangieren liegt das iPhone in der Halterung mit dunklem Bildschirm
|
||
// – hörte das Funkgerät dann auf, wäre die Anzeige am Handgelenk
|
||
// genau dann tot, wenn sie gebraucht wird. Die verbundenen Geräte
|
||
// (Neigungsmesser, BMS, Kühlbox) liefern im Hintergrund weiter; die
|
||
// Victron-Werbedaten nicht, dafür müsste ohne Filter gesucht werden.
|
||
watch.isAppInBackground = phase != .active
|
||
switch phase {
|
||
case .active:
|
||
bluetooth.start()
|
||
case .background:
|
||
// Läuft gerade eine Live Activity, muss der Neigungsmesser
|
||
// auch mit dunklem Bildschirm weiter Werte liefern – sonst
|
||
// friert die Anzeige auf Sperrbildschirm und CarPlay beim
|
||
// ersten Wegdrücken der App ein.
|
||
if !watch.wantsLiveUpdates && !levelActivity.isActive { bluetooth.stop() }
|
||
default:
|
||
break
|
||
}
|
||
}
|
||
}
|
||
}
|