import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../models/writing_task.dart'; import '../providers/writing_provider.dart'; import 'writing_detail_screen.dart'; /// 考试写作页面 class ExamWritingScreen extends ConsumerStatefulWidget { final ExamType? examType; final String title; const ExamWritingScreen({ super.key, this.examType, required this.title, }); @override ConsumerState createState() => _ExamWritingScreenState(); } class _ExamWritingScreenState extends ConsumerState { ExamType? selectedExamType; @override void initState() { super.initState(); selectedExamType = widget.examType; } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF5F5F5), appBar: AppBar( title: Text(widget.title), backgroundColor: Colors.white, foregroundColor: Colors.black, elevation: 0, ), body: Column( children: [ if (widget.examType == null) _buildExamTypeFilter(), Expanded( child: Builder( builder: (context) { if (selectedExamType != null) { final tasksAsync = ref.watch(examWritingTasksProvider(selectedExamType!)); return tasksAsync.when( data: (tasks) { if (tasks.isEmpty) return _buildEmptyState(); return ListView.builder( padding: const EdgeInsets.all(16), itemCount: tasks.length, itemBuilder: (context, index) { final task = tasks[index]; return _buildTaskCard(task); }, ); }, loading: () => const Center(child: CircularProgressIndicator()), error: (e, st) => Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 48, color: Colors.grey[400]), const SizedBox(height: 12), Text('加载失败', style: TextStyle(fontSize: 14, color: Colors.grey[600])), const SizedBox(height: 8), TextButton( onPressed: () { ref.invalidate(examWritingTasksProvider(selectedExamType!)); }, child: const Text('重试'), ), ], ), ), ); } else { final service = ref.watch(writingServiceProvider); return FutureBuilder>( future: service.getWritingTasks(limit: 100), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 48, color: Colors.grey[400]), const SizedBox(height: 12), Text('加载失败', style: TextStyle(fontSize: 14, color: Colors.grey[600])), const SizedBox(height: 8), TextButton( onPressed: () { setState(() {}); }, child: const Text('重试'), ), ], ), ); } final allTasks = snapshot.data ?? []; final examTasks = allTasks.where((t) => t.examType != null).toList(); if (examTasks.isEmpty) return _buildEmptyState(); return ListView.builder( padding: const EdgeInsets.all(16), itemCount: examTasks.length, itemBuilder: (context, index) { final task = examTasks[index]; return _buildTaskCard(task); }, ); }, ); } }, ), ), ], ), ); } Widget _buildExamTypeFilter() { return Container( padding: const EdgeInsets.all(16), color: Colors.white, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '选择考试类型', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 12), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _buildFilterChip('全部', null), const SizedBox(width: 8), ...ExamType.values.map((type) => Padding( padding: const EdgeInsets.only(right: 8), child: _buildFilterChip(type.displayName, type), )), ], ), ), ], ), ); } Widget _buildFilterChip(String label, ExamType? type) { final isSelected = selectedExamType == type; return FilterChip( label: Text(label), selected: isSelected, onSelected: (selected) { setState(() { selectedExamType = type; }); }, backgroundColor: Colors.grey[100], selectedColor: const Color(0xFF2196F3).withOpacity(0.2), checkmarkColor: const Color(0xFF2196F3), labelStyle: TextStyle( color: isSelected ? const Color(0xFF2196F3) : Colors.grey[700], fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), ); } Widget _buildEmptyState() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.school_outlined, size: 80, color: Colors.grey[400], ), const SizedBox(height: 16), Text( '暂无考试写作题目', style: TextStyle( fontSize: 18, color: Colors.grey[600], fontWeight: FontWeight.w500, ), ), const SizedBox(height: 8), Text( '请选择其他考试类型或稍后再试', style: TextStyle( fontSize: 14, color: Colors.grey[500], ), ), ], ), ); } Widget _buildTaskCard(WritingTask task) { return Container( margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: InkWell( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => WritingDetailScreen(task: task), ), ); }, borderRadius: BorderRadius.circular(16), child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( task.title, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _getExamTypeColor(task.examType!), borderRadius: BorderRadius.circular(12), ), child: Text( task.examType!.displayName, style: const TextStyle( fontSize: 12, color: Colors.white, fontWeight: FontWeight.bold, ), ), ), ], ), const SizedBox(height: 8), Text( task.description, style: const TextStyle( fontSize: 14, color: Colors.grey, height: 1.4, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 12), Row( children: [ _buildInfoChip( Icons.access_time, '${task.timeLimit}分钟', const Color(0xFF4CAF50), ), const SizedBox(width: 12), _buildInfoChip( Icons.text_fields, '${task.wordLimit}词', const Color(0xFFFF9800), ), const SizedBox(width: 12), _buildInfoChip( Icons.star, '${task.difficulty.level}级', _getDifficultyColor(task.difficulty), ), ], ), if (task.keywords.isNotEmpty) ...[ const SizedBox(height: 12), Wrap( spacing: 6, runSpacing: 6, children: task.keywords.take(3).map((keyword) => Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(8), ), child: Text( keyword, style: const TextStyle( fontSize: 12, color: Colors.grey, ), ), )).toList(), ), ], ], ), ), ), ); } Widget _buildInfoChip(IconData icon, String text, Color color) { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( icon, size: 14, color: color, ), const SizedBox(width: 4), Text( text, style: TextStyle( fontSize: 12, color: color, fontWeight: FontWeight.w500, ), ), ], ), ); } Color _getDifficultyColor(WritingDifficulty difficulty) { switch (difficulty) { case WritingDifficulty.beginner: return const Color(0xFF4CAF50); case WritingDifficulty.elementary: return const Color(0xFF8BC34A); case WritingDifficulty.intermediate: return const Color(0xFFFF9800); case WritingDifficulty.upperIntermediate: return const Color(0xFFFF5722); case WritingDifficulty.advanced: return const Color(0xFFF44336); } } Color _getExamTypeColor(ExamType examType) { switch (examType) { case ExamType.cet: return const Color(0xFF2196F3); case ExamType.kaoyan: return const Color(0xFFF44336); case ExamType.toefl: return const Color(0xFF4CAF50); case ExamType.ielts: return const Color(0xFF9C27B0); } } }