This commit is contained in:
sjk
2025-11-17 13:39:05 +08:00
commit d4cfe2b9de
479 changed files with 109324 additions and 0 deletions

View File

@@ -0,0 +1,591 @@
import 'package:flutter/foundation.dart';
import '../models/test_models.dart';
import '../services/test_api_service.dart';
import '../../../core/models/api_response.dart';
/// 综合测试状态管理类
class TestProvider with ChangeNotifier {
final TestApiService _testService;
TestProvider({TestApiService? testService})
: _testService = testService ?? TestApiService();
// 加载状<E8BDBD>? bool _isLoading = false;
bool get isLoading => _isLoading;
// 错误信息
String? _errorMessage;
String? get errorMessage => _errorMessage;
// 测试模板
List<TestTemplate> _templates = [];
List<TestTemplate> get templates => _templates;
// 当前测试会话
TestSession? _currentSession;
TestSession? get currentSession => _currentSession;
// 当前题目
TestQuestion? get currentQuestion {
if (_currentSession == null ||
_currentSession!.currentQuestionIndex >= _currentSession!.questions.length) {
return null;
}
return _currentSession!.questions[_currentSession!.currentQuestionIndex];
}
// 测试结果
List<TestResult> _testResults = [];
List<TestResult> get testResults => _testResults;
TestResult? _currentResult;
TestResult? get currentResult => _currentResult;
// 用户技能统<E883BD>? Map<SkillType, double> _skillStatistics = {};
Map<SkillType, double> get skillStatistics => _skillStatistics;
// 题目统计
Map<String, dynamic> _questionStatistics = {};
Map<String, dynamic> get questionStatistics => _questionStatistics;
// 计时器相<E599A8>? int _timeRemaining = 0;
int get timeRemaining => _timeRemaining;
bool _isTimerRunning = false;
bool get isTimerRunning => _isTimerRunning;
/// 设置加载状<E8BDBD>? void _setLoading(bool loading) {
_isLoading = loading;
notifyListeners();
}
/// 设置错误信息
void _setError(String? error) {
_errorMessage = error;
notifyListeners();
}
/// 清除错误信息
void clearError() {
_setError(null);
}
/// 加载所有测试模<E8AF95>? Future<void> loadTestTemplates() async {
_setLoading(true);
_setError(null);
try {
final response = await _testService.getTestTemplates();
if (response.success && response.data != null) {
_templates = response.data!;
} else {
_setError(response.message);
}
} catch (e) {
_setError('加载测试模板失败: ${e.toString()}');
} finally {
_setLoading(false);
}
}
/// 根据类型加载测试模板
Future<void> loadTestTemplatesByType(TestType type) async {
_setLoading(true);
_setError(null);
try {
final response = await _testService.getTestTemplatesByType(type);
if (response.success && response.data != null) {
_templates = response.data!;
} else {
_setError(response.message);
}
} catch (e) {
_setError('加载测试模板失败: ${e.toString()}');
} finally {
_setLoading(false);
}
}
/// 创建测试会话
Future<bool> createTestSession({
required String templateId,
required String userId,
}) async {
_setLoading(true);
_setError(null);
try {
final response = await _testService.createTestSession(
templateId: templateId,
userId: userId,
);
if (response.success && response.data != null) {
_currentSession = response.data!;
_timeRemaining = _currentSession!.timeRemaining;
return true;
} else {
_setError(response.message);
return false;
}
} catch (e) {
_setError('创建测试会话失败: ${e.toString()}');
return false;
} finally {
_setLoading(false);
}
}
/// 开始测<E5A78B>? Future<bool> startTest() async {
if (_currentSession == null) {
_setError('没有活动的测试会<EFBFBD>?);
return false;
}
_setLoading(true);
_setError(null);
try {
final response = await _testService.startTest(_currentSession!.id);
if (response.success && response.data != null) {
_currentSession = response.data!;
_timeRemaining = _currentSession!.timeRemaining;
_startTimer();
return true;
} else {
_setError(response.message);
return false;
}
} catch (e) {
_setError('开始测试失<EFBFBD>? ${e.toString()}');
return false;
} finally {
_setLoading(false);
}
}
/// 提交答案
Future<bool> submitAnswer(UserAnswer answer) async {
if (_currentSession == null) {
_setError('没有活动的测试会<EFBFBD>?);
return false;
}
_setLoading(true);
_setError(null);
try {
final response = await _testService.submitAnswer(
sessionId: _currentSession!.id,
answer: answer,
);
if (response.success && response.data != null) {
_currentSession = response.data!;
_timeRemaining = _currentSession!.timeRemaining;
return true;
} else {
_setError(response.message);
return false;
}
} catch (e) {
_setError('提交答案失败: ${e.toString()}');
return false;
} finally {
_setLoading(false);
}
}
/// 下一<E4B88B>? void nextQuestion() {
if (_currentSession != null &&
_currentSession!.currentQuestionIndex < _currentSession!.questions.length - 1) {
_currentSession = _currentSession!.copyWith(
currentQuestionIndex: _currentSession!.currentQuestionIndex + 1,
);
notifyListeners();
}
}
/// 上一<E4B88A>? void previousQuestion() {
if (_currentSession != null && _currentSession!.currentQuestionIndex > 0) {
_currentSession = _currentSession!.copyWith(
currentQuestionIndex: _currentSession!.currentQuestionIndex - 1,
);
notifyListeners();
}
}
/// 跳转到指定题<E5AE9A>? void goToQuestion(int index) {
if (_currentSession != null &&
index >= 0 &&
index < _currentSession!.questions.length) {
_currentSession = _currentSession!.copyWith(
currentQuestionIndex: index,
);
notifyListeners();
}
}
/// 暂停测试
Future<bool> pauseTest() async {
if (_currentSession == null) {
_setError('没有活动的测试会<EFBFBD>?);
return false;
}
try {
final response = await _testService.pauseTest(_currentSession!.id);
if (response.success && response.data != null) {
_currentSession = response.data!;
_pauseTimer();
return true;
} else {
_setError(response.message);
return false;
}
} catch (e) {
_setError('暂停测试失败: ${e.toString()}');
return false;
}
}
/// 恢复测试
Future<bool> resumeTest() async {
if (_currentSession == null) {
_setError('没有活动的测试会<EFBFBD>?);
return false;
}
try {
final response = await _testService.resumeTest(_currentSession!.id);
if (response.success && response.data != null) {
_currentSession = response.data!;
_startTimer();
return true;
} else {
_setError(response.message);
return false;
}
} catch (e) {
_setError('恢复测试失败: ${e.toString()}');
return false;
}
}
/// 完成测试
Future<bool> completeTest() async {
if (_currentSession == null) {
_setError('没有活动的测试会话');
return false;
}
_setLoading(true);
_setError(null);
try {
final response = await _testService.completeTest(_currentSession!.id);
if (response.success && response.data != null) {
_currentResult = response.data!;
_stopTimer();
_currentSession = _currentSession!.copyWith(
status: TestStatus.completed,
endTime: DateTime.now(),
);
return true;
} else {
_setError(response.message);
return false;
}
} catch (e) {
_setError('完成测试失败: ${e.toString()}');
return false;
} finally {
_setLoading(false);
}
}
/// 加载用户测试历史
Future<void> loadUserTestHistory(String userId, {int page = 1, int limit = 10}) async {
_setLoading(true);
_setError(null);
try {
final response = await _testService.getUserTestHistory(
page: page,
limit: limit,
);
if (response.success && response.data != null) {
if (page == 1) {
_testResults = response.data!;
} else {
_testResults.addAll(response.data!);
}
} else {
_setError(response.message);
}
} catch (e) {
_setError('加载测试历史失败: ${e.toString()}');
} finally {
_setLoading(false);
}
}
/// 加载最近的测试结果
Future<void> loadRecentTestResults(String userId, {int limit = 5}) async {
_setLoading(true);
_setError(null);
try {
final response = await _testService.getRecentTestResults(
userId,
limit: limit,
);
if (response.success && response.data != null) {
_testResults = response.data!;
} else {
_setError(response.message);
}
} catch (e) {
_setError('加载最近测试结果失<EFBFBD>? ${e.toString()}');
} finally {
_setLoading(false);
}
}
/// 加载测试结果详情
Future<void> loadTestResultById(String resultId) async {
_setLoading(true);
_setError(null);
try {
final response = await _testService.getTestResultById(resultId);
if (response.success && response.data != null) {
_currentResult = response.data!;
} else {
_setError(response.message);
}
} catch (e) {
_setError('加载测试结果失败: ${e.toString()}');
} finally {
_setLoading(false);
}
}
/// 加载用户技能统<E883BD>? Future<void> loadUserSkillStatistics(String userId) async {
_setLoading(true);
_setError(null);
try {
final response = await _testService.getUserSkillStatistics(userId);
if (response.success && response.data != null) {
_skillStatistics = response.data!;
} else {
_setError(response.message);
}
} catch (e) {
_setError('加载技能统计失<EFBFBD>? ${e.toString()}');
} finally {
_setLoading(false);
}
}
/// 加载题目统计
Future<void> loadQuestionStatistics() async {
_setLoading(true);
_setError(null);
try {
final response = await _testService.getQuestionStatistics();
if (response.success && response.data != null) {
_questionStatistics = response.data!;
} else {
_setError(response.message);
}
} catch (e) {
_setError('加载题目统计失败: ${e.toString()}');
} finally {
_setLoading(false);
}
}
/// 删除测试结果
Future<bool> deleteTestResult(String resultId) async {
_setLoading(true);
_setError(null);
try {
final response = await _testService.deleteTestResult(resultId);
if (response.success) {
_testResults.removeWhere((result) => result.id == resultId);
notifyListeners();
return true;
} else {
_setError(response.message);
return false;
}
} catch (e) {
_setError('删除测试结果失败: ${e.toString()}');
return false;
} finally {
_setLoading(false);
}
}
/// 重置当前会话
void resetCurrentSession() {
_currentSession = null;
_currentResult = null;
_stopTimer();
notifyListeners();
}
/// 开始计时器
void _startTimer() {
_isTimerRunning = true;
_updateTimer();
}
/// 暂停计时<E8AEA1>? void _pauseTimer() {
_isTimerRunning = false;
}
/// 停止计时<E8AEA1>? void _stopTimer() {
_isTimerRunning = false;
_timeRemaining = 0;
}
/// 更新计时<E8AEA1>? void _updateTimer() {
if (_isTimerRunning && _timeRemaining > 0) {
Future.delayed(const Duration(seconds: 1), () {
if (_isTimerRunning) {
_timeRemaining--;
notifyListeners();
if (_timeRemaining <= 0) {
// 时间到,自动完成测试
completeTest();
} else {
_updateTimer();
}
}
});
}
}
/// 格式化剩余时<E4BD99>? String getFormattedTimeRemaining() {
final hours = _timeRemaining ~/ 3600;
final minutes = (_timeRemaining % 3600) ~/ 60;
final seconds = _timeRemaining % 60;
if (hours > 0) {
return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
} else {
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
}
}
/// 获取测试进度百分<E799BE>? double getTestProgress() {
if (_currentSession == null || _currentSession!.questions.isEmpty) {
return 0.0;
}
return (_currentSession!.currentQuestionIndex + 1) / _currentSession!.questions.length;
}
/// 获取已回答题目数<E79BAE>? int getAnsweredQuestionsCount() {
return _currentSession?.answers.length ?? 0;
}
/// 获取未回答题目数<E79BAE>? int getUnansweredQuestionsCount() {
if (_currentSession == null) return 0;
return _currentSession!.questions.length - _currentSession!.answers.length;
}
/// 检查是否所有题目都已回<E5B7B2>? bool areAllQuestionsAnswered() {
if (_currentSession == null) return false;
return _currentSession!.answers.length == _currentSession!.questions.length;
}
/// 获取技能类型的中文名称
String getSkillTypeName(SkillType skillType) {
switch (skillType) {
case SkillType.vocabulary:
return '词汇';
case SkillType.grammar:
return '语法';
case SkillType.reading:
return '阅读';
case SkillType.listening:
return '听力';
case SkillType.speaking:
return '口语';
case SkillType.writing:
return '写作';
}
}
/// 获取难度等级的中文名<E69687>? String getDifficultyLevelName(DifficultyLevel level) {
switch (level) {
case DifficultyLevel.beginner:
return '初级';
case DifficultyLevel.elementary:
return '基础';
case DifficultyLevel.intermediate:
return '中级';
case DifficultyLevel.upperIntermediate:
return '中高<EFBFBD>?;
case DifficultyLevel.advanced:
return '高级';
case DifficultyLevel.expert:
return '专家<EFBFBD>?;
}
}
/// 获取测试类型的中文名<E69687>? String getTestTypeName(TestType type) {
switch (type) {
case TestType.quick:
return '快速测<EFBFBD>?;
case TestType.standard:
return '标准测试';
case TestType.full:
return '完整测试';
case TestType.mock:
return '模拟考试';
}
}
/// 加载最近的测试结果
Future<void> loadRecentResults({String? userId}) async {
_setLoading(true);
_setError(null);
try {
final response = await _testService.getRecentTestResults(userId);
if (response.success && response.data != null) {
_testResults = response.data!;
} else {
_setError(response.message);
}
} catch (e) {
_setError('加载测试结果失败: ${e.toString()}');
} finally {
_setLoading(false);
}
}
@override
void dispose() {
_stopTimer();
super.dispose();
}
}

