import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:lucide_icons/lucide_icons.dart'; import '../../../../core/di/injection.dart'; import '../../../../core/router/app_routes.dart'; import '../../../../core/theme/design_tokens.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/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; 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 '重要紧急'; case 2: return '重要不紧急'; case 3: return '紧急不重要'; case 4: return '不紧急不重要'; default: return '未知'; } } Color _getPriorityColor(int priority) { switch (priority) { case 1: return AppColors.g1Text; case 2: return AppColors.g3Text; case 3: return AppColors.g2Text; default: return AppColors.slate500; } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.todoBg, body: SafeArea( child: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [AppColors.homeBackgroundTop, AppColors.todoBg], ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildHeader(), Expanded(child: _buildContent()), ], ), ), ), ); } Widget _buildHeader() { return BackTitlePageHeader( title: '待办详情', onBack: () => context.pop(), trailing: _buildHeaderMenu(), ); } Widget? _buildHeaderMenu() { if (_todo == null) { return null; } return DetailHeaderActionMenu<_TodoHeaderAction>( items: const [ DetailHeaderActionItem<_TodoHeaderAction>( value: _TodoHeaderAction.edit, label: '编辑', icon: LucideIcons.pencil, ), DetailHeaderActionItem<_TodoHeaderAction>( value: _TodoHeaderAction.delete, label: '删除', 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() { if (_isLoading) { return const FullScreenLoading(); } if (_error != null) { return ErrorRetrySurface(message: '加载失败: $_error', onRetry: _loadTodo); } if (_todo == null) { return const Center(child: Text('待办不存在')); } 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( '日历事件卡片', style: TextStyle( fontFamily: 'Inter', fontSize: 12, fontWeight: FontWeight.w500, color: AppColors.slate500, ), ), const SizedBox(height: 10), ..._todo!.scheduleItems.map( (item) => _buildEventCard( id: item.id, title: item.title, time: _formatEventTime(item.startAt, item.endAt), borderColor: AppColors.todoEventBorder1, onTap: () => context.push(AppRoutes.calendarEventDetail(item.id)), ), ), ], ], ), ); } String _formatEventTime(DateTime start, DateTime? end) { final startStr = '${start.year}年${start.month}月${start.day}日 ${start.hour.toString().padLeft(2, '0')}:${start.minute.toString().padLeft(2, '0')}'; if (end != null) { final endStr = '${end.hour.toString().padLeft(2, '0')}:${end.minute.toString().padLeft(2, '0')}'; return '$startStr - $endStr'; } return startStr; } Widget _buildMainCard() { return Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.todoCardBg, borderRadius: BorderRadius.circular(14), border: Border.all(color: AppColors.todoDetailCardBorder, width: 1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _todo!.title, style: const TextStyle( fontFamily: 'Inter', fontSize: 18, fontWeight: FontWeight.w700, color: AppColors.slate900, ), ), const SizedBox(height: 4), Text( _buildSubtitle(), style: const TextStyle( fontFamily: 'Inter', fontSize: 12, fontWeight: FontWeight.w500, color: AppColors.slate500, ), ), if (_todo!.description != null && _todo!.description!.isNotEmpty) ...[ const SizedBox(height: 8), Text( _todo!.description!, style: const TextStyle( fontFamily: 'Inter', fontSize: 13, color: AppColors.slate600, ), ), ], const SizedBox(height: 8), Container(height: 1, color: AppColors.border), const SizedBox(height: 8), _buildInfoRow( label: '所属象限', value: _getPriorityLabel(_todo!.priority), valueColor: _getPriorityColor(_todo!.priority), ), const SizedBox(height: 8), _buildInfoRow( label: '关联日历事件', value: '${_todo!.scheduleItems.length}个', valueColor: AppColors.g3Text, ), const SizedBox(height: 8), _buildInfoRow( label: '状态', value: _todo!.status == 'done' ? '已完成' : '进行中', valueColor: _todo!.status == 'done' ? AppColors.success : AppColors.blue600, ), ], ), ); } String _buildSubtitle() { final parts = []; if (_todo!.dueAt != null) { final due = _todo!.dueAt!; parts.add( '截止 ${due.month}月${due.day}日 ${due.hour.toString().padLeft(2, '0')}:${due.minute.toString().padLeft(2, '0')}', ); } if (_todo!.scheduleItems.isNotEmpty) { parts.add('已拆分为${_todo!.scheduleItems.length}个日历事件'); } else { parts.add('未关联日历事件'); } return parts.join(' · '); } Widget _buildInfoRow({ required String label, required String value, required Color valueColor, }) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: const TextStyle( fontFamily: 'Inter', fontSize: 12, fontWeight: FontWeight.w600, color: AppColors.slate400, ), ), 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, }) { return GestureDetector( onTap: onTap, child: Container( width: double.infinity, padding: const EdgeInsets.all(10), margin: const EdgeInsets.only(bottom: 10), decoration: BoxDecoration( color: AppColors.todoCardBg, borderRadius: BorderRadius.circular(14), border: Border.all(color: borderColor, width: 1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontFamily: 'Inter', fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.slate700, ), ), const SizedBox(height: 8), Text( time, style: const TextStyle( fontFamily: 'Inter', fontSize: 12, fontWeight: FontWeight.w500, color: AppColors.slate500, ), ), ], ), ), ); } void _editTodo() async { if (_todo == null) { return; } final changed = await context.push(AppRoutes.todoEdit(_todo!.id)); if (changed == true) { await _loadTodo(); if (mounted && _error != null) { Toast.show(context, '刷新失败: $_error', type: ToastType.error); } } } void _deleteTodo() async { final confirm = await showDestructiveActionSheet( context, title: '删除待办', message: '确定要删除这个待办吗?', confirmText: '确认删除', ); if (confirm == true) { try { await _todoApi.deleteTodo(_todo!.id); if (mounted) { context.pop(); } } catch (e) { if (mounted) { Toast.show(context, '删除失败: $e', type: ToastType.error); } } } } }