leafittome/lib/features/today/presentation/today_screen.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

222 lines
7 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/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import '../../../core/router/app_router.dart';
import '../../../core/widgets/app_drawer.dart';
import '../../../l10n/generated/app_localizations.dart';
import '../../household/data/household_providers.dart';
import '../../household/domain/household.dart';
import '../../plants/data/plants_provider.dart';
import '../application/due_tasks_provider.dart';
import '../domain/due_task.dart';
/// Start-Screen der App: die Tages-Checkliste.
/// Push-Erinnerungen führen später direkt hierher.
class TodayScreen extends ConsumerWidget {
const TodayScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context);
final tasks = ref.watch(dueTasksProvider);
final Widget body;
if (ref.watch(dueTasksLoadingProvider)) {
body = const Center(child: CircularProgressIndicator());
} else if (tasks.isEmpty) {
body = const _AllDoneView();
} else {
body = _TaskListView(tasks: tasks);
}
final canEdit = ref.watch(myRoleProvider) == HouseholdRole.member;
return Scaffold(
appBar: AppBar(title: Text(l10n.todayTitle)),
drawer: const AppDrawer(),
floatingActionButton: canEdit
? FloatingActionButton.extended(
onPressed: () => context.push(AppRoutes.plantAdd),
icon: const Icon(Icons.add),
label: Text(l10n.addPlant),
)
: null,
body: body,
);
}
}
class _TaskListView extends ConsumerWidget {
const _TaskListView({required this.tasks});
final List<DueTask> tasks;
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context);
final theme = Theme.of(context);
// Zwischenüberschriften nur, wenn Aufgaben aus mehreren Haushalten da
// sind im Normalfall (ein Haushalt) bleibt die Liste wie gewohnt.
final householdIds = {for (final task in tasks) task.householdId};
final showHouseholdHeaders = householdIds.length > 1;
final children = <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text(
l10n.todayOpenTasks(tasks.length),
style: theme.textTheme.titleMedium,
),
),
];
String? currentHouseholdId;
for (final task in tasks) {
if (showHouseholdHeaders && task.householdId != currentHouseholdId) {
currentHouseholdId = task.householdId;
children.add(Padding(
padding: const EdgeInsets.only(top: 12, bottom: 4),
child: Row(
children: [
Icon(Icons.home_outlined,
size: 18, color: theme.colorScheme.primary),
const SizedBox(width: 6),
Expanded(
child: Text(
task.householdName,
style: theme.textTheme.titleSmall
?.copyWith(color: theme.colorScheme.primary),
overflow: TextOverflow.ellipsis,
),
),
],
),
));
}
children.add(_TaskCard(task: task));
}
return ListView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 96),
children: children,
);
}
}
class _TaskCard extends ConsumerWidget {
const _TaskCard({required this.task});
final DueTask task;
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context);
final theme = Theme.of(context);
final today = DateTime.now();
final overdueDays =
task.overdueDays(DateTime(today.year, today.month, today.day));
final title = switch (task.type) {
CareTaskType.watering => l10n.taskWaterTitle(task.plant.nickname),
CareTaskType.fertilizing => l10n.taskFertilizeTitle(task.plant.nickname),
};
final icon = switch (task.type) {
CareTaskType.watering => Icons.water_drop,
CareTaskType.fertilizing => Icons.compost,
};
return Card(
margin: const EdgeInsets.symmetric(vertical: 6),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Icon(icon, size: 32, color: theme.colorScheme.primary),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: theme.textTheme.titleMedium),
const SizedBox(height: 4),
Text(
overdueDays > 0
? l10n.taskOverdue(overdueDays)
: l10n.taskDueToday,
style: theme.textTheme.bodyMedium?.copyWith(
color: overdueDays > 0
? theme.colorScheme.error
: theme.colorScheme.onSurfaceVariant,
),
),
],
),
),
const SizedBox(width: 12),
FilledButton.icon(
onPressed: () {
ref.read(plantRepositoryProvider).confirmTask(
task.householdId, task.plant.id, task.type);
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(
content:
Text(l10n.taskDoneConfirmation(task.plant.nickname)),
),
);
},
icon: const Icon(Icons.check),
label: Text(l10n.taskDone),
),
],
),
),
);
}
}
class _AllDoneView extends ConsumerWidget {
const _AllDoneView();
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context);
final theme = Theme.of(context);
final nextDue = ref.watch(nextDueDateProvider);
final locale = Localizations.localeOf(context).toString();
return Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.check_circle,
size: 72,
color: theme.colorScheme.primary,
),
const SizedBox(height: 16),
Text(
l10n.todayAllDone,
style: theme.textTheme.headlineSmall,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
nextDue != null
? l10n.todayNextTask(
DateFormat('EEEE, d. MMMM', locale).format(nextDue))
: l10n.todayEmptyHint,
style: theme.textTheme.bodyLarge,
textAlign: TextAlign.center,
),
],
),
),
);
}
}