import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:lucide_icons/lucide_icons.dart'; import '../../../../app/di/injection.dart'; import '../../../../app/router/app_routes.dart'; import '../../../../core/l10n/l10n.dart'; import '../../../../shared/widgets/back_title_page_header.dart'; import '../../../../shared/widgets/detail_header_action_menu.dart'; import '../../../../shared/widgets/destructive_action_sheet.dart'; import '../../../../shared/widgets/error_retry_surface.dart'; import '../../../../shared/widgets/full_screen_loading.dart'; import '../../../../shared/widgets/toast/toast.dart'; import '../../../../shared/widgets/toast/toast_type.dart'; import '../../data/apis/todo_api.dart'; enum _TodoHeaderAction { edit, delete } class TodoDetailScreen extends StatefulWidget { final String todoId; const TodoDetailScreen({super.key, required this.todoId}); @override State createState() => _TodoDetailScreenState(); } class _TodoDetailScreenState extends State { final TodoApi _todoApi = sl(); TodoResponse? _todo; bool _isLoading = true; bool _didMutate = false; String? _error; @override void initState() { super.initState(); _loadTodo(); } Future _loadTodo() async { if (!mounted) { return; } setState(() { _isLoading = true; _error = null; }); try { final todo = await _todoApi.getTodo(widget.todoId); if (!mounted) { return; } setState(() { _todo = todo; _isLoading = false; }); } catch (e) { if (!mounted) { return; } setState(() { _error = e.toString(); _isLoading = false; }); } } String _getPriorityLabel(int priority) { switch (priority) { case 1: return context.l10n.todoQuadrantImportantUrgent; case 2: return context.l10n.todoQuadrantImportantNotUrgent; case 3: return context.l10n.todoQuadrantUrgentNotImportant; case 4: return context.l10n.todoQuadrantNotUrgentNotImportant; default: return context.l10n.commonUnknown; } } Color _getPriorityColor(int priority, ColorScheme colorScheme) { switch (priority) { case 1: return colorScheme.error; case 2: return colorScheme.tertiary; case 3: return colorScheme.primary; default: return colorScheme.onSurfaceVariant; } } @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Scaffold( backgroundColor: colorScheme.surface, body: SafeArea( child: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [colorScheme.surfaceContainerHigh, colorScheme.surface], ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildHeader(), Expanded(child: _buildContent()), ], ), ), ), ); } Widget _buildHeader() { return BackTitlePageHeader( title: context.l10n.todoDetailTitle, onBack: () => context.pop(_didMutate), trailing: _buildHeaderMenu(), ); } Widget? _buildHeaderMenu() { if (_todo == null) { return null; } return DetailHeaderActionMenu<_TodoHeaderAction>( items: [ DetailHeaderActionItem<_TodoHeaderAction>( value: _TodoHeaderAction.edit, label: context.l10n.commonEdit, icon: LucideIcons.pencil, ), DetailHeaderActionItem<_TodoHeaderAction>( value: _TodoHeaderAction.delete, label: context.l10n.commonDelete, icon: LucideIcons.trash2, isDestructive: true, ), ], onSelected: _handleHeaderAction, ); } void _handleHeaderAction(_TodoHeaderAction action) { switch (action) { case _TodoHeaderAction.edit: _editTodo(); return; case _TodoHeaderAction.delete: _deleteTodo(); return; } } Widget _buildContent() { final colorScheme = Theme.of(context).colorScheme; if (_isLoading) { return const FullScreenLoading(); } if (_error != null) { return ErrorRetrySurface( message: context.l10n.commonLoadFailed(_error!), onRetry: _loadTodo, ); } if (_todo == null) { return Center(child: Text(context.l10n.todoNotFound)); } return Padding( padding: const EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 20), child: ListView( children: [ _buildMainCard(), const SizedBox(height: 12), if (_todo!.scheduleItems.isNotEmpty) ...[ Text( context.l10n.todoCalendarEventCards, style: TextStyle( fontFamily: 'Inter', fontSize: 12, fontWeight: FontWeight.w500, color: colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 10), ..._todo!.scheduleItems.map( (item) => _buildEventCard( id: item.id, title: item.title, time: _formatEventTime(item.startAt, item.endAt), borderColor: colorScheme.outlineVariant, onTap: () => context.push(AppRoutes.calendarEventDetail(item.id)), ), ), ], ], ), ); } String _formatEventTime(DateTime start, DateTime? end) { final startStr = DateFormat.yMd( context.l10n.localeName, ).add_Hm().format(start); if (end != null) { final endStr = '${end.hour.toString().padLeft(2, '0')}:${end.minute.toString().padLeft(2, '0')}'; return '$startStr - $endStr'; } return startStr; } Widget _buildMainCard() { final colorScheme = Theme.of(context).colorScheme; return Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: colorScheme.surface, borderRadius: BorderRadius.circular(14), border: Border.all(color: colorScheme.outlineVariant, width: 1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _todo!.title, style: TextStyle( fontFamily: 'Inter', fontSize: 18, fontWeight: FontWeight.w700, color: colorScheme.onSurface, ), ), const SizedBox(height: 4), Text( _buildSubtitle(), style: TextStyle( fontFamily: 'Inter', fontSize: 12, fontWeight: FontWeight.w500, color: colorScheme.onSurfaceVariant, ), ), if (_todo!.description != null && _todo!.description!.isNotEmpty) ...[ const SizedBox(height: 8), Text( _todo!.description!, style: TextStyle( fontFamily: 'Inter', fontSize: 13, color: colorScheme.onSurfaceVariant, ), ), ], const SizedBox(height: 8), Container(height: 1, color: colorScheme.outlineVariant), const SizedBox(height: 8), _buildInfoRow( label: context.l10n.todoPriorityQuadrant, value: _getPriorityLabel(_todo!.priority), valueColor: _getPriorityColor(_todo!.priority, colorScheme), ), const SizedBox(height: 8), _buildInfoRow( label: context.l10n.todoLinkedCalendarEvents, value: context.l10n.todoItemCount(_todo!.scheduleItems.length), valueColor: colorScheme.tertiary, ), const SizedBox(height: 8), _buildInfoRow( label: context.l10n.todoStatus, value: _todo!.status == 'done' ? context.l10n.todoStatusDone : context.l10n.todoStatusInProgress, valueColor: _todo!.status == 'done' ? colorScheme.tertiary : colorScheme.primary, ), ], ), ); } String _buildSubtitle() { final parts = []; parts.add(context.l10n.todoQuadrantOrder(_todo!.order + 1)); if (_todo!.scheduleItems.isNotEmpty) { parts.add(context.l10n.todoSplitToEvents(_todo!.scheduleItems.length)); } else { parts.add(context.l10n.todoNoLinkedEvents); } return parts.join(' ยท '); } Widget _buildInfoRow({ required String label, required String value, required Color valueColor, }) { final colorScheme = Theme.of(context).colorScheme; return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: TextStyle( fontFamily: 'Inter', fontSize: 12, fontWeight: FontWeight.w600, color: colorScheme.outline, ), ), Text( value, style: TextStyle( fontFamily: 'Inter', fontSize: 12, fontWeight: FontWeight.w700, color: valueColor, ), ), ], ); } Widget _buildEventCard({ required String id, required String title, required String time, required Color borderColor, VoidCallback? onTap, }) { final colorScheme = Theme.of(context).colorScheme; return GestureDetector( onTap: onTap, child: Container( width: double.infinity, padding: const EdgeInsets.all(10), margin: const EdgeInsets.only(bottom: 10), decoration: BoxDecoration( color: colorScheme.surface, borderRadius: BorderRadius.circular(14), border: Border.all(color: borderColor, width: 1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontFamily: 'Inter', fontSize: 13, fontWeight: FontWeight.w600, color: colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 8), Text( time, style: TextStyle( fontFamily: 'Inter', fontSize: 12, fontWeight: FontWeight.w500, color: colorScheme.onSurfaceVariant, ), ), ], ), ), ); } void _editTodo() async { if (_todo == null) { return; } final changed = await context.push(AppRoutes.todoEdit(_todo!.id)); if (changed == true) { _didMutate = true; if (!mounted) { return; } context.pop(true); } } void _deleteTodo() async { final confirm = await showDestructiveActionSheet( context, title: context.l10n.todoDeleteTitle, message: context.l10n.todoDeleteMessage, confirmText: context.l10n.todoDeleteConfirm, ); if (confirm == true) { try { await _todoApi.deleteTodo(_todo!.id); if (mounted) { context.pop(true); } } catch (e) { if (mounted) { Toast.show( context, context.l10n.todoDeleteFailed(e.toString()), type: ToastType.error, ); } } } } }