Files
ai_english/client/lib/features/vocabulary/screens/study_screen.dart

300 lines
8.7 KiB
Dart
Raw Normal View History

2025-11-17 13:39:05 +08:00
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,
),
),
),
],
);
}
}