NFC-Tags fuer Einzelstuecke: beschreiben und lesen
Ein aufgeklebtes QR-Etikett uebersteht keine Waesche. Kleidungsstuecke bekommen deshalb eingenaehte NFC-Tags. Auf das Tag kommt genau das, was auch im QR steckt (vorrania://i/<UID>) - damit bleibt das Einlesen inhaltlich unveraendert, nur die Erfassungsart ist neu. Backend und Web brauchten nichts: die UID vergibt der Server, und GET /items/by-uid/<UID> gibt es laengst. Gelesen wird nur bei geoeffneter App. iOS startet Apps beim Antippen im Hintergrund ausschliesslich ueber https-Universal-Links auf einer beanspruchten Domain und ignoriert eigene Schemes dabei - eine feste Domain ist fuer einen selbstgehosteten Dienst aber gerade der falsche Weg (siehef981f41). Die /i/-Zerlegung lag dreifach kopiert in CheckInView, LookupScanView und AssignScanView und steckt jetzt in einzelstueckCode(aus:). Die nackte UID wird nur fuer NFC akzeptiert: eine achtstellige EAN-8 kann zufaellig genau wie eine UID aussehen, und die Kamera sieht fremde Barcodes - ein Tag haben wir selbst beschrieben. Zwei Altlasten in denselben Dateien mit erledigt: - project.yml deklarierte die applinks-Domain weiter, obwohlf981f41sie entfernt hat. XcodeGen erzeugt die Entitlements aus diesem Block und schrieb sie bei jedem "generate" zurueck; die Domain existiert laengst nicht mehr. - AssignScanView sendete ItemUpdateRequest ohne priceCents/currency. Da encode immer alle Felder schreibt (auch null), hat ein Zuordnen per Scan Kaufpreis und Waehrung stillschweigend geloescht. Ebenfalls raus: QRImage und qrLink(forUid:) in ItemViews - ungenutzt, und qrLink baute noch das alte https-Format. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9be772557e
commit
09a763a823
@@ -0,0 +1,124 @@
|
||||
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
|
||||
|
||||
@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") { dismiss() } } }
|
||||
}
|
||||
|
||||
private func schreiben() async {
|
||||
laeuft = true
|
||||
fehler = nil
|
||||
erfolg = false
|
||||
defer { laeuft = false }
|
||||
do {
|
||||
try await NFCTag.schreiben(nutzlast, sperren: sperren)
|
||||
erfolg = true
|
||||
} catch NFCTag.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 NFCTag.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 NFCTag.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 NFCTag.Fehler.abgebrochen {
|
||||
// Nutzer hat abgebrochen – keine Meldung.
|
||||
} catch {
|
||||
onFehler(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user