Author SHA1 Message Date
fototeddyandClaude Sonnet 5 8401f62d91 California-Seitenansicht eng zuschneiden statt in quadratischer Leinwand
Die Vorlage lag auf einer quadratischen 1254×1254-Leinwand, der Van
darin füllte aber nur rund 59% der Höhe – bei deklariertem
sideAspect = 1 (quadratisch) rechnete SwiftUI die Anzeigegrösse für
dieses volle Quadrat, wodurch der Van in der gemeinsamen Bildhülle
deutlich kleiner erschien als Vanster, dessen Vorlage eng auf die
Silhouette zugeschnitten ist.

Bild jetzt auf den Inhalt zugeschnitten (plus kleiner Rand, wie bei
Vanster), sideAspect entsprechend von 1 auf das tatsächliche Verhältnis
der zugeschnittenen Vorlage (1212×790) angepasst. In beiden
Asset-Katalogen ersetzt (iPhone und Watch, siehe vorherigen Commit zur
Watch-Fahrzeuggrafik).

Die Heckansicht war laut Rückmeldung bereits optisch stimmig und blieb
unverändert.

Build (App + Watch) und Tests grün.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 20:27:20 +02:00
fototeddyandClaude Sonnet 5 c45cf36920 Fahrzeuggrafik-Stil (Vanster/California) auf die Watch ausweiten
Die Uhr zeigte in der Fahrzeug-Ansicht bislang immer Vanster fest
verdrahtet, unabhängig von der Auswahl am iPhone – das galt schon vor
dieser Session, nicht erst seit dem Solar-Merge.

WatchDevice trägt jetzt vehicleGraphicStyle (nur beim Neigungsmesser
belegt, wie orientation), von PhoneWatchLink beim Aufbau der Nutzlast
gefüllt. WatchVehicleView nimmt den Stil als Parameter statt der fest
verdrahteten Vanster-Bildnamen, WatchLevelView reicht ihn aus dem
zuletzt vom iPhone übertragenen Stand durch. California-Bilder dafür
zusätzlich in den Watch-eigenen Asset-Katalog kopiert (die Uhr hat
einen eigenen, vom iPhone getrennten Katalog).

Ausgewählt wird der Stil weiterhin nur am iPhone (Neigungsmesser
einrichten → Fahrzeuggrafik). Persistenz war dafür schon vorhanden:
vehicleGraphicStyle ist ein normales, codiertes Feld auf
ConfiguredDevice ohne eigene encode(to:)-Überschreibung – wird also
schon beim Speichern mitgeschrieben.

Nebenbei: Xcode hat beim Bearbeiten den Build-Zähler auf 2 gesetzt und
LSApplicationCategoryType (Dienstprogramme) ergänzt – unverändert
übernommen.

Build (App + Watch) und Tests grün.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 20:22:32 +02:00
fototeddyandClaude Sonnet 5 47ead9b006 Watch-App-Icon an das neue Haupt-App-Icon angleichen
VanControlWatch hat ein eigenes, vom iPhone-Target unabhängiges
AppIcon.appiconset – das zeigte beim Einbinden von App1/App2 noch das
alte Logo. Jetzt dieselbe Vorlage wie das primäre iPhone-Icon
(App1-Testflight), damit iPhone- und Watch-App auf dem Homescreen
zusammengehörig aussehen.

Build (Watch + App) und Tests grün.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 20:14:17 +02:00
fototeddyandClaude Sonnet 5 b23e96615e App1/App2-Icons als primäres und Alternate-Icon einbinden
Vier neue AppIcon-Asset-Kataloge angelegt (App1/App2, jeweils Standard-
und TestFlight-Variante), aus den bereitgestellten 1024×1024-Vorlagen
(bereits randlos, ohne Alphakanal – nur noch skaliert).

Verdrahtet für den aktuellen TestFlight-Build:
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon-App1-Testflight (primär)
- ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = AppIcon-App2-Testflight
- ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES, damit auch die
  noch unverdrahteten App1/App2-Sets (für den finalen Store-Build) mit
  ins Bundle kommen, statt vom Compiler als ungenutzt verworfen zu werden

Fürs finale Release genügt es, ASSETCATALOG_COMPILER_APPICON_NAME auf
AppIcon-App1 und die Alternate-Liste auf AppIcon-App2 umzustellen – die
TestFlight-Varianten sollen Betatester optisch von der echten Version
unterscheiden.

