Zwei Dinge in einem Commit, weil sie sich dieselben Dateien teilen: project.pbxproj und WatchRootView.swift tragen beides. **Komplikation.** Neues Target CamperMonitorComplication, auf watchOS sind Komplikationen WidgetKit-Widgets. Es wird in die Watch-App eingebettet (PlugIns) und bietet die vier Zubehör-Familien an. Ein Tipp öffnet die App direkt auf der Nivellierung statt in der Geräteliste: Die Komplikation hängt eine Adresse an, die Watch-App führt dafür jetzt einen Navigationsweg, den sie von aussen setzen kann. Die Adresse steht in Shared/WatchDeepLink.swift, weil beide Seiten sie brauchen – ein Tippfehler auf einer Seite bliebe sonst stumm. Bewusst ohne Messwert: Eine Komplikation läuft in einem eigenen Prozess und käme an die Werte nur über eine App-Gruppe, und die verlangt eine bezahlte Mitgliedschaft. Ein Wert, der beim Blick aufs Zifferblatt Stunden alt sein kann, wäre ohnehin schlimmer als keiner – man glaubt ihm. Die Komplikation tut deshalb genau eine Sache, und die zuverlässig. **Rebranding.** Der sichtbare Name ist überall VanControl Pro: CFBundleDisplayName beider Apple-Targets, Android-Label, die Texte in beiden Apps, beide READMEs und der Name der APK im Gitea-Ablauf. Die Kennungen bleiben, wie sie sind – Bundle-IDs, Android-Paket, Target-, Ordner- und Schemanamen. Eine geänderte Bundle-ID ist für iOS eine neue App: Geräte, Fahrzeugprofile und Victron-Schlüssel wären weg, dazu neue Profile und eine neu zu koppelnde Watch-App. Sichtbar ist davon nichts, also gibt es dafür auch keinen Grund. Geprüft: alle drei Apple-Targets bauen, die Erweiterung liegt im Watch-Bundle unter PlugIns, die Anzeigenamen stimmen im gebauten Bundle, Tests grün. Die neue Bundle-ID der Erweiterung braucht beim ersten Lauf aus Xcode ein Profil, das Xcode selbst anlegt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
93 lines
3.4 KiB
Swift
93 lines
3.4 KiB
Swift
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<LevelComplicationEntry>) -> 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])
|
||
}
|
||
}
|