😋 初始化仓库
This commit is contained in:
325
lib/services/storage_service.dart
Normal file
325
lib/services/storage_service.dart
Normal file
@@ -0,0 +1,325 @@
|
||||
import 'dart:convert';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../models/evaluation_history.dart';
|
||||
|
||||
/// Service for managing local storage using SharedPreferences
|
||||
///
|
||||
/// Handles storage of evaluation history, cache management,
|
||||
/// and other persistent data (excluding secure credentials)
|
||||
///
|
||||
/// Usage example:
|
||||
/// ```dart
|
||||
/// final storageService = StorageService();
|
||||
///
|
||||
/// // Save evaluation history
|
||||
/// await storageService.saveEvaluationHistory(history);
|
||||
///
|
||||
/// // Load evaluation history
|
||||
/// final histories = await storageService.loadEvaluationHistory();
|
||||
///
|
||||
/// // Clear all data
|
||||
/// await storageService.clearAllData();
|
||||
/// ```
|
||||
class StorageService {
|
||||
static const String _evaluationHistoryKey = 'evaluation_history';
|
||||
static const String _lastSyncTimeKey = 'last_sync_time';
|
||||
static const String _cacheVersionKey = 'cache_version';
|
||||
static const int _maxHistoryItems = 100; // Maximum history items to keep
|
||||
|
||||
/// Save evaluation history to local storage
|
||||
///
|
||||
/// Appends new history item to existing list
|
||||
/// Automatically trims list if it exceeds max items
|
||||
///
|
||||
/// [history] - The evaluation history to save
|
||||
Future<void> saveEvaluationHistory(EvaluationHistory history) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
// Load existing history
|
||||
final histories = await loadEvaluationHistory();
|
||||
|
||||
// Add new history at the beginning
|
||||
histories.insert(0, history);
|
||||
|
||||
// Trim if exceeds max items
|
||||
if (histories.length > _maxHistoryItems) {
|
||||
histories.removeRange(_maxHistoryItems, histories.length);
|
||||
}
|
||||
|
||||
// Convert to JSON and save
|
||||
final jsonList = histories.map((h) => h.toJson()).toList();
|
||||
final jsonString = jsonEncode(jsonList);
|
||||
await prefs.setString(_evaluationHistoryKey, jsonString);
|
||||
} catch (e) {
|
||||
throw StorageException('Failed to save evaluation history: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Save multiple evaluation histories at once
|
||||
///
|
||||
/// Useful for batch operations
|
||||
///
|
||||
/// [histories] - List of evaluation histories to save
|
||||
Future<void> saveEvaluationHistories(
|
||||
List<EvaluationHistory> histories,
|
||||
) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
// Load existing history
|
||||
final existingHistories = await loadEvaluationHistory();
|
||||
|
||||
// Merge new histories at the beginning
|
||||
final mergedHistories = [...histories, ...existingHistories];
|
||||
|
||||
// Trim if exceeds max items
|
||||
final trimmedHistories = mergedHistories.length > _maxHistoryItems
|
||||
? mergedHistories.sublist(0, _maxHistoryItems)
|
||||
: mergedHistories;
|
||||
|
||||
// Convert to JSON and save
|
||||
final jsonList = trimmedHistories.map((h) => h.toJson()).toList();
|
||||
final jsonString = jsonEncode(jsonList);
|
||||
await prefs.setString(_evaluationHistoryKey, jsonString);
|
||||
} catch (e) {
|
||||
throw StorageException('Failed to save evaluation histories: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Load evaluation history from local storage
|
||||
///
|
||||
/// Returns list of evaluation histories sorted by timestamp (newest first)
|
||||
/// Returns empty list if no history exists
|
||||
Future<List<EvaluationHistory>> loadEvaluationHistory() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(_evaluationHistoryKey);
|
||||
|
||||
if (jsonString == null || jsonString.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final jsonList = jsonDecode(jsonString) as List<dynamic>;
|
||||
final histories = jsonList
|
||||
.map(
|
||||
(json) => EvaluationHistory.fromJson(json as Map<String, dynamic>),
|
||||
)
|
||||
.toList();
|
||||
|
||||
// Sort by timestamp (newest first)
|
||||
histories.sort((a, b) => b.timestamp.compareTo(a.timestamp));
|
||||
|
||||
return histories;
|
||||
} catch (e) {
|
||||
throw StorageException('Failed to load evaluation history: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get evaluation history for a specific course
|
||||
///
|
||||
/// [courseId] - The course ID to filter by
|
||||
/// Returns list of histories for the specified course
|
||||
Future<List<EvaluationHistory>> getHistoryByCourse(String courseId) async {
|
||||
try {
|
||||
final allHistories = await loadEvaluationHistory();
|
||||
return allHistories.where((h) => h.course.id == courseId).toList();
|
||||
} catch (e) {
|
||||
throw StorageException('Failed to get history by course: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get successful evaluation count
|
||||
///
|
||||
/// Returns the total number of successful evaluations
|
||||
Future<int> getSuccessfulEvaluationCount() async {
|
||||
try {
|
||||
final histories = await loadEvaluationHistory();
|
||||
return histories.where((h) => h.success).length;
|
||||
} catch (e) {
|
||||
throw StorageException('Failed to get successful evaluation count: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get failed evaluation count
|
||||
///
|
||||
/// Returns the total number of failed evaluations
|
||||
Future<int> getFailedEvaluationCount() async {
|
||||
try {
|
||||
final histories = await loadEvaluationHistory();
|
||||
return histories.where((h) => !h.success).length;
|
||||
} catch (e) {
|
||||
throw StorageException('Failed to get failed evaluation count: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear evaluation history
|
||||
///
|
||||
/// Removes all stored evaluation history
|
||||
Future<void> clearEvaluationHistory() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_evaluationHistoryKey);
|
||||
} catch (e) {
|
||||
throw StorageException('Failed to clear evaluation history: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Save last sync time
|
||||
///
|
||||
/// Records when the last data synchronization occurred
|
||||
///
|
||||
/// [time] - The timestamp to save
|
||||
Future<void> saveLastSyncTime(DateTime time) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_lastSyncTimeKey, time.toIso8601String());
|
||||
} catch (e) {
|
||||
throw StorageException('Failed to save last sync time: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Load last sync time
|
||||
///
|
||||
/// Returns the timestamp of the last synchronization
|
||||
/// Returns null if no sync has occurred
|
||||
Future<DateTime?> loadLastSyncTime() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final timeString = prefs.getString(_lastSyncTimeKey);
|
||||
|
||||
if (timeString == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return DateTime.parse(timeString);
|
||||
} catch (e) {
|
||||
throw StorageException('Failed to load last sync time: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get cache version
|
||||
///
|
||||
/// Returns the current cache version number
|
||||
/// Used for cache invalidation when data structure changes
|
||||
Future<int> getCacheVersion() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getInt(_cacheVersionKey) ?? 1;
|
||||
} catch (e) {
|
||||
throw StorageException('Failed to get cache version: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Set cache version
|
||||
///
|
||||
/// Updates the cache version number
|
||||
///
|
||||
/// [version] - The new version number
|
||||
Future<void> setCacheVersion(int version) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt(_cacheVersionKey, version);
|
||||
} catch (e) {
|
||||
throw StorageException('Failed to set cache version: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear cache if version mismatch
|
||||
///
|
||||
/// Compares current cache version with expected version
|
||||
/// Clears cache if they don't match
|
||||
///
|
||||
/// [expectedVersion] - The expected cache version
|
||||
/// Returns true if cache was cleared, false otherwise
|
||||
Future<bool> clearCacheIfVersionMismatch(int expectedVersion) async {
|
||||
try {
|
||||
final currentVersion = await getCacheVersion();
|
||||
|
||||
if (currentVersion != expectedVersion) {
|
||||
await clearEvaluationHistory();
|
||||
await setCacheVersion(expectedVersion);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (e) {
|
||||
throw StorageException('Failed to check cache version: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all local data
|
||||
///
|
||||
/// Removes all data stored by this service
|
||||
/// Does NOT clear secure storage (credentials)
|
||||
/// Does NOT clear theme preferences
|
||||
Future<void> clearAllData() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_evaluationHistoryKey);
|
||||
await prefs.remove(_lastSyncTimeKey);
|
||||
// Keep cache version to maintain compatibility
|
||||
} catch (e) {
|
||||
throw StorageException('Failed to clear all data: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get storage statistics
|
||||
///
|
||||
/// Returns information about stored data
|
||||
Future<StorageStats> getStorageStats() async {
|
||||
try {
|
||||
final histories = await loadEvaluationHistory();
|
||||
final lastSync = await loadLastSyncTime();
|
||||
final cacheVersion = await getCacheVersion();
|
||||
|
||||
return StorageStats(
|
||||
totalHistoryItems: histories.length,
|
||||
successfulEvaluations: histories.where((h) => h.success).length,
|
||||
failedEvaluations: histories.where((h) => !h.success).length,
|
||||
lastSyncTime: lastSync,
|
||||
cacheVersion: cacheVersion,
|
||||
);
|
||||
} catch (e) {
|
||||
throw StorageException('Failed to get storage stats: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Storage statistics data class
|
||||
class StorageStats {
|
||||
final int totalHistoryItems;
|
||||
final int successfulEvaluations;
|
||||
final int failedEvaluations;
|
||||
final DateTime? lastSyncTime;
|
||||
final int cacheVersion;
|
||||
|
||||
StorageStats({
|
||||
required this.totalHistoryItems,
|
||||
required this.successfulEvaluations,
|
||||
required this.failedEvaluations,
|
||||
this.lastSyncTime,
|
||||
required this.cacheVersion,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'StorageStats('
|
||||
'total: $totalHistoryItems, '
|
||||
'success: $successfulEvaluations, '
|
||||
'failed: $failedEvaluations, '
|
||||
'lastSync: $lastSyncTime, '
|
||||
'cacheVersion: $cacheVersion'
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
||||
/// Exception thrown when storage operations fail
|
||||
class StorageException implements Exception {
|
||||
final String message;
|
||||
|
||||
StorageException(this.message);
|
||||
|
||||
@override
|
||||
String toString() => 'StorageException: $message';
|
||||
}
|
||||
Reference in New Issue
Block a user