Erinnert an faellige Rueckgaben verliehener Einzelstuecke, auf dem Weg der Ablauf-Meldungen: lokale Benachrichtigung, geplant aus GET /loans?due_in=. Je Server einstellbar - "X vorher", am Rueckgabetag, und taeglich weiter, solange etwas ueberfaellig ist. Ein Verleih ohne Rueckgabedatum ist nie faellig und kommt nicht vor. Zwei Aenderungen am bestehenden Plan waren dafuer noetig, und beide haben denselben Grund: Der Plan muss allein stehen. Der BGAppRefreshTask ist keine Garantie - iOS fuehrt ihn nur aus, wenn es will -, also darf die Erinnerung nicht davon abhaengen, dass die App geoeffnet wird. 1. Alles, was an einem Tag auf einem Server anliegt, wird EINE Meldung. Vorher waren es zwei Arten (bald ablaufend, abgelaufen) mit je eigener Kennung; mit dem Verleih waeren es vier geworden. iOS erlaubt 64 geplante Mitteilungen je App - ein Platz je Tag statt je Art ist die Voraussetzung dafuer, dass der Horizont ueberhaupt hineinpasst. Ein vergessener Verleih hat sonst mit taeglicher Meldung den ganzen Monat belegt und alle MHD-Meldungen verdraengt, weil die weiter in der Zukunft liegen und bei der Kappung zuerst wegfallen. 2. Horizont 30 statt 60 Tage. Er ist damit die Zusage, wie lange die App ungeoeffnet bleiben darf, ohne dass Erinnerungen ausfallen. Ein laengerer Horizont haette nur spaetere Tage eingeplant und dafuer die naechsten verdraengt. Preis: Eine MHD-Regel mit mehr als 30 Tagen Vorlauf greift erst, wenn das Datum in dieses Fenster rueckt. Der LoanPlanner ist wie der ExpiryPlanner netzfrei und ohne iOS-Typen gebaut, damit sich die Entscheidungen ohne Geraet nachrechnen lassen. Gespeicherte Konfigurationen werden mit decodeIfPresent gelesen - eine bestehende Einstellung bleibt nach dem Update gueltig. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
162 lines
8.6 KiB
Markdown
162 lines
8.6 KiB
Markdown
# 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)
|
||
```bash
|
||
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](../README.md#single-sign-on-optional)), 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](../docs/ROADMAP.md).
|