View File

@@ -0,0 +1,459 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/test_models.dart';
import '../services/test_service.dart';
import '../services/test_service_impl.dart';
import '../../../core/models/api_response.dart';
/// 测试服务提供者
final testServiceProvider = Provider<TestService>((ref) {
return TestServiceImpl();
});
/// 测试状态类
class TestState {
final bool isLoading;
final String? errorMessage;
final List<TestTemplate> templates;
final TestSession? currentSession;
final List<TestResult> testResults;
final TestResult? currentResult;
final Map<SkillType, double> skillStatistics;
final Map<String, dynamic> questionStatistics;
final int timeRemaining;
final bool isTimerRunning;
const TestState({
this.isLoading = false,
this.errorMessage,
this.templates = const [],
this.currentSession,
this.testResults = const [],
this.currentResult,
this.skillStatistics = const {},
this.questionStatistics = const {},
this.timeRemaining = 0,
this.isTimerRunning = false,
});
TestState copyWith({
bool? isLoading,
String? errorMessage,
List<TestTemplate>? templates,
TestSession? currentSession,
List<TestResult>? testResults,
TestResult? currentResult,
Map<SkillType, double>? skillStatistics,
Map<String, dynamic>? questionStatistics,
int? timeRemaining,
bool? isTimerRunning,
}) {
return TestState(
isLoading: isLoading ?? this.isLoading,
errorMessage: errorMessage ?? this.errorMessage,
templates: templates ?? this.templates,
currentSession: currentSession ?? this.currentSession,
testResults: testResults ?? this.testResults,
currentResult: currentResult ?? this.currentResult,
skillStatistics: skillStatistics ?? this.skillStatistics,
questionStatistics: questionStatistics ?? this.questionStatistics,
timeRemaining: timeRemaining ?? this.timeRemaining,
isTimerRunning: isTimerRunning ?? this.isTimerRunning,
);
}
/// 获取当前题目
TestQuestion? get currentQuestion {
if (currentSession == null ||
currentSession!.currentQuestionIndex >= currentSession!.questions.length) {
return null;
}
return currentSession!.questions[currentSession!.currentQuestionIndex];
}
}
/// 测试状态管理器
class TestNotifier extends StateNotifier<TestState> {
final TestService _testService;
TestNotifier(this._testService) : super(const TestState());
/// 设置加载状态
void _setLoading(bool loading) {
state = state.copyWith(isLoading: loading);
}
/// 设置错误信息
void _setError(String? error) {
state = state.copyWith(errorMessage: error);
}
/// 清除错误信息
void clearError() {
_setError(null);
}
/// 加载所有测试模板
Future<void> loadTestTemplates() async {
_setLoading(true);
_setError(null);
try {
final response = await _testService.getTestTemplates();
if (response.success && response.data != null) {
state = state.copyWith(templates: response.data!, isLoading: false);
} else {
_setError(response.message);
_setLoading(false);
}
} catch (e) {
_setError('加载测试模板失败: ${e.toString()}');
_setLoading(false);
}
}
/// 加载指定类型的测试模板
Future<void> loadTestTemplatesByType(TestType type) async {
_setLoading(true);
_setError(null);
try {
final response = await _testService.getTestTemplatesByType(type);
if (response.success && response.data != null) {
state = state.copyWith(templates: response.data!, isLoading: false);
} else {
_setError(response.message);
_setLoading(false);
}
} catch (e) {
_setError('加载测试模板失败: ${e.toString()}');
_setLoading(false);
}
}
/// 加载最近的测试结果
Future<void> loadRecentResults({String? userId}) async {
if (userId == null) {
_setError('用户ID不能为空');
return;
}
_setLoading(true);
_setError(null);
try {
final response = await _testService.getRecentTestResults(userId);
if (response.success && response.data != null) {
state = state.copyWith(testResults: response.data!, isLoading: false);
} else {
_setError(response.message);
_setLoading(false);
}
} catch (e) {
_setError('加载测试结果失败: ${e.toString()}');
_setLoading(false);
}
}
/// 创建测试会话
Future<bool> createTestSession({
required String templateId,
required String userId,
}) async {
_setLoading(true);
_setError(null);
try {
final response = await _testService.createTestSession(
templateId: templateId,
userId: userId,
);
if (response.success && response.data != null) {
state = state.copyWith(
currentSession: response.data!,
timeRemaining: response.data!.timeRemaining,
isLoading: false,
);
return true;
} else {
_setError(response.message);
_setLoading(false);
return false;
}
} catch (e) {
_setError('创建测试会话失败: ${e.toString()}');
_setLoading(false);
return false;
}
}
/// 开始测试
Future<bool> startTest() async {
if (state.currentSession == null) {
_setError('没有活动的测试会话');
return false;
}
_setLoading(true);
_setError(null);
try {
final response = await _testService.startTest(state.currentSession!.id);
if (response.success && response.data != null) {
state = state.copyWith(
currentSession: response.data!,
isTimerRunning: true,
isLoading: false,
);
return true;
} else {
_setError(response.message);
_setLoading(false);
return false;
}
} catch (e) {
_setError('开始测试失败: ${e.toString()}');
_setLoading(false);
return false;
}
}
/// 设置当前测试会话
void setCurrentSession(TestSession session) {
state = state.copyWith(
currentSession: session,
timeRemaining: session.timeRemaining,
isTimerRunning: true,
);
}
/// 设置当前测试结果
void setCurrentResult(TestResult result) {
state = state.copyWith(
currentResult: result,
isLoading: false,
);
}
/// 记录答案(本地记录,不提交到服务器)
void recordAnswer(UserAnswer answer) {
if (state.currentSession == null) {
_setError('没有活动的测试会话');
return;
}
final currentSession = state.currentSession!;
final updatedAnswers = List<UserAnswer>.from(currentSession.answers);
// 移除该题目的旧答案
updatedAnswers.removeWhere((a) => a.questionId == answer.questionId);
// 添加新答案
updatedAnswers.add(answer);
final updatedSession = currentSession.copyWith(answers: updatedAnswers);
state = state.copyWith(currentSession: updatedSession);
}
/// 提交答案
Future<bool> submitAnswer(UserAnswer answer) async {
if (state.currentSession == null) {
_setError('没有活动的测试会话');
return false;
}
try {
final response = await _testService.submitAnswer(
sessionId: state.currentSession!.id,
answer: answer,
);
if (response.success && response.data != null) {
state = state.copyWith(currentSession: response.data!);
return true;
} else {
_setError(response.message);
return false;
}
} catch (e) {
_setError('提交答案失败: ${e.toString()}');
return false;
}
}
/// 完成测试
Future<TestResult?> completeTest() async {
if (state.currentSession == null) {
_setError('没有活动的测试会话');
return null;
}
_setLoading(true);
_setError(null);
try {
final response = await _testService.completeTest(
sessionId: state.currentSession!.id,
answers: state.currentSession!.answers,
);
if (response.success && response.data != null) {
state = state.copyWith(
currentResult: response.data!,
currentSession: null,
isTimerRunning: false,
isLoading: false,
);
return response.data!;
} else {
_setError(response.message);
_setLoading(false);
return null;
}
} catch (e) {
_setError('完成测试失败: ${e.toString()}');
_setLoading(false);
return null;
}
}
/// 下一题
void nextQuestion() {
if (state.currentSession == null) {
_setError('没有活动的测试会话');
return;
}
final currentSession = state.currentSession!;
if (currentSession.currentQuestionIndex < currentSession.questions.length - 1) {
final updatedSession = TestSession(
id: currentSession.id,
userId: currentSession.userId,
templateId: currentSession.templateId,
questions: currentSession.questions,
answers: currentSession.answers,
startTime: currentSession.startTime,
endTime: currentSession.endTime,
status: currentSession.status,
timeRemaining: currentSession.timeRemaining,
currentQuestionIndex: currentSession.currentQuestionIndex + 1,
);
state = state.copyWith(currentSession: updatedSession);
}
}
/// 上一题
void previousQuestion() {
if (state.currentSession == null) {
_setError('没有活动的测试会话');
return;
}
final currentSession = state.currentSession!;
if (currentSession.currentQuestionIndex > 0) {
final updatedSession = TestSession(
id: currentSession.id,
userId: currentSession.userId,
templateId: currentSession.templateId,
questions: currentSession.questions,
answers: currentSession.answers,
startTime: currentSession.startTime,
endTime: currentSession.endTime,
status: currentSession.status,
timeRemaining: currentSession.timeRemaining,
currentQuestionIndex: currentSession.currentQuestionIndex - 1,
);
state = state.copyWith(currentSession: updatedSession);
}
}
/// 获取测试类型的中文名称
String getTestTypeName(TestType type) {
switch (type) {
case TestType.quick:
return '快速测试';
case TestType.standard:
return '标准测试';
case TestType.full:
return '完整测试';
case TestType.mock:
return '模拟考试';
case TestType.vocabulary:
return '词汇专项';
case TestType.grammar:
return '语法专项';
case TestType.reading:
return '阅读专项';
case TestType.listening:
return '听力专项';
case TestType.speaking:
return '口语专项';
case TestType.writing:
return '写作专项';
}
}
/// 获取技能类型的中文名称
String getSkillTypeName(SkillType skill) {
switch (skill) {
case SkillType.vocabulary:
return '词汇';
case SkillType.grammar:
return '语法';
case SkillType.reading:
return '阅读';
case SkillType.listening:
return '听力';
case SkillType.speaking:
return '口语';
case SkillType.writing:
return '写作';
}
}
/// 获取难度等级的中文名称
String getDifficultyLevelName(DifficultyLevel level) {
switch (level) {
case DifficultyLevel.beginner:
return '初级';
case DifficultyLevel.elementary:
return '基础';
case DifficultyLevel.intermediate:
return '中级';
case DifficultyLevel.upperIntermediate:
return '中高级';
case DifficultyLevel.advanced:
return '高级';
case DifficultyLevel.expert:
return '专家级';
}
}
}
/// 测试状态提供者
final testProvider = StateNotifierProvider<TestNotifier, TestState>((ref) {
final testService = ref.watch(testServiceProvider);
return TestNotifier(testService);
});
/// 当前测试会话提供者
final currentTestSessionProvider = Provider<TestSession?>((ref) {
return ref.watch(testProvider).currentSession;
});
/// 当前题目提供者
final currentQuestionProvider = Provider<TestQuestion?>((ref) {
return ref.watch(testProvider).currentQuestion;
});
/// 测试模板提供者
final testTemplatesProvider = Provider<List<TestTemplate>>((ref) {
return ref.watch(testProvider).templates;
});
/// 测试结果提供者
final testResultsProvider = Provider<List<TestResult>>((ref) {
return ref.watch(testProvider).testResults;
});