leafittome/lib/features/today/application/due_tasks_provider.dart
cschlaefke 778b3bd34d V2.2: Besitzer-Konzept, Haushalte umbenennen, Austreten/Entfernen, Checkliste über alle Haushalte
- ownerUid je Haushalt (Alt-Haushalte: erster memberUids-Eintrag = Ersteller);
  Besitzer-Anzeige in Mitglieder- und Haushaltsliste
- Haushalt umbenennen (Dialog im Haushalts-Screen); Rules erlauben Clients am
  Haushalts-Dokument nur noch das Namensfeld
- Cloud Functions leaveHousehold (selbst austreten, auch als Sitter) und
  removeMember (nur Besitzer); beide biegen den aktiven Zeiger des Betroffenen
  um und legen notfalls einen frischen eigenen Haushalt an
- Haushalts-Wechsler im Drawer (PopupMenu, ab zwei Haushalten)
- Tages-Checkliste und nächste Fälligkeit aggregieren über alle Haushalte,
  mit Zwischenüberschrift je Haushalt; Bestätigen schreibt in den
  Herkunfts-Haushalt der Aufgabe
- Widget-Tests: Gruppierung, Besitzer-Rechte, Umbenennen

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 21:39:10 +02:00

107 lines
3.9 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;
});
/// 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;
});