import 'dart:collection'; import 'dart:io'; import 'dart:math'; 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 _movies = []; List _books = []; List _notes = []; List _games = []; List _playlists = []; List _people = []; // 当前主界面选中的标签 (0: 观影,1: 阅读,2: 笔记) int _mainTabIndex = 0; // 主页模块显示方式 (0=底部tab切换, 1=顶部下拉切换) int _homeModuleSwitchMode = 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; // 毛玻璃主题(themeMode=3):标志 + 随机封面 bool _frostedActive = false; String? _frostedCoverPath; // 配色方案索引 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; // 影视状态栏样式 (0: 胶囊滑块, 1: 下划线, 2: 芯片组, 3: 下拉) int _movieStatusBarStyle = 0; // 影视分类索引 int _movieCategoryIndex = 0; // 阅读选中的状态 (0: 读完,1: 在读,2: 准备读) int _bookStatusIndex = 0; // 书籍状态栏样式 (0: 胶囊滑块, 1: 下划线, 2: 芯片组, 3: 下拉) int _bookStatusBarStyle = 0; // 书架模式(不显示分类,按创建时间排序) bool _bookshelfMode = false; // 游戏选中的状态 (0: 已通关,1: 在玩,2: 想玩,3: 弃游) int _gameStatusIndex = 0; // 游戏列表布局样式 (0: 网格, 1: 列表, 2: 大图卡片) int _gameLayoutStyle = 0; // 游戏状态栏样式 (0: 胶囊滑块, 1: 下划线, 2: 芯片组, 3: 下拉) int _gameStatusBarStyle = 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 retryInitDatabase() async { _dbInitFailed = false; notifyListeners(); await initDatabase(); } // 初始化数据库 Future 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; _movieStatusBarStyle = userPrefs.movieStatusBarStyle; _bookStatusBarStyle = userPrefs.bookStatusBarStyle; _bookshelfMode = userPrefs.bookshelfMode; _gameLayoutStyle = userPrefs.gameLayoutStyle; _gameWallMode = userPrefs.gameWallMode; _gameStatusBarStyle = userPrefs.gameStatusBarStyle; _homeModuleSwitchMode = userPrefs.homeModuleSwitchMode; 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 loadMovies() async { _movies = await _movieDao.getAllMovies(sortMode: UserPrefs().movieSortMode); if (_frostedActive && _frostedCoverPath == null) refreshFrostedCover(); notifyListeners(); } // 加载书籍数据 Future loadBooks() async { _books = await _bookDao.getAllBooks(sortMode: UserPrefs().bookSortMode); if (_frostedActive && _frostedCoverPath == null) refreshFrostedCover(); notifyListeners(); } // 加载笔记数据 Future loadNotes({int? sortMode}) async { _notes = await _noteDao.getAllNotes(sortMode: sortMode ?? UserPrefs().noteSortMode); notifyListeners(); } // 加载游戏数据 Future loadGames() async { _games = await _gameDao.getAllGames(sortMode: UserPrefs().gameSortMode); if (_frostedActive && _frostedCoverPath == null) refreshFrostedCover(); notifyListeners(); } // 加载片单数据 Future loadPlaylists() async { _playlists = await _playlistDao.getAllPlaylists(); notifyListeners(); } // 加载人物数据 Future loadPeople() async { _people = await _personDao.getAllPeople(); notifyListeners(); } /// 编辑返回后触发列表页重载 /// [itemId] 被编辑条目的 ID,用于就地更新而非重置分页 void setEditRefresh([String? itemId]) { _editRefreshCounter++; _lastEditedItemId = itemId; notifyListeners(); } // ─── 分页加载(供列表页触底加载使用)──────────────────────── static const int _pageSize = 20; Future> 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> loadBooksPaged({String? status, required int offset, int sortMode = 0}) async { return _bookDao.getBooksPaged(status: status, limit: _pageSize, offset: offset, sortMode: sortMode); } Future> loadNotesPaged({required int offset, int sortMode = 0}) async { return _noteDao.getNotesPaged(limit: _pageSize, offset: offset, sortMode: sortMode); } Future> 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 homeModuleSwitchMode => _homeModuleSwitchMode; 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 movieStatusBarStyle => _movieStatusBarStyle; int get movieCategoryIndex => _movieCategoryIndex; int get bookStatusIndex => _bookStatusIndex; int get bookStatusBarStyle => _bookStatusBarStyle; bool get bookshelfMode => _bookshelfMode; int get gameStatusIndex => _gameStatusIndex; int get gameLayoutStyle => _gameLayoutStyle; bool get gameWallMode => _gameWallMode; int get gameStatusBarStyle => _gameStatusBarStyle; bool get drawerOpen => _drawerOpen; bool get bottomNavVisible => _bottomNavVisible; ThemeMode get themeMode => _themeMode; int get colorSchemeIndex => _colorSchemeIndex; // 毛玻璃状态 bool get frostedActive => _frostedActive; String? get frostedCoverPath => _frostedCoverPath; String get fontFamily => _fontFamily; List get movies => UnmodifiableListView(_movies); List get books => UnmodifiableListView(_books); List get notes => UnmodifiableListView(_notes); List get games => UnmodifiableListView(_games); List get playlists => UnmodifiableListView(_playlists); List get people => UnmodifiableListView(_people); // 根据状态获取影视列表 List getMoviesByStatus(String status) { return _movies.where((movie) => movie.status == status).toList(); } // 根据状态获取书籍列表 List getBooksByStatus(String status) { return _books.where((book) => book.status == status).toList(); } // Setters void setMainTabIndex(int index) { _mainTabIndex = index; notifyListeners(); } void setHomeModuleSwitchMode(int mode) { _homeModuleSwitchMode = mode; UserPrefs().setHomeModuleSwitchMode(mode); 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) { final wasFrosted = _frostedActive; _frostedActive = false; UserPrefs().setThemeMode(mode.index); // 0=system, 1=light, 2=dark, 3=frosted if (_themeMode != mode || wasFrosted) { _themeMode = mode; notifyListeners(); } } /// 启用毛玻璃主题(themeMode=3),随机取一张封面作为背景 void enableFrosted() { _frostedActive = true; _themeMode = ThemeMode.dark; UserPrefs().setThemeMode(3); refreshFrostedCover(); } /// 从影视/书籍/游戏封面中随机取一张作为毛玻璃背景 void refreshFrostedCover() { if (!_frostedActive) return; final paths = [ ..._movies.where((m) => m.posterPath != null && m.posterPath!.isNotEmpty).map((m) => m.posterPath!), ..._books.where((b) => b.coverPath != null && b.coverPath!.isNotEmpty).map((b) => b.coverPath!), ..._games.where((g) => g.coverPath != null && g.coverPath!.isNotEmpty).map((g) => g.coverPath!), ]; _frostedCoverPath = paths.isEmpty ? null : paths[Random().nextInt(paths.length)]; notifyListeners(); } void loadThemeMode() { final prefs = UserPrefs(); switch (prefs.themeMode) { case 1: _themeMode = ThemeMode.light; case 2: _themeMode = ThemeMode.dark; case 3: _frostedActive = true; _themeMode = ThemeMode.dark; default: _themeMode = ThemeMode.system; } _colorSchemeIndex = prefs.colorSchemeIndex.clamp(-1, AppTheme.seedColors.length - 1); _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 setMovieStatusBarStyle(int style) { _movieStatusBarStyle = style; UserPrefs().setMovieStatusBarStyle(style); 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 setBookStatusBarStyle(int style) { _bookStatusBarStyle = style; UserPrefs().setBookStatusBarStyle(style); 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 setGameStatusBarStyle(int style) { _gameStatusBarStyle = style; UserPrefs().setGameStatusBarStyle(style); notifyListeners(); } void toggleDrawer() { _drawerOpen = !_drawerOpen; notifyListeners(); } void closeDrawer() { _drawerOpen = false; notifyListeners(); } // ─── 图片上传辅助 ──────────────────────────────────────────────── // 添加影视记录 Future addMovie(Movie movie) async { await _movieDao.insertMovie(movie); _movies.add(movie); notifyListeners(); } Future 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 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 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 removeMovie(String id) async { await _movieDao.deleteMovie(id); _movies.removeWhere((m) => m.id == id); notifyListeners(); } Future addBook(Book book) async { await _bookDao.insertBook(book); _books.add(book); notifyListeners(); } Future 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 removeBook(String id) async { await _bookDao.deleteBook(id); _books.removeWhere((b) => b.id == id); notifyListeners(); } Future addNote(Note note) async { await _noteDao.insertNote(note); _notes.add(note); notifyListeners(); } Future 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 removeNote(String id) async { await _noteDao.deleteNote(id); _notes.removeWhere((n) => n.id == id); notifyListeners(); } Future addGame(Game game) async { await _gameDao.insertGame(game); _games.add(game); notifyListeners(); } Future 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 removeGame(String id) async { await _gameDao.deleteGame(id); _games.removeWhere((g) => g.id == id); notifyListeners(); } // ─── 片单操作 ───────────────────────────────────────────────── Future addPlaylist(Playlist playlist) async { await _playlistDao.insertPlaylist(playlist); _playlists.add(playlist); notifyListeners(); } Future removePlaylist(String id) async { await _playlistDao.deletePlaylist(id); _playlists.removeWhere((p) => p.id == id); notifyListeners(); } Future 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> getPlaylistItems(String playlistId) async { return _playlistDao.getPlaylistItems(playlistId); } Future 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 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> getPlaylistItemIds(String playlistId) async { return _playlistDao.getPlaylistItemIds(playlistId); } Future reorderPlaylists(List 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 reorderPlaylistItems(String playlistId, List itemIds) async { await _playlistDao.updatePlaylistItemOrder(playlistId, itemIds); } /// 仅更新游戏封面偏移量(不触发全量刷新) Future 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 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> getMovieReviews(String movieId) async { return await _reviewDao.getReviewsByMovieId(movieId); } /// 添加影评 Future addMovieReview(MovieReview review) async { await _reviewDao.insertReview(review); notifyListeners(); } /// 更新影评 Future updateMovieReview(MovieReview review) async { await _reviewDao.updateReview(review); notifyListeners(); } /// 删除影评 Future removeMovieReview(String id) async { await _reviewDao.deleteReview(id); notifyListeners(); } /// 获取影视的影评数量 Future getMovieReviewCount(String movieId) async { return await _reviewDao.getReviewCount(movieId); } // ========== 海报墙相关方法 ========== /// 获取影视的所有海报 Future> getMoviePosters(String movieId) async { return await _posterDao.getPostersByMovieId(movieId); } /// 添加海报 Future addMoviePoster(MoviePoster poster) async { await _posterDao.insertPoster(poster); notifyListeners(); } /// 删除海报 Future 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 getMoviePosterCount(String movieId) async { return await _posterDao.getPosterCount(movieId); } // ========== 游戏评价相关方法 ========== /// 获取游戏的所有评价 Future> getGameReviews(String gameId) async { return await _gameReviewDao.getReviewsByGameId(gameId); } /// 添加游戏评价 Future addGameReview(GameReview review) async { await _gameReviewDao.insertReview(review); notifyListeners(); } /// 更新游戏评价 Future updateGameReview(GameReview review) async { await _gameReviewDao.updateReview(review); notifyListeners(); } /// 删除游戏评价 Future removeGameReview(String id) async { await _gameReviewDao.deleteReview(id); notifyListeners(); } /// 获取游戏的评价数量 Future getGameReviewCount(String gameId) async { return await _gameReviewDao.getReviewCount(gameId); } // ========== 游戏截图相关方法 ========== /// 获取游戏的所有截图 Future> getGameScreenshots(String gameId) async { return await _gameScreenshotDao.getScreenshotsByGameId(gameId); } /// 添加游戏截图 Future addGameScreenshot(GameScreenshot screenshot) async { await _gameScreenshotDao.insertScreenshot(screenshot); notifyListeners(); } /// 删除游戏截图 Future 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 getGameScreenshotCount(String gameId) async { return await _gameScreenshotDao.getScreenshotCount(gameId); } // ========== 书评相关方法 ========== /// 获取书籍的所有书评 Future> getBookReviews(String bookId) async { return await _bookReviewDao.getReviewsByBookId(bookId); } /// 添加书评 Future addBookReview(BookReview review) async { await _bookReviewDao.insertReview(review); notifyListeners(); } /// 更新书评 Future updateBookReview(BookReview review) async { await _bookReviewDao.updateReview(review); notifyListeners(); } /// 删除书评 Future removeBookReview(String id) async { await _bookReviewDao.deleteReview(id); notifyListeners(); } /// 获取书籍的书评数量 Future getBookReviewCount(String bookId) async { return await _bookReviewDao.getReviewCount(bookId); } // ========== 摘抄相关方法 ========== /// 获取书籍的所有摘抄 Future> getBookExcerpts(String bookId) async { return await _bookExcerptDao.getExcerptsByBookId(bookId); } /// 添加摘抄 Future addBookExcerpt(BookExcerpt excerpt) async { await _bookExcerptDao.insertExcerpt(excerpt); notifyListeners(); } /// 更新摘抄 Future updateBookExcerpt(BookExcerpt excerpt) async { await _bookExcerptDao.updateExcerpt(excerpt); notifyListeners(); } /// 删除摘抄 Future removeBookExcerpt(String id) async { await _bookExcerptDao.deleteExcerpt(id); notifyListeners(); } /// 获取书籍的摘抄数量 Future getBookExcerptCount(String bookId) async { return await _bookExcerptDao.getExcerptCount(bookId); } // ========== 回收站相关方法 ========== /// 获取已删除的影视 Future> getDeletedMovies() async { return await _movieDao.getDeletedMovies(); } /// 恢复影视 Future restoreMovie(String id) async { await _movieDao.restoreMovie(id); await loadMovies(); } /// 彻底删除影视 Future permanentDeleteMovie(String id) async { await ImagePathHelper.instance.deleteMovieImages(id); await _movieDao.permanentDeleteMovie(id); } /// 获取已删除的书籍 Future> getDeletedBooks() async { return await _bookDao.getDeletedBooks(); } /// 恢复书籍 Future restoreBook(String id) async { await _bookDao.restoreBook(id); await loadBooks(); } /// 彻底删除书籍 Future permanentDeleteBook(String id) async { await ImagePathHelper.instance.deleteBookImages(id); await _bookDao.permanentDeleteBook(id); } /// 获取已删除的笔记 Future> getDeletedNotes() async { return await _noteDao.getDeletedNotes(); } /// 恢复笔记 Future restoreNote(String id) async { await _noteDao.restoreNote(id); await loadNotes(); } /// 彻底删除笔记 Future permanentDeleteNote(String id) async { await ImagePathHelper.instance.deleteNoteImages(id); await _noteDao.permanentDeleteNote(id); } /// 获取已删除的游戏 Future> getDeletedGames() async { return await _gameDao.getDeletedGames(); } /// 恢复游戏 Future restoreGame(String id) async { await _gameDao.restoreGame(id); await loadGames(); } /// 彻底删除游戏 Future permanentDeleteGame(String id) async { await ImagePathHelper.instance.deleteGameImages(id); await _gameDao.permanentDeleteGame(id); } /// 清空回收站 Future 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(); final deletedMovieCharacters = await getDeletedMovieCharacters(); final deletedBookCharacters = await getDeletedBookCharacters(); final deletedGameCharacters = await getDeletedGameCharacters(); // 先收集需要删除图片的 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 movieCharacterIds = deletedMovieCharacters.map((c) => c.id).toList(); final bookCharacterIds = deletedBookCharacters.map((c) => c.id).toList(); final gameCharacterIds = deletedGameCharacters.map((c) => c.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 movieCharacterIds) { await txn.delete('movie_characters', where: 'id = ?', whereArgs: [id]); } for (final id in bookCharacterIds) { await txn.delete('book_characters', where: 'id = ?', whereArgs: [id]); } for (final id in gameCharacterIds) { await txn.delete('game_characters', 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); } for (final id in movieCharacterIds) { await ImagePathHelper.instance.deleteCharacterImages(id); } for (final id in bookCharacterIds) { await ImagePathHelper.instance.deleteCharacterImages(id); } for (final id in gameCharacterIds) { await ImagePathHelper.instance.deleteCharacterImages(id); } await loadMovies(); await loadBooks(); await loadNotes(); await loadGames(); await loadPlaylists(); await loadPeople(); } // ========== 影评书评回收站 ========== Future> getDeletedMovieReviews() async { return await _reviewDao.getDeletedReviews(); } Future restoreMovieReview(String id) async { await _reviewDao.restoreReview(id); } Future permanentDeleteMovieReview(String id) async { await _reviewDao.permanentDeleteReview(id); } Future> getDeletedBookReviews() async { return await _bookReviewDao.getDeletedReviews(); } Future restoreBookReview(String id) async { await _bookReviewDao.restoreReview(id); } Future permanentDeleteBookReview(String id) async { await _bookReviewDao.permanentDeleteReview(id); } // ========== 游戏评价回收站 ========== Future> getDeletedGameReviews() async { return await _gameReviewDao.getDeletedReviews(); } Future restoreGameReview(String id) async { await _gameReviewDao.restoreReview(id); } Future permanentDeleteGameReview(String id) async { await _gameReviewDao.permanentDeleteReview(id); } // ========== 摘抄回收站方法 ========== Future> getDeletedBookExcerpts() async { return await _bookExcerptDao.getDeletedExcerpts(); } Future restoreBookExcerpt(String id) async { await _bookExcerptDao.restoreExcerpt(id); } Future permanentDeleteBookExcerpt(String id) async { await _bookExcerptDao.permanentDeleteExcerpt(id); } // ========== 标签管理方法 ========== Future>> getTags(String type, {bool excludeHidden = false}) async { return await _tagDao.getTagsByType(type, excludeHidden: excludeHidden); } Future toggleTagHidden(String tagId) async { await _tagDao.toggleHidden(tagId); notifyListeners(); } Future addTag(String name, String type, {String parentId = ''}) async { final id = await _tagDao.addTag(name, type, parentId: parentId); await _reloadByTagType(type); return id; } /// 设置标签的父级分类ID(空串表示顶级) Future setTagParent(String tagId, String type, String parentId) async { final result = await _tagDao.setTagParent(tagId, parentId); await _reloadByTagType(type); return result; } Future renameTag(String tagId, String newName, String type) async { final result = await _tagDao.renameTag(tagId, newName); if (result) { await _reloadByTagType(type); } return result; } Future deleteTag(String tagId, String type, {String? replacementName}) async { await _tagDao.deleteTag(tagId, replacementName: replacementName); await _reloadByTagType(type); } /// 仅删除标签本身,不级联影响已有条目 Future deleteTagOnly(String tagId, String type) async { await _tagDao.deleteTagOnly(tagId); await _reloadByTagType(type); } /// 从影视/书籍/笔记数据中解析标签,同步到 tags 表 Future syncTagsFromData() async { final db = await DatabaseHelper.instance.database; final now = DateTime.now().toIso8601String(); int counter = 0; int added = 0; Future 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 _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 addPerson(Person person) async { await _personDao.insertPerson(person); await loadPeople(); } /// 更新人物 Future updatePerson(Person person) async { await _personDao.updatePerson(person); await loadPeople(); } /// 更新人物封面偏移量 Future updatePersonCoverOffset(String personId, double offset) async { await _personDao.updateCoverOffset(personId, offset); } /// 软删除人物 Future removePerson(String id) async { await _personDao.deletePerson(id); await loadPeople(); } /// 恢复已删除的人物 Future restorePerson(String id) async { await _personDao.restorePerson(id); await loadPeople(); } /// 彻底删除人物 Future permanentDeletePerson(String id) async { await ImagePathHelper.instance.deletePersonImages(id); await _personDao.permanentDeletePerson(id); } /// 获取已删除的人物 Future> getDeletedPeople() async { return await _personDao.getDeletedPeople(); } /// 搜索人物 Future> searchPeople(String keyword) async { return await _personDao.searchPeople(keyword); } /// 根据ID获取人物 Future getPersonById(String id) async { return await _personDao.getPersonById(id); } // ========== 人物关联查询 ========== /// 获取某部影视的关联人物 Future> getMoviePeople(String movieId) async { return await _moviePersonDao.getByMovieId(movieId); } /// 获取某个人物参与的影视 Future> getPersonMovies(String personId) async { return await _moviePersonDao.getByPersonId(personId); } /// 获取某本书的关联人物 Future> getBookPeople(String bookId) async { return await _bookPersonDao.getByBookId(bookId); } /// 获取某个人物参与的书籍 Future> getPersonBooks(String personId) async { return await _bookPersonDao.getByPersonId(personId); } /// 获取某游戏的关联人物 Future> getGamePeople(String gameId) async { return await _gamePersonDao.getByGameId(gameId); } /// 获取某个人物参与的游戏 Future> getPersonGames(String personId) async { return await _gamePersonDao.getByPersonId(personId); } /// 保存影视的人物关联(先删后插) Future saveMoviePeople(String movieId, List people) async { await _moviePersonDao.deleteByMovieId(movieId); if (people.isNotEmpty) { await _moviePersonDao.insertAll(people); } } /// 保存书籍的人物关联 Future saveBookPeople(String bookId, List people) async { await _bookPersonDao.deleteByBookId(bookId); if (people.isNotEmpty) { await _bookPersonDao.insertAll(people); } } /// 保存游戏的人物关联 Future saveGamePeople(String gameId, List people) async { await _gamePersonDao.deleteByGameId(gameId); if (people.isNotEmpty) { await _gamePersonDao.insertAll(people); } } /// 保存某个人物的所有影视关联(先删后插,按 personId 维度) Future savePersonMovieRelations(String personId, List relations) async { await _moviePersonDao.deleteByPersonId(personId); if (relations.isNotEmpty) { await _moviePersonDao.insertAll(relations); } } /// 保存某个人物的所有书籍关联 Future savePersonBookRelations(String personId, List relations) async { await _bookPersonDao.deleteByPersonId(personId); if (relations.isNotEmpty) { await _bookPersonDao.insertAll(relations); } } /// 保存某个人物的所有游戏关联 Future savePersonGameRelations(String personId, List 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 = {}; for (final p in _people) { nameIndex[p.name] = p; } int newPersons = 0; int newRelations = 0; const uuid = Uuid(); final now = DateTime.now(); Future 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)>[ (movie.directors, 'director', '导演'), (movie.writers, 'writer', '编剧'), (movie.actors, 'actor', '演员'), ]; for (final (names, roleType, occupationLabel) in entries) { final seen = {}; for (final name in names) { final trimmed = name.trim(); if (trimmed.isEmpty || !seen.add(trimmed)) continue; final person = await findOrCreate(trimmed, occupationLabel); // 演员角色:若该影视已存在该人物的配音关联,则跳过添加演员关联 if (roleType == 'actor' && await _moviePersonDao.existsRelation(movie.id, person.id, 'voiceActor')) { continue; } 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)>[ (book.authors, 'author', '作者'), (book.translators, 'translator', '译者'), ]; for (final (names, roleType, occupationLabel) in entries) { final seen = {}; 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 = {}; 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> getMovieCharacters(String movieId) async { return await _movieCharacterDao.getByMovieId(movieId); } Future getMovieCharacterCount(String movieId) async { return await _movieCharacterDao.getCount(movieId); } Future addMovieCharacter(MovieCharacter character) async { await _movieCharacterDao.insert(character); notifyListeners(); } Future updateMovieCharacter(MovieCharacter character) async { await _movieCharacterDao.update(character); notifyListeners(); } Future deleteMovieCharacter(String id) async { await _movieCharacterDao.delete(id); notifyListeners(); } /// 恢复已删除的影视角色 Future restoreMovieCharacter(String id) async { await _movieCharacterDao.restore(id); notifyListeners(); } /// 彻底删除影视角色 Future permanentDeleteMovieCharacter(String id) async { await ImagePathHelper.instance.deleteCharacterImages(id); await _movieCharacterDao.permanentDelete(id); } /// 获取已删除的影视角色 Future> getDeletedMovieCharacters() async { return await _movieCharacterDao.getDeleted(); } // ─── 书籍角色 ─── Future> getBookCharacters(String bookId) async { return await _bookCharacterDao.getByBookId(bookId); } Future getBookCharacterCount(String bookId) async { return await _bookCharacterDao.getCount(bookId); } Future addBookCharacter(BookCharacter character) async { await _bookCharacterDao.insert(character); notifyListeners(); } Future updateBookCharacter(BookCharacter character) async { await _bookCharacterDao.update(character); notifyListeners(); } Future deleteBookCharacter(String id) async { await _bookCharacterDao.delete(id); notifyListeners(); } /// 恢复已删除的书籍角色 Future restoreBookCharacter(String id) async { await _bookCharacterDao.restore(id); notifyListeners(); } /// 彻底删除书籍角色 Future permanentDeleteBookCharacter(String id) async { await ImagePathHelper.instance.deleteCharacterImages(id); await _bookCharacterDao.permanentDelete(id); } /// 获取已删除的书籍角色 Future> getDeletedBookCharacters() async { return await _bookCharacterDao.getDeleted(); } // ─── 游戏角色 ─── Future> getGameCharacters(String gameId) async { return await _gameCharacterDao.getByGameId(gameId); } Future getGameCharacterCount(String gameId) async { return await _gameCharacterDao.getCount(gameId); } Future addGameCharacter(GameCharacter character) async { await _gameCharacterDao.insert(character); notifyListeners(); } Future updateGameCharacter(GameCharacter character) async { await _gameCharacterDao.update(character); notifyListeners(); } Future deleteGameCharacter(String id) async { await _gameCharacterDao.delete(id); notifyListeners(); } /// 恢复已删除的游戏角色 Future restoreGameCharacter(String id) async { await _gameCharacterDao.restore(id); notifyListeners(); } /// 彻底删除游戏角色 Future permanentDeleteGameCharacter(String id) async { await ImagePathHelper.instance.deleteCharacterImages(id); await _gameCharacterDao.permanentDelete(id); } /// 获取已删除的游戏角色 Future> getDeletedGameCharacters() async { return await _gameCharacterDao.getDeleted(); } }