Dritter Aufgabentyp repotting im Intervall-Modell: optionales repottingIntervalMonths + lastRepotted/By am Pflanzenprofil, Formular mit Intervall-Feld und Datums-Auswahl, Umtopf-Zeile im Detail, Aufgaben in Checkliste und Sammel-Push (reminders.ts gespiegelt), Sitter dürfen bestätigen (Rules-Allowlist). Monats-Arithmetik kürzt den 31. auf den Monatsletzten. Ende-zu-Ende-Widget-Test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6.6 KiB
6.6 KiB
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:StreamProviderliefern 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
- Ein
Notifierhält veränderlichen Zustand (z. B. die Pflanzenliste) und bietet Methoden zum Ändern (addPlant,confirmTask); einProviderberechnet abgeleiteten Zustand (z. B. fällige Aufgaben) und aktualisiert sich automatisch, wenn sich seine Quellen ändern. - Screens sind
ConsumerWidgets:ref.watch(provider)liest den Zustand und baut die UI bei Änderungen neu;ref.read(provider.notifier).methode()löst Änderungen aus (in Callbacks). ProviderScopeinmain.dartist 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 + wateringIntervalDaysergibt sich die Fälligkeit. Eine Bestätigung setzt nurlastWateredneu — 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 (repottingIntervalMonthsist 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 Appdue_tasks_providerund Functionreminders.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
memberUidsder Haushalte (myHouseholdsProviderfragt sie perarrayContainsab).users/{uid}.householdIdist 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,DueTaskträ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 perleaveHousehold-Function austreten. Umbenennen dürfen volle Mitglieder direkt (Rules lassen am Haushalts-Dokument clientseitig nur nochnamezu). nullbeilastWateredheißt „noch nie" → Aufgabe ist sofort fällig. So braucht eine neu angelegte Pflanze keine Sonderbehandlung.- 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), beobachtethouseholdBootstrapProvider(auth/data) dasusers-Dokument und legt beides an, sobald „angemeldet, aber kein Profil“ eintritt. DasBootstrapGateinapp.dartzeigt 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ü.