import 'package:flutter/foundation.dart'; import 'package:flutter_bloc/flutter_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/model/delivery_phase.dart'; import 'package:hl_lieferservice/feature/delivery/overview/service/phase_service.dart'; /// Liefert die Anzahl der dem aktuellen Fahrer-Team zugeordneten Fahrzeuge. /// Wird vom [PhaseBloc] bei der Ermittlung der Eintrittsphase aufgerufen. /// /// Optional: ist die Tour noch nicht geladen oder die Anzahl unbekannt, /// liefert die Funktion `null` zurück — der BLoC verwendet dann den /// Default-Eintritt [DeliveryPhase.sortieren]. typedef CarCountResolver = int? Function(); /// Zentraler State für die aktuelle Phase je Fahrzeug. Persistiert über /// [PhaseService] auf datumsbezogene SharedPreferences-Keys (siehe Service). /// /// Eintrittsphase nach Fahrzeugauswahl: /// * 1 Auto im Team → [DeliveryPhase.sortieren] (bisheriges Verhalten). /// * ≥2 Autos → [DeliveryPhase.auswaehlen] (neuer Auswahl-Schritt). /// /// Ist bereits eine Phase persistiert, wird diese verwendet (Resume nach /// Neustart der App). Die Eintrittslogik greift also nur beim "ersten Load /// des Tages" für ein Fahrzeug. class PhaseBloc extends Bloc { final PhaseService phaseService; /// Liefert die aktuelle Anzahl der Team-Fahrzeuge. Wird vom umgebenden /// Provider so verdrahtet, dass sie aus dem [TourBloc] kommt. final CarCountResolver? carCountResolver; PhaseBloc({ PhaseService? phaseService, this.carCountResolver, }) : phaseService = phaseService ?? PhaseService(), super(PhaseInitial()) { on(_load); on(_applyLoaded); on(_set); } PhaseReady _ensureReady() { final current = state; return current is PhaseReady ? current : PhaseReady(phaseByCar: const {}); } /// Bestimmt die initiale Phase für ein frisch ausgewähltes Fahrzeug. /// Sobald die Tour bekannt ist und ≥2 Fahrzeuge enthält, startet der /// Fahrer im Auswahl-Schritt; sonst direkt im Sortieren. DeliveryPhase _entryPhase() { final count = carCountResolver?.call(); if (count != null && count >= 2) return DeliveryPhase.auswaehlen; return DeliveryPhase.sortieren; } Future _load(PhaseLoadForCar event, Emitter emit) async { final current = _ensureReady(); // Wenn bereits geladen, nichts tun — der Stepper-Tap entscheidet aktiv. if (current.phaseByCar.containsKey(event.carId)) return; try { final persisted = await phaseService.load(event.carId); final persistedMax = await phaseService.loadMax(event.carId); final phase = persisted ?? _entryPhase(); // Max ist mindestens die aktuelle Phase. Falls in der Persistenz ein // höherer Wert steht (Rücksprung), den nehmen. DeliveryPhase maxPhase = phase; if (persistedMax != null && persistedMax.index > maxPhase.index) { maxPhase = persistedMax; } if (persisted == null) { // Erste Phase nach Fahrzeugauswahl direkt persistieren, damit // ein Resume nach App-Neustart die Phase kennt. await phaseService.save(event.carId, phase); await phaseService.saveMax(event.carId, maxPhase); } else if (persistedMax == null) { // Migration: alte Tage ohne Max-Tracking → einmalig nachziehen. await phaseService.saveMax(event.carId, maxPhase); } add(PhaseLoaded( carId: event.carId, phase: phase, maxPhase: maxPhase, )); } catch (e, st) { debugPrint("PhaseBloc._load: $e $st"); // Fail-soft: ohne Persistenz weiter, damit der Flow nicht hängen bleibt. final fallback = _entryPhase(); add(PhaseLoaded( carId: event.carId, phase: fallback, maxPhase: fallback, )); } } void _applyLoaded(PhaseLoaded event, Emitter emit) { final current = _ensureReady(); emit(current.withLoaded(event.carId, event.phase, event.maxPhase)); } Future _set(PhaseSet event, Emitter emit) async { final current = _ensureReady(); final next = current.withPhase(event.carId, event.phase); emit(next); try { await phaseService.save(event.carId, event.phase); // withPhase hat das Max ggf. hochgezogen — persistieren, damit ein // Neustart die "höchste erreichte Phase" kennt. final newMax = next.maxPhaseFor(event.carId); if (newMax != null) { await phaseService.saveMax(event.carId, newMax); } } catch (e, st) { debugPrint("PhaseBloc._set: $e $st"); // UI bleibt konsistent, Persistenz-Fehler ignorieren wir bewusst — // beim nächsten Setzen wird erneut versucht. } } }