Nur das Haupt-App-Target ist betroffen; Watch- und Complication-Icon
bleiben unverändert. Build (App + Watch) und Tests grün.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 17:14:22 +02:00
fototeddyandClaude Sonnet 5 0b3bf5e815 Neue California-Fahrzeuggrafik einbauen, eigene Heckansicht ergänzt
Seiten- und Heckansicht kamen als weiß gefüllte Vorlage ohne Alphakanal
(Tools/California Side.png, Tools/California Back.png). Dafür ein neues
Werkzeug ergänzt, das den weißen Grund in echte Transparenz übersetzt
(mit weichem statt hartem Schwellwert, um Kanten nicht auszufransen),
danach wie gewohnt durch make-vehicle-art.swift geschickt.

California hatte bisher keine eigene Heckansicht und griff ersatzweise
auf die von Vanster zurück – jetzt eigenes CaliforniaRear-Imageset,
VehicleGraphicStyle.rearImageName entsprechend angepasst. Seiten- und
Heckbild sind quadratisch (1254×1254), passend zu den bestehenden
Seitenverhältnissen (sideAspect/rearAspect = 1) – keine Anpassung dort
nötig.

Build und Tests grün.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 21:31:42 +02:00
fototeddyandClaude Sonnet 5 8d58ea6e49 Export-Compliance: ITSAppUsesNonExemptEncryption setzen
App Store Connect fragt beim Hochladen nach Verschlüsselung, wenn der
Info.plist-Schlüssel fehlt. Die App entschlüsselt Victron-Advertisements
per AES-128-CTR über CommonCrypto – ein Standardalgorithmus über eine
vom Betriebssystem gestellte Standardbibliothek, nur Nebenzweck der App
und damit nach US-Exportrecht typischerweise ausgenommen. Schlüssel auf
false gesetzt, damit die Abfrage bei künftigen Uploads entfällt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 22:53:39 +02:00
fototeddyandClaude Sonnet 5 141e321a6f Watch-Target: MARKETING_VERSION-Komma auf Punkt korrigieren
Beim Setzen der Versionsnummer in Xcode ist bei den beiden
VanControlWatch-Konfigurationen "0,1" statt "0.1" hereingerutscht,
vermutlich durch die deutsche Locale beim Zahlen-Eingabefeld. App Store
Connect erwartet ein Punkt-Format; mit dem Komma wäre das beim
Archivieren/Hochladen vermutlich angeeckt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 22:34:44 +02:00
fototeddyandClaude Sonnet 5 35f4e6fbcb Demo-Modus auch in Release-/TestFlight-Builds verfügbar machen
Der Einstellungen-Schalter für den Demo-Modus war in #if DEBUG
eingeschlossen und damit in Archiven (Release-Konfiguration, wie sie
Product → Archive und damit jeder TestFlight-Build verwendet) komplett
herauskompiliert. TestFlight-Tester konnten den Demo-Modus dadurch nicht
nutzen.

DemoData.isEnabled und der Schalter in SettingsView sind jetzt
konfigurationsunabhängig; nur die CAMPER_DEMO-Umgebungsvariable bleibt
auf Debug-Builds beschränkt, da sie ohnehin nur über Xcode/simctl
gesetzt werden kann.

