Files
MookNote/lib/providers/app_provider.dart
2026-08-09 01:22:35 +08:00

1533 lines
47 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:collection';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
import '../models/data_models.dart';
import '../data/movie/movie_dao.dart';
import '../data/book/book_dao.dart';
import '../data/note/note_dao.dart';
import '../data/movie/movie_review_dao.dart';
import '../data/movie/movie_poster_dao.dart';
import '../data/book/book_review_dao.dart';
import '../data/book/book_excerpt_dao.dart';
import '../data/game/game_dao.dart';
import '../data/game/game_review_dao.dart';
import '../data/game/game_screenshot_dao.dart';
import '../data/playlist/playlist_dao.dart';
import '../data/tag/tag_dao.dart';
import '../data/person/person_dao.dart';
import '../data/person/movie_person_dao.dart';
import '../data/person/book_person_dao.dart';
import '../data/person/game_person_dao.dart';
import '../data/character/movie_character_dao.dart';
import '../data/character/book_character_dao.dart';
import '../data/character/game_character_dao.dart';
import '../data/database_helper.dart';
import '../utils/image_path_helper.dart';
import '../utils/user_prefs.dart';
import '../utils/theme/app_theme.dart';
import '../services/font_download_manager.dart';
/// 应用全局状态管理
class AppProvider extends ChangeNotifier {
// 数据库访问对象
final MovieDao _movieDao = MovieDao();
final BookDao _bookDao = BookDao();
final NoteDao _noteDao = NoteDao();
final MovieReviewDao _reviewDao = MovieReviewDao();
final MoviePosterDao _posterDao = MoviePosterDao();
final BookReviewDao _bookReviewDao = BookReviewDao();
final BookExcerptDao _bookExcerptDao = BookExcerptDao();
final GameDao _gameDao = GameDao();
final GameReviewDao _gameReviewDao = GameReviewDao();
final GameScreenshotDao _gameScreenshotDao = GameScreenshotDao();
final PlaylistDao _playlistDao = PlaylistDao();
final TagDao _tagDao = TagDao();
final PersonDao _personDao = PersonDao();
final MoviePersonDao _moviePersonDao = MoviePersonDao();
final BookPersonDao _bookPersonDao = BookPersonDao();
final GamePersonDao _gamePersonDao = GamePersonDao();
final MovieCharacterDao _movieCharacterDao = MovieCharacterDao();
final BookCharacterDao _bookCharacterDao = BookCharacterDao();
final GameCharacterDao _gameCharacterDao = GameCharacterDao();
// 数据列表
List<Movie> _movies = [];
List<Book> _books = [];
List<Note> _notes = [];
List<Game> _games = [];
List<Playlist> _playlists = [];
List<Person> _people = [];
// 当前主界面选中的标签 (0: 观影1: 阅读2: 笔记)
int _mainTabIndex = 0;
// 当前底部导航选中的索引 (0: 主页1: 新增2: 我的)
int _bottomNavIndex = 0;
// 底部导航栏是否可见
bool _bottomNavVisible = true;
// 平板 Master-Detail 选中项
Movie? _selectedMovie;
Book? _selectedBook;
Note? _selectedNote;
Game? _selectedGame;
bool _isAdding = false;
int? _addingType; // 0=影视, 1=阅读, 2=笔记, 3=游戏, null=未选择类型
// 主题模式
ThemeMode _themeMode = ThemeMode.system;
// 配色方案索引
int _colorSchemeIndex = 0;
// 字体
String _fontFamily = '';
// 观影选中的状态 (0: 已看1: 想看2: 在看)
int _movieStatusIndex = 0;
// 影视列表布局样式 (0: 网格, 1: 列表, 2: 大图卡片)
int _movieLayoutStyle = 0;
// 影视墙模式(不显示分类,按创建时间排序)
bool _movieWallMode = false;
// 影视显示模式 (0: 观看状态, 1: 分类状态)
int _movieDisplayMode = 0;
// 影视分类索引
int _movieCategoryIndex = 0;
// 阅读选中的状态 (0: 读完1: 在读2: 准备读)
int _bookStatusIndex = 0;
// 书架模式(不显示分类,按创建时间排序)
bool _bookshelfMode = false;
// 游戏选中的状态 (0: 已通关1: 在玩2: 想玩3: 弃游)
int _gameStatusIndex = 0;
// 游戏列表布局样式 (0: 网格, 1: 列表, 2: 大图卡片)
int _gameLayoutStyle = 0;
// 游戏墙模式
bool _gameWallMode = false;
// 侧边菜单是否打开
bool _drawerOpen = false;
// 回到顶部信号(点击首页图标时递增)
int _scrollToTopSignal = 0;
int get scrollToTopSignal => _scrollToTopSignal;
// 编辑后刷新信号(影视/书籍编辑返回时递增)
int _editRefreshCounter = 0;
int get editRefreshCounter => _editRefreshCounter;
// 最近编辑的条目 ID用于就地更新避免重置分页
String? _lastEditedItemId;
String? get lastEditedItemId => _lastEditedItemId;
// 数据库初始化失败标志
bool _dbInitFailed = false;
bool get dbInitFailed => _dbInitFailed;
/// 标记数据库初始化失败(供 UI 提示重试)
void markDbInitFailed() {
_dbInitFailed = true;
notifyListeners();
}
/// 重试数据库初始化
Future<void> retryInitDatabase() async {
_dbInitFailed = false;
notifyListeners();
await initDatabase();
}
// 初始化数据库
Future<void> initDatabase() async {
debugPrint('[AppProvider] initDatabase');
// 独立加载每个 DAO避免一个失败导致全部中断
try {
_movies = await _movieDao.getAllMovies(sortMode: UserPrefs().movieSortMode);
} catch (e) {
debugPrint('[AppProvider] 加载影视数据失败: $e');
}
try {
_books = await _bookDao.getAllBooks(sortMode: UserPrefs().bookSortMode);
} catch (e) {
debugPrint('[AppProvider] 加载书籍数据失败: $e');
}
try {
_notes = await _noteDao.getAllNotes(sortMode: UserPrefs().noteSortMode);
} catch (e) {
debugPrint('[AppProvider] 加载笔记数据失败: $e');
}
try {
_games = await _gameDao.getAllGames(sortMode: UserPrefs().gameSortMode);
} catch (e) {
debugPrint('[AppProvider] 加载游戏数据失败: $e');
}
try {
_people = await _personDao.getAllPeople();
} catch (e) {
debugPrint('[AppProvider] 加载人物数据失败: $e');
}
// 检查是否全部失败
if (_movies.isEmpty && _books.isEmpty && _notes.isEmpty && _games.isEmpty) {
// 可能是初始化全部失败(非空数据库场景下不合理),标记以便 UI 提示
try {
final db = await DatabaseHelper.instance.database;
final count = (await db.rawQuery('SELECT COUNT(*) as cnt FROM movies')).first['cnt'] as int;
if (count > 0) _dbInitFailed = true;
} catch (_) {
_dbInitFailed = true;
}
}
debugPrint('[AppProvider] 本地数据: movies=${_movies.length}, books=${_books.length}, notes=${_notes.length}, games=${_games.length}, people=${_people.length}');
notifyListeners();
}
// 从用户偏好恢复默认启动标签
void initMainTabIndex() {
final userPrefs = UserPrefs();
_movieLayoutStyle = userPrefs.movieLayoutStyle;
_movieWallMode = userPrefs.movieWallMode;
_movieDisplayMode = userPrefs.movieDisplayMode;
_bookshelfMode = userPrefs.bookshelfMode;
_gameLayoutStyle = userPrefs.gameLayoutStyle;
_gameWallMode = userPrefs.gameWallMode;
final defaultIndex = userPrefs.defaultMainTabIndex;
// Windows 桌面端且主页开启时,强制默认为主页
if (Platform.isWindows && userPrefs.showDesktopHomeTab) {
if (defaultIndex != -1) {
userPrefs.setDefaultMainTabIndex(-1);
}
_mainTabIndex = -1;
} else {
// 确保选中的标签是启用的
final showMovie = userPrefs.showMovieTab;
final showBook = userPrefs.showBookTab;
final showNote = userPrefs.showNoteTab;
final showGame = userPrefs.showGameTab;
final enabled = [showMovie, showBook, showNote, showGame];
if (defaultIndex >= 0 && defaultIndex < enabled.length && enabled[defaultIndex]) {
_mainTabIndex = defaultIndex;
} else {
// 回退到第一个启用的标签
if (showMovie) {
_mainTabIndex = 0;
} else if (showBook) {
_mainTabIndex = 1;
} else if (showNote) {
_mainTabIndex = 2;
} else if (showGame) {
_mainTabIndex = 3;
}
}
}
notifyListeners();
}
// 加载影视数据
Future<void> loadMovies() async {
_movies = await _movieDao.getAllMovies(sortMode: UserPrefs().movieSortMode);
notifyListeners();
}
// 加载书籍数据
Future<void> loadBooks() async {
_books = await _bookDao.getAllBooks(sortMode: UserPrefs().bookSortMode);
notifyListeners();
}
// 加载笔记数据
Future<void> loadNotes({int? sortMode}) async {
_notes = await _noteDao.getAllNotes(sortMode: sortMode ?? UserPrefs().noteSortMode);
notifyListeners();
}
// 加载游戏数据
Future<void> loadGames() async {
_games = await _gameDao.getAllGames(sortMode: UserPrefs().gameSortMode);
notifyListeners();
}
// 加载片单数据
Future<void> loadPlaylists() async {
_playlists = await _playlistDao.getAllPlaylists();
notifyListeners();
}
// 加载人物数据
Future<void> loadPeople() async {
_people = await _personDao.getAllPeople();
notifyListeners();
}
/// 编辑返回后触发列表页重载
/// [itemId] 被编辑条目的 ID用于就地更新而非重置分页
void setEditRefresh([String? itemId]) {
_editRefreshCounter++;
_lastEditedItemId = itemId;
notifyListeners();
}
// ─── 分页加载(供列表页触底加载使用)────────────────────────
static const int _pageSize = 20;
Future<List<Movie>> loadMoviesPaged({String? status, String? category, required int offset, int sortMode = 0}) async {
return _movieDao.getMoviesPaged(status: status, category: category, limit: _pageSize, offset: offset, sortMode: sortMode);
}
Future<List<Book>> loadBooksPaged({String? status, required int offset, int sortMode = 0}) async {
return _bookDao.getBooksPaged(status: status, limit: _pageSize, offset: offset, sortMode: sortMode);
}
Future<List<Note>> loadNotesPaged({required int offset, int sortMode = 0}) async {
return _noteDao.getNotesPaged(limit: _pageSize, offset: offset, sortMode: sortMode);
}
Future<List<Game>> loadGamesPaged({String? status, required int offset, int sortMode = 0}) async {
return _gameDao.getGamesPaged(status: status, limit: _pageSize, offset: offset, sortMode: sortMode);
}
// Getters
int get mainTabIndex => _mainTabIndex;
int get bottomNavIndex => _bottomNavIndex;
Movie? get selectedMovie => _selectedMovie;
Book? get selectedBook => _selectedBook;
Note? get selectedNote => _selectedNote;
Game? get selectedGame => _selectedGame;
int get movieStatusIndex => _movieStatusIndex;
int get movieLayoutStyle => _movieLayoutStyle;
bool get movieWallMode => _movieWallMode;
int get movieDisplayMode => _movieDisplayMode;
int get movieCategoryIndex => _movieCategoryIndex;
int get bookStatusIndex => _bookStatusIndex;
bool get bookshelfMode => _bookshelfMode;
int get gameStatusIndex => _gameStatusIndex;
int get gameLayoutStyle => _gameLayoutStyle;
bool get gameWallMode => _gameWallMode;
bool get drawerOpen => _drawerOpen;
bool get bottomNavVisible => _bottomNavVisible;
ThemeMode get themeMode => _themeMode;
int get colorSchemeIndex => _colorSchemeIndex;
String get fontFamily => _fontFamily;
List<Movie> get movies => UnmodifiableListView(_movies);
List<Book> get books => UnmodifiableListView(_books);
List<Note> get notes => UnmodifiableListView(_notes);
List<Game> get games => UnmodifiableListView(_games);
List<Playlist> get playlists => UnmodifiableListView(_playlists);
List<Person> get people => UnmodifiableListView(_people);
// 根据状态获取影视列表
List<Movie> getMoviesByStatus(String status) {
return _movies.where((movie) => movie.status == status).toList();
}
// 根据状态获取书籍列表
List<Book> getBooksByStatus(String status) {
return _books.where((book) => book.status == status).toList();
}
// Setters
void setMainTabIndex(int index) {
_mainTabIndex = index;
notifyListeners();
}
void setBottomNavIndex(int index) {
if (index == 0 && _bottomNavIndex == 0) {
// 已在首页,再次点击 → 回到顶部
_scrollToTopSignal++;
notifyListeners();
return;
}
_bottomNavIndex = index;
_bottomNavVisible = true;
notifyListeners();
}
void selectMovie(Movie? movie) {
_selectedMovie = movie;
notifyListeners();
}
void selectBook(Book? book) {
_selectedBook = book;
notifyListeners();
}
void selectNote(Note? note) {
_selectedNote = note;
notifyListeners();
}
void selectGame(Game? game) {
_selectedGame = game;
notifyListeners();
}
bool get isAdding => _isAdding;
int? get addingType => _addingType;
void startAdding() {
_isAdding = true;
_addingType = null;
// 清除选中项,避免同时显示详情和添加
_selectedMovie = null;
_selectedBook = null;
_selectedNote = null;
_selectedGame = null;
notifyListeners();
}
void startAddingType(int type) {
_isAdding = true;
_addingType = type;
_selectedMovie = null;
_selectedBook = null;
_selectedNote = null;
_selectedGame = null;
_mainTabIndex = type;
notifyListeners();
}
void cancelAdding() {
_isAdding = false;
_addingType = null;
notifyListeners();
}
void finishAdding() {
_isAdding = false;
_addingType = null;
notifyListeners();
}
void setBottomNavVisible(bool visible) {
if (_bottomNavVisible != visible) {
_bottomNavVisible = visible;
notifyListeners();
}
}
void setThemeMode(ThemeMode mode) {
if (_themeMode != mode) {
_themeMode = mode;
UserPrefs().setThemeMode(mode.index); // 0=system, 1=light, 2=dark
notifyListeners();
}
}
void loadThemeMode() {
final prefs = UserPrefs();
switch (prefs.themeMode) {
case 1:
_themeMode = ThemeMode.light;
case 2:
_themeMode = ThemeMode.dark;
default:
_themeMode = ThemeMode.system;
}
_colorSchemeIndex = prefs.colorSchemeIndex;
_fontFamily = prefs.fontFamily;
AppTheme.setFontFamily(_fontFamily);
// 异步预加载已缓存的字体(不阻塞 UI
if (_fontFamily.isNotEmpty) {
FontDownloadManager().preloadCachedFont(_fontFamily);
}
notifyListeners();
}
void setColorScheme(int index) {
if (_colorSchemeIndex != index) {
_colorSchemeIndex = index;
UserPrefs().setColorSchemeIndex(index);
notifyListeners();
}
}
void setFontFamily(String family) {
if (_fontFamily != family) {
_fontFamily = family;
UserPrefs().setFontFamily(family);
AppTheme.setFontFamily(family);
notifyListeners();
}
}
void setMovieStatusIndex(int index) {
_movieStatusIndex = index;
notifyListeners();
}
void setMovieLayoutStyle(int style) {
_movieLayoutStyle = style;
UserPrefs().setMovieLayoutStyle(style);
notifyListeners();
}
void setMovieWallMode(bool enabled) {
_movieWallMode = enabled;
UserPrefs().setMovieWallMode(enabled);
notifyListeners();
}
void setMovieDisplayMode(int mode) {
_movieDisplayMode = mode;
UserPrefs().setMovieDisplayMode(mode);
notifyListeners();
}
void setMovieCategoryIndex(int index) {
_movieCategoryIndex = index;
notifyListeners();
}
void setBookStatusIndex(int index) {
_bookStatusIndex = index;
notifyListeners();
}
void setBookshelfMode(bool enabled) {
_bookshelfMode = enabled;
UserPrefs().setBookshelfMode(enabled);
notifyListeners();
}
void setGameStatusIndex(int index) {
_gameStatusIndex = index;
notifyListeners();
}
void setGameLayoutStyle(int style) {
_gameLayoutStyle = style;
UserPrefs().setGameLayoutStyle(style);
notifyListeners();
}
void setGameWallMode(bool enabled) {
_gameWallMode = enabled;
UserPrefs().setGameWallMode(enabled);
notifyListeners();
}
void toggleDrawer() {
_drawerOpen = !_drawerOpen;
notifyListeners();
}
void closeDrawer() {
_drawerOpen = false;
notifyListeners();
}
// ─── 图片上传辅助 ────────────────────────────────────────────────
// 添加影视记录
Future<void> addMovie(Movie movie) async {
await _movieDao.insertMovie(movie);
_movies.add(movie);
notifyListeners();
}
Future<void> updateMovie(Movie movie) async {
await _movieDao.updateMovie(movie);
final idx = _movies.indexWhere((m) => m.id == movie.id);
if (idx != -1) _movies[idx] = movie;
notifyListeners();
}
/// 仅更新封面偏移量(不触发全量刷新)
Future<void> updateMovieCoverOffset(String movieId, double offset) async {
await _movieDao.updateCoverOffset(movieId, offset);
final idx = _movies.indexWhere((m) => m.id == movieId);
if (idx != -1) {
_movies[idx] = _movies[idx].copyWith(coverOffset: offset);
notifyListeners();
}
}
/// 仅更新封面偏移量(不触发全量刷新)
Future<void> updateBookCoverOffset(String bookId, double offset) async {
await _bookDao.updateCoverOffset(bookId, offset);
final idx = _books.indexWhere((b) => b.id == bookId);
if (idx != -1) {
_books[idx] = _books[idx].copyWith(coverOffset: offset);
notifyListeners();
}
}
Future<void> removeMovie(String id) async {
await _movieDao.deleteMovie(id);
_movies.removeWhere((m) => m.id == id);
notifyListeners();
}
Future<void> addBook(Book book) async {
await _bookDao.insertBook(book);
_books.add(book);
notifyListeners();
}
Future<void> updateBook(Book book) async {
await _bookDao.updateBook(book);
final idx = _books.indexWhere((b) => b.id == book.id);
if (idx != -1) _books[idx] = book;
notifyListeners();
}
Future<void> removeBook(String id) async {
await _bookDao.deleteBook(id);
_books.removeWhere((b) => b.id == id);
notifyListeners();
}
Future<void> addNote(Note note) async {
await _noteDao.insertNote(note);
_notes.add(note);
notifyListeners();
}
Future<void> updateNote(Note note) async {
await _noteDao.updateNote(note);
final idx = _notes.indexWhere((n) => n.id == note.id);
if (idx != -1) _notes[idx] = note;
notifyListeners();
}
Future<void> removeNote(String id) async {
await _noteDao.deleteNote(id);
_notes.removeWhere((n) => n.id == id);
notifyListeners();
}
Future<void> addGame(Game game) async {
await _gameDao.insertGame(game);
_games.add(game);
notifyListeners();
}
Future<void> updateGame(Game game) async {
await _gameDao.updateGame(game);
final idx = _games.indexWhere((g) => g.id == game.id);
if (idx != -1) _games[idx] = game;
notifyListeners();
}
Future<void> removeGame(String id) async {
await _gameDao.deleteGame(id);
_games.removeWhere((g) => g.id == id);
notifyListeners();
}
// ─── 片单操作 ─────────────────────────────────────────────────
Future<void> addPlaylist(Playlist playlist) async {
await _playlistDao.insertPlaylist(playlist);
_playlists.add(playlist);
notifyListeners();
}
Future<void> removePlaylist(String id) async {
await _playlistDao.deletePlaylist(id);
_playlists.removeWhere((p) => p.id == id);
notifyListeners();
}
Future<void> updatePlaylist(Playlist playlist) async {
await _playlistDao.updatePlaylist(playlist);
final idx = _playlists.indexWhere((p) => p.id == playlist.id);
if (idx != -1) _playlists[idx] = playlist;
notifyListeners();
}
Future<List<PlaylistItem>> getPlaylistItems(String playlistId) async {
return _playlistDao.getPlaylistItems(playlistId);
}
Future<void> addPlaylistItem(PlaylistItem item) async {
await _playlistDao.addItem(item);
// 更新片单的 itemCount
final playlist = _playlists.firstWhere((p) => p.id == item.playlistId);
final updated = playlist.copyWith(itemCount: playlist.itemCount + 1, updatedAt: DateTime.now());
final idx = _playlists.indexWhere((p) => p.id == item.playlistId);
if (idx != -1) _playlists[idx] = updated;
notifyListeners();
}
Future<void> removePlaylistItem(String itemId, String playlistId) async {
await _playlistDao.removeItem(itemId, playlistId);
final playlist = _playlists.firstWhere((p) => p.id == playlistId);
final updated = playlist.copyWith(itemCount: (playlist.itemCount - 1).clamp(0, 99999), updatedAt: DateTime.now());
final idx = _playlists.indexWhere((p) => p.id == playlistId);
if (idx != -1) _playlists[idx] = updated;
notifyListeners();
}
Future<List<String>> getPlaylistItemIds(String playlistId) async {
return _playlistDao.getPlaylistItemIds(playlistId);
}
Future<void> reorderPlaylists(List<String> playlistIds) async {
await _playlistDao.updatePlaylistOrder(playlistIds);
// 更新内存中的排序
final orderMap = {for (int i = 0; i < playlistIds.length; i++) playlistIds[i]: i};
_playlists.sort((a, b) => (orderMap[a.id] ?? 0).compareTo(orderMap[b.id] ?? 0));
notifyListeners();
}
Future<void> reorderPlaylistItems(String playlistId, List<String> itemIds) async {
await _playlistDao.updatePlaylistItemOrder(playlistId, itemIds);
}
/// 仅更新游戏封面偏移量(不触发全量刷新)
Future<void> updateGameCoverOffset(String gameId, double offset) async {
await _gameDao.updateCoverOffset(gameId, offset);
final idx = _games.indexWhere((g) => g.id == gameId);
if (idx != -1) {
_games[idx] = _games[idx].copyWith(coverOffset: offset);
notifyListeners();
}
}
Future<void> toggleNotePin(String id, bool isPinned) async {
await _noteDao.togglePin(id, isPinned);
final idx = _notes.indexWhere((n) => n.id == id);
if (idx != -1) {
_notes[idx] = _notes[idx].copyWith(isPinned: isPinned);
}
notifyListeners();
}
// ========== 影评相关方法 ==========
/// 获取影视的所有影评
Future<List<MovieReview>> getMovieReviews(String movieId) async {
return await _reviewDao.getReviewsByMovieId(movieId);
}
/// 添加影评
Future<void> addMovieReview(MovieReview review) async {
await _reviewDao.insertReview(review);
notifyListeners();
}
/// 更新影评
Future<void> updateMovieReview(MovieReview review) async {
await _reviewDao.updateReview(review);
notifyListeners();
}
/// 删除影评
Future<void> removeMovieReview(String id) async {
await _reviewDao.deleteReview(id);
notifyListeners();
}
/// 获取影视的影评数量
Future<int> getMovieReviewCount(String movieId) async {
return await _reviewDao.getReviewCount(movieId);
}
// ========== 海报墙相关方法 ==========
/// 获取影视的所有海报
Future<List<MoviePoster>> getMoviePosters(String movieId) async {
return await _posterDao.getPostersByMovieId(movieId);
}
/// 添加海报
Future<void> addMoviePoster(MoviePoster poster) async {
await _posterDao.insertPoster(poster);
notifyListeners();
}
/// 删除海报
Future<void> removeMoviePoster(String id) async {
final poster = await _posterDao.getPosterById(id);
if (poster != null) {
await ImagePathHelper.instance.deleteFile(poster.posterPath);
}
await _posterDao.deletePoster(id);
notifyListeners();
}
/// 获取影视的海报数量
Future<int> getMoviePosterCount(String movieId) async {
return await _posterDao.getPosterCount(movieId);
}
// ========== 游戏评价相关方法 ==========
/// 获取游戏的所有评价
Future<List<GameReview>> getGameReviews(String gameId) async {
return await _gameReviewDao.getReviewsByGameId(gameId);
}
/// 添加游戏评价
Future<void> addGameReview(GameReview review) async {
await _gameReviewDao.insertReview(review);
notifyListeners();
}
/// 更新游戏评价
Future<void> updateGameReview(GameReview review) async {
await _gameReviewDao.updateReview(review);
notifyListeners();
}
/// 删除游戏评价
Future<void> removeGameReview(String id) async {
await _gameReviewDao.deleteReview(id);
notifyListeners();
}
/// 获取游戏的评价数量
Future<int> getGameReviewCount(String gameId) async {
return await _gameReviewDao.getReviewCount(gameId);
}
// ========== 游戏截图相关方法 ==========
/// 获取游戏的所有截图
Future<List<GameScreenshot>> getGameScreenshots(String gameId) async {
return await _gameScreenshotDao.getScreenshotsByGameId(gameId);
}
/// 添加游戏截图
Future<void> addGameScreenshot(GameScreenshot screenshot) async {
await _gameScreenshotDao.insertScreenshot(screenshot);
notifyListeners();
}
/// 删除游戏截图
Future<void> removeGameScreenshot(String id) async {
final screenshot = await _gameScreenshotDao.getScreenshotById(id);
if (screenshot != null) {
await ImagePathHelper.instance.deleteFile(screenshot.screenshotPath);
}
await _gameScreenshotDao.deleteScreenshot(id);
notifyListeners();
}
/// 获取游戏的截图数量
Future<int> getGameScreenshotCount(String gameId) async {
return await _gameScreenshotDao.getScreenshotCount(gameId);
}
// ========== 书评相关方法 ==========
/// 获取书籍的所有书评
Future<List<BookReview>> getBookReviews(String bookId) async {
return await _bookReviewDao.getReviewsByBookId(bookId);
}
/// 添加书评
Future<void> addBookReview(BookReview review) async {
await _bookReviewDao.insertReview(review);
notifyListeners();
}
/// 更新书评
Future<void> updateBookReview(BookReview review) async {
await _bookReviewDao.updateReview(review);
notifyListeners();
}
/// 删除书评
Future<void> removeBookReview(String id) async {
await _bookReviewDao.deleteReview(id);
notifyListeners();
}
/// 获取书籍的书评数量
Future<int> getBookReviewCount(String bookId) async {
return await _bookReviewDao.getReviewCount(bookId);
}
// ========== 摘抄相关方法 ==========
/// 获取书籍的所有摘抄
Future<List<BookExcerpt>> getBookExcerpts(String bookId) async {
return await _bookExcerptDao.getExcerptsByBookId(bookId);
}
/// 添加摘抄
Future<void> addBookExcerpt(BookExcerpt excerpt) async {
await _bookExcerptDao.insertExcerpt(excerpt);
notifyListeners();
}
/// 更新摘抄
Future<void> updateBookExcerpt(BookExcerpt excerpt) async {
await _bookExcerptDao.updateExcerpt(excerpt);
notifyListeners();
}
/// 删除摘抄
Future<void> removeBookExcerpt(String id) async {
await _bookExcerptDao.deleteExcerpt(id);
notifyListeners();
}
/// 获取书籍的摘抄数量
Future<int> getBookExcerptCount(String bookId) async {
return await _bookExcerptDao.getExcerptCount(bookId);
}
// ========== 回收站相关方法 ==========
/// 获取已删除的影视
Future<List<Movie>> getDeletedMovies() async {
return await _movieDao.getDeletedMovies();
}
/// 恢复影视
Future<void> restoreMovie(String id) async {
await _movieDao.restoreMovie(id);
await loadMovies();
}
/// 彻底删除影视
Future<void> permanentDeleteMovie(String id) async {
await ImagePathHelper.instance.deleteMovieImages(id);
await _movieDao.permanentDeleteMovie(id);
}
/// 获取已删除的书籍
Future<List<Book>> getDeletedBooks() async {
return await _bookDao.getDeletedBooks();
}
/// 恢复书籍
Future<void> restoreBook(String id) async {
await _bookDao.restoreBook(id);
await loadBooks();
}
/// 彻底删除书籍
Future<void> permanentDeleteBook(String id) async {
await ImagePathHelper.instance.deleteBookImages(id);
await _bookDao.permanentDeleteBook(id);
}
/// 获取已删除的笔记
Future<List<Note>> getDeletedNotes() async {
return await _noteDao.getDeletedNotes();
}
/// 恢复笔记
Future<void> restoreNote(String id) async {
await _noteDao.restoreNote(id);
await loadNotes();
}
/// 彻底删除笔记
Future<void> permanentDeleteNote(String id) async {
await ImagePathHelper.instance.deleteNoteImages(id);
await _noteDao.permanentDeleteNote(id);
}
/// 获取已删除的游戏
Future<List<Game>> getDeletedGames() async {
return await _gameDao.getDeletedGames();
}
/// 恢复游戏
Future<void> restoreGame(String id) async {
await _gameDao.restoreGame(id);
await loadGames();
}
/// 彻底删除游戏
Future<void> permanentDeleteGame(String id) async {
await ImagePathHelper.instance.deleteGameImages(id);
await _gameDao.permanentDeleteGame(id);
}
/// 清空回收站
Future<void> clearRecycleBin() async {
final deletedMovies = await getDeletedMovies();
final deletedBooks = await getDeletedBooks();
final deletedNotes = await getDeletedNotes();
final deletedGames = await getDeletedGames();
final deletedMovieReviews = await getDeletedMovieReviews();
final deletedBookReviews = await getDeletedBookReviews();
final deletedBookExcerpts = await getDeletedBookExcerpts();
final deletedGameReviews = await getDeletedGameReviews();
final deletedPeople = await getDeletedPeople();
// 先收集需要删除图片的 ID再在事务中批量删除数据库记录
final movieIds = deletedMovies.map((m) => m.id).toList();
final bookIds = deletedBooks.map((b) => b.id).toList();
final noteIds = deletedNotes.map((n) => n.id).toList();
final gameIds = deletedGames.map((g) => g.id).toList();
final personIds = deletedPeople.map((p) => p.id).toList();
// 事务内批量删除数据库记录,保证原子性
final db = await DatabaseHelper.instance.database;
await db.transaction((txn) async {
for (final id in movieIds) {
await txn.delete('movie_reviews', where: 'movie_id = ?', whereArgs: [id]);
await txn.delete('movie_posters', where: 'movie_id = ?', whereArgs: [id]);
await txn.delete('movies', where: 'id = ?', whereArgs: [id]);
}
for (final id in bookIds) {
await txn.delete('book_reviews', where: 'book_id = ?', whereArgs: [id]);
await txn.delete('book_excerpts', where: 'book_id = ?', whereArgs: [id]);
await txn.delete('books', where: 'id = ?', whereArgs: [id]);
}
for (final id in noteIds) {
await txn.delete('notes', where: 'id = ?', whereArgs: [id]);
}
for (final id in gameIds) {
await txn.delete('game_reviews', where: 'game_id = ?', whereArgs: [id]);
await txn.delete('game_screenshots', where: 'game_id = ?', whereArgs: [id]);
await txn.delete('games', where: 'id = ?', whereArgs: [id]);
}
for (final review in deletedMovieReviews) {
await txn.delete('movie_reviews', where: 'id = ?', whereArgs: [review.id]);
}
for (final review in deletedBookReviews) {
await txn.delete('book_reviews', where: 'id = ?', whereArgs: [review.id]);
}
for (final excerpt in deletedBookExcerpts) {
await txn.delete('book_excerpts', where: 'id = ?', whereArgs: [excerpt.id]);
}
for (final review in deletedGameReviews) {
await txn.delete('game_reviews', where: 'id = ?', whereArgs: [review.id]);
}
for (final id in personIds) {
await txn.delete('movie_people', where: 'person_id = ?', whereArgs: [id]);
await txn.delete('book_people', where: 'person_id = ?', whereArgs: [id]);
await txn.delete('game_people', where: 'person_id = ?', whereArgs: [id]);
await txn.delete('people', where: 'id = ?', whereArgs: [id]);
}
});
// 事务成功后,清理关联的图片文件(文件删除失败不影响数据一致性)
for (final id in movieIds) {
await ImagePathHelper.instance.deleteMovieImages(id);
}
for (final id in bookIds) {
await ImagePathHelper.instance.deleteBookImages(id);
}
for (final id in noteIds) {
await ImagePathHelper.instance.deleteNoteImages(id);
}
for (final id in gameIds) {
await ImagePathHelper.instance.deleteGameImages(id);
}
for (final id in personIds) {
await ImagePathHelper.instance.deletePersonImages(id);
}
await loadMovies();
await loadBooks();
await loadNotes();
await loadGames();
await loadPlaylists();
await loadPeople();
}
// ========== 影评书评回收站 ==========
Future<List<MovieReview>> getDeletedMovieReviews() async {
return await _reviewDao.getDeletedReviews();
}
Future<void> restoreMovieReview(String id) async {
await _reviewDao.restoreReview(id);
}
Future<void> permanentDeleteMovieReview(String id) async {
await _reviewDao.permanentDeleteReview(id);
}
Future<List<BookReview>> getDeletedBookReviews() async {
return await _bookReviewDao.getDeletedReviews();
}
Future<void> restoreBookReview(String id) async {
await _bookReviewDao.restoreReview(id);
}
Future<void> permanentDeleteBookReview(String id) async {
await _bookReviewDao.permanentDeleteReview(id);
}
// ========== 游戏评价回收站 ==========
Future<List<GameReview>> getDeletedGameReviews() async {
return await _gameReviewDao.getDeletedReviews();
}
Future<void> restoreGameReview(String id) async {
await _gameReviewDao.restoreReview(id);
}
Future<void> permanentDeleteGameReview(String id) async {
await _gameReviewDao.permanentDeleteReview(id);
}
// ========== 摘抄回收站方法 ==========
Future<List<BookExcerpt>> getDeletedBookExcerpts() async {
return await _bookExcerptDao.getDeletedExcerpts();
}
Future<void> restoreBookExcerpt(String id) async {
await _bookExcerptDao.restoreExcerpt(id);
}
Future<void> permanentDeleteBookExcerpt(String id) async {
await _bookExcerptDao.permanentDeleteExcerpt(id);
}
// ========== 标签管理方法 ==========
Future<List<Map<String, dynamic>>> getTags(String type, {bool excludeHidden = false}) async {
return await _tagDao.getTagsByType(type, excludeHidden: excludeHidden);
}
Future<void> toggleTagHidden(String tagId) async {
await _tagDao.toggleHidden(tagId);
notifyListeners();
}
Future<String> addTag(String name, String type) async {
final id = await _tagDao.addTag(name, type);
await _reloadByTagType(type);
return id;
}
Future<bool> renameTag(String tagId, String newName, String type) async {
final result = await _tagDao.renameTag(tagId, newName);
if (result) {
await _reloadByTagType(type);
}
return result;
}
Future<void> deleteTag(String tagId, String type,
{String? replacementName}) async {
await _tagDao.deleteTag(tagId, replacementName: replacementName);
await _reloadByTagType(type);
}
/// 仅删除标签本身,不级联影响已有条目
Future<void> deleteTagOnly(String tagId, String type) async {
await _tagDao.deleteTagOnly(tagId);
await _reloadByTagType(type);
}
/// 从影视/书籍/笔记数据中解析标签,同步到 tags 表
Future<int> syncTagsFromData() async {
final db = await DatabaseHelper.instance.database;
final now = DateTime.now().toIso8601String();
int counter = 0;
int added = 0;
Future<void> insertTag(String name, String type) async {
try {
await db.insert('tags', {
'id': 'tag_${DateTime.now().millisecondsSinceEpoch}_${counter++}',
'name': name,
'type': type,
'created_at': now,
});
added++;
} catch (_) {
// 忽略 UNIQUE 约束冲突(标签已存在)
}
}
// 影视类型
final movies = await db.query('movies',
where: 'genres IS NOT NULL AND genres != ?', whereArgs: ['[]']);
for (final row in movies) {
for (final genre in parseStringListGeneric(row['genres'])) {
await insertTag(genre, 'movie_genre');
}
}
// 书籍类型
final books = await db.query('books',
where: 'genres IS NOT NULL AND genres != ?', whereArgs: ['[]']);
for (final row in books) {
for (final genre in parseStringListGeneric(row['genres'])) {
await insertTag(genre, 'book_genre');
}
}
// 笔记标签
final notes = await db.query('notes',
where: 'tags IS NOT NULL AND tags != ? AND tags != ?',
whereArgs: ['[]', '']);
for (final row in notes) {
for (final tag in parseStringListGeneric(row['tags'])) {
await insertTag(tag, 'note_tag');
}
}
// 游戏类型
final games = await db.query('games',
where: 'genres IS NOT NULL AND genres != ?', whereArgs: ['[]']);
for (final row in games) {
for (final genre in parseStringListGeneric(row['genres'])) {
await insertTag(genre, 'game_genre');
}
}
return added;
}
Future<void> _reloadByTagType(String type) async {
switch (type) {
case 'movie_genre':
await loadMovies();
case 'book_genre':
await loadBooks();
case 'note_tag':
await loadNotes();
case 'game_genre':
await loadGames();
}
}
// ========== 人物管理 ==========
/// 添加人物
Future<void> addPerson(Person person) async {
await _personDao.insertPerson(person);
await loadPeople();
}
/// 更新人物
Future<void> updatePerson(Person person) async {
await _personDao.updatePerson(person);
await loadPeople();
}
/// 更新人物封面偏移量
Future<void> updatePersonCoverOffset(String personId, double offset) async {
await _personDao.updateCoverOffset(personId, offset);
}
/// 软删除人物
Future<void> removePerson(String id) async {
await _personDao.deletePerson(id);
await loadPeople();
}
/// 恢复已删除的人物
Future<void> restorePerson(String id) async {
await _personDao.restorePerson(id);
await loadPeople();
}
/// 彻底删除人物
Future<void> permanentDeletePerson(String id) async {
await ImagePathHelper.instance.deletePersonImages(id);
await _personDao.permanentDeletePerson(id);
}
/// 获取已删除的人物
Future<List<Person>> getDeletedPeople() async {
return await _personDao.getDeletedPeople();
}
/// 搜索人物
Future<List<Person>> searchPeople(String keyword) async {
return await _personDao.searchPeople(keyword);
}
/// 根据ID获取人物
Future<Person?> getPersonById(String id) async {
return await _personDao.getPersonById(id);
}
// ========== 人物关联查询 ==========
/// 获取某部影视的关联人物
Future<List<MoviePerson>> getMoviePeople(String movieId) async {
return await _moviePersonDao.getByMovieId(movieId);
}
/// 获取某个人物参与的影视
Future<List<MoviePerson>> getPersonMovies(String personId) async {
return await _moviePersonDao.getByPersonId(personId);
}
/// 获取某本书的关联人物
Future<List<BookPerson>> getBookPeople(String bookId) async {
return await _bookPersonDao.getByBookId(bookId);
}
/// 获取某个人物参与的书籍
Future<List<BookPerson>> getPersonBooks(String personId) async {
return await _bookPersonDao.getByPersonId(personId);
}
/// 获取某游戏的关联人物
Future<List<GamePerson>> getGamePeople(String gameId) async {
return await _gamePersonDao.getByGameId(gameId);
}
/// 获取某个人物参与的游戏
Future<List<GamePerson>> getPersonGames(String personId) async {
return await _gamePersonDao.getByPersonId(personId);
}
/// 保存影视的人物关联(先删后插)
Future<void> saveMoviePeople(String movieId, List<MoviePerson> people) async {
await _moviePersonDao.deleteByMovieId(movieId);
if (people.isNotEmpty) {
await _moviePersonDao.insertAll(people);
}
}
/// 保存书籍的人物关联
Future<void> saveBookPeople(String bookId, List<BookPerson> people) async {
await _bookPersonDao.deleteByBookId(bookId);
if (people.isNotEmpty) {
await _bookPersonDao.insertAll(people);
}
}
/// 保存游戏的人物关联
Future<void> saveGamePeople(String gameId, List<GamePerson> people) async {
await _gamePersonDao.deleteByGameId(gameId);
if (people.isNotEmpty) {
await _gamePersonDao.insertAll(people);
}
}
/// 保存某个人物的所有影视关联(先删后插,按 personId 维度)
Future<void> savePersonMovieRelations(String personId, List<MoviePerson> relations) async {
await _moviePersonDao.deleteByPersonId(personId);
if (relations.isNotEmpty) {
await _moviePersonDao.insertAll(relations);
}
}
/// 保存某个人物的所有书籍关联
Future<void> savePersonBookRelations(String personId, List<BookPerson> relations) async {
await _bookPersonDao.deleteByPersonId(personId);
if (relations.isNotEmpty) {
await _bookPersonDao.insertAll(relations);
}
}
/// 保存某个人物的所有游戏关联
Future<void> savePersonGameRelations(String personId, List<GamePerson> relations) async {
await _gamePersonDao.deleteByPersonId(personId);
if (relations.isNotEmpty) {
await _gamePersonDao.insertAll(relations);
}
}
/// 扫描所有作品的人物字段,自动创建人物并建立关联
/// 返回 (新增人物数, 新增关联数, 合并去重数)
Future<({int newPersons, int newRelations, int merged})> refreshPersonRelations() async {
// 先合并已有的同名重复人物
final merged = await _personDao.mergeDuplicatePeople();
if (merged > 0) {
await loadPeople();
}
final nameIndex = <String, Person>{};
for (final p in _people) {
nameIndex[p.name] = p;
}
int newPersons = 0;
int newRelations = 0;
const uuid = Uuid();
final now = DateTime.now();
Future<Person> findOrCreate(String name, String occupationLabel) async {
final existing = nameIndex[name];
if (existing != null) return existing;
// 内存索引未命中时回退查库,避免重复创建同名人物
final dbExisting = await _personDao.getPersonByName(name);
if (dbExisting != null) {
nameIndex[name] = dbExisting;
return dbExisting;
}
final person = Person(
id: uuid.v4(),
name: name,
occupation: [occupationLabel],
createdAt: now,
updatedAt: now,
);
await _personDao.insertPerson(person);
nameIndex[name] = person;
newPersons++;
return person;
}
// 影视directors/writers/actors
for (final movie in _movies) {
if (movie.isDeleted) continue;
final entries = <(List<String>, String, String)>[
(movie.directors, 'director', '导演'),
(movie.writers, 'writer', '编剧'),
(movie.actors, 'actor', '演员'),
];
for (final (names, roleType, occupationLabel) in entries) {
final seen = <String>{};
for (final name in names) {
final trimmed = name.trim();
if (trimmed.isEmpty || !seen.add(trimmed)) continue;
final person = await findOrCreate(trimmed, occupationLabel);
if (!await _moviePersonDao.existsRelation(movie.id, person.id, roleType)) {
await _moviePersonDao.insert(MoviePerson(
id: uuid.v4(),
movieId: movie.id,
personId: person.id,
roleType: roleType,
characterName: null,
sortOrder: 0,
));
newRelations++;
}
}
}
}
// 书籍authors/translators
for (final book in _books) {
if (book.isDeleted) continue;
final entries = <(List<String>, String, String)>[
(book.authors, 'author', '作者'),
(book.translators, 'translator', '译者'),
];
for (final (names, roleType, occupationLabel) in entries) {
final seen = <String>{};
for (final name in names) {
final trimmed = name.trim();
if (trimmed.isEmpty || !seen.add(trimmed)) continue;
final person = await findOrCreate(trimmed, occupationLabel);
if (!await _bookPersonDao.existsRelation(book.id, person.id, roleType)) {
await _bookPersonDao.insert(BookPerson(
id: uuid.v4(),
bookId: book.id,
personId: person.id,
roleType: roleType,
sortOrder: 0,
));
newRelations++;
}
}
}
}
// 游戏developer
for (final game in _games) {
if (game.isDeleted) continue;
final seen = <String>{};
for (final name in game.developer) {
final trimmed = name.trim();
if (trimmed.isEmpty || !seen.add(trimmed)) continue;
final person = await findOrCreate(trimmed, '开发者');
if (!await _gamePersonDao.existsRelation(game.id, person.id, 'developer')) {
await _gamePersonDao.insert(GamePerson(
id: uuid.v4(),
gameId: game.id,
personId: person.id,
roleType: 'developer',
sortOrder: 0,
));
newRelations++;
}
}
}
await loadPeople();
return (newPersons: newPersons, newRelations: newRelations, merged: merged);
}
// ========== 角色相关方法 ==========
// ─── 影视角色 ───
Future<List<MovieCharacter>> getMovieCharacters(String movieId) async {
return await _movieCharacterDao.getByMovieId(movieId);
}
Future<int> getMovieCharacterCount(String movieId) async {
return await _movieCharacterDao.getCount(movieId);
}
Future<void> addMovieCharacter(MovieCharacter character) async {
await _movieCharacterDao.insert(character);
notifyListeners();
}
Future<void> updateMovieCharacter(MovieCharacter character) async {
await _movieCharacterDao.update(character);
notifyListeners();
}
Future<void> deleteMovieCharacter(String id) async {
await _movieCharacterDao.delete(id);
notifyListeners();
}
// ─── 书籍角色 ───
Future<List<BookCharacter>> getBookCharacters(String bookId) async {
return await _bookCharacterDao.getByBookId(bookId);
}
Future<int> getBookCharacterCount(String bookId) async {
return await _bookCharacterDao.getCount(bookId);
}
Future<void> addBookCharacter(BookCharacter character) async {
await _bookCharacterDao.insert(character);
notifyListeners();
}
Future<void> updateBookCharacter(BookCharacter character) async {
await _bookCharacterDao.update(character);
notifyListeners();
}
Future<void> deleteBookCharacter(String id) async {
await _bookCharacterDao.delete(id);
notifyListeners();
}
// ─── 游戏角色 ───
Future<List<GameCharacter>> getGameCharacters(String gameId) async {
return await _gameCharacterDao.getByGameId(gameId);
}
Future<int> getGameCharacterCount(String gameId) async {
return await _gameCharacterDao.getCount(gameId);
}
Future<void> addGameCharacter(GameCharacter character) async {
await _gameCharacterDao.insert(character);
notifyListeners();
}
Future<void> updateGameCharacter(GameCharacter character) async {
await _gameCharacterDao.update(character);
notifyListeners();
}
Future<void> deleteGameCharacter(String id) async {
await _gameCharacterDao.delete(id);
notifyListeners();
}
}