import 'package:sqflite/sqflite.dart'; import 'package:wow_english/common/core/user_util.dart'; class VideoProgressCacheEntry { const VideoProgressCacheEntry({ required this.progressSeconds, required this.durationSeconds, }); final int progressSeconds; final int durationSeconds; } /// 使用 SQLite 按用户保存视频播放进度。 class VideoProgressCache { static const _databaseName = 'video_progress.db'; static const _databaseVersion = 1; static const _tableName = 'video_progress'; static const _maxRecordCount = 100; static Database? _database; static Future? _openingDatabase; static Future _writeQueue = Future.value(); static Future get(int videoId) async { final userId = _currentUserId; if (userId == null) return null; await _writeQueue; final database = await _getDatabase(); final rows = await database.query( _tableName, columns: const ['progress_seconds', 'duration_seconds'], where: 'user_id = ? AND video_id = ?', whereArgs: [userId, videoId], limit: 1, ); if (rows.isEmpty) return null; return VideoProgressCacheEntry( progressSeconds: _toInt(rows.first['progress_seconds']), durationSeconds: _toInt(rows.first['duration_seconds']), ); } static Future save({ required int videoId, required int progressSeconds, required int durationSeconds, }) async { final userId = _currentUserId; if (userId == null) return; await _enqueueWrite(() async { final database = await _getDatabase(); await database.transaction((transaction) async { await transaction.insert( _tableName, { 'user_id': userId, 'video_id': videoId, 'progress_seconds': progressSeconds, 'duration_seconds': durationSeconds, 'updated_at': DateTime.now().millisecondsSinceEpoch, }, conflictAlgorithm: ConflictAlgorithm.replace, ); await transaction.rawDelete( ''' DELETE FROM $_tableName WHERE user_id = ? AND video_id NOT IN ( SELECT video_id FROM $_tableName WHERE user_id = ? ORDER BY updated_at DESC LIMIT ? ) ''', [userId, userId, _maxRecordCount], ); }); }); } static Future remove(int videoId) async { final userId = _currentUserId; if (userId == null) return; await _enqueueWrite(() async { final database = await _getDatabase(); await database.delete( _tableName, where: 'user_id = ? AND video_id = ?', whereArgs: [userId, videoId], ); }); } static int? get _currentUserId => UserUtil.getUser()?.id; static Future _enqueueWrite(Future Function() operation) { final future = _writeQueue.then((_) => operation()); _writeQueue = future.then((_) {}, onError: (_, __) {}); return future; } static Future _getDatabase() async { final database = _database; if (database != null && database.isOpen) return database; final openingDatabase = _openingDatabase; if (openingDatabase != null) return openingDatabase; final future = _openDatabase(); _openingDatabase = future; try { final result = await future; _database = result; return result; } finally { _openingDatabase = null; } } static Future _openDatabase() async { final databasesPath = await getDatabasesPath(); return openDatabase( '$databasesPath/$_databaseName', version: _databaseVersion, onCreate: (database, _) => database.execute(''' CREATE TABLE $_tableName ( user_id INTEGER NOT NULL, video_id INTEGER NOT NULL, progress_seconds INTEGER NOT NULL, duration_seconds INTEGER NOT NULL, updated_at INTEGER NOT NULL, PRIMARY KEY (user_id, video_id) ) '''), ); } static int _toInt(Object? value) => value is num ? value.toInt() : int.tryParse('$value') ?? 0; }