fix: Fehlerbehandlung beim Laden und Meldungen überarbeiten

Reklamationen der Fahrer: Meldungen verdeckten Buttons, und nach dem
Abschluss stand „Lieferung nicht in der Tour gefunden" ohne Ausweg da.

Meldungen:
- Erfolgs-SnackBars entfernt, bei Erfolg bleibt der Spinner
- einheitliche, schwebende Meldungen statt Stau über den Buttons
- Server-Rohtexte durch verständliche Texte ersetzt (api_failure_text)

Laden:
- fehlt eine geöffnete Lieferung, wird die Tour einmal neu geholt; die
  Meldung kommt nur nach erfolgreichem Abruf ohne die Lieferung
- Endlos-Spinner auf Lieferdetail, Filial-Abholung und Beladen ersetzt
  durch Kopfzeile, Fehlergrund und „Erneut versuchen"
- Tour wird nach einem Tageswechsel nachgeladen, offene Seiten bleiben
  dabei unberührt
- Abschluss und Zuweisung überschreiben kein zwischenzeitliches Neuladen,
  alte Antworten drehen einen Abschluss nicht mehr zurück
- leere Server-Antworten gelten als Fehler statt als „keine Daten"

Anmeldung:
- Token-Erneuerung wartet höchstens 12 s, der native Aufruf läuft weiter
- App-Start ohne Netz zeigt einen Hinweis mit „Erneut versuchen"
- Fehlerseite der Fahrzeugauswahl bietet „Abmelden"

Außerdem Podfile.lock an die Plugins angeglichen. Tests für die neuen
Fehlerpfade ergänzt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dennis Nemec
2026-10-30 20:28:24 +01:00
parent 2ff331294e
commit bc95fe9577
40 changed files with 2063 additions and 365 deletions

View File

@ -8,6 +8,7 @@ import 'package:hl_lieferservice/feature/cars/bloc/cars_event.dart';
import 'package:hl_lieferservice/feature/cars/bloc/cars_state.dart';
import 'package:hl_lieferservice/feature/cars/presentation/car_fail_page.dart';
import 'package:hl_lieferservice/feature/cars/presentation/car_management.dart';
import 'package:hl_lieferservice/widget/app_message.dart';
class CarManagementPage extends StatefulWidget {
const CarManagementPage({super.key});
@ -39,15 +40,10 @@ class _CarManagementPageState extends State<CarManagementPage> {
final carSelectState = context.read<CarSelectBloc>().state;
if (carSelectState is CarSelectComplete &&
carSelectState.selectedCar.id == carId) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
"Dieses Fahrzeug ist aktuell ausgewählt und kann nicht "
"deaktiviert werden. Bitte wähle zuerst ein anderes Fahrzeug "
"aus.",
),
duration: Duration(seconds: 4),
),
context.showAppMessage(
"Dieses Fahrzeug ist aktuell ausgewählt und kann nicht "
"deaktiviert werden. Bitte wähle zuerst ein anderes Fahrzeug "
"aus.",
);
return;
}
@ -65,36 +61,38 @@ class _CarManagementPageState extends State<CarManagementPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocBuilder<CarsBloc, CarsState>(
builder: (context, state) {
if (state is CarsLoading || state is CarsInitial) {
return const Center(child: CircularProgressIndicator());
}
if (state is CarsLoaded) {
return BlocBuilder<CarSelectBloc, CarSelectState>(
builder: (context, selectState) {
final String? selectedCarId =
selectState is CarSelectComplete
? selectState.selectedCar.id
: null;
return CarManagementOverview(
cars: state.cars,
selectedCarId: selectedCarId,
onEdit: _edit,
onAdd: _add,
onDelete: _remove,
onRefresh: _refresh,
);
},
);
}
if (state is CarsLoadingFailed) {
return const CarsLoadingFailedPage();
}
return const SizedBox.shrink();
},
),
return BlocBuilder<CarsBloc, CarsState>(
builder: (context, state) {
if (state is CarsLoaded) {
// Bringt seinen eigenen Scaffold samt Plus-Button mit. Ein äußerer
// Scaffold würde die SnackBars anzeigen, ohne den Button zu kennen.
return BlocBuilder<CarSelectBloc, CarSelectState>(
builder: (context, selectState) {
final String? selectedCarId =
selectState is CarSelectComplete
? selectState.selectedCar.id
: null;
return CarManagementOverview(
cars: state.cars,
selectedCarId: selectedCarId,
onEdit: _edit,
onAdd: _add,
onDelete: _remove,
onRefresh: _refresh,
);
},
);
}
if (state is CarsLoadingFailed) {
return const Scaffold(body: CarsLoadingFailedPage());
}
if (state is CarsLoading || state is CarsInitial) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return const Scaffold(body: SizedBox.shrink());
},
);
}
}