😋 初始化仓库
This commit is contained in:
2
lib/providers/.gitkeep
Normal file
2
lib/providers/.gitkeep
Normal file
@@ -0,0 +1,2 @@
|
||||
# Providers directory
|
||||
# This directory contains state management providers
|
||||
273
lib/providers/auth_provider.dart
Normal file
273
lib/providers/auth_provider.dart
Normal file
@@ -0,0 +1,273 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../models/user_credentials.dart';
|
||||
import '../services/aufe_connection.dart';
|
||||
|
||||
/// Authentication state enum
|
||||
enum AuthState { initial, loading, authenticated, unauthenticated, error }
|
||||
|
||||
/// Provider for managing authentication state and user sessions
|
||||
///
|
||||
/// Handles login, logout, session checking, and credential management
|
||||
/// Uses ChangeNotifier to notify listeners of state changes
|
||||
///
|
||||
/// Usage example:
|
||||
/// ```dart
|
||||
/// final authProvider = Provider.of<AuthProvider>(context);
|
||||
///
|
||||
/// // Login
|
||||
/// await authProvider.login(
|
||||
/// userId: '学号',
|
||||
/// ecPassword: 'EC密码',
|
||||
/// password: 'UAAP密码',
|
||||
/// );
|
||||
///
|
||||
/// // Check session
|
||||
/// final isValid = await authProvider.checkSession();
|
||||
///
|
||||
/// // Logout
|
||||
/// await authProvider.logout();
|
||||
/// ```
|
||||
class AuthProvider extends ChangeNotifier {
|
||||
AUFEConnection? _connection;
|
||||
AuthState _state = AuthState.initial;
|
||||
String? _errorMessage;
|
||||
UserCredentials? _credentials;
|
||||
|
||||
/// Get current authentication state
|
||||
AuthState get state => _state;
|
||||
|
||||
/// Get current error message (if any)
|
||||
String? get errorMessage => _errorMessage;
|
||||
|
||||
/// Get current connection instance
|
||||
AUFEConnection? get connection => _connection;
|
||||
|
||||
/// Get current user credentials
|
||||
UserCredentials? get credentials => _credentials;
|
||||
|
||||
/// Check if user is authenticated
|
||||
bool get isAuthenticated => _state == AuthState.authenticated;
|
||||
|
||||
/// Login with user credentials
|
||||
///
|
||||
/// Creates AUFEConnection and performs both EC and UAAP login
|
||||
/// Saves credentials securely on successful login
|
||||
///
|
||||
/// [userId] - Student ID
|
||||
/// [ecPassword] - EC system password
|
||||
/// [password] - UAAP system password
|
||||
///
|
||||
/// Returns true if login succeeds, false otherwise
|
||||
Future<bool> login({
|
||||
required String userId,
|
||||
required String ecPassword,
|
||||
required String password,
|
||||
}) async {
|
||||
try {
|
||||
print('🔐 Starting login process...');
|
||||
print('🔐 User ID: $userId');
|
||||
|
||||
_setState(AuthState.loading);
|
||||
_errorMessage = null;
|
||||
|
||||
// Create credentials
|
||||
final credentials = UserCredentials(
|
||||
userId: userId,
|
||||
ecPassword: ecPassword,
|
||||
password: password,
|
||||
);
|
||||
|
||||
// Create connection
|
||||
print('🔐 Creating AUFEConnection...');
|
||||
final connection = AUFEConnection(
|
||||
userId: userId,
|
||||
ecPassword: ecPassword,
|
||||
password: password,
|
||||
);
|
||||
|
||||
// Initialize HTTP client
|
||||
print('🔐 Starting HTTP client...');
|
||||
connection.startClient();
|
||||
|
||||
// Perform EC login
|
||||
print('🔐 Performing EC login...');
|
||||
final ecLoginStatus = await connection.ecLogin();
|
||||
print('🔐 EC login result: ${ecLoginStatus.success}');
|
||||
|
||||
if (!ecLoginStatus.success) {
|
||||
_errorMessage = _getEcLoginErrorMessage(ecLoginStatus);
|
||||
print('❌ EC login failed: $_errorMessage');
|
||||
_setState(AuthState.error);
|
||||
await connection.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Perform UAAP login
|
||||
print('🔐 Performing UAAP login...');
|
||||
final uaapLoginStatus = await connection.uaapLogin();
|
||||
print('🔐 UAAP login result: ${uaapLoginStatus.success}');
|
||||
|
||||
if (!uaapLoginStatus.success) {
|
||||
_errorMessage = _getUaapLoginErrorMessage(uaapLoginStatus);
|
||||
print('❌ UAAP login failed: $_errorMessage');
|
||||
_setState(AuthState.error);
|
||||
await connection.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save credentials securely
|
||||
print('🔐 Saving credentials...');
|
||||
await credentials.saveSecurely();
|
||||
|
||||
// Update state
|
||||
_connection = connection;
|
||||
_credentials = credentials;
|
||||
_setState(AuthState.authenticated);
|
||||
|
||||
print('✅ Login successful!');
|
||||
return true;
|
||||
} catch (e, stackTrace) {
|
||||
_errorMessage = '登录过程出错: $e';
|
||||
print('❌ Login error: $e');
|
||||
print('❌ Stack trace: $stackTrace');
|
||||
_setState(AuthState.error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Logout and clear all session data
|
||||
///
|
||||
/// Closes connection, clears credentials from secure storage,
|
||||
/// and resets authentication state
|
||||
Future<void> logout() async {
|
||||
try {
|
||||
// Close connection
|
||||
if (_connection != null) {
|
||||
await _connection!.close();
|
||||
_connection = null;
|
||||
}
|
||||
|
||||
// Clear credentials from secure storage
|
||||
await UserCredentials.clearSecurely();
|
||||
|
||||
// Reset state
|
||||
_credentials = null;
|
||||
_errorMessage = null;
|
||||
_setState(AuthState.unauthenticated);
|
||||
} catch (e) {
|
||||
debugPrint('Error during logout: $e');
|
||||
// Still reset state even if cleanup fails
|
||||
_connection = null;
|
||||
_credentials = null;
|
||||
_setState(AuthState.unauthenticated);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if current session is still valid
|
||||
///
|
||||
/// Performs health check on the connection
|
||||
/// If session is invalid, updates state to unauthenticated
|
||||
///
|
||||
/// Returns true if session is valid, false otherwise
|
||||
Future<bool> checkSession() async {
|
||||
if (_connection == null || _state != AuthState.authenticated) {
|
||||
_setState(AuthState.unauthenticated);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final isHealthy = await _connection!.healthCheck();
|
||||
|
||||
if (!isHealthy) {
|
||||
_errorMessage = '会话已过期,请重新登录';
|
||||
_setState(AuthState.unauthenticated);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
_errorMessage = '检查会话状态失败: $e';
|
||||
_setState(AuthState.error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to restore session from saved credentials
|
||||
///
|
||||
/// Loads credentials from secure storage and attempts to login
|
||||
/// Useful for auto-login on app startup
|
||||
///
|
||||
/// Returns true if session restored successfully, false otherwise
|
||||
Future<bool> restoreSession() async {
|
||||
try {
|
||||
_setState(AuthState.loading);
|
||||
|
||||
// Load saved credentials
|
||||
final credentials = await UserCredentials.loadSecurely();
|
||||
if (credentials == null) {
|
||||
_setState(AuthState.unauthenticated);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attempt login with saved credentials
|
||||
return await login(
|
||||
userId: credentials.userId,
|
||||
ecPassword: credentials.ecPassword,
|
||||
password: credentials.password,
|
||||
);
|
||||
} catch (e) {
|
||||
_errorMessage = '恢复会话失败: $e';
|
||||
_setState(AuthState.unauthenticated);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Update authentication state and notify listeners
|
||||
void _setState(AuthState newState) {
|
||||
_state = newState;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Get user-friendly error message for EC login status
|
||||
String _getEcLoginErrorMessage(dynamic status) {
|
||||
if (status.failInvalidCredentials) {
|
||||
return 'EC系统用户名或密码错误';
|
||||
} else if (status.failNotFoundTwfid) {
|
||||
return '无法获取TwfID,请稍后重试';
|
||||
} else if (status.failNotFoundRsaKey) {
|
||||
return '无法获取RSA密钥,请稍后重试';
|
||||
} else if (status.failNotFoundRsaExp) {
|
||||
return '无法获取RSA指数,请稍后重试';
|
||||
} else if (status.failNotFoundCsrfCode) {
|
||||
return '无法获取CSRF代码,请稍后重试';
|
||||
} else if (status.failMaybeAttacked) {
|
||||
return '登录频繁,请稍后重试';
|
||||
} else if (status.failNetworkError) {
|
||||
return 'EC系统网络连接失败';
|
||||
} else {
|
||||
return 'EC系统登录失败';
|
||||
}
|
||||
}
|
||||
|
||||
/// Get user-friendly error message for UAAP login status
|
||||
String _getUaapLoginErrorMessage(dynamic status) {
|
||||
if (status.failInvalidCredentials) {
|
||||
return 'UAAP系统用户名或密码错误';
|
||||
} else if (status.failNotFoundLt) {
|
||||
return '无法获取lt参数,请稍后重试';
|
||||
} else if (status.failNotFoundExecution) {
|
||||
return '无法获取execution参数,请稍后重试';
|
||||
} else if (status.failNetworkError) {
|
||||
return 'UAAP系统网络连接失败';
|
||||
} else {
|
||||
return 'UAAP系统登录失败';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// Close connection when provider is disposed
|
||||
_connection?.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
935
lib/providers/evaluation_provider.dart
Normal file
935
lib/providers/evaluation_provider.dart
Normal file
@@ -0,0 +1,935 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
390
lib/providers/theme_provider.dart
Normal file
390
lib/providers/theme_provider.dart
Normal file
@@ -0,0 +1,390 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
/// Color scheme options for the app
|
||||
enum AppColorScheme { blue, green, purple, orange }
|
||||
|
||||
/// Provider for managing app theme and appearance settings
|
||||
///
|
||||
/// Handles theme mode (light/dark/system), color scheme selection,
|
||||
/// and persistence of user preferences
|
||||
///
|
||||
/// Usage example:
|
||||
/// ```dart
|
||||
/// final themeProvider = Provider.of<ThemeProvider>(context);
|
||||
///
|
||||
/// // Change theme mode
|
||||
/// themeProvider.setThemeMode(ThemeMode.dark);
|
||||
///
|
||||
/// // Change color scheme
|
||||
/// themeProvider.setColorScheme(AppColorScheme.green);
|
||||
///
|
||||
/// // Get current theme data
|
||||
/// final lightTheme = themeProvider.lightTheme;
|
||||
/// final darkTheme = themeProvider.darkTheme;
|
||||
/// ```
|
||||
class ThemeProvider extends ChangeNotifier {
|
||||
static const String _themeModeKey = 'theme_mode';
|
||||
static const String _colorSchemeKey = 'color_scheme';
|
||||
|
||||
ThemeMode _themeMode = ThemeMode.system;
|
||||
AppColorScheme _colorScheme = AppColorScheme.blue;
|
||||
|
||||
ThemeProvider() {
|
||||
_loadPreferences();
|
||||
}
|
||||
|
||||
/// Get current theme mode
|
||||
ThemeMode get themeMode => _themeMode;
|
||||
|
||||
/// Get current color scheme
|
||||
AppColorScheme get colorScheme => _colorScheme;
|
||||
|
||||
/// Get light theme data
|
||||
ThemeData get lightTheme => _buildLightTheme();
|
||||
|
||||
/// Get dark theme data
|
||||
ThemeData get darkTheme => _buildDarkTheme();
|
||||
|
||||
/// Set theme mode
|
||||
///
|
||||
/// [mode] - The theme mode to set (light, dark, or system)
|
||||
/// Saves preference and notifies listeners
|
||||
Future<void> setThemeMode(ThemeMode mode) async {
|
||||
if (_themeMode == mode) return;
|
||||
|
||||
_themeMode = mode;
|
||||
notifyListeners();
|
||||
await savePreferences();
|
||||
}
|
||||
|
||||
/// Set color scheme
|
||||
///
|
||||
/// [scheme] - The color scheme to set
|
||||
/// Saves preference and notifies listeners
|
||||
Future<void> setColorScheme(AppColorScheme scheme) async {
|
||||
if (_colorScheme == scheme) return;
|
||||
|
||||
_colorScheme = scheme;
|
||||
notifyListeners();
|
||||
await savePreferences();
|
||||
}
|
||||
|
||||
/// Save preferences to local storage
|
||||
Future<void> savePreferences() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_themeModeKey, _themeMode.name);
|
||||
await prefs.setString(_colorSchemeKey, _colorScheme.name);
|
||||
} catch (e) {
|
||||
debugPrint('Failed to save theme preferences: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Load preferences from local storage
|
||||
Future<void> _loadPreferences() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
// Load theme mode
|
||||
final themeModeStr = prefs.getString(_themeModeKey);
|
||||
if (themeModeStr != null) {
|
||||
_themeMode = ThemeMode.values.firstWhere(
|
||||
(mode) => mode.name == themeModeStr,
|
||||
orElse: () => ThemeMode.system,
|
||||
);
|
||||
}
|
||||
|
||||
// Load color scheme
|
||||
final colorSchemeStr = prefs.getString(_colorSchemeKey);
|
||||
if (colorSchemeStr != null) {
|
||||
_colorScheme = AppColorScheme.values.firstWhere(
|
||||
(scheme) => scheme.name == colorSchemeStr,
|
||||
orElse: () => AppColorScheme.blue,
|
||||
);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Failed to load theme preferences: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Build light theme based on current color scheme
|
||||
ThemeData _buildLightTheme() {
|
||||
final colorScheme = _getColorScheme(Brightness.light);
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
colorScheme: colorScheme,
|
||||
|
||||
// AppBar theme
|
||||
appBarTheme: AppBarTheme(
|
||||
centerTitle: true,
|
||||
elevation: 0,
|
||||
backgroundColor: colorScheme.primary,
|
||||
foregroundColor: colorScheme.onPrimary,
|
||||
),
|
||||
|
||||
// Card theme
|
||||
cardTheme: const CardThemeData(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
),
|
||||
|
||||
// Input decoration theme
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: colorScheme.surfaceContainerHighest.withValues(alpha: 0.3),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: colorScheme.primary, width: 2),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: colorScheme.error, width: 1),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: colorScheme.error, width: 2),
|
||||
),
|
||||
),
|
||||
|
||||
// Elevated button theme
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 2,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Text button theme
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
),
|
||||
|
||||
// Floating action button theme
|
||||
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
),
|
||||
|
||||
// Progress indicator theme
|
||||
progressIndicatorTheme: ProgressIndicatorThemeData(
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
|
||||
// Divider theme
|
||||
dividerTheme: DividerThemeData(
|
||||
color: colorScheme.outlineVariant,
|
||||
thickness: 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build dark theme based on current color scheme
|
||||
ThemeData _buildDarkTheme() {
|
||||
final colorScheme = _getColorScheme(Brightness.dark);
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: colorScheme,
|
||||
|
||||
// AppBar theme
|
||||
appBarTheme: AppBarTheme(
|
||||
centerTitle: true,
|
||||
elevation: 0,
|
||||
backgroundColor: colorScheme.surface,
|
||||
foregroundColor: colorScheme.onSurface,
|
||||
),
|
||||
|
||||
// Card theme
|
||||
cardTheme: const CardThemeData(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
),
|
||||
|
||||
// Input decoration theme
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: colorScheme.surfaceContainerHighest.withValues(alpha: 0.3),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: colorScheme.primary, width: 2),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: colorScheme.error, width: 1),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: colorScheme.error, width: 2),
|
||||
),
|
||||
),
|
||||
|
||||
// Elevated button theme
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 2,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Text button theme
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
),
|
||||
|
||||
// Floating action button theme
|
||||
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
),
|
||||
|
||||
// Progress indicator theme
|
||||
progressIndicatorTheme: ProgressIndicatorThemeData(
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
|
||||
// Divider theme
|
||||
dividerTheme: DividerThemeData(
|
||||
color: colorScheme.outlineVariant,
|
||||
thickness: 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Get color scheme based on brightness and selected color
|
||||
ColorScheme _getColorScheme(Brightness brightness) {
|
||||
switch (_colorScheme) {
|
||||
case AppColorScheme.blue:
|
||||
return _getBlueColorScheme(brightness);
|
||||
case AppColorScheme.green:
|
||||
return _getGreenColorScheme(brightness);
|
||||
case AppColorScheme.purple:
|
||||
return _getPurpleColorScheme(brightness);
|
||||
case AppColorScheme.orange:
|
||||
return _getOrangeColorScheme(brightness);
|
||||
}
|
||||
}
|
||||
|
||||
/// Blue color scheme
|
||||
ColorScheme _getBlueColorScheme(Brightness brightness) {
|
||||
if (brightness == Brightness.light) {
|
||||
return ColorScheme.fromSeed(
|
||||
seedColor: Colors.blue,
|
||||
brightness: Brightness.light,
|
||||
);
|
||||
} else {
|
||||
return ColorScheme.fromSeed(
|
||||
seedColor: Colors.blue,
|
||||
brightness: Brightness.dark,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Green color scheme
|
||||
ColorScheme _getGreenColorScheme(Brightness brightness) {
|
||||
if (brightness == Brightness.light) {
|
||||
return ColorScheme.fromSeed(
|
||||
seedColor: Colors.green,
|
||||
brightness: Brightness.light,
|
||||
);
|
||||
} else {
|
||||
return ColorScheme.fromSeed(
|
||||
seedColor: Colors.green,
|
||||
brightness: Brightness.dark,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Purple color scheme
|
||||
ColorScheme _getPurpleColorScheme(Brightness brightness) {
|
||||
if (brightness == Brightness.light) {
|
||||
return ColorScheme.fromSeed(
|
||||
seedColor: Colors.purple,
|
||||
brightness: Brightness.light,
|
||||
);
|
||||
} else {
|
||||
return ColorScheme.fromSeed(
|
||||
seedColor: Colors.purple,
|
||||
brightness: Brightness.dark,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Orange color scheme
|
||||
ColorScheme _getOrangeColorScheme(Brightness brightness) {
|
||||
if (brightness == Brightness.light) {
|
||||
return ColorScheme.fromSeed(
|
||||
seedColor: Colors.orange,
|
||||
brightness: Brightness.light,
|
||||
);
|
||||
} else {
|
||||
return ColorScheme.fromSeed(
|
||||
seedColor: Colors.orange,
|
||||
brightness: Brightness.dark,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get color scheme name for display
|
||||
String getColorSchemeName(AppColorScheme scheme) {
|
||||
switch (scheme) {
|
||||
case AppColorScheme.blue:
|
||||
return '蓝色';
|
||||
case AppColorScheme.green:
|
||||
return '绿色';
|
||||
case AppColorScheme.purple:
|
||||
return '紫色';
|
||||
case AppColorScheme.orange:
|
||||
return '橙色';
|
||||
}
|
||||
}
|
||||
|
||||
/// Get theme mode name for display
|
||||
String getThemeModeName(ThemeMode mode) {
|
||||
switch (mode) {
|
||||
case ThemeMode.light:
|
||||
return '浅色模式';
|
||||
case ThemeMode.dark:
|
||||
return '深色模式';
|
||||
case ThemeMode.system:
|
||||
return '跟随系统';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user