forked from fritob/Camper-Monitor
Namen leichter ändern, Oberfläche entlasten
Umbenennen war zwar möglich, aber praktisch unauffindbar: das Eingabe- feld trug nur einen Platzhalter, und übernommen wurde erst über einen zusätzlichen Knopf. Jetzt steht "Name" als Beschriftung davor, der Knopf ist weg, gesichert wird beim Abschluss der Eingabe und beim Verlassen der Ansicht. Der Funkname steht darunter als "Gefunden als", damit das Gerät weiter zuzuordnen ist. Beim Einrichten wird nicht mehr der Funkname vorgeschlagen - Namen wie "WTaEaAA25342229" taugen nicht als Anzeigename -, sondern die Art des Geräts, sofern der Funkname kryptisch wirkt. Die Detailansicht arbeitete auf einer Momentaufnahme des Geräts. Nach dem Umbenennen oder nach dem Umstellen der Kühlzonen zeigte sie deshalb weiter die alten Werte; sie liest den Stand jetzt aus dem Speicher. Gegen die träge Oberfläche: CoreBluetooth meldet jedes Advertisement einzeln und auf dem Hauptthread, und gescannt wird mit Duplikaten über alle Geräte in Reichweite. Victron sendet mehrmals je Sekunde, dazu kommt alles andere in Funkreichweite. Ausgewertet wird jetzt höchstens einmal je Sekunde und Gerät, die Geräteliste beim Einrichten alle zwei Sekunden, die Rohdaten der Diagnose alle drei. Ein Advertisement mehr ändert die Anzeige ohnehin nicht, kostet aber Entschlüsselung und eine Neuzeichnung. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -199,7 +199,15 @@ private struct ConfigureDeviceView: View {
|
||||
} else if discovery.looksLikeSupported {
|
||||
role = .bms
|
||||
}
|
||||
name = discovery.name?.isEmpty == false ? discovery.name! : role.title
|
||||
// Funknamen wie "WTaEaAA25342229" taugen nicht als Anzeigename. Die
|
||||
// Art des Geräts ist der bessere Vorschlag; der Funkname steht
|
||||
// ohnehin darüber unter "Gefunden als".
|
||||
if let advertised = discovery.name, advertised.count <= 20,
|
||||
advertised.contains(" ") || advertised.rangeOfCharacter(from: .decimalDigits) == nil {
|
||||
name = advertised
|
||||
} else {
|
||||
name = role.title
|
||||
}
|
||||
}
|
||||
|
||||
private func save() {
|
||||
|
||||
@@ -12,6 +12,12 @@ struct DeviceDetailView: View {
|
||||
@State private var keyInput = ""
|
||||
@State private var showDeleteConfirmation = false
|
||||
|
||||
/// Immer der aktuelle Stand aus dem Speicher – `device` ist die
|
||||
/// Momentaufnahme beim Öffnen und veraltet nach jeder Änderung.
|
||||
private var currentDevice: ConfiguredDevice {
|
||||
store.devices.first { $0.id == device.id } ?? device
|
||||
}
|
||||
|
||||
private var snapshot: DeviceSnapshot? { bluetooth.snapshots[device.id] }
|
||||
private var linkState: DeviceLinkState { bluetooth.linkStates[device.id] ?? .searching }
|
||||
private var samples: [HistorySample] { bluetooth.history[device.id] ?? [] }
|
||||
@@ -20,8 +26,8 @@ struct DeviceDetailView: View {
|
||||
List {
|
||||
statusSection
|
||||
|
||||
if device.role == .fridge, let fridge = bluetooth.fridgeStates[device.id], fridge.hasStatus {
|
||||
FridgeControls(device: device, state: fridge)
|
||||
if currentDevice.role == .fridge, let fridge = bluetooth.fridgeStates[device.id], fridge.hasStatus {
|
||||
FridgeControls(device: currentDevice, state: fridge)
|
||||
}
|
||||
|
||||
if let snapshot, !snapshot.metrics.isEmpty {
|
||||
@@ -75,7 +81,7 @@ struct DeviceDetailView: View {
|
||||
}
|
||||
}
|
||||
|
||||
if device.role.transport == .advertisement {
|
||||
if currentDevice.role.transport == .advertisement {
|
||||
keySection
|
||||
diagnosticsSection
|
||||
} else {
|
||||
@@ -84,10 +90,11 @@ struct DeviceDetailView: View {
|
||||
|
||||
settingsSection
|
||||
}
|
||||
.navigationTitle(device.name)
|
||||
.navigationTitle(currentDevice.name)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.onDisappear(perform: saveName)
|
||||
.onAppear {
|
||||
editedName = device.name
|
||||
editedName = currentDevice.name
|
||||
keyInput = store.victronKeyText(for: device.id) ?? ""
|
||||
}
|
||||
.confirmationDialog("Gerät entfernen?",
|
||||
@@ -332,17 +339,28 @@ struct DeviceDetailView: View {
|
||||
|
||||
private var settingsSection: some View {
|
||||
Section("Einstellungen") {
|
||||
TextField("Name", text: $editedName)
|
||||
.onSubmit(saveName)
|
||||
Button("Namen übernehmen", action: saveName)
|
||||
.disabled(editedName.trimmingCharacters(in: .whitespaces).isEmpty
|
||||
|| editedName == device.name)
|
||||
LabeledContent("Typ", value: device.role.title)
|
||||
if device.role == .fridge {
|
||||
HStack {
|
||||
Text("Name")
|
||||
Spacer()
|
||||
// Beim Abschluss der Eingabe und beim Verlassen der Ansicht
|
||||
// gesichert – bei jedem Tastendruck zu speichern hiesse, die
|
||||
// ganze Geräteliste je Zeichen neu zu schreiben.
|
||||
TextField("Gerätename", text: $editedName)
|
||||
.multilineTextAlignment(.trailing)
|
||||
.submitLabel(.done)
|
||||
.onSubmit(saveName)
|
||||
}
|
||||
if let advertised = currentDevice.advertisedName, !advertised.isEmpty {
|
||||
LabeledContent("Gefunden als") {
|
||||
Text(advertised).font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
LabeledContent("Typ", value: currentDevice.role.title)
|
||||
if currentDevice.role == .fridge {
|
||||
Picker("Kühlzonen", selection: Binding(
|
||||
get: { device.fridgeZoneMode },
|
||||
get: { currentDevice.fridgeZoneMode },
|
||||
set: { mode in
|
||||
var updated = device
|
||||
var updated = currentDevice
|
||||
updated.fridgeZoneMode = mode
|
||||
store.update(updated)
|
||||
bluetooth.updateFridgeZoneMode(for: updated)
|
||||
@@ -354,7 +372,7 @@ struct DeviceDetailView: View {
|
||||
}
|
||||
}
|
||||
LabeledContent("Bluetooth-ID") {
|
||||
Text(device.peripheralID.uuidString.prefix(8) + "…")
|
||||
Text(currentDevice.peripheralID.uuidString.prefix(8) + "…")
|
||||
.font(.caption.monospaced())
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
@@ -372,8 +390,11 @@ struct DeviceDetailView: View {
|
||||
}
|
||||
|
||||
private func saveName() {
|
||||
var updated = device
|
||||
updated.name = editedName.trimmingCharacters(in: .whitespaces)
|
||||
let trimmed = editedName.trimmingCharacters(in: .whitespaces)
|
||||
// Ein leeres Feld beim Tippen darf den Namen nicht löschen.
|
||||
guard !trimmed.isEmpty, trimmed != currentDevice.name else { return }
|
||||
var updated = currentDevice
|
||||
updated.name = trimmed
|
||||
store.update(updated)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user