# MarianumMobile Client Flutter-App für die Schul-Community: Webuntis-Stundenplan, Nextcloud Talk + Files, Custom MHSL-Backend (Breaker, Custom Events, Push). ## Stack - **Flutter** (Dart >= 3.8) - **State:** `flutter_bloc` + `hydrated_bloc` (persistente BLoCs pro Modul) - **Navigation:** `persistent_bottom_nav_bar_v2` mit zentraler `AppRoutes`-Klasse als Single Entry Point - **HTTP:** `dio`, lokales Caching via `localstore` (Generic `RequestCache`) - **Calendar:** `syncfusion_flutter_calendar` - **Datum/Zeit:** `jiffy` – wird **nur** über die Extensions in `lib/extensions/date_time.dart` verwendet - **Code-Gen:** `freezed`, `json_serializable` ## Ordnerstruktur ``` lib/ ├── api/ HTTP-Layer pro Backend (mhsl/, marianumcloud/, webuntis/, holidays/) ├── state/app/modules/ BLoC pro Feature-Modul (timetable, chat, chat_list, files, ...) ├── state/app/infrastructure LoadableState, DataLoader, geteilte BLoC-Bausteine ├── view/ Screens │ ├── login/ Login-Flow │ └── pages/ ein Verzeichnis pro Modul (timetable, files, talk, ...) ├── widget/ Geteilte UI-Komponenten (Dialoge, Buttons, Sheets) ├── extensions/ DateTime-, Text-, TimeOfDay-Extensions ├── routing/ AppRoutes (Single Navigation Entry) ├── theming/ Light/Dark Theme ├── storage/ Freezed Settings-Modelle (HydratedBloc-persistent) ├── notification/ Firebase + flutter_local_notifications └── utils/ Helper (clipboard_helper, debouncer, download_manager, ...) ``` ## Konventionen **Navigation:** Ausschließlich über `AppRoutes.openX(context, ...)`. Direkte `Navigator.push(...)` für volle Pages sind nicht erlaubt – `Navigator.pop` für Sheets/Dialogs bleibt am Call-Site. **Dialoge:** - Info/Fehler: `InfoDialog.show(context, body, copyable: true, title: '...')` aus `lib/widget/info_dialog.dart`. - Bestätigung: `ConfirmDialog(...).asDialog(context)` aus `lib/widget/confirm_dialog.dart`. Async-Bestätigung nutzt `onConfirmAsync` (zeigt Spinner und Inline-Fehler über `AsyncDialogAction`). - **Kein** inline `AlertDialog`/`SimpleDialog` mehr. **Bottom-Sheets:** Detail-Sheets gehen über `showDetailsBottomSheet(context, header: ..., children: (ctx) => [...])` aus `lib/widget/details_bottom_sheet.dart`. Header ist optional. **Async-Actions:** Statt manuelles Spinner+Try/Catch die `AsyncActionButton`-Familie aus `lib/widget/async_action_button.dart` (`AsyncActionButton`, `AsyncTextButton`, `AsyncIconButton`, `AsyncFab`, `AsyncListTile`, `AsyncDialogAction`, `runWithErrorDialog`). Fehler-Mapping läuft über `errorBuilder` oder zentral über `errorToUserMessage` aus `lib/api/errors/error_mapper.dart`. **Clipboard:** Über `copyToClipboard(context, text)` aus `lib/utils/clipboard_helper.dart`. Zeigt automatisch SnackBar. **Datum/Zeit-Formatierung:** Über die Extensions in `lib/extensions/date_time.dart`: `dt.formatHm()`, `dt.formatDate()`, `dt.formatDateTime()`, `dt.formatDateShort()`, `dt.formatRelative()`, `start.timeRangeTo(end)`. **Kein** direktes `Jiffy.parseFromDateTime(...).format(pattern: '...')` im View-Code. **Settings:** Pro Feature ein Freezed-Modell unter `lib/storage/`, persistiert via HydratedBloc. ## Build / Run ```bash flutter pub get dart run build_runner build --delete-conflicting-outputs # nach Änderungen an Freezed/JSON-Modellen flutter run # Debug auf angeschlossenem Device flutter analyze # statische Analyse, muss 0 Issues melden flutter test # Tests (siehe test/) ``` ## Backend-Integrationen | Backend | Pfad | Zweck | |---------------------------|-----------------------|----------------------------------------| | Webuntis | `lib/api/webuntis/` | Stundenplan, Klassen, Räume, Lehrer | | Nextcloud (Talk + WebDAV) | `lib/api/marianumcloud/` | Chats, Datei-Verwaltung | | Custom MHSL-Server | `lib/api/mhsl/` | Breaker, Custom Events, Notify, Noten | | Holiday-Calendar | `lib/api/holidays/` | Ferien | `nextcloud`-Paket ist auf einen Custom-Fork gepinnt (siehe `pubspec.yaml` `dependency_overrides`). ## Tests `test/` deckt aktuell nur Kern-Funktionen ab (DateTime-Extensions, AsyncActionController, LessonResolver). Beim Hinzufügen neuer pure-function-Helper bitte Test mit dazu.