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>
126 lines
3.7 KiB
Dart
126 lines
3.7 KiB
Dart
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
|
|
import 'package:hl_lieferservice/domain/entity/car.dart';
|
|
import 'package:hl_lieferservice/domain/repository/cars_repository.dart';
|
|
import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart';
|
|
import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart';
|
|
|
|
import 'cars_event.dart';
|
|
import 'cars_state.dart';
|
|
|
|
/// Bloc für die Fahrzeugverwaltung des angemeldeten Fahrers.
|
|
///
|
|
/// `personalnummer`/`teamId` wird **nicht mehr** in den Events
|
|
/// übergeben — der Account kommt serverseitig aus dem JWT.
|
|
/// Session-Expiry geht über den globalen Provider-Stream
|
|
/// (KeycloakOidcTokenProvider → AuthSessionExpired), nicht über
|
|
/// dieses Bloc.
|
|
class CarsBloc extends Bloc<CarEvents, CarsState> {
|
|
CarsBloc({required this.repository, required this.opBloc})
|
|
: super(const CarsInitial()) {
|
|
on<CarLoad>(_handleLoad);
|
|
on<CarAdd>(_handleAdd);
|
|
on<CarEdit>(_handleEdit);
|
|
on<CarDeactivate>(_handleDeactivate);
|
|
}
|
|
|
|
final CarsRepository repository;
|
|
final OperationBloc opBloc;
|
|
|
|
Future<void> _handleLoad(CarLoad event, Emitter<CarsState> emit) async {
|
|
if (state is CarsLoaded && !event.force) return;
|
|
|
|
emit(const CarsLoading());
|
|
try {
|
|
final cars = await repository.listMine();
|
|
emit(CarsLoaded(cars: cars));
|
|
} on CarsRepositoryException catch (e) {
|
|
emit(CarsLoadingFailed(message: e.message));
|
|
} catch (_) {
|
|
emit(const CarsLoadingFailed());
|
|
}
|
|
}
|
|
|
|
Future<void> _handleAdd(CarAdd event, Emitter<CarsState> emit) async {
|
|
final current = state;
|
|
opBloc.add(StartOperation());
|
|
try {
|
|
final created = await repository.create(plate: event.plate);
|
|
if (current is CarsLoaded) {
|
|
emit(current.copyWith(cars: [...current.cars, created]));
|
|
}
|
|
opBloc.add(FinishOperation());
|
|
} on CarsRepositoryException catch (e) {
|
|
opBloc.add(FailOperation(message: e.message));
|
|
} catch (_) {
|
|
opBloc.add(
|
|
FailOperation(message: "Fehler beim Anlegen eines Fahrzeugs"),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _handleEdit(CarEdit event, Emitter<CarsState> emit) async {
|
|
final current = state;
|
|
opBloc.add(StartOperation());
|
|
try {
|
|
final updated = await repository.update(
|
|
carId: event.carId,
|
|
plate: event.plate,
|
|
active: event.active,
|
|
);
|
|
_emitReplaced(current, updated, emit);
|
|
opBloc.add(FinishOperation());
|
|
} on CarsRepositoryException catch (e) {
|
|
opBloc.add(FailOperation(message: e.message));
|
|
} catch (_) {
|
|
opBloc.add(
|
|
FailOperation(message: "Fehler beim Aktualisieren des Fahrzeugs"),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _handleDeactivate(
|
|
CarDeactivate event,
|
|
Emitter<CarsState> emit,
|
|
) async {
|
|
final current = state;
|
|
opBloc.add(StartOperation());
|
|
try {
|
|
await repository.update(carId: event.carId, active: false);
|
|
if (current is CarsLoaded) {
|
|
// Inaktive Fahrzeuge raus aus der UI-Liste — die Liste zeigt
|
|
// nur aktive (Default).
|
|
emit(
|
|
current.copyWith(
|
|
cars: current.cars
|
|
.where((c) => c.id != event.carId)
|
|
.toList(growable: false),
|
|
),
|
|
);
|
|
}
|
|
opBloc.add(FinishOperation());
|
|
} on CarsRepositoryException catch (e) {
|
|
opBloc.add(FailOperation(message: e.message));
|
|
} catch (_) {
|
|
opBloc.add(
|
|
FailOperation(message: "Fehler beim Deaktivieren des Fahrzeugs"),
|
|
);
|
|
}
|
|
}
|
|
|
|
void _emitReplaced(
|
|
CarsState current,
|
|
Car updated,
|
|
Emitter<CarsState> emit,
|
|
) {
|
|
if (current is! CarsLoaded) return;
|
|
emit(
|
|
current.copyWith(
|
|
cars: current.cars
|
|
.map((c) => c.id == updated.id ? updated : c)
|
|
.toList(growable: false),
|
|
),
|
|
);
|
|
}
|
|
}
|