Files
ai_english/client/lib/features/vocabulary/screens/study_screen.dart
2025-11-17 14:09:17 +08:00

300 lines
8.7 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:ai_english_learning/core/network/api_client.dart';
import 'package:ai_english_learning/core/services/storage_service.dart';
import 'package:ai_english_learning/features/vocabulary/models/vocabulary_book_model.dart';
import 'package:ai_english_learning/features/vocabulary/models/word_model.dart';
import 'package:ai_english_learning/features/vocabulary/models/learning_session_model.dart';
import 'package:ai_english_learning/features/vocabulary/services/learning_service.dart';
import 'package:ai_english_learning/features/vocabulary/services/vocabulary_service.dart';
import 'package:ai_english_learning/features/vocabulary/widgets/study_card_widget.dart';
class StudyScreen extends StatefulWidget {
final VocabularyBook vocabularyBook;
final int dailyGoal;
const StudyScreen({
Key? key,
required this.vocabularyBook,
this.dailyGoal = 20,
}) : super(key: key);
@override
State<StudyScreen> createState() => _StudyScreenState();
}
class _StudyScreenState extends State<StudyScreen> {
late LearningService _learningService;
late VocabularyService _vocabularyService;
bool _isLoading = true;
String? _error;
LearningSession? _session;
DailyLearningTasks? _tasks;
List<Word> _wordsList = [];
int _currentIndex = 0;
int _studiedCount = 0;
DateTime? _studyStartTime;
@override
void initState() {
super.initState();
_init();
}
Future<void> _init() async {
final storageService = await StorageService.getInstance();
_learningService = LearningService(apiClient: ApiClient.instance);
_vocabularyService = VocabularyService(
apiClient: ApiClient.instance,
storageService: storageService,
);
await _startLearning();
}
Future<void> _startLearning() async {
setState(() {
_isLoading = true;
_error = null;
});
try {
// 1. 开始学习会话
final result = await _learningService.startLearning(
widget.vocabularyBook.id,
widget.dailyGoal,
);
_session = result['session'] as LearningSession;
_tasks = result['tasks'] as DailyLearningTasks;
print('✅ 学习会话创建成功');
print('📝 新单词数量: ${_tasks!.newWords.length}');
print('🔄 复习单词数量: ${_tasks!.reviewWords.length}');
// 2. 加载单词详情 - 从词汇书API获取
if (_tasks!.newWords.isEmpty && _tasks!.reviewWords.isEmpty) {
setState(() {
_isLoading = false;
_wordsList = [];
});
return;
}
// 合并新单词和复习单词的ID学习逻辑新词 + 所有到期复习词)
final allWordIds = <int>{
..._tasks!.newWords,
..._tasks!.reviewWords.map((r) => r['vocabulary_id'] as int),
};
final totalWords = allWordIds.length;
print('📚 今日学习任务: ${_tasks!.newWords.length}个新词 + ${_tasks!.reviewWords.length}个复习词 = $totalWords个单词');
// 使用词汇书的单词加载API - 动态设置limit以包含所有需要的单词
final limit = (totalWords / 50).ceil() * 50; // 向上取整到50的倍数
final bookWords = await _vocabularyService.getVocabularyBookWords(
widget.vocabularyBook.id,
page: 1,
limit: limit < 100 ? 100 : limit,
);
// 筛选出需要学习的单词(新词 + 复习词)
final words = <Word>[];
for (final bookWord in bookWords) {
if (bookWord.word == null) continue;
final wordId = int.tryParse(bookWord.word!.id);
if (wordId != null && allWordIds.contains(wordId)) {
words.add(bookWord.word!);
}
if (words.length >= totalWords) break;
}
print('✅ 加载了 ${words.length} 个单词,需要 $totalWords');
setState(() {
_wordsList = words;
_isLoading = false;
_studyStartTime = DateTime.now();
});
} catch (e, stackTrace) {
print('❌ 开始学习失败: $e');
print('Stack trace: $stackTrace');
setState(() {
_error = '加载学习内容失败,请稍后重试\n错误:$e';
_isLoading = false;
});
}
}
Future<void> _handleAnswer(StudyDifficulty difficulty) async {
if (_currentIndex >= _wordsList.length) return;
final word = _wordsList[_currentIndex];
final studyTime = _studyStartTime != null
? DateTime.now().difference(_studyStartTime!).inMilliseconds
: 0;
try {
// 提交学习结果
await _learningService.submitWordStudy(
word.id,
difficulty,
sessionId: int.tryParse(_session?.id ?? '0'),
);
setState(() {
_studiedCount++;
_currentIndex++;
_studyStartTime = DateTime.now();
});
// 检查是否完成
if (_currentIndex >= _wordsList.length) {
_showCompletionDialog();
}
} catch (e) {
print('提交学习结果失败: $e');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('提交失败,请重试')),
);
}
}
void _showCompletionDialog() {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: const Text('🎉 今日学习完成!'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('学习单词数:$_studiedCount'),
Text('新学单词:${_tasks?.newWords.length ?? 0}'),
Text('复习单词:${_tasks?.reviewWords.length ?? 0}'),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop(true); // 返回到上一页
},
child: const Text('完成'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.vocabularyBook.name),
actions: [
// 进度显示
Center(
child: Padding(
padding: const EdgeInsets.only(right: 16),
child: Text(
'$_studiedCount / ${_wordsList.length}',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
body: _buildBody(),
);
}
Widget _buildBody() {
if (_isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (_error != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 64, color: Colors.grey[400]),
const SizedBox(height: 16),
Text(
_error!,
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _startLearning,
child: const Text('重试'),
),
],
),
);
}
if (_wordsList.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.check_circle, size: 64, color: Colors.green[400]),
const SizedBox(height: 16),
const Text(
'今日任务已完成!',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(
'明天继续加油!',
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('返回'),
),
],
),
);
}
if (_currentIndex >= _wordsList.length) {
return const Center(child: CircularProgressIndicator());
}
return Column(
children: [
// 进度条
LinearProgressIndicator(
value: _currentIndex / _wordsList.length,
backgroundColor: Colors.grey[200],
),
// 学习卡片
Expanded(
child: Padding(
padding: const EdgeInsets.all(16),
child: StudyCardWidget(
word: _wordsList[_currentIndex],
onAnswer: _handleAnswer,
),
),
),
],
);
}
}