Files
Vorrania/ios
ScarriffleandClaude Opus 5 f04d80a330 App: die selbst draufgesetzten Posten fehlten in der Einkaufsliste
Die Einkaufsliste besteht aus zwei Haelften: gerechnete Bedarfe (Artikel
und Gruppen unter ihrem Mindestbestand, Bedarfe je Lagerort) und das,
was jemand selbst draufgesetzt hat - eigene Eintraege und die aus einem
Rezept uebernommenen Zutaten.

Die App kannte nur die erste Haelfte. Sie fragte /shopping-list,
/shopping-list/groups und /shopping-list/by-location ab - drei Routen,
die ausschliesslich rechnen. Die gespeicherten Eintraege liegen in
/shopping-list/entries und kommen gebuendelt ueber /shopping-list/all;
danach hat die App nie gefragt. Ergebnis: Man setzt im Web etwas auf die
Liste, nimmt das Telefon mit in den Laden, und es ist nicht da.

Jetzt holt die App die gebuendelte Route - ein Aufruf statt drei - und
zeigt die eigenen Posten in einem eigenen Abschnitt "Selbst
draufgesetzt", mit Herkunft ("fuer Pfannkuchen") und Menge. Antippen
hakt ab: Der Posten verschwindet sofort, geht das Speichern schief,
kommt er zurueck - lieber ein Ruecksprung als ein falsches Haekchen.

Dieselbe Blindstelle hatte die Dashboard-Karte der App; sie meldete
"alle Mindestbestaende sind gedeckt", waehrend Posten auf der Liste
standen. Auch sie nimmt jetzt die gebuendelte Route.

Dazu ein Hinweis im Docstring von GET /shopping-list: Diese Route ist
ein Drittel der Liste, wer "die Liste" will, nimmt /all. Die Aufteilung
hat in zwei Tagen zweimal jemanden glauben lassen, es sei nichts da -
beim zweiten Mal war es die App selbst.

Gegen den laufenden Server geprueft: Mit einem Eintrag "Backpapier"
liefert /shopping-list weiterhin [] und /shopping-list/all den Eintrag
samt art, name, menge_text, source, recipe_name und done - genau die
Felder, die das neue Swift-Modell liest.

Nicht kompiliert: kein Xcode auf dieser Maschine. Geprueft sind
Klammer- und Anfuehrungsbalance, dass die drei alten Routen niemand mehr
aufruft, und dass die benutzten Hilfsfunktionen (makeRequest, jsonBody,
send) wirklich so heissen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-03 07:24:58 +02:00
..

Vorrania – iOS-App

Native SwiftUI-App für Lebensmittel und Gegenstände. Sie spricht dieselbe REST-API wie die Web-Oberfläche.

Stand: Voll nutzbar. Login; Scannen von Barcode, MHD/Datum (per Texterkennung, auch nur Tag+Monat ohne Jahr), Lagerort-QR und Name/Marke; Ein-/Auslagern mit mehreren Chargen bzw. Menge je Lagerort; Einzelstücke mit UID/QR, Kaufdatum, Garantie und Beleg – auch verleihen und zurücknehmen; Verbrauchsgegenstände als Charge; Artikel anlegen (Open Food/Products Facts oder manuell, mit Foto); getrennte Listen für Lebensmittel/Gegenstände/Einzelstücke, Kategoriefilter, Sortierung; Einkaufsliste, Ablaufliste, Mindestbestände; Verlauf mit Sprung zum Produkt; lokale Dashboards und Push-Benachrichtigungen bei ablaufenden Produkten.

Projekt in Xcode öffnen

Variante A – mit XcodeGen (empfohlen)

brew install xcodegen
cd ios
xcodegen generate
open Vorrania.xcodeproj

project.yml beschreibt das Projekt vollständig (Bundle-ID, Info.plist, Shortcuts, URL-Schema).

