leafittome/lib/features/today/application/due_tasks_provider.dart
cschlaefke 6560232655 Fix: Checklisten-Provider dauerhaft warm halten (markNeedsBuild during build)
Riverpod 3 pausiert Provider ohne Zuhörer; beim Router-Wechsel Login →
Heute wurde die veraltete Kette mitten im Widget-Build wiederbelebt und
löste die Warnung weiter aus. checklistWarmupProvider hält die Kette
aus app.dart heraus aktiv, damit sie beim Screen-Mount schon frisch ist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 22:16:39 +02:00

122 lines
4.5 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../household/data/household_providers.dart';
import '../../household/domain/household.dart';
import '../../plants/data/plants_provider.dart';
import '../../plants/domain/plant.dart';
import '../domain/due_task.dart';
DateTime _dateOnly(DateTime dt) => DateTime(dt.year, dt.month, dt.day);
DateTime? _nextDue(DateTime? lastDone, int intervalDays, DateTime today) {
// Noch nie erledigt → sofort fällig.
if (lastDone == null) return today;
return _dateOnly(lastDone).add(Duration(days: intervalDays));
}
Iterable<DueTask> _tasksForPlant(
Plant plant, Household household, DateTime today) sync* {
final watering = _nextDue(plant.lastWatered, plant.wateringIntervalDays, today);
if (watering != null && !watering.isAfter(today)) {
yield DueTask(
plant: plant,
type: CareTaskType.watering,
dueDate: watering,
householdId: household.id,
householdName: household.name,
);
}
final fertilizing =
_nextDue(plant.lastFertilized, plant.fertilizingIntervalDays, today);
if (fertilizing != null && !fertilizing.isAfter(today)) {
yield DueTask(
plant: plant,
type: CareTaskType.fertilizing,
dueDate: fertilizing,
householdId: household.id,
householdName: household.name,
);
}
}
/// Alle heute fälligen und überfälligen Aufgaben aus **allen** Haushalten
/// des Nutzers. Sortiert nach Haushalt (aktiver zuerst), darin Überfälliges
/// zuerst so gruppiert die Checkliste ohne weitere Logik.
final dueTasksProvider = Provider<List<DueTask>>((ref) {
final households = ref.watch(myHouseholdsProvider).value ?? const [];
final activeId = ref.watch(householdIdProvider).value;
final today = _dateOnly(DateTime.now());
final ordered = [...households]..sort((a, b) {
if (a.id == activeId) return -1;
if (b.id == activeId) return 1;
return 0; // myHouseholdsProvider liefert bereits nach Name sortiert.
});
final tasks = <DueTask>[];
for (final household in ordered) {
final plants =
ref.watch(householdPlantsProvider(household.id)).value ?? const [];
final householdTasks = [
for (final plant in plants) ..._tasksForPlant(plant, household, today),
]..sort((a, b) {
final byDate = a.dueDate.compareTo(b.dueDate);
if (byDate != 0) return byDate;
return a.plant.nickname.compareTo(b.plant.nickname);
});
tasks.addAll(householdTasks);
}
return tasks;
});
/// True, solange die Haushalte oder deren Pflanzen noch erstmalig laden
/// damit die Checkliste nicht kurz „Alles versorgt“ zeigt, bevor Daten da sind.
final dueTasksLoadingProvider = Provider<bool>((ref) {
final householdsAsync = ref.watch(myHouseholdsProvider);
if (householdsAsync.isLoading && !householdsAsync.hasValue) return true;
final households = householdsAsync.value ?? const [];
for (final household in households) {
if (!ref.watch(householdPlantsProvider(household.id)).hasValue) {
return true;
}
}
return false;
});
/// Hält die Provider-Kette der Checkliste dauerhaft aktiv (via app.dart).
///
/// Riverpod 3 pausiert Provider ohne aktive Zuhörer. Wechselt der Router
/// von Login auf die Heute-Ansicht, würde deren Einhängen die pausierte,
/// inzwischen veraltete Kette mitten im Widget-Build wiederbeleben — das
/// löst „setState() or markNeedsBuild() called during build“ aus. Mit einem
/// dauerhaften Zuhörer läuft die Kette stattdessen sofort mit und ist beim
/// Einhängen der Screens bereits frisch.
final checklistWarmupProvider = Provider<void>((ref) {
ref.watch(dueTasksProvider);
ref.watch(dueTasksLoadingProvider);
ref.watch(nextDueDateProvider);
ref.watch(myRoleProvider);
});
/// Das nächste zukünftige Fälligkeitsdatum über alle Haushalte für den
/// "Alles versorgt"-Screen ("Nächste Aufgabe: Freitag").
final nextDueDateProvider = Provider<DateTime?>((ref) {
final households = ref.watch(myHouseholdsProvider).value ?? const [];
final today = _dateOnly(DateTime.now());
DateTime? next;
for (final household in households) {
final plants =
ref.watch(householdPlantsProvider(household.id)).value ?? const [];
for (final plant in plants) {
for (final candidate in [
_nextDue(plant.lastWatered, plant.wateringIntervalDays, today),
_nextDue(plant.lastFertilized, plant.fertilizingIntervalDays, today),
]) {
if (candidate == null || !candidate.isAfter(today)) continue;
if (next == null || candidate.isBefore(next)) next = candidate;
}
}
}
return next;
});