import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../models/reading_exercise_model.dart'; import '../providers/reading_provider.dart'; import 'reading_category_screen.dart'; import 'reading_exercise_screen.dart'; /// 阅读理解主页面 class ReadingHomeScreen extends ConsumerStatefulWidget { const ReadingHomeScreen({super.key}); @override ConsumerState createState() => _ReadingHomeScreenState(); } class _ReadingHomeScreenState extends ConsumerState { @override void initState() { super.initState(); // 加载推荐文章 WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(readingMaterialsProvider.notifier).loadRecommendedMaterials(); }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF5F5F5), appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.black87), onPressed: () => Navigator.of(context).pop(), ), title: const Text( '阅读理解', style: TextStyle( color: Colors.black87, fontSize: 18, fontWeight: FontWeight.w600, ), ), centerTitle: true, ), body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildReadingModes(context), const SizedBox(height: 20), _buildArticleCategories(), const SizedBox(height: 20), _buildRecommendedArticles(context), const SizedBox(height: 20), _buildReadingProgress(), const SizedBox(height: 100), // 底部导航栏空间 ], ), ), ), ); } Widget _buildReadingModes(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.all(20), 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: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '阅读模式', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), LayoutBuilder( builder: (context, constraints) { // 在宽屏幕上使用更宽松的布局 if (constraints.maxWidth > 600) { return Row( children: [ Expanded( child: _buildModeCard( context, '休闲阅读', '轻松阅读体验', Icons.book_outlined, Colors.green, () => _navigateToCategory(context, ReadingExerciseType.story), ), ), const SizedBox(width: 20), Expanded( child: _buildModeCard( context, '练习阅读', '结合练习题', Icons.quiz, Colors.blue, () => _showExerciseTypeDialog(context), ), ), ], ); } else { return Row( children: [ Expanded( child: _buildModeCard( context, '休闲阅读', '轻松阅读体验', Icons.book_outlined, Colors.green, () => _navigateToCategory(context, ReadingExerciseType.story), ), ), const SizedBox(width: 12), Expanded( child: _buildModeCard( context, '练习阅读', '结合练习题', Icons.quiz, Colors.blue, () => _showExerciseTypeDialog(context), ), ), ], ); } }, ), ], ), ); } Widget _buildModeCard(BuildContext context, String title, String subtitle, IconData icon, Color color, VoidCallback onTap) { return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all(color: color.withOpacity(0.3)), ), child: Column( children: [ Icon( icon, color: color, size: 32, ), const SizedBox(height: 8), Text( title, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: color, ), ), const SizedBox(height: 4), Text( subtitle, style: const TextStyle( fontSize: 12, color: Colors.grey, ), textAlign: TextAlign.center, ), ], ), ), ); } Widget _buildArticleCategories() { return Container( width: double.infinity, padding: const EdgeInsets.all(20), 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: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '文章分类', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), Consumer( builder: (context, ref, _) { final materialsState = ref.watch(readingMaterialsProvider); if (materialsState.isLoading) { return const Center(child: CircularProgressIndicator()); } if (materialsState.error != null) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, size: 48, color: Colors.redAccent), const SizedBox(height: 12), Text('加载失败: ${materialsState.error}', style: const TextStyle(color: Colors.redAccent)), const SizedBox(height: 8), TextButton( onPressed: () { ref.read(readingMaterialsProvider.notifier).loadRecommendedMaterials(); }, child: const Text('重试'), ), ], ), ); } final materials = materialsState.materials; if (materials.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.category, size: 48, color: Colors.grey), const SizedBox(height: 12), const Text('暂无文章分类', style: TextStyle(color: Colors.grey)), const SizedBox(height: 8), TextButton( onPressed: () { ref.read(readingMaterialsProvider.notifier).loadRecommendedMaterials(); }, child: const Text('刷新'), ), ], ), ); } final Map counts = {}; for (final m in materials) { final key = m.type.name; counts[key] = (counts[key] ?? 0) + 1; } final categories = counts.keys.toList(); return GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 12, mainAxisSpacing: 12, childAspectRatio: 2.5, ), itemCount: categories.length.clamp(0, 4), itemBuilder: (context, index) { final name = categories[index]; final count = counts[name] ?? 0; final color = [Colors.blue, Colors.green, Colors.orange, Colors.purple][index % 4]; final icon = [Icons.computer, Icons.people, Icons.business_center, Icons.history_edu][index % 4]; return GestureDetector( onTap: () => _navigateToCategory(context, _mapStringToType(name)), child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon(icon, color: color, size: 24), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( name, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: color, ), ), Text( '$count篇', style: const TextStyle(fontSize: 10, color: Colors.grey), ), ], ), ), ], ), ), ); }, ); }, ), ], ), ); } ReadingExerciseType _mapStringToType(String name) { switch (name.toLowerCase()) { case 'news': return ReadingExerciseType.news; case 'story': return ReadingExerciseType.story; case 'science': return ReadingExerciseType.science; case 'business': return ReadingExerciseType.business; case 'technology': return ReadingExerciseType.technology; default: return ReadingExerciseType.news; } } Widget _buildRecommendedArticles(BuildContext context) { final materialsState = ref.watch(readingMaterialsProvider); return Container( width: double.infinity, padding: const EdgeInsets.all(20), 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: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '推荐文章', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), materialsState.isLoading ? const Center(child: CircularProgressIndicator()) : materialsState.error != null ? Center( child: Text( '加载失败: ${materialsState.error}', style: const TextStyle(color: Colors.red), ), ) : LayoutBuilder( builder: (context, constraints) { final exercises = materialsState.materials; if (exercises.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.article_outlined, size: 48, color: Colors.grey), const SizedBox(height: 12), const Text('暂无推荐文章', style: TextStyle(color: Colors.grey)), const SizedBox(height: 8), TextButton( onPressed: () { ref.read(readingMaterialsProvider.notifier).loadRecommendedMaterials(); }, child: const Text('刷新'), ), ], ), ); } if (constraints.maxWidth > 800) { // 宽屏幕:使用网格布局 return GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 16, mainAxisSpacing: 12, childAspectRatio: 3.0, ), itemCount: exercises.length, itemBuilder: (context, index) { return _buildArticleItem(context, exercises[index]); }, ); } else { // 窄屏幕:使用列表布局 return Column( children: exercises.map((exercise) => Padding( padding: const EdgeInsets.only(bottom: 12), child: _buildArticleItem(context, exercise), ), ).toList(), ); } }, ), ], ), ); } Widget _buildArticleItem(BuildContext context, ReadingExercise exercise) { String getDifficultyLabel(ReadingDifficulty difficulty) { switch (difficulty) { case ReadingDifficulty.elementary: return 'A1-A2'; case ReadingDifficulty.intermediate: return 'B1'; case ReadingDifficulty.upperIntermediate: return 'B2'; case ReadingDifficulty.advanced: return 'C1'; case ReadingDifficulty.proficient: return 'C2'; } } return GestureDetector( onTap: () => _navigateToExercise(context, exercise), child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: const Color(0xFF2196F3).withOpacity(0.1), borderRadius: BorderRadius.circular(20), ), child: const Icon( Icons.article, color: Color(0xFF2196F3), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( exercise.title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), Text( exercise.summary, style: const TextStyle( fontSize: 12, color: Colors.grey, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Row( children: [ Text( '${exercise.wordCount}词', style: const TextStyle( fontSize: 10, color: Colors.grey, ), ), const SizedBox(width: 8), Text( '${exercise.estimatedTime}分钟', style: const TextStyle( fontSize: 10, color: Colors.grey, ), ), ], ), ], ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: const Color(0xFF2196F3), borderRadius: BorderRadius.circular(4), ), child: Text( getDifficultyLabel(exercise.difficulty), style: const TextStyle( fontSize: 10, color: Colors.white, ), ), ), ], ), ), ); } Widget _buildReadingProgress() { return Container( padding: const EdgeInsets.all(20), 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: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '阅读统计', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), Consumer( builder: (context, ref, _) { final service = ref.watch(readingServiceProvider); return FutureBuilder( future: service.getReadingStats(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, size: 48, color: Colors.redAccent), const SizedBox(height: 12), Text('获取统计失败: ${snapshot.error}', style: const TextStyle(color: Colors.redAccent)), const SizedBox(height: 8), TextButton( onPressed: () { setState(() {}); }, child: const Text('重试'), ), ], ), ); } if (!snapshot.hasData) { return const Center(child: Text('暂无阅读统计', style: TextStyle(color: Colors.grey))); } final stats = snapshot.data!; if (stats.totalArticlesRead == 0) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: const [ Icon(Icons.bar_chart, size: 48, color: Colors.grey), SizedBox(height: 12), Text('暂无阅读统计', style: TextStyle(color: Colors.grey)), ], ), ); } return Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildProgressItem('${stats.totalArticlesRead}', '已读文章', Icons.article), _buildProgressItem('${stats.averageReadingSpeed.toStringAsFixed(0)}', '阅读速度', Icons.speed), _buildProgressItem('${stats.comprehensionAccuracy.toStringAsFixed(0)}%', '理解率', Icons.psychology), ], ); }, ); }, ), ], ), ); } Widget _buildProgressItem(String value, String label, IconData icon) { return Column( children: [ Icon( icon, color: const Color(0xFF2196F3), size: 24, ), const SizedBox(height: 8), Text( value, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Color(0xFF2196F3), ), ), const SizedBox(height: 4), Text( label, style: const TextStyle( fontSize: 12, color: Colors.grey, ), ), ], ); } // 导航到分类页面 void _navigateToCategory(BuildContext context, ReadingExerciseType type) { Navigator.push( context, MaterialPageRoute( builder: (context) => ReadingCategoryScreen(exerciseType: type), ), ); } // 导航到练习页面 void _navigateToExercise(BuildContext context, ReadingExercise exercise) { Navigator.push( context, MaterialPageRoute( builder: (context) => ReadingExerciseScreen(exercise: exercise), ), ); } // 显示练习类型选择对话框 void _showExerciseTypeDialog(BuildContext context) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('选择练习类型'), content: Column( mainAxisSize: MainAxisSize.min, children: [ {'name': 'news', 'label': '新闻'}, {'name': 'story', 'label': '故事'}, {'name': 'science', 'label': '科学'}, {'name': 'business', 'label': '商务'}, {'name': 'technology', 'label': '科技'}, ].map((category) { return ListTile( leading: const Icon(Icons.book), title: Text(category['label']!), onTap: () { Navigator.pop(context); _navigateToCategory(context, _mapStringToType(category['name']!)); }, ); }).toList(), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消'), ), ], ); }, ); } }