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/presentation/daily_plan_providers.dart'; import 'package:household_keeper/features/home/presentation/daily_plan_task_row.dart'; import 'package:household_keeper/features/home/presentation/progress_card.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); /// The app's primary screen: daily plan showing what's due today, /// overdue tasks, and a preview of tomorrow. /// /// Replaces the former placeholder with a full daily workflow: /// see what's due, check it off, feel progress. class HomeScreen extends ConsumerStatefulWidget { const HomeScreen({super.key}); @override ConsumerState createState() => _HomeScreenState(); } class _HomeScreenState 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 dailyPlan = ref.watch(dailyPlanProvider); return dailyPlan.when( loading: () => const Center(child: CircularProgressIndicator()), error: (error, _) => Center(child: Text(error.toString())), data: (state) { // Clean up completing IDs that are no longer in the data _completingTaskIds.removeWhere((id) => !state.overdueTasks.any((t) => t.task.id == id) && !state.todayTasks.any((t) => t.task.id == id)); return _buildDailyPlan(context, state, l10n, theme); }, ); } Widget _buildDailyPlan( BuildContext context, DailyPlanState state, AppLocalizations l10n, ThemeData theme, ) { // Case a: No tasks at all (user hasn't created any rooms/tasks) if (state.totalTodayCount == 0 && state.tomorrowTasks.isEmpty && state.completedTodayCount == 0) { return _buildNoTasksState(l10n, theme); } // Case b: All clear -- there WERE tasks today but all are done if (state.overdueTasks.isEmpty && state.todayTasks.isEmpty && state.completedTodayCount > 0 && state.tomorrowTasks.isEmpty) { return _buildAllClearState(state, l10n, theme); } // Case c: Nothing today, but stuff tomorrow -- show celebration + tomorrow if (state.overdueTasks.isEmpty && state.todayTasks.isEmpty && state.completedTodayCount == 0 && state.tomorrowTasks.isNotEmpty) { return _buildAllClearWithTomorrow(state, l10n, theme); } // Case b extended: all clear with tomorrow tasks if (state.overdueTasks.isEmpty && state.todayTasks.isEmpty && state.completedTodayCount > 0 && state.tomorrowTasks.isNotEmpty) { return _buildAllClearWithTomorrow(state, l10n, theme); } // Case d: Normal state -- tasks exist return _buildNormalState(state, l10n, theme); } /// No tasks at all -- first-run empty state. Widget _buildNoTasksState(AppLocalizations l10n, ThemeData theme) { 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), ), ], ), ), ); } /// All tasks done, no tomorrow tasks -- celebration state. Widget _buildAllClearState( DailyPlanState state, AppLocalizations l10n, ThemeData theme, ) { return Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 32), child: Column( mainAxisSize: MainAxisSize.min, children: [ ProgressCard( completed: state.completedTodayCount, total: state.totalTodayCount, ), const SizedBox(height: 16), 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, ), ], ), ), ); } /// All clear for today but tomorrow tasks exist. Widget _buildAllClearWithTomorrow( DailyPlanState state, AppLocalizations l10n, ThemeData theme, ) { return ListView( children: [ ProgressCard( completed: state.completedTodayCount, total: state.totalTodayCount, ), const SizedBox(height: 16), Center( child: Column( 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, ), ], ), ), const SizedBox(height: 16), _buildTomorrowSection(state, l10n, theme), ], ); } /// Normal state with overdue/today/tomorrow sections. Widget _buildNormalState( DailyPlanState state, AppLocalizations l10n, ThemeData theme, ) { return ListView( children: [ ProgressCard( completed: state.completedTodayCount, total: state.totalTodayCount, ), // Overdue section (conditional) if (state.overdueTasks.isNotEmpty) ...[ _buildSectionHeader( l10n.dailyPlanSectionOverdue, theme, color: _overdueColor, ), ...state.overdueTasks.map( (tw) => _buildAnimatedTaskRow(tw, showCheckbox: true), ), ], // Today section _buildSectionHeader( l10n.dailyPlanSectionToday, theme, color: theme.colorScheme.primary, ), if (state.todayTasks.isEmpty) Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text( l10n.dailyPlanAllClearMessage, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), ) else ...state.todayTasks.map( (tw) => _buildAnimatedTaskRow(tw, showCheckbox: true), ), // Tomorrow section (conditional, collapsed) if (state.tomorrowTasks.isNotEmpty) _buildTomorrowSection(state, l10n, theme), ], ); } 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 showCheckbox, }) { final isCompleting = _completingTaskIds.contains(tw.task.id); if (isCompleting) { return _CompletingTaskRow( key: ValueKey('completing-${tw.task.id}'), taskWithRoom: tw, ); } return DailyPlanTaskRow( key: ValueKey('task-${tw.task.id}'), taskWithRoom: tw, showCheckbox: showCheckbox, onCompleted: () => _onTaskCompleted(tw.task.id), ); } Widget _buildTomorrowSection( DailyPlanState state, AppLocalizations l10n, ThemeData theme, ) { return ExpansionTile( initiallyExpanded: false, title: Text( l10n.dailyPlanUpcomingCount(state.tomorrowTasks.length), style: theme.textTheme.titleMedium, ), children: state.tomorrowTasks .map( (tw) => DailyPlanTaskRow( key: ValueKey('tomorrow-${tw.task.id}'), taskWithRoom: tw, showCheckbox: false, ), ) .toList(), ); } } /// A task row that animates to zero height on completion. class _CompletingTaskRow extends StatefulWidget { const _CompletingTaskRow({ super.key, required this.taskWithRoom, }); final TaskWithRoom taskWithRoom; @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: DailyPlanTaskRow( taskWithRoom: widget.taskWithRoom, showCheckbox: true, ), ), ); } }