import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hl_lieferservice/feature/car_selection/bloc/bloc.dart'; import 'package:hl_lieferservice/feature/car_selection/bloc/state.dart'; import 'package:hl_lieferservice/feature/cars/bloc/cars_bloc.dart'; 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/delivery/bloc/phase_bloc.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/phase_event.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/phase_state.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/tour_bloc.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/tour_event.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/tour_state.dart'; import 'package:hl_lieferservice/feature/delivery/model/delivery_phase.dart'; import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_overview_page.dart'; import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_selection_page.dart'; import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_sort_page.dart'; import 'package:hl_lieferservice/feature/loading/presentation/loading_overview_page.dart'; /// Wurzel-Widget des authentifizierten Bereichs. Routet anhand der aktuellen /// Phase des ausgewählten Fahrzeugs: /// /// * Phase Sortieren / Beladen → die jeweilige Phase-Page wird direkt /// gerendert (kein BottomNav). Navigation läuft über den Phasen-Stepper. /// * Phase Ausliefern → klassisches Home mit BottomNavigationBar /// (Auslieferung / Fahrzeuge / Einstellungen). Beladung als Tab entfällt, /// da die Phase abgeschlossen ist. class Home extends StatefulWidget { const Home({super.key}); @override State createState() => _HomeState(); } class _HomeState extends State { String? _initializedCarId; /// Merkt, für welches Auto die automatische Lieferungs-Zuordnung bereits /// erledigt ist (Ein-Auto-Teams, s. [_ensureSingleCarAssignment]). String? _autoAssignedForCar; @override void initState() { super.initState(); // Tour beim ersten Aufbau laden. Account-Filter sitzt jetzt // serverseitig im JWT — kein Personalnummer-Argument mehr nötig. context.read().add(const LoadTour()); // CarsBloc auch hier triggern: wenn der CarSelectBloc beim App-Start // eine valide Tages-Auswahl aus den SharedPreferences fand, wurde die // CarSelectionPage übersprungen — und damit auch ihr `CarLoad`-Trigger. // Ohne diesen Aufruf hängen drei Stellen am leeren CarsBloc-State: // `PhaseStepper.carCount`, `app.carCountResolver` (Eintrittsphase) und // `delivery_selection_page._plateFor` (Vergeben-Tab zeigt sonst "?"). // Der Bloc-interne `if (state is CarsLoaded && !event.force) return;` // macht den Aufruf idempotent. context.read().add(const CarLoad()); } /// Stellt sicher, dass für das aktuell gewählte Auto eine Phase im /// [PhaseBloc] existiert. Wird im build() reaktiv aufgerufen, daher mit /// `_initializedCarId` gegen mehrfache Loads gesichert. /// /// Wichtig: Wir feuern den Load erst, sobald sowohl Tour als auch Cars /// einen geladen-Zustand haben — sonst fragt der `PhaseBloc` den /// `carCountResolver` ab, bekommt `null` und entscheidet für Mehr-Auto- /// Teams fälschlich auf `sortieren` statt `auswaehlen`. void _ensurePhaseLoaded(String carId) { if (_initializedCarId == carId) return; final carsState = context.read().state; if (carsState is! CarsLoaded) return; _initializedCarId = carId; context.read().add(PhaseLoadForCar(carId: carId)); } /// Ein-Auto-Teams überspringen die „Auswählen"-Phase (s. [PhaseBloc]), /// in der Lieferungen normalerweise einem Fahrzeug zugeordnet werden. /// Ohne Zuordnung (`assignedCarId`) blieben sie in „Ausliefern" unsichtbar. /// Daher hier einmalig: ist genau EIN Fahrzeug im Team, werden alle noch /// nicht zugeordneten Lieferungen automatisch diesem Fahrzeug zugewiesen. /// /// Reaktiv (im build) aufgerufen, aber per [_autoAssignedForCar] gegen /// Mehrfachausführung gesichert. Greift erst, wenn Tour UND Cars geladen /// sind (sonst Abbruch ohne Flag → erneuter Versuch beim nächsten Build). void _ensureSingleCarAssignment(String carId) { if (_autoAssignedForCar == carId) return; final carsState = context.read().state; final tourState = context.read().state; if (carsState is! CarsLoaded || tourState is! TourLoaded) return; _autoAssignedForCar = carId; // Nur Ein-Auto-Teams: bei ≥2 Fahrzeugen entscheidet der Fahrer aktiv in // der „Auswählen"-Phase, hier wird bewusst nichts automatisch zugewiesen. if (carsState.cars.length != 1) return; final tourBloc = context.read(); for (final delivery in tourState.details.deliveries) { if (delivery.assignedCarId != carId) { tourBloc.add( AssignCarToDelivery(deliveryId: delivery.id, carId: carId), ); } } } @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, carState) { // Ohne ausgewähltes Auto bleibt Home leer — der CarSelectionEnforcer // legt die Selection-Page als Overlay darüber. if (carState is! CarSelectComplete) { return const Scaffold(body: SizedBox.shrink()); } final carId = carState.selectedCar.id.toString(); // Nachzieh-Trigger: wenn Cars erst NACH dem Tour-Build fertig // werden, würde `_ensurePhaseLoaded` beim Tour-Build noch // skippen. Dieser Listener feuert sobald `CarsLoaded` da ist. return BlocListener( listenWhen: (prev, curr) => prev is! CarsLoaded && curr is CarsLoaded, listener: (context, _) { _ensurePhaseLoaded(carId); _ensureSingleCarAssignment(carId); }, child: BlocBuilder( builder: (context, tourState) { if (tourState is TourLoaded || tourState is TourEmpty) { _ensurePhaseLoaded(carId); } if (tourState is TourLoaded) { _ensureSingleCarAssignment(carId); } return BlocBuilder( builder: (context, phaseState) { final phase = phaseState is PhaseReady ? phaseState.phaseFor(carId) : null; // Solange Tour, Cars oder Phase noch laden, kurzen // Spinner zeigen — das dauert in der Praxis maximal // ein paar Frames. if (phase == null) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } return _buildForPhase( context, phase, carState.selectedCar.id, ); }, ); }, ), ); }, ); } Widget _buildForPhase( BuildContext context, DeliveryPhase phase, String selectedCarId, ) { switch (phase) { case DeliveryPhase.auswaehlen: // Auswahl-Page nur sichtbar bei Teams mit ≥2 Fahrzeugen — der // PhaseBloc setzt diese Phase nicht für Ein-Auto-Teams. return DeliverySelectionPage(selectedCarId: selectedCarId); case DeliveryPhase.sortieren: // Sort-Page baut eigenen Scaffold inkl. Stepper-Header. return DeliverySortPage(selectedCarId: selectedCarId); case DeliveryPhase.beladen: // Beladen-Phase: Einstieg über die Übersicht. Der Fahrer wählt selbst // aus, mit welchem Kunden er starten möchte — das Kunden-Vollbild // wird per Tap auf eine Karte geöffnet (siehe LoadingOverviewPage). return const LoadingOverviewPage(); case DeliveryPhase.ausliefern: return const _DeliveryHome(); } } } /// Home für die Auslieferungs-Phase. Reine Hülle um die Übersicht — es gibt /// keine BottomNavigationBar mehr: Fahrzeug-Verwaltung und Einstellungen /// sind über den Drawer erreichbar, der Fahrzeug-Wechsel direkt aus dem /// PhaseStepper (Icon neben dem Plate). class _DeliveryHome extends StatelessWidget { const _DeliveryHome(); @override Widget build(BuildContext context) { return const DeliveryOverviewPage(); } }