diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e6f84e6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,144 @@ +# AGENTS.md + +Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed. + +**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment. + +## 1. Think Before Coding + +**Don't assume. Don't hide confusion. Surface tradeoffs.** + +Before implementing: +- State your assumptions explicitly. If uncertain, ask. +- If multiple interpretations exist, present them - don't pick silently. +- If a simpler approach exists, say so. Push back when warranted. +- If something is unclear, stop. Name what's confusing. Ask. + +## 2. Simplicity First + +**Minimum code that solves the problem. Nothing speculative.** + +- No features beyond what was asked. +- No abstractions for single-use code. +- No "flexibility" or "configurability" that wasn't requested. +- No error handling for impossible scenarios. +- If you write 200 lines and it could be 50, rewrite it. + +Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. + +## 3. Surgical Changes + +**Touch only what you must. Clean up only your own mess.** + +When editing existing code: +- Don't "improve" adjacent code, comments, or formatting. +- Don't refactor things that aren't broken. +- Match existing style, even if you'd do it differently. +- If you notice unrelated dead code, mention it - don't delete it. + +When your changes create orphans: +- Remove imports/variables/functions that YOUR changes made unused. +- Don't remove pre-existing dead code unless asked. + +The test: Every changed line should trace directly to the user's request. + +## 4. Goal-Driven Execution + +**Define success criteria. Loop until verified.** + +Transform tasks into verifiable goals: +- "Add validation" → "Write tests for invalid inputs, then make them pass" +- "Fix the bug" → "Write a test that reproduces it, then make it pass" +- "Refactor X" → "Ensure tests pass before and after" + +For multi-step tasks, state a brief plan: +``` +1. [Step] → verify: [check] +2. [Step] → verify: [check] +3. [Step] → verify: [check] +``` + +Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. + +--- + +**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes. + + +--- + +## Project Overview + +MookNote is a Flutter-based Android app for tracking movies, books, and notes. Language: Dart/Flutter with Chinese UI. AGPL-3.0 licensed. + +## Common Commands + +```bash +# Install dependencies +flutter pub get + +# Run the app (connect Android device or start emulator first) +flutter run + +# Build release APK +flutter build apk --release + +# Build App Bundle (for Google Play) +flutter build appbundle --release + +# Clean build +flutter clean && flutter pub get + +# Static analysis +flutter analyze + +# Use China mirror if pub get fails +$env:PUB_HOSTED_URL="https://pub.flutter-io.cn" +$env:FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn" +``` + +## Architecture + +### App Structure (lib/) + +- **main.dart** — App entry; initializes `UserPrefs`, `AppProvider`, auto-backup, usage stats, and sync validation on startup +- **models/data_models.dart** — All data models in one file: `Movie`, `Book`, `Note`, `MovieReview`, `BookReview`, `MoviePoster`, `BookExcerpt`. Each has `fromJson`/`toJson`/`copyWith` +- **providers/app_provider.dart** — Single `AppProvider` (ChangeNotifier) holds all app state. Manages movies, books, notes lists, theme mode, tab indices, drawer state. Uses DAO pattern for data access. Has a `_useRemote` flag to switch between local SQLite and remote server +- **pages/** — UI pages organized by feature domain: `movies/`, `book/`, `note/`, `sync/`, `markdown_reader/` +- **utils/** — Business logic layer: + - `database_helper.dart` — SQLite database (sqflite), version 13, with migration chain + - `movie/`, `book/`, `note/` — DAO classes for each entity (CRUD operations) + - `tag/tag_dao.dart` — Tag management + - `sync/` — Server sync, WebDAV sync, auto-backup, backup service + - `theme/app_theme.dart` — Minimalist black/white/gray theme with Material 3 + - `user_prefs.dart` — SharedPreferences wrapper (singleton) +- **widgets/** — Shared reusable widgets (list items, star rating, drawer, bottom nav, shimmer skeleton) +- **utils/app_router.dart** — Named route generator using `onGenerateRoute` with `SlideUpPageRoute` transitions + +### State Management + +Provider pattern. A single `AppProvider` (ChangeNotifier) is provided at the root via `MultiProvider`/`Consumer`. All pages read state from `context.watch()` or `context.read()`. + +### Data Layer + +- **Local**: SQLite via sqflite (`DatabaseHelper` singleton, DB name: `mooknote.db`) +- **Remote**: Optional server sync via `ServerSyncService` / `ServerDataService`. Controlled by `UserPrefs.syncEnabled` + activation code +- **DAO pattern**: Each entity has its own DAO (`MovieDao`, `BookDao`, `NoteDao`, etc.) that can operate locally or remotely based on `_useRemote` flag in AppProvider +- **Soft delete**: All models have `isDeleted` field; a recycle bin page manages restores + +### Image Storage + +Images stored at: `/mooknote/images///` on device. `ImagePathHelper` manages paths. `FadeInLocalImage` widget handles local image display. + +### Server (server/) + +Python Flask backend for user stats and sync API. Not part of the Flutter build. Run with `python app.py` or gunicorn. + +## Key Conventions + +- All Chinese UI strings are hardcoded (no i18n framework beyond Flutter's built-in localization delegates) +- UUID-based IDs for all entities (generated at creation time) +- List fields (directors, actors, genres, tags) stored as JSON-encoded strings in SQLite, parsed via `Movie.parseStringList()` +- `copyWith` uses a `_CopyWithNullSentinel` pattern to distinguish "not passed" from "passed null" for nullable fields +- Route transitions use custom `SlideUpPageRoute` (bottom-to-top slide) +- DB migrations in `DatabaseHelper._onUpgrade` — always add new migration blocks with version checks (`if (oldVersion < N)`) diff --git a/lib/pages/book/book_review_form_page.dart b/lib/pages/book/book_review_form_page.dart index a32ad9a..3e43e43 100644 --- a/lib/pages/book/book_review_form_page.dart +++ b/lib/pages/book/book_review_form_page.dart @@ -233,6 +233,7 @@ class _BookReviewFormPageState extends State { return; } + try { final now = DateTime.now(); if (widget.review == null) { @@ -264,5 +265,9 @@ class _BookReviewFormPageState extends State { ToastUtil.show(context, widget.review == null ? '添加成功' : '更新成功'); Navigator.pop(context); + } catch (e) { + if (!mounted) return; + ToastUtil.show(context, '保存失败: $e'); + } } } diff --git a/lib/pages/book/book_tab_page.dart b/lib/pages/book/book_tab_page.dart index 5854c8c..b7bf16f 100644 --- a/lib/pages/book/book_tab_page.dart +++ b/lib/pages/book/book_tab_page.dart @@ -28,6 +28,7 @@ class _BookTabPageState extends State { int _lastDataCount = -1; DateTime? _lastUpdatedAt; late ScrollController _scrollController; + AppProvider? _provider; static const _statusMap = {0: 'read', 1: 'reading', 2: 'want_to_read'}; @@ -38,6 +39,7 @@ class _BookTabPageState extends State { _scrollController = ScrollController()..addListener(_onScroll); WidgetsBinding.instance.addPostFrameCallback((_) { final provider = context.read(); + _provider = provider; provider.addListener(_onDataChanged); _lastDataCount = provider.books.length; if (provider.books.isNotEmpty) _lastUpdatedAt = provider.books.first.updatedAt; @@ -47,6 +49,7 @@ class _BookTabPageState extends State { @override void dispose() { + _provider?.removeListener(_onDataChanged); _scrollController.dispose(); super.dispose(); } diff --git a/lib/pages/movies/movie_form_page.dart b/lib/pages/movies/movie_form_page.dart index ff974d8..dffbbf9 100644 --- a/lib/pages/movies/movie_form_page.dart +++ b/lib/pages/movies/movie_form_page.dart @@ -1806,6 +1806,7 @@ class _MovieFormPageState extends State { return; } + try { // 收集所有多值字段输入框中的未提交内容 _collectUnsubmittedValues(); @@ -1869,6 +1870,10 @@ class _MovieFormPageState extends State { ToastUtil.show(context, widget.movie == null ? '添加成功' : '更新成功'); Navigator.pop(context); + } catch (e) { + if (!mounted) return; + ToastUtil.show(context, '保存失败: $e'); + } } /// 将海报从临时ID目录移动到新的影视ID目录 diff --git a/lib/pages/movies/movie_review_form_page.dart b/lib/pages/movies/movie_review_form_page.dart index ea8dca6..b1e609e 100644 --- a/lib/pages/movies/movie_review_form_page.dart +++ b/lib/pages/movies/movie_review_form_page.dart @@ -303,6 +303,7 @@ class _MovieReviewFormPageState extends State { Future _saveReview() async { if (!_formKey.currentState!.validate()) return; + try { final now = DateTime.now(); if (widget.review == null) { @@ -331,5 +332,9 @@ class _MovieReviewFormPageState extends State { if (!mounted) return; ToastUtil.show(context, widget.review == null ? '添加成功' : '更新成功'); Navigator.pop(context); + } catch (e) { + if (!mounted) return; + ToastUtil.show(context, '保存失败: $e'); + } } } diff --git a/lib/pages/movies/movie_tab_page.dart b/lib/pages/movies/movie_tab_page.dart index 0db8512..d4d8483 100644 --- a/lib/pages/movies/movie_tab_page.dart +++ b/lib/pages/movies/movie_tab_page.dart @@ -28,6 +28,7 @@ class _MovieTabPageState extends State { int _lastDataCount = -1; DateTime? _lastUpdatedAt; late ScrollController _scrollController; + AppProvider? _provider; static const _statusMap = {0: 'watched', 1: 'watching', 2: 'want_to_watch'}; @@ -38,6 +39,7 @@ class _MovieTabPageState extends State { _scrollController = ScrollController()..addListener(_onScroll); WidgetsBinding.instance.addPostFrameCallback((_) { final provider = context.read(); + _provider = provider; provider.addListener(_onDataChanged); _lastDataCount = provider.movies.length; if (provider.movies.isNotEmpty) _lastUpdatedAt = provider.movies.first.updatedAt; @@ -47,6 +49,7 @@ class _MovieTabPageState extends State { @override void dispose() { + _provider?.removeListener(_onDataChanged); _scrollController.dispose(); super.dispose(); } diff --git a/lib/pages/note/note_form_page.dart b/lib/pages/note/note_form_page.dart index f13deab..e96c1d3 100644 --- a/lib/pages/note/note_form_page.dart +++ b/lib/pages/note/note_form_page.dart @@ -825,6 +825,7 @@ class _NoteFormPageState extends State { return; } + try { final now = DateTime.now(); if (_isEditing) { @@ -873,6 +874,10 @@ class _NoteFormPageState extends State { if (!mounted) return; Navigator.pop(context); + } catch (e) { + if (!mounted) return; + ToastUtil.show(context, '保存失败: $e'); + } } /// 将图片从临时ID目录移动到新ID目录 diff --git a/lib/pages/note/note_tab_page.dart b/lib/pages/note/note_tab_page.dart index 2a6e0be..7c6bb6b 100644 --- a/lib/pages/note/note_tab_page.dart +++ b/lib/pages/note/note_tab_page.dart @@ -21,6 +21,7 @@ class _NoteTabPageState extends State { bool _isLoading = false; int _offset = 0; late ScrollController _scrollController; + AppProvider? _provider; int _layoutStyle = 0; bool _initialized = false; int _lastDataCount = -1; @@ -33,6 +34,7 @@ class _NoteTabPageState extends State { _scrollController = ScrollController()..addListener(_onScroll); WidgetsBinding.instance.addPostFrameCallback((_) { final provider = context.read(); + _provider = provider; provider.addListener(_onDataChanged); _lastDataCount = provider.notes.length; if (provider.notes.isNotEmpty) _lastUpdatedAt = provider.notes.first.updatedAt; @@ -42,6 +44,7 @@ class _NoteTabPageState extends State { @override void dispose() { + _provider?.removeListener(_onDataChanged); _scrollController.dispose(); super.dispose(); } diff --git a/lib/utils/app_router.dart b/lib/utils/app_router.dart index 06e5264..d60b6c4 100644 --- a/lib/utils/app_router.dart +++ b/lib/utils/app_router.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import '../models/data_models.dart'; +import 'slide_up_page_route.dart'; import '../pages/movies/movie_form_page.dart'; import '../pages/book/book_form_page.dart'; import '../pages/note/note_form_page.dart'; @@ -7,7 +8,6 @@ import '../pages/movies/movie_detail_page.dart'; import '../pages/book/book_detail_page.dart'; import '../pages/note/note_detail_page.dart'; import '../pages/movies/douban_webview_page.dart'; -import 'slide_up_page_route.dart'; /// 路由生成器 class AppRouter { @@ -15,56 +15,65 @@ class AppRouter { switch (settings.name) { case '/movie-form': final args = settings.arguments; - Movie? movie; - String? initialStatus; - if (args is Movie) { - movie = args; - } else if (args is Map) { - initialStatus = args['initialStatus'] as String?; - } + final Movie? movie = args is Movie ? args : null; + final String? initialStatus = + args is Map ? (args['initialStatus'] as String?) : null; return SlideUpPageRoute( page: MovieFormPage(movie: movie, initialStatus: initialStatus), ); case '/book-form': final args = settings.arguments; - Book? book; - String? initialStatus; - if (args is Book) { - book = args; - } else if (args is Map) { - initialStatus = args['initialStatus'] as String?; - } + final Book? book = args is Book ? args : null; + final String? initialStatus = + args is Map ? (args['initialStatus'] as String?) : null; return SlideUpPageRoute( page: BookFormPage(book: book, initialStatus: initialStatus), ); case '/note-form': - final note = settings.arguments as Note?; + final args = settings.arguments; + final Note? note = args is Note ? args : null; return SlideUpPageRoute(page: NoteFormPage(note: note)); case '/movie-detail': - final movie = settings.arguments as Movie; + final movie = settings.arguments is Movie ? settings.arguments as Movie : null; + if (movie == null) { + return _buildUnknownRoute(settings.name); + } return SlideUpPageRoute(page: MovieDetailPage(movie: movie)); case '/book-detail': - final book = settings.arguments as Book; + final book = settings.arguments is Book ? settings.arguments as Book : null; + if (book == null) { + return _buildUnknownRoute(settings.name); + } return SlideUpPageRoute(page: BookDetailPage(book: book)); case '/note-detail': - final note = settings.arguments as Note; + final note = settings.arguments is Note ? settings.arguments as Note : null; + if (note == null) { + return _buildUnknownRoute(settings.name); + } return SlideUpPageRoute(page: NoteDetailPage(note: note)); case '/douban-webview': - final url = settings.arguments as String; + final url = settings.arguments is String ? settings.arguments as String : null; + if (url == null) { + return _buildUnknownRoute(settings.name); + } return SlideUpPageRoute(page: DoubanWebViewPage(url: url)); default: - return MaterialPageRoute( - builder: (_) => Scaffold( - body: Center(child: Text('未找到页面:${settings.name}')), - ), - ); + return _buildUnknownRoute(settings.name); } } + + static Route _buildUnknownRoute(String? name) { + return MaterialPageRoute( + builder: (_) => Scaffold( + body: Center(child: Text('未找到页面:${name ?? ''}')), + ), + ); + } } diff --git a/lib/utils/book/book_dao.dart b/lib/utils/book/book_dao.dart index 7698f4c..3d963c4 100644 --- a/lib/utils/book/book_dao.dart +++ b/lib/utils/book/book_dao.dart @@ -1,4 +1,4 @@ -import 'package:sqflite/sqflite.dart'; +import 'package:flutter/foundation.dart'; import '../../models/data_models.dart'; import '../database_helper.dart'; @@ -6,8 +6,17 @@ import '../database_helper.dart'; class BookDao { final DatabaseHelper _dbHelper = DatabaseHelper.instance; + Future _wrap(String op, Future Function() fn) async { + try { + return await fn(); + } catch (e) { + debugPrint('[BookDao] $op error: $e'); + rethrow; + } + } + // 获取所有未删除的书籍记录 - Future> getAllBooks() async { + Future> getAllBooks() => _wrap('getAllBooks', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'books', @@ -15,12 +24,11 @@ class BookDao { whereArgs: [0], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => Book.fromJson(maps[i])); - } + }); // 分页查询书籍记录 - Future> getBooksPaged({String? status, int limit = 20, int offset = 0}) async { + Future> getBooksPaged({String? status, int limit = 20, int offset = 0}) => _wrap('getBooksPaged', () async { final db = await _dbHelper.database; String where = 'is_deleted = 0'; List whereArgs = []; @@ -31,10 +39,10 @@ class BookDao { final maps = await db.query('books', where: where, whereArgs: whereArgs, orderBy: 'created_at DESC', limit: limit, offset: offset); return List.generate(maps.length, (i) => Book.fromJson(maps[i])); - } + }); // 根据状态筛选书籍记录 - Future> getBooksByStatus(String status) async { + Future> getBooksByStatus(String status) => _wrap('getBooksByStatus', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'books', @@ -42,31 +50,29 @@ class BookDao { whereArgs: [status, 0], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => Book.fromJson(maps[i])); - } + }); // 根据ID获取书籍 - Future getBookById(String id) async { + Future getBookById(String id) => _wrap('getBookById', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'books', where: 'id = ? AND is_deleted = ?', whereArgs: [id, 0], ); - if (maps.isEmpty) return null; return Book.fromJson(maps.first); - } + }); // 添加书籍记录 - Future insertBook(Book book) async { + Future insertBook(Book book) => _wrap('insertBook', () async { final db = await _dbHelper.database; return await db.insert('books', book.toJson()); - } + }); // 更新书籍记录 - Future updateBook(Book book) async { + Future updateBook(Book book) => _wrap('updateBook', () async { final db = await _dbHelper.database; return await db.update( 'books', @@ -74,10 +80,10 @@ class BookDao { where: 'id = ?', whereArgs: [book.id], ); - } + }); // 软删除书籍记录(移入回收站) - Future deleteBook(String id) async { + Future deleteBook(String id) => _wrap('deleteBook', () async { final db = await _dbHelper.database; return await db.update( 'books', @@ -88,10 +94,10 @@ class BookDao { where: 'id = ?', whereArgs: [id], ); - } + }); // 搜索书籍(标题、别名) - Future> searchBooks(String query) async { + Future> searchBooks(String query) => _wrap('searchBooks', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'books', @@ -99,12 +105,11 @@ class BookDao { whereArgs: ['%$query%', '%$query%', 0], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => Book.fromJson(maps[i])); - } + }); // 根据作者筛选 - Future> getBooksByAuthor(String author) async { + Future> getBooksByAuthor(String author) => _wrap('getBooksByAuthor', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'books', @@ -112,12 +117,11 @@ class BookDao { whereArgs: ['%$author%', 0], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => Book.fromJson(maps[i])); - } + }); // 根据类型筛选 - Future> getBooksByGenre(String genre) async { + Future> getBooksByGenre(String genre) => _wrap('getBooksByGenre', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'books', @@ -125,14 +129,13 @@ class BookDao { whereArgs: ['%$genre%', 0], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => Book.fromJson(maps[i])); - } + }); // ========== 回收站相关方法 ========== // 获取已删除的书籍 - Future> getDeletedBooks() async { + Future> getDeletedBooks() => _wrap('getDeletedBooks', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'books', @@ -140,12 +143,11 @@ class BookDao { whereArgs: [1], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => Book.fromJson(maps[i])); - } + }); // 恢复已删除的书籍 - Future restoreBook(String id) async { + Future restoreBook(String id) => _wrap('restoreBook', () async { final db = await _dbHelper.database; return await db.update( 'books', @@ -153,15 +155,15 @@ class BookDao { where: 'id = ?', whereArgs: [id], ); - } + }); // 彻底删除书籍 - Future permanentDeleteBook(String id) async { + Future permanentDeleteBook(String id) => _wrap('permanentDeleteBook', () async { final db = await _dbHelper.database; return await db.delete( 'books', where: 'id = ?', whereArgs: [id], ); - } + }); } diff --git a/lib/utils/book/book_excerpt_dao.dart b/lib/utils/book/book_excerpt_dao.dart index 2d617d7..8340b26 100644 --- a/lib/utils/book/book_excerpt_dao.dart +++ b/lib/utils/book/book_excerpt_dao.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:sqflite/sqflite.dart'; import '../../models/data_models.dart'; import '../database_helper.dart'; @@ -6,8 +7,17 @@ import '../database_helper.dart'; class BookExcerptDao { final DatabaseHelper _dbHelper = DatabaseHelper.instance; + Future _wrap(String op, Future Function() fn) async { + try { + return await fn(); + } catch (e) { + debugPrint('[BookExcerptDao] $op error: $e'); + rethrow; + } + } + /// 获取书籍的所有摘抄 - Future> getExcerptsByBookId(String bookId) async { + Future> getExcerptsByBookId(String bookId) => _wrap('getExcerptsByBookId', () async { final db = await _dbHelper.database; final maps = await db.query( 'book_excerpts', @@ -16,24 +26,22 @@ class BookExcerptDao { orderBy: 'created_at DESC', ); return maps.map((map) => BookExcerpt.fromJson(map)).toList(); - } + }); /// 根据ID获取摘抄 - Future getExcerptById(String id) async { + Future getExcerptById(String id) => _wrap('getExcerptById', () async { final db = await _dbHelper.database; final maps = await db.query( 'book_excerpts', where: 'id = ? AND is_deleted = 0', whereArgs: [id], ); - if (maps.isNotEmpty) { - return BookExcerpt.fromJson(maps.first); - } + if (maps.isNotEmpty) return BookExcerpt.fromJson(maps.first); return null; - } + }); /// 插入摘抄 - Future insertExcerpt(BookExcerpt excerpt) async { + Future insertExcerpt(BookExcerpt excerpt) => _wrap('insertExcerpt', () async { final db = await _dbHelper.database; await db.insert( 'book_excerpts', @@ -41,10 +49,10 @@ class BookExcerptDao { conflictAlgorithm: ConflictAlgorithm.replace, ); return excerpt.id; - } + }); /// 更新摘抄 - Future updateExcerpt(BookExcerpt excerpt) async { + Future updateExcerpt(BookExcerpt excerpt) => _wrap('updateExcerpt', () async { final db = await _dbHelper.database; await db.update( 'book_excerpts', @@ -52,10 +60,10 @@ class BookExcerptDao { where: 'id = ?', whereArgs: [excerpt.id], ); - } + }); /// 删除摘抄(软删除) - Future deleteExcerpt(String id) async { + Future deleteExcerpt(String id) => _wrap('deleteExcerpt', () async { final db = await _dbHelper.database; await db.update( 'book_excerpts', @@ -63,30 +71,30 @@ class BookExcerptDao { where: 'id = ?', whereArgs: [id], ); - } + }); /// 彻底删除摘抄 - Future permanentDeleteExcerpt(String id) async { + Future permanentDeleteExcerpt(String id) => _wrap('permanentDeleteExcerpt', () async { final db = await _dbHelper.database; await db.delete( 'book_excerpts', where: 'id = ?', whereArgs: [id], ); - } + }); /// 获取摘抄数量 - Future getExcerptCount(String bookId) async { + Future getExcerptCount(String bookId) => _wrap('getExcerptCount', () async { final db = await _dbHelper.database; final result = await db.rawQuery( 'SELECT COUNT(*) as count FROM book_excerpts WHERE book_id = ? AND is_deleted = 0', [bookId], ); return result.first['count'] as int? ?? 0; - } + }); /// 获取所有已删除的摘抄 - Future> getDeletedExcerpts() async { + Future> getDeletedExcerpts() => _wrap('getDeletedExcerpts', () async { final db = await _dbHelper.database; final maps = await db.query( 'book_excerpts', @@ -94,10 +102,10 @@ class BookExcerptDao { orderBy: 'updated_at DESC', ); return maps.map((map) => BookExcerpt.fromJson(map)).toList(); - } + }); /// 恢复已删除的摘抄 - Future restoreExcerpt(String id) async { + Future restoreExcerpt(String id) => _wrap('restoreExcerpt', () async { final db = await _dbHelper.database; await db.update( 'book_excerpts', @@ -105,5 +113,5 @@ class BookExcerptDao { where: 'id = ?', whereArgs: [id], ); - } + }); } diff --git a/lib/utils/book/book_review_dao.dart b/lib/utils/book/book_review_dao.dart index e722583..9e887e9 100644 --- a/lib/utils/book/book_review_dao.dart +++ b/lib/utils/book/book_review_dao.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:sqflite/sqflite.dart'; import '../../models/data_models.dart'; import '../database_helper.dart'; @@ -6,8 +7,17 @@ import '../database_helper.dart'; class BookReviewDao { final DatabaseHelper _dbHelper = DatabaseHelper.instance; + Future _wrap(String op, Future Function() fn) async { + try { + return await fn(); + } catch (e) { + debugPrint('[BookReviewDao] $op error: $e'); + rethrow; + } + } + /// 获取书籍的所有书评 - Future> getReviewsByBookId(String bookId) async { + Future> getReviewsByBookId(String bookId) => _wrap('getReviewsByBookId', () async { final db = await _dbHelper.database; final maps = await db.query( 'book_reviews', @@ -16,24 +26,22 @@ class BookReviewDao { orderBy: 'created_at DESC', ); return maps.map((map) => BookReview.fromJson(map)).toList(); - } + }); /// 根据ID获取书评 - Future getReviewById(String id) async { + Future getReviewById(String id) => _wrap('getReviewById', () async { final db = await _dbHelper.database; final maps = await db.query( 'book_reviews', where: 'id = ? AND is_deleted = 0', whereArgs: [id], ); - if (maps.isNotEmpty) { - return BookReview.fromJson(maps.first); - } + if (maps.isNotEmpty) return BookReview.fromJson(maps.first); return null; - } + }); /// 插入书评 - Future insertReview(BookReview review) async { + Future insertReview(BookReview review) => _wrap('insertReview', () async { final db = await _dbHelper.database; await db.insert( 'book_reviews', @@ -41,10 +49,10 @@ class BookReviewDao { conflictAlgorithm: ConflictAlgorithm.replace, ); return review.id; - } + }); /// 更新书评 - Future updateReview(BookReview review) async { + Future updateReview(BookReview review) => _wrap('updateReview', () async { final db = await _dbHelper.database; await db.update( 'book_reviews', @@ -52,10 +60,10 @@ class BookReviewDao { where: 'id = ?', whereArgs: [review.id], ); - } + }); /// 删除书评(软删除) - Future deleteReview(String id) async { + Future deleteReview(String id) => _wrap('deleteReview', () async { final db = await _dbHelper.database; await db.update( 'book_reviews', @@ -63,30 +71,30 @@ class BookReviewDao { where: 'id = ?', whereArgs: [id], ); - } + }); /// 彻底删除书评 - Future permanentDeleteReview(String id) async { + Future permanentDeleteReview(String id) => _wrap('permanentDeleteReview', () async { final db = await _dbHelper.database; await db.delete( 'book_reviews', where: 'id = ?', whereArgs: [id], ); - } + }); /// 获取书评数量 - Future getReviewCount(String bookId) async { + Future getReviewCount(String bookId) => _wrap('getReviewCount', () async { final db = await _dbHelper.database; final result = await db.rawQuery( 'SELECT COUNT(*) as count FROM book_reviews WHERE book_id = ? AND is_deleted = 0', [bookId], ); return result.first['count'] as int? ?? 0; - } + }); /// 获取所有已删除的书评 - Future> getDeletedReviews() async { + Future> getDeletedReviews() => _wrap('getDeletedReviews', () async { final db = await _dbHelper.database; final maps = await db.query( 'book_reviews', @@ -94,10 +102,10 @@ class BookReviewDao { orderBy: 'updated_at DESC', ); return maps.map((map) => BookReview.fromJson(map)).toList(); - } + }); /// 恢复已删除的书评 - Future restoreReview(String id) async { + Future restoreReview(String id) => _wrap('restoreReview', () async { final db = await _dbHelper.database; await db.update( 'book_reviews', @@ -105,5 +113,5 @@ class BookReviewDao { where: 'id = ?', whereArgs: [id], ); - } + }); } diff --git a/lib/utils/database_helper.dart b/lib/utils/database_helper.dart index a7ab622..b33c831 100644 --- a/lib/utils/database_helper.dart +++ b/lib/utils/database_helper.dart @@ -1,5 +1,7 @@ import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; +import 'dart:io'; +import 'dart:typed_data'; import '../models/data_models.dart'; /// 数据库帮助类 - 管理数据库的创建和版本控制 @@ -26,6 +28,23 @@ class DatabaseHelper { _database = await _initDB('mooknote.db'); } + /// 从备份字节重写数据库文件并安全重连, + /// 始终按当前代码的 targetVersion 重新初始化,避免版本不一致。 + Future reopenDatabaseFromBytes(Uint8List bytes) async { + await close(); + + final dbPath = await getDatabasesPath(); + final path = join(dbPath, 'mooknote.db'); + + final dbFile = File(path); + if (await dbFile.exists()) { + await dbFile.delete(); + } + + await dbFile.writeAsBytes(bytes, flush: true); + _database = await _initDB('mooknote.db'); + } + Future get database async { if (_database != null) return _database!; _database = await _initDB('mooknote.db'); diff --git a/lib/utils/image_path_helper.dart b/lib/utils/image_path_helper.dart index e731697..a9ade11 100644 --- a/lib/utils/image_path_helper.dart +++ b/lib/utils/image_path_helper.dart @@ -1,9 +1,10 @@ import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as p; /// 图片路径管理助手 - 按分类和ID组织图片存储 -/// +/// /// 存储结构: /// images/movies/{movieId}/xxxx.jpg - 影视海报 /// images/movies/{movieId}/posterimgs/xxxx.jpg - 影视海报墙图片 @@ -11,11 +12,11 @@ import 'package:path/path.dart' as p; /// images/notes/{noteId}/xxxx.jpg - 笔记图片 class ImagePathHelper { static final ImagePathHelper instance = ImagePathHelper._init(); - + ImagePathHelper._init(); - + String? _appDirPath; - + /// 获取应用文档目录 Future get _appDir async { if (_appDirPath != null) return _appDirPath!; @@ -23,141 +24,166 @@ class ImagePathHelper { _appDirPath = appDir.path; return _appDirPath!; } - + /// 获取图片根目录 Future get imagesRoot async { final appDir = await _appDir; return p.join(appDir, 'images'); } - + // ==================== 影视相关路径 ==================== - + /// 获取影视图片目录 /// 路径: images/movies/{movieId}/ Future getMovieImagesDir(String movieId) async { final root = await imagesRoot; return p.join(root, 'movies', movieId); } - + /// 获取影视海报路径 /// 路径: images/movies/{movieId}/{fileName} Future getMoviePosterPath(String movieId, String fileName) async { final dir = await getMovieImagesDir(movieId); return p.join(dir, fileName); } - + /// 获取影视海报墙目录 /// 路径: images/movies/{movieId}/posterimgs/ Future getMoviePosterImgsDir(String movieId) async { final dir = await getMovieImagesDir(movieId); return p.join(dir, 'posterimgs'); } - + /// 获取影视海报墙图片路径 /// 路径: images/movies/{movieId}/posterimgs/{fileName} Future getMoviePosterImgPath(String movieId, String fileName) async { final dir = await getMoviePosterImgsDir(movieId); return p.join(dir, fileName); } - + // ==================== 书籍相关路径 ==================== - + /// 获取书籍图片目录 /// 路径: images/books/{bookId}/ Future getBookImagesDir(String bookId) async { final root = await imagesRoot; return p.join(root, 'books', bookId); } - + /// 获取书籍封面路径 /// 路径: images/books/{bookId}/{fileName} Future getBookCoverPath(String bookId, String fileName) async { final dir = await getBookImagesDir(bookId); return p.join(dir, fileName); } - + // ==================== 笔记相关路径 ==================== - + /// 获取笔记图片目录 /// 路径: images/notes/{noteId}/ Future getNoteImagesDir(String noteId) async { final root = await imagesRoot; return p.join(root, 'notes', noteId); } - + /// 获取笔记图片路径 /// 路径: images/notes/{noteId}/{fileName} Future getNoteImagePath(String noteId, String fileName) async { final dir = await getNoteImagesDir(noteId); return p.join(dir, fileName); } - + // ==================== 目录操作 ==================== - + /// 确保目录存在 Future ensureDirExists(String dirPath) async { - final dir = Directory(dirPath); - if (!await dir.exists()) { - await dir.create(recursive: true); + try { + final dir = Directory(dirPath); + if (!await dir.exists()) { + await dir.create(recursive: true); + } + } catch (e) { + debugPrint('[ImagePathHelper] ensureDirExists($dirPath) error: $e'); + rethrow; } } - + /// 删除影视图片目录(包括海报和海报墙) /// 删除路径: images/movies/{movieId}/ Future deleteMovieImages(String movieId) async { final dirPath = await getMovieImagesDir(movieId); await _deleteDirectory(dirPath); } - + /// 删除书籍图片目录 /// 删除路径: images/books/{bookId}/ Future deleteBookImages(String bookId) async { final dirPath = await getBookImagesDir(bookId); await _deleteDirectory(dirPath); } - + /// 删除笔记图片目录 /// 删除路径: images/notes/{noteId}/ Future deleteNoteImages(String noteId) async { final dirPath = await getNoteImagesDir(noteId); await _deleteDirectory(dirPath); } - + /// 删除目录及其内容 Future _deleteDirectory(String dirPath) async { - final dir = Directory(dirPath); - if (await dir.exists()) { - await dir.delete(recursive: true); + try { + final dir = Directory(dirPath); + if (await dir.exists()) { + await dir.delete(recursive: true); + } + } catch (e) { + debugPrint('[ImagePathHelper] deleteDirectory($dirPath) error: $e'); + rethrow; } } - + /// 移动文件到新位置 Future moveFile(String sourcePath, String targetDir, String fileName) async { - await ensureDirExists(targetDir); - final targetPath = p.join(targetDir, fileName); - final sourceFile = File(sourcePath); - if (await sourceFile.exists()) { - await sourceFile.rename(targetPath); + try { + await ensureDirExists(targetDir); + final targetPath = p.join(targetDir, fileName); + final sourceFile = File(sourcePath); + if (await sourceFile.exists()) { + await sourceFile.rename(targetPath); + } + return targetPath; + } catch (e) { + debugPrint('[ImagePathHelper] moveFile($sourcePath) error: $e'); + rethrow; } - return targetPath; } - + /// 复制文件到新位置 Future copyFile(String sourcePath, String targetDir, String fileName) async { - await ensureDirExists(targetDir); - final targetPath = p.join(targetDir, fileName); - final sourceFile = File(sourcePath); - if (await sourceFile.exists()) { - await sourceFile.copy(targetPath); + try { + await ensureDirExists(targetDir); + final targetPath = p.join(targetDir, fileName); + final sourceFile = File(sourcePath); + if (await sourceFile.exists()) { + await sourceFile.copy(targetPath); + } + return targetPath; + } catch (e) { + debugPrint('[ImagePathHelper] copyFile($sourcePath) error: $e'); + rethrow; } - return targetPath; } - + /// 删除单个文件 Future deleteFile(String filePath) async { - final file = File(filePath); - if (await file.exists()) { - await file.delete(); + try { + final file = File(filePath); + if (await file.exists()) { + await file.delete(); + } + } catch (e) { + debugPrint('[ImagePathHelper] deleteFile($filePath) error: $e'); + rethrow; } } } diff --git a/lib/utils/movie/movie_dao.dart b/lib/utils/movie/movie_dao.dart index 81cc529..bc78e54 100644 --- a/lib/utils/movie/movie_dao.dart +++ b/lib/utils/movie/movie_dao.dart @@ -1,4 +1,4 @@ -import 'package:sqflite/sqflite.dart'; +import 'package:flutter/foundation.dart'; import '../../models/data_models.dart'; import '../database_helper.dart'; @@ -6,8 +6,17 @@ import '../database_helper.dart'; class MovieDao { final DatabaseHelper _dbHelper = DatabaseHelper.instance; + Future _wrap(String op, Future Function() fn) async { + try { + return await fn(); + } catch (e) { + debugPrint('[MovieDao] $op error: $e'); + rethrow; + } + } + // 获取所有影视记录(未删除的) - Future> getAllMovies() async { + Future> getAllMovies() => _wrap('getAllMovies', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'movies', @@ -15,12 +24,11 @@ class MovieDao { whereArgs: [0], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => Movie.fromJson(maps[i])); - } + }); // 分页查询影视记录 - Future> getMoviesPaged({String? status, int limit = 20, int offset = 0}) async { + Future> getMoviesPaged({String? status, int limit = 20, int offset = 0}) => _wrap('getMoviesPaged', () async { final db = await _dbHelper.database; String where = 'is_deleted = 0'; List whereArgs = []; @@ -31,10 +39,10 @@ class MovieDao { final maps = await db.query('movies', where: where, whereArgs: whereArgs, orderBy: 'created_at DESC', limit: limit, offset: offset); return List.generate(maps.length, (i) => Movie.fromJson(maps[i])); - } + }); // 根据状态筛选影视记录 - Future> getMoviesByStatus(String status) async { + Future> getMoviesByStatus(String status) => _wrap('getMoviesByStatus', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'movies', @@ -42,96 +50,86 @@ class MovieDao { whereArgs: [status, 0], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => Movie.fromJson(maps[i])); - } + }); // 根据导演筛选 - Future> getMoviesByDirector(String director) async { + Future> getMoviesByDirector(String director) => _wrap('getMoviesByDirector', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'movies', - where: 'is_deleted = ?', - whereArgs: [0], + where: 'directors LIKE ? AND is_deleted = ?', + whereArgs: ['%$director%', 0], orderBy: 'created_at DESC', ); - - return List.generate(maps.length, (i) => Movie.fromJson(maps[i])) + return maps.map((m) => Movie.fromJson(m)) .where((movie) => movie.directors.contains(director)) .toList(); - } + }); // 根据编剧筛选 - Future> getMoviesByWriter(String writer) async { + Future> getMoviesByWriter(String writer) => _wrap('getMoviesByWriter', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'movies', - where: 'is_deleted = ?', - whereArgs: [0], + where: 'writers LIKE ? AND is_deleted = ?', + whereArgs: ['%$writer%', 0], orderBy: 'created_at DESC', ); - - return List.generate(maps.length, (i) => Movie.fromJson(maps[i])) + return maps.map((m) => Movie.fromJson(m)) .where((movie) => movie.writers.contains(writer)) .toList(); - } + }); // 根据演员筛选 - Future> getMoviesByActor(String actor) async { + Future> getMoviesByActor(String actor) => _wrap('getMoviesByActor', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'movies', - where: 'is_deleted = ?', - whereArgs: [0], + where: 'actors LIKE ? AND is_deleted = ?', + whereArgs: ['%$actor%', 0], orderBy: 'created_at DESC', ); - - return List.generate(maps.length, (i) => Movie.fromJson(maps[i])) + return maps.map((m) => Movie.fromJson(m)) .where((movie) => movie.actors.contains(actor)) .toList(); - } + }); // 根据类型筛选 - Future> getMoviesByGenre(String genre) async { + Future> getMoviesByGenre(String genre) => _wrap('getMoviesByGenre', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'movies', - where: 'is_deleted = ?', - whereArgs: [0], + where: 'genres LIKE ? AND is_deleted = ?', + whereArgs: ['%$genre%', 0], orderBy: 'created_at DESC', ); - - return List.generate(maps.length, (i) => Movie.fromJson(maps[i])) + return maps.map((m) => Movie.fromJson(m)) .where((movie) => movie.genres.contains(genre)) .toList(); - } + }); // 搜索影视(标题或别名) - Future> searchMovies(String keyword) async { + Future> searchMovies(String keyword) => _wrap('searchMovies', () async { final db = await _dbHelper.database; + final likeKeyword = '%$keyword%'; final List> maps = await db.query( 'movies', - where: 'is_deleted = ?', - whereArgs: [0], + where: '(title LIKE ? OR alternate_titles LIKE ?) AND is_deleted = ?', + whereArgs: [likeKeyword, likeKeyword, 0], orderBy: 'created_at DESC', ); - - final lowerKeyword = keyword.toLowerCase(); - return List.generate(maps.length, (i) => Movie.fromJson(maps[i])) - .where((movie) => - movie.title.toLowerCase().contains(lowerKeyword) || - movie.alternateTitles.any((t) => t.toLowerCase().contains(lowerKeyword))) - .toList(); - } + return maps.map((m) => Movie.fromJson(m)).toList(); + }); // 添加影视记录 - Future insertMovie(Movie movie) async { + Future insertMovie(Movie movie) => _wrap('insertMovie', () async { final db = await _dbHelper.database; return await db.insert('movies', movie.toJson()); - } + }); // 更新影视记录 - Future updateMovie(Movie movie) async { + Future updateMovie(Movie movie) => _wrap('updateMovie', () async { final db = await _dbHelper.database; return await db.update( 'movies', @@ -139,10 +137,10 @@ class MovieDao { where: 'id = ?', whereArgs: [movie.id], ); - } + }); // 删除影视记录(软删除) - Future deleteMovie(String id) async { + Future deleteMovie(String id) => _wrap('deleteMovie', () async { final db = await _dbHelper.database; return await db.update( 'movies', @@ -150,52 +148,73 @@ class MovieDao { where: 'id = ?', whereArgs: [id], ); - } + }); // 获取所有导演(去重) - Future> getAllDirectors() async { + Future> getAllDirectors() => _wrap('getAllDirectors', () async { final movies = await getAllMovies(); final directors = {}; for (final movie in movies) { directors.addAll(movie.directors); } return directors.toList()..sort(); - } + }); // 获取所有编剧(去重) - Future> getAllWriters() async { + Future> getAllWriters() => _wrap('getAllWriters', () async { final movies = await getAllMovies(); final writers = {}; for (final movie in movies) { writers.addAll(movie.writers); } return writers.toList()..sort(); - } + }); // 获取所有演员(去重) - Future> getAllActors() async { + Future> getAllActors() => _wrap('getAllActors', () async { final movies = await getAllMovies(); final actors = {}; for (final movie in movies) { actors.addAll(movie.actors); } return actors.toList()..sort(); - } + }); // 获取所有类型(去重) - Future> getAllGenres() async { + Future> getAllGenres() => _wrap('getAllGenres', () async { final movies = await getAllMovies(); final genres = {}; for (final movie in movies) { genres.addAll(movie.genres); } return genres.toList()..sort(); - } + }); + + // 一次性获取所有元数据(导演、编剧、演员、类型) + Future>> getAllMetadata() => _wrap('getAllMetadata', () async { + final movies = await getAllMovies(); + final directors = {}; + final writers = {}; + final actors = {}; + final genres = {}; + for (final movie in movies) { + directors.addAll(movie.directors); + writers.addAll(movie.writers); + actors.addAll(movie.actors); + genres.addAll(movie.genres); + } + return { + 'directors': directors.toList()..sort(), + 'writers': writers.toList()..sort(), + 'actors': actors.toList()..sort(), + 'genres': genres.toList()..sort(), + }; + }); // ========== 回收站相关方法 ========== // 获取已删除的影视 - Future> getDeletedMovies() async { + Future> getDeletedMovies() => _wrap('getDeletedMovies', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'movies', @@ -203,12 +222,11 @@ class MovieDao { whereArgs: [1], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => Movie.fromJson(maps[i])); - } + }); // 恢复已删除的影视 - Future restoreMovie(String id) async { + Future restoreMovie(String id) => _wrap('restoreMovie', () async { final db = await _dbHelper.database; return await db.update( 'movies', @@ -216,15 +234,15 @@ class MovieDao { where: 'id = ?', whereArgs: [id], ); - } + }); // 彻底删除影视 - Future permanentDeleteMovie(String id) async { + Future permanentDeleteMovie(String id) => _wrap('permanentDeleteMovie', () async { final db = await _dbHelper.database; return await db.delete( 'movies', where: 'id = ?', whereArgs: [id], ); - } + }); } diff --git a/lib/utils/movie/movie_poster_dao.dart b/lib/utils/movie/movie_poster_dao.dart index 9b11480..0898249 100644 --- a/lib/utils/movie/movie_poster_dao.dart +++ b/lib/utils/movie/movie_poster_dao.dart @@ -1,4 +1,4 @@ -import 'package:sqflite/sqflite.dart'; +import 'package:flutter/foundation.dart'; import '../../models/data_models.dart'; import '../database_helper.dart'; @@ -6,8 +6,17 @@ import '../database_helper.dart'; class MoviePosterDao { final DatabaseHelper _dbHelper = DatabaseHelper.instance; + Future _wrap(String op, Future Function() fn) async { + try { + return await fn(); + } catch (e) { + debugPrint('[MoviePosterDao] $op error: $e'); + rethrow; + } + } + /// 获取影视的所有海报 - Future> getPostersByMovieId(String movieId) async { + Future> getPostersByMovieId(String movieId) => _wrap('getPostersByMovieId', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'movie_posters', @@ -15,31 +24,29 @@ class MoviePosterDao { whereArgs: [movieId], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => MoviePoster.fromJson(maps[i])); - } + }); /// 根据ID获取海报 - Future getPosterById(String id) async { + Future getPosterById(String id) => _wrap('getPosterById', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'movie_posters', where: 'id = ? AND is_deleted = 0', whereArgs: [id], ); - if (maps.isEmpty) return null; return MoviePoster.fromJson(maps.first); - } + }); /// 添加海报 - Future insertPoster(MoviePoster poster) async { + Future insertPoster(MoviePoster poster) => _wrap('insertPoster', () async { final db = await _dbHelper.database; return await db.insert('movie_posters', poster.toJson()); - } + }); /// 软删除海报 - Future deletePoster(String id) async { + Future deletePoster(String id) => _wrap('deletePoster', () async { final db = await _dbHelper.database; return await db.update( 'movie_posters', @@ -47,15 +54,15 @@ class MoviePosterDao { where: 'id = ?', whereArgs: [id], ); - } + }); /// 获取影视的海报数量 - Future getPosterCount(String movieId) async { + Future getPosterCount(String movieId) => _wrap('getPosterCount', () async { final db = await _dbHelper.database; final result = await db.rawQuery( 'SELECT COUNT(*) as count FROM movie_posters WHERE movie_id = ? AND is_deleted = 0', [movieId], ); - return result.first['count'] as int; - } + return result.first['count'] as int? ?? 0; + }); } diff --git a/lib/utils/movie/movie_review_dao.dart b/lib/utils/movie/movie_review_dao.dart index 95d7032..5675787 100644 --- a/lib/utils/movie/movie_review_dao.dart +++ b/lib/utils/movie/movie_review_dao.dart @@ -1,4 +1,4 @@ -import 'package:sqflite/sqflite.dart'; +import 'package:flutter/foundation.dart'; import '../../models/data_models.dart'; import '../database_helper.dart'; @@ -6,8 +6,17 @@ import '../database_helper.dart'; class MovieReviewDao { final DatabaseHelper _dbHelper = DatabaseHelper.instance; + Future _wrap(String op, Future Function() fn) async { + try { + return await fn(); + } catch (e) { + debugPrint('[MovieReviewDao] $op error: $e'); + rethrow; + } + } + /// 获取影视的所有影评 - Future> getReviewsByMovieId(String movieId) async { + Future> getReviewsByMovieId(String movieId) => _wrap('getReviewsByMovieId', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'movie_reviews', @@ -15,31 +24,29 @@ class MovieReviewDao { whereArgs: [movieId], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => MovieReview.fromJson(maps[i])); - } + }); /// 根据ID获取影评 - Future getReviewById(String id) async { + Future getReviewById(String id) => _wrap('getReviewById', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'movie_reviews', where: 'id = ? AND is_deleted = 0', whereArgs: [id], ); - if (maps.isEmpty) return null; return MovieReview.fromJson(maps.first); - } + }); /// 添加影评 - Future insertReview(MovieReview review) async { + Future insertReview(MovieReview review) => _wrap('insertReview', () async { final db = await _dbHelper.database; return await db.insert('movie_reviews', review.toJson()); - } + }); /// 更新影评 - Future updateReview(MovieReview review) async { + Future updateReview(MovieReview review) => _wrap('updateReview', () async { final db = await _dbHelper.database; return await db.update( 'movie_reviews', @@ -47,10 +54,10 @@ class MovieReviewDao { where: 'id = ?', whereArgs: [review.id], ); - } + }); /// 软删除影评 - Future deleteReview(String id) async { + Future deleteReview(String id) => _wrap('deleteReview', () async { final db = await _dbHelper.database; return await db.update( 'movie_reviews', @@ -58,20 +65,20 @@ class MovieReviewDao { where: 'id = ?', whereArgs: [id], ); - } + }); /// 获取影视的影评数量 - Future getReviewCount(String movieId) async { + Future getReviewCount(String movieId) => _wrap('getReviewCount', () async { final db = await _dbHelper.database; final result = await db.rawQuery( 'SELECT COUNT(*) as count FROM movie_reviews WHERE movie_id = ? AND is_deleted = 0', [movieId], ); - return result.first['count'] as int; - } + return result.first['count'] as int? ?? 0; + }); /// 获取短评列表 - Future> getShortReviews(String movieId) async { + Future> getShortReviews(String movieId) => _wrap('getShortReviews', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'movie_reviews', @@ -79,12 +86,11 @@ class MovieReviewDao { whereArgs: [movieId], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => MovieReview.fromJson(maps[i])); - } + }); /// 获取长评列表 - Future> getLongReviews(String movieId) async { + Future> getLongReviews(String movieId) => _wrap('getLongReviews', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'movie_reviews', @@ -92,7 +98,6 @@ class MovieReviewDao { whereArgs: [movieId], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => MovieReview.fromJson(maps[i])); - } + }); } diff --git a/lib/utils/note/note_dao.dart b/lib/utils/note/note_dao.dart index cf9141b..7ffc178 100644 --- a/lib/utils/note/note_dao.dart +++ b/lib/utils/note/note_dao.dart @@ -1,4 +1,4 @@ -import 'package:sqflite/sqflite.dart'; +import 'package:flutter/foundation.dart'; import '../../models/data_models.dart'; import '../database_helper.dart'; @@ -6,8 +6,17 @@ import '../database_helper.dart'; class NoteDao { final DatabaseHelper _dbHelper = DatabaseHelper.instance; + Future _wrap(String op, Future Function() fn) async { + try { + return await fn(); + } catch (e) { + debugPrint('[NoteDao] $op error: $e'); + rethrow; + } + } + // 获取所有未删除的笔记 - Future> getAllNotes() async { + Future> getAllNotes() => _wrap('getAllNotes', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'notes', @@ -15,39 +24,37 @@ class NoteDao { whereArgs: [0], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => Note.fromJson(maps[i])); - } + }); // 分页查询笔记 - Future> getNotesPaged({int limit = 20, int offset = 0}) async { + Future> getNotesPaged({int limit = 20, int offset = 0}) => _wrap('getNotesPaged', () async { final db = await _dbHelper.database; final maps = await db.query('notes', where: 'is_deleted = 0', orderBy: 'created_at DESC', limit: limit, offset: offset); return List.generate(maps.length, (i) => Note.fromJson(maps[i])); - } + }); // 根据ID获取笔记 - Future getNoteById(String id) async { + Future getNoteById(String id) => _wrap('getNoteById', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'notes', - where: 'id = ?', - whereArgs: [id], + where: 'id = ? AND is_deleted = ?', + whereArgs: [id, 0], ); - if (maps.isEmpty) return null; return Note.fromJson(maps.first); - } + }); // 添加笔记 - Future insertNote(Note note) async { + Future insertNote(Note note) => _wrap('insertNote', () async { final db = await _dbHelper.database; return await db.insert('notes', note.toJson()); - } + }); // 更新笔记 - Future updateNote(Note note) async { + Future updateNote(Note note) => _wrap('updateNote', () async { final db = await _dbHelper.database; return await db.update( 'notes', @@ -55,10 +62,10 @@ class NoteDao { where: 'id = ?', whereArgs: [note.id], ); - } + }); // 软删除笔记 - Future deleteNote(String id) async { + Future deleteNote(String id) => _wrap('deleteNote', () async { final db = await _dbHelper.database; return await db.update( 'notes', @@ -66,12 +73,12 @@ class NoteDao { where: 'id = ?', whereArgs: [id], ); - } + }); // ========== 回收站相关方法 ========== // 获取已删除的笔记 - Future> getDeletedNotes() async { + Future> getDeletedNotes() => _wrap('getDeletedNotes', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'notes', @@ -79,12 +86,11 @@ class NoteDao { whereArgs: [1], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => Note.fromJson(maps[i])); - } + }); // 恢复已删除的笔记 - Future restoreNote(String id) async { + Future restoreNote(String id) => _wrap('restoreNote', () async { final db = await _dbHelper.database; return await db.update( 'notes', @@ -92,20 +98,20 @@ class NoteDao { where: 'id = ?', whereArgs: [id], ); - } + }); // 彻底删除笔记 - Future permanentDeleteNote(String id) async { + Future permanentDeleteNote(String id) => _wrap('permanentDeleteNote', () async { final db = await _dbHelper.database; return await db.delete( 'notes', where: 'id = ?', whereArgs: [id], ); - } + }); // 搜索笔记 - Future> searchNotes(String query) async { + Future> searchNotes(String query) => _wrap('searchNotes', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'notes', @@ -113,12 +119,11 @@ class NoteDao { whereArgs: ['%$query%', '%$query%', '%$query%', 0], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => Note.fromJson(maps[i])); - } + }); // 根据标签筛选 - Future> getNotesByTag(String tag) async { + Future> getNotesByTag(String tag) => _wrap('getNotesByTag', () async { final db = await _dbHelper.database; final List> maps = await db.query( 'notes', @@ -126,7 +131,6 @@ class NoteDao { whereArgs: ['%$tag%', 0], orderBy: 'created_at DESC', ); - return List.generate(maps.length, (i) => Note.fromJson(maps[i])); - } + }); } diff --git a/lib/utils/sync/server_sync_service.dart b/lib/utils/sync/server_sync_service.dart index f0ef1c5..da831b4 100644 --- a/lib/utils/sync/server_sync_service.dart +++ b/lib/utils/sync/server_sync_service.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:path_provider/path_provider.dart'; @@ -402,18 +403,8 @@ class ServerSyncService { debugPrint('[Sync] 数据库下载响应: ${dbResp.statusCode} size=${dbResp.bodyBytes.length}'); if (dbResp.statusCode != 200) return false; - final dbPath = await DatabaseHelper.instance.databasePath; - if (dbPath != null) { - final dbFile = File(dbPath); - // 先关掉旧连接,删除旧文件,再写入新数据库 - await DatabaseHelper.instance.close(); - if (await dbFile.exists()) await dbFile.delete(); - await dbFile.writeAsBytes(dbResp.bodyBytes); - // 设置正确的版本号 - final db = await openDatabase(dbPath, version: 14); - await db.close(); - debugPrint('[Sync] 数据库已写入: $dbPath'); - } + await DatabaseHelper.instance.reopenDatabaseFromBytes(dbResp.bodyBytes); + debugPrint('[Sync] 数据库已重写并重新打开'); final images = (info['images'] as List?) ?.map((e) => e is Map ? {'name': e['name'] as String, 'rel_path': e['rel_path'] as String} : null) diff --git a/lib/utils/tag/tag_dao.dart b/lib/utils/tag/tag_dao.dart index ac1b44a..4209d8d 100644 --- a/lib/utils/tag/tag_dao.dart +++ b/lib/utils/tag/tag_dao.dart @@ -1,27 +1,38 @@ import 'dart:convert'; +import 'package:flutter/foundation.dart'; +import 'package:sqflite/sqflite.dart'; import '../database_helper.dart'; class TagDao { final DatabaseHelper _dbHelper = DatabaseHelper.instance; + Future _wrap(String op, Future Function() fn) async { + try { + return await fn(); + } catch (e) { + debugPrint('[TagDao] $op error: $e'); + rethrow; + } + } + /// 获取指定类型的所有标签,按名称排序 - Future>> getTagsByType(String type) async { + Future>> getTagsByType(String type) => _wrap('getTagsByType', () async { final db = await _dbHelper.database; return await db.query('tags', where: 'type = ?', whereArgs: [type], orderBy: 'name ASC'); - } + }); /// 根据ID获取标签 - Future?> getTagById(String id) async { + Future?> getTagById(String id) => _wrap('getTagById', () async { final db = await _dbHelper.database; final results = await db.query('tags', where: 'id = ?', whereArgs: [id]); return results.isNotEmpty ? results.first : null; - } + }); /// 添加标签,返回新标签ID - Future addTag(String name, String type) async { + Future addTag(String name, String type) => _wrap('addTag', () async { final db = await _dbHelper.database; final id = 'tag_${DateTime.now().millisecondsSinceEpoch}'; await db.insert('tags', { @@ -31,10 +42,10 @@ class TagDao { 'created_at': DateTime.now().toIso8601String(), }); return id; - } + }); - /// 重命名标签,同时级联更新所有关联条目 - Future renameTag(String tagId, String newName) async { + /// 重命名标签,同时级联更新所有关联条目(事务保护) + Future renameTag(String tagId, String newName) => _wrap('renameTag', () async { final db = await _dbHelper.database; final tag = await getTagById(tagId); @@ -50,26 +61,27 @@ class TagDao { whereArgs: [newName, type, tagId]); if (existing.isNotEmpty) return false; - await db.update('tags', {'name': newName}, - where: 'id = ?', whereArgs: [tagId]); + await db.transaction((txn) async { + await txn.update('tags', {'name': newName}, + where: 'id = ?', whereArgs: [tagId]); - // 级联更新 - switch (type) { - case 'movie_genre': - await _cascadeRenameInMovies(oldName, newName); - case 'book_genre': - await _cascadeRenameInBooks(oldName, newName); - case 'note_tag': - await _cascadeRenameInNotes(oldName, newName); - } + switch (type) { + case 'movie_genre': + await _cascadeRenameInMovies(txn, oldName, newName); + case 'book_genre': + await _cascadeRenameInBooks(txn, oldName, newName); + case 'note_tag': + await _cascadeRenameInNotes(txn, oldName, newName); + } + }); return true; - } + }); - /// 删除标签 + /// 删除标签(事务保护) /// [replacementName] 不为 null 时,先将所有条目中的旧标签替换为新标签,再删除 /// [replacementName] 为 null 时,从所有条目中移除该标签 - Future deleteTag(String tagId, {String? replacementName}) async { + Future deleteTag(String tagId, {String? replacementName}) => _wrap('deleteTag', () async { final db = await _dbHelper.database; final tag = await getTagById(tagId); @@ -78,137 +90,144 @@ class TagDao { final name = tag['name'] as String; final type = tag['type'] as String; - if (replacementName != null && replacementName != name) { - await _ensureTagExists(replacementName, type); - switch (type) { - case 'movie_genre': - await _cascadeRenameInMovies(name, replacementName); - case 'book_genre': - await _cascadeRenameInBooks(name, replacementName); - case 'note_tag': - await _cascadeRenameInNotes(name, replacementName); + await db.transaction((txn) async { + if (replacementName != null && replacementName != name) { + await _ensureTagExists(txn, replacementName, type); + switch (type) { + case 'movie_genre': + await _cascadeRenameInMovies(txn, name, replacementName); + case 'book_genre': + await _cascadeRenameInBooks(txn, name, replacementName); + case 'note_tag': + await _cascadeRenameInNotes(txn, name, replacementName); + } + } else if (replacementName == null) { + switch (type) { + case 'movie_genre': + await _cascadeDeleteFromMovies(txn, name); + case 'book_genre': + await _cascadeDeleteFromBooks(txn, name); + case 'note_tag': + await _cascadeDeleteFromNotes(txn, name); + } } - } else if (replacementName == null) { - switch (type) { - case 'movie_genre': - await _cascadeDeleteFromMovies(name); - case 'book_genre': - await _cascadeDeleteFromBooks(name); - case 'note_tag': - await _cascadeDeleteFromNotes(name); - } - } - await db.delete('tags', where: 'id = ?', whereArgs: [tagId]); - } + await txn.delete('tags', where: 'id = ?', whereArgs: [tagId]); + }); + }); /// 仅删除标签本身,不级联影响已有条目(标签名保留在条目上) - Future deleteTagOnly(String tagId) async { + Future deleteTagOnly(String tagId) => _wrap('deleteTagOnly', () async { final db = await _dbHelper.database; await db.delete('tags', where: 'id = ?', whereArgs: [tagId]); - } + }); /// 确保标签存在(用于替换操作) - Future _ensureTagExists(String name, String type) async { - final db = await _dbHelper.database; - final existing = await db.query('tags', + Future _ensureTagExists(Transaction txn, String name, String type) async { + final existing = await txn.query('tags', where: 'name = ? AND type = ?', whereArgs: [name, type]); if (existing.isEmpty) { - await addTag(name, type); + final id = 'tag_${DateTime.now().millisecondsSinceEpoch}'; + await txn.insert('tags', { + 'id': id, + 'name': name, + 'type': type, + 'created_at': DateTime.now().toIso8601String(), + }); } } // ====== 级联重命名 ====== - Future _cascadeRenameInMovies(String oldName, String newName) async { - final db = await _dbHelper.database; - final movies = await db.query('movies'); + Future _cascadeRenameInMovies(Transaction txn, String oldName, String newName) async { + final movies = await txn.query('movies', + where: "genres LIKE ?", whereArgs: ['%$oldName%']); + final now = DateTime.now().toIso8601String(); for (final row in movies) { final genres = _parseList(row['genres']); - if (genres.contains(oldName) && !genres.contains(newName)) { - final updated = genres.map((g) => g == oldName ? newName : g).toList(); - await db.update('movies', { - 'genres': jsonEncode(updated), - 'updated_at': DateTime.now().toIso8601String(), - }, where: 'id = ?', whereArgs: [row['id']]); - } + if (!genres.contains(oldName)) continue; + final updated = genres.map((g) => g == oldName ? newName : g).toList(); + await txn.update('movies', { + 'genres': jsonEncode(updated), + 'updated_at': now, + }, where: 'id = ?', whereArgs: [row['id']]); } } - Future _cascadeRenameInBooks(String oldName, String newName) async { - final db = await _dbHelper.database; - final books = await db.query('books'); + Future _cascadeRenameInBooks(Transaction txn, String oldName, String newName) async { + final books = await txn.query('books', + where: "genres LIKE ?", whereArgs: ['%$oldName%']); + final now = DateTime.now().toIso8601String(); for (final row in books) { final genres = _parseList(row['genres']); - if (genres.contains(oldName) && !genres.contains(newName)) { - final updated = genres.map((g) => g == oldName ? newName : g).toList(); - await db.update('books', { - 'genres': jsonEncode(updated), - 'updated_at': DateTime.now().toIso8601String(), - }, where: 'id = ?', whereArgs: [row['id']]); - } + if (!genres.contains(oldName)) continue; + final updated = genres.map((g) => g == oldName ? newName : g).toList(); + await txn.update('books', { + 'genres': jsonEncode(updated), + 'updated_at': now, + }, where: 'id = ?', whereArgs: [row['id']]); } } - Future _cascadeRenameInNotes(String oldName, String newName) async { - final db = await _dbHelper.database; - final notes = await db.query('notes'); + Future _cascadeRenameInNotes(Transaction txn, String oldName, String newName) async { + final notes = await txn.query('notes', + where: "tags LIKE ?", whereArgs: ['%$oldName%']); + final now = DateTime.now().toIso8601String(); for (final row in notes) { final tags = _parseList(row['tags']); - if (tags.contains(oldName) && !tags.contains(newName)) { - final updated = tags.map((t) => t == oldName ? newName : t).toList(); - await db.update('notes', { - 'tags': jsonEncode(updated), - 'updated_at': DateTime.now().toIso8601String(), - }, where: 'id = ?', whereArgs: [row['id']]); - } + if (!tags.contains(oldName)) continue; + final updated = tags.map((t) => t == oldName ? newName : t).toList(); + await txn.update('notes', { + 'tags': jsonEncode(updated), + 'updated_at': now, + }, where: 'id = ?', whereArgs: [row['id']]); } } // ====== 级联删除 ====== - Future _cascadeDeleteFromMovies(String tagName) async { - final db = await _dbHelper.database; - final movies = await db.query('movies'); + Future _cascadeDeleteFromMovies(Transaction txn, String tagName) async { + final movies = await txn.query('movies', + where: "genres LIKE ?", whereArgs: ['%$tagName%']); + final now = DateTime.now().toIso8601String(); for (final row in movies) { final genres = _parseList(row['genres']); - if (genres.contains(tagName)) { - genres.removeWhere((g) => g == tagName); - await db.update('movies', { - 'genres': jsonEncode(genres), - 'updated_at': DateTime.now().toIso8601String(), - }, where: 'id = ?', whereArgs: [row['id']]); - } + if (!genres.contains(tagName)) continue; + genres.removeWhere((g) => g == tagName); + await txn.update('movies', { + 'genres': jsonEncode(genres), + 'updated_at': now, + }, where: 'id = ?', whereArgs: [row['id']]); } } - Future _cascadeDeleteFromBooks(String tagName) async { - final db = await _dbHelper.database; - final books = await db.query('books'); + Future _cascadeDeleteFromBooks(Transaction txn, String tagName) async { + final books = await txn.query('books', + where: "genres LIKE ?", whereArgs: ['%$tagName%']); + final now = DateTime.now().toIso8601String(); for (final row in books) { final genres = _parseList(row['genres']); - if (genres.contains(tagName)) { - genres.removeWhere((g) => g == tagName); - await db.update('books', { - 'genres': jsonEncode(genres), - 'updated_at': DateTime.now().toIso8601String(), - }, where: 'id = ?', whereArgs: [row['id']]); - } + if (!genres.contains(tagName)) continue; + genres.removeWhere((g) => g == tagName); + await txn.update('books', { + 'genres': jsonEncode(genres), + 'updated_at': now, + }, where: 'id = ?', whereArgs: [row['id']]); } } - Future _cascadeDeleteFromNotes(String tagName) async { - final db = await _dbHelper.database; - final notes = await db.query('notes'); + Future _cascadeDeleteFromNotes(Transaction txn, String tagName) async { + final notes = await txn.query('notes', + where: "tags LIKE ?", whereArgs: ['%$tagName%']); + final now = DateTime.now().toIso8601String(); for (final row in notes) { final tags = _parseList(row['tags']); - if (tags.contains(tagName)) { - tags.removeWhere((t) => t == tagName); - await db.update('notes', { - 'tags': jsonEncode(tags), - 'updated_at': DateTime.now().toIso8601String(), - }, where: 'id = ?', whereArgs: [row['id']]); - } + if (!tags.contains(tagName)) continue; + tags.removeWhere((t) => t == tagName); + await txn.update('notes', { + 'tags': jsonEncode(tags), + 'updated_at': now, + }, where: 'id = ?', whereArgs: [row['id']]); } } diff --git a/lib/utils/theme/app_theme.dart b/lib/utils/theme/app_theme.dart index a083d15..4fb9351 100644 --- a/lib/utils/theme/app_theme.dart +++ b/lib/utils/theme/app_theme.dart @@ -69,7 +69,7 @@ class AppTheme { fontSize: 18, fontWeight: _semibold, color: _black, - letterSpacing: -0.3, + letterSpacing: 0, ), ), @@ -184,7 +184,7 @@ class AppTheme { fontSize: 32, fontWeight: _semibold, color: _black, - letterSpacing: -0.5, + letterSpacing: 0, height: 1.2, ), headlineMedium: TextStyle( @@ -192,7 +192,7 @@ class AppTheme { fontSize: 24, fontWeight: _semibold, color: _black, - letterSpacing: -0.3, + letterSpacing: 0, height: 1.3, ), headlineSmall: TextStyle( @@ -200,7 +200,7 @@ class AppTheme { fontSize: 20, fontWeight: _semibold, color: _black, - letterSpacing: -0.2, + letterSpacing: 0, height: 1.4, ), // 正文 @@ -282,7 +282,7 @@ class AppTheme { fontSize: 18, fontWeight: _semibold, color: _white, - letterSpacing: -0.3, + letterSpacing: 0, ), ), @@ -380,7 +380,7 @@ class AppTheme { fontSize: 32, fontWeight: _semibold, color: _white, - letterSpacing: -0.5, + letterSpacing: 0, height: 1.2, ), headlineMedium: TextStyle( @@ -388,7 +388,7 @@ class AppTheme { fontSize: 24, fontWeight: _semibold, color: _white, - letterSpacing: -0.3, + letterSpacing: 0, height: 1.3, ), headlineSmall: TextStyle( @@ -396,7 +396,7 @@ class AppTheme { fontSize: 20, fontWeight: _semibold, color: _white, - letterSpacing: -0.2, + letterSpacing: 0, height: 1.4, ), bodyLarge: TextStyle( diff --git a/lib/utils/user_prefs.dart b/lib/utils/user_prefs.dart index 6194e21..2a5ccc1 100644 --- a/lib/utils/user_prefs.dart +++ b/lib/utils/user_prefs.dart @@ -91,6 +91,8 @@ class UserPrefs { // ========== 应用图标设置 ========== + // ========== Markdown 阅读器 ========== + /// Markdown 阅读器最近选择的目录 String? get lastMdFolder => prefs.getString('lastMdFolder'); Future setLastMdFolder(String value) => prefs.setString('lastMdFolder', value); diff --git a/pubspec.lock b/pubspec.lock index 5a427dd..b9205fd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: archive sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.6.1" args: @@ -14,23 +14,23 @@ packages: description: name: args sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.7.0" async: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.dev" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.flutter-io.cn" source: hosted - version: "2.13.0" + version: "2.13.1" boolean_selector: dependency: transitive description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" characters: @@ -38,7 +38,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" checked_yaml: @@ -46,7 +46,7 @@ packages: description: name: checked_yaml sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.4" cli_util: @@ -54,7 +54,7 @@ packages: description: name: cli_util sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.2" clock: @@ -62,23 +62,23 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" code_assets: dependency: transitive description: name: code_assets - sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" - url: "https://pub.dev" + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 + url: "https://pub.flutter-io.cn" source: hosted - version: "1.0.0" + version: "1.2.1" collection: dependency: transitive description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" cross_file: @@ -86,7 +86,7 @@ packages: description: name: cross_file sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.3.5+2" crypto: @@ -94,23 +94,23 @@ packages: description: name: crypto sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.7" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.dev" + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.0.8" + version: "1.0.9" equatable: dependency: transitive description: name: equatable sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.8" extended_text_field: @@ -118,7 +118,7 @@ packages: description: name: extended_text_field sha256: "3996195c117c6beb734026a7bc0ba80d7e4e84e4edd4728caa544d8209ab4d7d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "16.0.2" extended_text_library: @@ -126,7 +126,7 @@ packages: description: name: extended_text_library sha256: "13d99f8a10ead472d5e2cf4770d3d047203fe5054b152e9eb5dc692a71befbba" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "12.0.1" fake_async: @@ -134,7 +134,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" ffi: @@ -142,7 +142,7 @@ packages: description: name: ffi sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" file: @@ -150,7 +150,7 @@ packages: description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.1" file_picker: @@ -158,7 +158,7 @@ packages: description: name: file_picker sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "8.3.7" file_selector_linux: @@ -166,7 +166,7 @@ packages: description: name: file_selector_linux sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.9.4" file_selector_macos: @@ -174,7 +174,7 @@ packages: description: name: file_selector_macos sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.9.5" file_selector_platform_interface: @@ -182,7 +182,7 @@ packages: description: name: file_selector_platform_interface sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.7.0" file_selector_windows: @@ -190,7 +190,7 @@ packages: description: name: file_selector_windows sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.9.3+5" fixnum: @@ -198,7 +198,7 @@ packages: description: name: fixnum sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" fl_chart: @@ -206,7 +206,7 @@ packages: description: name: fl_chart sha256: "74959b99b92b9eebeed1a4049426fd67c4abc3c5a0f4d12e2877097d6a11ae08" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.69.2" flutter: @@ -219,7 +219,7 @@ packages: description: name: flutter_launcher_icons sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.1" flutter_lints: @@ -227,7 +227,7 @@ packages: description: name: flutter_lints sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" flutter_localizations: @@ -240,23 +240,23 @@ packages: description: name: flutter_markdown_plus sha256: "039177906850278e8fb1cd364115ee0a46281135932fa8ecea8455522166d2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.7" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 - url: "https://pub.dev" + sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.33" + version: "2.0.35" flutter_staggered_grid_view: dependency: "direct main" description: name: flutter_staggered_grid_view sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.0" flutter_test: @@ -269,28 +269,20 @@ packages: description: flutter source: sdk version: "0.0.0" - glob: - dependency: transitive - description: - name: glob - sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" - source: hosted - version: "2.1.3" hooks: dependency: transitive description: name: hooks - sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6" - url: "https://pub.dev" + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.0.1" + version: "2.0.2" http: dependency: "direct main" description: name: http sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.6.0" http_parser: @@ -298,7 +290,7 @@ packages: description: name: http_parser sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.1.2" image: @@ -306,31 +298,31 @@ packages: description: name: image sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.3.0" image_picker: dependency: "direct main" description: name: image_picker - sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" - url: "https://pub.dev" + sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.2.1" + version: "1.2.2" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: eda9b91b7e266d9041084a42d605a74937d996b87083395c5e47835916a86156 - url: "https://pub.dev" + sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f + url: "https://pub.flutter-io.cn" source: hosted - version: "0.8.13+14" + version: "0.8.13+17" image_picker_for_web: dependency: transitive description: name: image_picker_for_web sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.1" image_picker_ios: @@ -338,7 +330,7 @@ packages: description: name: image_picker_ios sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.8.13+6" image_picker_linux: @@ -346,7 +338,7 @@ packages: description: name: image_picker_linux sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.2" image_picker_macos: @@ -354,7 +346,7 @@ packages: description: name: image_picker_macos sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.2+1" image_picker_platform_interface: @@ -362,7 +354,7 @@ packages: description: name: image_picker_platform_interface sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.11.1" image_picker_windows: @@ -370,7 +362,7 @@ packages: description: name: image_picker_windows sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.2" intl: @@ -378,23 +370,39 @@ packages: description: name: intl sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.20.2" + jni: + dependency: transitive + description: + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" json_annotation: dependency: transitive description: name: json_annotation - sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 - url: "https://pub.dev" + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.flutter-io.cn" source: hosted - version: "4.11.0" + version: "4.12.0" leak_tracker: dependency: transitive description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -402,7 +410,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -410,7 +418,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" lints: @@ -418,7 +426,7 @@ packages: description: name: lints sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" logging: @@ -426,23 +434,23 @@ packages: description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" markdown: dependency: transitive description: name: markdown - sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1" - url: "https://pub.dev" + sha256: ee85086ad7698b42522c6ad42fe195f1b9898e4d974a1af4576c1a3a176cada9 + url: "https://pub.flutter-io.cn" source: hosted - version: "7.3.0" + version: "7.3.1" matcher: dependency: transitive description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -450,7 +458,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -458,7 +466,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" mime: @@ -466,39 +474,39 @@ packages: description: name: mime sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" - native_toolchain_c: - dependency: transitive - description: - name: native_toolchain_c - sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac" - url: "https://pub.dev" - source: hosted - version: "0.17.4" nested: dependency: transitive description: name: nested sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" objective_c: dependency: transitive description: name: objective_c - sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" - url: "https://pub.dev" + sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" + url: "https://pub.flutter-io.cn" source: hosted - version: "9.3.0" + version: "9.4.1" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" package_info_plus: dependency: "direct main" description: name: package_info_plus sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "8.3.1" package_info_plus_platform_interface: @@ -506,7 +514,7 @@ packages: description: name: package_info_plus_platform_interface sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.1" path: @@ -514,31 +522,31 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" path_provider: dependency: "direct main" description: name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 + url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.5" + version: "2.1.6" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e - url: "https://pub.dev" + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.22" + version: "2.3.1" path_provider_foundation: dependency: transitive description: name: path_provider_foundation sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.6.0" path_provider_linux: @@ -546,23 +554,23 @@ packages: description: name: path_provider_linux sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.1" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" + sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_windows: dependency: transitive description: name: path_provider_windows sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.0" permission_handler: @@ -570,7 +578,7 @@ packages: description: name: permission_handler sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.4.0" permission_handler_android: @@ -578,23 +586,23 @@ packages: description: name: permission_handler_android sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "12.1.0" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 - url: "https://pub.dev" + sha256: "79dfa1df734798aa3cfdad166d3a3698c206d8813de13516ea1071b5d7e2f420" + url: "https://pub.flutter-io.cn" source: hosted - version: "9.4.7" + version: "9.4.10" permission_handler_html: dependency: transitive description: name: permission_handler_html sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.3+5" permission_handler_platform_interface: @@ -602,7 +610,7 @@ packages: description: name: permission_handler_platform_interface sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.3.0" permission_handler_windows: @@ -610,7 +618,7 @@ packages: description: name: permission_handler_windows sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.1" petitparser: @@ -618,7 +626,7 @@ packages: description: name: petitparser sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.2" platform: @@ -626,7 +634,7 @@ packages: description: name: platform sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.6" plugin_platform_interface: @@ -634,7 +642,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" provider: @@ -642,7 +650,7 @@ packages: description: name: provider sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.1.5+1" pub_semver: @@ -650,15 +658,23 @@ packages: description: name: pub_semver sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.6.0" share_plus: dependency: "direct main" description: name: share_plus sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "10.1.4" share_plus_platform_interface: @@ -666,31 +682,31 @@ packages: description: name: share_plus_platform_interface sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.0.2" shared_preferences: dependency: "direct main" description: name: shared_preferences - sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" - url: "https://pub.dev" + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.flutter-io.cn" source: hosted - version: "2.5.4" + version: "2.5.5" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "8374d6200ab33ac99031a852eba4c8eb2170c4bf20778b3e2c9eccb45384fb41" - url: "https://pub.dev" + sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 + url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.21" + version: "2.4.23" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.5.6" shared_preferences_linux: @@ -698,23 +714,23 @@ packages: description: name: shared_preferences_linux sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.1" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.dev" + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.1" + version: "2.4.2" shared_preferences_web: dependency: transitive description: name: shared_preferences_web sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.3" shared_preferences_windows: @@ -722,7 +738,7 @@ packages: description: name: shared_preferences_windows sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.1" sky_engine: @@ -735,39 +751,39 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" sqflite: dependency: "direct main" description: name: sqflite - sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 - url: "https://pub.dev" + sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.2" + version: "2.4.2+1" sqflite_android: dependency: transitive description: name: sqflite_android - sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 - url: "https://pub.dev" + sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.2+2" + version: "2.4.2+3" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" - url: "https://pub.dev" + sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.5.6" + version: "2.5.8" sqflite_darwin: dependency: transitive description: name: sqflite_darwin sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.2" sqflite_platform_interface: @@ -775,7 +791,7 @@ packages: description: name: sqflite_platform_interface sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.0" stack_trace: @@ -783,7 +799,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -791,7 +807,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -799,23 +815,23 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" synchronized: dependency: transitive description: name: synchronized - sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 - url: "https://pub.dev" + sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5" + url: "https://pub.flutter-io.cn" source: hosted - version: "3.4.0" + version: "3.4.0+1" term_glyph: dependency: transitive description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -823,7 +839,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" typed_data: @@ -831,7 +847,7 @@ packages: description: name: typed_data sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.0" url_launcher: @@ -839,23 +855,23 @@ packages: description: name: url_launcher sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.3.2" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" - url: "https://pub.dev" + sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c" + url: "https://pub.flutter-io.cn" source: hosted - version: "6.3.28" + version: "6.3.30" url_launcher_ios: dependency: transitive description: name: url_launcher_ios sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.4.1" url_launcher_linux: @@ -863,7 +879,7 @@ packages: description: name: url_launcher_linux sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.2" url_launcher_macos: @@ -871,7 +887,7 @@ packages: description: name: url_launcher_macos sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.5" url_launcher_platform_interface: @@ -879,23 +895,23 @@ packages: description: name: url_launcher_platform_interface sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f - url: "https://pub.dev" + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.2" + version: "2.4.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.5" uuid: @@ -903,7 +919,7 @@ packages: description: name: uuid sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.5.3" vector_math: @@ -911,63 +927,63 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: dependency: transitive description: name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" - url: "https://pub.dev" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.flutter-io.cn" source: hosted - version: "15.0.2" + version: "15.2.0" web: dependency: transitive description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" webview_flutter: dependency: "direct main" description: name: webview_flutter - sha256: a3da219916aba44947d3a5478b1927876a09781174b5a2b67fa5be0555154bf9 - url: "https://pub.dev" + sha256: e4d3474277c5043f5f3100ed27d94ad693abfd5c602b0221c719a4497e4ba1e4 + url: "https://pub.flutter-io.cn" source: hosted - version: "4.13.1" + version: "4.14.0" webview_flutter_android: dependency: transitive description: name: webview_flutter_android - sha256: "2a03df01df2fd30b075d1e7f24c28aee593f2e5d5ac4c3c4283c5eda63717b24" - url: "https://pub.dev" + sha256: ad5182eff9a550925330cb9f0cb038eddfdd5712aba8b77aa0f0400e50f6e688 + url: "https://pub.flutter-io.cn" source: hosted - version: "4.10.13" + version: "4.12.0" webview_flutter_platform_interface: dependency: transitive description: name: webview_flutter_platform_interface - sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0" - url: "https://pub.dev" + sha256: "1221c1b12f5278791042f2ec2841743784cf25c5a644e23d6680e5d718824f04" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.14.0" + version: "2.15.1" webview_flutter_wkwebview: dependency: transitive description: name: webview_flutter_wkwebview - sha256: fc0af89d403e1c053f03d023d97550412fa79f35332e2939514c82e6fe633198 - url: "https://pub.dev" + sha256: "82648217f537573e1ca9ae9952d3eacedca6ab5aee69dc84445fc763766dcea2" + url: "https://pub.flutter-io.cn" source: hosted - version: "3.23.8" + version: "3.25.1" win32: dependency: transitive description: name: win32 sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.15.0" xdg_directories: @@ -975,7 +991,7 @@ packages: description: name: xdg_directories sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" xml: @@ -983,7 +999,7 @@ packages: description: name: xml sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.6.1" yaml: @@ -991,9 +1007,9 @@ packages: description: name: yaml sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.3" sdks: - dart: ">=3.10.3 <4.0.0" + dart: ">=3.11.0 <4.0.0" flutter: ">=3.38.4"