fix(phasen): ohne Tour nur erster Schritt aktiv - Stepper sperrt Beladen/Ausliefern
An einem Tag ohne Tour konnte man in der AppBar durch alle Stepper-Schritte navigieren. Ursache: an leeren Tagen gilt der GETEILTE 'no-tour'-Persistenz- Eimer; einmal dort aufgeschlossener Fortschritt (hohes Max) machte an JEDEM leeren Tag Beladen/Ausliefern anklickbar. - Stepper: ohne geladene Tour (TourEmpty/loading) ist maxReached hart der erste sichtbare Schritt -> weitere Schritte nicht anklickbar, keine Haekchen - PhaseBloc._load: ohne Tour ausschliesslich Eintrittsphase, Persistenz wird weder gelesen noch geschrieben (kein Fortschritt im 'no-tour'-Eimer) - PhaseBloc._set: ohne Tour jeden Vorwaerts-Wechsel verwerfen, nichts persistieren - unabhaengig vom ausloesenden Button/Tap Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -66,6 +66,14 @@ class PhaseBloc extends Bloc<PhaseEvent, PhaseState> {
|
|||||||
/// bedeutungslos.
|
/// bedeutungslos.
|
||||||
String _token() => tourTokenResolver?.call() ?? 'no-tour';
|
String _token() => tourTokenResolver?.call() ?? 'no-tour';
|
||||||
|
|
||||||
|
/// `true`, wenn aktuell eine Tour geladen ist (der Resolver liefert einen
|
||||||
|
/// Token). Ohne Resolver (z. B. in Tests) gilt „Tour vorhanden", damit
|
||||||
|
/// nichts unnötig gesperrt wird.
|
||||||
|
bool _hasTour() {
|
||||||
|
final resolve = tourTokenResolver;
|
||||||
|
return resolve == null || resolve() != null;
|
||||||
|
}
|
||||||
|
|
||||||
PhaseReady _ensureReady() {
|
PhaseReady _ensureReady() {
|
||||||
final current = state;
|
final current = state;
|
||||||
return current is PhaseReady
|
return current is PhaseReady
|
||||||
@ -106,6 +114,17 @@ class PhaseBloc extends Bloc<PhaseEvent, PhaseState> {
|
|||||||
emit(current.withLoaded(event.carId, entry, entry));
|
emit(current.withLoaded(event.carId, entry, entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keine Tour geladen (Tag ohne Tour): ausschließlich die Eintrittsphase,
|
||||||
|
// ohne Persistenz zu lesen oder zu schreiben. Der geteilte
|
||||||
|
// 'no-tour'-Eimer darf keinen Fortschritt tragen — sonst wären an jedem
|
||||||
|
// leeren Tag Beladen/Ausliefern anklickbar.
|
||||||
|
if (!_hasTour()) {
|
||||||
|
final entry = _entryPhase();
|
||||||
|
_loadedTokenByCar[event.carId] = token;
|
||||||
|
add(PhaseLoaded(carId: event.carId, phase: entry, maxPhase: entry));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final persisted = await phaseService.load(event.carId, token);
|
final persisted = await phaseService.load(event.carId, token);
|
||||||
final persistedMax = await phaseService.loadMax(event.carId, token);
|
final persistedMax = await phaseService.loadMax(event.carId, token);
|
||||||
@ -156,9 +175,17 @@ class PhaseBloc extends Bloc<PhaseEvent, PhaseState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _set(PhaseSet event, Emitter<PhaseState> emit) async {
|
Future<void> _set(PhaseSet event, Emitter<PhaseState> emit) async {
|
||||||
|
final hasTour = _hasTour();
|
||||||
|
// Ohne geladene Tour gibt es nichts zu beladen/auszuliefern: nur die
|
||||||
|
// Eintrittsphase ist erlaubt, jeder Vorwärts-Wechsel wird verworfen —
|
||||||
|
// egal, welcher Button oder Stepper-Tap ihn auslöst.
|
||||||
|
if (!hasTour && event.phase.index > _entryPhase().index) return;
|
||||||
final current = _ensureReady();
|
final current = _ensureReady();
|
||||||
final next = current.withPhase(event.carId, event.phase);
|
final next = current.withPhase(event.carId, event.phase);
|
||||||
emit(next);
|
emit(next);
|
||||||
|
// Ohne Tour nichts persistieren — der 'no-tour'-Eimer darf keinen
|
||||||
|
// Fortschritt tragen (s. _load).
|
||||||
|
if (!hasTour) return;
|
||||||
try {
|
try {
|
||||||
final token = _token();
|
final token = _token();
|
||||||
await phaseService.save(event.carId, token, event.phase);
|
await phaseService.save(event.carId, token, event.phase);
|
||||||
|
|||||||
@ -125,12 +125,19 @@ class PhaseStepper extends StatelessWidget {
|
|||||||
final carCount = carsState is CarsLoaded ? carsState.cars.length : 0;
|
final carCount = carsState is CarsLoaded ? carsState.cars.length : 0;
|
||||||
final phases = visiblePhases ?? _effectivePhases(carCount);
|
final phases = visiblePhases ?? _effectivePhases(carCount);
|
||||||
|
|
||||||
|
// Ohne geladene Tour (Tag ohne Tour / noch ladend) ist ausschließlich
|
||||||
|
// der erste Schritt aktiv: es gibt nichts zu beladen oder auszuliefern,
|
||||||
|
// also dürfen die weiteren Schritte weder anklickbar noch abgehakt
|
||||||
|
// sein — unabhängig davon, was der PhaseBloc als Max kennt.
|
||||||
|
final hasTour = context.watch<TourBloc>().state is TourLoaded;
|
||||||
|
|
||||||
// Höchste erreichte Phase aus dem PhaseBloc — bestimmt, welche
|
// Höchste erreichte Phase aus dem PhaseBloc — bestimmt, welche
|
||||||
// Vorwärts-Sprünge erlaubt sind.
|
// Vorwärts-Sprünge erlaubt sind.
|
||||||
final phaseBlocState = context.watch<PhaseBloc>().state;
|
final phaseBlocState = context.watch<PhaseBloc>().state;
|
||||||
final maxReached = phaseBlocState is PhaseReady
|
final blocMax = phaseBlocState is PhaseReady
|
||||||
? (phaseBlocState.maxPhaseFor(carId) ?? currentPhase)
|
? (phaseBlocState.maxPhaseFor(carId) ?? currentPhase)
|
||||||
: currentPhase;
|
: currentPhase;
|
||||||
|
final maxReached = hasTour ? blocMax : phases.first;
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
color: theme.primaryColor,
|
color: theme.primaryColor,
|
||||||
|
|||||||
Reference in New Issue
Block a user