leafittome/lib/features/today/presentation/today_screen.dart
cschlaefke 6e22d9c3aa Projekt-Gerüst: Flutter-App mit Heute-Checkliste, Pflanzenverwaltung, Stellplätzen und Einstellungen
- Flutter/Dart (iOS + Android), Riverpod, go_router, i18n via ARB (deutsch)
- Feature-Struktur: today, plants, locations, household (V2-Platzhalter), settings
- Intervall-Modell: Aufgaben werden aus lastWatered/lastFertilized + Intervall berechnet
- In-Memory-Demo-Daten, Firebase-Anbindung folgt im nächsten Block
- Doku: Architektur, Firebase-Einrichtung, Forgejo-Git-Hosting

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

176 lines
5.4 KiB
Dart

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 '../../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);
return Scaffold(
appBar: AppBar(title: Text(l10n.todayTitle)),
drawer: const AppDrawer(),
floatingActionButton: FloatingActionButton.extended(
onPressed: () => context.push(AppRoutes.plantAdd),
icon: const Icon(Icons.add),
label: Text(l10n.addPlant),
),
body: tasks.isEmpty ? const _AllDoneView() : _TaskListView(tasks: tasks),
);
}
}
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);
return ListView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 96),
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text(
l10n.todayOpenTasks(tasks.length),
style: Theme.of(context).textTheme.titleMedium,
),
),
for (final task in tasks) _TaskCard(task: task),
],
);
}
}
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(plantsProvider.notifier)
.confirmTask(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,
),
],
),
),
);
}
}