import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../models/vocabulary_book_model.dart'; import '../models/word_model.dart'; import '../providers/vocabulary_provider.dart'; import '../services/vocabulary_service.dart'; import '../../../core/network/api_client.dart'; import '../../../core/services/storage_service.dart'; import '../../../shared/widgets/custom_app_bar.dart'; import '../../../shared/widgets/custom_card.dart'; import '../../../shared/widgets/loading_widget.dart'; import '../../../shared/widgets/error_widget.dart' as custom_error; import 'word_learning_screen.dart'; import 'study_screen.dart'; /// 词汇书详情页面 class VocabularyBookScreen extends ConsumerStatefulWidget { final VocabularyBook vocabularyBook; const VocabularyBookScreen({ super.key, required this.vocabularyBook, }); @override ConsumerState createState() => _VocabularyBookScreenState(); } class _VocabularyBookScreenState extends ConsumerState with SingleTickerProviderStateMixin { late TabController _tabController; ScrollController? _scrollController; List _words = []; List _filteredWords = []; // 过滤后的单词列表 bool _isLoading = false; bool _isLoadingMore = false; // 是否正在加载更多 String? _error; UserVocabularyBookProgress? _progress; bool _isLoadingProgress = false; // 搜索相关 bool _isSearching = false; final TextEditingController _searchController = TextEditingController(); String _searchQuery = ''; // 分页相关 int _currentPage = 1; final int _pageSize = 50; // 每频50个单词 bool _hasMore = true; // 是否还有更多数据 @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); _scrollController = ScrollController(); _scrollController?.addListener(_onScroll); _loadVocabularyBookWords(); // 加载第一页 _loadProgress(); // 监听搜索输入变化 _searchController.addListener(_onSearchChanged); } /// 监听滚动,实现懒加载 void _onScroll() { if (_scrollController == null) return; // 如果滚动到底部80%的位置,且不在加载中,且还有更多数据 if (_scrollController!.position.pixels >= _scrollController!.position.maxScrollExtent * 0.8 && !_isLoadingMore && _hasMore && _tabController.index == 0) { // 只在单词列表标签触发 _loadMoreWords(); } } /// 加载更多单词 Future _loadMoreWords() async { if (_isLoadingMore || !_hasMore) return; setState(() { _isLoadingMore = true; }); try { _currentPage++; final vocabularyService = VocabularyService( apiClient: ApiClient.instance, storageService: await StorageService.getInstance(), ); final bookWords = await vocabularyService.getVocabularyBookWords( widget.vocabularyBook.id, page: _currentPage, limit: _pageSize, ); final newWords = bookWords .where((bw) => bw.word != null) .map((bw) => bw.word!) .toList(); setState(() { _words.addAll(newWords); _filteredWords = _searchQuery.isEmpty ? _words : _filterWords(_searchQuery); _isLoadingMore = false; // 如果返回的数据少于pageSize,说明没有更多数据了 _hasMore = newWords.length >= _pageSize; }); } catch (e) { print('加载更多单词失败: $e'); setState(() { _isLoadingMore = false; _currentPage--; // 回退页码 }); } } /// 搜索输入变化监听 void _onSearchChanged() { setState(() { _searchQuery = _searchController.text; _filteredWords = _searchQuery.isEmpty ? _words : _filterWords(_searchQuery); }); } /// 过滤单词 List _filterWords(String query) { final lowerQuery = query.toLowerCase(); return _words.where((word) { // 搜索单词、音标、释义 final wordMatch = word.word.toLowerCase().contains(lowerQuery); final phoneticMatch = word.phonetic?.toLowerCase().contains(lowerQuery) ?? false; final definitionMatch = word.definitions.any( (def) => def.translation.toLowerCase().contains(lowerQuery), ); return wordMatch || phoneticMatch || definitionMatch; }).toList(); } /// 切换搜索状态 void _toggleSearch() { setState(() { _isSearching = !_isSearching; if (!_isSearching) { _searchController.clear(); _searchQuery = ''; _filteredWords = _words; } }); } @override void dispose() { _scrollController?.removeListener(_onScroll); _scrollController?.dispose(); _tabController.dispose(); _searchController.dispose(); super.dispose(); } /// 从后端API加载词汇书单词(第一页) Future _loadVocabularyBookWords() async { setState(() { _isLoading = true; _error = null; _currentPage = 1; _hasMore = true; }); try { final vocabularyService = VocabularyService( apiClient: ApiClient.instance, storageService: await StorageService.getInstance(), ); final bookWords = await vocabularyService.getVocabularyBookWords( widget.vocabularyBook.id, page: 1, limit: _pageSize, ); // 提取单词对象 final words = bookWords .where((bw) => bw.word != null) .map((bw) => bw.word!) .toList(); setState(() { _words = words; _filteredWords = words; // 初始化过滤列表 _isLoading = false; }); } catch (e) { print('加载词汇书单词失败: $e'); setState(() { _error = '加载失败,请稍后重试'; _isLoading = false; // API失败时显示空状态,不使用模拟数据 _words = []; }); } } /// 加载学习进度 Future _loadProgress({bool forceRefresh = false}) async { setState(() { _isLoadingProgress = true; }); try { final vocabularyService = VocabularyService( apiClient: ApiClient.instance, storageService: await StorageService.getInstance(), ); final progress = await vocabularyService.getVocabularyBookProgress( widget.vocabularyBook.id, forceRefresh: forceRefresh, // 传递强制刷新参数 ); setState(() { _progress = progress; _isLoadingProgress = false; }); } catch (e) { print('加载学习进度失败: $e'); setState(() { _isLoadingProgress = false; // 使用默认进度 _progress = UserVocabularyBookProgress( id: '0', userId: '0', vocabularyBookId: widget.vocabularyBook.id, learnedWords: 0, masteredWords: 0, progressPercentage: 0.0, streakDays: 0, totalStudyDays: 0, averageDailyWords: 0.0, startedAt: DateTime.now(), lastStudiedAt: null, ); }); } } void _handleMenuAction(String action) { switch (action) { case 'start_learning': _startLearning(context); break; case 'vocabulary_test': _startVocabularyTest(); break; case 'smart_review': _startSmartReview(); break; case 'export': _exportWords(); break; } } void _startLearning(BuildContext context) async { // 显示学习目标选择对话框 double selectedGoal = 20.0; final dailyGoal = await showDialog( context: context, builder: (context) => StatefulBuilder( builder: (context, setState) { return AlertDialog( title: const Text('设置学习目标'), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Text('请选择每日学习单词数:'), const SizedBox(height: 8), Text( '${selectedGoal.toInt()}个单词', style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Colors.blue, ), ), const SizedBox(height: 16), Slider( value: selectedGoal, min: 5, max: 50, divisions: 9, label: '${selectedGoal.toInt()}个单词', onChanged: (value) { setState(() { selectedGoal = value; }); }, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('5', style: TextStyle(color: Colors.grey[600])), Text('50', style: TextStyle(color: Colors.grey[600])), ], ), ], ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('取消'), ), ElevatedButton( onPressed: () => Navigator.of(context).pop(selectedGoal.toInt()), child: const Text('开始学习'), ), ], ); }, ), ); if (dailyGoal == null) return; // 导航到学习页面 final result = await Navigator.of(context).push( MaterialPageRoute( builder: (context) => StudyScreen( vocabularyBook: widget.vocabularyBook, dailyGoal: dailyGoal, ), ), ); // 学习完成后强制刷新进度(跳过缓存) if (result == true) { _loadProgress(forceRefresh: true); } } void _startVocabularyTest() { Navigator.of(context).pushNamed( '/vocabulary_test', arguments: { 'vocabularyBook': widget.vocabularyBook, 'testType': 'bookTest', 'questionCount': 20, }, ); } void _startSmartReview() { final reviewWords = _words.where((word) { // 这里可以根据学习进度和遗忘曲线来筛选需要复习的单词 // 暂时返回所有单词 return true; }).toList(); if (reviewWords.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('暂无需要复习的单词')), ); return; } Navigator.of(context).pushNamed( '/smart_review', arguments: { 'vocabularyBook': widget.vocabularyBook, 'words': reviewWords, 'reviewMode': 'adaptive', }, ); } void _exportWords() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('导出单词'), content: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.text_snippet), title: const Text('导出为文本文件'), onTap: () { Navigator.of(context).pop(); _exportAsText(); }, ), ListTile( leading: const Icon(Icons.table_chart), title: const Text('导出为Excel文件'), onTap: () { Navigator.of(context).pop(); _exportAsExcel(); }, ), ListTile( leading: const Icon(Icons.picture_as_pdf), title: const Text('导出为PDF文件'), onTap: () { Navigator.of(context).pop(); _exportAsPDF(); }, ), ], ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('取消'), ), ], ), ); } void _exportAsText() { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('正在导出为文本文件...')), ); // TODO: 实现文本导出功能 } void _exportAsExcel() { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('正在导出为Excel文件...')), ); // TODO: 实现Excel导出功能 } void _exportAsPDF() { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('正在导出为PDF文件...')), ); // TODO: 实现PDF导出功能 } @override Widget build(BuildContext context) { return Scaffold( appBar: TabAppBar( title: widget.vocabularyBook.name, controller: _tabController, tabs: const [ Tab(text: '单词列表'), Tab(text: '学习进度'), ], actions: [ IconButton( icon: Icon(_isSearching ? Icons.close : Icons.search), onPressed: _toggleSearch, ), PopupMenuButton( onSelected: _handleMenuAction, itemBuilder: (context) => [ const PopupMenuItem( value: 'start_learning', child: Row( children: [ Icon(Icons.school), SizedBox(width: 8), Text('开始学习'), ], ), ), const PopupMenuItem( value: 'vocabulary_test', child: Row( children: [ Icon(Icons.quiz), SizedBox(width: 8), Text('词汇测试'), ], ), ), const PopupMenuItem( value: 'smart_review', child: Row( children: [ Icon(Icons.psychology), SizedBox(width: 8), Text('智能复习'), ], ), ), const PopupMenuItem( value: 'export', child: Row( children: [ Icon(Icons.download), SizedBox(width: 8), Text('导出单词'), ], ), ), ], ), ], ), body: Column( children: [ // 搜索框 if (_isSearching) Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: TextField( controller: _searchController, autofocus: true, decoration: InputDecoration( hintText: '搜索单词、音标或释义...', prefixIcon: const Icon(Icons.search), suffixIcon: _searchQuery.isNotEmpty ? IconButton( icon: const Icon(Icons.clear), onPressed: () { _searchController.clear(); }, ) : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey[300]!), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: Theme.of(context).primaryColor, width: 2, ), ), filled: true, fillColor: Colors.grey[50], ), ), ), // TabBarView Expanded( child: TabBarView( controller: _tabController, children: [ _buildWordListTab(), _buildProgressTab(), ], ), ), ], ), floatingActionButton: _buildFloatingActionButton(), ); } Widget _buildFloatingActionButton() { return Column( mainAxisSize: MainAxisSize.min, children: [ FloatingActionButton.extended( onPressed: () => _startLearning(context), icon: const Icon(Icons.school), label: const Text('开始学习'), heroTag: 'start_learning', ), const SizedBox(height: 8), FloatingActionButton( onPressed: () => _startVocabularyTest(), child: const Icon(Icons.quiz), heroTag: 'vocabulary_test', ), ], ); } Widget _buildWordListTab() { if (_isLoading) { return const Center(child: LoadingWidget()); } if (_error != null) { return Center( child: custom_error.PageErrorWidget( message: _error!, onRetry: _loadVocabularyBookWords, ), ); } if (_words.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.book_outlined, size: 64, color: Colors.grey[400], ), const SizedBox(height: 16), Text( '暂无单词', style: TextStyle( fontSize: 16, color: Colors.grey[600], ), ), const SizedBox(height: 8), Text( '该词汇书还没有单词', style: TextStyle( fontSize: 14, color: Colors.grey[500], ), ), ], ), ); } return RefreshIndicator( onRefresh: _loadVocabularyBookWords, child: ListView.builder( controller: _scrollController, // 可以为null,ListView会自动处理 padding: const EdgeInsets.all(16), itemCount: _filteredWords.length + (_hasMore && _searchQuery.isEmpty ? 1 : 0), // 搜索时不显示加载更多 itemBuilder: (context, index) { // 搜索结果提示 if (_searchQuery.isNotEmpty && index == 0 && _filteredWords.isEmpty) { return _buildNoResultsWidget(); } // 如果是最后一项且还有更多数据,显示加载指示器 if (index == _filteredWords.length && _searchQuery.isEmpty) { return _buildLoadingIndicator(); } final word = _filteredWords[index]; return _buildWordCard(word, index); }, ), ); } /// 构建无搜索结果提示 Widget _buildNoResultsWidget() { return Center( child: Padding( padding: const EdgeInsets.all(48), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.search_off, size: 64, color: Colors.grey[400], ), const SizedBox(height: 16), Text( '未找到相关单词', style: TextStyle( fontSize: 16, color: Colors.grey[600], ), ), const SizedBox(height: 8), Text( '请尝试使用其他关键词搜索', style: TextStyle( fontSize: 14, color: Colors.grey[500], ), ), ], ), ), ); } /// 构建加载更多指示器 Widget _buildLoadingIndicator() { return Padding( padding: const EdgeInsets.all(16), child: Center( child: Column( children: [ const CircularProgressIndicator(), const SizedBox(height: 8), Text( '正在加载更多...', style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), ], ), ), ); } Widget _buildWordCard(Word word, int index) { return CustomCard( margin: const EdgeInsets.only(bottom: 12), child: InkWell( onTap: () => _showWordDetail(word), borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 32, height: 32, decoration: BoxDecoration( color: Theme.of(context).primaryColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Center( child: Text( '${index + 1}', style: TextStyle( color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold, ), ), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( word.word, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), if (word.phonetic != null) ...[ const SizedBox(width: 8), Text( word.phonetic!, style: TextStyle( fontSize: 14, color: Colors.grey[600], ), ), ], ], ), if (word.definitions.isNotEmpty) ...[ const SizedBox(height: 4), Text( word.definitions.first.translation, style: TextStyle( fontSize: 14, color: Colors.grey[700], ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ], ), ), IconButton( icon: const Icon(Icons.volume_up), onPressed: () => _playWordPronunciation(word), ), ], ), if (word.examples.isNotEmpty) ...[ const SizedBox(height: 12), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( word.examples.first.sentence, style: const TextStyle( fontSize: 13, fontStyle: FontStyle.italic, ), ), const SizedBox(height: 4), Text( word.examples.first.translation, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), ], ), ), ], ], ), ), ), ); } void _showWordDetail(Word word) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => DraggableScrollableSheet( initialChildSize: 0.7, minChildSize: 0.5, maxChildSize: 0.95, builder: (context, scrollController) => Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( children: [ Container( margin: const EdgeInsets.symmetric(vertical: 12), width: 40, height: 4, decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(2), ), ), Expanded( child: ListView( controller: scrollController, padding: const EdgeInsets.all(24), children: [ Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( word.word, style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, ), ), if (word.phonetic != null) ...[ const SizedBox(height: 4), Text( word.phonetic!, style: TextStyle( fontSize: 16, color: Colors.grey[600], ), ), ], ], ), ), IconButton( icon: const Icon(Icons.volume_up, size: 32), onPressed: () => _playWordPronunciation(word), ), ], ), const SizedBox(height: 24), const Text( '释义', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 12), ...word.definitions.map((def) => Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: Theme.of(context).primaryColor.withOpacity(0.1), borderRadius: BorderRadius.circular(4), ), child: Text( _getWordTypeText(def.type), style: TextStyle( fontSize: 12, color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold, ), ), ), const SizedBox(width: 12), Expanded( child: Text( def.translation, style: const TextStyle(fontSize: 16), ), ), ], ), )), if (word.examples.isNotEmpty) ...[ const SizedBox(height: 24), const Text( '例句', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 12), ...word.examples.map((example) => Container( margin: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( example.sentence, style: const TextStyle( fontSize: 15, fontStyle: FontStyle.italic, ), ), const SizedBox(height: 8), Text( example.translation, style: TextStyle( fontSize: 14, color: Colors.grey[700], ), ), ], ), )), ], if (word.synonyms.isNotEmpty) ...[ const SizedBox(height: 24), const Text( '同义词', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: word.synonyms.map((synonym) => Chip( label: Text(synonym), backgroundColor: Colors.blue[50], )).toList(), ), ], if (word.antonyms.isNotEmpty) ...[ const SizedBox(height: 24), const Text( '反义词', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: word.antonyms.map((antonym) => Chip( label: Text(antonym), backgroundColor: Colors.orange[50], )).toList(), ), ], ], ), ), ], ), ), ), ); } String _getWordTypeText(WordType type) { switch (type) { case WordType.noun: return 'n.'; case WordType.verb: return 'v.'; case WordType.adjective: return 'adj.'; case WordType.adverb: return 'adv.'; case WordType.pronoun: return 'pron.'; case WordType.preposition: return 'prep.'; case WordType.conjunction: return 'conj.'; case WordType.interjection: return 'interj.'; case WordType.article: return 'art.'; case WordType.phrase: return 'phrase'; } } void _playWordPronunciation(Word word) { // TODO: 实现音频播放功能 ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('播放 ${word.word} 的发音')), ); } Widget _buildProgressTab() { if (_isLoadingProgress) { return const Center(child: LoadingWidget()); } if (_progress == null) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.trending_up, size: 64, color: Colors.grey[400]), const SizedBox(height: 16), Text( '暂无学习进度', style: TextStyle(fontSize: 16, color: Colors.grey[600]), ), ], ), ); } return RefreshIndicator( onRefresh: () => _loadProgress(forceRefresh: true), // 下拉刷新强制跳过缓存 child: ListView( padding: const EdgeInsets.all(16), children: [ // 整体进度卡片 CustomCard( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '整体进度', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: Theme.of(context).primaryColor.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( '${_progress!.progressPercentage.toStringAsFixed(1)}%', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Theme.of(context).primaryColor, ), ), ), ], ), const SizedBox(height: 20), // 进度条 ClipRRect( borderRadius: BorderRadius.circular(10), child: LinearProgressIndicator( value: _progress!.progressPercentage / 100, minHeight: 20, backgroundColor: Colors.grey[200], valueColor: AlwaysStoppedAnimation( Theme.of(context).primaryColor, ), ), ), const SizedBox(height: 16), // 进度详情 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '${_progress!.learnedWords} / ${widget.vocabularyBook.totalWords} 个单词', style: TextStyle( fontSize: 14, color: Colors.grey[600], ), ), Text( '剩余 ${widget.vocabularyBook.totalWords - _progress!.learnedWords} 个', style: TextStyle( fontSize: 14, color: Colors.grey[600], ), ), ], ), ], ), ), ), const SizedBox(height: 16), // 单词状态统计 Row( children: [ Expanded( child: _buildStatCard( '已学习', _progress!.learnedWords.toString(), Icons.school, Colors.blue, ), ), const SizedBox(width: 12), Expanded( child: _buildStatCard( '已掌握', _progress!.masteredWords.toString(), Icons.check_circle, Colors.green, ), ), ], ), const SizedBox(height: 12), Row( children: [ Expanded( child: _buildStatCard( '待复习', '0', Icons.refresh, Colors.orange, ), ), const SizedBox(width: 12), Expanded( child: _buildStatCard( '未学习', '${widget.vocabularyBook.totalWords - _progress!.learnedWords}', Icons.fiber_new, Colors.grey, ), ), ], ), const SizedBox(height: 16), // 学习信息 CustomCard( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '学习信息', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), _buildInfoRow( Icons.calendar_today, '累计学习', '${_progress!.totalStudyDays} 天', ), const Divider(height: 24), _buildInfoRow( Icons.trending_up, '掌握率', '${(_progress!.masteredWords / widget.vocabularyBook.totalWords * 100).toStringAsFixed(1)}%', ), const Divider(height: 24), _buildInfoRow( Icons.access_time, '上次学习', _progress!.lastStudiedAt != null ? _formatDateTime(_progress!.lastStudiedAt!) : '从未学习', ), const Divider(height: 24), _buildInfoRow( Icons.play_circle_outline, '开始时间', _formatDateTime(_progress!.startedAt), ), ], ), ), ), ], ), ); } Widget _buildStatCard(String label, String value, IconData icon, Color color) { return CustomCard( child: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), child: Icon(icon, size: 24, color: color), ), const SizedBox(height: 12), Text( value, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: color, ), ), const SizedBox(height: 4), Text( label, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), ], ), ), ); } Widget _buildInfoRow(IconData icon, String label, String value) { return Row( children: [ Icon(icon, size: 20, color: Colors.grey[600]), const SizedBox(width: 12), Expanded( child: Text( label, style: TextStyle( fontSize: 14, color: Colors.grey[700], ), ), ), Text( value, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, ), ), ], ); } String _formatDateTime(DateTime dateTime) { final now = DateTime.now(); final difference = now.difference(dateTime); if (difference.inDays == 0) { if (difference.inHours == 0) { if (difference.inMinutes == 0) { return '刚刚'; } return '${difference.inMinutes} 分钟前'; } return '${difference.inHours} 小时前'; } else if (difference.inDays == 1) { return '昨天'; } else if (difference.inDays < 7) { return '${difference.inDays} 天前'; } else { return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')}'; } } }