Build (Debug + Release) und Tests grün; per strings(1) im
Release-Binary verifiziert, dass der Demo-Modus-Code tatsächlich
mitkompiliert wird.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 22:33:11 +02:00
29 changed files with 226 additions and 60 deletions
+10 -8
View File
@@ -430,16 +430,18 @@ eingerichtet wurden, wandern beim Update automatisch ins erste Profil.
## Ohne Fahrzeug ansehen
Ein Demo-Modus füllt die App mit erfundenen Fahrzeugen und Messwerten, damit
sich die Ansichten ohne Bluetooth prüfen lassen. Nur in Debug-Builds, auf zwei
Wegen einzuschalten:
sich die Ansichten ohne Bluetooth prüfen lassen auch ohne die Hardware zur
Hand zu haben. Auf zwei Wegen einzuschalten:
* **Schalter in den Einstellungen** (*Einstellungen → Entwicklung →
Demo-Modus*) der Weg für einen lokal installierten Build ohne
Xcode-Verbindung, etwa auf dem eigenen iPhone. Wirkt erst nach einem
Neustart der App (im App-Umschalter nach oben wischen, dann neu öffnen),
weil `DeviceStore` und `BluetoothManager` den Stand nur beim Start lesen.
* **Umgebungsvariable** `CAMPER_DEMO=1` in Xcode unter *Product → Scheme →
Edit Scheme → Run → Arguments*, oder im Simulator direkt:
Demo-Modus*) in jeder Build-Konfiguration verfügbar, auch in TestFlight-
und App-Store-Builds. Der Weg für jeden lokal installierten Build ohne
Xcode-Verbindung. Wirkt erst nach einem Neustart der App (im
App-Umschalter nach oben wischen, dann neu öffnen), weil `DeviceStore` und
`BluetoothManager` den Stand nur beim Start lesen.
* **Umgebungsvariable** `CAMPER_DEMO=1` nur in Debug-Builds, in Xcode unter
*Product → Scheme → Edit Scheme → Run → Arguments*, oder im Simulator
direkt:
```bash
xcrun simctl launch --terminate-running-process booted <deine-bundle-id>
+3 -4
View File
@@ -26,11 +26,10 @@ enum VehicleGraphicStyle: String, CaseIterable, Identifiable, Codable, Sendable
}
}
/// Für California liegt noch keine eigene Heckansicht vor, deshalb
/// vorerst die von Vanster.
var rearImageName: String {
switch self {
case .vanster, .california: return "VansterRear"
case .vanster: return "VansterRear"
case .california: return "CaliforniaRear"
}
}
@@ -40,7 +39,7 @@ enum VehicleGraphicStyle: String, CaseIterable, Identifiable, Codable, Sendable
var sideAspect: Double {
switch self {
case .vanster: return VehicleTilt.sideAspect
case .california: return 1
case .california: return 1212.0 / 790.0
}
}
+4
View File
@@ -92,6 +92,10 @@ struct WatchDevice: Codable, Equatable, Identifiable, Sendable {
/// den Neigungsmesser selbst an und braucht die Zuordnung deshalb auch
/// eingestellt wird sie weiterhin nur am iPhone.
var orientation: SensorOrientation?
/// Ebenfalls nur beim Neigungsmesser: welche Fahrzeuggrafik die
/// Fahrzeug-Ansicht zeigt. Ausgewählt wird sie nur am iPhone, die Uhr
/// übernimmt nur die Anzeige.
var vehicleGraphicStyle: VehicleGraphicStyle?
/// Nur bei der Kühlbox belegt.
var fridge: WatchFridge?
+4 -3
View File
@@ -1190,13 +1190,14 @@ let watchPayload = WatchPayload(
devices: [
WatchDevice(id: watchLevelID, name: "Nivellierung", role: .leveling,
link: .live, snapshot: watchLevelSnapshot,
level: watchLevel, orientation: watchOrientation, fridge: nil),
level: watchLevel, orientation: watchOrientation,
vehicleGraphicStyle: .vanster, fridge: nil),
WatchDevice(id: watchBoosterID, name: "Ladebooster", role: .chargeBooster,
link: .failed("Nicht gefunden"), snapshot: boosterSnapshot,
level: nil, orientation: nil, fridge: nil),
level: nil, orientation: nil, vehicleGraphicStyle: nil, fridge: nil),
WatchDevice(id: watchFridgeID, name: "Kühlbox", role: .fridge,
link: .live, snapshot: nil, level: nil,
orientation: nil, fridge: watchFridge),
orientation: nil, vehicleGraphicStyle: nil, fridge: watchFridge),
])
// Der ganze Weg: verpacken, als WatchConnectivity-Wörterbuch lesen, vergleichen.
+19 -2
View File
@@ -38,5 +38,22 @@ sips --resampleHeight 600 heckansicht.png --out /tmp/rear.png
Jedes Fahrzeug-Grafikset (siehe `VehicleGraphicStyle`) hat sein eigenes
Bildpaar `<Name>Side`/`<Name>Rear`. Hat die Vorlage schon einen
transparenten Hintergrund, geht die Quelldatei direkt ins Skript. Liegt sie
auf weißem Grund, muss der Rand vorher weg sonst wird die ganze Fläche
mitgefärbt.
auf weißem Grund, muss der Rand vorher weg siehe `remove-white-background.swift`.
## remove-white-background.swift
Vorstufe zu `make-vehicle-art.swift`, wenn die Vorlage auf weißem statt
transparentem Grund liegt (kein Alphakanal). Ohne diesen Schritt hielte
`make-vehicle-art` die ganze Fläche für Zeichnung und färbte den Hintergrund
mit ein.
Reines Weiß wird voll durchsichtig, alles andere bleibt stehen; ein weicher
Übergang statt einer harten Schwelle vermeidet gezackte Kanten an
kantengeglätteten Linien.
```bash
swiftc -O -o /tmp/remove-white-background Tools/remove-white-background.swift
swiftc -O -o /tmp/make-vehicle-art Tools/make-vehicle-art.swift
/tmp/remove-white-background seitenansicht.png /tmp/seite-transparent.png
/tmp/make-vehicle-art /tmp/seite-transparent.png VanControl/Assets.xcassets/CaliforniaSide.imageset/CaliforniaSide.png
```
+62
View File
@@ -0,0 +1,62 @@
import AppKit
import CoreGraphics
import Foundation
// Vorlagen kommen manchmal auf weißem statt transparentem Grund. Ohne
// Alphakanal hielte `make-vehicle-art.swift` die ganze Fläche für Zeichnung
// und färbte den Hintergrund mit ein. Dieses Werkzeug macht daraus einen
// echten Alphakanal: reines Weiß wird voll durchsichtig, alles andere bleibt
// stehen. Ein weicher Übergang statt einer harten Schwelle verhindert
// gezackte Kanten an den (leicht kantengeglätteten) Linien.
let arguments = CommandLine.arguments
guard arguments.count >= 3,
let source = NSImage(contentsOfFile: arguments[1]),
let image = source.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
print("Quelle nicht lesbar"); exit(1)
}
let width = image.width, height = image.height
let bytesPerRow = width * 4
var pixels = [UInt8](repeating: 0, count: bytesPerRow * height)
guard let context = CGContext(data: &pixels, width: width, height: height,
bitsPerComponent: 8, bytesPerRow: bytesPerRow,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
print("Puffer nicht anlegbar"); exit(1)
}
context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
/// Unterhalb davon gilt ein Pixel als reiner Hintergrund, oberhalb als
/// sicher Zeichnung. Dazwischen wird linear zwischen 0 und voller Deckkraft
/// überblendet, damit Kanten nicht ausgefranst wirken.
let whiteFloor = 230.0
let whiteCeiling = 250.0
for index in stride(from: 0, to: pixels.count, by: 4) {
// Quelle ist opak (kein Alphakanal), also stehen hier die Rohfarben.
let r = Double(pixels[index])
let g = Double(pixels[index + 1])
let b = Double(pixels[index + 2])
let whiteness = min(r, g, b)
let t = min(1, max(0, (whiteness - whiteFloor) / (whiteCeiling - whiteFloor)))
let alpha = 1 - t
guard alpha > 0.004 else {
pixels[index] = 0; pixels[index + 1] = 0; pixels[index + 2] = 0; pixels[index + 3] = 0
continue
}
// Premultipliziert ablegen.
pixels[index] = UInt8(r * alpha)
pixels[index + 1] = UInt8(g * alpha)
pixels[index + 2] = UInt8(b * alpha)
pixels[index + 3] = UInt8(alpha * 255)
}
guard let result = context.makeImage() else { print("Bild leer"); exit(1) }
let rep = NSBitmapImageRep(cgImage: result)
guard let png = rep.representation(using: .png, properties: [:]) else {
print("PNG nicht erzeugbar"); exit(1)
}
try png.write(to: URL(fileURLWithPath: arguments[2]))
print("\(arguments[2]): fertig")
+26 -18
View File
@@ -496,16 +496,20 @@
AA0000000000000000000014 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIcon-App2-Testflight";
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-App1-Testflight";
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = V5C6Q86XJR;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Config/VanControl-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "VanControl Pro";
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Zum Auslesen von Victron-Geraeten und dem Daly BMS per Bluetooth.";
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
@@ -516,7 +520,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1;
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -528,16 +532,20 @@
AA0000000000000000000015 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIcon-App2-Testflight";
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-App1-Testflight";
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = V5C6Q86XJR;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Config/VanControl-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "VanControl Pro";
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Zum Auslesen von Victron-Geraeten und dem Daly BMS per Bluetooth.";
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
@@ -548,7 +556,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1;
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -563,7 +571,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = V5C6Q86XJR;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -574,7 +582,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1;
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = watchos;
@@ -595,7 +603,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = V5C6Q86XJR;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -606,7 +614,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1;
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = watchos;
@@ -625,7 +633,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = V5C6Q86XJR;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Config/VanControlComplication-Info.plist";
@@ -635,7 +643,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1;
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp.levelwidget;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = watchos;
@@ -654,7 +662,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = V5C6Q86XJR;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Config/VanControlComplication-Info.plist";
@@ -664,7 +672,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1;
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp.levelwidget;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = watchos;
@@ -685,7 +693,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = V5C6Q86XJR;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = VanControlLiveActivity/Info.plist;
@@ -696,7 +704,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1;
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.VanControlLiveActivity;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -714,7 +722,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = V5C6Q86XJR;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = VanControlLiveActivity/Info.plist;
@@ -725,7 +733,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1;
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.VanControlLiveActivity;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

@@ -0,0 +1,11 @@
{
"images" : [
{
"filename" : "AppIcon-App1-Testflight-1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : { "author" : "xcode", "version" : 1 }
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

@@ -0,0 +1,11 @@
{
"images" : [
{
"filename" : "AppIcon-App1-1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : { "author" : "xcode", "version" : 1 }
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

@@ -0,0 +1,11 @@
{
"images" : [
{
"filename" : "AppIcon-App2-Testflight-1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : { "author" : "xcode", "version" : 1 }
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

@@ -0,0 +1,11 @@
{
"images" : [
{
"filename" : "AppIcon-App2-1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : { "author" : "xcode", "version" : 1 }
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

@@ -0,0 +1,10 @@
{
"images" : [
{
"filename" : "CaliforniaRear.png",
"idiom" : "universal"
}
],
"info" : { "author" : "xcode", "version" : 1 },
"properties" : { "template-rendering-intent" : "template" }
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 KiB

After

Width:  |  Height:  |  Size: 311 KiB

+9 -13
View File
@@ -3,21 +3,19 @@ import Foundation
/// Füllt die App mit erfundenen Messwerten, damit sich die Ansichten ohne
/// Fahrzeug und ohne Bluetooth prüfen lassen.
///
/// Nur in Debug-Builds, auf zwei Wegen einzuschalten:
/// Auf zwei Wegen einzuschalten:
///
/// * Umgebungsvariable `CAMPER_DEMO=1` beim Start praktisch im Simulator:
/// * Schalter in den Einstellungen (`SettingsView`) in jeder
/// Build-Konfiguration verfügbar, auch in TestFlight- und
/// App-Store-Builds. Der Weg für jeden lokal installierten Build ohne
/// Xcode-Verbindung. Wirkt erst nach einem Neustart der App, weil
/// `DeviceStore` und `BluetoothManager` den Stand nur beim Start lesen.
/// * Umgebungsvariable `CAMPER_DEMO=1` beim Start nur in Debug-Builds,
/// praktisch im Simulator:
///
/// xcrun simctl launch --terminate-running-process \
/// booted de.fritob.VanControl
/// # mit: SIMCTL_CHILD_CAMPER_DEMO=1 davor
///
/// * Schalter in den Einstellungen (`SettingsView`) der einzige Weg auf
/// einem lokal installierten Build ohne Xcode-Verbindung, da sich dort
/// keine Umgebungsvariable setzen lässt. Wirkt erst nach einem Neustart der
/// App, weil `DeviceStore` und `BluetoothManager` den Stand nur beim Start
/// lesen.
///
/// Im normalen Betrieb wird hiervon nichts ausgeführt.
enum DemoData {
/// Schlüssel für den Einstellungen-Schalter, siehe `SettingsView`.
@@ -26,10 +24,8 @@ enum DemoData {
static var isEnabled: Bool {
#if DEBUG
if ProcessInfo.processInfo.environment["CAMPER_DEMO"] == "1" { return true }
return UserDefaults.standard.bool(forKey: enabledKey)
#else
return false
#endif
return UserDefaults.standard.bool(forKey: enabledKey)
}
static let mainProfile = Profile(id: Profile.defaultID, name: "Kastenwagen",
-4
View File
@@ -4,16 +4,13 @@ import SwiftUI
/// betrifft, steht bei diesem Gerät.
struct SettingsView: View {
@AppStorage(AppSettings.showDiagnosticsKey) private var showDiagnostics = false
#if DEBUG
@AppStorage(DemoData.enabledKey) private var demoModeEnabled = false
#endif
@Environment(PhoneWatchLink.self) private var watch
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationStack {
List {
#if DEBUG
Section {
Toggle("Demo-Modus", isOn: $demoModeEnabled)
} header: {
@@ -24,7 +21,6 @@ struct SettingsView: View {
+ "einem Neustart der App einmal beenden (im App-Umschalter "
+ "nach oben wischen) und wieder öffnen.")
}
#endif
Section {
Toggle("Diagnose anzeigen", isOn: $showDiagnostics)
+1
View File
@@ -135,6 +135,7 @@ final class PhoneWatchLink: NSObject {
snapshot: bluetooth.snapshots[device.id],
level: device.role == .leveling ? bluetooth.levelStates[device.id] : nil,
orientation: device.role == .leveling ? device.sensorOrientation : nil,
vehicleGraphicStyle: device.role == .leveling ? device.vehicleGraphicStyle : nil,
fridge: device.role == .fridge ? fridgeState(for: device) : nil)
}
return WatchPayload(generatedAt: Date(),
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

@@ -0,0 +1,10 @@
{
"images" : [
{
"filename" : "CaliforniaRear.png",
"idiom" : "universal"
}
],
"info" : { "author" : "xcode", "version" : 1 },
"properties" : { "template-rendering-intent" : "template" }
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

@@ -0,0 +1,10 @@
{
"images" : [
{
"filename" : "CaliforniaSide.png",
"idiom" : "universal"
}
],
"info" : { "author" : "xcode", "version" : 1 },
"properties" : { "template-rendering-intent" : "template" }
}
+2 -1
View File
@@ -50,7 +50,8 @@ struct LevelReadingPage: View {
ScrollView {
VStack(spacing: 8) {
if showsVehicle {
WatchVehicleView(pitch: reading.state.pitch, roll: reading.state.roll)
WatchVehicleView(pitch: reading.state.pitch, roll: reading.state.roll,
style: link.payload?.levelDevice?.vehicleGraphicStyle ?? .vanster)
} else {
WatchBubble(pitch: reading.state.pitch, roll: reading.state.roll)
.frame(maxWidth: 110)
+7 -4
View File
@@ -9,15 +9,18 @@ import SwiftUI
struct WatchVehicleView: View {
let pitch: Double?
let roll: Double?
/// Vom iPhone übernommen ausgewählt wird sie nur dort, siehe
/// `WatchDevice.vehicleGraphicStyle`.
var style: VehicleGraphicStyle = .vanster
/// Wie am iPhone dreifach überhöht zwei Grad wären sonst kaum zu sehen.
/// Die angeschriebenen Gradzahlen bleiben echt.
var body: some View {
VStack(spacing: 8) {
panel(image: "VansterSide", angle: pitch, title: "Längs",
lower: "Front", upper: "Heck", aspect: VehicleTilt.sideAspect)
panel(image: "VansterRear", angle: roll, title: "Quer",
lower: "links", upper: "rechts", aspect: VehicleTilt.rearAspect)
panel(image: style.sideImageName, angle: pitch, title: "Längs",
lower: "Front", upper: "Heck", aspect: style.sideAspect)
panel(image: style.rearImageName, angle: roll, title: "Quer",
lower: "links", upper: "rechts", aspect: style.rearAspect)
}
}
+5 -3
View File
@@ -63,15 +63,17 @@ enum WatchDemo {
devices: [
WatchDevice(id: levelID, name: "Nivellierung", role: .leveling,
link: .live, snapshot: level.snapshot(deviceID: levelID, rssi: -70),
level: level, orientation: .identity, fridge: nil),
level: level, orientation: .identity,
vehicleGraphicStyle: .vanster, fridge: nil),
WatchDevice(id: batteryID, name: "Bulltron 200 Ah", role: .bms,
link: .live, snapshot: battery, level: nil,
orientation: nil, fridge: nil),
orientation: nil, vehicleGraphicStyle: nil, fridge: nil),
WatchDevice(id: victronSolarID, name: "Solar Dach", role: .victronSolarCharger,
link: .live, snapshot: victronSolar, level: nil,
orientation: nil, fridge: nil),
orientation: nil, vehicleGraphicStyle: nil, fridge: nil),
WatchDevice(id: fridgeID, name: "Kühlbox", role: .fridge,
link: .live, snapshot: fridge, level: nil, orientation: nil,
vehicleGraphicStyle: nil,
fridge: WatchFridge(isLive: true, updated: now,
isPoweredOn: true, isEco: true, isLocked: false,
isDualZone: false, unitSymbol: "°C",