Letzter Roadmap-Punkt von V3. Foto-basierte Lichtverhältnis-Analyse pro Stellplatz (Cloud Function analyzeLocation) und darauf aufbauende, rein textbasierte Eignungs-Bewertung einer Pflanze (assessPlantFit) — beides ausschließlich auf Abruf, nicht automatisch (Kostenkontrolle, eigener Anthropic-Key). Neue Stellplatz-Detailseite, Verlinkung im Pflanzen-Detail, Erkennung veralteter Bewertungen bei Umzug/Neuanalyse. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
64 lines
7.7 KiB
Markdown
64 lines
7.7 KiB
Markdown
# Architektur & Projektstruktur
|
|
|
|
Dieses Dokument erklärt den Aufbau des LeafItToMe-Codes — auch als Blaupause für weitere Flutter-Apps.
|
|
|
|
## Ordnerstruktur
|
|
|
|
```
|
|
lib/
|
|
├── main.dart # Einstiegspunkt: lädt SharedPreferences, startet die App
|
|
├── app.dart # MaterialApp: Theme, Routing, Lokalisierung, Schriftgröße
|
|
├── core/ # Alles, was NICHT zu einem einzelnen Feature gehört
|
|
│ ├── router/app_router.dart # Zentrale Routen-Definition (go_router)
|
|
│ ├── settings/ # App-Einstellungen (Theme, Schriftgröße, Erinnerungszeit)
|
|
│ ├── theme/app_theme.dart # Farbschema und Bedienbarkeits-Anpassungen
|
|
│ └── widgets/app_drawer.dart# Das Hamburger-Menü
|
|
├── features/ # Ein Ordner pro Fachlichkeit
|
|
│ ├── auth/ # Login/Registrierung + Haushalts-Bootstrap
|
|
│ ├── today/ # Tages-Checkliste (Start-Screen)
|
|
│ ├── plants/ # Pflanzen: Liste, Profil, Anlegen/Bearbeiten
|
|
│ ├── locations/ # Stellplätze
|
|
│ ├── household/ # Haushalt: Mitglieder, Einladen, Wechseln
|
|
│ └── settings/ # Einstellungs-Screen
|
|
└── l10n/
|
|
├── app_de.arb # ALLE deutschen Texte (Quelle)
|
|
└── generated/ # Automatisch generierter Code (nicht anfassen)
|
|
```
|
|
|
|
## Schichten innerhalb eines Features
|
|
|
|
Jedes Feature folgt demselben Muster (nicht jedes braucht alle Schichten):
|
|
|
|
- **`domain/`** — Die fachlichen Modelle (`Plant`, `DueTask`, `PlantLocation`). Reines Dart, keine Flutter- oder Firebase-Abhängigkeiten. Hier steckt die Fachlogik (z. B. „Fälligkeit = letzte Erledigung + Intervall").
|
|
- **`data/`** — Datenhaltung: `StreamProvider` liefern die Live-Daten aus Firestore (Echtzeit-Sync im Haushalt), `Repository`-Klassen kapseln die Schreibzugriffe (anlegen, ändern, bestätigen). In Tests werden Auth und Firestore per Provider-Override durch Mocks ersetzt (`firebase_auth_mocks`, `fake_cloud_firestore`).
|
|
- **`application/`** — Abgeleitete Zustände / Anwendungslogik, z. B. `dueTasksProvider`, der aus den Pflanzen die heute fälligen Aufgaben berechnet.
|
|
- **`presentation/`** — Die Screens und Widgets.
|
|
|
|
**Faustregel für Abhängigkeiten:** `presentation → application → data → domain`. Niemals andersherum, und Features greifen nur über Provider aufeinander zu.
|
|
|
|
## State Management: Riverpod in 3 Sätzen
|
|
|
|
1. Ein **`Notifier`** hält veränderlichen Zustand (z. B. die Pflanzenliste) und bietet Methoden zum Ändern (`addPlant`, `confirmTask`); ein **`Provider`** berechnet abgeleiteten Zustand (z. B. fällige Aufgaben) und aktualisiert sich automatisch, wenn sich seine Quellen ändern.
|
|
2. Screens sind `ConsumerWidget`s: `ref.watch(provider)` liest den Zustand und baut die UI bei Änderungen neu; `ref.read(provider.notifier).methode()` löst Änderungen aus (in Callbacks).
|
|
3. `ProviderScope` in `main.dart` ist die Wurzel; dort werden auch echte Abhängigkeiten (SharedPreferences, später Firebase) hineingereicht — Tests können sie durch Mocks ersetzen.
|
|
|
|
## Kernentscheidungen im fachlichen Modell
|
|
|
|
- **Aufgaben werden nicht gespeichert, sondern berechnet.** Es gibt keine „Task-Tabelle": Aus `lastWatered + wateringIntervalDays` ergibt sich die Fälligkeit. Eine Bestätigung setzt nur `lastWatered` neu — dadurch kann nichts inkonsistent werden, und Überfälliges „wandert" automatisch mit. Seit V3 gibt es zusätzlich **Umtopfen** als dritten Aufgabentyp — als Einziger opt-in (`repottingIntervalMonths` ist optional; nur wer es setzt, bekommt Erinnerungen) und mit Intervall in Kalender-**Monaten** statt Tagen (ein 31. wird auf den Monatsletzten gekürzt; Logik gespiegelt in App `due_tasks_provider` und Function `reminders.ts`).
|
|
- **Haushaltszentriert gedacht:** Pflanzen, Stellplätze und Aufgaben gehören dem *Haushalt*, nicht einem Nutzer. In Firestore wird das `households/{id}/plants/{id}` usw. — so ist V2 (Sharing) nur „weiteres Mitglied hinzufügen", keine Datenmigration.
|
|
- **Mehrere Haushalte pro Nutzer:** Quelle der Wahrheit für die Mitgliedschaft ist das Feld `memberUids` der Haushalte (`myHouseholdsProvider` fragt sie per `arrayContains` ab). `users/{uid}.householdId` ist nur noch der Zeiger auf den gerade **aktiven** Haushalt — Wechseln heißt: diesen Zeiger umstellen (Haushalts-Screen „Meine Haushalte“ oder Wechsler im Menü). Der aktive Haushalt bestimmt Pflanzen-/Stellplatz-Listen und wohin neue Pflanzen wandern; die **Tages-Checkliste und die Sammel-Push aggregieren dagegen über alle Haushalte** (Checkliste mit Zwischenüberschriften, `DueTask` trägt dafür seine Haushalts-Herkunft). Jeder Haushalt hat einen **Besitzer** (`ownerUid`): nur er entfernt Mitglieder (`removeMember`-Function), er kann nicht austreten; alle anderen können per `leaveHousehold`-Function austreten. Umbenennen dürfen volle Mitglieder direkt (Rules lassen am Haushalts-Dokument clientseitig nur noch `name` zu).
|
|
- **`null` bei `lastWatered` heißt „noch nie"** → Aufgabe ist sofort fällig. So braucht eine neu angelegte Pflanze keine Sonderbehandlung.
|
|
- **Stellplatz-Bewertung (V3) ist zweistufig und beides auf Abruf, nicht automatisch:** (1) Eine **Standort-Analyse pro Stellplatz** (Foto → Cloud Function `analyzeLocation` → Claude schätzt Lichtverhältnisse ein, eine von vier festen Kategorien + Freitext, gespeichert direkt auf `PlantLocation`, keine Historie — immer der aktuelle Stand). (2) **Eignungs-Sterne pro Pflanze** für ihren aktuell zugeordneten Stellplatz (Cloud Function `assessPlantFit`, rein textbasiert anhand Pflegeprofil + vorhandener Standort-Analyse, kein neues Foto nötig), gespeichert direkt auf `Plant` (`fitStars`, `fitReasoning`, `fitLocationId`, `fitAssessedAt`). Beide Schritte lösen ausschließlich Buttons aus — bewusst keine automatische Neuberechnung bei Änderungen, da der Nutzer die Claude-Aufrufe über seinen eigenen API-Key bezahlt. Ein Fit-Ergebnis gilt als **veraltet** (UI zeigt Hinweis statt Sterne), wenn die Pflanze seither an einen anderen Stellplatz verschoben wurde oder der Stellplatz seither neu analysiert wurde — rein clientseitig durch Vergleich der Zeitstempel/IDs berechnet, kein weiteres Feld nötig.
|
|
- **Haushalts-Bootstrap läuft reaktiv, nicht am Login-Aufruf:** Beim ersten Login (egal ob E-Mail-Registrierung, Apple oder künftig Google) müssen Profil-Dokument (`users/{uid}`) und ein eigener Haushalt angelegt werden — ohne sie lehnen die Rules jeden Schreibzugriff ab. Statt den Bootstrap an die einzelnen Login-Methoden zu hängen (fehleranfällig: der Router leitet schon bei „angemeldet“ weiter, ein Bootstrap-Fehler danach wäre unsichtbar), beobachtet `householdBootstrapProvider` (auth/data) das `users`-Dokument und legt beides an, sobald „angemeldet, aber kein Profil“ eintritt. Das `BootstrapGate` in `app.dart` zeigt so lange einen Warte-Bildschirm und macht Fehler mit „Erneut versuchen“ sichtbar.
|
|
|
|
## Übersetzungen (i18n)
|
|
|
|
Alle Texte stehen in `lib/l10n/app_de.arb` als Schlüssel-Wert-Paare. Im Code heißt es nie `Text('Gießen')`, sondern `Text(l10n.taskWaterTitle(name))`. Englisch später hinzufügen = eine Datei `app_en.arb` mit denselben Schlüsseln anlegen und `flutter gen-l10n` ausführen — kein Code-Umbau.
|
|
|
|
Nach jeder Änderung an der `.arb`-Datei: `flutter gen-l10n` ausführen (passiert bei `flutter run` auch automatisch).
|
|
|
|
## Bedienbarkeit (Zielgruppe: auch ältere Menschen)
|
|
|
|
- Schriftgröße in den Einstellungen wirkt **zusätzlich** zur System-Schriftgröße (`app.dart`, `builder`).
|
|
- „Hoher Kontrast" nutzt Material 3 `contrastLevel` — dasselbe Farbschema, kräftigere Kontraste.
|
|
- Große Touch-Ziele (`app_theme.dart`), keine Swipe-Gesten, alle Funktionen über sichtbare Buttons und das Hamburger-Menü.
|