# 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<T>`)
- **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<T>, 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.
