Files
AutoJudge-Flutter/lib/providers/evaluation_provider.dart
2025-11-13 09:14:49 +08:00

936 lines
27 KiB
Dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import '../models/course.dart';
import '../models/evaluation_result.dart';
import '../models/evaluation_history.dart';
import '../models/concurrent_task.dart';
import '../services/evaluation_service.dart';
import '../services/notification_service.dart';
import '../services/storage_service.dart';
import '../services/questionnaire_parser.dart';
import '../utils/text_generator.dart';
/// Evaluation state enum
enum EvaluationState { idle, loading, evaluating, completed, error }
/// Provider for managing course evaluation state and operations
///
/// Handles loading courses, batch evaluation, progress tracking,
/// and notification management
///
/// Usage example:
/// ```dart
/// final evaluationProvider = Provider.of<EvaluationProvider>(context);
///
/// // Load courses
/// await evaluationProvider.loadCourses();
///
/// // Start batch evaluation
/// await evaluationProvider.startBatchEvaluation();
///
/// // Retry failed courses
/// await evaluationProvider.retryFailed();
/// ```
class EvaluationProvider extends ChangeNotifier {
EvaluationService? _service;
final NotificationService _notificationService;
final StorageService _storageService;
List<Course> _courses = [];
EvaluationState _state = EvaluationState.idle;
BatchEvaluationResult? _lastResult;
String? _errorMessage;
List<EvaluationHistory> _evaluationHistory = [];
// Progress tracking
int _currentProgress = 0;
int _totalProgress = 0;
Course? _currentCourse;
String? _currentStatus;
final List<String> _logs = [];
// Countdown tracking
int _countdownRemaining = 0;
int _countdownTotal = 0;
// Concurrent evaluation tracking
final List<ConcurrentTask> _concurrentTasks = [];
bool _isConcurrentMode = false;
EvaluationProvider({
EvaluationService? service,
required NotificationService notificationService,
StorageService? storageService,
}) : _service = service,
_notificationService = notificationService,
_storageService = storageService ?? StorageService() {
_loadEvaluationHistory();
}
dynamic _connection;
/// Set the connection to use for fetching courses and create evaluation service
void setConnection(dynamic connection) {
_connection = connection;
if (connection != null) {
_service = EvaluationService(
connection: connection,
parser: QuestionnaireParser(),
textGenerator: TextGenerator(),
);
}
}
/// Get current evaluation state
EvaluationState get state => _state;
/// Get list of courses
List<Course> get courses => _courses;
/// Get last batch evaluation result
BatchEvaluationResult? get lastResult => _lastResult;
/// Get current error message (if any)
String? get errorMessage => _errorMessage;
/// Get current progress (0.0 to 1.0)
double get progress =>
_totalProgress > 0 ? _currentProgress / _totalProgress : 0.0;
/// Get current progress count
int get currentProgress => _currentProgress;
/// Get total progress count
int get totalProgress => _totalProgress;
/// Get current course being evaluated
Course? get currentCourse => _currentCourse;
/// Get current status message
String? get currentStatus => _currentStatus;
/// Get evaluation logs
List<String> get logs => List.unmodifiable(_logs);
/// Get countdown remaining seconds
int get countdownRemaining => _countdownRemaining;
/// Get countdown total seconds
int get countdownTotal => _countdownTotal;
/// Get countdown progress (0.0 to 1.0)
double get countdownProgress => _countdownTotal > 0
? (_countdownTotal - _countdownRemaining) / _countdownTotal
: 0.0;
/// Get concurrent tasks
List<ConcurrentTask> get concurrentTasks =>
List.unmodifiable(_concurrentTasks);
/// Check if in concurrent mode
bool get isConcurrentMode => _isConcurrentMode;
/// Add a log entry
void _addLog(String message, {bool updateLast = false}) {
final timestamp = DateTime.now().toString().substring(11, 19);
final logEntry = '[$timestamp] $message';
if (updateLast && _logs.isNotEmpty) {
// Update the last log entry
_logs[_logs.length - 1] = logEntry;
} else {
// Add new log entry
_logs.add(logEntry);
// Keep only last 100 logs
if (_logs.length > 100) {
_logs.removeAt(0);
}
}
notifyListeners();
}
/// Clear logs
void clearLogs() {
_logs.clear();
notifyListeners();
}
/// Get pending courses (not yet evaluated)
List<Course> get pendingCourses =>
_courses.where((c) => !c.isEvaluated).toList();
/// Get evaluated courses
List<Course> get evaluatedCourses =>
_courses.where((c) => c.isEvaluated).toList();
/// Get failed courses from last batch evaluation
List<Course> get failedCourses {
if (_lastResult == null) return [];
return _lastResult!.results
.where((r) => !r.success)
.map((r) => r.course)
.toList();
}
/// Get evaluation history
List<EvaluationHistory> get evaluationHistory => _evaluationHistory;
/// Load evaluation history from storage
Future<void> _loadEvaluationHistory() async {
try {
_evaluationHistory = await _storageService.loadEvaluationHistory();
notifyListeners();
} catch (e) {
debugPrint('Failed to load evaluation history: $e');
}
}
/// Refresh evaluation history from storage
Future<void> refreshEvaluationHistory() async {
await _loadEvaluationHistory();
}
/// Clear evaluation history
Future<void> clearEvaluationHistory() async {
try {
await _storageService.clearEvaluationHistory();
_evaluationHistory = [];
notifyListeners();
} catch (e) {
debugPrint('Failed to clear evaluation history: $e');
}
}
/// Save evaluation results to history
Future<void> _saveEvaluationResults(List<EvaluationResult> results) async {
try {
final histories = results.map((result) {
return EvaluationHistory(
id: '${result.course.id}_${result.timestamp.millisecondsSinceEpoch}',
course: result.course,
timestamp: result.timestamp,
success: result.success,
errorMessage: result.errorMessage,
);
}).toList();
await _storageService.saveEvaluationHistories(histories);
await _loadEvaluationHistory();
} catch (e) {
debugPrint('Failed to save evaluation results: $e');
}
}
/// Load courses from the server
///
/// Fetches the list of courses that need evaluation
/// Updates state and notifies listeners
///
/// Returns true if successful, false otherwise
Future<bool> loadCourses() async {
print('📚 loadCourses called');
print('📚 _connection: $_connection');
if (_connection == null) {
_errorMessage = '未设置连接';
_setState(EvaluationState.error);
print('❌ No connection set');
return false;
}
try {
print('📚 Setting state to loading...');
_setState(EvaluationState.loading);
_errorMessage = null;
print('📚 Calling _connection.fetchCourseList()...');
final courses = await _connection.fetchCourseList();
print('📚 Received ${courses.length} courses');
_courses = courses;
_setState(EvaluationState.idle);
return true;
} catch (e, stackTrace) {
_errorMessage = '加载课程列表失败: $e';
_setState(EvaluationState.error);
print('❌ Error loading courses: $e');
print('❌ Stack trace: $stackTrace');
return false;
}
}
/// Start batch evaluation of all pending courses
///
/// Evaluates all courses that haven't been evaluated yet
/// Shows notifications for progress and completion
/// Updates state and progress in real-time
///
/// Returns the batch evaluation result
Future<BatchEvaluationResult?> startBatchEvaluation() async {
if (_service == null) {
_errorMessage = '评教服务未初始化';
_setState(EvaluationState.error);
return null;
}
try {
_setState(EvaluationState.evaluating);
_errorMessage = null;
// Get pending courses
final pending = pendingCourses;
if (pending.isEmpty) {
_errorMessage = '没有待评课程';
_setState(EvaluationState.idle);
return null;
}
// Initialize progress
_currentProgress = 0;
_totalProgress = pending.length;
_currentCourse = null;
_currentStatus = null;
notifyListeners();
// Show start notification
await _notificationService.showBatchStartNotification(_totalProgress);
// Clear previous logs
_logs.clear();
_addLog('开始批量评教,共 $_totalProgress 门课程');
// Start batch evaluation with custom logic
final results = <EvaluationResult>[];
final startTime = DateTime.now();
for (int i = 0; i < pending.length; i++) {
final course = pending[i];
_currentProgress = i;
_currentCourse = course;
_currentStatus = '准备评教';
notifyListeners();
_addLog(
'❤ Created By LoveACE Team, 🌧 Powered By Sibuxiangx & Flutter',
);
_addLog('开始评教: ${course.name} (${course.teacher})');
// 1. Prepare evaluation
_currentStatus = '访问评价页面';
notifyListeners();
_addLog('📝 ${course.name}: 访问评价页面');
final formData = await _service!.prepareEvaluation(
course,
totalCourses: _totalProgress,
);
if (formData == null) {
final result = EvaluationResult(
course: course,
success: false,
errorMessage: '无法访问评价页面',
);
results.add(result);
_addLog('${course.name}: 访问失败,任务中断');
// Stop on error
_currentProgress = i + 1;
break;
}
_currentStatus = '解析问卷';
_addLog('📝 ${course.name}: 解析问卷');
_currentStatus = '生成答案';
_addLog('📝 ${course.name}: 生成答案');
// 2. Countdown (140 seconds)
_currentStatus = '等待提交';
_countdownTotal = 140;
for (int countdown = 140; countdown > 0; countdown--) {
_countdownRemaining = countdown;
notifyListeners();
await Future.delayed(const Duration(seconds: 1));
}
_countdownRemaining = 0;
_countdownTotal = 0;
// 3. Submit evaluation
_currentStatus = '提交评价';
notifyListeners();
_addLog('📝 ${course.name}: 提交评价');
final result = await _service!.submitEvaluation(course, formData);
results.add(result);
if (!result.success) {
_addLog('${course.name}: 评教失败,任务中断');
_currentProgress = i + 1;
break;
}
// 4. Verify evaluation
_currentStatus = '验证结果';
_addLog('📝 ${course.name}: 验证结果');
final updatedCourses = await _connection!.fetchCourseList();
final updatedCourse = updatedCourses.firstWhere(
(c) => c.id == course.id,
orElse: () => course,
);
if (!updatedCourse.isEvaluated) {
results[results.length - 1] = EvaluationResult(
course: course,
success: false,
errorMessage: '评教未生效,服务器未确认',
);
_addLog('${course.name}: 评教未生效,任务中断');
_currentProgress = i + 1;
break;
}
_addLog('${course.name}: 评教完成');
_currentProgress = i + 1;
_currentStatus = '评教完成';
notifyListeners();
// Small delay between courses
if (i < pending.length - 1) {
await Future.delayed(const Duration(milliseconds: 500));
}
}
// Calculate statistics
final successCount = results.where((r) => r.success).length;
final failedCount = results.where((r) => !r.success).length;
final duration = DateTime.now().difference(startTime);
final result = BatchEvaluationResult(
total: results.length,
success: successCount,
failed: failedCount,
results: results,
duration: duration,
);
// Save result
_lastResult = result;
// Save evaluation results to history
await _saveEvaluationResults(result.results);
// Add completion log
if (result.failed > 0) {
_addLog(
'❌ 批量评教中断: 成功 ${result.success}/${result.total},失败 ${result.failed}',
);
} else {
_addLog('✅ 批量评教完成: 全部 ${result.total} 门课程评教成功');
}
// Show completion notification
await _notificationService.showCompletionNotification(
success: result.success,
failed: result.failed,
total: result.total,
);
// Reload courses to update evaluation status
_addLog('刷新课程列表...');
await loadCourses();
_addLog('课程列表已更新');
_setState(EvaluationState.completed);
return result;
} catch (e) {
_errorMessage = '批量评教失败: $e';
_setState(EvaluationState.error);
// Show error notification
await _notificationService.showErrorNotification(_errorMessage!);
return null;
}
}
/// Retry evaluation for failed courses
///
/// Re-evaluates only the courses that failed in the last batch evaluation
/// Useful for handling temporary network issues or server errors
///
/// Returns the batch evaluation result for retry attempt
Future<BatchEvaluationResult?> retryFailed() async {
if (_service == null) {
_errorMessage = '评教服务未初始化';
_setState(EvaluationState.error);
return null;
}
if (_lastResult == null || failedCourses.isEmpty) {
_errorMessage = '没有失败的课程需要重试';
return null;
}
try {
_setState(EvaluationState.evaluating);
_errorMessage = null;
final failedList = failedCourses;
// Initialize progress
_currentProgress = 0;
_totalProgress = failedList.length;
_currentCourse = null;
_currentStatus = null;
notifyListeners();
// Show start notification
await _notificationService.showBatchStartNotification(_totalProgress);
// Evaluate each failed course
final results = <EvaluationResult>[];
final startTime = DateTime.now();
for (int i = 0; i < failedList.length; i++) {
final course = failedList[i];
// Update progress
_currentProgress = i + 1;
_currentCourse = course;
_currentStatus = '正在重试...';
notifyListeners();
// Update notification
await _notificationService.updateProgressNotification(
current: i + 1,
total: _totalProgress,
courseName: course.name,
);
// Evaluate course
final result = await _service!.evaluateCourse(
course,
onStatusChange: (status) {
_currentStatus = status;
notifyListeners();
},
);
results.add(result);
// Small delay between evaluations
if (i < failedList.length - 1) {
await Future.delayed(const Duration(milliseconds: 500));
}
}
// Calculate statistics
final successCount = results.where((r) => r.success).length;
final failedCount = results.where((r) => !r.success).length;
final duration = DateTime.now().difference(startTime);
final retryResult = BatchEvaluationResult(
total: failedList.length,
success: successCount,
failed: failedCount,
results: results,
duration: duration,
);
// Update last result
_lastResult = retryResult;
// Save retry results to history
await _saveEvaluationResults(results);
// Show completion notification
await _notificationService.showCompletionNotification(
success: successCount,
failed: failedCount,
total: failedList.length,
);
// Reload courses
await loadCourses();
_setState(EvaluationState.completed);
return retryResult;
} catch (e) {
_errorMessage = '重试失败: $e';
_setState(EvaluationState.error);
// Show error notification
await _notificationService.showErrorNotification(_errorMessage!);
return null;
}
}
/// Evaluate a single course
///
/// [course] - The course to evaluate
///
/// Returns the evaluation result
Future<EvaluationResult?> evaluateSingleCourse(Course course) async {
if (_service == null) {
_errorMessage = '评教服务未初始化';
_setState(EvaluationState.error);
return null;
}
try {
_setState(EvaluationState.evaluating);
_errorMessage = null;
_currentProgress = 0;
_totalProgress = 1;
_currentCourse = course;
_currentStatus = '正在评教...';
notifyListeners();
final result = await _service!.evaluateCourse(
course,
onStatusChange: (status) {
_currentStatus = status;
notifyListeners();
},
);
// Save single evaluation result to history
await _saveEvaluationResults([result]);
if (result.success) {
// Reload courses to update status
await loadCourses();
}
_setState(EvaluationState.completed);
return result;
} catch (e) {
_errorMessage = '评教失败: $e';
_setState(EvaluationState.error);
return null;
}
}
/// Clear last evaluation result
void clearLastResult() {
_lastResult = null;
_currentProgress = 0;
_totalProgress = 0;
_currentCourse = null;
_currentStatus = null;
notifyListeners();
}
/// Reset provider state
void reset() {
_courses = [];
_state = EvaluationState.idle;
_lastResult = null;
_errorMessage = null;
_currentProgress = 0;
_totalProgress = 0;
_currentCourse = null;
_currentStatus = null;
_logs.clear();
notifyListeners();
}
/// Update evaluation state and notify listeners
void _setState(EvaluationState newState) {
_state = newState;
notifyListeners();
}
/// Start concurrent batch evaluation
///
/// Evaluates all pending courses concurrently with 6-second intervals
/// Each task runs independently with its own 140-second countdown
///
/// Returns the batch evaluation result
Future<BatchEvaluationResult?> startConcurrentBatchEvaluation() async {
if (_service == null) {
_errorMessage = '评教服务未初始化';
_setState(EvaluationState.error);
return null;
}
try {
_setState(EvaluationState.evaluating);
_errorMessage = null;
_isConcurrentMode = true;
// Get pending courses
final pending = pendingCourses;
if (pending.isEmpty) {
_errorMessage = '没有待评课程';
_setState(EvaluationState.idle);
_isConcurrentMode = false;
return null;
}
// Initialize
_currentProgress = 0;
_totalProgress = pending.length;
_concurrentTasks.clear();
_logs.clear();
notifyListeners();
// Show start notification
await _notificationService.showBatchStartNotification(_totalProgress);
_addLog('开始并发批量评教,共 $_totalProgress 门课程');
// Create tasks
for (int i = 0; i < pending.length; i++) {
_concurrentTasks.add(
ConcurrentTask(
taskId: i + 1,
course: pending[i],
status: TaskStatus.waiting,
),
);
}
notifyListeners();
// Start tasks with 6-second intervals
final taskFutures = <Future<EvaluationResult>>[];
final startTime = DateTime.now();
for (int i = 0; i < _concurrentTasks.length; i++) {
final task = _concurrentTasks[i];
// Wait 6 seconds before starting next task (except first one)
if (i > 0) {
_addLog('等待 6 秒后启动下一个任务... (${i + 1}/$_totalProgress)');
await Future.delayed(const Duration(seconds: 6));
}
// Start task
_addLog('启动任务 ${task.taskId}: ${task.course.name}');
taskFutures.add(_executeConcurrentTask(task));
}
// Wait for all tasks to complete
_addLog('所有任务已启动,等待完成...');
final results = await Future.wait(taskFutures);
// Calculate statistics
final successCount = results.where((r) => r.success).length;
final failedCount = results.where((r) => !r.success).length;
final duration = DateTime.now().difference(startTime);
final result = BatchEvaluationResult(
total: results.length,
success: successCount,
failed: failedCount,
results: results,
duration: duration,
);
// Save result
_lastResult = result;
_currentProgress = _totalProgress;
// Save evaluation results to history
await _saveEvaluationResults(result.results);
// Add completion log
if (result.failed > 0) {
_addLog(
'⚠️ 并发评教完成: 成功 ${result.success}/${result.total},失败 ${result.failed}',
);
} else {
_addLog('✅ 并发评教完成: 全部 ${result.total} 门课程评教成功');
}
// Show completion notification
await _notificationService.showCompletionNotification(
success: result.success,
failed: result.failed,
total: result.total,
);
// Reload courses to update evaluation status
_addLog('刷新课程列表...');
await loadCourses();
_addLog('课程列表已更新');
_setState(EvaluationState.completed);
_isConcurrentMode = false;
return result;
} catch (e) {
_errorMessage = '并发评教失败: $e';
_setState(EvaluationState.error);
_isConcurrentMode = false;
// Show error notification
await _notificationService.showErrorNotification(_errorMessage!);
return null;
}
}
/// Execute a single concurrent task
Future<EvaluationResult> _executeConcurrentTask(ConcurrentTask task) async {
try {
// Update task status
_updateTaskStatus(
task.taskId,
TaskStatus.preparing,
'❤ Created By LoveACE Team, 🌧 Powered By Sibuxiangx & Flutter',
);
_addLog('任务 ${task.taskId} [${task.course.name}]: 开始评教');
task.startTime = DateTime.now();
// 1. Prepare evaluation
_updateTaskStatus(task.taskId, TaskStatus.preparing, '访问评价页面');
_addLog('任务 ${task.taskId} [${task.course.name}]: 访问评价页面');
final formData = await _service!.prepareEvaluation(
task.course,
totalCourses: _totalProgress,
);
if (formData == null) {
_updateTaskStatus(
task.taskId,
TaskStatus.failed,
'访问失败',
errorMessage: '无法访问评价页面',
);
_addLog('任务 ${task.taskId} [${task.course.name}]: ❌ 访问失败');
return EvaluationResult(
course: task.course,
success: false,
errorMessage: '无法访问评价页面',
);
}
_updateTaskStatus(task.taskId, TaskStatus.preparing, '解析问卷');
_addLog('任务 ${task.taskId} [${task.course.name}]: 解析问卷');
_updateTaskStatus(task.taskId, TaskStatus.preparing, '生成答案');
_addLog('任务 ${task.taskId} [${task.course.name}]: 生成答案');
// 2. Countdown (140 seconds) - independent for each task
_updateTaskStatus(
task.taskId,
TaskStatus.countdown,
'等待提交',
countdownTotal: 140,
);
_addLog('任务 ${task.taskId} [${task.course.name}]: 开始独立等待 140 秒');
for (int countdown = 140; countdown > 0; countdown--) {
_updateTaskCountdown(task.taskId, countdown, 140);
await Future.delayed(const Duration(seconds: 1));
}
_addLog('任务 ${task.taskId} [${task.course.name}]: 等待完成');
// 3. Submit evaluation
_updateTaskStatus(task.taskId, TaskStatus.submitting, '提交评价');
_addLog('任务 ${task.taskId} [${task.course.name}]: 提交评价');
final result = await _service!.submitEvaluation(task.course, formData);
if (!result.success) {
_updateTaskStatus(
task.taskId,
TaskStatus.failed,
'提交失败',
errorMessage: result.errorMessage,
);
_addLog('任务 ${task.taskId} [${task.course.name}]: ❌ 提交失败');
return result;
}
// 4. Verify evaluation
_updateTaskStatus(task.taskId, TaskStatus.verifying, '验证结果');
_addLog('任务 ${task.taskId} [${task.course.name}]: 验证结果');
final updatedCourses = await _connection!.fetchCourseList();
final updatedCourse = updatedCourses.firstWhere(
(c) => c.id == task.course.id,
orElse: () => task.course,
);
if (!updatedCourse.isEvaluated) {
_updateTaskStatus(
task.taskId,
TaskStatus.failed,
'验证失败',
errorMessage: '评教未生效,服务器未确认',
);
_addLog('任务 ${task.taskId} [${task.course.name}]: ❌ 评教未生效');
return EvaluationResult(
course: task.course,
success: false,
errorMessage: '评教未生效,服务器未确认',
);
}
// Success
_updateTaskStatus(task.taskId, TaskStatus.completed, '完成');
_addLog('任务 ${task.taskId} [${task.course.name}]: ✅ 评教完成');
_currentProgress++;
notifyListeners();
task.endTime = DateTime.now();
return EvaluationResult(course: task.course, success: true);
} catch (e) {
_updateTaskStatus(
task.taskId,
TaskStatus.failed,
'异常',
errorMessage: e.toString(),
);
_addLog('任务 ${task.taskId} [${task.course.name}]: ❌ 异常: $e');
return EvaluationResult(
course: task.course,
success: false,
errorMessage: '评教过程出错: $e',
);
}
}
/// Update task status
void _updateTaskStatus(
int taskId,
TaskStatus status,
String? statusMessage, {
String? errorMessage,
int? countdownTotal,
}) {
final index = _concurrentTasks.indexWhere((t) => t.taskId == taskId);
if (index != -1) {
_concurrentTasks[index] = _concurrentTasks[index].copyWith(
status: status,
statusMessage: statusMessage,
errorMessage: errorMessage,
countdownTotal: countdownTotal,
);
notifyListeners();
}
}
/// Update task countdown
void _updateTaskCountdown(int taskId, int remaining, int total) {
final index = _concurrentTasks.indexWhere((t) => t.taskId == taskId);
if (index != -1) {
_concurrentTasks[index] = _concurrentTasks[index].copyWith(
countdownRemaining: remaining,
countdownTotal: total,
);
notifyListeners();
}
}
}