diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml
index 371d033..3e2775d 100644
--- a/.gitea/workflows/release.yml
+++ b/.gitea/workflows/release.yml
@@ -59,7 +59,7 @@ jobs:
-PversionName="$VERSION" \
-PversionCode="$CODE" --no-daemon
mv app/build/outputs/apk/release/app-release.apk \
- "../CamperMonitor-$VERSION.apk"
+ "../VanControlPro-$VERSION.apk"
- name: An das Release hängen
env:
@@ -88,7 +88,7 @@ jobs:
fi
curl -sf -X POST -H "$AUTH" \
- -F "attachment=@CamperMonitor-$VERSION.apk" \
- "$API/$ID/assets?name=CamperMonitor-$VERSION.apk" > /dev/null
+ -F "attachment=@VanControlPro-$VERSION.apk" \
+ "$API/$ID/assets?name=VanControlPro-$VERSION.apk" > /dev/null
- echo "CamperMonitor-$VERSION.apk hängt an Release $GITHUB_REF_NAME."
+ echo "VanControlPro-$VERSION.apk hängt an Release $GITHUB_REF_NAME."
diff --git a/Android/app/src/main/kotlin/de/fritob/campermonitor/ui/Permissions.kt b/Android/app/src/main/kotlin/de/fritob/campermonitor/ui/Permissions.kt
index 51776fd..769a143 100644
--- a/Android/app/src/main/kotlin/de/fritob/campermonitor/ui/Permissions.kt
+++ b/Android/app/src/main/kotlin/de/fritob/campermonitor/ui/Permissions.kt
@@ -89,7 +89,7 @@ fun WithBluetoothPermission(content: @Composable () -> Unit) {
if (asked) {
Text(
"Die Freigabe wurde abgelehnt. Sie lässt sich in den " +
- "Android-Einstellungen unter „Apps → Camper Monitor → " +
+ "Android-Einstellungen unter „Apps → VanControl Pro → " +
"Berechtigungen“ nachholen.",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error,
diff --git a/Android/app/src/main/kotlin/de/fritob/campermonitor/ui/SettingsScreen.kt b/Android/app/src/main/kotlin/de/fritob/campermonitor/ui/SettingsScreen.kt
index 36c2740..4fe0d05 100644
--- a/Android/app/src/main/kotlin/de/fritob/campermonitor/ui/SettingsScreen.kt
+++ b/Android/app/src/main/kotlin/de/fritob/campermonitor/ui/SettingsScreen.kt
@@ -65,7 +65,7 @@ fun SettingsScreen(store: DeviceStore, onBack: () -> Unit) {
)
Text(
- "Camper Monitor liest Victron-Geräte über ihr Advertisement mit " +
+ "VanControl Pro liest Victron-Geräte über ihr Advertisement mit " +
"(„Instant Readout“) und spricht Batterie, Kühlbox und " +
"Neigungsmesser direkt über Bluetooth an.",
style = MaterialTheme.typography.bodySmall,
diff --git a/Android/app/src/main/res/values/strings.xml b/Android/app/src/main/res/values/strings.xml
index 38352cf..0ade3f2 100644
--- a/Android/app/src/main/res/values/strings.xml
+++ b/Android/app/src/main/res/values/strings.xml
@@ -1,4 +1,4 @@
- Camper Monitor
+ VanControl Pro
diff --git a/CamperMonitor.xcodeproj/project.pbxproj b/CamperMonitor.xcodeproj/project.pbxproj
index 004c516..cc2390e 100644
--- a/CamperMonitor.xcodeproj/project.pbxproj
+++ b/CamperMonitor.xcodeproj/project.pbxproj
@@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
AA0000000000000000000027 /* CamperMonitorWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = AA0000000000000000000016 /* CamperMonitorWatch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ AA0000000000000000000042 /* CamperMonitorComplication.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = AA0000000000000000000032 /* CamperMonitorComplication.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -18,6 +19,13 @@
remoteGlobalIDString = AA0000000000000000000022;
remoteInfo = CamperMonitorWatch;
};
+ AA0000000000000000000043 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = AA0000000000000000000009 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = AA0000000000000000000037;
+ remoteInfo = CamperMonitorComplication;
+ };
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -32,12 +40,25 @@
name = "Embed Watch Content";
runOnlyForDeploymentPostprocessing = 0;
};
+ AA0000000000000000000041 /* Embed Foundation Extensions */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 13;
+ files = (
+ AA0000000000000000000042 /* CamperMonitorComplication.appex in Embed Foundation Extensions */,
+ );
+ name = "Embed Foundation Extensions";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
AA0000000000000000000001 /* CamperMonitor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CamperMonitor.app; sourceTree = BUILT_PRODUCTS_DIR; };
AA0000000000000000000016 /* CamperMonitorWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CamperMonitorWatch.app; sourceTree = BUILT_PRODUCTS_DIR; };
AA0000000000000000000030 /* CamperMonitor-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "CamperMonitor-Info.plist"; sourceTree = ""; };
+ AA0000000000000000000045 /* CamperMonitorComplication-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "CamperMonitorComplication-Info.plist"; sourceTree = ""; };
+ AA0000000000000000000032 /* CamperMonitorComplication.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = CamperMonitorComplication.appex; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
@@ -51,6 +72,11 @@
path = CamperMonitorWatch;
sourceTree = "";
};
+ AA0000000000000000000033 /* CamperMonitorComplication */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ path = CamperMonitorComplication;
+ sourceTree = "";
+ };
AA0000000000000000000018 /* Shared */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = Shared;
@@ -73,6 +99,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ AA0000000000000000000035 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -81,6 +114,7 @@
children = (
AA0000000000000000000002 /* CamperMonitor */,
AA0000000000000000000017 /* CamperMonitorWatch */,
+ AA0000000000000000000033 /* CamperMonitorComplication */,
AA0000000000000000000018 /* Shared */,
AA0000000000000000000031 /* Config */,
AA0000000000000000000005 /* Products */,
@@ -92,6 +126,7 @@
children = (
AA0000000000000000000001 /* CamperMonitor.app */,
AA0000000000000000000016 /* CamperMonitorWatch.app */,
+ AA0000000000000000000032 /* CamperMonitorComplication.appex */,
);
name = Products;
sourceTree = "";
@@ -100,6 +135,7 @@
isa = PBXGroup;
children = (
AA0000000000000000000030 /* CamperMonitor-Info.plist */,
+ AA0000000000000000000045 /* CamperMonitorComplication-Info.plist */,
);
path = Config;
sourceTree = "";
@@ -137,10 +173,12 @@
AA0000000000000000000019 /* Sources */,
AA0000000000000000000020 /* Frameworks */,
AA0000000000000000000021 /* Resources */,
+ AA0000000000000000000041 /* Embed Foundation Extensions */,
);
buildRules = (
);
dependencies = (
+ AA0000000000000000000044 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
AA0000000000000000000017 /* CamperMonitorWatch */,
@@ -151,6 +189,27 @@
productReference = AA0000000000000000000016 /* CamperMonitorWatch.app */;
productType = "com.apple.product-type.application";
};
+ AA0000000000000000000037 /* CamperMonitorComplication */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = AA0000000000000000000040 /* Build configuration list for PBXNativeTarget "CamperMonitorComplication" */;
+ buildPhases = (
+ AA0000000000000000000034 /* Sources */,
+ AA0000000000000000000035 /* Frameworks */,
+ AA0000000000000000000036 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ AA0000000000000000000033 /* CamperMonitorComplication */,
+ AA0000000000000000000018 /* Shared */,
+ );
+ name = CamperMonitorComplication;
+ productName = CamperMonitorComplication;
+ productReference = AA0000000000000000000032 /* CamperMonitorComplication.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -167,6 +226,9 @@
AA0000000000000000000022 = {
CreatedOnToolsVersion = 26.6;
};
+ AA0000000000000000000037 = {
+ CreatedOnToolsVersion = 26.6;
+ };
};
};
buildConfigurationList = AA0000000000000000000010 /* Build configuration list for PBXProject "CamperMonitor" */;
@@ -186,6 +248,7 @@
targets = (
AA0000000000000000000006 /* CamperMonitor */,
AA0000000000000000000022 /* CamperMonitorWatch */,
+ AA0000000000000000000037 /* CamperMonitorComplication */,
);
};
/* End PBXProject section */
@@ -205,6 +268,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ AA0000000000000000000036 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -222,6 +292,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ AA0000000000000000000034 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -231,6 +308,11 @@
target = AA0000000000000000000022 /* CamperMonitorWatch */;
targetProxy = AA0000000000000000000028 /* PBXContainerItemProxy */;
};
+ AA0000000000000000000044 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = AA0000000000000000000037 /* CamperMonitorComplication */;
+ targetProxy = AA0000000000000000000043 /* PBXContainerItemProxy */;
+ };
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
@@ -301,7 +383,7 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Config/CamperMonitor-Info.plist";
- INFOPLIST_KEY_CFBundleDisplayName = "Camper Monitor";
+ INFOPLIST_KEY_CFBundleDisplayName = "VanControl Pro";
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Zum Auslesen von Victron-Geraeten und dem Daly BMS per Bluetooth.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -332,7 +414,7 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Config/CamperMonitor-Info.plist";
- INFOPLIST_KEY_CFBundleDisplayName = "Camper Monitor";
+ INFOPLIST_KEY_CFBundleDisplayName = "VanControl Pro";
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Zum Auslesen von Victron-Geraeten und dem Daly BMS per Bluetooth.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -361,7 +443,7 @@
DEVELOPMENT_TEAM = 6AP73NYV5W;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
- INFOPLIST_KEY_CFBundleDisplayName = "Camper Monitor";
+ INFOPLIST_KEY_CFBundleDisplayName = "VanControl Pro";
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Zum Auslesen des Neigungsmessers direkt an der Uhr, ohne Umweg ueber das iPhone.";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = de.fritob.CamperMonitor;
LD_RUNPATH_SEARCH_PATHS = (
@@ -393,7 +475,7 @@
DEVELOPMENT_TEAM = 6AP73NYV5W;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
- INFOPLIST_KEY_CFBundleDisplayName = "Camper Monitor";
+ INFOPLIST_KEY_CFBundleDisplayName = "VanControl Pro";
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Zum Auslesen des Neigungsmessers direkt an der Uhr, ohne Umweg ueber das iPhone.";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = de.fritob.CamperMonitor;
LD_RUNPATH_SEARCH_PATHS = (
@@ -415,6 +497,64 @@
};
name = Release;
};
+ AA0000000000000000000038 = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = 6AP73NYV5W;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = "Config/CamperMonitorComplication-Info.plist";
+ INFOPLIST_KEY_CFBundleDisplayName = "Nivellierung";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = de.fritob.CamperMonitor.watchkitapp.complication;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = watchos;
+ SKIP_INSTALL = YES;
+ SUPPORTED_PLATFORMS = "watchos watchsimulator";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = 4;
+ WATCHOS_DEPLOYMENT_TARGET = 10.0;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ AA0000000000000000000039 = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = 6AP73NYV5W;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = "Config/CamperMonitorComplication-Info.plist";
+ INFOPLIST_KEY_CFBundleDisplayName = "Nivellierung";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = de.fritob.CamperMonitor.watchkitapp.complication;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = watchos;
+ SKIP_INSTALL = YES;
+ SUPPORTED_PLATFORMS = "watchos watchsimulator";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = 4;
+ WATCHOS_DEPLOYMENT_TARGET = 10.0;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -445,6 +585,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ AA0000000000000000000040 /* Build configuration list for PBXNativeTarget "CamperMonitorComplication" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ AA0000000000000000000038 /* Debug */,
+ AA0000000000000000000039 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
/* End XCConfigurationList section */
};
rootObject = AA0000000000000000000009 /* Project object */;
diff --git a/CamperMonitorComplication/LevelComplication.swift b/CamperMonitorComplication/LevelComplication.swift
new file mode 100644
index 0000000..bf1bebd
--- /dev/null
+++ b/CamperMonitorComplication/LevelComplication.swift
@@ -0,0 +1,92 @@
+import SwiftUI
+import WidgetKit
+
+/// Komplikation fürs Zifferblatt: ein Tipp, und die Nivellierung steht da.
+///
+/// Bewusst **ohne Messwert**. Eine Komplikation läuft in einem eigenen Prozess
+/// und käme an die Werte nur über eine App-Gruppe – die verlangt eine bezahlte
+/// Entwicklermitgliedschaft. Ein Wert, der beim Blick aufs Zifferblatt
+/// womöglich Stunden alt ist, wäre ausserdem schlimmer als keiner: Man würde
+/// ihm glauben.
+///
+/// Die Komplikation tut deshalb genau eine Sache, und die zuverlässig: Sie
+/// öffnet die App auf der Nivellierung. Von dort sind die Werte live, weil die
+/// Uhr den Sensor selbst anfunkt.
+struct LevelComplicationEntry: TimelineEntry {
+ let date: Date
+}
+
+/// Nichts zu planen: Die Anzeige ändert sich nie, also ein einziger Eintrag
+/// ohne Nachschub. Das kostet die Uhr auch nichts.
+struct LevelComplicationProvider: TimelineProvider {
+ func placeholder(in context: Context) -> LevelComplicationEntry {
+ LevelComplicationEntry(date: .now)
+ }
+
+ func getSnapshot(in context: Context,
+ completion: @escaping (LevelComplicationEntry) -> Void) {
+ completion(LevelComplicationEntry(date: .now))
+ }
+
+ func getTimeline(in context: Context,
+ completion: @escaping (Timeline) -> Void) {
+ completion(Timeline(entries: [LevelComplicationEntry(date: .now)], policy: .never))
+ }
+}
+
+struct LevelComplicationView: View {
+ @Environment(\.widgetFamily) private var family
+
+ var body: some View {
+ switch family {
+ case .accessoryInline:
+ // Eine Zeile Text, mehr lässt diese Familie nicht zu.
+ Label("Nivellierung", systemImage: "level")
+
+ case .accessoryCorner:
+ Image(systemName: "level")
+ .font(.title2)
+ .widgetLabel("Nivellieren")
+
+ case .accessoryRectangular:
+ HStack(spacing: 6) {
+ Image(systemName: "level")
+ .font(.title3)
+ VStack(alignment: .leading, spacing: 0) {
+ Text("Nivellierung")
+ .font(.headline)
+ Text("Antippen zum Ausrichten")
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ }
+ }
+
+ default:
+ // Rund – auf dem Zifferblatt der knappste Platz, also nur das
+ // Symbol, gross genug zum Treffen.
+ ZStack {
+ AccessoryWidgetBackground()
+ Image(systemName: "level")
+ .font(.title2)
+ }
+ }
+ }
+}
+
+@main
+struct LevelComplication: Widget {
+ var body: some WidgetConfiguration {
+ StaticConfiguration(kind: "de.fritob.CamperMonitor.complication.level",
+ provider: LevelComplicationProvider()) { _ in
+ LevelComplicationView()
+ // Der Tipp landet nicht irgendwo in der App, sondern auf der
+ // Nivellierung – dafür wertet die App diese Adresse aus.
+ .widgetURL(WatchDeepLink.level)
+ .containerBackground(.clear, for: .widget)
+ }
+ .configurationDisplayName("Nivellierung")
+ .description("Öffnet die Nivellierung direkt vom Zifferblatt.")
+ .supportedFamilies([.accessoryCircular, .accessoryCorner,
+ .accessoryInline, .accessoryRectangular])
+ }
+}
diff --git a/CamperMonitorWatch/CamperWatchApp.swift b/CamperMonitorWatch/CamperWatchApp.swift
index 678e366..fa69cec 100644
--- a/CamperMonitorWatch/CamperWatchApp.swift
+++ b/CamperMonitorWatch/CamperWatchApp.swift
@@ -1,6 +1,6 @@
import SwiftUI
-/// Camper Monitor am Handgelenk.
+/// VanControl Pro am Handgelenk.
///
/// Zwei Quellen, jede dort, wo sie hingehört:
///
diff --git a/CamperMonitorWatch/Views/WatchLevelView.swift b/CamperMonitorWatch/Views/WatchLevelView.swift
index 09f2c76..0061ed3 100644
--- a/CamperMonitorWatch/Views/WatchLevelView.swift
+++ b/CamperMonitorWatch/Views/WatchLevelView.swift
@@ -41,9 +41,7 @@ struct WatchLevelView: View {
.padding(.top, 2)
}
- NavigationLink {
- WatchAlignView()
- } label: {
+ NavigationLink(value: WatchRoute.align) {
Label("Ausrichten", systemImage: "arrow.left.and.right")
}
.buttonStyle(.borderedProminent)
diff --git a/CamperMonitorWatch/Views/WatchRootView.swift b/CamperMonitorWatch/Views/WatchRootView.swift
index 1bf0df3..18b9f6b 100644
--- a/CamperMonitorWatch/Views/WatchRootView.swift
+++ b/CamperMonitorWatch/Views/WatchRootView.swift
@@ -6,10 +6,28 @@ struct WatchRootView: View {
@Environment(PhoneLink.self) private var link
@Environment(WatchLevelRadio.self) private var radio
+ /// Der Weg durch die Ansichten liegt hier, damit ihn auch die Komplikation
+ /// setzen kann: Ein Tipp aufs Zifferblatt springt direkt zur Nivellierung,
+ /// statt in der Geräteliste zu landen.
+ @State private var path: [WatchRoute] = []
+
var body: some View {
- NavigationStack {
+ NavigationStack(path: $path) {
content
.navigationTitle(link.payload?.profile.name ?? "Camper")
+ .navigationDestination(for: WatchRoute.self) { route in
+ switch route {
+ case .level: WatchLevelView()
+ case .align: WatchAlignView()
+ case .device(let id): WatchDeviceDetailView(deviceID: id)
+ }
+ }
+ }
+ .onOpenURL { url in
+ guard WatchDeepLink.isLevel(url) else { return }
+ // Nicht anhängen, sondern ersetzen: Wer vom Zifferblatt kommt, will
+ // die Nivellierung sehen und nicht einen Stapel von vorhin.
+ path = [.level]
}
}
@@ -18,9 +36,7 @@ struct WatchRootView: View {
// Die Nivellierung steht immer oben und immer da: Sie kommt vom
// Sensor selbst und braucht das iPhone nicht.
Section {
- NavigationLink {
- WatchLevelView()
- } label: {
+ NavigationLink(value: WatchRoute.level) {
LevelSummaryRow(reading: WatchLevelReading(radio: radio, phone: link))
}
}
@@ -28,9 +44,7 @@ struct WatchRootView: View {
if let payload = link.payload {
Section {
ForEach(payload.devices.filter { $0.role != .leveling }) { device in
- NavigationLink {
- WatchDeviceDetailView(deviceID: device.id)
- } label: {
+ NavigationLink(value: WatchRoute.device(device.id)) {
DeviceRow(device: device)
}
}
@@ -55,7 +69,7 @@ struct WatchRootView: View {
VStack(spacing: 4) {
Label("Warte auf das iPhone", systemImage: "iphone.gen3.radiowaves.left.and.right")
.font(.footnote.weight(.medium))
- Text("Dort muss Camper Monitor geöffnet sein.")
+ Text("Dort muss VanControl Pro geöffnet sein.")
.font(.caption2)
.foregroundStyle(.secondary)
}
@@ -63,6 +77,14 @@ struct WatchRootView: View {
}
}
+/// Wohin die Uhr springen kann. Als eigener Typ, weil der Weg von aussen
+/// gesetzt wird – von der Komplikation.
+enum WatchRoute: Hashable {
+ case level
+ case align
+ case device(UUID)
+}
+
/// Eine Gerätezeile: Name klein, Hauptwert gross – auf dem kleinen Bildschirm
/// zählt nur der eine Wert, wegen dem man den Arm hebt.
struct DeviceRow: View {
diff --git a/Config/CamperMonitorComplication-Info.plist b/Config/CamperMonitorComplication-Info.plist
new file mode 100644
index 0000000..0f118fb
--- /dev/null
+++ b/Config/CamperMonitorComplication-Info.plist
@@ -0,0 +1,11 @@
+
+
+
+
+ NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.widgetkit-extension
+
+
+
diff --git a/README.md b/README.md
index 08703b1..c4b56d3 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Camper Monitor
+# VanControl Pro
iOS-App, die per Bluetooth LE die Energieanlage im Wohnmobil ausliest:
@@ -120,7 +120,7 @@ Protokoll erkannt wurde, steht in der Detailansicht unter **Diagnose** –
zusammen mit der letzten Rohantwort.
Nur **eine** App gleichzeitig kann mit dem BMS verbunden sein. Wenn die
-Hersteller-App offen ist, bekommt Camper Monitor keine Verbindung.
+Hersteller-App offen ist, bekommt VanControl Pro keine Verbindung.
### Kühlbox
@@ -305,7 +305,7 @@ auch dort nur, was das Gerät zurückmeldet, nicht der Tastendruck.
### Was das für den Betrieb heisst
-Für Batterie, Solar und Kühlbox muss Camper Monitor auf dem iPhone laufen.
+Für Batterie, Solar und Kühlbox muss VanControl Pro auf dem iPhone laufen.
Solange die Uhr meldet, dass jemand hinschaut, hält die App das Funkgerät auch
im Hintergrund am Leben; die Victron-Werbedaten stehen dabei still, weil iOS im
Hintergrund kein ungefiltertes Suchen erlaubt.
diff --git a/Shared/WatchDeepLink.swift b/Shared/WatchDeepLink.swift
new file mode 100644
index 0000000..0a4ceea
--- /dev/null
+++ b/Shared/WatchDeepLink.swift
@@ -0,0 +1,19 @@
+import Foundation
+
+/// Die Adressen, über die etwas in der Watch-App geöffnet wird.
+///
+/// Sie stehen hier, weil beide Seiten sie brauchen und sie sonst zweimal
+/// dastünden: Die Komplikation hängt sie an ihre Anzeige, die App wertet sie
+/// beim Öffnen aus. Ein Tippfehler auf einer Seite wäre stumm – der Tipp aufs
+/// Zifferblatt öffnete dann einfach die Geräteliste.
+enum WatchDeepLink {
+ private static let scheme = "campermonitor"
+
+ /// Führt direkt auf die Nivellierung.
+ static let level = URL(string: "\(scheme)://nivellierung")!
+
+ /// Ob diese Adresse zur Nivellierung führt.
+ static func isLevel(_ url: URL) -> Bool {
+ url.scheme == scheme && (url.host() == "nivellierung")
+ }
+}
diff --git a/firmware/vanalign/README.md b/firmware/vanalign/README.md
index fcbba15..0ac375b 100644
--- a/firmware/vanalign/README.md
+++ b/firmware/vanalign/README.md
@@ -26,7 +26,7 @@ darüber auffindbar).
| Roll | `…3425` | lesen | Querneigung, Float32 little-endian, Grad |
| Kalibrieren | `…3427` | schreiben | ein Byte: `0` setzt zurück, alles andere kalibriert |
-Der Client fragt Pitch und Roll im Takt ab; Camper Monitor tut das zweimal je
+Der Client fragt Pitch und Roll im Takt ab; VanControl Pro tut das zweimal je
Sekunde.
Positiver Pitch heißt: das Heck steht höher. Positiver Roll: die rechte Seite
@@ -35,7 +35,7 @@ steht höher.
### Kalibrierung
Fahrzeug eben stellen, dann ein beliebiges Byte ungleich `0` auf `…3427`
-schreiben – oder in Camper Monitor auf *Auf aktuelle Lage kalibrieren* tippen.
+schreiben – oder in VanControl Pro auf *Auf aktuelle Lage kalibrieren* tippen.
Die Offsets werden dauerhaft gespeichert und überstehen einen Neustart.
## Was sich gegenüber 1.0 geändert hat
@@ -70,7 +70,7 @@ Die UUIDs sind unverändert, die vorhandene WebApp läuft weiter.
## Clients
-* **Camper Monitor** (iOS) – die App in diesem Projekt. Geräteart
+* **VanControl Pro** (iOS) – die App in diesem Projekt. Geräteart
*Nivellierung*: grafische Libelle, Kalibrierung aus der App, zusammen mit
den übrigen Geräten im Fahrzeug.
* Die ursprüngliche WebBLE-Oberfläche liegt weiterhin im Projekt