import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../providers/evaluation_provider.dart'; /// Progress screen showing real-time evaluation progress /// /// Displays progress percentage, current course, and statistics /// Allows canceling the evaluation process class ProgressScreen extends StatelessWidget { const ProgressScreen({super.key}); Future _onWillPop(BuildContext context, EvaluationState state) async { // 如果已完成或出错,允许直接返回 if (state == EvaluationState.completed || state == EvaluationState.error) { return true; } // 如果正在评教,显示确认对话框 final shouldPop = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('确认中断'), content: const Text('评教正在进行中,确定要中断吗?\n\n中断后当前进度将丢失。'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('继续评教'), ), TextButton( onPressed: () => Navigator.of(context).pop(true), style: TextButton.styleFrom( foregroundColor: Theme.of(context).colorScheme.error, ), child: const Text('中断'), ), ], ), ); return shouldPop ?? false; } @override Widget build(BuildContext context) { return Consumer( builder: (context, evaluationProvider, child) { final state = evaluationProvider.state; final progress = evaluationProvider.progress; final current = evaluationProvider.currentProgress; final total = evaluationProvider.totalProgress; final currentCourse = evaluationProvider.currentCourse; final currentStatus = evaluationProvider.currentStatus; final lastResult = evaluationProvider.lastResult; return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) async { if (didPop) return; final shouldPop = await _onWillPop(context, state); if (shouldPop && context.mounted) { Navigator.of(context).pop(); } }, child: Scaffold( appBar: AppBar( title: const Text('评教进度'), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () async { final shouldPop = await _onWillPop(context, state); if (shouldPop && context.mounted) { Navigator.of(context).pop(); } }, ), ), body: Builder( builder: (context) { // Show completion screen if (state == EvaluationState.completed && lastResult != null) { return _buildCompletionScreen(context, lastResult); } // Show error screen if (state == EvaluationState.error) { return _buildErrorScreen( context, evaluationProvider.errorMessage ?? '评教失败', ); } // Show progress screen return _buildProgressScreen( context, progress, current, total, currentCourse, currentStatus, ); }, ), ), ); }, ); } Widget _buildProgressScreen( BuildContext context, double progress, int current, int total, dynamic currentCourse, String? status, ) { return Column( children: [ // Top: Overall progress info Expanded( flex: 2, child: _buildProgressContent( context, progress, current, total, currentCourse, status, ), ), // Bottom: Task list Expanded(flex: 3, child: _buildTaskListPanel(context)), ], ); } Widget _buildProgressContent( BuildContext context, double progress, int current, int total, dynamic currentCourse, String? status, ) { return Center( child: Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: Theme.of(context).colorScheme.primaryContainer, borderRadius: BorderRadius.circular(16), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Progress percentage Text( '${(progress * 100).toInt()}%', style: Theme.of(context).textTheme.displayMedium?.copyWith( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onPrimaryContainer, ), ), const SizedBox(height: 16), // Progress bar ClipRRect( borderRadius: BorderRadius.circular(12), child: LinearProgressIndicator( value: progress, minHeight: 24, backgroundColor: Theme.of( context, ).colorScheme.onPrimaryContainer.withValues(alpha: 0.2), valueColor: AlwaysStoppedAnimation( Theme.of(context).colorScheme.onPrimaryContainer, ), ), ), const SizedBox(height: 16), // Complete / All Text( '$current / $total 节', style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onPrimaryContainer, ), ), ], ), ), ); } Widget _buildTaskListPanel(BuildContext context) { return Consumer( builder: (context, provider, child) { final tasks = provider.concurrentTasks; return Container( margin: const EdgeInsets.all(8), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(12), border: Border.all( color: Theme.of( context, ).colorScheme.outline.withValues(alpha: 0.2), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Header Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceContainerHigh, borderRadius: const BorderRadius.only( topLeft: Radius.circular(12), topRight: Radius.circular(12), ), ), child: Row( children: [ Icon( Icons.list_alt, size: 20, color: Theme.of(context).colorScheme.onSurfaceVariant, ), const SizedBox(width: 8), Text( '并发任务列表', style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, ), ), const Spacer(), Text( '${tasks.length} 个任务', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], ), ), // Task list Expanded( child: tasks.isEmpty ? Center( child: Text( '暂无任务', style: Theme.of(context).textTheme.bodyMedium ?.copyWith( color: Theme.of( context, ).colorScheme.onSurfaceVariant, ), ), ) : ListView.builder( padding: const EdgeInsets.all(8), itemCount: tasks.length, itemBuilder: (context, index) { final task = tasks[index]; return _buildTaskCard(context, task); }, ), ), ], ), ); }, ); } Widget _buildTaskCard(BuildContext context, dynamic task) { final statusColor = _getTaskStatusColor(context, task.status); final statusIcon = _getTaskStatusIcon(task.status); return Card( margin: const EdgeInsets.only(bottom: 8), child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Task header Row( children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: statusColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), border: Border.all( color: statusColor.withValues(alpha: 0.3), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(statusIcon, size: 14, color: statusColor), const SizedBox(width: 4), Text( '任务 ${task.taskId}', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: statusColor, fontWeight: FontWeight.bold, ), ), ], ), ), const SizedBox(width: 8), Expanded( child: Text( task.course.name, style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 8), // Teacher info Row( children: [ Icon( Icons.person, size: 14, color: Theme.of(context).colorScheme.onSurfaceVariant, ), const SizedBox(width: 4), Text( task.course.teacher, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], ), const SizedBox(height: 8), // Status and progress Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( task.statusText, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: statusColor, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 4), ClipRRect( borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: task.progress, minHeight: 6, backgroundColor: Theme.of( context, ).colorScheme.surfaceContainerHighest, valueColor: AlwaysStoppedAnimation( statusColor, ), ), ), ], ), ), const SizedBox(width: 8), Text( '${(task.progress * 100).toInt()}%', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: statusColor, fontWeight: FontWeight.bold, ), ), ], ), // Error message if failed if (task.status.toString().contains('failed') && task.errorMessage != null) ...[ const SizedBox(height: 8), Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Theme.of(context).colorScheme.errorContainer, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon( Icons.error_outline, size: 16, color: Theme.of(context).colorScheme.onErrorContainer, ), const SizedBox(width: 8), Expanded( child: Text( task.errorMessage, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onErrorContainer, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ], ), ), ], ], ), ), ); } Color _getTaskStatusColor(BuildContext context, dynamic status) { final statusStr = status.toString(); if (statusStr.contains('completed')) { return Colors.green; } else if (statusStr.contains('failed')) { return Theme.of(context).colorScheme.error; } else if (statusStr.contains('countdown')) { return Theme.of(context).colorScheme.tertiary; } else if (statusStr.contains('submitting') || statusStr.contains('verifying')) { return Theme.of(context).colorScheme.secondary; } else if (statusStr.contains('preparing')) { return Theme.of(context).colorScheme.primary; } else { return Theme.of(context).colorScheme.onSurfaceVariant; } } IconData _getTaskStatusIcon(dynamic status) { final statusStr = status.toString(); if (statusStr.contains('completed')) { return Icons.check_circle; } else if (statusStr.contains('failed')) { return Icons.error; } else if (statusStr.contains('countdown')) { return Icons.timer; } else if (statusStr.contains('submitting')) { return Icons.upload; } else if (statusStr.contains('verifying')) { return Icons.verified; } else if (statusStr.contains('preparing')) { return Icons.settings; } else { return Icons.pending; } } Widget _buildCompletionScreen(BuildContext context, dynamic result) { final isAllSuccess = result.failed == 0; return SingleChildScrollView( padding: const EdgeInsets.all(24.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: 40), // Success/Warning icon Icon( isAllSuccess ? Icons.check_circle : Icons.warning, size: 100, color: isAllSuccess ? Colors.green : Theme.of(context).colorScheme.error, ), const SizedBox(height: 24), // Title Text( isAllSuccess ? '评教完成' : '评教完成(部分失败)', style: Theme.of( context, ).textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.bold), ), const SizedBox(height: 32), // Statistics _buildResultCard( context, '总计', result.total.toString(), Icons.list_alt, Theme.of(context).colorScheme.primary, ), const SizedBox(height: 12), _buildResultCard( context, '成功', result.success.toString(), Icons.check_circle, Colors.green, ), if (result.failed > 0) ...[ const SizedBox(height: 12), _buildResultCard( context, '失败', result.failed.toString(), Icons.error, Theme.of(context).colorScheme.error, ), ], const SizedBox(height: 12), _buildResultCard( context, '耗时', '${result.duration.inSeconds}秒', Icons.timer, Theme.of(context).colorScheme.secondary, ), const SizedBox(height: 48), // Action buttons Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (result.failed > 0) ElevatedButton.icon( onPressed: () { final evaluationProvider = Provider.of( context, listen: false, ); evaluationProvider.retryFailed(); }, icon: const Icon(Icons.refresh), label: const Text('重试失败'), style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.error, foregroundColor: Theme.of(context).colorScheme.onError, ), ), if (result.failed > 0) const SizedBox(width: 16), ElevatedButton.icon( onPressed: () { Navigator.of(context).pop(); }, icon: const Icon(Icons.home), label: const Text('返回首页'), ), ], ), const SizedBox(height: 40), ], ), ); } Widget _buildResultCard( BuildContext context, String label, String value, IconData icon, Color color, ) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), border: Border.all(color: color.withValues(alpha: 0.3)), ), child: Row( children: [ Icon(icon, color: color, size: 32), const SizedBox(width: 16), Expanded( child: Text(label, style: Theme.of(context).textTheme.titleMedium), ), Text( value, style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, color: color, ), ), ], ), ); } Widget _buildErrorScreen(BuildContext context, String errorMessage) { return SingleChildScrollView( padding: const EdgeInsets.all(24.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: 80), Icon( Icons.error_outline, size: 100, color: Theme.of(context).colorScheme.error, ), const SizedBox(height: 24), Text( '评教失败', style: Theme.of( context, ).textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.bold), ), const SizedBox(height: 16), Text( errorMessage, style: Theme.of(context).textTheme.bodyLarge, textAlign: TextAlign.center, ), const SizedBox(height: 48), ElevatedButton.icon( onPressed: () { Navigator.of(context).pop(); }, icon: const Icon(Icons.home), label: const Text('返回首页'), ), const SizedBox(height: 80), ], ), ); } }