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(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 _courses = []; EvaluationState _state = EvaluationState.idle; BatchEvaluationResult? _lastResult; String? _errorMessage; List _evaluationHistory = []; // Progress tracking int _currentProgress = 0; int _totalProgress = 0; Course? _currentCourse; String? _currentStatus; final List _logs = []; // Countdown tracking int _countdownRemaining = 0; int _countdownTotal = 0; // Concurrent evaluation tracking final List _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 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 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 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 get pendingCourses => _courses.where((c) => !c.isEvaluated).toList(); /// Get evaluated courses List get evaluatedCourses => _courses.where((c) => c.isEvaluated).toList(); /// Get failed courses from last batch evaluation List get failedCourses { if (_lastResult == null) return []; return _lastResult!.results .where((r) => !r.success) .map((r) => r.course) .toList(); } /// Get evaluation history List get evaluationHistory => _evaluationHistory; /// Load evaluation history from storage Future _loadEvaluationHistory() async { try { _evaluationHistory = await _storageService.loadEvaluationHistory(); notifyListeners(); } catch (e) { debugPrint('Failed to load evaluation history: $e'); } } /// Refresh evaluation history from storage Future refreshEvaluationHistory() async { await _loadEvaluationHistory(); } /// Clear evaluation history Future clearEvaluationHistory() async { try { await _storageService.clearEvaluationHistory(); _evaluationHistory = []; notifyListeners(); } catch (e) { debugPrint('Failed to clear evaluation history: $e'); } } /// Save evaluation results to history Future _saveEvaluationResults(List 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 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 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 = []; 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 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 = []; 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 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 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 = >[]; 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 _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(); } } }