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)); } /// Kalendermonate addieren; ein 31. wird notfalls auf den Monatsletzten /// gekürzt (31. Jan + 1 Monat → 28./29. Feb). DateTime _addMonths(DateTime date, int months) { final totalMonths = date.month - 1 + months; final year = date.year + totalMonths ~/ 12; final month = totalMonths % 12 + 1; final lastDayOfMonth = DateTime(year, month + 1, 0).day; return DateTime( year, month, date.day > lastDayOfMonth ? lastDayOfMonth : date.day); } /// Fälligkeit fürs Umtopfen — nur wenn ein Intervall gesetzt ist (opt-in). DateTime? _nextRepotDue(Plant plant, DateTime today) { final months = plant.repottingIntervalMonths; if (months == null) return null; if (plant.lastRepotted == null) return today; return _addMonths(_dateOnly(plant.lastRepotted!), months); } Iterable _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, ); } final repotting = _nextRepotDue(plant, today); if (repotting != null && !repotting.isAfter(today)) { yield DueTask( plant: plant, type: CareTaskType.repotting, dueDate: repotting, 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>((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 = []; 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((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((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((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), _nextRepotDue(plant, today), ]) { if (candidate == null || !candidate.isAfter(today)) continue; if (next == null || candidate.isBefore(next)) next = candidate; } } } return next; });