Nach dem Anlegen eines einzelnen Stuecks sollte das Tag-Blatt aufgehen. Es kam nie. Ein zweites Blatt aus einem Blatt heraus ist hier gleich dreifach riskant: ItemAddSheet traegt schon ein fullScreenCover (Lagerort scannen) - zwei Praesentations-Modifikatoren am selben View vertragen sich in SwiftUI nicht zuverlaessig - und das Formular wird selbst aus einer Section heraus gezeigt. Vor beidem warnen ProductDetailView:349 und ItemViews:775 bereits aus eigener Erfahrung. Der Tag-Schritt wird deshalb in den ohnehin vorhandenen NavigationStack geschoben. Ein Schub kennt keine dieser Fallen und ist fuer einen Schritt in einem Ablauf ohnehin die passendere Form. NFCSchreibenView bekommt dafuer ein optionales onFertig. Ohne Angabe schliesst sie sich selbst wie bisher - der Aufruf aus der Stueck-Bearbeitung bleibt unveraendert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
134 lines
4.5 KiB
Swift
134 lines
4.5 KiB
Swift
import SwiftUI
|
||
|
||
/// Ein NFC-Tag mit dem Link auf ein Einzelstück beschreiben.
|
||
///
|
||
/// Gedacht für Kleidung: Das Tag wird eingenäht und ersetzt den QR, der die
|
||
/// Wäsche nicht überstehen würde. Auf das Tag kommt genau das, was auch im QR
|
||
/// steht – `vorrania://i/<UID>`.
|
||
struct NFCSchreibenView: View {
|
||
let item: Item
|
||
/// Was „Fertig" tun soll. Ohne Angabe schliesst die Ansicht sich selbst –
|
||
/// das ist der Fall, wenn sie aus der Stueck-Bearbeitung heraus aufgerufen
|
||
/// wird. Haengt sie als Schritt an einem Ablauf (gleich nach dem Anlegen),
|
||
/// muss stattdessen der ganze Ablauf enden.
|
||
var onFertig: (() -> Void)? = nil
|
||
|
||
@Environment(\.dismiss) private var dismiss
|
||
|
||
@State private var sperren = false
|
||
@State private var laeuft = false
|
||
@State private var erfolg = false
|
||
@State private var fehler: String?
|
||
|
||
private var nutzlast: String { einzelstueckLink(fuer: item.uid) }
|
||
|
||
var body: some View {
|
||
Form {
|
||
Section {
|
||
if let name = item.productName {
|
||
LabeledContent("Artikel", value: name)
|
||
}
|
||
LabeledContent("UID", value: item.uid)
|
||
} footer: {
|
||
Text("Auf das Tag wird geschrieben: \(nutzlast)")
|
||
}
|
||
|
||
Section {
|
||
Toggle("Tag dauerhaft schreibschützen", isOn: $sperren)
|
||
} footer: {
|
||
if sperren {
|
||
Text("Das lässt sich nicht rückgängig machen. Das Tag gehört danach "
|
||
+ "für immer zu diesem Einzelstück und kann nicht neu beschrieben werden.")
|
||
.foregroundStyle(.orange)
|
||
} else {
|
||
Text("Ohne Schreibschutz kannst du das Tag später einem anderen "
|
||
+ "Einzelstück zuweisen.")
|
||
}
|
||
}
|
||
|
||
Section {
|
||
Button(laeuft ? "Tag anhalten…" : "Tag beschreiben") {
|
||
Task { await schreiben() }
|
||
}
|
||
.disabled(laeuft)
|
||
}
|
||
|
||
if erfolg {
|
||
Section {
|
||
Label("Tag beschrieben.", systemImage: "checkmark.circle")
|
||
.foregroundStyle(.green)
|
||
}
|
||
}
|
||
if let fehler {
|
||
Section {
|
||
Text(fehler).foregroundStyle(.red).font(.callout)
|
||
}
|
||
}
|
||
}
|
||
.navigationTitle("NFC-Tag")
|
||
.navigationBarTitleDisplayMode(.inline)
|
||
.toolbar {
|
||
ToolbarItem(placement: .topBarLeading) {
|
||
Button("Fertig") { if let onFertig { onFertig() } else { dismiss() } }
|
||
}
|
||
}
|
||
}
|
||
|
||
private func schreiben() async {
|
||
laeuft = true
|
||
fehler = nil
|
||
erfolg = false
|
||
defer { laeuft = false }
|
||
do {
|
||
try await NFCDienst.schreiben(nutzlast, sperren: sperren)
|
||
erfolg = true
|
||
} catch NFCDienst.Fehler.abgebrochen {
|
||
// Kein Fehler, sondern eine Entscheidung – nichts anzeigen.
|
||
} catch {
|
||
fehler = error.localizedDescription
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Knopf, der ein NFC-Tag liest und die gefundene Einzelstück-UID zurückgibt.
|
||
///
|
||
/// Blendet sich auf Geräten ohne NFC (iPad) selbst aus, damit die umgebenden
|
||
/// Ansichten das nicht jedes Mal selbst prüfen müssen.
|
||
struct NFCLesenButton: View {
|
||
var titel: String = "NFC-Tag lesen"
|
||
var onUid: (String) -> Void
|
||
var onFehler: (String) -> Void
|
||
|
||
@State private var laeuft = false
|
||
|
||
var body: some View {
|
||
if NFCDienst.verfuegbar {
|
||
Button {
|
||
Task { await lesen() }
|
||
} label: {
|
||
Label(laeuft ? "Tag anhalten…" : titel, systemImage: "wave.3.right")
|
||
}
|
||
.disabled(laeuft)
|
||
}
|
||
}
|
||
|
||
private func lesen() async {
|
||
laeuft = true
|
||
defer { laeuft = false }
|
||
do {
|
||
let roh = try await NFCDienst.lesen()
|
||
// Ein selbst beschriebenes Tag darf auch die nackte UID tragen – anders
|
||
// als bei der Kamera droht hier keine Verwechslung mit einem Barcode.
|
||
guard let uid = einzelstueckCode(aus: roh, nackteUidErlauben: true) else {
|
||
onFehler("Auf dem Tag steht kein Einzelstück: \(roh)")
|
||
return
|
||
}
|
||
onUid(uid)
|
||
} catch NFCDienst.Fehler.abgebrochen {
|
||
// Nutzer hat abgebrochen – keine Meldung.
|
||
} catch {
|
||
onFehler(error.localizedDescription)
|
||
}
|
||
}
|
||
}
|