Variante B – ohne XcodeGen

  1. Xcode → File ▸ New ▸ Project… → App, Interface SwiftUI, Sprache Swift
  2. Produktname Vorrania, Bundle-ID z. B. com.scarriffle.vorrania
  3. Die von Xcode erzeugte ContentView.swift und …App.swift löschen
  4. Den Ordner Sources/ per Drag & Drop ins Projekt ziehen („Copy items if needed")
  5. In den Target-Einstellungen die mitgelieferte Sources/Info.plist als Info.plist setzen (Build Settings ▸ Info.plist File) und Generate Info.plist File auf No stellen

Auf dem iPhone installieren

  1. iPhone per Kabel verbinden, in Xcode als Ziel auswählen
  2. Signing & Capabilities → dein Apple-Developer-Team wählen
  3. ▶︎ Run. Beim ersten Start auf dem iPhone unter Einstellungen ▸ Allgemein ▸ VPN & Geräteverwaltung dem Entwickler vertrauen

Erste Schritte in der App

Beim Start nach der Server-Adresse fragen lassen, z. B. http://192.168.1.50:8080 (dieselbe Adresse wie im Browser), dann mit deinem Benutzer anmelden. Adresse und Token bleiben gespeichert – das Token liegt im Keychain.

Hat der Server einen Anmeldedienst eingerichtet (Single Sign-on, siehe ../README.md), erscheint darunter ein Knopf „Mit … anmelden". Die Anmeldung läuft dann im System-Browser (AppAuth mit PKCE, kein eingebettetes WebView); der Anmeldezustand samt Refresh-Token liegt je Serverprofil getrennt im Keychain, und die App erneuert ein abgelaufenes Token still im Hintergrund.

Home-Screen-Shortcuts

Es gibt zwei Wege direkt in den Scan-Bildschirm:

Schnellaktionen – langer Druck auf das App-Symbol zeigt „Einlagern" und „Auslagern" (funktioniert ohne weitere Einrichtung).

Eigene Symbole auf dem Home-Bildschirm – über die Apple-App Kurzbefehle:

  1. Kurzbefehle ▸ + ▸ Aktion URL öffnen
  2. URL vorrania://checkin (bzw. vorrania://checkout) eintragen
  3. Kurzbefehl benennen, dann ▸ Zum Home-Bildschirm hinzufügen

Beide Wege öffnen die App direkt in der Kamera.

Einzelstücke verleihen

In der Ansicht eines Einzelstücks steht Verleih ganz oben: verliehen an wen, seit wann, bis wann – und der Knopf zum Herausgeben bzw. Zurücknehmen. Pflicht ist nur der Name; Grund und Rückgabedatum sind optional, denn „kurz mal mitgenommen" soll nicht an einem Datum scheitern.

Dass der Knopf genau dort sitzt, macht scannen und verleihen möglich, ohne einen eigenen Weg dafür zu bauen: Alle Scan-Wege der App enden in dieser Ansicht – der QR über Nachschlagen, ein eingenähtes NFC-Tag über NFC-Tag lesen und ein angetipptes Tag über vorrania://i/<UID>.

Der Lagerort bleibt beim Verleihen stehen – dorthin gehört das Stück zurück. Als verliehen zählt es aber nicht mehr zum Bestand, es ist ja nicht im Haus. In der Einzelstück-Liste steht der Verleih unter dem Stück (überfällig in Rot) und lässt sich über das Filter-Menü auf Verliehen, Überfällig oder Im Haus eingrenzen.

Rückgabe-Erinnerung

Unter Benachrichtigungen (je Server) gibt es neben den MHD-Regeln den Abschnitt Verliehene Einzelstücke: „X vorher", am Rückgabetag, und täglich weiter, solange etwas überfällig ist. Ein Verleih ohne Rückgabedatum ist nie fällig und kommt nicht vor.

Zwei Dinge dazu, die man wissen sollte:

  • Ein Meldetag ist eine Meldung. Alles, was an einem Tag auf einem Server anliegt – ablaufende Chargen und fällige Rückgaben – steht in derselben Mitteilung („2 Produkte laufen bald ab: … / Kamera (Markus) ist überfällig"). Das ist nicht nur weniger Lärm: iOS erlaubt 64 geplante Mitteilungen je App, und ein Platz je Tag statt einer je Meldungsart ist der Grund, warum der ganze Monat hineinpasst.
  • Der Plan steht einen Monat. Geplant werden die nächsten 30 Tage, damit die Erinnerungen auch kommen, wenn die App einen Monat nicht geöffnet wird – der Hintergrundlauf (BGAppRefreshTask) ist keine Garantie, iOS führt ihn nur aus, wenn es will. Weiter entfernte Tage plant die App beim nächsten Start. Eine MHD-Regel mit mehr als 30 Tagen Vorlauf greift deshalb erst, wenn das Datum in dieses Fenster rückt.

NFC-Tags für Kleidung

Ein aufgeklebtes QR-Etikett übersteht keine Wäsche. Für Kleidungsstücke gibt es deshalb eingenähte NFC-Tags: Einzelstück öffnen ▸ NFC-Tag beschreiben ▸ Tag an die Oberseite des iPhones halten. Auf das Tag kommt derselbe Link, der auch im QR steckt (vorrania://i/<UID>).

Gelesen wird es unter Nachschlagen oder Ort zuordnen über NFC-Tag lesen. Das Antippen bei gesperrtem Handy startet die App bewusst nicht: iOS macht das nur über Universal Links auf einer festen Domain, und die gibt es bei einem selbst gehosteten Dienst nicht.

Zum Bauen muss für die App-ID im Apple-Developer-Portal die Capability Near Field Communication Tag Reading aktiv sein, sonst schlägt das Signieren fehl. NFC gibt es nur auf dem iPhone – auf dem iPad sind die Knöpfe ausgeblendet.

Aufbau der Quellen

Datei Inhalt
VorraniaApp.swift App-Einstieg, Schnellaktionen, URL-Schema
Session.swift Server-Adresse (UserDefaults) und Token (Keychain)
APIClient.swift REST-Aufrufe gegen /api/...
Models.swift Codable-Typen passend zu backend/app/schemas.py
ScannerView.swift Kamera + Barcode-Erkennung (EAN-8/13, UPC-E, Code128, QR)
RootView.swift Startbildschirm und Routing
LoginView.swift Server + Anmeldung
CheckInView.swift / CheckInFormView.swift Scan → Charge (MHD), Menge je Lagerort oder Einzelstück
CheckOutView.swift Scan → Menge, Chargenauswahl oder FEFO
ProductViews.swift Artikelsuche und Anlegen (mit OFF-Vorbefüllung, Foto, Text-Scan)
ProductDetailView.swift Produkt bearbeiten, Verwaltungsart ändern, Chargen korrigieren
ObjectStockView.swift Gegenstände: Menge je Lagerort (hinzufügen/umlagern/entfernen)
ItemViews.swift / ItemListView.swift Einzelstücke anlegen, bearbeiten und auflisten
AssignScanView.swift Einzelstück (Scan, NFC oder Liste) einem Lagerort-QR zuordnen
NFCTag.swift / NFCViews.swift NFC-Tags lesen und beschreiben, Einzelstück-Code zerlegen
ListViews.swift Produktlisten, Einkaufsliste, „bald ablaufend", Mindestbestände
DashboardView.swift / DashboardCards.swift / ChartCards.swift Übersicht mit Karten und Diagrammen
NotificationScheduler.swift / NotificationSettings*.swift Push-Benachrichtigungen bei Ablauf
DateScanView.swift / BestBeforeText.swift MHD/Text per Texterkennung ablesen
DisplaySettings.swift Datumsformat und Einheiten-Beschriftungen vom Server
CategoryPicker.swift Kategorie-Auswahl und Gruppe anlegen

Gruppe und Kategorie – zwei verschiedene Dinge

Gruppe dient der Bestandsrechnung: Mehl von Rewe, Aldi und Migros zählen zusammen, wichtig ist „ich habe 5 kg Mehl“. Eine Gruppe hat deshalb einen gemeinsamen Mindestbestand mit Einheit und EAN-Codes. Ordnest du einen Artikel einer Gruppe zu, erscheint sein EAN-Code automatisch dort; wechselt er die Gruppe, wandert der Code mit.

Kategorie dient allein dem Überblick („Süßwaren“, „Molkereiprodukte“) und ist verschachtelbar wie ein Lagerort (Süßwaren → Schokolade). Sie hat keinen Bestand, keinen Mindestbestand und keine EAN-Codes. Filterst du auf eine Oberkategorie, erscheinen die Artikel ihrer Unterkategorien mit. Beim Anlegen schlägt der Server eine Kategorie aus der Open-Food-Facts-Einordnung vor.

Weiteres

Push-Benachrichtigungen bei Ablauf und Lagerort je Charge beim Einlagern sind umgesetzt. Weitere Ideen stehen in ../docs/ROADMAP.md.