From 9f902ff2c7b39b4f939eaa77eab707b228d202a9 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Mon, 16 Mar 2026 21:57:11 +0100 Subject: [PATCH] feat(06-01): build task history sheet, wire into TaskFormScreen, add CalendarTaskRow navigation - Create task_history_sheet.dart: showTaskHistorySheet() modal bottom sheet - Sheet uses StreamBuilder on watchCompletionsForTask, shows dates in dd.MM.yyyy + HH:mm format - Empty state: Icons.history + 'Noch nie erledigt' message - Count summary shown above list when completions exist - Add Verlauf ListTile to TaskFormScreen (edit mode only) opening history sheet - Add onTap to CalendarTaskRow navigating to /rooms/:roomId/tasks/:taskId - All 106 tests pass, zero analyze issues --- .../home/presentation/calendar_task_row.dart | 3 + .../tasks/presentation/task_form_screen.dart | 16 +++ .../presentation/task_history_sheet.dart | 136 ++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 lib/features/tasks/presentation/task_history_sheet.dart diff --git a/lib/features/home/presentation/calendar_task_row.dart b/lib/features/home/presentation/calendar_task_row.dart index daa9ac3..f80725b 100644 --- a/lib/features/home/presentation/calendar_task_row.dart +++ b/lib/features/home/presentation/calendar_task_row.dart @@ -36,6 +36,9 @@ class CalendarTaskRow extends StatelessWidget { final task = taskWithRoom.task; return ListTile( + onTap: () => context.go( + '/rooms/${taskWithRoom.roomId}/tasks/${taskWithRoom.task.id}', + ), leading: Checkbox( value: false, onChanged: (_) => onCompleted(), diff --git a/lib/features/tasks/presentation/task_form_screen.dart b/lib/features/tasks/presentation/task_form_screen.dart index 991b978..392bb70 100644 --- a/lib/features/tasks/presentation/task_form_screen.dart +++ b/lib/features/tasks/presentation/task_form_screen.dart @@ -10,6 +10,7 @@ import '../../../core/providers/database_provider.dart'; import '../../../l10n/app_localizations.dart'; import '../domain/effort_level.dart'; import '../domain/frequency.dart'; +import 'task_history_sheet.dart'; import 'task_providers.dart'; /// Full-screen form for task creation and editing. @@ -186,6 +187,21 @@ class _TaskFormScreenState extends ConsumerState { ), const SizedBox(height: 8), _buildDueDatePicker(theme), + + // History section (edit mode only) + if (widget.isEditing) ...[ + const SizedBox(height: 24), + const Divider(), + ListTile( + leading: const Icon(Icons.history), + title: Text(l10n.taskHistoryTitle), + trailing: const Icon(Icons.chevron_right), + onTap: () => showTaskHistorySheet( + context: context, + taskId: widget.taskId!, + ), + ), + ], ], ), ), diff --git a/lib/features/tasks/presentation/task_history_sheet.dart b/lib/features/tasks/presentation/task_history_sheet.dart new file mode 100644 index 0000000..402b384 --- /dev/null +++ b/lib/features/tasks/presentation/task_history_sheet.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; + +import '../../../core/database/database.dart'; +import '../../../core/providers/database_provider.dart'; +import '../../../l10n/app_localizations.dart'; + +/// Shows a modal bottom sheet displaying the completion history for a task. +/// +/// The sheet displays all past completions in reverse-chronological order +/// (newest first). If the task has never been completed, an empty state is shown. +Future showTaskHistorySheet({ + required BuildContext context, + required int taskId, +}) { + return showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => _TaskHistorySheet(taskId: taskId), + ); +} + +class _TaskHistorySheet extends ConsumerWidget { + const _TaskHistorySheet({required this.taskId}); + + final int taskId; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + final l10n = AppLocalizations.of(context); + + return SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Drag handle + Container( + width: 32, + height: 4, + margin: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: colorScheme.onSurfaceVariant.withValues(alpha: 0.4), + borderRadius: BorderRadius.circular(2), + ), + ), + // Title + Text( + l10n.taskHistoryTitle, + style: theme.textTheme.titleMedium, + ), + const SizedBox(height: 16), + // Completion list via StreamBuilder + StreamBuilder>( + stream: ref + .read(appDatabaseProvider) + .tasksDao + .watchCompletionsForTask(taskId), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const Center(child: CircularProgressIndicator()); + } + + final completions = snapshot.data!; + + if (completions.isEmpty) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.history, + size: 48, + color: colorScheme.onSurfaceVariant, + ), + const SizedBox(height: 8), + Text( + l10n.taskHistoryEmpty, + style: theme.textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ); + } + + // Show count summary + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + l10n.taskHistoryCount(completions.length), + style: theme.textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + const SizedBox(height: 8), + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: + MediaQuery.of(context).size.height * 0.4, + ), + child: ListView.builder( + shrinkWrap: true, + itemCount: completions.length, + itemBuilder: (context, index) { + final completion = completions[index]; + final dateStr = DateFormat('dd.MM.yyyy', 'de') + .format(completion.completedAt); + final timeStr = DateFormat('HH:mm', 'de') + .format(completion.completedAt); + return ListTile( + leading: Icon( + Icons.check_circle_outline, + color: colorScheme.primary, + ), + title: Text(dateStr), + subtitle: Text(timeStr), + ); + }, + ), + ), + ], + ); + }, + ), + const SizedBox(height: 8), + ], + ), + ), + ); + } +}