Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8401f62d91 | ||
|
|
c45cf36920 | ||
|
|
47ead9b006 | ||
|
|
b23e96615e | ||
|
|
0b3bf5e815 | ||
|
|
8d58ea6e49 | ||
|
|
141e321a6f | ||
|
|
35f4e6fbcb |
@@ -430,16 +430,18 @@ eingerichtet wurden, wandern beim Update automatisch ins erste Profil.
|
|||||||
## Ohne Fahrzeug ansehen
|
## Ohne Fahrzeug ansehen
|
||||||
|
|
||||||
Ein Demo-Modus füllt die App mit erfundenen Fahrzeugen und Messwerten, damit
|
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
|
sich die Ansichten ohne Bluetooth prüfen lassen – auch ohne die Hardware zur
|
||||||
Wegen einzuschalten:
|
Hand zu haben. Auf zwei Wegen einzuschalten:
|
||||||
|
|
||||||
* **Schalter in den Einstellungen** (*Einstellungen → Entwicklung →
|
* **Schalter in den Einstellungen** (*Einstellungen → Entwicklung →
|
||||||
Demo-Modus*) – der Weg für einen lokal installierten Build ohne
|
Demo-Modus*) – in jeder Build-Konfiguration verfügbar, auch in TestFlight-
|
||||||
Xcode-Verbindung, etwa auf dem eigenen iPhone. Wirkt erst nach einem
|
und App-Store-Builds. Der Weg für jeden lokal installierten Build ohne
|
||||||
Neustart der App (im App-Umschalter nach oben wischen, dann neu öffnen),
|
Xcode-Verbindung. Wirkt erst nach einem Neustart der App (im
|
||||||
weil `DeviceStore` und `BluetoothManager` den Stand nur beim Start lesen.
|
App-Umschalter nach oben wischen, dann neu öffnen), weil `DeviceStore` und
|
||||||
* **Umgebungsvariable** `CAMPER_DEMO=1` – in Xcode unter *Product → Scheme →
|
`BluetoothManager` den Stand nur beim Start lesen.
|
||||||
Edit Scheme → Run → Arguments*, oder im Simulator direkt:
|
* **Umgebungsvariable** `CAMPER_DEMO=1` – nur in Debug-Builds, in Xcode unter
|
||||||
|
*Product → Scheme → Edit Scheme → Run → Arguments*, oder im Simulator
|
||||||
|
direkt:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
xcrun simctl launch --terminate-running-process booted <deine-bundle-id>
|
xcrun simctl launch --terminate-running-process booted <deine-bundle-id>
|
||||||
|
|||||||
@@ -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 {
|
var rearImageName: String {
|
||||||
switch self {
|
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 {
|
var sideAspect: Double {
|
||||||
switch self {
|
switch self {
|
||||||
case .vanster: return VehicleTilt.sideAspect
|
case .vanster: return VehicleTilt.sideAspect
|
||||||
case .california: return 1
|
case .california: return 1212.0 / 790.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,10 @@ struct WatchDevice: Codable, Equatable, Identifiable, Sendable {
|
|||||||
/// den Neigungsmesser selbst an und braucht die Zuordnung deshalb auch –
|
/// den Neigungsmesser selbst an und braucht die Zuordnung deshalb auch –
|
||||||
/// eingestellt wird sie weiterhin nur am iPhone.
|
/// eingestellt wird sie weiterhin nur am iPhone.
|
||||||
var orientation: SensorOrientation?
|
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.
|
/// Nur bei der Kühlbox belegt.
|
||||||
var fridge: WatchFridge?
|
var fridge: WatchFridge?
|
||||||
|
|
||||||
|
|||||||
@@ -1190,13 +1190,14 @@ let watchPayload = WatchPayload(
|
|||||||
devices: [
|
devices: [
|
||||||
WatchDevice(id: watchLevelID, name: "Nivellierung", role: .leveling,
|
WatchDevice(id: watchLevelID, name: "Nivellierung", role: .leveling,
|
||||||
link: .live, snapshot: watchLevelSnapshot,
|
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,
|
WatchDevice(id: watchBoosterID, name: "Ladebooster", role: .chargeBooster,
|
||||||
link: .failed("Nicht gefunden"), snapshot: boosterSnapshot,
|
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,
|
WatchDevice(id: watchFridgeID, name: "Kühlbox", role: .fridge,
|
||||||
link: .live, snapshot: nil, level: nil,
|
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.
|
// Der ganze Weg: verpacken, als WatchConnectivity-Wörterbuch lesen, vergleichen.
|
||||||
|
|||||||
@@ -38,5 +38,22 @@ sips --resampleHeight 600 heckansicht.png --out /tmp/rear.png
|
|||||||
Jedes Fahrzeug-Grafikset (siehe `VehicleGraphicStyle`) hat sein eigenes
|
Jedes Fahrzeug-Grafikset (siehe `VehicleGraphicStyle`) hat sein eigenes
|
||||||
Bildpaar `<Name>Side`/`<Name>Rear`. Hat die Vorlage schon einen
|
Bildpaar `<Name>Side`/`<Name>Rear`. Hat die Vorlage schon einen
|
||||||
transparenten Hintergrund, geht die Quelldatei direkt ins Skript. Liegt sie
|
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
|
auf weißem Grund, muss der Rand vorher weg – siehe `remove-white-background.swift`.
|
||||||
mitgefärbt.
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|||||||
@@ -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")
|
||||||
@@ -496,16 +496,20 @@
|
|||||||
AA0000000000000000000014 /* Debug */ = {
|
AA0000000000000000000014 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
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_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Config/VanControl-Info.plist";
|
INFOPLIST_FILE = "Config/VanControl-Info.plist";
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "VanControl Pro";
|
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_NSBluetoothAlwaysUsageDescription = "Zum Auslesen von Victron-Geraeten und dem Daly BMS per Bluetooth.";
|
||||||
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
|
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
@@ -516,7 +520,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl;
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -528,16 +532,20 @@
|
|||||||
AA0000000000000000000015 /* Release */ = {
|
AA0000000000000000000015 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
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_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Config/VanControl-Info.plist";
|
INFOPLIST_FILE = "Config/VanControl-Info.plist";
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "VanControl Pro";
|
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_NSBluetoothAlwaysUsageDescription = "Zum Auslesen von Victron-Geraeten und dem Daly BMS per Bluetooth.";
|
||||||
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
|
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
@@ -548,7 +556,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl;
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -563,7 +571,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 3;
|
||||||
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -574,7 +582,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp;
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = watchos;
|
SDKROOT = watchos;
|
||||||
@@ -595,7 +603,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 3;
|
||||||
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -606,7 +614,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp;
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = watchos;
|
SDKROOT = watchos;
|
||||||
@@ -625,7 +633,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Config/VanControlComplication-Info.plist";
|
INFOPLIST_FILE = "Config/VanControlComplication-Info.plist";
|
||||||
@@ -635,7 +643,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp.levelwidget;
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp.levelwidget;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = watchos;
|
SDKROOT = watchos;
|
||||||
@@ -654,7 +662,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Config/VanControlComplication-Info.plist";
|
INFOPLIST_FILE = "Config/VanControlComplication-Info.plist";
|
||||||
@@ -664,7 +672,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp.levelwidget;
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.watchkitapp.levelwidget;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = watchos;
|
SDKROOT = watchos;
|
||||||
@@ -685,7 +693,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = VanControlLiveActivity/Info.plist;
|
INFOPLIST_FILE = VanControlLiveActivity/Info.plist;
|
||||||
@@ -696,7 +704,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.VanControlLiveActivity;
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.VanControlLiveActivity;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@@ -714,7 +722,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
DEVELOPMENT_TEAM = V5C6Q86XJR;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = VanControlLiveActivity/Info.plist;
|
INFOPLIST_FILE = VanControlLiveActivity/Info.plist;
|
||||||
@@ -725,7 +733,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.VanControlLiveActivity;
|
PRODUCT_BUNDLE_IDENTIFIER = de.s0.fototeddy.VanControl.VanControlLiveActivity;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
|||||||
|
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 }
|
||||||
|
}
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
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" }
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 341 KiB After Width: | Height: | Size: 311 KiB |
@@ -3,21 +3,19 @@ import Foundation
|
|||||||
/// Füllt die App mit erfundenen Messwerten, damit sich die Ansichten ohne
|
/// Füllt die App mit erfundenen Messwerten, damit sich die Ansichten ohne
|
||||||
/// Fahrzeug und ohne Bluetooth prüfen lassen.
|
/// 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 \
|
/// xcrun simctl launch --terminate-running-process \
|
||||||
/// booted de.fritob.VanControl
|
/// booted de.fritob.VanControl
|
||||||
/// # mit: SIMCTL_CHILD_CAMPER_DEMO=1 davor
|
/// # 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 {
|
enum DemoData {
|
||||||
|
|
||||||
/// Schlüssel für den Einstellungen-Schalter, siehe `SettingsView`.
|
/// Schlüssel für den Einstellungen-Schalter, siehe `SettingsView`.
|
||||||
@@ -26,10 +24,8 @@ enum DemoData {
|
|||||||
static var isEnabled: Bool {
|
static var isEnabled: Bool {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if ProcessInfo.processInfo.environment["CAMPER_DEMO"] == "1" { return true }
|
if ProcessInfo.processInfo.environment["CAMPER_DEMO"] == "1" { return true }
|
||||||
return UserDefaults.standard.bool(forKey: enabledKey)
|
|
||||||
#else
|
|
||||||
return false
|
|
||||||
#endif
|
#endif
|
||||||
|
return UserDefaults.standard.bool(forKey: enabledKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
static let mainProfile = Profile(id: Profile.defaultID, name: "Kastenwagen",
|
static let mainProfile = Profile(id: Profile.defaultID, name: "Kastenwagen",
|
||||||
|
|||||||
@@ -4,16 +4,13 @@ import SwiftUI
|
|||||||
/// betrifft, steht bei diesem Gerät.
|
/// betrifft, steht bei diesem Gerät.
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
@AppStorage(AppSettings.showDiagnosticsKey) private var showDiagnostics = false
|
@AppStorage(AppSettings.showDiagnosticsKey) private var showDiagnostics = false
|
||||||
#if DEBUG
|
|
||||||
@AppStorage(DemoData.enabledKey) private var demoModeEnabled = false
|
@AppStorage(DemoData.enabledKey) private var demoModeEnabled = false
|
||||||
#endif
|
|
||||||
@Environment(PhoneWatchLink.self) private var watch
|
@Environment(PhoneWatchLink.self) private var watch
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
List {
|
List {
|
||||||
#if DEBUG
|
|
||||||
Section {
|
Section {
|
||||||
Toggle("Demo-Modus", isOn: $demoModeEnabled)
|
Toggle("Demo-Modus", isOn: $demoModeEnabled)
|
||||||
} header: {
|
} header: {
|
||||||
@@ -24,7 +21,6 @@ struct SettingsView: View {
|
|||||||
+ "einem Neustart der App – einmal beenden (im App-Umschalter "
|
+ "einem Neustart der App – einmal beenden (im App-Umschalter "
|
||||||
+ "nach oben wischen) und wieder öffnen.")
|
+ "nach oben wischen) und wieder öffnen.")
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Toggle("Diagnose anzeigen", isOn: $showDiagnostics)
|
Toggle("Diagnose anzeigen", isOn: $showDiagnostics)
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ final class PhoneWatchLink: NSObject {
|
|||||||
snapshot: bluetooth.snapshots[device.id],
|
snapshot: bluetooth.snapshots[device.id],
|
||||||
level: device.role == .leveling ? bluetooth.levelStates[device.id] : nil,
|
level: device.role == .leveling ? bluetooth.levelStates[device.id] : nil,
|
||||||
orientation: device.role == .leveling ? device.sensorOrientation : nil,
|
orientation: device.role == .leveling ? device.sensorOrientation : nil,
|
||||||
|
vehicleGraphicStyle: device.role == .leveling ? device.vehicleGraphicStyle : nil,
|
||||||
fridge: device.role == .fridge ? fridgeState(for: device) : nil)
|
fridge: device.role == .fridge ? fridgeState(for: device) : nil)
|
||||||
}
|
}
|
||||||
return WatchPayload(generatedAt: Date(),
|
return WatchPayload(generatedAt: Date(),
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
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" }
|
||||||
|
}
|
||||||
|
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" }
|
||||||
|
}
|
||||||
@@ -50,7 +50,8 @@ struct LevelReadingPage: View {
|
|||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
if showsVehicle {
|
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 {
|
} else {
|
||||||
WatchBubble(pitch: reading.state.pitch, roll: reading.state.roll)
|
WatchBubble(pitch: reading.state.pitch, roll: reading.state.roll)
|
||||||
.frame(maxWidth: 110)
|
.frame(maxWidth: 110)
|
||||||
|
|||||||
@@ -9,15 +9,18 @@ import SwiftUI
|
|||||||
struct WatchVehicleView: View {
|
struct WatchVehicleView: View {
|
||||||
let pitch: Double?
|
let pitch: Double?
|
||||||
let roll: 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.
|
/// Wie am iPhone dreifach überhöht – zwei Grad wären sonst kaum zu sehen.
|
||||||
/// Die angeschriebenen Gradzahlen bleiben echt.
|
/// Die angeschriebenen Gradzahlen bleiben echt.
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
panel(image: "VansterSide", angle: pitch, title: "Längs",
|
panel(image: style.sideImageName, angle: pitch, title: "Längs",
|
||||||
lower: "Front", upper: "Heck", aspect: VehicleTilt.sideAspect)
|
lower: "Front", upper: "Heck", aspect: style.sideAspect)
|
||||||
panel(image: "VansterRear", angle: roll, title: "Quer",
|
panel(image: style.rearImageName, angle: roll, title: "Quer",
|
||||||
lower: "links", upper: "rechts", aspect: VehicleTilt.rearAspect)
|
lower: "links", upper: "rechts", aspect: style.rearAspect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,15 +63,17 @@ enum WatchDemo {
|
|||||||
devices: [
|
devices: [
|
||||||
WatchDevice(id: levelID, name: "Nivellierung", role: .leveling,
|
WatchDevice(id: levelID, name: "Nivellierung", role: .leveling,
|
||||||
link: .live, snapshot: level.snapshot(deviceID: levelID, rssi: -70),
|
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,
|
WatchDevice(id: batteryID, name: "Bulltron 200 Ah", role: .bms,
|
||||||
link: .live, snapshot: battery, level: nil,
|
link: .live, snapshot: battery, level: nil,
|
||||||
orientation: nil, fridge: nil),
|
orientation: nil, vehicleGraphicStyle: nil, fridge: nil),
|
||||||
WatchDevice(id: victronSolarID, name: "Solar Dach", role: .victronSolarCharger,
|
WatchDevice(id: victronSolarID, name: "Solar Dach", role: .victronSolarCharger,
|
||||||
link: .live, snapshot: victronSolar, level: nil,
|
link: .live, snapshot: victronSolar, level: nil,
|
||||||
orientation: nil, fridge: nil),
|
orientation: nil, vehicleGraphicStyle: nil, fridge: nil),
|
||||||
WatchDevice(id: fridgeID, name: "Kühlbox", role: .fridge,
|
WatchDevice(id: fridgeID, name: "Kühlbox", role: .fridge,
|
||||||
link: .live, snapshot: fridge, level: nil, orientation: nil,
|
link: .live, snapshot: fridge, level: nil, orientation: nil,
|
||||||
|
vehicleGraphicStyle: nil,
|
||||||
fridge: WatchFridge(isLive: true, updated: now,
|
fridge: WatchFridge(isLive: true, updated: now,
|
||||||
isPoweredOn: true, isEco: true, isLocked: false,
|
isPoweredOn: true, isEco: true, isLocked: false,
|
||||||
isDualZone: false, unitSymbol: "°C",
|
isDualZone: false, unitSymbol: "°C",
|
||||||
|
|||||||