Files
Vorrania/ios/Sources/NFCViews.swift
T
ScarriffleandClaude Opus 5 09a763a823 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 (siehe f981f41).

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, obwohl f981f41 sie
  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>
2026-09-12 13:37:24 +02:00

125 lines
4.1 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}
}