4.6 KiB
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_v2mit zentralerAppRoutes-Klasse als Single Entry Point - HTTP:
dio, lokales Caching vialocalstore(GenericRequestCache<T>) - Calendar:
syncfusion_flutter_calendar - Datum/Zeit:
jiffy– wird nur über die Extensions inlib/extensions/date_time.dartverwendet - 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: '...')auslib/widget/info_dialog.dart. - Bestätigung:
ConfirmDialog(...).asDialog(context)auslib/widget/confirm_dialog.dart. Async-Bestätigung nutztonConfirmAsync(zeigt Spinner und Inline-Fehler überAsyncDialogAction). - Kein inline
AlertDialog/SimpleDialogmehr.
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
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.