import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:household_keeper/features/home/domain/daily_plan_models.dart'; import 'package:household_keeper/features/home/domain/calendar_models.dart'; import 'package:household_keeper/features/home/presentation/calendar_providers.dart'; import 'package:household_keeper/features/home/presentation/calendar_task_row.dart'; import 'package:household_keeper/features/tasks/presentation/task_providers.dart'; import 'package:household_keeper/l10n/app_localizations.dart'; /// Warm coral/terracotta color for overdue section header. const _overdueColor = Color(0xFFE07A5F); /// Shows the task list for the selected calendar day. /// /// Watches [calendarDayProvider] (or [roomCalendarDayProvider] when [roomId] /// is provided) and renders one of several states: /// - Loading spinner while data loads /// - Error text on failure /// - First-run empty state (no rooms/tasks at all) — prompts to create a room /// - Empty day state (tasks exist elsewhere but not this day) /// - Celebration state (today is selected and all tasks are done) /// - Has-tasks state with optional overdue section (today only) and checkboxes class CalendarDayList extends ConsumerStatefulWidget { const CalendarDayList({super.key, this.roomId}); /// When non-null, filters tasks to this room only. final int? roomId; @override ConsumerState createState() => _CalendarDayListState(); } class _CalendarDayListState extends ConsumerState { /// Task IDs currently animating out after completion. final Set _completingTaskIds = {}; void _onTaskCompleted(int taskId) { setState(() { _completingTaskIds.add(taskId); }); ref.read(taskActionsProvider.notifier).completeTask(taskId); } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); final theme = Theme.of(context); final dayState = widget.roomId != null ? ref.watch(roomCalendarDayProvider(widget.roomId!)) : ref.watch(calendarDayProvider); return dayState.when( loading: () => const Center(child: CircularProgressIndicator()), error: (error, _) => Center(child: Text(error.toString())), data: (state) { // Clean up animation IDs for tasks that are no longer in the data. _completingTaskIds.removeWhere((id) => !state.overdueTasks.any((t) => t.task.id == id) && !state.dayTasks.any((t) => t.task.id == id)); return _buildContent(context, state, l10n, theme); }, ); } Widget _buildContent( BuildContext context, CalendarDayState state, AppLocalizations l10n, ThemeData theme, ) { final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); final isToday = state.selectedDate == today; // State (a): First-run empty — no tasks exist at all in the database. if (state.isEmpty && state.totalTaskCount == 0) { return _buildFirstRunEmpty(context, l10n, theme); } // State (e): Celebration — today is selected and all tasks are done // (totalTaskCount > 0 so at least some task exists somewhere, but today // has none remaining after completion). if (isToday && state.dayTasks.isEmpty && state.overdueTasks.isEmpty && state.totalTaskCount > 0) { return _buildCelebration(l10n, theme); } // State (d): Empty day — tasks exist elsewhere but not this day. if (state.isEmpty) { return _buildEmptyDay(theme); } // State (f): Has tasks — render overdue section (today only) + day tasks. return _buildTaskList(state, l10n, theme); } /// First-run: no rooms/tasks created yet. Widget _buildFirstRunEmpty( BuildContext context, AppLocalizations l10n, ThemeData theme, ) { // Room-scoped: prompt to create a task in this room. if (widget.roomId != null) { return Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 32), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.task_alt, size: 80, color: theme.colorScheme.onSurface.withValues(alpha: 0.4), ), const SizedBox(height: 24), Text( l10n.taskEmptyTitle, style: theme.textTheme.headlineSmall, textAlign: TextAlign.center, ), const SizedBox(height: 8), Text( l10n.taskEmptyMessage, style: theme.textTheme.bodyLarge?.copyWith( color: theme.colorScheme.onSurface.withValues(alpha: 0.6), ), textAlign: TextAlign.center, ), const SizedBox(height: 24), FilledButton.tonal( onPressed: () => context.go('/rooms/${widget.roomId}/tasks/new'), child: Text(l10n.taskEmptyAction), ), ], ), ), ); } // Home-screen: prompt to create a room. return Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 32), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.checklist_rounded, size: 80, color: theme.colorScheme.onSurface.withValues(alpha: 0.4), ), const SizedBox(height: 24), Text( l10n.dailyPlanNoTasks, style: theme.textTheme.headlineSmall, textAlign: TextAlign.center, ), const SizedBox(height: 8), Text( l10n.homeEmptyMessage, style: theme.textTheme.bodyLarge?.copyWith( color: theme.colorScheme.onSurface.withValues(alpha: 0.6), ), textAlign: TextAlign.center, ), const SizedBox(height: 24), FilledButton.tonal( onPressed: () => context.go('/rooms'), child: Text(l10n.homeEmptyAction), ), ], ), ), ); } /// Celebration state: today is selected and all tasks are done. Widget _buildCelebration(AppLocalizations l10n, ThemeData theme) { return Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 32), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.celebration_outlined, size: 80, color: theme.colorScheme.onSurface.withValues(alpha: 0.4), ), const SizedBox(height: 24), Text( l10n.dailyPlanAllClearTitle, style: theme.textTheme.headlineSmall, textAlign: TextAlign.center, ), const SizedBox(height: 8), Text( l10n.dailyPlanAllClearMessage, style: theme.textTheme.bodyLarge?.copyWith( color: theme.colorScheme.onSurface.withValues(alpha: 0.6), ), textAlign: TextAlign.center, ), ], ), ), ); } /// Empty day: tasks exist elsewhere but nothing scheduled for this day. Widget _buildEmptyDay(ThemeData theme) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.event_available, size: 48, color: theme.colorScheme.onSurface.withValues(alpha: 0.3), ), const SizedBox(height: 12), Text( 'Keine Aufgaben', style: theme.textTheme.bodyLarge?.copyWith( color: theme.colorScheme.onSurface.withValues(alpha: 0.5), ), ), ], ), ); } /// Task list with optional overdue section. Widget _buildTaskList( CalendarDayState state, AppLocalizations l10n, ThemeData theme, ) { final showRoomTag = widget.roomId == null; final items = []; // Overdue section (today only, when overdue tasks exist). // Overdue tasks are always completable (they're past due, only shown on today). if (state.overdueTasks.isNotEmpty) { items.add(_buildSectionHeader(l10n.dailyPlanSectionOverdue, theme, color: _overdueColor)); for (final tw in state.overdueTasks) { items.add(_buildAnimatedTaskRow( tw, isOverdue: true, showRoomTag: showRoomTag, canComplete: true, )); } } // Day tasks section. for (final tw in state.dayTasks) { items.add(_buildAnimatedTaskRow( tw, isOverdue: false, showRoomTag: showRoomTag, canComplete: true, )); } return ListView(children: items); } Widget _buildSectionHeader( String title, ThemeData theme, { required Color color, }) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text( title, style: theme.textTheme.titleMedium?.copyWith(color: color), ), ); } Widget _buildAnimatedTaskRow( TaskWithRoom tw, { required bool isOverdue, required bool showRoomTag, required bool canComplete, }) { final isCompleting = _completingTaskIds.contains(tw.task.id); if (isCompleting) { return _CompletingTaskRow( key: ValueKey('completing-${tw.task.id}'), taskWithRoom: tw, isOverdue: isOverdue, showRoomTag: showRoomTag, ); } return CalendarTaskRow( key: ValueKey('task-${tw.task.id}'), taskWithRoom: tw, isOverdue: isOverdue, showRoomTag: showRoomTag, canComplete: canComplete, onCompleted: () => _onTaskCompleted(tw.task.id), ); } } /// A task row that animates (slide + size) to zero height on completion. class _CompletingTaskRow extends StatefulWidget { const _CompletingTaskRow({ super.key, required this.taskWithRoom, required this.isOverdue, required this.showRoomTag, }); final TaskWithRoom taskWithRoom; final bool isOverdue; final bool showRoomTag; @override State<_CompletingTaskRow> createState() => _CompletingTaskRowState(); } class _CompletingTaskRowState extends State<_CompletingTaskRow> with SingleTickerProviderStateMixin { late final AnimationController _controller; late final Animation _sizeAnimation; late final Animation _slideAnimation; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _sizeAnimation = Tween(begin: 1.0, end: 0.0).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOut), ); _slideAnimation = Tween( begin: Offset.zero, end: const Offset(1.0, 0.0), ).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOut), ); _controller.forward(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SizeTransition( sizeFactor: _sizeAnimation, child: SlideTransition( position: _slideAnimation, child: CalendarTaskRow( taskWithRoom: widget.taskWithRoom, isOverdue: widget.isOverdue, showRoomTag: widget.showRoomTag, onCompleted: () {}, // Already completing — ignore repeat taps. ), ), ); } }