From 4de8bc9811bc26f48f1666af6149454ec06afd00 Mon Sep 17 00:00:00 2001 From: warren Date: Mon, 25 May 2026 14:21:40 +0800 Subject: [PATCH 01/16] =?UTF-8?q?=E5=B0=8F=E4=BC=98=E5=8C=96=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=AF=BC=E5=85=A5=E6=97=B6=E5=80=99=E7=9A=84=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../parse/service/parse_archive_service.dart | 59 +++++++++++++++++-- .../parse/service/parse_pdf_service.dart | 39 ++++++++++-- .../ui/view/parse_batch_archive_view.dart | 4 ++ .../view/parse_batch_image_folder_view.dart | 4 ++ .../parse/ui/view/parse_batch_pdf_view.dart | 2 + .../parse_batch_archive_viewmodel.dart | 31 ++++++++++ .../parse_batch_image_folder_viewmodel.dart | 31 ++++++++++ .../viewmodel/parse_batch_pdf_viewmodel.dart | 31 ++++++++++ version.properties | 2 +- 9 files changed, 194 insertions(+), 9 deletions(-) diff --git a/lib/feature/parse/service/parse_archive_service.dart b/lib/feature/parse/service/parse_archive_service.dart index 0f2ff37..ad8395b 100644 --- a/lib/feature/parse/service/parse_archive_service.dart +++ b/lib/feature/parse/service/parse_archive_service.dart @@ -93,6 +93,10 @@ class ParseArchiveService { List archivePaths, Function(int total) onStart, Function(int count) onProgress, + { + void Function(String currentName)? onCurrentItemChanged, + void Function(int current, int total)? onCurrentItemProgress, + } ) async { try { final filteredPaths = archivePaths @@ -105,11 +109,18 @@ class ParseArchiveService { final results = []; for (var index = 0; index < filteredPaths.length; index++) { final path = filteredPaths[index]; + onCurrentItemChanged?.call(_baseName(path)); + onCurrentItemProgress?.call(0, 2); // 每处理一个文件,让出事件循环,让 UI / GC 有机会运行 await Future.delayed(Duration.zero); - final parseResult = await parseArchive(path); + final parseResult = await parseArchive( + path, + onProgress: (current, total) { + onCurrentItemProgress?.call(current, total); + }, + ); if (parseResult.isSuccess) { results.add( ParseBatchArchiveVo( @@ -148,15 +159,21 @@ class ParseArchiveService { } // ── 单压缩包解析 ────────────────────────────────────── - Future>> parseArchive(String archivePath) async { + Future>> parseArchive( + String archivePath, { + void Function(int current, int total)? onProgress, + }) async { try { final tempOutputDir = "${GlobalConfig.appTempDir.path}/${const Uuid().v4()}"; + onProgress?.call(0, 2); // ① 解压放后台 Isolate(最重,可能 OOM,独立内存空间更安全) await compute(_extractInBackground, [archivePath, tempOutputDir]); + onProgress?.call(1, 2); // ② 扫描解压后的目录,也放后台 Isolate final imagePaths = await compute(_collectImagePathsSync, tempOutputDir); + onProgress?.call(2, 2); return Result.success(imagePaths); } catch (e, st) { @@ -171,6 +188,10 @@ class ParseArchiveService { String archiveDirPath, Function(int total) onStart, Function(int count) onProgress, + { + void Function(String currentName)? onCurrentItemChanged, + void Function(int current, int total)? onCurrentItemProgress, + } ) async { try { final archiveDir = Directory(archiveDirPath); @@ -185,7 +206,13 @@ class ParseArchiveService { .map((e) => e.path) .toList(); - return _parseBatchArchivePaths(archivePaths, onStart, onProgress); + return _parseBatchArchivePaths( + archivePaths, + onStart, + onProgress, + onCurrentItemChanged: onCurrentItemChanged, + onCurrentItemProgress: onCurrentItemProgress, + ); } catch (e, st) { return Result.failure( BusinessFailure(message: "批量解析压缩包失败", details: e, stackTrace: st), @@ -197,8 +224,18 @@ class ParseArchiveService { List archivePaths, Function(int total) onStart, Function(int count) onProgress, + { + void Function(String currentName)? onCurrentItemChanged, + void Function(int current, int total)? onCurrentItemProgress, + } ) { - return _parseBatchArchivePaths(archivePaths, onStart, onProgress); + return _parseBatchArchivePaths( + archivePaths, + onStart, + onProgress, + onCurrentItemChanged: onCurrentItemChanged, + onCurrentItemProgress: onCurrentItemProgress, + ); } // ── 批量文件夹解析 ──────────────────────────────────── @@ -206,6 +243,10 @@ class ParseArchiveService { String parentDirPath, Function(int total) onStart, Function(int count) onProgress, + { + void Function(String currentName)? onCurrentItemChanged, + void Function(int current, int total)? onCurrentItemProgress, + } ) async { try { final parentDir = Directory(parentDirPath); @@ -221,6 +262,8 @@ class ParseArchiveService { final results = []; for (var index = 0; index < folders.length; index++) { final folderPath = folders[index]; + onCurrentItemChanged?.call(_baseName(folderPath)); + onCurrentItemProgress?.call(0, 1); // 让出事件循环 await Future.delayed(Duration.zero); @@ -235,6 +278,7 @@ class ParseArchiveService { ), ); } + onCurrentItemProgress?.call(1, 1); onProgress(index + 1); } return Result.success(results); @@ -249,6 +293,10 @@ class ParseArchiveService { List imagePaths, Function(int total) onStart, Function(int count) onProgress, + { + void Function(String currentName)? onCurrentItemChanged, + void Function(int current, int total)? onCurrentItemProgress, + } ) async { try { final grouped = >{}; @@ -264,6 +312,8 @@ class ParseArchiveService { final results = []; for (var index = 0; index < keys.length; index++) { final key = keys[index]; + onCurrentItemChanged?.call(key.isEmpty ? '未命名文件夹' : _baseName(key)); + onCurrentItemProgress?.call(0, 1); final paths = grouped[key]!..sort(); if (paths.length > 1) { results.add( @@ -273,6 +323,7 @@ class ParseArchiveService { ), ); } + onCurrentItemProgress?.call(1, 1); onProgress(index + 1); await Future.delayed(Duration.zero); } diff --git a/lib/feature/parse/service/parse_pdf_service.dart b/lib/feature/parse/service/parse_pdf_service.dart index 6196ee7..ae5a9e5 100644 --- a/lib/feature/parse/service/parse_pdf_service.dart +++ b/lib/feature/parse/service/parse_pdf_service.dart @@ -107,6 +107,10 @@ class ParsePdfService { String pdfDirPath, void Function(int total) onStart, void Function(int count) onProgress, + { + void Function(String fileName)? onCurrentFileChanged, + void Function(int current, int total)? onCurrentFileProgress, + } ) async { try { final dir = Directory(pdfDirPath); @@ -121,7 +125,13 @@ class ParsePdfService { .toList() ..sort(); - return _parseBatchPdfsByPaths(pdfPaths, onStart, onProgress); + return _parseBatchPdfsByPaths( + pdfPaths, + onStart, + onProgress, + onCurrentFileChanged: onCurrentFileChanged, + onCurrentFileProgress: onCurrentFileProgress, + ); } catch (e, st) { return Result.failure( BusinessFailure( @@ -137,17 +147,31 @@ class ParsePdfService { List pdfPaths, void Function(int total) onStart, void Function(int count) onProgress, + { + void Function(String fileName)? onCurrentFileChanged, + void Function(int current, int total)? onCurrentFileProgress, + } ) { final filtered = pdfPaths .where((path) => path.toLowerCase().endsWith('.pdf')) .toList(); - return _parseBatchPdfsByPaths(filtered, onStart, onProgress); + return _parseBatchPdfsByPaths( + filtered, + onStart, + onProgress, + onCurrentFileChanged: onCurrentFileChanged, + onCurrentFileProgress: onCurrentFileProgress, + ); } Future>> _parseBatchPdfsByPaths( List pdfPaths, void Function(int total) onStart, void Function(int count) onProgress, + { + void Function(String fileName)? onCurrentFileChanged, + void Function(int current, int total)? onCurrentFileProgress, + } ) async { try { pdfPaths.sort(); @@ -158,8 +182,15 @@ class ParsePdfService { for (var i = 0; i < pdfPaths.length; i++) { await Future.delayed(Duration.zero); final path = pdfPaths[i]; - - final result = await parsePdf(path); + onCurrentFileChanged?.call(p.basename(path)); + onCurrentFileProgress?.call(0, 0); + + final result = await parsePdf( + path, + onProgress: (current, total) { + onCurrentFileProgress?.call(current, total); + }, + ); if (result.isSuccess) { results.add( ParseBatchArchiveVo( diff --git a/lib/feature/parse/ui/view/parse_batch_archive_view.dart b/lib/feature/parse/ui/view/parse_batch_archive_view.dart index dc84457..abff88f 100644 --- a/lib/feature/parse/ui/view/parse_batch_archive_view.dart +++ b/lib/feature/parse/ui/view/parse_batch_archive_view.dart @@ -51,6 +51,10 @@ class _ParseBatchArchiveContentView extends StatelessWidget { CircularProgressIndicator(), SizedBox(height: 8), Text("正在处理:${viewmodel.completeCount}/${viewmodel.totalCount}"), + if (viewmodel.currentFileName.isNotEmpty) + Text("当前文件:${viewmodel.currentFileName}"), + if (viewmodel.currentFileProgressText.isNotEmpty) + Text(viewmodel.currentFileProgressText), ], ), ); diff --git a/lib/feature/parse/ui/view/parse_batch_image_folder_view.dart b/lib/feature/parse/ui/view/parse_batch_image_folder_view.dart index 99eaac9..473fe8a 100644 --- a/lib/feature/parse/ui/view/parse_batch_image_folder_view.dart +++ b/lib/feature/parse/ui/view/parse_batch_image_folder_view.dart @@ -53,6 +53,10 @@ class _ParseBatchImageFolderContentView extends StatelessWidget { const CircularProgressIndicator(), const SizedBox(height: 8), Text("正在处理:${viewmodel.completeCount}/${viewmodel.totalCount}"), + if (viewmodel.currentFileName.isNotEmpty) + Text("当前文件:${viewmodel.currentFileName}"), + if (viewmodel.currentFileProgressText.isNotEmpty) + Text(viewmodel.currentFileProgressText), ], ), ); diff --git a/lib/feature/parse/ui/view/parse_batch_pdf_view.dart b/lib/feature/parse/ui/view/parse_batch_pdf_view.dart index b67a441..e9c8999 100644 --- a/lib/feature/parse/ui/view/parse_batch_pdf_view.dart +++ b/lib/feature/parse/ui/view/parse_batch_pdf_view.dart @@ -52,6 +52,8 @@ class _ParseBatchPdfContent extends StatelessWidget { const CircularProgressIndicator(), const SizedBox(height: 8), Text('正在处理:${vm.completeCount} / ${vm.totalCount}'), + if (vm.currentFileName.isNotEmpty) Text('当前文件:${vm.currentFileName}'), + if (vm.currentFileProgressText.isNotEmpty) Text(vm.currentFileProgressText), ], ), ); diff --git a/lib/feature/parse/ui/viewmodel/parse_batch_archive_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_batch_archive_viewmodel.dart index 5125548..a497f32 100644 --- a/lib/feature/parse/ui/viewmodel/parse_batch_archive_viewmodel.dart +++ b/lib/feature/parse/ui/viewmodel/parse_batch_archive_viewmodel.dart @@ -18,10 +18,19 @@ class ParseBatchArchiveViewmodel extends ChangeNotifier { List parseBatchArchiveList = []; int completeCount = 0; int totalCount = 0; + String currentFileName = ''; + int currentFileProgress = 0; + int currentFileTotal = 0; int saveAsBookCount = 0; EventState parseBatchArchiveState = IdleEventState(); EventState saveBatchAsBookState = IdleEventState(); + String get currentFileProgressText { + if (currentFileName.isEmpty) return ''; + if (currentFileTotal <= 0) return '当前文件进度:准备中'; + return '当前文件进度:$currentFileProgress / $currentFileTotal'; + } + ParseBatchArchiveViewmodel({ this.archiveDirPath, this.archivePaths, @@ -71,6 +80,17 @@ class ParseBatchArchiveViewmodel extends ChangeNotifier { completeCount = count; notifyListeners(); }, + onCurrentItemChanged: (fileName) { + currentFileName = fileName; + currentFileProgress = 0; + currentFileTotal = 0; + notifyListeners(); + }, + onCurrentItemProgress: (current, total) { + currentFileProgress = current; + currentFileTotal = total; + notifyListeners(); + }, ) : await parseArchiveService.parseBatchArchives( archiveDirPath ?? '', @@ -82,6 +102,17 @@ class ParseBatchArchiveViewmodel extends ChangeNotifier { completeCount = count; notifyListeners(); }, + onCurrentItemChanged: (fileName) { + currentFileName = fileName; + currentFileProgress = 0; + currentFileTotal = 0; + notifyListeners(); + }, + onCurrentItemProgress: (current, total) { + currentFileProgress = current; + currentFileTotal = total; + notifyListeners(); + }, ); result.fold( diff --git a/lib/feature/parse/ui/viewmodel/parse_batch_image_folder_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_batch_image_folder_viewmodel.dart index 0282ab0..a8b1f70 100644 --- a/lib/feature/parse/ui/viewmodel/parse_batch_image_folder_viewmodel.dart +++ b/lib/feature/parse/ui/viewmodel/parse_batch_image_folder_viewmodel.dart @@ -19,10 +19,19 @@ class ParseBatchImageFolderViewmodel extends ChangeNotifier { List parseBatchFolderList = []; int completeCount = 0; int totalCount = 0; + String currentFileName = ''; + int currentFileProgress = 0; + int currentFileTotal = 0; int saveAsBookCount = 0; EventState parseBatchFolderState = IdleEventState(); EventState saveBatchAsBookState = IdleEventState(); + String get currentFileProgressText { + if (currentFileName.isEmpty) return ''; + if (currentFileTotal <= 0) return '当前文件进度:准备中'; + return '当前文件进度:$currentFileProgress / $currentFileTotal'; + } + ParseBatchImageFolderViewmodel({ this.parentDirPath, this.imagePaths, @@ -67,6 +76,17 @@ class ParseBatchImageFolderViewmodel extends ChangeNotifier { completeCount = count; notifyListeners(); }, + onCurrentItemChanged: (fileName) { + currentFileName = fileName; + currentFileProgress = 0; + currentFileTotal = 0; + notifyListeners(); + }, + onCurrentItemProgress: (current, total) { + currentFileProgress = current; + currentFileTotal = total; + notifyListeners(); + }, ) : await parseArchiveService.parseBatchImageFolders( parentDirPath ?? '', @@ -78,6 +98,17 @@ class ParseBatchImageFolderViewmodel extends ChangeNotifier { completeCount = count; notifyListeners(); }, + onCurrentItemChanged: (fileName) { + currentFileName = fileName; + currentFileProgress = 0; + currentFileTotal = 0; + notifyListeners(); + }, + onCurrentItemProgress: (current, total) { + currentFileProgress = current; + currentFileTotal = total; + notifyListeners(); + }, ); result.fold( diff --git a/lib/feature/parse/ui/viewmodel/parse_batch_pdf_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_batch_pdf_viewmodel.dart index 44f6628..011eeae 100644 --- a/lib/feature/parse/ui/viewmodel/parse_batch_pdf_viewmodel.dart +++ b/lib/feature/parse/ui/viewmodel/parse_batch_pdf_viewmodel.dart @@ -19,11 +19,20 @@ class ParseBatchPdfViewmodel extends ChangeNotifier { List parseBatchList = []; int completeCount = 0; int totalCount = 0; + String currentFileName = ''; + int currentFileProgress = 0; + int currentFileTotal = 0; int saveAsBookCount = 0; EventState parseBatchState = const IdleEventState(); EventState saveBatchAsBookState = const IdleEventState(); + String get currentFileProgressText { + if (currentFileName.isEmpty) return ''; + if (currentFileTotal <= 0) return '当前文件进度:准备中'; + return '当前文件进度:$currentFileProgress / $currentFileTotal'; + } + ParseBatchPdfViewmodel({ this.pdfDirPath, this.pdfPaths, @@ -67,6 +76,17 @@ class ParseBatchPdfViewmodel extends ChangeNotifier { completeCount = count; notifyListeners(); }, + onCurrentFileChanged: (fileName) { + currentFileName = fileName; + currentFileProgress = 0; + currentFileTotal = 0; + notifyListeners(); + }, + onCurrentFileProgress: (current, total) { + currentFileProgress = current; + currentFileTotal = total; + notifyListeners(); + }, ) : await parsePdfService.parseBatchPdfs( pdfDirPath ?? '', @@ -78,6 +98,17 @@ class ParseBatchPdfViewmodel extends ChangeNotifier { completeCount = count; notifyListeners(); }, + onCurrentFileChanged: (fileName) { + currentFileName = fileName; + currentFileProgress = 0; + currentFileTotal = 0; + notifyListeners(); + }, + onCurrentFileProgress: (current, total) { + currentFileProgress = current; + currentFileTotal = total; + notifyListeners(); + }, ); result.fold( diff --git a/version.properties b/version.properties index 5cac1a8..1c9772c 100644 --- a/version.properties +++ b/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.1.4 -VERSION_CODE=3 +VERSION_CODE=4 From 1e321a45187d31c6b9ce0ec6288fb725b7c0aca6 Mon Sep 17 00:00:00 2001 From: gdfenhui Date: Mon, 25 May 2026 14:53:59 +0800 Subject: [PATCH 02/16] =?UTF-8?q?1.=20=E4=BC=98=E5=8C=96=E4=BA=86=E6=A1=8C?= =?UTF-8?q?=E9=9D=A2=E7=AB=AF=E4=B9=A6=E7=B1=8D=E7=BD=91=E6=A0=BC=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E7=9A=84=E6=98=BE=E7=A4=BA=E3=80=82=202.=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=BA=86mac=E5=B9=B3=E5=8F=B0=E4=B8=8B=E7=9A=84?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E5=90=8D=E7=A7=B0=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/feature/book/ui/view/book_view.dart | 25 +++++++++++++++++++------ macos/Runner/Configs/AppInfo.xcconfig | 4 ++-- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/lib/feature/book/ui/view/book_view.dart b/lib/feature/book/ui/view/book_view.dart index a29def9..b180de9 100644 --- a/lib/feature/book/ui/view/book_view.dart +++ b/lib/feature/book/ui/view/book_view.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; +import 'package:responsive_framework/responsive_framework.dart'; import 'package:tele_book/common/widget/local_image_widget.dart'; import 'package:tele_book/core/db/app_database.dart'; import 'package:tele_book/core/route/app_route.dart'; @@ -281,15 +282,27 @@ class _BookGridView extends StatelessWidget { Widget build(BuildContext context) { final bookStore = context.watch(); + final isTablet = ResponsiveBreakpoints.of(context).isTablet; + + final gridDelegate = isTablet + ? const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 180, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + childAspectRatio: 0.65, + ) + : const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + childAspectRatio: 0.65, + ); + + return GridView.builder( controller: vm.scrollController, padding: const EdgeInsets.all(12), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 12, - mainAxisSpacing: 12, - childAspectRatio: 0.65, - ), + gridDelegate: gridDelegate, itemCount: bookStore.books.length + (bookStore.isLoading ? 1 : 0), itemBuilder: (context, index) { if (index == bookStore.books.length) { diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig index 7579872..5e3a4f5 100644 --- a/macos/Runner/Configs/AppInfo.xcconfig +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = wo_nas +PRODUCT_NAME = TeleBook // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.dorkytiger.woNas +PRODUCT_BUNDLE_IDENTIFIER = com.dorkytiger.telebook // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2023 com.dorkytiger. All rights reserved. From 1c34b41b0366136084b257957a854f6f1cc6c9a2 Mon Sep 17 00:00:00 2001 From: warren Date: Mon, 25 May 2026 17:56:14 +0800 Subject: [PATCH 03/16] =?UTF-8?q?=E4=BD=BF=E7=94=A8riverpod=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- analysis_options.yaml | 4 + lib/common/widget/empty_widget.dart | 29 + lib/common/widget/error_widget.dart | 54 + lib/core/db/app_database.dart | 23 +- lib/core/db/app_database.g.dart | 962 +++++++++++++++++- lib/core/route/app_route.dart | 17 +- lib/feature/book/ui/view/book_view.dart | 73 +- .../ui/viewmodel/book_form_viewmodel.dart | 2 +- .../book/ui/viewmodel/book_viewmodel.dart | 58 +- .../collection_book_local_datasource.dart | 29 + .../collection_book_local_datasource.g.dart | 21 + .../local/collection_local_datasource.dart | 27 + .../local/collection_local_datasource.g.dart | 20 + .../model/table/collection_book_table.dart | 9 + .../model/table/collection_table.dart | 11 + .../model/vo/collection_list_item_vo.dart | 11 + .../repository/collection_repository.dart | 32 + .../ui/provider/collection_provider.dart | 81 ++ .../ui/provider/collection_provider.g.dart | 103 ++ .../collection/ui/view/collection_view.dart | 152 +++ .../download/ui/view/download_list_view.dart | 2 +- lib/feature/main/view/main_view.dart | 40 + .../main/viewmodel/main_viewmodel.dart | 12 + .../ui/viewmodel/parse_archive_viewmodel.dart | 2 +- .../parse_batch_archive_viewmodel.dart | 2 +- .../parse_batch_image_folder_viewmodel.dart | 2 +- .../viewmodel/parse_batch_pdf_viewmodel.dart | 2 +- .../parse_image_folder_viewmodel.dart | 2 +- .../ui/viewmodel/parse_pdf_viewmodel.dart | 2 +- .../ui/viewmodel/parse_web_viewmodel.dart | 2 +- lib/main.dart | 37 +- pubspec.lock | 210 +++- pubspec.yaml | 10 +- 33 files changed, 1958 insertions(+), 85 deletions(-) create mode 100644 lib/common/widget/empty_widget.dart create mode 100644 lib/common/widget/error_widget.dart create mode 100644 lib/feature/collection/datasource/local/collection_book_local_datasource.dart create mode 100644 lib/feature/collection/datasource/local/collection_book_local_datasource.g.dart create mode 100644 lib/feature/collection/datasource/local/collection_local_datasource.dart create mode 100644 lib/feature/collection/datasource/local/collection_local_datasource.g.dart create mode 100644 lib/feature/collection/model/table/collection_book_table.dart create mode 100644 lib/feature/collection/model/table/collection_table.dart create mode 100644 lib/feature/collection/model/vo/collection_list_item_vo.dart create mode 100644 lib/feature/collection/repository/collection_repository.dart create mode 100644 lib/feature/collection/ui/provider/collection_provider.dart create mode 100644 lib/feature/collection/ui/provider/collection_provider.g.dart create mode 100644 lib/feature/collection/ui/view/collection_view.dart create mode 100644 lib/feature/main/view/main_view.dart create mode 100644 lib/feature/main/viewmodel/main_viewmodel.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index 9403522..9a2e3cc 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -26,3 +26,7 @@ linter: # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options + +analyzer: + plugins: + - custom_lint \ No newline at end of file diff --git a/lib/common/widget/empty_widget.dart b/lib/common/widget/empty_widget.dart new file mode 100644 index 0000000..28a393b --- /dev/null +++ b/lib/common/widget/empty_widget.dart @@ -0,0 +1,29 @@ +import 'package:flutter/cupertino.dart'; + +class CustomEmptyWidget extends StatelessWidget { + final IconData? icon; + final String? text; + + const CustomEmptyWidget({super.key, this.icon, this.text}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 40), + Icon( + icon ?? CupertinoIcons.infinite, + size: 60, + color: CupertinoColors.systemGrey, + ), + SizedBox(height: 20), + Text( + text ?? "暂无数据", + style: TextStyle(fontSize: 16, color: CupertinoColors.systemGrey), + ), + ], + ); + } +} diff --git a/lib/common/widget/error_widget.dart b/lib/common/widget/error_widget.dart new file mode 100644 index 0000000..86abb37 --- /dev/null +++ b/lib/common/widget/error_widget.dart @@ -0,0 +1,54 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class CustomErrorWidget extends StatelessWidget { + final String? errorMessage; + final StackTrace? stackTrace; + final VoidCallback? onRetry; + + const CustomErrorWidget({ + Key? key, + this.errorMessage, + this.stackTrace, + this.onRetry, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('发生错误: $errorMessage'), + SizedBox(height: 8), + if (stackTrace != null) + OutlinedButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('错误详情'), + content: SingleChildScrollView( + child: Text(stackTrace.toString()), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('关闭'), + ), + ], + ), + ); + }, + child: Text("查看详情"), + ), + SizedBox(height: 8), + if (onRetry != null) + ElevatedButton(onPressed: onRetry, child: Text('重试')), + ], + ), + ); + } +} diff --git a/lib/core/db/app_database.dart b/lib/core/db/app_database.dart index 857c0a2..e4ed293 100644 --- a/lib/core/db/app_database.dart +++ b/lib/core/db/app_database.dart @@ -1,16 +1,25 @@ import 'package:drift/drift.dart'; import 'package:drift_flutter/drift_flutter.dart'; -import 'package:path/path.dart' as p; -import 'package:path_provider/path_provider.dart'; - +import 'package:riverpod/riverpod.dart'; import 'package:tele_book/feature/book/datasource/local/book_local_datasource.dart'; import 'package:tele_book/feature/book/model/table/book_table.dart'; +import 'package:tele_book/feature/collection/datasource/local/collection_book_local_datasource.dart'; +import 'package:tele_book/feature/collection/datasource/local/collection_local_datasource.dart'; +import 'package:tele_book/feature/collection/model/table/collection_book_table.dart'; +import 'package:tele_book/feature/collection/model/table/collection_table.dart'; import 'converter/string_list_converter.dart'; part 'app_database.g.dart'; -@DriftDatabase(tables: [BookTable], daos: [BookLocalDatasource]) +@DriftDatabase( + tables: [BookTable, CollectionTable, CollectionBookTable], + daos: [ + BookLocalDatasource, + CollectionLocalDatasource, + CollectionBookLocalDatasource, + ], +) class AppDatabase extends _$AppDatabase { // Allow injecting a QueryExecutor for tests. If null, use the default on-disk executor. AppDatabase([QueryExecutor? executor]) @@ -23,3 +32,9 @@ class AppDatabase extends _$AppDatabase { return driftDatabase(name: 'tele_book'); } } + +final databaseProvider = Provider((ref) { + final db = AppDatabase(); + ref.onDispose(() => db.close()); + return db; +}); diff --git a/lib/core/db/app_database.g.dart b/lib/core/db/app_database.g.dart index f08d276..f09640c 100644 --- a/lib/core/db/app_database.g.dart +++ b/lib/core/db/app_database.g.dart @@ -403,18 +403,611 @@ class BookTableCompanion extends UpdateCompanion { } } +class $CollectionTableTable extends CollectionTable + with TableInfo<$CollectionTableTable, CollectionTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $CollectionTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _descriptionMeta = const VerificationMeta( + 'description', + ); + @override + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _coverImageSubPathMeta = const VerificationMeta( + 'coverImageSubPath', + ); + @override + late final GeneratedColumn coverImageSubPath = + GeneratedColumn( + 'cover_image_sub_path', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + description, + coverImageSubPath, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'collection_table'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('description')) { + context.handle( + _descriptionMeta, + description.isAcceptableOrUnknown( + data['description']!, + _descriptionMeta, + ), + ); + } + if (data.containsKey('cover_image_sub_path')) { + context.handle( + _coverImageSubPathMeta, + coverImageSubPath.isAcceptableOrUnknown( + data['cover_image_sub_path']!, + _coverImageSubPathMeta, + ), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + CollectionTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return CollectionTableData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + coverImageSubPath: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}cover_image_sub_path'], + ), + ); + } + + @override + $CollectionTableTable createAlias(String alias) { + return $CollectionTableTable(attachedDatabase, alias); + } +} + +class CollectionTableData extends DataClass + implements Insertable { + final int id; + final String name; + final String? description; + final String? coverImageSubPath; + const CollectionTableData({ + required this.id, + required this.name, + this.description, + this.coverImageSubPath, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || coverImageSubPath != null) { + map['cover_image_sub_path'] = Variable(coverImageSubPath); + } + return map; + } + + CollectionTableCompanion toCompanion(bool nullToAbsent) { + return CollectionTableCompanion( + id: Value(id), + name: Value(name), + description: description == null && nullToAbsent + ? const Value.absent() + : Value(description), + coverImageSubPath: coverImageSubPath == null && nullToAbsent + ? const Value.absent() + : Value(coverImageSubPath), + ); + } + + factory CollectionTableData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return CollectionTableData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + coverImageSubPath: serializer.fromJson( + json['coverImageSubPath'], + ), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'coverImageSubPath': serializer.toJson(coverImageSubPath), + }; + } + + CollectionTableData copyWith({ + int? id, + String? name, + Value description = const Value.absent(), + Value coverImageSubPath = const Value.absent(), + }) => CollectionTableData( + id: id ?? this.id, + name: name ?? this.name, + description: description.present ? description.value : this.description, + coverImageSubPath: coverImageSubPath.present + ? coverImageSubPath.value + : this.coverImageSubPath, + ); + CollectionTableData copyWithCompanion(CollectionTableCompanion data) { + return CollectionTableData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + coverImageSubPath: data.coverImageSubPath.present + ? data.coverImageSubPath.value + : this.coverImageSubPath, + ); + } + + @override + String toString() { + return (StringBuffer('CollectionTableData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('coverImageSubPath: $coverImageSubPath') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, description, coverImageSubPath); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is CollectionTableData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.coverImageSubPath == this.coverImageSubPath); +} + +class CollectionTableCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value coverImageSubPath; + const CollectionTableCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.coverImageSubPath = const Value.absent(), + }); + CollectionTableCompanion.insert({ + this.id = const Value.absent(), + required String name, + this.description = const Value.absent(), + this.coverImageSubPath = const Value.absent(), + }) : name = Value(name); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? coverImageSubPath, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (coverImageSubPath != null) 'cover_image_sub_path': coverImageSubPath, + }); + } + + CollectionTableCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? coverImageSubPath, + }) { + return CollectionTableCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + coverImageSubPath: coverImageSubPath ?? this.coverImageSubPath, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (coverImageSubPath.present) { + map['cover_image_sub_path'] = Variable(coverImageSubPath.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CollectionTableCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('coverImageSubPath: $coverImageSubPath') + ..write(')')) + .toString(); + } +} + +class $CollectionBookTableTable extends CollectionBookTable + with TableInfo<$CollectionBookTableTable, CollectionBookTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $CollectionBookTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const VerificationMeta _collectionIdMeta = const VerificationMeta( + 'collectionId', + ); + @override + late final GeneratedColumn collectionId = GeneratedColumn( + 'collection_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + static const VerificationMeta _bookIdMeta = const VerificationMeta('bookId'); + @override + late final GeneratedColumn bookId = GeneratedColumn( + 'book_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [id, collectionId, bookId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'collection_book_table'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('collection_id')) { + context.handle( + _collectionIdMeta, + collectionId.isAcceptableOrUnknown( + data['collection_id']!, + _collectionIdMeta, + ), + ); + } else if (isInserting) { + context.missing(_collectionIdMeta); + } + if (data.containsKey('book_id')) { + context.handle( + _bookIdMeta, + bookId.isAcceptableOrUnknown(data['book_id']!, _bookIdMeta), + ); + } else if (isInserting) { + context.missing(_bookIdMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + CollectionBookTableData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return CollectionBookTableData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + collectionId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}collection_id'], + )!, + bookId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}book_id'], + )!, + ); + } + + @override + $CollectionBookTableTable createAlias(String alias) { + return $CollectionBookTableTable(attachedDatabase, alias); + } +} + +class CollectionBookTableData extends DataClass + implements Insertable { + final int id; + final int collectionId; + final int bookId; + const CollectionBookTableData({ + required this.id, + required this.collectionId, + required this.bookId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['collection_id'] = Variable(collectionId); + map['book_id'] = Variable(bookId); + return map; + } + + CollectionBookTableCompanion toCompanion(bool nullToAbsent) { + return CollectionBookTableCompanion( + id: Value(id), + collectionId: Value(collectionId), + bookId: Value(bookId), + ); + } + + factory CollectionBookTableData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return CollectionBookTableData( + id: serializer.fromJson(json['id']), + collectionId: serializer.fromJson(json['collectionId']), + bookId: serializer.fromJson(json['bookId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'collectionId': serializer.toJson(collectionId), + 'bookId': serializer.toJson(bookId), + }; + } + + CollectionBookTableData copyWith({int? id, int? collectionId, int? bookId}) => + CollectionBookTableData( + id: id ?? this.id, + collectionId: collectionId ?? this.collectionId, + bookId: bookId ?? this.bookId, + ); + CollectionBookTableData copyWithCompanion(CollectionBookTableCompanion data) { + return CollectionBookTableData( + id: data.id.present ? data.id.value : this.id, + collectionId: data.collectionId.present + ? data.collectionId.value + : this.collectionId, + bookId: data.bookId.present ? data.bookId.value : this.bookId, + ); + } + + @override + String toString() { + return (StringBuffer('CollectionBookTableData(') + ..write('id: $id, ') + ..write('collectionId: $collectionId, ') + ..write('bookId: $bookId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, collectionId, bookId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is CollectionBookTableData && + other.id == this.id && + other.collectionId == this.collectionId && + other.bookId == this.bookId); +} + +class CollectionBookTableCompanion + extends UpdateCompanion { + final Value id; + final Value collectionId; + final Value bookId; + const CollectionBookTableCompanion({ + this.id = const Value.absent(), + this.collectionId = const Value.absent(), + this.bookId = const Value.absent(), + }); + CollectionBookTableCompanion.insert({ + this.id = const Value.absent(), + required int collectionId, + required int bookId, + }) : collectionId = Value(collectionId), + bookId = Value(bookId); + static Insertable custom({ + Expression? id, + Expression? collectionId, + Expression? bookId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (collectionId != null) 'collection_id': collectionId, + if (bookId != null) 'book_id': bookId, + }); + } + + CollectionBookTableCompanion copyWith({ + Value? id, + Value? collectionId, + Value? bookId, + }) { + return CollectionBookTableCompanion( + id: id ?? this.id, + collectionId: collectionId ?? this.collectionId, + bookId: bookId ?? this.bookId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (collectionId.present) { + map['collection_id'] = Variable(collectionId.value); + } + if (bookId.present) { + map['book_id'] = Variable(bookId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CollectionBookTableCompanion(') + ..write('id: $id, ') + ..write('collectionId: $collectionId, ') + ..write('bookId: $bookId') + ..write(')')) + .toString(); + } +} + abstract class _$AppDatabase extends GeneratedDatabase { _$AppDatabase(QueryExecutor e) : super(e); $AppDatabaseManager get managers => $AppDatabaseManager(this); late final $BookTableTable bookTable = $BookTableTable(this); + late final $CollectionTableTable collectionTable = $CollectionTableTable( + this, + ); + late final $CollectionBookTableTable collectionBookTable = + $CollectionBookTableTable(this); late final BookLocalDatasource bookLocalDatasource = BookLocalDatasource( this as AppDatabase, ); + late final CollectionLocalDatasource collectionLocalDatasource = + CollectionLocalDatasource(this as AppDatabase); + late final CollectionBookLocalDatasource collectionBookLocalDatasource = + CollectionBookLocalDatasource(this as AppDatabase); @override Iterable> get allTables => allSchemaEntities.whereType>(); @override - List get allSchemaEntities => [bookTable]; + List get allSchemaEntities => [ + bookTable, + collectionTable, + collectionBookTable, + ]; } typedef $$BookTableTableCreateCompanionBuilder = @@ -636,10 +1229,377 @@ typedef $$BookTableTableProcessedTableManager = BookTableData, PrefetchHooks Function() >; +typedef $$CollectionTableTableCreateCompanionBuilder = + CollectionTableCompanion Function({ + Value id, + required String name, + Value description, + Value coverImageSubPath, + }); +typedef $$CollectionTableTableUpdateCompanionBuilder = + CollectionTableCompanion Function({ + Value id, + Value name, + Value description, + Value coverImageSubPath, + }); + +class $$CollectionTableTableFilterComposer + extends Composer<_$AppDatabase, $CollectionTableTable> { + $$CollectionTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get description => $composableBuilder( + column: $table.description, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get coverImageSubPath => $composableBuilder( + column: $table.coverImageSubPath, + builder: (column) => ColumnFilters(column), + ); +} + +class $$CollectionTableTableOrderingComposer + extends Composer<_$AppDatabase, $CollectionTableTable> { + $$CollectionTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get description => $composableBuilder( + column: $table.description, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get coverImageSubPath => $composableBuilder( + column: $table.coverImageSubPath, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$CollectionTableTableAnnotationComposer + extends Composer<_$AppDatabase, $CollectionTableTable> { + $$CollectionTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get description => $composableBuilder( + column: $table.description, + builder: (column) => column, + ); + + GeneratedColumn get coverImageSubPath => $composableBuilder( + column: $table.coverImageSubPath, + builder: (column) => column, + ); +} + +class $$CollectionTableTableTableManager + extends + RootTableManager< + _$AppDatabase, + $CollectionTableTable, + CollectionTableData, + $$CollectionTableTableFilterComposer, + $$CollectionTableTableOrderingComposer, + $$CollectionTableTableAnnotationComposer, + $$CollectionTableTableCreateCompanionBuilder, + $$CollectionTableTableUpdateCompanionBuilder, + ( + CollectionTableData, + BaseReferences< + _$AppDatabase, + $CollectionTableTable, + CollectionTableData + >, + ), + CollectionTableData, + PrefetchHooks Function() + > { + $$CollectionTableTableTableManager( + _$AppDatabase db, + $CollectionTableTable table, + ) : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$CollectionTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$CollectionTableTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$CollectionTableTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value description = const Value.absent(), + Value coverImageSubPath = const Value.absent(), + }) => CollectionTableCompanion( + id: id, + name: name, + description: description, + coverImageSubPath: coverImageSubPath, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required String name, + Value description = const Value.absent(), + Value coverImageSubPath = const Value.absent(), + }) => CollectionTableCompanion.insert( + id: id, + name: name, + description: description, + coverImageSubPath: coverImageSubPath, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$CollectionTableTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $CollectionTableTable, + CollectionTableData, + $$CollectionTableTableFilterComposer, + $$CollectionTableTableOrderingComposer, + $$CollectionTableTableAnnotationComposer, + $$CollectionTableTableCreateCompanionBuilder, + $$CollectionTableTableUpdateCompanionBuilder, + ( + CollectionTableData, + BaseReferences< + _$AppDatabase, + $CollectionTableTable, + CollectionTableData + >, + ), + CollectionTableData, + PrefetchHooks Function() + >; +typedef $$CollectionBookTableTableCreateCompanionBuilder = + CollectionBookTableCompanion Function({ + Value id, + required int collectionId, + required int bookId, + }); +typedef $$CollectionBookTableTableUpdateCompanionBuilder = + CollectionBookTableCompanion Function({ + Value id, + Value collectionId, + Value bookId, + }); + +class $$CollectionBookTableTableFilterComposer + extends Composer<_$AppDatabase, $CollectionBookTableTable> { + $$CollectionBookTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get collectionId => $composableBuilder( + column: $table.collectionId, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get bookId => $composableBuilder( + column: $table.bookId, + builder: (column) => ColumnFilters(column), + ); +} + +class $$CollectionBookTableTableOrderingComposer + extends Composer<_$AppDatabase, $CollectionBookTableTable> { + $$CollectionBookTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get collectionId => $composableBuilder( + column: $table.collectionId, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get bookId => $composableBuilder( + column: $table.bookId, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$CollectionBookTableTableAnnotationComposer + extends Composer<_$AppDatabase, $CollectionBookTableTable> { + $$CollectionBookTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get collectionId => $composableBuilder( + column: $table.collectionId, + builder: (column) => column, + ); + + GeneratedColumn get bookId => + $composableBuilder(column: $table.bookId, builder: (column) => column); +} + +class $$CollectionBookTableTableTableManager + extends + RootTableManager< + _$AppDatabase, + $CollectionBookTableTable, + CollectionBookTableData, + $$CollectionBookTableTableFilterComposer, + $$CollectionBookTableTableOrderingComposer, + $$CollectionBookTableTableAnnotationComposer, + $$CollectionBookTableTableCreateCompanionBuilder, + $$CollectionBookTableTableUpdateCompanionBuilder, + ( + CollectionBookTableData, + BaseReferences< + _$AppDatabase, + $CollectionBookTableTable, + CollectionBookTableData + >, + ), + CollectionBookTableData, + PrefetchHooks Function() + > { + $$CollectionBookTableTableTableManager( + _$AppDatabase db, + $CollectionBookTableTable table, + ) : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$CollectionBookTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$CollectionBookTableTableOrderingComposer( + $db: db, + $table: table, + ), + createComputedFieldComposer: () => + $$CollectionBookTableTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value collectionId = const Value.absent(), + Value bookId = const Value.absent(), + }) => CollectionBookTableCompanion( + id: id, + collectionId: collectionId, + bookId: bookId, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required int collectionId, + required int bookId, + }) => CollectionBookTableCompanion.insert( + id: id, + collectionId: collectionId, + bookId: bookId, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$CollectionBookTableTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $CollectionBookTableTable, + CollectionBookTableData, + $$CollectionBookTableTableFilterComposer, + $$CollectionBookTableTableOrderingComposer, + $$CollectionBookTableTableAnnotationComposer, + $$CollectionBookTableTableCreateCompanionBuilder, + $$CollectionBookTableTableUpdateCompanionBuilder, + ( + CollectionBookTableData, + BaseReferences< + _$AppDatabase, + $CollectionBookTableTable, + CollectionBookTableData + >, + ), + CollectionBookTableData, + PrefetchHooks Function() + >; class $AppDatabaseManager { final _$AppDatabase _db; $AppDatabaseManager(this._db); $$BookTableTableTableManager get bookTable => $$BookTableTableTableManager(_db, _db.bookTable); + $$CollectionTableTableTableManager get collectionTable => + $$CollectionTableTableTableManager(_db, _db.collectionTable); + $$CollectionBookTableTableTableManager get collectionBookTable => + $$CollectionBookTableTableTableManager(_db, _db.collectionBookTable); } diff --git a/lib/core/route/app_route.dart b/lib/core/route/app_route.dart index 593dda5..77c49de 100644 --- a/lib/core/route/app_route.dart +++ b/lib/core/route/app_route.dart @@ -7,6 +7,7 @@ import 'package:tele_book/feature/book/ui/view/book_view.dart'; import 'package:tele_book/feature/download/ui/view/download_list_view.dart'; import 'package:tele_book/feature/export/ui/view/export_batch_form_view.dart'; import 'package:tele_book/feature/export/ui/view/export_single_form_view.dart'; +import 'package:tele_book/feature/main/view/main_view.dart'; import 'package:tele_book/feature/parse/ui/view/parse_archive_view.dart'; import 'package:tele_book/feature/parse/ui/view/parse_batch_archive_view.dart'; import 'package:tele_book/feature/parse/ui/view/parse_batch_image_folder_view.dart'; @@ -18,7 +19,7 @@ import 'package:tele_book/feature/parse/ui/view/parse_web_view.dart'; class AppRoute { // 主页面 - static const book = '/book'; + static const main = '/main'; // 导出 static const exportSingle = '/export/single'; @@ -43,12 +44,12 @@ class AppRoute { static const parseArchiveBatchEdit = '/parse/archive/batch/edit'; static final GoRouter router = GoRouter( - initialLocation: book, + initialLocation: main, routes: [ GoRoute( - path: book, + path: main, pageBuilder: (context, state) { - return MaterialPage(child: BookView()); + return MaterialPage(child: MainView()); }, ), GoRoute( @@ -126,7 +127,9 @@ class AppRoute { if (extra is List) { final paths = extra.whereType().toList(); if (paths.isNotEmpty) { - return MaterialPage(child: ParseImageFolderView(imagePaths: paths)); + return MaterialPage( + child: ParseImageFolderView(imagePaths: paths), + ); } } return MaterialPage(child: ErrorRoutePage(message: "缺少图片路径参数")); @@ -200,7 +203,9 @@ class AppRoute { pageBuilder: (context, state) { final path = state.extra as String?; if (path == null) { - return MaterialPage(child: ErrorRoutePage(message: "缺少 PDF 文件路径参数")); + return MaterialPage( + child: ErrorRoutePage(message: "缺少 PDF 文件路径参数"), + ); } return MaterialPage(child: ParsePdfView(pdfPath: path)); }, diff --git a/lib/feature/book/ui/view/book_view.dart b/lib/feature/book/ui/view/book_view.dart index b180de9..79b4f7c 100644 --- a/lib/feature/book/ui/view/book_view.dart +++ b/lib/feature/book/ui/view/book_view.dart @@ -77,6 +77,23 @@ class _BookViewContent extends StatelessWidget { title: const Text('书籍'), elevation: 0, actions: [ + SearchAnchor( + builder: (context, controller) { + return IconButton( + onPressed: controller.openView, + icon: const Icon(Icons.search), + ); + }, + suggestionsBuilder: (context, searchController) { + final results = bookStore.books.where( + (book) => book.book.name.toLowerCase().contains( + searchController.text.toLowerCase(), + ), + ); + return results.map((book) => _BookListTile(book: book, vm: vm)); + }, + ), + downloadStore.tasks.isNotEmpty ? IconButton( onPressed: () => context.push(AppRoute.download), @@ -106,17 +123,30 @@ class _BookViewContent extends StatelessWidget { return SafeArea( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: FilledButton.icon( - onPressed: vm.selectedBookIds.isEmpty - ? null - : () => vm.onExportSelected(context), - icon: const Icon(Icons.upload_file), - label: Text( - vm.selectedBookIds.isEmpty - ? '请选择书籍' - : '导出 ${vm.selectedBookIds.length} 本', - ), - ), + child: vm.selectedBooks.isNotEmpty + ? Row( + children: [ + IconButton( + onPressed: () { + vm.onExportSelected(context); + }, + icon: Icon( + Icons.move_to_inbox, + color: Theme.of(context).colorScheme.primary, + ), + ), + IconButton( + onPressed: () { + vm.deleteSelected(context); + }, + icon: Icon( + Icons.delete, + color: Theme.of(context).colorScheme.error, + ), + ), + ], + ) + : SizedBox.shrink(), ), ); } @@ -286,18 +316,17 @@ class _BookGridView extends StatelessWidget { final gridDelegate = isTablet ? const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 180, - crossAxisSpacing: 12, - mainAxisSpacing: 12, - childAspectRatio: 0.65, - ) + maxCrossAxisExtent: 180, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + childAspectRatio: 0.65, + ) : const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 12, - mainAxisSpacing: 12, - childAspectRatio: 0.65, - ); - + crossAxisCount: 3, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + childAspectRatio: 0.65, + ); return GridView.builder( controller: vm.scrollController, diff --git a/lib/feature/book/ui/viewmodel/book_form_viewmodel.dart b/lib/feature/book/ui/viewmodel/book_form_viewmodel.dart index 472dae3..5fb21f8 100644 --- a/lib/feature/book/ui/viewmodel/book_form_viewmodel.dart +++ b/lib/feature/book/ui/viewmodel/book_form_viewmodel.dart @@ -43,7 +43,7 @@ class BookFormViewmodel extends ChangeNotifier { localSubPaths: imagePaths.map((p) => p.subPath).toList(), ); await _bookRepository.updateBook(updatedBook); - context.go(AppRoute.book); + context.go(AppRoute.main); } } diff --git a/lib/feature/book/ui/viewmodel/book_viewmodel.dart b/lib/feature/book/ui/viewmodel/book_viewmodel.dart index 001dcf5..92030e8 100644 --- a/lib/feature/book/ui/viewmodel/book_viewmodel.dart +++ b/lib/feature/book/ui/viewmodel/book_viewmodel.dart @@ -21,8 +21,9 @@ class BookViewmodel extends ChangeNotifier { bool isSelectionMode = false; final Set selectedBookIds = {}; - List get selectedBooks => - bookStore.books.where((b) => selectedBookIds.contains(b.book.id)).toList(); + List get selectedBooks => bookStore.books + .where((b) => selectedBookIds.contains(b.book.id)) + .toList(); void enterSelectionMode(BookTableData book) { isSelectionMode = true; @@ -56,6 +57,33 @@ class BookViewmodel extends ChangeNotifier { context.push(AppRoute.exportBatch, extra: books); } + void deleteSelected(BuildContext context) { + showAdaptiveDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('删除书籍'), + content: Text('确定要删除这${selectedBooks.length}本书吗?'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('取消'), + ), + TextButton( + onPressed: () { + for (var book in selectedBooks) { + _bookRepository.deleteBook(book.book.id); + } + Navigator.of(context).pop(); + }, + child: const Text('删除'), + ), + ], + ); + }, + ); + } + // ── 滚动 ───────────────────────────────────────────────── BookViewmodel(this._bookRepository, this.bookStore) { @@ -65,7 +93,9 @@ class BookViewmodel extends ChangeNotifier { } void _onScroll() { - if (!scrollController.hasClients || bookStore.isLoading || !bookStore.hasMore) { + if (!scrollController.hasClients || + bookStore.isLoading || + !bookStore.hasMore) { return; } final pos = scrollController.position; @@ -174,26 +204,26 @@ class BookViewmodel extends ChangeNotifier { enum BookTopMenuType { add('添加', Icons.add), - desc('降序',Icons.arrow_downward), - asc('升序',Icons.arrow_upward), - name('按标题',Icons.sort_by_alpha), - lastCreatedAt('按时间',Icons.access_time), - list('列表',Icons.view_list), - grid('网格',Icons.grid_view); + desc('降序', Icons.arrow_downward), + asc('升序', Icons.arrow_upward), + name('按标题', Icons.sort_by_alpha), + lastCreatedAt('按时间', Icons.access_time), + list('列表', Icons.view_list), + grid('网格', Icons.grid_view); final String title; final IconData icon; - const BookTopMenuType(this.title,this.icon); + const BookTopMenuType(this.title, this.icon); } enum BookItemMenuType { - edit('编辑',Icons.edit), - export('导出',Icons.file_download), - delete('删除',Icons.delete); + edit('编辑', Icons.edit), + export('导出', Icons.file_download), + delete('删除', Icons.delete); final String title; final IconData icon; - const BookItemMenuType(this.title,this.icon); + const BookItemMenuType(this.title, this.icon); } diff --git a/lib/feature/collection/datasource/local/collection_book_local_datasource.dart b/lib/feature/collection/datasource/local/collection_book_local_datasource.dart new file mode 100644 index 0000000..789a5dc --- /dev/null +++ b/lib/feature/collection/datasource/local/collection_book_local_datasource.dart @@ -0,0 +1,29 @@ +import 'package:drift/drift.dart'; +import 'package:tele_book/core/db/app_database.dart'; +import 'package:tele_book/feature/collection/model/table/collection_book_table.dart'; + +part 'collection_book_local_datasource.g.dart'; + +@DriftAccessor(tables: [CollectionBookTable]) +class CollectionBookLocalDatasource extends DatabaseAccessor + with _$CollectionBookLocalDatasourceMixin { + CollectionBookLocalDatasource(super.db); + + Stream> watchAllCollectionBooks() { + return select(collectionBookTable).watch(); + } + + Future> getAllCollectionBooks() { + return select(collectionBookTable).get(); + } + + Future insertCollectionBook(CollectionBookTableCompanion entry) { + return into(collectionBookTable).insert(entry); + } + + Future deleteCollectionBook(int id) { + return (delete( + collectionBookTable, + )..where((tbl) => tbl.id.equals(id))).go(); + } +} diff --git a/lib/feature/collection/datasource/local/collection_book_local_datasource.g.dart b/lib/feature/collection/datasource/local/collection_book_local_datasource.g.dart new file mode 100644 index 0000000..86013aa --- /dev/null +++ b/lib/feature/collection/datasource/local/collection_book_local_datasource.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'collection_book_local_datasource.dart'; + +// ignore_for_file: type=lint +mixin _$CollectionBookLocalDatasourceMixin on DatabaseAccessor { + $CollectionBookTableTable get collectionBookTable => + attachedDatabase.collectionBookTable; + CollectionBookLocalDatasourceManager get managers => + CollectionBookLocalDatasourceManager(this); +} + +class CollectionBookLocalDatasourceManager { + final _$CollectionBookLocalDatasourceMixin _db; + CollectionBookLocalDatasourceManager(this._db); + $$CollectionBookTableTableTableManager get collectionBookTable => + $$CollectionBookTableTableTableManager( + _db.attachedDatabase, + _db.collectionBookTable, + ); +} diff --git a/lib/feature/collection/datasource/local/collection_local_datasource.dart b/lib/feature/collection/datasource/local/collection_local_datasource.dart new file mode 100644 index 0000000..3a42303 --- /dev/null +++ b/lib/feature/collection/datasource/local/collection_local_datasource.dart @@ -0,0 +1,27 @@ +import 'package:drift/drift.dart'; +import 'package:tele_book/core/db/app_database.dart'; +import 'package:tele_book/feature/collection/model/table/collection_table.dart'; + +part 'collection_local_datasource.g.dart'; + +@DriftAccessor(tables: [CollectionTable]) +class CollectionLocalDatasource extends DatabaseAccessor + with _$CollectionLocalDatasourceMixin { + CollectionLocalDatasource(super.db); + + Stream> watchCollections() { + return (select(collectionTable).watch()); + } + + Future> getCollectionsById(int id) { + return (select(collectionTable)..where((tbl) => tbl.id.equals(id))).get(); + } + + Future insertCollection(CollectionTableCompanion collection) { + return into(collectionTable).insert(collection); + } + + Future deleteCollectionById(int id) { + return (delete(collectionTable)..where((tbl) => tbl.id.equals(id))).go(); + } +} diff --git a/lib/feature/collection/datasource/local/collection_local_datasource.g.dart b/lib/feature/collection/datasource/local/collection_local_datasource.g.dart new file mode 100644 index 0000000..0159b20 --- /dev/null +++ b/lib/feature/collection/datasource/local/collection_local_datasource.g.dart @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'collection_local_datasource.dart'; + +// ignore_for_file: type=lint +mixin _$CollectionLocalDatasourceMixin on DatabaseAccessor { + $CollectionTableTable get collectionTable => attachedDatabase.collectionTable; + CollectionLocalDatasourceManager get managers => + CollectionLocalDatasourceManager(this); +} + +class CollectionLocalDatasourceManager { + final _$CollectionLocalDatasourceMixin _db; + CollectionLocalDatasourceManager(this._db); + $$CollectionTableTableTableManager get collectionTable => + $$CollectionTableTableTableManager( + _db.attachedDatabase, + _db.collectionTable, + ); +} diff --git a/lib/feature/collection/model/table/collection_book_table.dart b/lib/feature/collection/model/table/collection_book_table.dart new file mode 100644 index 0000000..9703658 --- /dev/null +++ b/lib/feature/collection/model/table/collection_book_table.dart @@ -0,0 +1,9 @@ +import 'package:drift/drift.dart'; + +class CollectionBookTable extends Table { + IntColumn get id => integer().autoIncrement()(); + + IntColumn get collectionId => integer()(); + + IntColumn get bookId => integer()(); +} diff --git a/lib/feature/collection/model/table/collection_table.dart b/lib/feature/collection/model/table/collection_table.dart new file mode 100644 index 0000000..faf83fc --- /dev/null +++ b/lib/feature/collection/model/table/collection_table.dart @@ -0,0 +1,11 @@ +import 'package:drift/drift.dart'; + +class CollectionTable extends Table { + IntColumn get id => integer().autoIncrement()(); + + TextColumn get name => text()(); + + TextColumn get description => text().nullable()(); + + TextColumn get coverImageSubPath => text().nullable()(); +} diff --git a/lib/feature/collection/model/vo/collection_list_item_vo.dart b/lib/feature/collection/model/vo/collection_list_item_vo.dart new file mode 100644 index 0000000..b531854 --- /dev/null +++ b/lib/feature/collection/model/vo/collection_list_item_vo.dart @@ -0,0 +1,11 @@ +import 'package:tele_book/core/db/app_database.dart'; + +class CollectionListItemVo { + final CollectionTableData collection; + final int count; + + CollectionListItemVo({ + required this.collection, + required this.count, + }); +} \ No newline at end of file diff --git a/lib/feature/collection/repository/collection_repository.dart b/lib/feature/collection/repository/collection_repository.dart new file mode 100644 index 0000000..7cb8bbb --- /dev/null +++ b/lib/feature/collection/repository/collection_repository.dart @@ -0,0 +1,32 @@ +import 'package:drift/drift.dart'; +import 'package:riverpod/riverpod.dart'; +import 'package:tele_book/core/db/app_database.dart'; + +final collectionRepositoryProvider = Provider((ref) { + final database = ref.watch(databaseProvider); + return CollectionRepository(database); +}); + +class CollectionRepository { + final AppDatabase _db; + + CollectionRepository(this._db); + + Stream> watchCollections() => + _db.collectionLocalDatasource.watchCollections(); + + Stream> watchAllCollectionBooks() => + _db.collectionBookLocalDatasource.watchAllCollectionBooks(); + + Future createCollection({ + required String name, + String? description, + }) async { + await _db.collectionLocalDatasource.insertCollection( + CollectionTableCompanion.insert( + name: name, + description: Value(description), + ), + ); + } +} diff --git a/lib/feature/collection/ui/provider/collection_provider.dart b/lib/feature/collection/ui/provider/collection_provider.dart new file mode 100644 index 0000000..fb687c7 --- /dev/null +++ b/lib/feature/collection/ui/provider/collection_provider.dart @@ -0,0 +1,81 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:tele_book/core/db/app_database.dart'; +import 'package:tele_book/feature/collection/model/vo/collection_list_item_vo.dart'; +import 'package:tele_book/feature/collection/repository/collection_repository.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'collection_provider.g.dart'; + +final collectionsProvider = + StreamProvider.autoDispose>((ref) { + final repo = ref.watch(collectionRepositoryProvider); + return repo.watchCollections(); + }); + +final collectionBooksProvider = + StreamProvider.autoDispose>((ref) { + final repo = ref.watch(collectionRepositoryProvider); + return repo.watchAllCollectionBooks(); + }); + +@riverpod +AsyncValue> collectionList(Ref ref) { + final collectionsAsync = ref.watch(collectionsProvider); + final booksAsync = ref.watch(collectionBooksProvider); + + if (collectionsAsync.hasError) { + return AsyncError( + collectionsAsync.error!, + collectionsAsync.stackTrace ?? StackTrace.current, + ); + } + if (booksAsync.hasError) { + return AsyncError( + booksAsync.error!, + booksAsync.stackTrace ?? StackTrace.current, + ); + } + if (collectionsAsync.isLoading || booksAsync.isLoading) { + return const AsyncLoading(); + } + + final collections = collectionsAsync.value ?? const []; + final books = booksAsync.value ?? const []; + + final bookCountMap = {}; + for (final cb in books) { + bookCountMap[cb.collectionId] = (bookCountMap[cb.collectionId] ?? 0) + 1; + } + + final list = collections.map((c) { + final count = bookCountMap[c.id] ?? 0; + return CollectionListItemVo(collection: c, count: count); + }).toList(); + + return AsyncData(list); +} + +@riverpod +class CollectionController extends _$CollectionController { + @override + FutureOr build() { + // 可以在这里进行一些初始化操作 + } + + Future createCollection({ + required String name, + String? description, + }) async { + final trimmedName = name.trim(); + if (trimmedName.isEmpty) { + state = AsyncError('Collection name cannot be empty', StackTrace.current); + return; + } + + state = const AsyncLoading(); + state = await AsyncValue.guard(() async { + final repo = ref.read(collectionRepositoryProvider); + await repo.createCollection(name: name, description: description); + }); + } +} diff --git a/lib/feature/collection/ui/provider/collection_provider.g.dart b/lib/feature/collection/ui/provider/collection_provider.g.dart new file mode 100644 index 0000000..4cf0847 --- /dev/null +++ b/lib/feature/collection/ui/provider/collection_provider.g.dart @@ -0,0 +1,103 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'collection_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(collectionList) +final collectionListProvider = CollectionListProvider._(); + +final class CollectionListProvider + extends + $FunctionalProvider< + AsyncValue>, + AsyncValue>, + AsyncValue> + > + with $Provider>> { + CollectionListProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'collectionListProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$collectionListHash(); + + @$internal + @override + $ProviderElement>> $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + AsyncValue> create(Ref ref) { + return collectionList(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(AsyncValue> value) { + return $ProviderOverride( + origin: this, + providerOverride: + $SyncValueProvider>>(value), + ); + } +} + +String _$collectionListHash() => r'8dee32d769cba75cfab03c365a6de455a8222e29'; + +@ProviderFor(CollectionController) +final collectionControllerProvider = CollectionControllerProvider._(); + +final class CollectionControllerProvider + extends $AsyncNotifierProvider { + CollectionControllerProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'collectionControllerProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$collectionControllerHash(); + + @$internal + @override + CollectionController create() => CollectionController(); +} + +String _$collectionControllerHash() => + r'9258092689b48ab819099d458d6e7e15f4d970fe'; + +abstract class _$CollectionController extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, void>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, void>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/lib/feature/collection/ui/view/collection_view.dart b/lib/feature/collection/ui/view/collection_view.dart new file mode 100644 index 0000000..19339ce --- /dev/null +++ b/lib/feature/collection/ui/view/collection_view.dart @@ -0,0 +1,152 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:tele_book/common/widget/empty_widget.dart'; +import 'package:tele_book/common/widget/error_widget.dart'; +import 'package:tele_book/feature/collection/ui/provider/collection_provider.dart'; + +class CollectionView extends ConsumerWidget { + const CollectionView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final listAsync = ref.watch(collectionListProvider); + final controllerState = ref.watch(collectionControllerProvider); + + ref.listen>(collectionControllerProvider, (prev, next) { + next.whenOrNull( + data: (_) { + if (prev?.isLoading == true) { + Navigator.of(context, rootNavigator: true).maybePop(); + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text("创建收藏夹成功"))); + } + }, + error: (e, _) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text("创建收藏夹失败: $e"))); + }, + ); + }); + + return Scaffold( + appBar: AppBar( + title: Text("收藏夹"), + actions: [ + IconButton( + onPressed: controllerState.isLoading + ? null + : () => _showCreateDialog(context, ref), + icon: controllerState.isLoading + ? const SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Icon(Icons.add), + ), + ], + ), + body: listAsync.when( + data: (list) { + if (list.isEmpty) { + return const Center( + child: CustomEmptyWidget( + icon: Icons.collections_bookmark_outlined, + ), + ); + } + + return ListView.builder( + itemCount: list.length, + itemBuilder: (context, index) { + final item = list[index]; + return ListTile( + title: Text(item.collection.name), + subtitle: Text(item.count.toString()), + ); + }, + ); + }, + loading: () => const CircularProgressIndicator(), + error: (e, st) { + return Center( + child: CustomErrorWidget( + stackTrace: st, + errorMessage: e.toString(), + ), + ); + }, + ), + ); + } + + Future _showCreateDialog(BuildContext context, WidgetRef ref) async { + final nameController = TextEditingController(); + final descController = TextEditingController(); + + await showDialog( + context: context, + builder: (dialogContext) { + return AlertDialog( + title: const Text("创建收藏夹"), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: nameController, + decoration: const InputDecoration( + labelText: "名称", + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.bookmark_outline), + ), + ), + SizedBox(height: 16), + TextField( + controller: descController, + decoration: const InputDecoration( + labelText: "描述", + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.description_outlined), + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(dialogContext).pop(), + child: const Text("取消"), + ), + Consumer( + builder: (context, ref, _) { + final isLoading = ref + .watch(collectionControllerProvider) + .isLoading; + return TextButton( + onPressed: isLoading + ? null + : () { + ref + .read(collectionControllerProvider.notifier) + .createCollection( + name: nameController.text, + description: descController.text, + ); + }, + child: isLoading + ? const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text("创建"), + ); + }, + ), + ], + ); + }, + ); + } +} diff --git a/lib/feature/download/ui/view/download_list_view.dart b/lib/feature/download/ui/view/download_list_view.dart index cf11d82..312136c 100644 --- a/lib/feature/download/ui/view/download_list_view.dart +++ b/lib/feature/download/ui/view/download_list_view.dart @@ -38,7 +38,7 @@ class _DownloadListContentState extends State<_DownloadListContent> { title: Text("下载任务"), leading: BackButton( onPressed: () { - context.go(AppRoute.book); + context.go(AppRoute.main); }, ), ), diff --git a/lib/feature/main/view/main_view.dart b/lib/feature/main/view/main_view.dart new file mode 100644 index 0000000..4ae51d0 --- /dev/null +++ b/lib/feature/main/view/main_view.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:tele_book/feature/book/ui/view/book_view.dart'; +import 'package:tele_book/feature/collection/ui/view/collection_view.dart'; +import 'package:tele_book/feature/main/viewmodel/main_viewmodel.dart'; + +class MainView extends StatelessWidget { + const MainView({super.key}); + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (context) => MainViewmodel(), + child: const _MainContent(), + ); + } +} + +class _MainContent extends StatelessWidget { + const _MainContent(); + + @override + Widget build(BuildContext context) { + final vm = context.watch(); + return Scaffold( + body: IndexedStack( + index: vm.currentIndex, + children: [BookView(), CollectionView()], + ), + bottomNavigationBar: BottomNavigationBar( + currentIndex: vm.currentIndex, + onTap: (index) => vm.onTabChange(index), + items: const [ + BottomNavigationBarItem(icon: Icon(Icons.book), label: "书籍"), + BottomNavigationBarItem(icon: Icon(Icons.collections), label: "收藏夹"), + ], + ), + ); + } +} diff --git a/lib/feature/main/viewmodel/main_viewmodel.dart b/lib/feature/main/viewmodel/main_viewmodel.dart new file mode 100644 index 0000000..1dc5753 --- /dev/null +++ b/lib/feature/main/viewmodel/main_viewmodel.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class MainViewmodel extends ChangeNotifier { + int currentIndex = 0; + + void onTabChange(int index) { + if (currentIndex != index) { + currentIndex = index; + notifyListeners(); + } + } +} diff --git a/lib/feature/parse/ui/viewmodel/parse_archive_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_archive_viewmodel.dart index ac0c186..fd9a3cd 100644 --- a/lib/feature/parse/ui/viewmodel/parse_archive_viewmodel.dart +++ b/lib/feature/parse/ui/viewmodel/parse_archive_viewmodel.dart @@ -58,7 +58,7 @@ class ParseArchiveViewmodel extends ChangeNotifier { context, ).showSnackBar(const SnackBar(content: Text("保存成功"))); notifyListeners(); - context.go(AppRoute.book); + context.go(AppRoute.main); }, onError: (error) { saveToBookState = ErrorEventState(error.message); diff --git a/lib/feature/parse/ui/viewmodel/parse_batch_archive_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_batch_archive_viewmodel.dart index a497f32..2c9da58 100644 --- a/lib/feature/parse/ui/viewmodel/parse_batch_archive_viewmodel.dart +++ b/lib/feature/parse/ui/viewmodel/parse_batch_archive_viewmodel.dart @@ -151,7 +151,7 @@ class ParseBatchArchiveViewmodel extends ChangeNotifier { saveBatchAsBookState = SuccessEventState(data); notifyListeners(); saveAsBookCount = 0; - context.go(AppRoute.book); + context.go(AppRoute.main); }, onError: (error) { saveBatchAsBookState = ErrorEventState(error.message); diff --git a/lib/feature/parse/ui/viewmodel/parse_batch_image_folder_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_batch_image_folder_viewmodel.dart index a8b1f70..af75055 100644 --- a/lib/feature/parse/ui/viewmodel/parse_batch_image_folder_viewmodel.dart +++ b/lib/feature/parse/ui/viewmodel/parse_batch_image_folder_viewmodel.dart @@ -147,7 +147,7 @@ class ParseBatchImageFolderViewmodel extends ChangeNotifier { saveBatchAsBookState = SuccessEventState(null); saveAsBookCount = 0; notifyListeners(); - context.go(AppRoute.book); + context.go(AppRoute.main); }, onError: (error) { saveBatchAsBookState = ErrorEventState(error.message); diff --git a/lib/feature/parse/ui/viewmodel/parse_batch_pdf_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_batch_pdf_viewmodel.dart index 011eeae..5c8b98f 100644 --- a/lib/feature/parse/ui/viewmodel/parse_batch_pdf_viewmodel.dart +++ b/lib/feature/parse/ui/viewmodel/parse_batch_pdf_viewmodel.dart @@ -146,7 +146,7 @@ class ParseBatchPdfViewmodel extends ChangeNotifier { saveBatchAsBookState = const SuccessEventState(null); saveAsBookCount = 0; notifyListeners(); - context.go(AppRoute.book); + context.go(AppRoute.main); }, onError: (error) { saveBatchAsBookState = ErrorEventState(error.message); diff --git a/lib/feature/parse/ui/viewmodel/parse_image_folder_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_image_folder_viewmodel.dart index 33c1470..372bf0a 100644 --- a/lib/feature/parse/ui/viewmodel/parse_image_folder_viewmodel.dart +++ b/lib/feature/parse/ui/viewmodel/parse_image_folder_viewmodel.dart @@ -88,7 +88,7 @@ class ParseImageFolderViewmodel extends ChangeNotifier { onSuccess: (_) { saveToBookState = SuccessEventState(null); notifyListeners(); - context.go(AppRoute.book); + context.go(AppRoute.main); }, onError: (error) { saveToBookState = ErrorEventState(error.message); diff --git a/lib/feature/parse/ui/viewmodel/parse_pdf_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_pdf_viewmodel.dart index 8ca1060..94cbb56 100644 --- a/lib/feature/parse/ui/viewmodel/parse_pdf_viewmodel.dart +++ b/lib/feature/parse/ui/viewmodel/parse_pdf_viewmodel.dart @@ -70,7 +70,7 @@ class ParsePdfViewmodel extends ChangeNotifier { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('保存成功')), ); - context.go(AppRoute.book); + context.go(AppRoute.main); }, onError: (error) { saveToBookState = ErrorEventState(error.message); diff --git a/lib/feature/parse/ui/viewmodel/parse_web_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_web_viewmodel.dart index ab66cb4..88510dd 100644 --- a/lib/feature/parse/ui/viewmodel/parse_web_viewmodel.dart +++ b/lib/feature/parse/ui/viewmodel/parse_web_viewmodel.dart @@ -51,6 +51,6 @@ class ParseWebViewmodel extends ChangeNotifier { void startDownload(BuildContext context) { if (urls.isEmpty) return; _downloadService.startDownload(urls, title); - context.go(AppRoute.book); + context.go(AppRoute.main); } } diff --git a/lib/main.dart b/lib/main.dart index 66064a0..9491136 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:provider/provider.dart'; import 'package:responsive_framework/responsive_framework.dart'; import 'package:tele_book/common/config/global_config.dart'; @@ -40,23 +41,25 @@ Future _init() async { await InAppWebViewController.setWebContentsDebuggingEnabled(kDebugMode); } - appProviders = MultiProvider( - providers: [...createAppDI()], - child: MaterialApp.router( - title: 'TeleBook', - routerConfig: AppRoute.router, - debugShowCheckedModeBanner: false, - theme: AppTheme.light, - darkTheme: AppTheme.dark, - themeMode: ThemeMode.system, - builder: (context, child) => ResponsiveBreakpoints.builder( - child: child!, - breakpoints: [ - const Breakpoint(start: 0, end: 480, name: MOBILE), - const Breakpoint(start: 481, end: 800, name: TABLET), - const Breakpoint(start: 801, end: 1200, name: DESKTOP), - const Breakpoint(start: 1201, end: double.infinity, name: '4K'), - ], + appProviders = ProviderScope( + child: MultiProvider( + providers: [...createAppDI()], + child: MaterialApp.router( + title: 'TeleBook', + routerConfig: AppRoute.router, + debugShowCheckedModeBanner: false, + theme: AppTheme.light, + darkTheme: AppTheme.dark, + themeMode: ThemeMode.system, + builder: (context, child) => ResponsiveBreakpoints.builder( + child: child!, + breakpoints: [ + const Breakpoint(start: 0, end: 480, name: MOBILE), + const Breakpoint(start: 481, end: 800, name: TABLET), + const Breakpoint(start: 801, end: 1200, name: DESKTOP), + const Breakpoint(start: 1201, end: double.infinity, name: '4K'), + ], + ), ), ), ); diff --git a/pubspec.lock b/pubspec.lock index c57ecc4..4b5e008 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,42 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" + sha256: "5b7468c326d2f8a4f630056404ca0d291ade42918f4a3c6233618e724f39da8e" url: "https://pub.flutter-io.cn" source: hosted - version: "93.0.0" + version: "92.0.0" + analysis_server_plugin: + dependency: transitive + description: + name: analysis_server_plugin + sha256: "44adba4d74a2541173bad4c11531d2a4d22810c29c5ddb458a38e9f4d0e5eac7" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.4" analyzer: dependency: transitive description: name: analyzer - sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b + sha256: "70e4b1ef8003c64793a9e268a551a82869a8a96f39deb73dea28084b0e8bf75e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "9.0.0" + analyzer_buffer: + dependency: transitive + description: + name: analyzer_buffer + sha256: ff4bd291778c7417fe53fe24ee0d0a1f1ffe281a2d4ea887e7094f16e36eace7 url: "https://pub.flutter-io.cn" source: hosted - version: "10.0.1" + version: "0.3.0" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: "6645a029da947ffd823d98118f385d4bd26b54eb069c006b22e0b94e451814b5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.13.11" archive: dependency: "direct main" description: @@ -101,10 +125,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "521daf8d189deb79ba474e43a696b41c49fb3987818dbacf3308f1e03673a75e" + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" url: "https://pub.flutter-io.cn" source: hosted - version: "2.13.1" + version: "2.15.0" built_collection: dependency: transitive description: @@ -145,6 +169,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.0.4" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.0" cli_util: dependency: transitive description: @@ -193,6 +225,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.15.0" cross_file: dependency: transitive description: @@ -229,10 +269,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2" + sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b url: "https://pub.flutter-io.cn" source: hosted - version: "3.1.7" + version: "3.1.3" dbus: dependency: transitive description: @@ -446,6 +486,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "5.6.0" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "4e166be88e1dbbaa34a280bdb744aeae73b7ef25fdf8db7a3bb776760a3648e2" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.3.1" flutter_staggered_grid_view: dependency: "direct main" description: @@ -464,6 +512,22 @@ packages: description: flutter source: sdk version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.0" get: dependency: transitive description: @@ -640,6 +704,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" + mockito: + dependency: transitive + description: + name: mockito + sha256: eff30d002f0c8bf073b6f929df4483b543133fcafce056870163587b03f1d422 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.6.4" native_toolchain_c: dependency: transitive description: @@ -656,6 +728,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.2" nsd: dependency: transitive description: @@ -937,7 +1017,7 @@ packages: source: hosted version: "6.5.0" provider: - dependency: "direct main" + dependency: transitive description: name: provider sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" @@ -992,6 +1072,46 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.5.1" + riverpod: + dependency: "direct main" + description: + name: riverpod + sha256: "8c22216be8ad3ef2b44af3a329693558c98eca7b8bd4ef495c92db0bba279f83" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.1" + riverpod_analyzer_utils: + dependency: transitive + description: + name: riverpod_analyzer_utils + sha256: e55bc08c084a424e1bbdc303fe8ea75daafe4269b68fd0e0f6f1678413715b66 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0-dev.9" + riverpod_annotation: + dependency: "direct main" + description: + name: riverpod_annotation + sha256: "16471a1260b94e939394d78f1c63a9350936ac4a68c9fbdab40be47268c0b04f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.2" + riverpod_generator: + dependency: "direct dev" + description: + name: riverpod_generator + sha256: "6f9220534d7a353b53c875ea191a84d28cb4e52ac420a66a1bd7318329d977b0" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.3" + riverpod_lint: + dependency: "direct dev" + description: + name: riverpod_lint + sha256: "64e8debf5b719a37d48b9785dd595d34133fdcd84b8fd07157a621c54ab2156f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.3" rxdart: dependency: transitive description: @@ -1080,6 +1200,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.3" shelf_web_socket: dependency: transitive description: @@ -1109,6 +1245,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.2.2" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.10.13" source_span: dependency: transitive description: @@ -1149,6 +1301,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" stream_channel: dependency: transitive description: @@ -1189,6 +1349,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" + test: + dependency: transitive + description: + name: test + sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.30.0" test_api: dependency: transitive description: @@ -1197,6 +1365,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" + test_core: + dependency: transitive + description: + name: test_core + sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.6.16" typed_data: dependency: transitive description: @@ -1341,6 +1517,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.1" webview_flutter: dependency: "direct main" description: @@ -1405,6 +1589,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.1.3" + yaml_edit: + dependency: transitive + description: + name: yaml_edit + sha256: "07c9e63ba42519745182b88ca12264a7ba2484d8239958778dfe4d44fe760488" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.4" sdks: dart: ">=3.11.0 <4.0.0" flutter: ">=3.41.0" diff --git a/pubspec.yaml b/pubspec.yaml index 4f8401a..9d236ac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: tele_book -version: 3.1.4+3 +version: 3.1.4+4 publish_to: none description: Simple way that download the telegraph Book. environment: @@ -33,20 +33,24 @@ dependencies: flutter_reorderable_grid_view: ^5.5.2 flutter_staggered_grid_view: ^0.7.0 responsive_framework: ^1.5.1 - provider: ^6.1.5+1 + riverpod: ^3.2.1 go_router: ^17.2.1 uuid: ^4.5.3 synchronized: ^3.4.0+1 shimmer: ^3.0.0 pdf: ^3.11.0 pdfrx: ^2.3.2 + flutter_riverpod: ^3.3.1 + riverpod_annotation: ^4.0.2 dev_dependencies: - build_runner: ^2.4.15 + build_runner: ^2.15.0 drift_dev: ^2.26.0 flutter_lints: ^6.0.0 flutter_test: sdk: flutter + riverpod_generator: ^4.0.3 + riverpod_lint: ^3.1.3 flutter: uses-material-design: true From bf531f83e13e424257a24117bab319be3f5197b8 Mon Sep 17 00:00:00 2001 From: warren Date: Mon, 25 May 2026 18:01:24 +0800 Subject: [PATCH 04/16] =?UTF-8?q?=E4=BD=BF=E7=94=A8riverpod=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/provider/collection_provider.dart | 2 +- .../ui/provider/collection_provider.g.dart | 23 ++++++++++--------- .../collection/ui/view/collection_view.dart | 18 +++++++-------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/lib/feature/collection/ui/provider/collection_provider.dart b/lib/feature/collection/ui/provider/collection_provider.dart index fb687c7..8b98100 100644 --- a/lib/feature/collection/ui/provider/collection_provider.dart +++ b/lib/feature/collection/ui/provider/collection_provider.dart @@ -56,7 +56,7 @@ AsyncValue> collectionList(Ref ref) { } @riverpod -class CollectionController extends _$CollectionController { +class CreateCollectionController extends _$CreateCollectionController { @override FutureOr build() { // 可以在这里进行一些初始化操作 diff --git a/lib/feature/collection/ui/provider/collection_provider.g.dart b/lib/feature/collection/ui/provider/collection_provider.g.dart index 4cf0847..0a0a73f 100644 --- a/lib/feature/collection/ui/provider/collection_provider.g.dart +++ b/lib/feature/collection/ui/provider/collection_provider.g.dart @@ -57,34 +57,35 @@ final class CollectionListProvider String _$collectionListHash() => r'8dee32d769cba75cfab03c365a6de455a8222e29'; -@ProviderFor(CollectionController) -final collectionControllerProvider = CollectionControllerProvider._(); +@ProviderFor(CreateCollectionController) +final createCollectionControllerProvider = + CreateCollectionControllerProvider._(); -final class CollectionControllerProvider - extends $AsyncNotifierProvider { - CollectionControllerProvider._() +final class CreateCollectionControllerProvider + extends $AsyncNotifierProvider { + CreateCollectionControllerProvider._() : super( from: null, argument: null, retry: null, - name: r'collectionControllerProvider', + name: r'createCollectionControllerProvider', isAutoDispose: true, dependencies: null, $allTransitiveDependencies: null, ); @override - String debugGetCreateSourceHash() => _$collectionControllerHash(); + String debugGetCreateSourceHash() => _$createCollectionControllerHash(); @$internal @override - CollectionController create() => CollectionController(); + CreateCollectionController create() => CreateCollectionController(); } -String _$collectionControllerHash() => - r'9258092689b48ab819099d458d6e7e15f4d970fe'; +String _$createCollectionControllerHash() => + r'4a614af941a2c48759c089990e1658f64b044933'; -abstract class _$CollectionController extends $AsyncNotifier { +abstract class _$CreateCollectionController extends $AsyncNotifier { FutureOr build(); @$mustCallSuper @override diff --git a/lib/feature/collection/ui/view/collection_view.dart b/lib/feature/collection/ui/view/collection_view.dart index 19339ce..476f6d1 100644 --- a/lib/feature/collection/ui/view/collection_view.dart +++ b/lib/feature/collection/ui/view/collection_view.dart @@ -10,9 +10,9 @@ class CollectionView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final listAsync = ref.watch(collectionListProvider); - final controllerState = ref.watch(collectionControllerProvider); + final createState = ref.watch(createCollectionControllerProvider); - ref.listen>(collectionControllerProvider, (prev, next) { + ref.listen>(createCollectionControllerProvider, (prev, next) { next.whenOrNull( data: (_) { if (prev?.isLoading == true) { @@ -35,10 +35,10 @@ class CollectionView extends ConsumerWidget { title: Text("收藏夹"), actions: [ IconButton( - onPressed: controllerState.isLoading + onPressed: createState.isLoading ? null : () => _showCreateDialog(context, ref), - icon: controllerState.isLoading + icon: createState.isLoading ? const SizedBox( width: 18, height: 18, @@ -120,21 +120,21 @@ class CollectionView extends ConsumerWidget { ), Consumer( builder: (context, ref, _) { - final isLoading = ref - .watch(collectionControllerProvider) + final creating = ref + .watch(createCollectionControllerProvider) .isLoading; return TextButton( - onPressed: isLoading + onPressed: creating ? null : () { ref - .read(collectionControllerProvider.notifier) + .read(createCollectionControllerProvider.notifier) .createCollection( name: nameController.text, description: descController.text, ); }, - child: isLoading + child: creating ? const SizedBox( width: 16, height: 16, From 8fad0a1a3c460b4c4979d6d6b43d579da11820c5 Mon Sep 17 00:00:00 2001 From: Warren <604440452@qq.com> Date: Mon, 25 May 2026 19:45:41 +0800 Subject: [PATCH 05/16] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dwindows=E4=B8=8A?= =?UTF-8?q?=E7=9A=84=E7=BC=96=E8=AF=91=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .metadata | 12 ++++---- windows/CMakeLists.txt | 41 +++++--------------------- windows/flutter/CMakeLists.txt | 2 +- windows/runner/Runner.rc | 6 ++-- windows/runner/resources/app_icon.ico | Bin 1806 -> 33772 bytes windows/runner/runner.exe.manifest | 6 ---- windows/runner/utils.cpp | 4 +-- windows/runner/win32_window.cpp | 2 +- windows/runner/win32_window.h | 2 +- 9 files changed, 22 insertions(+), 53 deletions(-) diff --git a/.metadata b/.metadata index c5f4036..c96b2aa 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "3b62efc2a3da49882f43c372e0bc53daef7295a6" + revision: "00b0c91f06209d9e4a41f71b7a512d6eb3b9c694" channel: "stable" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 - base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 - - platform: linux - create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 - base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 + base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 + - platform: windows + create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 + base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 # User provided section diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index df20df8..0ee9434 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -1,23 +1,15 @@ # Project-level configuration. cmake_minimum_required(VERSION 3.14) -project(TeleBook LANGUAGES CXX) - -# Suppress CMP0175 warning from third-party plugins (e.g. flutter_inappwebview_windows) -if(POLICY CMP0175) - cmake_policy(SET CMP0175 OLD) -endif() +project(tele_book LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. -set(BINARY_NAME "TeleBook") +set(BINARY_NAME "tele_book") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(VERSION 3.14...3.25) -# 保证 CMake 允许 add_custom_command(TARGET) 使用 DEPENDS,消除 flutter 插件的警告 -cmake_policy(SET CMP0175 NEW) - # Define build configuration option. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) @@ -69,12 +61,13 @@ include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) -# so that building and running form within Visual Studio will work. +# so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) - -set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") @@ -94,14 +87,14 @@ if(PLUGIN_BUNDLED_LIBRARIES) COMPONENT Runtime) endif() -# Copy the native assets provided by the build.dart form all packages. +# Copy the native assets provided by the build.dart from all packages. set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") install(DIRECTORY "${NATIVE_ASSETS_DIR}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) # Fully re-copy the assets directory on each build to avoid having stale files -# form a previous install. +# from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") @@ -113,21 +106,3 @@ install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) - -# 检查 NuGet 可执行文件(允许在项目 tools/nuget 目录或系统 PATH 中查找) -find_program(NUGET_EXECUTABLE - NAMES nuget.exe nuget - HINTS - "${CMAKE_SOURCE_DIR}/tools/nuget" - PATHS - ENV PATH -) - -if(NOT NUGET_EXECUTABLE) - message(FATAL_ERROR - "\nNuGet 未找到!flutter_inappwebview_windows 插件需要 NuGet。\n\ -请运行 D:\\StudioProjects\\TeleBook\\tools\\install_nuget.ps1 以下载 nuget.exe,\n\ -或按照 https://inappwebview.dev/docs/intro#setup-windows 进行手动安装。\n\n\ -下载后请将 nuget.exe 所在目录加入系统 PATH,或在环境变量中设置 NUGET_EXECUTABLE 指向 nuget.exe 的完整路径。\n" - ) -endif() diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt index c663404..903f489 100644 --- a/windows/flutter/CMakeLists.txt +++ b/windows/flutter/CMakeLists.txt @@ -85,7 +85,7 @@ add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, -# since currently there's no way to get a full input/output list form the +# since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc index d6b6ba4..f71b184 100644 --- a/windows/runner/Runner.rc +++ b/windows/runner/Runner.rc @@ -6,7 +6,7 @@ #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // -// Generated form the TEXTINCLUDE 2 resource. +// Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" @@ -93,7 +93,7 @@ BEGIN VALUE "FileDescription", "tele_book" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "tele_book" "\0" - VALUE "LegalCopyright", "Copyright (C) 2023 com.dorkytiger. All rights reserved." "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.dorkytiger. All rights reserved." "\0" VALUE "OriginalFilename", "tele_book.exe" "\0" VALUE "ProductName", "tele_book" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" @@ -113,7 +113,7 @@ END #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // -// Generated form the TEXTINCLUDE 3 resource. +// Generated from the TEXTINCLUDE 3 resource. // diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico index 32de11cf7bc906ad909c2095394de9c87936dc7c..c04e20caf6370ebb9253ad831cc31de4a9c965f6 100644 GIT binary patch literal 33772 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 literal 1806 zcmV+p2l4m-0096205C8B0000W0Qd#~02TlM0EtjeM-2)Z3IG5A4M|8uQUCw|FaQ7m zFbDzw007uvZqNV#2ER!}K~#90?U!v#Q&}2-Pul_8k}0?(Gef8`Ks1U4OIs|FVK8Mx zM;2|2EtHm6T6W~4+F*i$L8JZvcEK3af&~>}Yg<4|w}wweMusr0owjycoIw<=iB73a z5GduF-d%fl38Nbd1KHh4cz!g^$vN+N&N=se?>(P@|1&=MUkiN5x{x>4g}kvYxTsZxZQ4@hx6yppFVxspf_Y>WN0)R06=8qPMPdhXJ=68WcZzx`(a ze)ZqfE|+V`*_ISSqENXxIm5%lPN$R2X4`Bw0DwRs7#$r|tJNbTBZY;9N~N-~vElgf z6O)sZH8nL{F8ATXhww6zl9Gagf%HwA?Y=&~$;rtUizOf+AU{7pEiJ9P zySt&G0eD8j0TUAwX*60(OUvNk;BOHE0K9$sHak1}#fujUEgFrcv$GS8MrUOm(`vQ( z`T0#vP1RM^Xf)blu_PoUNF!b&(Dm!r zsZ^?5E*FVJO-(AbTK(?bI|zc@Za0}ss;H@si>$ZDJi*o_wJ!XhX8m#*@g`pPj89^c&DTvu0j>eQ*s%*?K?E}2ZW zXklw4gaC-ef4aNBfAF9Shr{LN<>~eM?Cfm)kNWtpzJe2QqI9zRQt)xm~Fc{+E;$~)MVq;?g1X-=txVX5Xp&=|5%j5Af zGc#vrXS=(*`F#GG2>}RlLy*6}e{F5;z_Wp_@4hQ8F825LKXKxCpT$yCbXF>r{;MYf zG3kNHWR{ngx3sh{7!14J9upHoB9UrpYJ!4-+-~=(gghCBn3|lVQmLV#p4G& zSFir^%bVM`@1&%pUc7kmz<~oi9uF?VzP`RFRxzrtD zDIu53wQ19)&6_uq$z%$JLZ{P-M54`R)9dv*oi05+oe)7NEGYQwv(HH+G62BG$H#8B z|D~+#szeeQ5g`x=qNAhnc)Z18(dl&eI_{MemsqXVrK^DVEPLglsi_G*#E_7XsHiB| zFFZVaVqyY=!61=HxZX@pPZt&x^!N80jmG=;@0XUA4h{~kWgYqe2!dXCP%4#h3xPmT zsZ?QMVE}+PZ{Bb?oPU1*uPElKgg&OPuW#G7Z98`CK%tO*eNSd*XIB|;4X)Dj^Ydo2 wdG&E%e$RLN{|@Uy-dGp%#=4L<)`h(B4|4Af$83DsBLDyZ07*qoM6N<$f~aP1NB{r; diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest index a42ea76..153653e 100644 --- a/windows/runner/runner.exe.manifest +++ b/windows/runner/runner.exe.manifest @@ -9,12 +9,6 @@ - - - - - - diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp index b2b0873..3a0b465 100644 --- a/windows/runner/utils.cpp +++ b/windows/runner/utils.cpp @@ -45,13 +45,13 @@ std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } - int target_length = ::WideCharToMultiByte( + unsigned int target_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, nullptr, 0, nullptr, nullptr) -1; // remove the trailing null character int input_length = (int)wcslen(utf16_string); std::string utf8_string; - if (target_length <= 0 || target_length > utf8_string.max_size()) { + if (target_length == 0 || target_length > utf8_string.max_size()) { return utf8_string; } utf8_string.resize(target_length); diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp index e516426..60608d0 100644 --- a/windows/runner/win32_window.cpp +++ b/windows/runner/win32_window.cpp @@ -37,7 +37,7 @@ int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } -// Dynamically loads the |EnableNonClientDpiScaling| form the User32 module. +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h index 0e02598..e901dde 100644 --- a/windows/runner/win32_window.h +++ b/windows/runner/win32_window.h @@ -8,7 +8,7 @@ #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be -// inherited form by classes that wish to specialize with custom +// inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: From 0bbc0f12dce80ba578160ce6c290429216bf9a9a Mon Sep 17 00:00:00 2001 From: Warren <604440452@qq.com> Date: Mon, 25 May 2026 23:07:50 +0800 Subject: [PATCH 06/16] =?UTF-8?q?=E5=BC=80=E5=A7=8B=E6=8D=A2=E6=88=90river?= =?UTF-8?q?pod?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/db/app_database.dart | 19 +- lib/core/di/app_di.dart | 18 - lib/core/di/core_di.dart | 7 - lib/core/di/datasource_di.dart | 10 - lib/core/di/repository_di.dart | 11 - lib/core/di/service_di.dart | 19 - lib/core/di/store_di.dart | 11 - lib/core/route/app_route.dart | 2 +- .../local/book_local_datasource.dart | 2 +- lib/feature/book/enum/book_menu_type.dart | 28 + .../book/model/state/book_list_state.dart | 65 ++ lib/feature/book/model/vo/book_vo.dart | 16 +- .../book/repository/book_repository.dart | 64 +- lib/feature/book/service/book_service.dart | 65 -- .../book/ui/provider/book_provider.dart | 225 +++++++ lib/feature/book/ui/view/book_form_view.dart | 1 - lib/feature/book/ui/view/book_list_view.dart | 604 ++++++++++++++++++ lib/feature/book/ui/view/book_view.dart | 458 ------------- .../ui/viewmodel/book_form_viewmodel.dart | 57 -- .../ui/viewmodel/book_page_viewmodel.dart | 65 -- .../book/ui/viewmodel/book_viewmodel.dart | 229 ------- .../ui/provider/download_provider.dart | 31 + lib/feature/main/view/main_view.dart | 4 +- lib/main.dart | 35 +- 24 files changed, 1029 insertions(+), 1017 deletions(-) delete mode 100644 lib/core/di/app_di.dart delete mode 100644 lib/core/di/core_di.dart delete mode 100644 lib/core/di/datasource_di.dart delete mode 100644 lib/core/di/repository_di.dart delete mode 100644 lib/core/di/service_di.dart delete mode 100644 lib/core/di/store_di.dart create mode 100644 lib/feature/book/enum/book_menu_type.dart create mode 100644 lib/feature/book/model/state/book_list_state.dart delete mode 100644 lib/feature/book/service/book_service.dart create mode 100644 lib/feature/book/ui/provider/book_provider.dart create mode 100644 lib/feature/book/ui/view/book_list_view.dart delete mode 100644 lib/feature/book/ui/view/book_view.dart delete mode 100644 lib/feature/book/ui/viewmodel/book_form_viewmodel.dart delete mode 100644 lib/feature/book/ui/viewmodel/book_page_viewmodel.dart delete mode 100644 lib/feature/book/ui/viewmodel/book_viewmodel.dart create mode 100644 lib/feature/download/ui/provider/download_provider.dart diff --git a/lib/core/db/app_database.dart b/lib/core/db/app_database.dart index e4ed293..cdc1333 100644 --- a/lib/core/db/app_database.dart +++ b/lib/core/db/app_database.dart @@ -1,5 +1,8 @@ +import 'dart:io'; + import 'package:drift/drift.dart'; import 'package:drift_flutter/drift_flutter.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:riverpod/riverpod.dart'; import 'package:tele_book/feature/book/datasource/local/book_local_datasource.dart'; import 'package:tele_book/feature/book/model/table/book_table.dart'; @@ -29,7 +32,21 @@ class AppDatabase extends _$AppDatabase { int get schemaVersion => 1; static QueryExecutor _openConnection() { - return driftDatabase(name: 'tele_book'); + return driftDatabase( + name: 'tele_book', + native: DriftNativeOptions( + databaseDirectory: () async { + if (Platform.isIOS || Platform.isAndroid) { + final dbFolder = await getApplicationDocumentsDirectory(); + print('Database path: ${dbFolder.path}'); + return dbFolder.path; + } else { + // For desktop platforms, use the current directory + return Directory.current.path; + } + }, + ), + ); } } diff --git a/lib/core/di/app_di.dart b/lib/core/di/app_di.dart deleted file mode 100644 index 6f76a02..0000000 --- a/lib/core/di/app_di.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:provider/single_child_widget.dart'; -import 'package:tele_book/core/di/core_di.dart'; -import 'package:tele_book/core/di/datasource_di.dart'; -import 'package:tele_book/core/di/repository_di.dart'; -import 'package:tele_book/core/di/service_di.dart'; -import 'package:tele_book/core/di/store_di.dart'; - - - -List createAppDI() { - return [ - ...createCoreDI(), - ...createDatasourceDI(), - ...createRepositoryDI(), - ...createServiceDI(), - ...createStoreDI(), - ]; -} diff --git a/lib/core/di/core_di.dart b/lib/core/di/core_di.dart deleted file mode 100644 index ac8ac5d..0000000 --- a/lib/core/di/core_di.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:provider/single_child_widget.dart'; -import 'package:provider/provider.dart'; -import 'package:tele_book/core/db/app_database.dart'; - -List createCoreDI() { - return [Provider(create: (_) => AppDatabase())]; -} diff --git a/lib/core/di/datasource_di.dart b/lib/core/di/datasource_di.dart deleted file mode 100644 index c346333..0000000 --- a/lib/core/di/datasource_di.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:provider/provider.dart'; -import 'package:provider/single_child_widget.dart'; -import 'package:tele_book/feature/download/datasource/runtime/download_runtime_datasource.dart'; - -List createDatasourceDI() { - return [ - - Provider(create: (context) => DownloadRuntimeDatasource()), - ]; -} diff --git a/lib/core/di/repository_di.dart b/lib/core/di/repository_di.dart deleted file mode 100644 index c7f8cc3..0000000 --- a/lib/core/di/repository_di.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:provider/provider.dart'; -import 'package:provider/single_child_widget.dart'; -import 'package:tele_book/feature/book/repository/book_repository.dart'; -import 'package:tele_book/feature/download/repository/download_repository.dart'; - -List createRepositoryDI() { - return [ - Provider(create: (context) => BookRepository(context.read())), - Provider(create: (context) => DownloadRepository(context.read())), - ]; -} diff --git a/lib/core/di/service_di.dart b/lib/core/di/service_di.dart deleted file mode 100644 index cf56ea5..0000000 --- a/lib/core/di/service_di.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:provider/provider.dart'; -import 'package:provider/single_child_widget.dart'; -import 'package:tele_book/feature/book/service/book_service.dart'; -import 'package:tele_book/feature/download/service/download_service.dart'; -import 'package:tele_book/feature/parse/service/parse_archive_service.dart'; -import 'package:tele_book/feature/parse/service/parse_pdf_service.dart'; -import 'package:tele_book/feature/parse/service/parse_web_service.dart'; - -List createServiceDI() { - return [ - Provider(create: (context) => BookService(context.read())), - Provider(create: (context) => ParseWebService()), - Provider(create: (context) => ParseArchiveService()), - Provider(create: (context) => ParsePdfService()), - Provider( - create: (context) => DownloadService(context.read(), context.read()), - ), - ]; -} diff --git a/lib/core/di/store_di.dart b/lib/core/di/store_di.dart deleted file mode 100644 index 3cf917a..0000000 --- a/lib/core/di/store_di.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:provider/provider.dart'; -import 'package:provider/single_child_widget.dart'; -import 'package:tele_book/feature/book/store/book_store.dart'; -import 'package:tele_book/feature/download/store/download_store.dart'; - -List createStoreDI() { - return [ - ChangeNotifierProvider(create: (context) => BookStore(context.read())), - ChangeNotifierProvider(create: (context) => DownloadStore(context.read())), - ]; -} diff --git a/lib/core/route/app_route.dart b/lib/core/route/app_route.dart index 77c49de..f0e8221 100644 --- a/lib/core/route/app_route.dart +++ b/lib/core/route/app_route.dart @@ -3,7 +3,7 @@ import 'package:go_router/go_router.dart'; import 'package:tele_book/core/db/app_database.dart'; import 'package:tele_book/feature/book/ui/view/book_form_view.dart'; import 'package:tele_book/feature/book/ui/view/book_page_view.dart'; -import 'package:tele_book/feature/book/ui/view/book_view.dart'; +import 'package:tele_book/feature/book/ui/view/book_list_view.dart'; import 'package:tele_book/feature/download/ui/view/download_list_view.dart'; import 'package:tele_book/feature/export/ui/view/export_batch_form_view.dart'; import 'package:tele_book/feature/export/ui/view/export_single_form_view.dart'; diff --git a/lib/feature/book/datasource/local/book_local_datasource.dart b/lib/feature/book/datasource/local/book_local_datasource.dart index 5bfebe7..d11891d 100644 --- a/lib/feature/book/datasource/local/book_local_datasource.dart +++ b/lib/feature/book/datasource/local/book_local_datasource.dart @@ -46,7 +46,7 @@ class BookLocalDatasource extends DatabaseAccessor return q.watch(); } - Future> getBooks({ + Future> getPagingBooks({ int? page, DateTime? lastCreatedAt, int limit = 20, diff --git a/lib/feature/book/enum/book_menu_type.dart b/lib/feature/book/enum/book_menu_type.dart new file mode 100644 index 0000000..b8632d5 --- /dev/null +++ b/lib/feature/book/enum/book_menu_type.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +/// 顶部菜单(排序方式 + 布局切换) +enum BookTopMenuType { + asc(icon: Icons.arrow_upward, title: '升序'), + desc(icon: Icons.arrow_downward, title: '降序'), + name(icon: Icons.sort_by_alpha, title: '按书名'), + lastCreatedAt(icon: Icons.access_time, title: '按添加时间'), + list(icon: Icons.view_list, title: '列表视图'), + grid(icon: Icons.grid_view, title: '网格视图'); + + const BookTopMenuType({required this.icon, required this.title}); + + final IconData icon; + final String title; +} + +/// 单本书条目菜单 +enum BookItemMenuType { + export(icon: Icons.move_to_inbox, title: '导出'), + delete(icon: Icons.delete, title: '删除'); + + const BookItemMenuType({required this.icon, required this.title}); + + final IconData icon; + final String title; +} + diff --git a/lib/feature/book/model/state/book_list_state.dart b/lib/feature/book/model/state/book_list_state.dart new file mode 100644 index 0000000..031ec46 --- /dev/null +++ b/lib/feature/book/model/state/book_list_state.dart @@ -0,0 +1,65 @@ +import 'package:tele_book/core/db/app_database.dart'; +import 'package:tele_book/feature/book/enum/book_sort.dart'; + +class BookListState { + // --- 数据与查询状态 --- + final List bookVos; + final bool hasMore; + final bool isLoadingMore; + final String name; + final BookSort? sort; + + // --- 💡 塞入 UI 交互状态 --- + final bool isSelectionMode; + final Set selectedBookIds; // 使用 Set 处理查找更高效 + final BookLayout layout; // 列表或网格布局 + + BookListState({ + required this.bookVos, + required this.hasMore, + required this.name, + this.sort, + this.isLoadingMore = false, + this.isSelectionMode = false, + this.selectedBookIds = const {}, + this.layout = BookLayout.list, + }); + + // 快捷派生属性:获取当前选中的所有书籍模型 + List get selectedBooks => + bookVos.where((vo) => selectedBookIds.contains(vo.book.id)).toList(); + + BookListState copyWith({ + List? bookVos, + bool? hasMore, + bool? isLoadingMore, + String? name, + BookSort? sort, + bool? isSelectionMode, + Set? selectedBookIds, + BookLayout? layout, + }) { + return BookListState( + bookVos: bookVos ?? this.bookVos, + hasMore: hasMore ?? this.hasMore, + isLoadingMore: isLoadingMore ?? this.isLoadingMore, + name: name ?? this.name, + sort: sort ?? this.sort, + isSelectionMode: isSelectionMode ?? this.isSelectionMode, + selectedBookIds: selectedBookIds ?? this.selectedBookIds, + layout: layout ?? this.layout, + ); + } +} + +enum BookLayout { + list, + grid, +} + +class BookListItemVo { + final BookTableData book; + final String coverImagePath; + + BookListItemVo({required this.book, required this.coverImagePath}); +} diff --git a/lib/feature/book/model/vo/book_vo.dart b/lib/feature/book/model/vo/book_vo.dart index 4d87bb8..9a2c2ad 100644 --- a/lib/feature/book/model/vo/book_vo.dart +++ b/lib/feature/book/model/vo/book_vo.dart @@ -1,22 +1,10 @@ import 'package:tele_book/core/db/app_database.dart'; +import 'package:tele_book/feature/book/enum/book_sort.dart'; -class BookListVo { - final List bookVos; - - BookListVo({required this.bookVos}); -} - - -class BookListItemVo { - final BookTableData book; - final String coverImagePath; - - BookListItemVo({required this.book, required this.coverImagePath}); -} class BookDetailVo { final BookTableData book; final List imagePaths; BookDetailVo({required this.book, required this.imagePaths}); -} \ No newline at end of file +} diff --git a/lib/feature/book/repository/book_repository.dart b/lib/feature/book/repository/book_repository.dart index 9ee4c9f..349b084 100644 --- a/lib/feature/book/repository/book_repository.dart +++ b/lib/feature/book/repository/book_repository.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path/path.dart' as p; import 'package:tele_book/core/db/app_database.dart'; import 'package:tele_book/core/util/failure_util.dart'; @@ -18,7 +19,12 @@ class _CopyBookArgs { final String bookId; final String destDirPath; final List srcPaths; - _CopyBookArgs({required this.bookId, required this.destDirPath, required this.srcPaths}); + + _CopyBookArgs({ + required this.bookId, + required this.destDirPath, + required this.srcPaths, + }); } /// 在后台 Isolate 中复制文件,返回存储用的相对路径列表 @@ -38,7 +44,6 @@ Future> _copyBookFiles(_CopyBookArgs args) async { return relPaths; } - /// 在后台 Isolate 中递归删除目录列表 Future _deleteBookDirs(List dirPaths) async { for (final path in dirPaths) { @@ -49,39 +54,28 @@ Future _deleteBookDirs(List dirPaths) async { } } +final bookRepositoryProvider = Provider((ref) { + final database = ref.watch(databaseProvider); + return BookRepository(database); +}); + class BookRepository { final AppDatabase _db; late final BookLocalDatasource _bookLocalDatasource = _db.bookLocalDatasource; BookRepository(this._db); - Stream> watchBooks({ + Future> getPagedBooks({ int? page, - int? pageSize, + int pageSize = 20, DateTime? lastCreatedAt, String? name, BookSort? sort, }) { - return _bookLocalDatasource.watchBooks( - page: page, - pageSize: pageSize, - lastCreatedAt: lastCreatedAt, - name: name, - sort: sort, - ); - } - - Future> fetchBooks({ - int? page, - int? pageSize, - DateTime? lastCreatedAt, - String? name, - BookSort? sort, - }) async { - return _bookLocalDatasource.getBooks( + return _bookLocalDatasource.getPagingBooks( page: page, lastCreatedAt: lastCreatedAt, - limit: pageSize ?? 20, + limit: pageSize, name: name, sort: sort, ); @@ -111,7 +105,10 @@ class BookRepository { bookDirs.add(p.dirname(subPath)); } else { final normalized = subPath.replaceAll('\\', '/'); - final segments = normalized.split('/').where((e) => e.isNotEmpty).toList(); + final segments = normalized + .split('/') + .where((e) => e.isNotEmpty) + .toList(); if (segments.isNotEmpty) { bookDirs.add(p.join(GlobalConfig.booksDir.path, segments.first)); } @@ -135,7 +132,11 @@ class BookRepository { // ① 文件复制放后台 Isolate final relPaths = await compute( _copyBookFiles, - _CopyBookArgs(bookId: bookId, destDirPath: destDirPath, srcPaths: dto.paths), + _CopyBookArgs( + bookId: bookId, + destDirPath: destDirPath, + srcPaths: dto.paths, + ), ); // ② DB 写入(快,主线程即可) @@ -147,7 +148,9 @@ class BookRepository { } catch (e, st) { // 清理已创建的目录(后台 Isolate) await compute(_deleteBookDirs, [destDirPath]); - return Result.failure(BusinessFailure(message: '保存书籍失败', details: e, stackTrace: st)); + return Result.failure( + BusinessFailure(message: '保存书籍失败', details: e, stackTrace: st), + ); } } @@ -176,7 +179,11 @@ class BookRepository { final relPaths = await compute( _copyBookFiles, - _CopyBookArgs(bookId: bookId, destDirPath: destDirPath, srcPaths: dto.paths), + _CopyBookArgs( + bookId: bookId, + destDirPath: destDirPath, + srcPaths: dto.paths, + ), ); createdDirs.add(destDirPath); @@ -188,7 +195,10 @@ class BookRepository { await _db.transaction(() async { for (final book in bookData) { await _bookLocalDatasource.insertBook( - BookTableCompanion.insert(name: book.title, localSubPaths: book.relPaths), + BookTableCompanion.insert( + name: book.title, + localSubPaths: book.relPaths, + ), ); } }); diff --git a/lib/feature/book/service/book_service.dart b/lib/feature/book/service/book_service.dart deleted file mode 100644 index 4822000..0000000 --- a/lib/feature/book/service/book_service.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:tele_book/common/config/global_config.dart'; -import 'package:tele_book/core/db/app_database.dart'; -import 'package:tele_book/feature/book/enum/book_sort.dart'; -import 'package:tele_book/feature/book/model/vo/book_vo.dart'; -import 'package:tele_book/feature/book/repository/book_repository.dart'; - -class BookService { - final BookRepository _bookRepository; - - BookService(this._bookRepository); - - Stream watchBooks({ - int? page, - int? pageSize, - DateTime? lastCreatedAt, - String? name, - BookSort? sort, - }) { - return _bookRepository - .watchBooks( - page: page, - pageSize: pageSize, - lastCreatedAt: lastCreatedAt, - name: name, - sort: sort, - ) - .map((books) { - final bookVos = books.map((book) { - // 用 GlobalConfig 解析封面路径,避免 async/await - final coverPath = book.localSubPaths.isNotEmpty - ? GlobalConfig.resolveBookPath(book.localSubPaths.first) - : ''; - return BookListItemVo(book: book, coverImagePath: coverPath); - }).toList(); - return BookListVo(bookVos: bookVos); - }); - } - - Future fetchBooks({ - int? page, - DateTime? lastCreatedAt, - int pageSize = 20, - String? name, - BookSort? sort, - }) async { - final books = await _bookRepository.fetchBooks( - page: page, - lastCreatedAt: lastCreatedAt, - pageSize: pageSize, - name: name, - sort: sort, - ); - final bookVos = books.map((book) { - final coverPath = book.localSubPaths.isNotEmpty - ? GlobalConfig.resolveBookPath(book.localSubPaths.first) - : ''; - return BookListItemVo(book: book, coverImagePath: coverPath); - }).toList(); - return BookListVo(bookVos: bookVos); - } - - Future updateBook(BookTableData book) { - return _bookRepository.updateBook(book); - } - } diff --git a/lib/feature/book/ui/provider/book_provider.dart b/lib/feature/book/ui/provider/book_provider.dart new file mode 100644 index 0000000..8077797 --- /dev/null +++ b/lib/feature/book/ui/provider/book_provider.dart @@ -0,0 +1,225 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:tele_book/common/config/global_config.dart'; +import 'package:tele_book/core/db/app_database.dart'; +import 'package:tele_book/feature/book/enum/book_sort.dart'; +import 'package:tele_book/feature/book/model/state/book_list_state.dart'; +import 'package:tele_book/feature/book/repository/book_repository.dart'; + +part 'book_provider.g.dart'; + +@riverpod +class BookList extends _$BookList { + final int _pageSize = 20; + + @override + Future build() async { + return _fetchPage( + name: '', + sort: null, + isFirstPage: true, + previousBooks: [], + ); + } + + Future _fetchPage({ + required String name, + required BookSort? sort, + required bool isFirstPage, + required List previousBooks, + }) async { + final bookRepository = ref.watch(bookRepositoryProvider); + + final lastCreatedAt = isFirstPage + ? null + : previousBooks.last.book.createdAt; + + final newBooks = await bookRepository.getPagedBooks( + page: isFirstPage ? 1 : null, + pageSize: _pageSize, + name: name, + sort: sort, + lastCreatedAt: lastCreatedAt, + ); + + final bookVos = newBooks + .map( + (book) => BookListItemVo( + book: book, + coverImagePath: GlobalConfig.resolveBookPath( + book.localSubPaths.first, + ), + ), + ) + .toList(); + + return BookListState( + bookVos: isFirstPage ? bookVos : [...previousBooks, ...bookVos], + hasMore: newBooks.length == _pageSize, + name: name, + sort: sort, + isLoadingMore: false, + ); + } + + Future loadNextPage() async { + if (!state.hasValue || state.isLoading) return; + final current = state.requireValue; + + if (!current.hasMore || current.isLoadingMore) return; + state = AsyncData(current.copyWith(isLoadingMore: true)); + + try { + final nextState = await _fetchPage( + name: current.name, + sort: current.sort, + isFirstPage: false, + previousBooks: current.bookVos, + ); + state = AsyncData(nextState); + } catch (e, st) { + state = AsyncError(e, st); + } + } + + Future updateSearch(String name) async { + if (!state.hasValue) return; + + state = const AsyncLoading(); + try { + final nextState = await _fetchPage( + name: name, + sort: state.requireValue.sort, + isFirstPage: true, + previousBooks: [], + ); + state = AsyncData(nextState); + } catch (e, st) { + state = AsyncError(e, st); + } + } + + Future updateSort(BookSort? sort) async { + if (!state.hasValue) return; + state = const AsyncLoading(); + try { + final nextState = await _fetchPage( + name: state.requireValue.name, + sort: sort, + isFirstPage: true, + previousBooks: [], + ); + state = AsyncData(nextState); + } catch (e, st) { + state = AsyncError(e, st); + } + } + + // ➡️ 进入多选模式并勾选第一本书 + void enterSelectionMode(BookTableData firstBook) { + if (!state.hasValue) return; + state = AsyncData(state.requireValue.copyWith( + isSelectionMode: true, + selectedBookIds: {firstBook.id}, + )); + } + + // ➡️ 退出多选模式 + void exitSelectionMode() { + if (!state.hasValue) return; + state = AsyncData(state.requireValue.copyWith( + isSelectionMode: false, + selectedBookIds: {}, // 清空勾选 + )); + } + + // ➡️ 切换某本书的选中状态 + void toggleSelection(int bookId) { + if (!state.hasValue) return; + final updatedIds = Set.from(state.requireValue.selectedBookIds); + if (updatedIds.contains(bookId)) { + updatedIds.remove(bookId); + // 如果全取消了,可以选择自动退出多选模式 + if (updatedIds.isEmpty) { + exitSelectionMode(); + return; + } + } else { + updatedIds.add(bookId); + } + state = AsyncData(state.requireValue.copyWith(selectedBookIds: updatedIds)); + } + + // ➡️ 全选 + void selectAll() { + if (!state.hasValue) return; + final allIds = state.requireValue.bookVos.map((vo) => vo.book.id).toSet(); + state = AsyncData(state.requireValue.copyWith(selectedBookIds: allIds)); + } + + // ➡️ 切换布局 + void toggleLayout() { + if (!state.hasValue) return; + final nextLayout = state.requireValue.layout == BookLayout.list + ? BookLayout.grid + : BookLayout.list; + state = AsyncData(state.requireValue.copyWith(layout: nextLayout)); + } + + // ➡️ 删除单本书并刷新列表 + Future deleteBook(int bookId) async { + if (!state.hasValue) return; + final current = state.requireValue; + final bookRepository = ref.read(bookRepositoryProvider); + + // 乐观更新:先从 UI 中移除 + state = AsyncData(current.copyWith( + bookVos: current.bookVos.where((v) => v.book.id != bookId).toList(), + )); + + await bookRepository.deleteBook(bookId); + + // 重新拉取第一页确保数据一致 + try { + final nextState = await _fetchPage( + name: current.name, + sort: current.sort, + isFirstPage: true, + previousBooks: [], + ); + state = AsyncData(nextState.copyWith(layout: current.layout)); + } catch (e, st) { + state = AsyncError(e, st); + } + } + + // ➡️ 批量删除选中书籍并刷新列表 + Future deleteSelected() async { + if (!state.hasValue) return; + final current = state.requireValue; + final selectedIds = Set.from(current.selectedBookIds); + final bookRepository = ref.read(bookRepositoryProvider); + + // 乐观更新 + state = AsyncData(current.copyWith( + bookVos: current.bookVos.where((v) => !selectedIds.contains(v.book.id)).toList(), + isSelectionMode: false, + selectedBookIds: {}, + )); + + for (final id in selectedIds) { + await bookRepository.deleteBook(id); + } + + try { + final nextState = await _fetchPage( + name: current.name, + sort: current.sort, + isFirstPage: true, + previousBooks: [], + ); + state = AsyncData(nextState.copyWith(layout: current.layout)); + } catch (e, st) { + state = AsyncError(e, st); + } + } +} diff --git a/lib/feature/book/ui/view/book_form_view.dart b/lib/feature/book/ui/view/book_form_view.dart index 21e61da..1c6eb74 100644 --- a/lib/feature/book/ui/view/book_form_view.dart +++ b/lib/feature/book/ui/view/book_form_view.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:tele_book/common/widget/local_image_widget.dart'; import 'package:tele_book/core/db/app_database.dart'; -import 'package:tele_book/feature/book/ui/viewmodel/book_form_viewmodel.dart'; class BookFormView extends StatelessWidget { final BookTableData book; diff --git a/lib/feature/book/ui/view/book_list_view.dart b/lib/feature/book/ui/view/book_list_view.dart new file mode 100644 index 0000000..ad19425 --- /dev/null +++ b/lib/feature/book/ui/view/book_list_view.dart @@ -0,0 +1,604 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:responsive_framework/responsive_framework.dart'; +import 'package:tele_book/common/widget/local_image_widget.dart'; +import 'package:tele_book/core/route/app_route.dart'; +import 'package:tele_book/feature/book/enum/book_menu_type.dart'; +import 'package:tele_book/feature/book/enum/book_sort.dart'; +import 'package:tele_book/feature/book/model/state/book_list_state.dart'; +import 'package:tele_book/feature/book/ui/provider/book_provider.dart'; +import 'package:tele_book/feature/download/ui/provider/download_provider.dart'; +import 'package:tele_book/feature/export/ui/view/export_batch_form_view.dart'; +import 'package:tele_book/feature/export/ui/view/export_single_form_view.dart'; + +// ── 根页面 ───────────────────────────────────────────────────── + +class BookListView extends ConsumerStatefulWidget { + const BookListView({super.key}); + + @override + ConsumerState createState() => _BookListViewState(); +} + +class _BookListViewState extends ConsumerState { + final ScrollController _scrollController = ScrollController(); + + @override + void initState() { + super.initState(); + _scrollController.addListener(_onScroll); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _onScroll() { + if (_scrollController.position.pixels >= + _scrollController.position.maxScrollExtent - 200) { + ref.read(bookListProvider.notifier).loadNextPage(); + } + } + + @override + Widget build(BuildContext context) { + final bookState = ref.watch(bookListProvider); + final isSelectionMode = bookState.value?.isSelectionMode ?? false; + final layout = bookState.value?.layout ?? BookLayout.list; + + return PopScope( + canPop: !isSelectionMode, + onPopInvokedWithResult: (didPop, _) { + if (!didPop && isSelectionMode) { + ref.read(bookListProvider.notifier).exitSelectionMode(); + } + }, + child: Scaffold( + appBar: isSelectionMode + ? _buildSelectionAppBar(context) + : _buildNormalAppBar(context), + body: bookState.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (e, _) => Center(child: Text('加载失败: $e')), + data: (state) { + if (state.bookVos.isEmpty) return _buildEmpty(); + return layout == BookLayout.list + ? _BookListContent( + bookVos: state.bookVos, + isLoadingMore: state.isLoadingMore, + scrollController: _scrollController, + ) + : _BookGridContent( + bookVos: state.bookVos, + isLoadingMore: state.isLoadingMore, + scrollController: _scrollController, + ); + }, + ), + bottomNavigationBar: + isSelectionMode ? _buildSelectionBottomBar(context) : null, + ), + ); + } + + Widget _buildEmpty() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.library_books_outlined, size: 64, color: Colors.grey[400]), + const SizedBox(height: 16), + Text('暂无书籍', + style: TextStyle(fontSize: 16, color: Colors.grey[600])), + ], + ), + ); + } + + AppBar _buildNormalAppBar(BuildContext context) { + final bookVos = + ref.watch(bookListProvider.select((s) => s.value?.bookVos ?? [])); + final taskCount = ref.watch(downloadTasksProvider).value?.length ?? 0; + final state = ref.watch(bookListProvider).value; + + return AppBar( + title: const Text('书籍'), + elevation: 0, + actions: [ + SearchAnchor( + builder: (context, controller) => IconButton( + onPressed: controller.openView, + icon: const Icon(Icons.search), + ), + suggestionsBuilder: (context, searchController) { + final query = searchController.text.toLowerCase(); + final results = + bookVos.where((vo) => vo.book.name.toLowerCase().contains(query)); + return results.map( + (vo) => ListTile( + leading: LocalImageWidget(imagePath: vo.coverImagePath), + title: Text(vo.book.name), + onTap: () { + searchController.closeView(vo.book.name); + context.push(AppRoute.bookPage, extra: vo.book); + }, + ), + ); + }, + ), + if (taskCount > 0) + IconButton( + onPressed: () => context.push(AppRoute.download), + icon: Badge( + label: Text('$taskCount'), + child: const Icon(Icons.download), + ), + ), + _buildTopMenuButton(context, state), + ], + ); + } + + AppBar _buildSelectionAppBar(BuildContext context) { + final selectedCount = ref.watch( + bookListProvider.select((s) => s.value?.selectedBookIds.length ?? 0), + ); + final notifier = ref.read(bookListProvider.notifier); + + return AppBar( + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: notifier.exitSelectionMode, + ), + title: Text('已选 $selectedCount 本'), + actions: [ + TextButton(onPressed: notifier.selectAll, child: const Text('全选')), + ], + ); + } + + Widget _buildSelectionBottomBar(BuildContext context) { + final selectedBooks = ref.watch( + bookListProvider.select((s) => s.value?.selectedBooks ?? []), + ); + + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: selectedBooks.isNotEmpty + ? Row( + children: [ + IconButton( + tooltip: '导出选中', + onPressed: () => _onExportSelected(context), + icon: Icon(Icons.move_to_inbox, + color: Theme.of(context).colorScheme.primary), + ), + IconButton( + tooltip: '删除选中', + onPressed: () => _deleteSelected(context), + icon: Icon(Icons.delete, + color: Theme.of(context).colorScheme.error), + ), + ], + ) + : const SizedBox.shrink(), + ), + ); + } + + Widget _buildTopMenuButton(BuildContext context, BookListState? state) { + return PopupMenuButton( + onSelected: (type) => _onTopMenuSelected(type), + itemBuilder: (context) => BookTopMenuType.values.map((type) { + return PopupMenuItem( + value: type, + child: Row( + children: [ + Icon(type.icon, size: 16, color: Colors.grey[600]), + const SizedBox(width: 8), + Text(type.title), + const SizedBox(width: 8), + if (type == BookTopMenuType.asc && + state?.sort?.order == BookSortOrder.asc) + const Icon(Icons.check, size: 16), + if (type == BookTopMenuType.desc && + state?.sort?.order == BookSortOrder.desc) + const Icon(Icons.check, size: 16), + if (type == BookTopMenuType.name && + state?.sort?.type == BookSortType.title) + const Icon(Icons.check, size: 16), + if (type == BookTopMenuType.lastCreatedAt && + state?.sort?.type == BookSortType.lastCreatedAt) + const Icon(Icons.check, size: 16), + if (type == BookTopMenuType.list && + state?.layout == BookLayout.list) + const Icon(Icons.check, size: 16), + if (type == BookTopMenuType.grid && + state?.layout == BookLayout.grid) + const Icon(Icons.check, size: 16), + ], + ), + ); + }).toList(), + ); + } + + void _onTopMenuSelected(BookTopMenuType type) { + final notifier = ref.read(bookListProvider.notifier); + final current = ref.read(bookListProvider).value; + final currentSort = current?.sort ?? + BookSort(order: BookSortOrder.desc, type: BookSortType.lastCreatedAt); + + switch (type) { + case BookTopMenuType.asc: + notifier.updateSort(currentSort.copyWith(order: BookSortOrder.asc)); + case BookTopMenuType.desc: + notifier.updateSort(currentSort.copyWith(order: BookSortOrder.desc)); + case BookTopMenuType.name: + notifier.updateSort(currentSort.copyWith(type: BookSortType.title)); + case BookTopMenuType.lastCreatedAt: + notifier + .updateSort(currentSort.copyWith(type: BookSortType.lastCreatedAt)); + case BookTopMenuType.list: + if (current?.layout != BookLayout.list) notifier.toggleLayout(); + case BookTopMenuType.grid: + if (current?.layout != BookLayout.grid) notifier.toggleLayout(); + } + } + + void _onExportSelected(BuildContext context) { + final selectedBooks = + ref.read(bookListProvider).value?.selectedBooks ?? []; + if (selectedBooks.isEmpty) return; + Navigator.of(context).push(MaterialPageRoute( + builder: (_) => + ExportBatchFormView(books: selectedBooks.map((v) => v.book).toList()), + )); + } + + Future _deleteSelected(BuildContext context) async { + final count = + ref.read(bookListProvider).value?.selectedBookIds.length ?? 0; + final confirmed = await showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text('确认删除'), + content: Text('将删除选中的 $count 本书籍,删除后不可恢复,是否继续?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('取消')), + ElevatedButton( + onPressed: () => Navigator.pop(context, true), + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.error), + child: const Text('删除', style: TextStyle(color: Colors.white)), + ), + ], + ), + ); + if (confirmed == true && context.mounted) { + await ref.read(bookListProvider.notifier).deleteSelected(); + } + } +} + +// ── 列表布局 ─────────────────────────────────────────────────── + +class _BookListContent extends ConsumerWidget { + final List bookVos; + final bool isLoadingMore; + final ScrollController scrollController; + + const _BookListContent({ + required this.bookVos, + required this.isLoadingMore, + required this.scrollController, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final itemCount = bookVos.length + (isLoadingMore ? 1 : 0); + return ListView.separated( + controller: scrollController, + padding: const EdgeInsets.symmetric(vertical: 8), + separatorBuilder: (_, __) => const SizedBox(height: 4), + itemCount: itemCount, + itemBuilder: (context, index) { + if (index == bookVos.length) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 16), + child: Center(child: CircularProgressIndicator(strokeWidth: 2)), + ); + } + return _BookListTile(book: bookVos[index]); + }, + ); + } +} + +class _BookListTile extends ConsumerWidget { + final BookListItemVo book; + + const _BookListTile({required this.book}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isSelectionMode = ref.watch( + bookListProvider.select((s) => s.value?.isSelectionMode ?? false)); + final isSelected = ref.watch(bookListProvider.select( + (s) => s.value?.selectedBookIds.contains(book.book.id) ?? false)); + final notifier = ref.read(bookListProvider.notifier); + + return GestureDetector( + onTap: () => isSelectionMode + ? notifier.toggleSelection(book.book.id) + : context.push(AppRoute.bookPage, extra: book.book), + onLongPress: () { + if (!isSelectionMode) notifier.enterSelectionMode(book.book); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + children: [ + if (isSelectionMode) + Padding( + padding: const EdgeInsets.only(right: 8), + child: Checkbox( + value: isSelected, + onChanged: (_) => notifier.toggleSelection(book.book.id), + ), + ), + LocalImageWidget(imagePath: book.coverImagePath), + Expanded( + child: ListTile( + title: Text(book.book.name, + maxLines: 2, overflow: TextOverflow.ellipsis), + subtitle: Text('共 ${book.book.localSubPaths.length} 页'), + ), + ), + if (!isSelectionMode) _buildItemMenu(context, ref), + ], + ), + ), + ); + } + + Widget _buildItemMenu(BuildContext context, WidgetRef ref) { + return PopupMenuButton( + onSelected: (type) => _onItemSelected(context, ref, type), + itemBuilder: (context) => BookItemMenuType.values + .map((type) => PopupMenuItem( + value: type, + child: Row(children: [ + Icon(type.icon, size: 16, color: Colors.grey[600]), + const SizedBox(width: 8), + Text(type.title), + ]), + )) + .toList(), + ); + } + + void _onItemSelected( + BuildContext context, WidgetRef ref, BookItemMenuType type) { + switch (type) { + case BookItemMenuType.export: + Navigator.of(context).push(MaterialPageRoute( + builder: (_) => ExportSingleFormView(book: book.book))); + case BookItemMenuType.delete: + _confirmDelete(context, ref); + } + } + + Future _confirmDelete(BuildContext context, WidgetRef ref) async { + final ok = await showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text('确认删除'), + content: Text('删除《${book.book.name}》后不可恢复,是否继续?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('取消')), + ElevatedButton( + onPressed: () => Navigator.pop(context, true), + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.error), + child: const Text('删除', style: TextStyle(color: Colors.white)), + ), + ], + ), + ); + if (ok == true && context.mounted) { + await ref.read(bookListProvider.notifier).deleteBook(book.book.id); + } + } +} + +// ── 网格布局 ─────────────────────────────────────────────────── + +class _BookGridContent extends ConsumerWidget { + final List bookVos; + final bool isLoadingMore; + final ScrollController scrollController; + + const _BookGridContent({ + required this.bookVos, + required this.isLoadingMore, + required this.scrollController, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isTablet = ResponsiveBreakpoints.of(context).isTablet; + final gridDelegate = isTablet + ? const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 180, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + childAspectRatio: 0.65, + ) + : const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + childAspectRatio: 0.65, + ); + + final itemCount = bookVos.length + (isLoadingMore ? 1 : 0); + return GridView.builder( + controller: scrollController, + padding: const EdgeInsets.all(12), + gridDelegate: gridDelegate, + itemCount: itemCount, + itemBuilder: (context, index) { + if (index == bookVos.length) { + return const Center(child: CircularProgressIndicator(strokeWidth: 2)); + } + return _BookGridTile(book: bookVos[index]); + }, + ); + } +} + +class _BookGridTile extends ConsumerWidget { + final BookListItemVo book; + + const _BookGridTile({required this.book}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isSelectionMode = ref.watch( + bookListProvider.select((s) => s.value?.isSelectionMode ?? false)); + final isSelected = ref.watch(bookListProvider.select( + (s) => s.value?.selectedBookIds.contains(book.book.id) ?? false)); + final notifier = ref.read(bookListProvider.notifier); + + return GestureDetector( + onTap: () => isSelectionMode + ? notifier.toggleSelection(book.book.id) + : context.push(AppRoute.bookPage, extra: book.book), + onLongPress: () => isSelectionMode + ? _showItemMenu(context, ref) + : notifier.enterSelectionMode(book.book), + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: book.coverImagePath.isNotEmpty + ? Image.file( + File(book.coverImagePath), + width: double.infinity, + fit: BoxFit.cover, + cacheWidth: 300, + errorBuilder: (_, __, ___) => _placeholder(), + ) + : _placeholder(), + ), + ), + const SizedBox(height: 4), + Text(book.book.name, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 12)), + Text('${book.book.localSubPaths.length} 页', + style: TextStyle(fontSize: 11, color: Colors.grey[600])), + ], + ), + if (isSelectionMode) + Positioned( + top: 4, + right: 4, + child: Container( + decoration: BoxDecoration( + color: isSelected + ? Theme.of(context).colorScheme.primary + : Colors.white.withValues(alpha: 0.8), + shape: BoxShape.circle, + border: Border.all( + color: Theme.of(context).colorScheme.primary, width: 2), + ), + child: isSelected + ? const Icon(Icons.check, color: Colors.white, size: 18) + : const SizedBox(width: 18, height: 18), + ), + ), + ], + ), + ); + } + + Widget _placeholder() => Container( + width: double.infinity, + height: double.infinity, + color: Colors.grey[200], + child: Icon(Icons.book, color: Colors.grey[400], size: 40), + ); + + void _showItemMenu(BuildContext context, WidgetRef ref) { + showModalBottomSheet( + context: context, + builder: (_) => SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: BookItemMenuType.values + .map((type) => ListTile( + leading: Icon(type.icon), + title: Text(type.title), + onTap: () { + Navigator.pop(context); + _onItemSelected(context, ref, type); + }, + )) + .toList(), + ), + ), + ); + } + + void _onItemSelected( + BuildContext context, WidgetRef ref, BookItemMenuType type) { + switch (type) { + case BookItemMenuType.export: + Navigator.of(context).push(MaterialPageRoute( + builder: (_) => ExportSingleFormView(book: book.book))); + case BookItemMenuType.delete: + _confirmDelete(context, ref); + } + } + + Future _confirmDelete(BuildContext context, WidgetRef ref) async { + final ok = await showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text('确认删除'), + content: Text('删除《${book.book.name}》后不可恢复,是否继续?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('取消')), + ElevatedButton( + onPressed: () => Navigator.pop(context, true), + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.error), + child: const Text('删除', style: TextStyle(color: Colors.white)), + ), + ], + ), + ); + if (ok == true && context.mounted) { + await ref.read(bookListProvider.notifier).deleteBook(book.book.id); + } + } +} diff --git a/lib/feature/book/ui/view/book_view.dart b/lib/feature/book/ui/view/book_view.dart deleted file mode 100644 index 79b4f7c..0000000 --- a/lib/feature/book/ui/view/book_view.dart +++ /dev/null @@ -1,458 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:provider/provider.dart'; -import 'package:responsive_framework/responsive_framework.dart'; -import 'package:tele_book/common/widget/local_image_widget.dart'; -import 'package:tele_book/core/db/app_database.dart'; -import 'package:tele_book/core/route/app_route.dart'; -import 'package:tele_book/feature/book/enum/book_sort.dart'; -import 'package:tele_book/feature/book/model/vo/book_vo.dart'; -import 'package:tele_book/feature/book/store/book_store.dart'; -import 'package:tele_book/feature/book/ui/viewmodel/book_viewmodel.dart'; - -import 'package:tele_book/feature/download/store/download_store.dart'; - -class BookView extends StatelessWidget { - const BookView({super.key}); - - @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (context) => BookViewmodel(context.read(), context.read()), - child: _BookViewContent(), - ); - } -} - -class _BookViewContent extends StatelessWidget { - @override - Widget build(BuildContext context) { - final bookStore = context.watch(); - final downloadStore = context.watch(); - final vm = context.watch(); - - return PopScope( - canPop: !vm.isSelectionMode, - onPopInvokedWithResult: (didPop, _) { - if (!didPop && vm.isSelectionMode) vm.exitSelectionMode(); - }, - child: Scaffold( - appBar: vm.isSelectionMode - ? _buildSelectionAppBar(context, vm) - : _buildNormalAppBar(context, vm, bookStore, downloadStore), - body: bookStore.books.isEmpty && !bookStore.isLoading - ? _buildEmpty() - : vm.layout == BookLayout.list - ? _BookListView(vm: vm) - : _BookGridView(vm: vm), - bottomNavigationBar: vm.isSelectionMode - ? _buildSelectionBottomBar(context, vm) - : null, - ), - ); - } - - Widget _buildEmpty() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.library_books_outlined, size: 64, color: Colors.grey[400]), - const SizedBox(height: 16), - Text('暂无书籍', style: TextStyle(fontSize: 16, color: Colors.grey[600])), - ], - ), - ); - } - - AppBar _buildNormalAppBar( - BuildContext context, - BookViewmodel vm, - BookStore bookStore, - DownloadStore downloadStore, - ) { - return AppBar( - title: const Text('书籍'), - elevation: 0, - actions: [ - SearchAnchor( - builder: (context, controller) { - return IconButton( - onPressed: controller.openView, - icon: const Icon(Icons.search), - ); - }, - suggestionsBuilder: (context, searchController) { - final results = bookStore.books.where( - (book) => book.book.name.toLowerCase().contains( - searchController.text.toLowerCase(), - ), - ); - return results.map((book) => _BookListTile(book: book, vm: vm)); - }, - ), - - downloadStore.tasks.isNotEmpty - ? IconButton( - onPressed: () => context.push(AppRoute.download), - icon: Badge( - label: Text('${downloadStore.tasks.length}'), - child: const Icon(Icons.download), - ), - ) - : const SizedBox.shrink(), - _buildTopMenuButton(context, vm, bookStore), - ], - ); - } - - AppBar _buildSelectionAppBar(BuildContext context, BookViewmodel vm) { - return AppBar( - leading: IconButton( - icon: const Icon(Icons.close), - onPressed: vm.exitSelectionMode, - ), - title: Text('已选 ${vm.selectedBookIds.length} 本'), - actions: [TextButton(onPressed: vm.selectAll, child: const Text('全选'))], - ); - } - - Widget _buildSelectionBottomBar(BuildContext context, BookViewmodel vm) { - return SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: vm.selectedBooks.isNotEmpty - ? Row( - children: [ - IconButton( - onPressed: () { - vm.onExportSelected(context); - }, - icon: Icon( - Icons.move_to_inbox, - color: Theme.of(context).colorScheme.primary, - ), - ), - IconButton( - onPressed: () { - vm.deleteSelected(context); - }, - icon: Icon( - Icons.delete, - color: Theme.of(context).colorScheme.error, - ), - ), - ], - ) - : SizedBox.shrink(), - ), - ); - } - - Widget _buildTopMenuButton( - BuildContext context, - BookViewmodel vm, - BookStore bookStore, - ) { - return PopupMenuButton( - onSelected: (value) => vm.onTopMenuSelected(context, value), - itemBuilder: (context) => [ - ...BookTopMenuType.values.map((type) { - return PopupMenuItem( - value: type, - child: Row( - children: [ - Icon(type.icon, size: 16, color: Colors.grey[600]), - SizedBox(width: 8), - Text(type.title), - const SizedBox(width: 8), - if (type == BookTopMenuType.asc && - bookStore.sort.order == BookSortOrder.asc) - const Icon(Icons.check, size: 16), - if (type == BookTopMenuType.desc && - bookStore.sort.order == BookSortOrder.desc) - const Icon(Icons.check, size: 16), - if (type == BookTopMenuType.name && - bookStore.sort.type == BookSortType.title) - const Icon(Icons.check, size: 16), - if (type == BookTopMenuType.lastCreatedAt && - bookStore.sort.type == BookSortType.lastCreatedAt) - const Icon(Icons.check, size: 16), - if (type == BookTopMenuType.list && - vm.layout == BookLayout.list) - const Icon(Icons.check, size: 16), - if (type == BookTopMenuType.grid && - vm.layout == BookLayout.grid) - const Icon(Icons.check, size: 16), - ], - ), - ); - }), - ], - ); - } -} - -// ── 列表布局 ────────────────────────────────────────────── -class _BookListView extends StatelessWidget { - final BookViewmodel vm; - - const _BookListView({required this.vm}); - - @override - Widget build(BuildContext context) { - final bookStore = context.watch(); - final itemCount = bookStore.books.length + (bookStore.isLoading ? 1 : 0); - - return ListView.separated( - controller: vm.scrollController, - padding: const EdgeInsets.symmetric(vertical: 8), - separatorBuilder: (_, __) => const SizedBox(height: 4), - itemCount: itemCount, - itemBuilder: (context, index) { - if (index == bookStore.books.length) { - return const Padding( - padding: EdgeInsets.symmetric(vertical: 16), - child: Center(child: CircularProgressIndicator(strokeWidth: 2)), - ); - } - final book = bookStore.books[index]; - return _BookListTile(book: book, vm: vm); - }, - ); - } -} - -class _BookListTile extends StatelessWidget { - final BookListItemVo book; - final BookViewmodel vm; - - const _BookListTile({required this.book, required this.vm}); - - @override - Widget build(BuildContext context) { - final isSelected = vm.selectedBookIds.contains(book.book.id); - - return GestureDetector( - onTap: () { - if (vm.isSelectionMode) { - vm.toggleSelection(book.book.id); - } else { - context.push(AppRoute.bookPage, extra: book.book); - } - }, - onLongPress: () { - if (!vm.isSelectionMode) vm.enterSelectionMode(book.book); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Row( - children: [ - if (vm.isSelectionMode) - Padding( - padding: const EdgeInsets.only(right: 8), - child: Checkbox( - value: isSelected, - onChanged: (_) => vm.toggleSelection(book.book.id), - ), - ), - LocalImageWidget(imagePath: book.coverImagePath), - Expanded( - child: ListTile( - title: Text( - book.book.name, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - subtitle: Text('共 ${book.book.localSubPaths.length} 页'), - ), - ), - if (!vm.isSelectionMode) - _buildItemMenuButton(context, vm, book.book), - ], - ), - ), - ); - } - - Widget _buildItemMenuButton( - BuildContext context, - BookViewmodel vm, - BookTableData book, - ) { - return PopupMenuButton( - onSelected: (value) => vm.onItemMenuSelected(context, value, book), - itemBuilder: (context) => BookItemMenuType.values - .map( - (type) => PopupMenuItem( - value: type, - child: Row( - children: [ - Icon(type.icon, size: 16, color: Colors.grey[600]), - SizedBox(width: 8), - Text(type.title), - ], - ), - ), - ) - .toList(), - ); - } -} - -// ── 网格布局 ────────────────────────────────────────────── -class _BookGridView extends StatelessWidget { - final BookViewmodel vm; - - const _BookGridView({required this.vm}); - - @override - Widget build(BuildContext context) { - final bookStore = context.watch(); - - final isTablet = ResponsiveBreakpoints.of(context).isTablet; - - final gridDelegate = isTablet - ? const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 180, - crossAxisSpacing: 12, - mainAxisSpacing: 12, - childAspectRatio: 0.65, - ) - : const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 12, - mainAxisSpacing: 12, - childAspectRatio: 0.65, - ); - - return GridView.builder( - controller: vm.scrollController, - padding: const EdgeInsets.all(12), - gridDelegate: gridDelegate, - itemCount: bookStore.books.length + (bookStore.isLoading ? 1 : 0), - itemBuilder: (context, index) { - if (index == bookStore.books.length) { - return const Center(child: CircularProgressIndicator(strokeWidth: 2)); - } - final book = bookStore.books[index]; - return _BookGridTile(book: book, vm: vm); - }, - ); - } -} - -class _BookGridTile extends StatelessWidget { - final BookListItemVo book; - final BookViewmodel vm; - - const _BookGridTile({required this.book, required this.vm}); - - @override - Widget build(BuildContext context) { - final isSelected = vm.selectedBookIds.contains(book.book.id); - - return GestureDetector( - onTap: () { - if (vm.isSelectionMode) { - vm.toggleSelection(book.book.id); - } else { - context.push(AppRoute.bookPage, extra: book.book); - } - }, - onLongPress: () { - if (vm.isSelectionMode) { - _showItemMenu(context); - } else { - vm.enterSelectionMode(book.book); - } - }, - child: Stack( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: book.coverImagePath.isNotEmpty - ? Image.file( - File(book.coverImagePath), - width: double.infinity, - fit: BoxFit.cover, - cacheWidth: 300, - errorBuilder: (_, __, ___) => _placeholder(), - ) - : _placeholder(), - ), - ), - const SizedBox(height: 4), - Text( - book.book.name, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 12), - ), - Text( - '${book.book.localSubPaths.length} 页', - style: TextStyle(fontSize: 11, color: Colors.grey[600]), - ), - ], - ), - // 选择模式时显示选中指示器 - if (vm.isSelectionMode) - Positioned( - top: 4, - right: 4, - child: Container( - decoration: BoxDecoration( - color: isSelected - ? Theme.of(context).colorScheme.primary - : Colors.white.withValues(alpha: 0.8), - shape: BoxShape.circle, - border: Border.all( - color: Theme.of(context).colorScheme.primary, - width: 2, - ), - ), - child: isSelected - ? const Icon(Icons.check, color: Colors.white, size: 18) - : const SizedBox(width: 18, height: 18), - ), - ), - ], - ), - ); - } - - Widget _placeholder() => Container( - width: double.infinity, - height: double.infinity, - color: Colors.grey[200], - child: Icon(Icons.book, color: Colors.grey[400], size: 40), - ); - - void _showItemMenu(BuildContext context) { - showModalBottomSheet( - context: context, - builder: (_) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: BookItemMenuType.values - .map( - (type) => ListTile( - title: Text(type.title), - onTap: () { - Navigator.pop(context); - vm.onItemMenuSelected(context, type, book.book); - }, - ), - ) - .toList(), - ), - ), - ); - } -} diff --git a/lib/feature/book/ui/viewmodel/book_form_viewmodel.dart b/lib/feature/book/ui/viewmodel/book_form_viewmodel.dart deleted file mode 100644 index 5fb21f8..0000000 --- a/lib/feature/book/ui/viewmodel/book_form_viewmodel.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:go_router/go_router.dart'; -import 'package:tele_book/common/config/global_config.dart'; -import 'package:tele_book/core/db/app_database.dart'; -import 'package:tele_book/core/route/app_route.dart'; -import 'package:tele_book/feature/book/repository/book_repository.dart'; - -class BookFormViewmodel extends ChangeNotifier { - final BookTableData book; - final BookRepository _bookRepository; - final List imagePaths = []; - final TextEditingController titleController = TextEditingController(); - - BookFormViewmodel(this.book, this._bookRepository) { - titleController.text = book.name; - for (final subPath in book.localSubPaths) { - imagePaths.add(BookFormPath(GlobalConfig.booksDir.path, subPath)); - } - } - - Future deleteImage(BookFormPath path) async { - imagePaths.remove(path); - notifyListeners(); - } - - void reorderImages(int oldIndex, int newIndex) { - if (oldIndex < 0 || oldIndex >= imagePaths.length) return; - if (newIndex < 0 || newIndex > imagePaths.length) return; - if (oldIndex == newIndex) return; - - if (oldIndex < newIndex) { - newIndex -= 1; - } - - final item = imagePaths.removeAt(oldIndex); - imagePaths.insert(newIndex, item); - notifyListeners(); - } - - Future updateBook(BuildContext context) async { - final updatedBook = book.copyWith( - name: titleController.text, - localSubPaths: imagePaths.map((p) => p.subPath).toList(), - ); - await _bookRepository.updateBook(updatedBook); - context.go(AppRoute.main); - } -} - -class BookFormPath { - final String parentPath; - final String subPath; - - BookFormPath(this.parentPath, this.subPath); - - String get fullPath => '$parentPath/$subPath'; -} diff --git a/lib/feature/book/ui/viewmodel/book_page_viewmodel.dart b/lib/feature/book/ui/viewmodel/book_page_viewmodel.dart deleted file mode 100644 index 96ea4bc..0000000 --- a/lib/feature/book/ui/viewmodel/book_page_viewmodel.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:tele_book/common/config/global_config.dart'; -import 'package:tele_book/core/db/app_database.dart'; -import 'package:tele_book/feature/book/service/book_service.dart'; - -class BookPageViewmodel extends ChangeNotifier { - final BookTableData book; - final BookService bookService; - List paths = []; - String title = ""; - bool isShowBar = false; - int currentPage = 0; - late final PageController pageController; - - BookPageViewmodel({required this.book, required this.bookService}) { - paths = book.localSubPaths - .map((rel) => GlobalConfig.resolveBookPath(rel)) - .toList(); - title = book.name; - currentPage = book.currentPage.clamp( - 0, - paths.isEmpty ? 0 : paths.length - 1, - ); - pageController = PageController(initialPage: currentPage); - } - - double get progress => paths.isEmpty ? 0.0 : (currentPage + 1) / paths.length; - - void onPageChanged(int index) { - currentPage = index; - notifyListeners(); - } - - void jumpToPage(int index) { - if (paths.isEmpty) return; - final target = index.clamp(0, paths.length - 1); - pageController.jumpToPage(target); - } - - void toggleBar() { - isShowBar = !isShowBar; - notifyListeners(); - } - - void nextPage() { - if (currentPage < paths.length - 1) { - currentPage++; - notifyListeners(); - } - } - - void prevPage() { - if (currentPage > 0) { - currentPage--; - notifyListeners(); - } - } - - @override - void dispose() { - pageController.dispose(); - super.dispose(); - } -} diff --git a/lib/feature/book/ui/viewmodel/book_viewmodel.dart b/lib/feature/book/ui/viewmodel/book_viewmodel.dart deleted file mode 100644 index 92030e8..0000000 --- a/lib/feature/book/ui/viewmodel/book_viewmodel.dart +++ /dev/null @@ -1,229 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:tele_book/core/db/app_database.dart'; -import 'package:tele_book/core/route/app_route.dart'; -import 'package:tele_book/feature/book/enum/book_sort.dart'; -import 'package:tele_book/feature/book/model/vo/book_vo.dart'; -import 'package:tele_book/feature/book/repository/book_repository.dart'; -import 'package:tele_book/feature/book/store/book_store.dart'; - -enum BookLayout { list, grid } - -class BookViewmodel extends ChangeNotifier { - final BookRepository _bookRepository; - final BookStore bookStore; - final ScrollController scrollController = ScrollController(); - bool _isViewportCheckScheduled = false; - - BookLayout layout = BookLayout.list; - - // ── 批量选择模式 ────────────────────────────────────────── - bool isSelectionMode = false; - final Set selectedBookIds = {}; - - List get selectedBooks => bookStore.books - .where((b) => selectedBookIds.contains(b.book.id)) - .toList(); - - void enterSelectionMode(BookTableData book) { - isSelectionMode = true; - selectedBookIds.add(book.id); - notifyListeners(); - } - - void exitSelectionMode() { - isSelectionMode = false; - selectedBookIds.clear(); - notifyListeners(); - } - - void toggleSelection(int bookId) { - if (selectedBookIds.contains(bookId)) { - selectedBookIds.remove(bookId); - } else { - selectedBookIds.add(bookId); - } - notifyListeners(); - } - - void selectAll() { - selectedBookIds.addAll(bookStore.books.map((b) => b.book.id)); - notifyListeners(); - } - - void onExportSelected(BuildContext context) { - final books = selectedBooks.map((v) => v.book).toList(); - exitSelectionMode(); - context.push(AppRoute.exportBatch, extra: books); - } - - void deleteSelected(BuildContext context) { - showAdaptiveDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('删除书籍'), - content: Text('确定要删除这${selectedBooks.length}本书吗?'), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('取消'), - ), - TextButton( - onPressed: () { - for (var book in selectedBooks) { - _bookRepository.deleteBook(book.book.id); - } - Navigator.of(context).pop(); - }, - child: const Text('删除'), - ), - ], - ); - }, - ); - } - - // ── 滚动 ───────────────────────────────────────────────── - - BookViewmodel(this._bookRepository, this.bookStore) { - scrollController.addListener(_onScroll); - bookStore.addListener(_onBookStoreChanged); - _scheduleViewportCheck(); - } - - void _onScroll() { - if (!scrollController.hasClients || - bookStore.isLoading || - !bookStore.hasMore) { - return; - } - final pos = scrollController.position; - if (pos.maxScrollExtent <= 0 || pos.pixels >= pos.maxScrollExtent - 200) { - bookStore.loadMore(); - } - } - - void _onBookStoreChanged() { - _scheduleViewportCheck(); - } - - void _scheduleViewportCheck() { - if (_isViewportCheckScheduled) return; - _isViewportCheckScheduled = true; - WidgetsBinding.instance.addPostFrameCallback((_) { - _isViewportCheckScheduled = false; - _onScroll(); - }); - } - - void toggleLayout() { - layout = layout == BookLayout.list ? BookLayout.grid : BookLayout.list; - notifyListeners(); - } - - @override - void dispose() { - scrollController.removeListener(_onScroll); - bookStore.removeListener(_onBookStoreChanged); - scrollController.dispose(); - super.dispose(); - } - - void onTopMenuSelected(BuildContext context, BookTopMenuType type) { - switch (type) { - case BookTopMenuType.add: - context.push(AppRoute.parseForm); - break; - case BookTopMenuType.desc: - bookStore.updateSort( - bookStore.sort.copyWith(order: BookSortOrder.desc), - ); - break; - case BookTopMenuType.asc: - bookStore.updateSort(bookStore.sort.copyWith(order: BookSortOrder.asc)); - break; - case BookTopMenuType.name: - bookStore.updateSort(bookStore.sort.copyWith(type: BookSortType.title)); - break; - case BookTopMenuType.lastCreatedAt: - bookStore.updateSort( - bookStore.sort.copyWith(type: BookSortType.lastCreatedAt), - ); - break; - case BookTopMenuType.list: - layout = BookLayout.list; - notifyListeners(); - break; - case BookTopMenuType.grid: - layout = BookLayout.grid; - notifyListeners(); - break; - } - } - - void onItemMenuSelected( - BuildContext context, - BookItemMenuType type, - BookTableData book, - ) { - switch (type) { - case BookItemMenuType.edit: - context.push(AppRoute.bookForm, extra: book); - break; - case BookItemMenuType.export: - context.push(AppRoute.exportSingle, extra: book); - break; - case BookItemMenuType.delete: - showGeneralDialog( - context: context, - pageBuilder: (_, __, ___) { - return AlertDialog( - title: const Text('删除书籍'), - content: const Text('确定要删除这本书吗?'), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('取消'), - ), - TextButton( - onPressed: () { - _bookRepository.deleteBook(book.id); - Navigator.of(context).pop(); - }, - child: const Text('删除'), - ), - ], - ); - }, - ); - break; - } - } -} - -enum BookTopMenuType { - add('添加', Icons.add), - desc('降序', Icons.arrow_downward), - asc('升序', Icons.arrow_upward), - name('按标题', Icons.sort_by_alpha), - lastCreatedAt('按时间', Icons.access_time), - list('列表', Icons.view_list), - grid('网格', Icons.grid_view); - - final String title; - final IconData icon; - - const BookTopMenuType(this.title, this.icon); -} - -enum BookItemMenuType { - edit('编辑', Icons.edit), - export('导出', Icons.file_download), - delete('删除', Icons.delete); - - final String title; - final IconData icon; - - const BookItemMenuType(this.title, this.icon); -} diff --git a/lib/feature/download/ui/provider/download_provider.dart b/lib/feature/download/ui/provider/download_provider.dart new file mode 100644 index 0000000..99680ad --- /dev/null +++ b/lib/feature/download/ui/provider/download_provider.dart @@ -0,0 +1,31 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:tele_book/feature/book/repository/book_repository.dart'; +import 'package:tele_book/feature/download/datasource/runtime/download_runtime_datasource.dart'; +import 'package:tele_book/feature/download/model/vo/download_vo.dart'; +import 'package:tele_book/feature/download/repository/download_repository.dart'; +import 'package:tele_book/feature/download/service/download_service.dart'; + +/// 内存数据源 —— 全局单例,生命周期同 ProviderScope +final downloadRuntimeDatasourceProvider = + Provider((ref) { + final ds = DownloadRuntimeDatasource(); + ref.onDispose(ds.dispose); + return ds; +}); + +final downloadRepositoryProvider = Provider((ref) { + return DownloadRepository(ref.watch(downloadRuntimeDatasourceProvider)); +}); + +final downloadServiceProvider = Provider((ref) { + return DownloadService( + ref.watch(downloadRepositoryProvider), + ref.watch(bookRepositoryProvider), + ); +}); + +/// 监听下载任务列表(响应式 Stream) +final downloadTasksProvider = StreamProvider>((ref) { + return ref.watch(downloadServiceProvider).watchDownloadTasks(); +}); + diff --git a/lib/feature/main/view/main_view.dart b/lib/feature/main/view/main_view.dart index 4ae51d0..bd465ae 100644 --- a/lib/feature/main/view/main_view.dart +++ b/lib/feature/main/view/main_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:tele_book/feature/book/ui/view/book_view.dart'; +import 'package:tele_book/feature/book/ui/view/book_list_view.dart'; import 'package:tele_book/feature/collection/ui/view/collection_view.dart'; import 'package:tele_book/feature/main/viewmodel/main_viewmodel.dart'; @@ -25,7 +25,7 @@ class _MainContent extends StatelessWidget { return Scaffold( body: IndexedStack( index: vm.currentIndex, - children: [BookView(), CollectionView()], + children: [BookListView(), CollectionView()], ), bottomNavigationBar: BottomNavigationBar( currentIndex: vm.currentIndex, diff --git a/lib/main.dart b/lib/main.dart index 9491136..909174d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,11 +2,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:provider/provider.dart'; import 'package:responsive_framework/responsive_framework.dart'; import 'package:tele_book/common/config/global_config.dart'; import 'package:tele_book/common/theme/app_theme.dart'; -import 'package:tele_book/core/di/app_di.dart'; import 'package:tele_book/core/route/app_route.dart'; void main() async { @@ -42,24 +40,21 @@ Future _init() async { } appProviders = ProviderScope( - child: MultiProvider( - providers: [...createAppDI()], - child: MaterialApp.router( - title: 'TeleBook', - routerConfig: AppRoute.router, - debugShowCheckedModeBanner: false, - theme: AppTheme.light, - darkTheme: AppTheme.dark, - themeMode: ThemeMode.system, - builder: (context, child) => ResponsiveBreakpoints.builder( - child: child!, - breakpoints: [ - const Breakpoint(start: 0, end: 480, name: MOBILE), - const Breakpoint(start: 481, end: 800, name: TABLET), - const Breakpoint(start: 801, end: 1200, name: DESKTOP), - const Breakpoint(start: 1201, end: double.infinity, name: '4K'), - ], - ), + child: MaterialApp.router( + title: 'TeleBook', + routerConfig: AppRoute.router, + debugShowCheckedModeBanner: false, + theme: AppTheme.light, + darkTheme: AppTheme.dark, + themeMode: ThemeMode.system, + builder: (context, child) => ResponsiveBreakpoints.builder( + child: child!, + breakpoints: [ + const Breakpoint(start: 0, end: 480, name: MOBILE), + const Breakpoint(start: 481, end: 800, name: TABLET), + const Breakpoint(start: 801, end: 1200, name: DESKTOP), + const Breakpoint(start: 1201, end: double.infinity, name: '4K'), + ], ), ), ); From 210923ba0b52fb25687a136253f2fa482c847d6f Mon Sep 17 00:00:00 2001 From: warren Date: Tue, 26 May 2026 10:40:37 +0800 Subject: [PATCH 07/16] =?UTF-8?q?=E4=BD=BF=E7=94=A8riverpod=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/feature/book/store/book_store.dart | 128 ------------- .../book/ui/provider/book_form_provider.dart | 125 +++++++++++++ .../ui/provider/book_form_provider.g.dart | 143 +++++++++++++++ .../book/ui/provider/book_page_provider.dart | 81 +++++++++ .../ui/provider/book_page_provider.g.dart | 107 +++++++++++ .../book/ui/provider/book_provider.g.dart | 54 ++++++ lib/feature/book/ui/view/book_form_view.dart | 171 +++++++++++------- lib/feature/book/ui/view/book_page_view.dart | 51 ++---- 8 files changed, 636 insertions(+), 224 deletions(-) delete mode 100644 lib/feature/book/store/book_store.dart create mode 100644 lib/feature/book/ui/provider/book_form_provider.dart create mode 100644 lib/feature/book/ui/provider/book_form_provider.g.dart create mode 100644 lib/feature/book/ui/provider/book_page_provider.dart create mode 100644 lib/feature/book/ui/provider/book_page_provider.g.dart create mode 100644 lib/feature/book/ui/provider/book_provider.g.dart diff --git a/lib/feature/book/store/book_store.dart b/lib/feature/book/store/book_store.dart deleted file mode 100644 index 7d045d2..0000000 --- a/lib/feature/book/store/book_store.dart +++ /dev/null @@ -1,128 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:tele_book/feature/book/model/vo/book_vo.dart'; -import 'package:tele_book/feature/book/enum/book_sort.dart'; -import 'package:tele_book/feature/book/service/book_service.dart'; - -class BookStore extends ChangeNotifier { - final BookService _bookService; - final List books = []; - BookSort sort = BookSort( - order: BookSortOrder.desc, - type: BookSortType.lastCreatedAt, - ); - final int pageSize = 20; - - int _currentPage = 1; - String? _nameFilter; - bool _isLoading = false; - bool _hasMore = true; - - StreamSubscription? _bookSubscription; - - BookStore(this._bookService) { - _loadFirstPage(); - } - - bool get isLoading => _isLoading; - - bool get hasMore => _hasMore; - - /// 加载第一页 - void _loadFirstPage() { - _currentPage = 1; - books.clear(); - _watchBooks(page: _currentPage); - } - - /// 订阅书籍列表流 - void _watchBooks({int page = 1}) { - _bookSubscription?.cancel(); - _bookSubscription = _bookService - .watchBooks( - page: page, - pageSize: pageSize, - name: _nameFilter, - sort: sort, - ) - .listen( - (bookListVo) { - if (page == 1) { - // 第一页:清空并重新设置 - books.clear(); - books.addAll(bookListVo.bookVos); - } else { - // 后续页:追加 - books.addAll(bookListVo.bookVos); - } - - // 判断是否还有更多数据 - _hasMore = bookListVo.bookVos.length >= pageSize; - _isLoading = false; - notifyListeners(); - }, - onError: (e) { - _isLoading = false; - notifyListeners(); - }, - ); - } - - /// 加载下一页 - Future loadMore() async { - if (_isLoading || !_hasMore) return; - _isLoading = true; - notifyListeners(); - - try { - // 用非 watch 的 fetch 方法直接获取下一页,避免重复订阅 - final bookListVo = await _bookService.fetchBooks( - page: _currentPage + 1, - pageSize: pageSize, - name: _nameFilter, - sort: sort, - ); - - // 追加下一页数据 - books.addAll(bookListVo.bookVos); - - if (bookListVo.bookVos.isNotEmpty) { - _currentPage += 1; - } - - // 判断是否还有更多 - _hasMore = bookListVo.bookVos.length >= pageSize; - } catch (e) { - debugPrint('加载更多失败: $e'); - } finally { - _isLoading = false; - notifyListeners(); - } - } - - /// 搜索(重置分页) - Future search(String? name) async { - _nameFilter = name; - _loadFirstPage(); // 重载第一页 - } - - Future updateReadProgress(int bookId, int progress) async { - final book = books.firstWhere((b) => b.book.id == bookId).book; - final updatedBook = book.copyWith(currentPage: progress); - await _bookService.updateBook(updatedBook); - } - - void updateSort(BookSort newSort) { - if (sort == newSort) return; - sort = newSort; - - _loadFirstPage(); // 重载第一页 - } - - @override - void dispose() { - _bookSubscription?.cancel(); - super.dispose(); - } -} diff --git a/lib/feature/book/ui/provider/book_form_provider.dart b/lib/feature/book/ui/provider/book_form_provider.dart new file mode 100644 index 0000000..3556555 --- /dev/null +++ b/lib/feature/book/ui/provider/book_form_provider.dart @@ -0,0 +1,125 @@ +import 'package:flutter/cupertino.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:tele_book/common/config/global_config.dart'; +import 'package:tele_book/core/db/app_database.dart'; + +part 'book_form_provider.g.dart'; + +class BookFormState { + final String title; + final List imagePaths; + + BookFormState({required this.title, required this.imagePaths}); + + BookFormState copyWith({String? title, List? imagePaths}) { + return BookFormState( + title: title ?? this.title, + imagePaths: imagePaths ?? this.imagePaths, + ); + } +} + +class BookFormPath { + final String parentPath; + final String subPath; + + BookFormPath(this.parentPath, this.subPath); + + String get fullPath => '$parentPath/$subPath'; +} + +@riverpod +class BookForm extends _$BookForm { + late final TextEditingController titleController; + + @override + FutureOr build(int bookId) async { + ref.onDispose(() => titleController.dispose()); + final book = await ref + .read(databaseProvider) + .bookLocalDatasource + .getById(bookId); + if (book == null) throw Exception('书籍不存在'); + + titleController = TextEditingController(text: book.name); + + titleController.addListener((){ + if(state.hasValue){ + state = AsyncValue.data(state.value!.copyWith(title: titleController.text)); + } + }); + final imagePaths = book.localSubPaths + .map((subPath) => BookFormPath(GlobalConfig.booksDir.path, subPath)) + .toList(); + return BookFormState( + title: book.name, + imagePaths: imagePaths, + ); + } + + Future deleteImage(BookFormPath path) async { + if(!state.hasValue) return; + final current =state.requireValue; + + final updatePaths = current.imagePaths.where((p) => p.fullPath != path.fullPath).toList(); + + state = AsyncValue.data(current.copyWith(imagePaths: updatePaths)); + } + + void reorderImages(int oldIndex, int newIndex) { + if (!state.hasValue) return; + final current = state.requireValue; + + if (oldIndex < 0 || oldIndex >= current.imagePaths.length) return; + if (newIndex < 0 || newIndex > current.imagePaths.length) return; + if (oldIndex == newIndex) return; + + if (oldIndex < newIndex) { + newIndex -= 1; + } + + // 创建一个可变的新副本进行操作 + final updatedPaths = List.from(current.imagePaths); + final item = updatedPaths.removeAt(oldIndex); + updatedPaths.insert(newIndex, item); + + // 重新赋给 state 触发 UI 刷新 + state = AsyncData(current.copyWith(imagePaths: updatedPaths)); + } + +} + +@riverpod +class BookFormSubmit extends _$BookFormSubmit { + + @override + FutureOr build() => null; + + Future submit({ + required int bookId, + required String title, + required List imagePaths, + }) async { + state = const AsyncLoading(); + + state = await AsyncValue.guard(() async { + final book = await ref + .read(databaseProvider) + .bookLocalDatasource + .getById(bookId); + if (book == null) return; + final newTitle = title; + final newSubPaths = imagePaths.map((path) => path.subPath).toList(); + final updatedBook = book.copyWith( + name: newTitle, + localSubPaths: newSubPaths, + ); + await ref + .read(databaseProvider) + .bookLocalDatasource + .updateBook(updatedBook); + }); + + return !state.hasError; + } +} diff --git a/lib/feature/book/ui/provider/book_form_provider.g.dart b/lib/feature/book/ui/provider/book_form_provider.g.dart new file mode 100644 index 0000000..6b9c72d --- /dev/null +++ b/lib/feature/book/ui/provider/book_form_provider.g.dart @@ -0,0 +1,143 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'book_form_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(BookForm) +final bookFormProvider = BookFormFamily._(); + +final class BookFormProvider + extends $AsyncNotifierProvider { + BookFormProvider._({ + required BookFormFamily super.from, + required int super.argument, + }) : super( + retry: null, + name: r'bookFormProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$bookFormHash(); + + @override + String toString() { + return r'bookFormProvider' + '' + '($argument)'; + } + + @$internal + @override + BookForm create() => BookForm(); + + @override + bool operator ==(Object other) { + return other is BookFormProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$bookFormHash() => r'b794138246713fac4e945d5cee8535e25de68702'; + +final class BookFormFamily extends $Family + with + $ClassFamilyOverride< + BookForm, + AsyncValue, + BookFormState, + FutureOr, + int + > { + BookFormFamily._() + : super( + retry: null, + name: r'bookFormProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + BookFormProvider call(int bookId) => + BookFormProvider._(argument: bookId, from: this); + + @override + String toString() => r'bookFormProvider'; +} + +abstract class _$BookForm extends $AsyncNotifier { + late final _$args = ref.$arg as int; + int get bookId => _$args; + + FutureOr build(int bookId); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, BookFormState>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, BookFormState>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} + +@ProviderFor(BookFormSubmit) +final bookFormSubmitProvider = BookFormSubmitProvider._(); + +final class BookFormSubmitProvider + extends $AsyncNotifierProvider { + BookFormSubmitProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'bookFormSubmitProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$bookFormSubmitHash(); + + @$internal + @override + BookFormSubmit create() => BookFormSubmit(); +} + +String _$bookFormSubmitHash() => r'b9c0adc2639c452eecc20c65f0a7d99febd5e693'; + +abstract class _$BookFormSubmit extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, void>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, void>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/lib/feature/book/ui/provider/book_page_provider.dart b/lib/feature/book/ui/provider/book_page_provider.dart new file mode 100644 index 0000000..310e8bf --- /dev/null +++ b/lib/feature/book/ui/provider/book_page_provider.dart @@ -0,0 +1,81 @@ +import 'package:flutter/cupertino.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:tele_book/common/config/global_config.dart'; +import 'package:tele_book/core/db/app_database.dart'; +import 'package:tele_book/feature/book/ui/provider/book_provider.dart'; + +part 'book_page_provider.g.dart'; + +class BookPageState { + final BookTableData book; + final List paths; + final int currentPage; + final bool isShowBar; + + BookPageState({ + required this.book, + required this.paths, + required this.currentPage, + required this.isShowBar, + }); + + BookPageState copyWith({ + BookTableData? book, + List? paths, + int? currentPage, + bool? isShowBar, + }) { + return BookPageState( + book: book ?? this.book, + paths: paths ?? this.paths, + currentPage: currentPage ?? this.currentPage, + isShowBar: isShowBar ?? this.isShowBar, + ); + } +} + +@riverpod +class BookPage extends _$BookPage { + late final PageController pageController; + + @override + BookPageState build(int bookId) { + final book = ref + .watch(bookListProvider) + .value + ?.bookVos + .where((e) => e.book.id == bookId) + .first + .book; + if (book == null) { + throw Exception("Book not found"); + } + pageController = PageController(initialPage: book.currentPage); + ref.onDispose(() { + pageController.dispose(); + }); + + final fullPaths = book.localSubPaths + .map((e) => GlobalConfig.resolveBookPath(e)) + .toList(); + return BookPageState( + book: book, + paths: fullPaths, + currentPage: book.currentPage, + isShowBar: false, + ); + } + + void onPageChanged(int index) { + state = state.copyWith(currentPage: index); + } + + void toggleBar() { + state = state.copyWith(isShowBar: !state.isShowBar); + } + + void jumpToPage(int index) { + state = state.copyWith(currentPage: index); + pageController.jumpToPage(index); + } +} diff --git a/lib/feature/book/ui/provider/book_page_provider.g.dart b/lib/feature/book/ui/provider/book_page_provider.g.dart new file mode 100644 index 0000000..8839ee9 --- /dev/null +++ b/lib/feature/book/ui/provider/book_page_provider.g.dart @@ -0,0 +1,107 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'book_page_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(BookPage) +final bookPageProvider = BookPageFamily._(); + +final class BookPageProvider + extends $NotifierProvider { + BookPageProvider._({ + required BookPageFamily super.from, + required int super.argument, + }) : super( + retry: null, + name: r'bookPageProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$bookPageHash(); + + @override + String toString() { + return r'bookPageProvider' + '' + '($argument)'; + } + + @$internal + @override + BookPage create() => BookPage(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(BookPageState value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } + + @override + bool operator ==(Object other) { + return other is BookPageProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$bookPageHash() => r'a1503c3304f4c51ee417945992ed4de071a27061'; + +final class BookPageFamily extends $Family + with + $ClassFamilyOverride< + BookPage, + BookPageState, + BookPageState, + BookPageState, + int + > { + BookPageFamily._() + : super( + retry: null, + name: r'bookPageProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + BookPageProvider call(int bookId) => + BookPageProvider._(argument: bookId, from: this); + + @override + String toString() => r'bookPageProvider'; +} + +abstract class _$BookPage extends $Notifier { + late final _$args = ref.$arg as int; + int get bookId => _$args; + + BookPageState build(int bookId); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + BookPageState, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} diff --git a/lib/feature/book/ui/provider/book_provider.g.dart b/lib/feature/book/ui/provider/book_provider.g.dart new file mode 100644 index 0000000..4b0f466 --- /dev/null +++ b/lib/feature/book/ui/provider/book_provider.g.dart @@ -0,0 +1,54 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'book_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(BookList) +final bookListProvider = BookListProvider._(); + +final class BookListProvider + extends $AsyncNotifierProvider { + BookListProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'bookListProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$bookListHash(); + + @$internal + @override + BookList create() => BookList(); +} + +String _$bookListHash() => r'd582e10acb9b6a19e0a98a36ae1456008a03e235'; + +abstract class _$BookList extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, BookListState>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, BookListState>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/lib/feature/book/ui/view/book_form_view.dart b/lib/feature/book/ui/view/book_form_view.dart index 1c6eb74..6e25632 100644 --- a/lib/feature/book/ui/view/book_form_view.dart +++ b/lib/feature/book/ui/view/book_form_view.dart @@ -1,83 +1,124 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:tele_book/common/widget/error_widget.dart'; import 'package:tele_book/common/widget/local_image_widget.dart'; import 'package:tele_book/core/db/app_database.dart'; +import 'package:tele_book/feature/book/ui/provider/book_form_provider.dart'; -class BookFormView extends StatelessWidget { +class BookFormView extends ConsumerWidget { final BookTableData book; const BookFormView({super.key, required this.book}); @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (context) => BookFormViewmodel(book, context.read()), - child: _BookFormViewContent(), - ); - } -} + Widget build(BuildContext context, WidgetRef ref) { + final formState = ref.watch(bookFormProvider(book.id)); + final submitState = ref.watch(bookFormSubmitProvider); + + ref.listen(bookFormSubmitProvider, (previous, next) { + if (previous?.isLoading == true && next.isLoading == false) { + if (next.error != null) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text("保存失败: ${next.error}"))); + } else { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text("保存成功"))); + } + } + }); -class _BookFormViewContent extends StatelessWidget { - @override - Widget build(BuildContext context) { - final vm = context.watch(); return Scaffold( appBar: AppBar(title: Text("编辑书籍")), - body: Column( - children: [ - Padding( - padding: EdgeInsets.all(16), - child: TextField( - controller: vm.titleController, - decoration: InputDecoration(labelText: "书籍名称"), - ), + body: formState.when( + error: (error, stack) => Center( + child: CustomErrorWidget( + errorMessage: error.toString(), + stackTrace: stack, ), - Expanded( - child: ReorderableListView.builder( - onReorder: vm.reorderImages, - itemCount: vm.imagePaths.length, - itemBuilder: (context, index) { - final imagePath = vm.imagePaths[index]; - return Padding( - key: ObjectKey(imagePath), - padding: EdgeInsets.symmetric(vertical: 8.0), - child: ListTile( - leading: LocalImageWidget(imagePath: imagePath.fullPath), - title: Text("图片 ${index + 1}"), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: Icon(Icons.delete), - onPressed: () { - vm.deleteImage(imagePath); - }, + ), + loading: () => Center(child: CircularProgressIndicator()), + data: (formState) { + final formNotifier = ref.read(bookFormProvider(book.id).notifier); + final isSubmitting = submitState.isLoading; + + return Column( + children: [ + Padding( + padding: EdgeInsets.all(16), + child: TextField( + controller: formNotifier.titleController, + decoration: InputDecoration(labelText: "书籍名称"), + ), + ), + Expanded( + child: ReorderableListView.builder( + onReorder: formNotifier.reorderImages, + itemCount: formState.imagePaths.length, + itemBuilder: (context, index) { + final imagePath = formState.imagePaths[index]; + return Padding( + key: ObjectKey(imagePath), + padding: EdgeInsets.symmetric(vertical: 8.0), + child: ListTile( + leading: LocalImageWidget( + imagePath: imagePath.fullPath, ), - ReorderableDragStartListener( - index: index, - child: const Padding( - padding: EdgeInsets.symmetric(horizontal: 8), - child: Icon(Icons.drag_handle), - ), + title: Text("图片 ${index + 1}"), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.delete), + onPressed: () { + formNotifier.deleteImage(imagePath); + }, + ), + ReorderableDragStartListener( + index: index, + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: Icon(Icons.drag_handle), + ), + ), + ], ), - ], - ), - ), - ); - }, - ), - ), - Container( - padding: EdgeInsets.all(16), - width: double.infinity, - child: FilledButton( - onPressed: () { - vm.updateBook(context); - }, - child: Text("保存"), - ), - ), - ], + ), + ); + }, + ), + ), + Container( + padding: EdgeInsets.all(16), + width: double.infinity, + child: FilledButton( + onPressed: isSubmitting + ? null + : () async { + final success = await ref + .read(bookFormSubmitProvider.notifier) + .submit( + bookId: book.id, + title: formState.title, + imagePaths: formState.imagePaths, + ); + if (success && context.mounted) { + Navigator.pop(context); + } + }, + child: isSubmitting + ? SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : Text("保存"), + ), + ), + ], + ); + }, ), ); } diff --git a/lib/feature/book/ui/view/book_page_view.dart b/lib/feature/book/ui/view/book_page_view.dart index 556c23b..5609424 100644 --- a/lib/feature/book/ui/view/book_page_view.dart +++ b/lib/feature/book/ui/view/book_page_view.dart @@ -1,52 +1,41 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:tele_book/core/db/app_database.dart'; -import 'package:tele_book/feature/book/ui/viewmodel/book_page_viewmodel.dart'; +import 'package:tele_book/feature/book/ui/provider/book_page_provider.dart'; -class BookPageView extends StatelessWidget { +class BookPageView extends ConsumerWidget { final BookTableData book; const BookPageView({super.key, required this.book}); @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (context) => - BookPageViewmodel(book: book, bookService: context.read()), - child: _BookPageContent(), - ); - } -} + Widget build(BuildContext context,WidgetRef ref) { + final state =ref.watch(bookPageProvider(book.id)); + final notifier = ref.watch(bookPageProvider(book.id).notifier); -class _BookPageContent extends StatelessWidget { - const _BookPageContent({super.key}); - - @override - Widget build(BuildContext context) { final fullWidth = MediaQuery.sizeOf(context).width; final boxWidth = fullWidth / 3; - final viewmodel = context.watch(); return Scaffold( - appBar: AppBar(title: Text(viewmodel.book.name)), + appBar: AppBar(title: Text(state.book.name)), body: Stack( children: [ PageView.builder( - controller: viewmodel.pageController, - onPageChanged: viewmodel.onPageChanged, + controller: notifier.pageController, + onPageChanged: notifier.onPageChanged, itemBuilder: (context, index) { - final page = viewmodel.paths[index]; + final page = state.paths[index]; return Image.file(File(page), fit: BoxFit.contain); }, - itemCount: viewmodel.paths.length, + itemCount: state.paths.length, ), Positioned.fill( child: Align( alignment: Alignment.center, child: GestureDetector( behavior: HitTestBehavior.translucent, - onTap: viewmodel.toggleBar, + onTap: notifier.toggleBar, child: SizedBox(width: boxWidth, height: double.infinity), ), ), @@ -57,7 +46,7 @@ class _BookPageContent extends StatelessWidget { alignment: Alignment.bottomCenter, child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), - child: viewmodel.isShowBar && viewmodel.paths.isNotEmpty + child: state.isShowBar && state.paths.isNotEmpty ? Container( key: const ValueKey('progress_slider'), height: 80, @@ -67,21 +56,21 @@ class _BookPageContent extends StatelessWidget { ), child: Row( children: [ - Text('${viewmodel.currentPage + 1}'), + Text('${state.currentPage + 1}'), Expanded( child: Slider( - value: viewmodel.currentPage.toDouble(), + value: state.currentPage.toDouble(), min: 0, - max: (viewmodel.paths.length - 1).toDouble(), - divisions: viewmodel.paths.length > 1 - ? viewmodel.paths.length - 1 + max: (state.paths.length - 1).toDouble(), + divisions: state.paths.length > 1 + ? state.paths.length - 1 : 1, onChanged: (value) { - viewmodel.jumpToPage(value.toInt()); + notifier.jumpToPage(value.toInt()); }, ), ), - Text('${viewmodel.paths.length}'), + Text('${state.paths.length}'), ], ), ) From 0d3b0b966b3e0c8c1b423132dfa11bd3cb28d9c6 Mon Sep 17 00:00:00 2001 From: warren Date: Tue, 26 May 2026 17:35:34 +0800 Subject: [PATCH 08/16] =?UTF-8?q?=E4=BD=BF=E7=94=A8riverpod=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .metadata | 30 - .../book/repository/book_repository.dart | 9 +- .../book/ui/provider/book_form_provider.dart | 61 +- .../provider/book_form_provider.freezed.dart | 540 ++++++ .../ui/provider/book_form_provider.g.dart | 4 +- lib/feature/book/ui/view/book_list_view.dart | 3 + .../runtime/download_runtime_datasource.dart | 9 + .../repository/download_repository.dart | 6 + .../download/service/download_service.dart | 8 + .../ui/provider/download_provider.dart | 18 - .../parse/service/parse_archive_service.dart | 189 +- .../parse/service/parse_pdf_service.dart | 49 +- .../parse/service/parse_web_service.dart | 9 +- .../ui/provider/parse_archive_provider.dart | 117 ++ .../parse_archive_provider.freezed.dart | 806 +++++++++ .../ui/provider/parse_archive_provider.g.dart | 145 ++ .../parse_batch_archive_provider.dart | 219 +++ .../parse_batch_archive_provider.freezed.dart | 1074 +++++++++++ .../parse_batch_archive_provider.g.dart | 150 ++ .../ui/provider/parse_batch_image_folder.dart | 213 +++ .../parse_batch_image_folder.freezed.dart | 826 +++++++++ .../provider/parse_batch_image_folder.g.dart | 210 +++ .../ui/provider/parse_batch_pdf_provider.dart | 214 +++ .../parse_batch_pdf_provider.freezed.dart | 1074 +++++++++++ .../provider/parse_batch_pdf_provider.g.dart | 145 ++ .../ui/provider/parse_form_provider.dart | 270 +++ .../provider/parse_form_provider.freezed.dart | 328 ++++ .../ui/provider/parse_form_provider.g.dart | 62 + .../provider/parse_image_folder_provider.dart | 156 ++ .../parse_image_folder_provider.freezed.dart | 1074 +++++++++++ .../parse_image_folder_provider.g.dart | 196 ++ .../parse/ui/provider/parse_pdf_provider.dart | 102 ++ .../provider/parse_pdf_provider.freezed.dart | 280 +++ .../ui/provider/parse_pdf_provider.g.dart | 143 ++ .../parse/ui/provider/parse_web_provider.dart | 66 + .../provider/parse_web_provider.freezed.dart | 283 +++ .../ui/provider/parse_web_provider.g.dart | 107 ++ .../parse/ui/view/parse_archive_view.dart | 126 +- .../ui/view/parse_batch_archive_view.dart | 163 +- .../view/parse_batch_image_folder_view.dart | 150 +- .../parse/ui/view/parse_batch_pdf_view.dart | 97 +- .../parse/ui/view/parse_form_view.dart | 100 +- .../ui/view/parse_image_folder_view.dart | 116 +- lib/feature/parse/ui/view/parse_pdf_view.dart | 69 +- lib/feature/parse/ui/view/parse_web_view.dart | 65 +- .../ui/viewmodel/parse_archive_viewmodel.dart | 79 - .../parse_batch_archive_viewmodel.dart | 163 -- .../parse_batch_image_folder_viewmodel.dart | 160 -- .../viewmodel/parse_batch_pdf_viewmodel.dart | 159 -- .../ui/viewmodel/parse_form_viewmodel.dart | 254 --- .../parse_image_folder_viewmodel.dart | 100 - .../ui/viewmodel/parse_pdf_viewmodel.dart | 92 - .../ui/viewmodel/parse_web_viewmodel.dart | 56 - linux/flutter/generated_plugins.cmake | 1 + pubspec.lock | 1602 ----------------- pubspec.yaml | 2 + windows/flutter/generated_plugins.cmake | 1 + 57 files changed, 9507 insertions(+), 3243 deletions(-) delete mode 100644 .metadata create mode 100644 lib/feature/book/ui/provider/book_form_provider.freezed.dart create mode 100644 lib/feature/parse/ui/provider/parse_archive_provider.dart create mode 100644 lib/feature/parse/ui/provider/parse_archive_provider.freezed.dart create mode 100644 lib/feature/parse/ui/provider/parse_archive_provider.g.dart create mode 100644 lib/feature/parse/ui/provider/parse_batch_archive_provider.dart create mode 100644 lib/feature/parse/ui/provider/parse_batch_archive_provider.freezed.dart create mode 100644 lib/feature/parse/ui/provider/parse_batch_archive_provider.g.dart create mode 100644 lib/feature/parse/ui/provider/parse_batch_image_folder.dart create mode 100644 lib/feature/parse/ui/provider/parse_batch_image_folder.freezed.dart create mode 100644 lib/feature/parse/ui/provider/parse_batch_image_folder.g.dart create mode 100644 lib/feature/parse/ui/provider/parse_batch_pdf_provider.dart create mode 100644 lib/feature/parse/ui/provider/parse_batch_pdf_provider.freezed.dart create mode 100644 lib/feature/parse/ui/provider/parse_batch_pdf_provider.g.dart create mode 100644 lib/feature/parse/ui/provider/parse_form_provider.dart create mode 100644 lib/feature/parse/ui/provider/parse_form_provider.freezed.dart create mode 100644 lib/feature/parse/ui/provider/parse_form_provider.g.dart create mode 100644 lib/feature/parse/ui/provider/parse_image_folder_provider.dart create mode 100644 lib/feature/parse/ui/provider/parse_image_folder_provider.freezed.dart create mode 100644 lib/feature/parse/ui/provider/parse_image_folder_provider.g.dart create mode 100644 lib/feature/parse/ui/provider/parse_pdf_provider.dart create mode 100644 lib/feature/parse/ui/provider/parse_pdf_provider.freezed.dart create mode 100644 lib/feature/parse/ui/provider/parse_pdf_provider.g.dart create mode 100644 lib/feature/parse/ui/provider/parse_web_provider.dart create mode 100644 lib/feature/parse/ui/provider/parse_web_provider.freezed.dart create mode 100644 lib/feature/parse/ui/provider/parse_web_provider.g.dart delete mode 100644 lib/feature/parse/ui/viewmodel/parse_archive_viewmodel.dart delete mode 100644 lib/feature/parse/ui/viewmodel/parse_batch_archive_viewmodel.dart delete mode 100644 lib/feature/parse/ui/viewmodel/parse_batch_image_folder_viewmodel.dart delete mode 100644 lib/feature/parse/ui/viewmodel/parse_batch_pdf_viewmodel.dart delete mode 100644 lib/feature/parse/ui/viewmodel/parse_form_viewmodel.dart delete mode 100644 lib/feature/parse/ui/viewmodel/parse_image_folder_viewmodel.dart delete mode 100644 lib/feature/parse/ui/viewmodel/parse_pdf_viewmodel.dart delete mode 100644 lib/feature/parse/ui/viewmodel/parse_web_viewmodel.dart delete mode 100644 pubspec.lock diff --git a/.metadata b/.metadata deleted file mode 100644 index c96b2aa..0000000 --- a/.metadata +++ /dev/null @@ -1,30 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: "00b0c91f06209d9e4a41f71b7a512d6eb3b9c694" - channel: "stable" - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 - base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 - - platform: windows - create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 - base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/lib/feature/book/repository/book_repository.dart b/lib/feature/book/repository/book_repository.dart index 349b084..8152182 100644 --- a/lib/feature/book/repository/book_repository.dart +++ b/lib/feature/book/repository/book_repository.dart @@ -19,11 +19,13 @@ class _CopyBookArgs { final String bookId; final String destDirPath; final List srcPaths; + final Function(int current,int total)? onProgress; _CopyBookArgs({ required this.bookId, required this.destDirPath, required this.srcPaths, + this.onProgress, }); } @@ -40,6 +42,7 @@ Future> _copyBookFiles(_CopyBookArgs args) async { final fileName = i.toString().padLeft(7, '0'); await src.copy('${args.destDirPath}/$fileName'); relPaths.add('${args.bookId}/$fileName'); + args.onProgress?.call(i + 1, args.srcPaths.length); } return relPaths; } @@ -124,7 +127,10 @@ class BookRepository { } /// 保存单本书:文件复制在后台 Isolate,DB 写入在主线程 - Future> saveAsBook(SaveAsBookDto dto) async { + Future> saveAsBook( + SaveAsBookDto dto, { + Function(int current,int total)? onProgress, + }) async { final bookId = const Uuid().v4(); final destDirPath = '${GlobalConfig.booksDir.path}/$bookId'; @@ -136,6 +142,7 @@ class BookRepository { bookId: bookId, destDirPath: destDirPath, srcPaths: dto.paths, + onProgress: onProgress, ), ); diff --git a/lib/feature/book/ui/provider/book_form_provider.dart b/lib/feature/book/ui/provider/book_form_provider.dart index 3556555..4967a27 100644 --- a/lib/feature/book/ui/provider/book_form_provider.dart +++ b/lib/feature/book/ui/provider/book_form_provider.dart @@ -1,29 +1,29 @@ import 'package:flutter/cupertino.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:tele_book/common/config/global_config.dart'; import 'package:tele_book/core/db/app_database.dart'; -part 'book_form_provider.g.dart'; - -class BookFormState { - final String title; - final List imagePaths; +part 'book_form_provider.freezed.dart'; - BookFormState({required this.title, required this.imagePaths}); +part 'book_form_provider.g.dart'; - BookFormState copyWith({String? title, List? imagePaths}) { - return BookFormState( - title: title ?? this.title, - imagePaths: imagePaths ?? this.imagePaths, - ); - } +@freezed +abstract class BookFormState with _$BookFormState { + const factory BookFormState({ + required String title, + required List imagePaths, + }) = _BookFormState; } -class BookFormPath { - final String parentPath; - final String subPath; +@freezed +abstract class BookFormPath with _$BookFormPath { + const factory BookFormPath({ + required String parentPath, + required String subPath, + }) = _BookFormPath; - BookFormPath(this.parentPath, this.subPath); + const BookFormPath._(); String get fullPath => '$parentPath/$subPath'; } @@ -43,26 +43,29 @@ class BookForm extends _$BookForm { titleController = TextEditingController(text: book.name); - titleController.addListener((){ - if(state.hasValue){ - state = AsyncValue.data(state.value!.copyWith(title: titleController.text)); + titleController.addListener(() { + if (state.hasValue) { + state = AsyncValue.data( + state.value!.copyWith(title: titleController.text), + ); } }); final imagePaths = book.localSubPaths - .map((subPath) => BookFormPath(GlobalConfig.booksDir.path, subPath)) + .map( + (subPath) => BookFormPath( + parentPath: GlobalConfig.booksDir.path, + subPath: subPath, + ), + ) .toList(); - return BookFormState( - title: book.name, - imagePaths: imagePaths, - ); + return BookFormState(title: book.name, imagePaths: imagePaths); } Future deleteImage(BookFormPath path) async { - if(!state.hasValue) return; - final current =state.requireValue; - - final updatePaths = current.imagePaths.where((p) => p.fullPath != path.fullPath).toList(); + if (!state.hasValue) return; + final current = state.requireValue; + final updatePaths = current.imagePaths.where((p) => p != path).toList(); state = AsyncValue.data(current.copyWith(imagePaths: updatePaths)); } @@ -86,12 +89,10 @@ class BookForm extends _$BookForm { // 重新赋给 state 触发 UI 刷新 state = AsyncData(current.copyWith(imagePaths: updatedPaths)); } - } @riverpod class BookFormSubmit extends _$BookFormSubmit { - @override FutureOr build() => null; diff --git a/lib/feature/book/ui/provider/book_form_provider.freezed.dart b/lib/feature/book/ui/provider/book_form_provider.freezed.dart new file mode 100644 index 0000000..bf19497 --- /dev/null +++ b/lib/feature/book/ui/provider/book_form_provider.freezed.dart @@ -0,0 +1,540 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'book_form_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$BookFormState { + + String get title; List get imagePaths; +/// Create a copy of BookFormState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$BookFormStateCopyWith get copyWith => _$BookFormStateCopyWithImpl(this as BookFormState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is BookFormState&&(identical(other.title, title) || other.title == title)&&const DeepCollectionEquality().equals(other.imagePaths, imagePaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,title,const DeepCollectionEquality().hash(imagePaths)); + +@override +String toString() { + return 'BookFormState(title: $title, imagePaths: $imagePaths)'; +} + + +} + +/// @nodoc +abstract mixin class $BookFormStateCopyWith<$Res> { + factory $BookFormStateCopyWith(BookFormState value, $Res Function(BookFormState) _then) = _$BookFormStateCopyWithImpl; +@useResult +$Res call({ + String title, List imagePaths +}); + + + + +} +/// @nodoc +class _$BookFormStateCopyWithImpl<$Res> + implements $BookFormStateCopyWith<$Res> { + _$BookFormStateCopyWithImpl(this._self, this._then); + + final BookFormState _self; + final $Res Function(BookFormState) _then; + +/// Create a copy of BookFormState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? title = null,Object? imagePaths = null,}) { + return _then(_self.copyWith( +title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,imagePaths: null == imagePaths ? _self.imagePaths : imagePaths // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [BookFormState]. +extension BookFormStatePatterns on BookFormState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _BookFormState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _BookFormState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _BookFormState value) $default,){ +final _that = this; +switch (_that) { +case _BookFormState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _BookFormState value)? $default,){ +final _that = this; +switch (_that) { +case _BookFormState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String title, List imagePaths)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _BookFormState() when $default != null: +return $default(_that.title,_that.imagePaths);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String title, List imagePaths) $default,) {final _that = this; +switch (_that) { +case _BookFormState(): +return $default(_that.title,_that.imagePaths);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String title, List imagePaths)? $default,) {final _that = this; +switch (_that) { +case _BookFormState() when $default != null: +return $default(_that.title,_that.imagePaths);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _BookFormState implements BookFormState { + const _BookFormState({required this.title, required final List imagePaths}): _imagePaths = imagePaths; + + +@override final String title; + final List _imagePaths; +@override List get imagePaths { + if (_imagePaths is EqualUnmodifiableListView) return _imagePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_imagePaths); +} + + +/// Create a copy of BookFormState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$BookFormStateCopyWith<_BookFormState> get copyWith => __$BookFormStateCopyWithImpl<_BookFormState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _BookFormState&&(identical(other.title, title) || other.title == title)&&const DeepCollectionEquality().equals(other._imagePaths, _imagePaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,title,const DeepCollectionEquality().hash(_imagePaths)); + +@override +String toString() { + return 'BookFormState(title: $title, imagePaths: $imagePaths)'; +} + + +} + +/// @nodoc +abstract mixin class _$BookFormStateCopyWith<$Res> implements $BookFormStateCopyWith<$Res> { + factory _$BookFormStateCopyWith(_BookFormState value, $Res Function(_BookFormState) _then) = __$BookFormStateCopyWithImpl; +@override @useResult +$Res call({ + String title, List imagePaths +}); + + + + +} +/// @nodoc +class __$BookFormStateCopyWithImpl<$Res> + implements _$BookFormStateCopyWith<$Res> { + __$BookFormStateCopyWithImpl(this._self, this._then); + + final _BookFormState _self; + final $Res Function(_BookFormState) _then; + +/// Create a copy of BookFormState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? title = null,Object? imagePaths = null,}) { + return _then(_BookFormState( +title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,imagePaths: null == imagePaths ? _self._imagePaths : imagePaths // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +/// @nodoc +mixin _$BookFormPath { + + String get parentPath; String get subPath; +/// Create a copy of BookFormPath +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$BookFormPathCopyWith get copyWith => _$BookFormPathCopyWithImpl(this as BookFormPath, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is BookFormPath&&(identical(other.parentPath, parentPath) || other.parentPath == parentPath)&&(identical(other.subPath, subPath) || other.subPath == subPath)); +} + + +@override +int get hashCode => Object.hash(runtimeType,parentPath,subPath); + +@override +String toString() { + return 'BookFormPath(parentPath: $parentPath, subPath: $subPath)'; +} + + +} + +/// @nodoc +abstract mixin class $BookFormPathCopyWith<$Res> { + factory $BookFormPathCopyWith(BookFormPath value, $Res Function(BookFormPath) _then) = _$BookFormPathCopyWithImpl; +@useResult +$Res call({ + String parentPath, String subPath +}); + + + + +} +/// @nodoc +class _$BookFormPathCopyWithImpl<$Res> + implements $BookFormPathCopyWith<$Res> { + _$BookFormPathCopyWithImpl(this._self, this._then); + + final BookFormPath _self; + final $Res Function(BookFormPath) _then; + +/// Create a copy of BookFormPath +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? parentPath = null,Object? subPath = null,}) { + return _then(_self.copyWith( +parentPath: null == parentPath ? _self.parentPath : parentPath // ignore: cast_nullable_to_non_nullable +as String,subPath: null == subPath ? _self.subPath : subPath // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [BookFormPath]. +extension BookFormPathPatterns on BookFormPath { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _BookFormPath value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _BookFormPath() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _BookFormPath value) $default,){ +final _that = this; +switch (_that) { +case _BookFormPath(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _BookFormPath value)? $default,){ +final _that = this; +switch (_that) { +case _BookFormPath() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String parentPath, String subPath)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _BookFormPath() when $default != null: +return $default(_that.parentPath,_that.subPath);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String parentPath, String subPath) $default,) {final _that = this; +switch (_that) { +case _BookFormPath(): +return $default(_that.parentPath,_that.subPath);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String parentPath, String subPath)? $default,) {final _that = this; +switch (_that) { +case _BookFormPath() when $default != null: +return $default(_that.parentPath,_that.subPath);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _BookFormPath extends BookFormPath { + const _BookFormPath({required this.parentPath, required this.subPath}): super._(); + + +@override final String parentPath; +@override final String subPath; + +/// Create a copy of BookFormPath +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$BookFormPathCopyWith<_BookFormPath> get copyWith => __$BookFormPathCopyWithImpl<_BookFormPath>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _BookFormPath&&(identical(other.parentPath, parentPath) || other.parentPath == parentPath)&&(identical(other.subPath, subPath) || other.subPath == subPath)); +} + + +@override +int get hashCode => Object.hash(runtimeType,parentPath,subPath); + +@override +String toString() { + return 'BookFormPath(parentPath: $parentPath, subPath: $subPath)'; +} + + +} + +/// @nodoc +abstract mixin class _$BookFormPathCopyWith<$Res> implements $BookFormPathCopyWith<$Res> { + factory _$BookFormPathCopyWith(_BookFormPath value, $Res Function(_BookFormPath) _then) = __$BookFormPathCopyWithImpl; +@override @useResult +$Res call({ + String parentPath, String subPath +}); + + + + +} +/// @nodoc +class __$BookFormPathCopyWithImpl<$Res> + implements _$BookFormPathCopyWith<$Res> { + __$BookFormPathCopyWithImpl(this._self, this._then); + + final _BookFormPath _self; + final $Res Function(_BookFormPath) _then; + +/// Create a copy of BookFormPath +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? parentPath = null,Object? subPath = null,}) { + return _then(_BookFormPath( +parentPath: null == parentPath ? _self.parentPath : parentPath // ignore: cast_nullable_to_non_nullable +as String,subPath: null == subPath ? _self.subPath : subPath // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/lib/feature/book/ui/provider/book_form_provider.g.dart b/lib/feature/book/ui/provider/book_form_provider.g.dart index 6b9c72d..ac39171 100644 --- a/lib/feature/book/ui/provider/book_form_provider.g.dart +++ b/lib/feature/book/ui/provider/book_form_provider.g.dart @@ -50,7 +50,7 @@ final class BookFormProvider } } -String _$bookFormHash() => r'b794138246713fac4e945d5cee8535e25de68702'; +String _$bookFormHash() => r'2407f1247c5dcb1db39c18a8efb0dee929528e93'; final class BookFormFamily extends $Family with @@ -122,7 +122,7 @@ final class BookFormSubmitProvider BookFormSubmit create() => BookFormSubmit(); } -String _$bookFormSubmitHash() => r'b9c0adc2639c452eecc20c65f0a7d99febd5e693'; +String _$bookFormSubmitHash() => r'6e1d62948fc4e5a71a99ddc07525ba1aa5e47a86'; abstract class _$BookFormSubmit extends $AsyncNotifier { FutureOr build(); diff --git a/lib/feature/book/ui/view/book_list_view.dart b/lib/feature/book/ui/view/book_list_view.dart index ad19425..6ba8e26 100644 --- a/lib/feature/book/ui/view/book_list_view.dart +++ b/lib/feature/book/ui/view/book_list_view.dart @@ -139,6 +139,9 @@ class _BookListViewState extends ConsumerState { child: const Icon(Icons.download), ), ), + IconButton(onPressed: (){ + context.push(AppRoute.parseForm); + }, icon: const Icon(Icons.add)), _buildTopMenuButton(context, state), ], ); diff --git a/lib/feature/download/datasource/runtime/download_runtime_datasource.dart b/lib/feature/download/datasource/runtime/download_runtime_datasource.dart index c31fad7..e92eb9a 100644 --- a/lib/feature/download/datasource/runtime/download_runtime_datasource.dart +++ b/lib/feature/download/datasource/runtime/download_runtime_datasource.dart @@ -1,9 +1,18 @@ import 'dart:async'; import 'dart:collection'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:tele_book/feature/download/model/bo/download_bo.dart'; import 'package:tele_book/feature/download/model/vo/download_vo.dart'; +final downloadRuntimeDatasourceProvider = Provider(( + ref, +) { + final ds = DownloadRuntimeDatasource(); + ref.onDispose(ds.dispose); + return ds; +}); + class DownloadRuntimeDatasource { final LinkedHashMap _groupMap = LinkedHashMap(); final LinkedHashMap _itemMap = LinkedHashMap(); diff --git a/lib/feature/download/repository/download_repository.dart b/lib/feature/download/repository/download_repository.dart index 9de8d2e..ed76f24 100644 --- a/lib/feature/download/repository/download_repository.dart +++ b/lib/feature/download/repository/download_repository.dart @@ -1,7 +1,13 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:tele_book/feature/download/datasource/runtime/download_runtime_datasource.dart'; import 'package:tele_book/feature/download/model/bo/download_bo.dart'; import 'package:tele_book/feature/download/model/vo/download_vo.dart'; +final downloadRepositoryProvider = Provider((ref) { + return DownloadRepository(ref.watch(downloadRuntimeDatasourceProvider)); +}); + + class DownloadRepository { final DownloadRuntimeDatasource _downloadRuntimeDatasource; diff --git a/lib/feature/download/service/download_service.dart b/lib/feature/download/service/download_service.dart index 6703d87..78a7f8f 100644 --- a/lib/feature/download/service/download_service.dart +++ b/lib/feature/download/service/download_service.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:background_downloader/background_downloader.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path_provider/path_provider.dart'; import 'package:synchronized/synchronized.dart'; import 'package:tele_book/common/config/global_config.dart'; @@ -14,6 +15,13 @@ import 'package:tele_book/feature/download/model/vo/download_vo.dart'; import 'package:tele_book/feature/download/repository/download_repository.dart'; import 'package:uuid/uuid.dart'; +final downloadServiceProvider = Provider((ref) { + return DownloadService( + ref.watch(downloadRepositoryProvider), + ref.watch(bookRepositoryProvider), + ); +}); + class DownloadService { final FileDownloader _downloader = FileDownloader(); final DownloadRepository _downloadRepository; diff --git a/lib/feature/download/ui/provider/download_provider.dart b/lib/feature/download/ui/provider/download_provider.dart index 99680ad..8a7a182 100644 --- a/lib/feature/download/ui/provider/download_provider.dart +++ b/lib/feature/download/ui/provider/download_provider.dart @@ -5,24 +5,6 @@ import 'package:tele_book/feature/download/model/vo/download_vo.dart'; import 'package:tele_book/feature/download/repository/download_repository.dart'; import 'package:tele_book/feature/download/service/download_service.dart'; -/// 内存数据源 —— 全局单例,生命周期同 ProviderScope -final downloadRuntimeDatasourceProvider = - Provider((ref) { - final ds = DownloadRuntimeDatasource(); - ref.onDispose(ds.dispose); - return ds; -}); - -final downloadRepositoryProvider = Provider((ref) { - return DownloadRepository(ref.watch(downloadRuntimeDatasourceProvider)); -}); - -final downloadServiceProvider = Provider((ref) { - return DownloadService( - ref.watch(downloadRepositoryProvider), - ref.watch(bookRepositoryProvider), - ); -}); /// 监听下载任务列表(响应式 Stream) final downloadTasksProvider = StreamProvider>((ref) { diff --git a/lib/feature/parse/service/parse_archive_service.dart b/lib/feature/parse/service/parse_archive_service.dart index ad8395b..547388d 100644 --- a/lib/feature/parse/service/parse_archive_service.dart +++ b/lib/feature/parse/service/parse_archive_service.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:tele_book/common/config/global_config.dart'; import 'package:tele_book/core/util/failure_util.dart'; import 'package:tele_book/core/util/result_util.dart'; @@ -8,79 +9,13 @@ import 'package:tele_book/feature/parse/model/parse_batch_archive_vo.dart'; import 'package:uuid/uuid.dart'; import 'package:archive/archive_io.dart'; -// ── 顶层函数,供 compute() 在后台 Isolate 中调用 ────────── -// Isolate 中不能访问闭包,必须是 top-level / static -Future _extractInBackground(List args) async { - final archivePath = args[0]; - final outputDir = args[1]; - await extractFileToDisk(archivePath, outputDir); -} - -/// 在后台 Isolate 中扫描目录,返回图片路径列表 -List _collectImagePathsSync(String dirPath) { - final dir = Directory(dirPath); - final result = []; - for (final entity in dir.listSync(recursive: true, followLinks: false)) { - if (entity is File && _isImageFileStatic(entity.path)) { - result.add(entity.path); - } - } - result.sort(); - return result; -} - -List _collectDirectImagePathsSync(String dirPath) { - final dir = Directory(dirPath); - final result = []; - for (final entity in dir.listSync(recursive: false, followLinks: false)) { - if (entity is File && _isImageFileStatic(entity.path)) { - result.add(entity.path); - } - } - result.sort(); - return result; -} - -List _collectSubDirectoryPathsSync(String dirPath) { - final dir = Directory(dirPath); - final result = []; - for (final entity in dir.listSync(recursive: true, followLinks: false)) { - if (entity is Directory) { - result.add(entity.path); - } - } - result.sort(); - return result; -} - -bool _isImageFileStatic(String path) { - final p = path.toLowerCase(); - return p.endsWith('.jpg') || - p.endsWith('.jpeg') || - p.endsWith('.png') || - p.endsWith('.gif') || - p.endsWith('.bmp') || - p.endsWith('.webp'); -} - -String _baseName(String path) { - return path.split(RegExp(r'[\\/]')).last; -} - -String _dirName(String path) { - final normalized = path.replaceAll('\\', '/'); - final index = normalized.lastIndexOf('/'); - if (index <= 0) return ''; - return normalized.substring(0, index); -} +final parseArchiveServiceProvider = Provider((ref) => ParseArchiveService()); class ParseArchiveService { Future>> parseImagePaths(List imagePaths) async { try { - final images = imagePaths - .where((path) => _isImageFileStatic(path)) - .toList() - ..sort(); + final images = + imagePaths.where((path) => _isImageFileStatic(path)).toList()..sort(); return Result.success(images); } catch (e, st) { return Result.failure( @@ -92,17 +27,16 @@ class ParseArchiveService { Future>> _parseBatchArchivePaths( List archivePaths, Function(int total) onStart, - Function(int count) onProgress, - { + Function(int count) onProgress, { void Function(String currentName)? onCurrentItemChanged, void Function(int current, int total)? onCurrentItemProgress, - } - ) async { + }) async { try { - final filteredPaths = archivePaths - .where((path) => path.toLowerCase().endsWith('.zip')) - .toList() - ..sort(); + final filteredPaths = + archivePaths + .where((path) => path.toLowerCase().endsWith('.zip')) + .toList() + ..sort(); onStart(filteredPaths.length); @@ -164,7 +98,8 @@ class ParseArchiveService { void Function(int current, int total)? onProgress, }) async { try { - final tempOutputDir = "${GlobalConfig.appTempDir.path}/${const Uuid().v4()}"; + final tempOutputDir = + "${GlobalConfig.appTempDir.path}/${const Uuid().v4()}"; onProgress?.call(0, 2); // ① 解压放后台 Isolate(最重,可能 OOM,独立内存空间更安全) @@ -187,12 +122,10 @@ class ParseArchiveService { Future>> parseBatchArchives( String archiveDirPath, Function(int total) onStart, - Function(int count) onProgress, - { + Function(int count) onProgress, { void Function(String currentName)? onCurrentItemChanged, void Function(int current, int total)? onCurrentItemProgress, - } - ) async { + }) async { try { final archiveDir = Directory(archiveDirPath); if (!await archiveDir.exists()) { @@ -223,12 +156,10 @@ class ParseArchiveService { Future>> parseBatchArchivesFromPaths( List archivePaths, Function(int total) onStart, - Function(int count) onProgress, - { + Function(int count) onProgress, { void Function(String currentName)? onCurrentItemChanged, void Function(int current, int total)? onCurrentItemProgress, - } - ) { + }) { return _parseBatchArchivePaths( archivePaths, onStart, @@ -242,12 +173,10 @@ class ParseArchiveService { Future>> parseBatchImageFolders( String parentDirPath, Function(int total) onStart, - Function(int count) onProgress, - { + Function(int count) onProgress, { void Function(String currentName)? onCurrentItemChanged, void Function(int current, int total)? onCurrentItemProgress, - } - ) async { + }) async { try { final parentDir = Directory(parentDirPath); if (!await parentDir.exists()) { @@ -255,7 +184,10 @@ class ParseArchiveService { } // 遍历父目录下的所有子文件夹(不包含父目录自身) - final folders = await compute(_collectSubDirectoryPathsSync, parentDirPath); + final folders = await compute( + _collectSubDirectoryPathsSync, + parentDirPath, + ); onStart(folders.length); @@ -272,10 +204,7 @@ class ParseArchiveService { final images = await compute(_collectDirectImagePathsSync, folderPath); if (images.length > 1) { results.add( - ParseBatchArchiveVo( - name: _baseName(folderPath), - tempPaths: images, - ), + ParseBatchArchiveVo(name: _baseName(folderPath), tempPaths: images), ); } onCurrentItemProgress?.call(1, 1); @@ -292,12 +221,10 @@ class ParseArchiveService { Future>> parseBatchImageFoldersFromPaths( List imagePaths, Function(int total) onStart, - Function(int count) onProgress, - { + Function(int count) onProgress, { void Function(String currentName)? onCurrentItemChanged, void Function(int current, int total)? onCurrentItemProgress, - } - ) async { + }) async { try { final grouped = >{}; for (final path in imagePaths) { @@ -337,4 +264,68 @@ class ParseArchiveService { } } +// ── 顶层函数,供 compute() 在后台 Isolate 中调用 ────────── +// Isolate 中不能访问闭包,必须是 top-level / static +Future _extractInBackground(List args) async { + final archivePath = args[0]; + final outputDir = args[1]; + await extractFileToDisk(archivePath, outputDir); +} + +/// 在后台 Isolate 中扫描目录,返回图片路径列表 +List _collectImagePathsSync(String dirPath) { + final dir = Directory(dirPath); + final result = []; + for (final entity in dir.listSync(recursive: true, followLinks: false)) { + if (entity is File && _isImageFileStatic(entity.path)) { + result.add(entity.path); + } + } + result.sort(); + return result; +} + +List _collectDirectImagePathsSync(String dirPath) { + final dir = Directory(dirPath); + final result = []; + for (final entity in dir.listSync(recursive: false, followLinks: false)) { + if (entity is File && _isImageFileStatic(entity.path)) { + result.add(entity.path); + } + } + result.sort(); + return result; +} + +List _collectSubDirectoryPathsSync(String dirPath) { + final dir = Directory(dirPath); + final result = []; + for (final entity in dir.listSync(recursive: true, followLinks: false)) { + if (entity is Directory) { + result.add(entity.path); + } + } + result.sort(); + return result; +} +bool _isImageFileStatic(String path) { + final p = path.toLowerCase(); + return p.endsWith('.jpg') || + p.endsWith('.jpeg') || + p.endsWith('.png') || + p.endsWith('.gif') || + p.endsWith('.bmp') || + p.endsWith('.webp'); +} + +String _baseName(String path) { + return path.split(RegExp(r'[\\/]')).last; +} + +String _dirName(String path) { + final normalized = path.replaceAll('\\', '/'); + final index = normalized.lastIndexOf('/'); + if (index <= 0) return ''; + return normalized.substring(0, index); +} diff --git a/lib/feature/parse/service/parse_pdf_service.dart b/lib/feature/parse/service/parse_pdf_service.dart index ae5a9e5..e406120 100644 --- a/lib/feature/parse/service/parse_pdf_service.dart +++ b/lib/feature/parse/service/parse_pdf_service.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'dart:typed_data'; import 'dart:ui' as ui; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path/path.dart' as p; import 'package:pdfrx/pdfrx.dart'; import 'package:tele_book/common/config/global_config.dart'; @@ -11,6 +12,8 @@ import 'package:tele_book/core/util/result_util.dart'; import 'package:tele_book/feature/parse/model/parse_batch_archive_vo.dart'; import 'package:uuid/uuid.dart'; +final parsePdfServiceProvider = Provider((ref) => ParsePdfService()); + class ParsePdfService { static bool _pdfrxInitialized = false; @@ -106,24 +109,25 @@ class ParsePdfService { Future>> parseBatchPdfs( String pdfDirPath, void Function(int total) onStart, - void Function(int count) onProgress, - { + void Function(int count) onProgress, { void Function(String fileName)? onCurrentFileChanged, void Function(int current, int total)? onCurrentFileProgress, - } - ) async { + }) async { try { final dir = Directory(pdfDirPath); if (!await dir.exists()) { throw FileSystemException('目录不存在', pdfDirPath); } - final pdfPaths = await dir - .list() - .where((e) => e is File && e.path.toLowerCase().endsWith('.pdf')) - .map((e) => e.path) - .toList() - ..sort(); + final pdfPaths = + await dir + .list() + .where( + (e) => e is File && e.path.toLowerCase().endsWith('.pdf'), + ) + .map((e) => e.path) + .toList() + ..sort(); return _parseBatchPdfsByPaths( pdfPaths, @@ -134,11 +138,7 @@ class ParsePdfService { ); } catch (e, st) { return Result.failure( - BusinessFailure( - message: '批量解析 PDF 失败', - details: e, - stackTrace: st, - ), + BusinessFailure(message: '批量解析 PDF 失败', details: e, stackTrace: st), ); } } @@ -146,12 +146,10 @@ class ParsePdfService { Future>> parseBatchPdfsFromPaths( List pdfPaths, void Function(int total) onStart, - void Function(int count) onProgress, - { + void Function(int count) onProgress, { void Function(String fileName)? onCurrentFileChanged, void Function(int current, int total)? onCurrentFileProgress, - } - ) { + }) { final filtered = pdfPaths .where((path) => path.toLowerCase().endsWith('.pdf')) .toList(); @@ -167,12 +165,10 @@ class ParsePdfService { Future>> _parseBatchPdfsByPaths( List pdfPaths, void Function(int total) onStart, - void Function(int count) onProgress, - { + void Function(int count) onProgress, { void Function(String fileName)? onCurrentFileChanged, void Function(int current, int total)? onCurrentFileProgress, - } - ) async { + }) async { try { pdfPaths.sort(); @@ -207,13 +203,8 @@ class ParsePdfService { return Result.success(results); } catch (e, st) { return Result.failure( - BusinessFailure( - message: '批量解析 PDF 失败', - details: e, - stackTrace: st, - ), + BusinessFailure(message: '批量解析 PDF 失败', details: e, stackTrace: st), ); } } } - diff --git a/lib/feature/parse/service/parse_web_service.dart b/lib/feature/parse/service/parse_web_service.dart index 32f94b4..2263e4d 100644 --- a/lib/feature/parse/service/parse_web_service.dart +++ b/lib/feature/parse/service/parse_web_service.dart @@ -1,6 +1,11 @@ import 'dart:convert'; import 'package:dio/dio.dart'; +import 'package:riverpod/riverpod.dart'; + +final parseWebServiceProvider = Provider((ref) { + return ParseWebService(); +}); class ParseWebService { Future extractTitleFromWebView({ @@ -29,7 +34,7 @@ class ParseWebService { return title.replaceAll(RegExp(r'[<>:"/\\|?*]'), '').trim(); } - Future> extractImagesFromWebView({ + Future> extractImagesFromWebView({ required Future Function(String) onExtractImages, }) async { final js = r""" @@ -80,7 +85,7 @@ class ParseWebService { } } - Future downloadImageToFile(String url, String saveDir) async { + Future downloadImageToFile(String url, String saveDir) async { final filePath = '$saveDir/${DateTime.now().microsecondsSinceEpoch}_${url.split('/').last}.jpg'; final dio = Dio(); diff --git a/lib/feature/parse/ui/provider/parse_archive_provider.dart b/lib/feature/parse/ui/provider/parse_archive_provider.dart new file mode 100644 index 0000000..edda691 --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_archive_provider.dart @@ -0,0 +1,117 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_riverpod/legacy.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:tele_book/feature/book/model/dto/save_as_book_dto.dart'; +import 'package:tele_book/feature/book/repository/book_repository.dart'; +import 'package:tele_book/feature/parse/service/parse_archive_service.dart'; + +part 'parse_archive_provider.freezed.dart'; + +part 'parse_archive_provider.g.dart'; + +@freezed +abstract class ParseArchiveState with _$ParseArchiveState { + const factory ParseArchiveState({ + required String archiveName, + required List tempPaths, + }) = _ParseArchiveState; +} + +@freezed +abstract class ParseArchiveSaveBookParam with _$ParseArchiveSaveBookParam { + const factory ParseArchiveSaveBookParam({ + required String archiveName, + required List tempPaths, + }) = _ParseArchiveSaveBookParam; +} + +@freezed +abstract class ParseArchiveSaveBookProgress with _$ParseArchiveSaveBookProgress { + const factory ParseArchiveSaveBookProgress({ + @Default(0) int current, + @Default(0) int total, + }) = _ParseArchiveSaveBookProgress; +} + +final parseArchiveProgressProvider = + StateProvider.family<(int current, int total), String>((_, __) => (0, 0)); + +final parseArchiveSaveBookProgressProvider = + StateProvider( + (_) => const ParseArchiveSaveBookProgress(), + ); + +@riverpod +class ParseArchive extends _$ParseArchive { + ParseArchiveService get _parseArchiveService => + ref.read(parseArchiveServiceProvider); + + @override + FutureOr build(String archivePath) async { + final archiveName = archivePath.split(RegExp(r'[\\/]')).last; + Future.microtask(() => _parseArchive(archivePath)); + return ParseArchiveState(archiveName: archiveName, tempPaths: const []); + } + + Future _parseArchive(String archivePath) async { + final archiveName = archivePath.split(RegExp(r'[\\/]')).last; + + ref.read(parseArchiveProgressProvider(archivePath).notifier).state = (0, 0); + state = const AsyncLoading(); + + final result = await _parseArchiveService.parseArchive( + archivePath, + onProgress: (current, total) { + ref.read(parseArchiveProgressProvider(archivePath).notifier).state = ( + current, + total, + ); + }, + ); + + result.fold( + onSuccess: (data) { + state = + AsyncData(ParseArchiveState(archiveName: archiveName, tempPaths: data)); + }, + onError: (error) { + state = AsyncError(error.message, StackTrace.current); + }, + ); + } +} + +@riverpod +class ParseArchiveSaveBook extends _$ParseArchiveSaveBook { + BookRepository get _bookRepository => ref.read(bookRepositoryProvider); + + @override + FutureOr build() => null; + + Future submit(ParseArchiveSaveBookParam param) async { + if (state.isLoading || param.tempPaths.isEmpty) return; + + state = const AsyncLoading(); + ref.read(parseArchiveSaveBookProgressProvider.notifier).state = + ParseArchiveSaveBookProgress(current: 0, total: param.tempPaths.length); + + final result = await _bookRepository.saveAsBook( + SaveAsBookDto(title: param.archiveName, paths: param.tempPaths), + onProgress: (current, total) { + ref.read(parseArchiveSaveBookProgressProvider.notifier).state = + ParseArchiveSaveBookProgress(current: current, total: total); + }, + ); + + result.fold( + onSuccess: (_) { + state = const AsyncData(null); + }, + onError: (error) { + state = AsyncError(error.message, StackTrace.current); + }, + ); + } +} + diff --git a/lib/feature/parse/ui/provider/parse_archive_provider.freezed.dart b/lib/feature/parse/ui/provider/parse_archive_provider.freezed.dart new file mode 100644 index 0000000..732eed2 --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_archive_provider.freezed.dart @@ -0,0 +1,806 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'parse_archive_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$ParseArchiveState { + + String get archiveName; List get tempPaths; +/// Create a copy of ParseArchiveState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseArchiveStateCopyWith get copyWith => _$ParseArchiveStateCopyWithImpl(this as ParseArchiveState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseArchiveState&&(identical(other.archiveName, archiveName) || other.archiveName == archiveName)&&const DeepCollectionEquality().equals(other.tempPaths, tempPaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,archiveName,const DeepCollectionEquality().hash(tempPaths)); + +@override +String toString() { + return 'ParseArchiveState(archiveName: $archiveName, tempPaths: $tempPaths)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseArchiveStateCopyWith<$Res> { + factory $ParseArchiveStateCopyWith(ParseArchiveState value, $Res Function(ParseArchiveState) _then) = _$ParseArchiveStateCopyWithImpl; +@useResult +$Res call({ + String archiveName, List tempPaths +}); + + + + +} +/// @nodoc +class _$ParseArchiveStateCopyWithImpl<$Res> + implements $ParseArchiveStateCopyWith<$Res> { + _$ParseArchiveStateCopyWithImpl(this._self, this._then); + + final ParseArchiveState _self; + final $Res Function(ParseArchiveState) _then; + +/// Create a copy of ParseArchiveState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? archiveName = null,Object? tempPaths = null,}) { + return _then(_self.copyWith( +archiveName: null == archiveName ? _self.archiveName : archiveName // ignore: cast_nullable_to_non_nullable +as String,tempPaths: null == tempPaths ? _self.tempPaths : tempPaths // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseArchiveState]. +extension ParseArchiveStatePatterns on ParseArchiveState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseArchiveState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseArchiveState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseArchiveState value) $default,){ +final _that = this; +switch (_that) { +case _ParseArchiveState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseArchiveState value)? $default,){ +final _that = this; +switch (_that) { +case _ParseArchiveState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String archiveName, List tempPaths)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseArchiveState() when $default != null: +return $default(_that.archiveName,_that.tempPaths);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String archiveName, List tempPaths) $default,) {final _that = this; +switch (_that) { +case _ParseArchiveState(): +return $default(_that.archiveName,_that.tempPaths);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String archiveName, List tempPaths)? $default,) {final _that = this; +switch (_that) { +case _ParseArchiveState() when $default != null: +return $default(_that.archiveName,_that.tempPaths);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseArchiveState implements ParseArchiveState { + const _ParseArchiveState({required this.archiveName, required final List tempPaths}): _tempPaths = tempPaths; + + +@override final String archiveName; + final List _tempPaths; +@override List get tempPaths { + if (_tempPaths is EqualUnmodifiableListView) return _tempPaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_tempPaths); +} + + +/// Create a copy of ParseArchiveState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseArchiveStateCopyWith<_ParseArchiveState> get copyWith => __$ParseArchiveStateCopyWithImpl<_ParseArchiveState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseArchiveState&&(identical(other.archiveName, archiveName) || other.archiveName == archiveName)&&const DeepCollectionEquality().equals(other._tempPaths, _tempPaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,archiveName,const DeepCollectionEquality().hash(_tempPaths)); + +@override +String toString() { + return 'ParseArchiveState(archiveName: $archiveName, tempPaths: $tempPaths)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseArchiveStateCopyWith<$Res> implements $ParseArchiveStateCopyWith<$Res> { + factory _$ParseArchiveStateCopyWith(_ParseArchiveState value, $Res Function(_ParseArchiveState) _then) = __$ParseArchiveStateCopyWithImpl; +@override @useResult +$Res call({ + String archiveName, List tempPaths +}); + + + + +} +/// @nodoc +class __$ParseArchiveStateCopyWithImpl<$Res> + implements _$ParseArchiveStateCopyWith<$Res> { + __$ParseArchiveStateCopyWithImpl(this._self, this._then); + + final _ParseArchiveState _self; + final $Res Function(_ParseArchiveState) _then; + +/// Create a copy of ParseArchiveState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? archiveName = null,Object? tempPaths = null,}) { + return _then(_ParseArchiveState( +archiveName: null == archiveName ? _self.archiveName : archiveName // ignore: cast_nullable_to_non_nullable +as String,tempPaths: null == tempPaths ? _self._tempPaths : tempPaths // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +/// @nodoc +mixin _$ParseArchiveSaveBookParam { + + String get archiveName; List get tempPaths; +/// Create a copy of ParseArchiveSaveBookParam +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseArchiveSaveBookParamCopyWith get copyWith => _$ParseArchiveSaveBookParamCopyWithImpl(this as ParseArchiveSaveBookParam, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseArchiveSaveBookParam&&(identical(other.archiveName, archiveName) || other.archiveName == archiveName)&&const DeepCollectionEquality().equals(other.tempPaths, tempPaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,archiveName,const DeepCollectionEquality().hash(tempPaths)); + +@override +String toString() { + return 'ParseArchiveSaveBookParam(archiveName: $archiveName, tempPaths: $tempPaths)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseArchiveSaveBookParamCopyWith<$Res> { + factory $ParseArchiveSaveBookParamCopyWith(ParseArchiveSaveBookParam value, $Res Function(ParseArchiveSaveBookParam) _then) = _$ParseArchiveSaveBookParamCopyWithImpl; +@useResult +$Res call({ + String archiveName, List tempPaths +}); + + + + +} +/// @nodoc +class _$ParseArchiveSaveBookParamCopyWithImpl<$Res> + implements $ParseArchiveSaveBookParamCopyWith<$Res> { + _$ParseArchiveSaveBookParamCopyWithImpl(this._self, this._then); + + final ParseArchiveSaveBookParam _self; + final $Res Function(ParseArchiveSaveBookParam) _then; + +/// Create a copy of ParseArchiveSaveBookParam +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? archiveName = null,Object? tempPaths = null,}) { + return _then(_self.copyWith( +archiveName: null == archiveName ? _self.archiveName : archiveName // ignore: cast_nullable_to_non_nullable +as String,tempPaths: null == tempPaths ? _self.tempPaths : tempPaths // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseArchiveSaveBookParam]. +extension ParseArchiveSaveBookParamPatterns on ParseArchiveSaveBookParam { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseArchiveSaveBookParam value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseArchiveSaveBookParam() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseArchiveSaveBookParam value) $default,){ +final _that = this; +switch (_that) { +case _ParseArchiveSaveBookParam(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseArchiveSaveBookParam value)? $default,){ +final _that = this; +switch (_that) { +case _ParseArchiveSaveBookParam() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String archiveName, List tempPaths)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseArchiveSaveBookParam() when $default != null: +return $default(_that.archiveName,_that.tempPaths);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String archiveName, List tempPaths) $default,) {final _that = this; +switch (_that) { +case _ParseArchiveSaveBookParam(): +return $default(_that.archiveName,_that.tempPaths);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String archiveName, List tempPaths)? $default,) {final _that = this; +switch (_that) { +case _ParseArchiveSaveBookParam() when $default != null: +return $default(_that.archiveName,_that.tempPaths);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseArchiveSaveBookParam implements ParseArchiveSaveBookParam { + const _ParseArchiveSaveBookParam({required this.archiveName, required final List tempPaths}): _tempPaths = tempPaths; + + +@override final String archiveName; + final List _tempPaths; +@override List get tempPaths { + if (_tempPaths is EqualUnmodifiableListView) return _tempPaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_tempPaths); +} + + +/// Create a copy of ParseArchiveSaveBookParam +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseArchiveSaveBookParamCopyWith<_ParseArchiveSaveBookParam> get copyWith => __$ParseArchiveSaveBookParamCopyWithImpl<_ParseArchiveSaveBookParam>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseArchiveSaveBookParam&&(identical(other.archiveName, archiveName) || other.archiveName == archiveName)&&const DeepCollectionEquality().equals(other._tempPaths, _tempPaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,archiveName,const DeepCollectionEquality().hash(_tempPaths)); + +@override +String toString() { + return 'ParseArchiveSaveBookParam(archiveName: $archiveName, tempPaths: $tempPaths)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseArchiveSaveBookParamCopyWith<$Res> implements $ParseArchiveSaveBookParamCopyWith<$Res> { + factory _$ParseArchiveSaveBookParamCopyWith(_ParseArchiveSaveBookParam value, $Res Function(_ParseArchiveSaveBookParam) _then) = __$ParseArchiveSaveBookParamCopyWithImpl; +@override @useResult +$Res call({ + String archiveName, List tempPaths +}); + + + + +} +/// @nodoc +class __$ParseArchiveSaveBookParamCopyWithImpl<$Res> + implements _$ParseArchiveSaveBookParamCopyWith<$Res> { + __$ParseArchiveSaveBookParamCopyWithImpl(this._self, this._then); + + final _ParseArchiveSaveBookParam _self; + final $Res Function(_ParseArchiveSaveBookParam) _then; + +/// Create a copy of ParseArchiveSaveBookParam +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? archiveName = null,Object? tempPaths = null,}) { + return _then(_ParseArchiveSaveBookParam( +archiveName: null == archiveName ? _self.archiveName : archiveName // ignore: cast_nullable_to_non_nullable +as String,tempPaths: null == tempPaths ? _self._tempPaths : tempPaths // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +/// @nodoc +mixin _$ParseArchiveSaveBookProgress { + + int get current; int get total; +/// Create a copy of ParseArchiveSaveBookProgress +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseArchiveSaveBookProgressCopyWith get copyWith => _$ParseArchiveSaveBookProgressCopyWithImpl(this as ParseArchiveSaveBookProgress, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseArchiveSaveBookProgress&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); +} + + +@override +int get hashCode => Object.hash(runtimeType,current,total); + +@override +String toString() { + return 'ParseArchiveSaveBookProgress(current: $current, total: $total)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseArchiveSaveBookProgressCopyWith<$Res> { + factory $ParseArchiveSaveBookProgressCopyWith(ParseArchiveSaveBookProgress value, $Res Function(ParseArchiveSaveBookProgress) _then) = _$ParseArchiveSaveBookProgressCopyWithImpl; +@useResult +$Res call({ + int current, int total +}); + + + + +} +/// @nodoc +class _$ParseArchiveSaveBookProgressCopyWithImpl<$Res> + implements $ParseArchiveSaveBookProgressCopyWith<$Res> { + _$ParseArchiveSaveBookProgressCopyWithImpl(this._self, this._then); + + final ParseArchiveSaveBookProgress _self; + final $Res Function(ParseArchiveSaveBookProgress) _then; + +/// Create a copy of ParseArchiveSaveBookProgress +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? current = null,Object? total = null,}) { + return _then(_self.copyWith( +current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable +as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseArchiveSaveBookProgress]. +extension ParseArchiveSaveBookProgressPatterns on ParseArchiveSaveBookProgress { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseArchiveSaveBookProgress value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseArchiveSaveBookProgress() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseArchiveSaveBookProgress value) $default,){ +final _that = this; +switch (_that) { +case _ParseArchiveSaveBookProgress(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseArchiveSaveBookProgress value)? $default,){ +final _that = this; +switch (_that) { +case _ParseArchiveSaveBookProgress() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( int current, int total)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseArchiveSaveBookProgress() when $default != null: +return $default(_that.current,_that.total);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( int current, int total) $default,) {final _that = this; +switch (_that) { +case _ParseArchiveSaveBookProgress(): +return $default(_that.current,_that.total);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( int current, int total)? $default,) {final _that = this; +switch (_that) { +case _ParseArchiveSaveBookProgress() when $default != null: +return $default(_that.current,_that.total);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseArchiveSaveBookProgress implements ParseArchiveSaveBookProgress { + const _ParseArchiveSaveBookProgress({this.current = 0, this.total = 0}); + + +@override@JsonKey() final int current; +@override@JsonKey() final int total; + +/// Create a copy of ParseArchiveSaveBookProgress +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseArchiveSaveBookProgressCopyWith<_ParseArchiveSaveBookProgress> get copyWith => __$ParseArchiveSaveBookProgressCopyWithImpl<_ParseArchiveSaveBookProgress>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseArchiveSaveBookProgress&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); +} + + +@override +int get hashCode => Object.hash(runtimeType,current,total); + +@override +String toString() { + return 'ParseArchiveSaveBookProgress(current: $current, total: $total)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseArchiveSaveBookProgressCopyWith<$Res> implements $ParseArchiveSaveBookProgressCopyWith<$Res> { + factory _$ParseArchiveSaveBookProgressCopyWith(_ParseArchiveSaveBookProgress value, $Res Function(_ParseArchiveSaveBookProgress) _then) = __$ParseArchiveSaveBookProgressCopyWithImpl; +@override @useResult +$Res call({ + int current, int total +}); + + + + +} +/// @nodoc +class __$ParseArchiveSaveBookProgressCopyWithImpl<$Res> + implements _$ParseArchiveSaveBookProgressCopyWith<$Res> { + __$ParseArchiveSaveBookProgressCopyWithImpl(this._self, this._then); + + final _ParseArchiveSaveBookProgress _self; + final $Res Function(_ParseArchiveSaveBookProgress) _then; + +/// Create a copy of ParseArchiveSaveBookProgress +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? current = null,Object? total = null,}) { + return _then(_ParseArchiveSaveBookProgress( +current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable +as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +// dart format on diff --git a/lib/feature/parse/ui/provider/parse_archive_provider.g.dart b/lib/feature/parse/ui/provider/parse_archive_provider.g.dart new file mode 100644 index 0000000..adc5b8f --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_archive_provider.g.dart @@ -0,0 +1,145 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'parse_archive_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(ParseArchive) +final parseArchiveProvider = ParseArchiveFamily._(); + +final class ParseArchiveProvider + extends $AsyncNotifierProvider { + ParseArchiveProvider._({ + required ParseArchiveFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'parseArchiveProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$parseArchiveHash(); + + @override + String toString() { + return r'parseArchiveProvider' + '' + '($argument)'; + } + + @$internal + @override + ParseArchive create() => ParseArchive(); + + @override + bool operator ==(Object other) { + return other is ParseArchiveProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$parseArchiveHash() => r'abd52c0a9e981404affd3711f8cbd7f3194ee84e'; + +final class ParseArchiveFamily extends $Family + with + $ClassFamilyOverride< + ParseArchive, + AsyncValue, + ParseArchiveState, + FutureOr, + String + > { + ParseArchiveFamily._() + : super( + retry: null, + name: r'parseArchiveProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + ParseArchiveProvider call(String archivePath) => + ParseArchiveProvider._(argument: archivePath, from: this); + + @override + String toString() => r'parseArchiveProvider'; +} + +abstract class _$ParseArchive extends $AsyncNotifier { + late final _$args = ref.$arg as String; + String get archivePath => _$args; + + FutureOr build(String archivePath); + @$mustCallSuper + @override + void runBuild() { + final ref = + this.ref as $Ref, ParseArchiveState>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, ParseArchiveState>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} + +@ProviderFor(ParseArchiveSaveBook) +final parseArchiveSaveBookProvider = ParseArchiveSaveBookProvider._(); + +final class ParseArchiveSaveBookProvider + extends $AsyncNotifierProvider { + ParseArchiveSaveBookProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'parseArchiveSaveBookProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$parseArchiveSaveBookHash(); + + @$internal + @override + ParseArchiveSaveBook create() => ParseArchiveSaveBook(); +} + +String _$parseArchiveSaveBookHash() => + r'41bf64ce2c4064ea3a2d35995742e750a7d10c9e'; + +abstract class _$ParseArchiveSaveBook extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, void>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, void>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/lib/feature/parse/ui/provider/parse_batch_archive_provider.dart b/lib/feature/parse/ui/provider/parse_batch_archive_provider.dart new file mode 100644 index 0000000..31a36a5 --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_batch_archive_provider.dart @@ -0,0 +1,219 @@ +import 'dart:io'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_riverpod/legacy.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:tele_book/feature/book/model/dto/save_as_book_dto.dart'; +import 'package:tele_book/feature/book/repository/book_repository.dart'; +import 'package:tele_book/feature/parse/model/parse_batch_archive_vo.dart'; +import 'package:tele_book/feature/parse/service/parse_archive_service.dart'; + +part 'parse_batch_archive_provider.freezed.dart'; + +part 'parse_batch_archive_provider.g.dart'; + +@freezed +abstract class ParseBatchArchiveParam with _$ParseBatchArchiveParam { + const factory ParseBatchArchiveParam({ + String? archiveDirPath, + List? archivePaths, + }) = _ParseBatchArchiveParam; +} + +@freezed +abstract class ParseBatchArchiveState with _$ParseBatchArchiveState { + const factory ParseBatchArchiveState({ + required List parseBatchArchiveList, + }) = _ParseBatchArchiveState; +} + +@freezed +abstract class ParseBatchArchiveProgress with _$ParseBatchArchiveProgress { + const factory ParseBatchArchiveProgress({ + required int completeCount, + required int totalCount, + required String currentFileName, + required int currentFileProgress, + required int currentFileTotal, + }) = _ParseBatchArchiveProgress; + + const ParseBatchArchiveProgress._(); + + String get currentFileProgressText { + if (currentFileName.isEmpty) return ''; + if (currentFileTotal <= 0) return '当前文件进度:准备中'; + return '当前文件进度:$currentFileProgress / $currentFileTotal'; + } +} + +@freezed +abstract class ParseBatchArchiveSaveBookProgress + with _$ParseBatchArchiveSaveBookProgress { + const factory ParseBatchArchiveSaveBookProgress({ + @Default(0) int current, + @Default(0) int total, + }) = _ParseBatchArchiveSaveBookProgress; +} + +final parseBatchArchiveProgressProvider = StateProvider( + (_) => + const ParseBatchArchiveProgress( + completeCount: 0, + totalCount: 0, + currentFileName: '', + currentFileProgress: 0, + currentFileTotal: 0, + ), +); + +final parseBatchArchiveSaveBookProgressProvider = + StateProvider( + (_) => const ParseBatchArchiveSaveBookProgress(), + ); + +@riverpod +class ParseBatchArchive extends _$ParseBatchArchive { + ParseArchiveService get _parseArchiveService => + ref.read(parseArchiveServiceProvider); + + late final ParseBatchArchiveParam _param; + + @override + FutureOr build(ParseBatchArchiveParam param) async { + _param = param; + Future.microtask(_parseBatchArchive); + return const ParseBatchArchiveState(parseBatchArchiveList: []); + } + + Future _requestStoragePermission() async { + if (!Platform.isAndroid) return true; + if (await Permission.manageExternalStorage.isGranted) return true; + final status = await Permission.manageExternalStorage.request(); + if (status.isGranted) return true; + + if (await Permission.storage.isGranted) return true; + final storageStatus = await Permission.storage.request(); + return storageStatus.isGranted; + } + + Future _parseBatchArchive() async { + state = const AsyncLoading(); + ref.read(parseBatchArchiveProgressProvider.notifier).state = + const ParseBatchArchiveProgress( + completeCount: 0, + totalCount: 0, + currentFileName: '', + currentFileProgress: 0, + currentFileTotal: 0, + ); + + final hasPermission = await _requestStoragePermission(); + if (!hasPermission) { + state = AsyncError( + '需要「所有文件访问权限」才能读取外部目录,请在系统设置中授权后重试。', + StackTrace.current, + ); + return; + } + + final parseResult = _param.archivePaths != null && + _param.archivePaths!.isNotEmpty + ? await _parseArchiveService.parseBatchArchivesFromPaths( + _param.archivePaths!, + _onStart, + _onProgress, + onCurrentItemChanged: _onCurrentItemChanged, + onCurrentItemProgress: _onCurrentItemProgress, + ) + : await _parseArchiveService.parseBatchArchives( + _param.archiveDirPath ?? '', + _onStart, + _onProgress, + onCurrentItemChanged: _onCurrentItemChanged, + onCurrentItemProgress: _onCurrentItemProgress, + ); + + parseResult.fold( + onSuccess: (data) { + state = AsyncData(ParseBatchArchiveState(parseBatchArchiveList: data)); + }, + onError: (error) { + state = AsyncError(error.message, StackTrace.current); + }, + ); + } + + void _onStart(int total) { + final current = ref.read(parseBatchArchiveProgressProvider); + ref.read(parseBatchArchiveProgressProvider.notifier).state = + current.copyWith(totalCount: total); + } + + void _onProgress(int count) { + final current = ref.read(parseBatchArchiveProgressProvider); + ref.read(parseBatchArchiveProgressProvider.notifier).state = + current.copyWith(completeCount: count); + } + + void _onCurrentItemChanged(String fileName) { + final current = ref.read(parseBatchArchiveProgressProvider); + ref.read(parseBatchArchiveProgressProvider.notifier).state = + current.copyWith( + currentFileName: fileName, + currentFileProgress: 0, + currentFileTotal: 0, + ); + } + + void _onCurrentItemProgress(int currentCount, int total) { + final current = ref.read(parseBatchArchiveProgressProvider); + ref.read(parseBatchArchiveProgressProvider.notifier).state = + current.copyWith( + currentFileProgress: currentCount, + currentFileTotal: total, + ); + } +} + +@riverpod +class ParseBatchArchiveSaveBook extends _$ParseBatchArchiveSaveBook { + BookRepository get _bookRepository => ref.read(bookRepositoryProvider); + + @override + FutureOr build() => null; + + Future saveBatchAsBook(List parseBatchList) async { + if (state.isLoading || parseBatchList.isEmpty) return; + + state = const AsyncLoading(); + ref.read(parseBatchArchiveSaveBookProgressProvider.notifier).state = + ParseBatchArchiveSaveBookProgress( + current: 0, + total: parseBatchList.length, + ); + + final dos = parseBatchList + .map((e) => SaveAsBookDto(title: e.name, paths: e.tempPaths)) + .toList(); + + final result = await _bookRepository.saveBatchAsBooks(dos, (count) { + ref.read(parseBatchArchiveSaveBookProgressProvider.notifier).state = + ParseBatchArchiveSaveBookProgress( + current: count, + total: parseBatchList.length, + ); + }); + + result.fold( + onSuccess: (_) { + state = const AsyncData(null); + }, + onError: (error) { + state = AsyncError(error.message, StackTrace.current); + }, + ); + } +} + diff --git a/lib/feature/parse/ui/provider/parse_batch_archive_provider.freezed.dart b/lib/feature/parse/ui/provider/parse_batch_archive_provider.freezed.dart new file mode 100644 index 0000000..588ac4f --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_batch_archive_provider.freezed.dart @@ -0,0 +1,1074 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'parse_batch_archive_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$ParseBatchArchiveParam { + + String? get archiveDirPath; List? get archivePaths; +/// Create a copy of ParseBatchArchiveParam +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseBatchArchiveParamCopyWith get copyWith => _$ParseBatchArchiveParamCopyWithImpl(this as ParseBatchArchiveParam, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseBatchArchiveParam&&(identical(other.archiveDirPath, archiveDirPath) || other.archiveDirPath == archiveDirPath)&&const DeepCollectionEquality().equals(other.archivePaths, archivePaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,archiveDirPath,const DeepCollectionEquality().hash(archivePaths)); + +@override +String toString() { + return 'ParseBatchArchiveParam(archiveDirPath: $archiveDirPath, archivePaths: $archivePaths)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseBatchArchiveParamCopyWith<$Res> { + factory $ParseBatchArchiveParamCopyWith(ParseBatchArchiveParam value, $Res Function(ParseBatchArchiveParam) _then) = _$ParseBatchArchiveParamCopyWithImpl; +@useResult +$Res call({ + String? archiveDirPath, List? archivePaths +}); + + + + +} +/// @nodoc +class _$ParseBatchArchiveParamCopyWithImpl<$Res> + implements $ParseBatchArchiveParamCopyWith<$Res> { + _$ParseBatchArchiveParamCopyWithImpl(this._self, this._then); + + final ParseBatchArchiveParam _self; + final $Res Function(ParseBatchArchiveParam) _then; + +/// Create a copy of ParseBatchArchiveParam +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? archiveDirPath = freezed,Object? archivePaths = freezed,}) { + return _then(_self.copyWith( +archiveDirPath: freezed == archiveDirPath ? _self.archiveDirPath : archiveDirPath // ignore: cast_nullable_to_non_nullable +as String?,archivePaths: freezed == archivePaths ? _self.archivePaths : archivePaths // ignore: cast_nullable_to_non_nullable +as List?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseBatchArchiveParam]. +extension ParseBatchArchiveParamPatterns on ParseBatchArchiveParam { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseBatchArchiveParam value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseBatchArchiveParam() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseBatchArchiveParam value) $default,){ +final _that = this; +switch (_that) { +case _ParseBatchArchiveParam(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseBatchArchiveParam value)? $default,){ +final _that = this; +switch (_that) { +case _ParseBatchArchiveParam() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String? archiveDirPath, List? archivePaths)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseBatchArchiveParam() when $default != null: +return $default(_that.archiveDirPath,_that.archivePaths);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String? archiveDirPath, List? archivePaths) $default,) {final _that = this; +switch (_that) { +case _ParseBatchArchiveParam(): +return $default(_that.archiveDirPath,_that.archivePaths);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? archiveDirPath, List? archivePaths)? $default,) {final _that = this; +switch (_that) { +case _ParseBatchArchiveParam() when $default != null: +return $default(_that.archiveDirPath,_that.archivePaths);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseBatchArchiveParam implements ParseBatchArchiveParam { + const _ParseBatchArchiveParam({this.archiveDirPath, final List? archivePaths}): _archivePaths = archivePaths; + + +@override final String? archiveDirPath; + final List? _archivePaths; +@override List? get archivePaths { + final value = _archivePaths; + if (value == null) return null; + if (_archivePaths is EqualUnmodifiableListView) return _archivePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); +} + + +/// Create a copy of ParseBatchArchiveParam +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseBatchArchiveParamCopyWith<_ParseBatchArchiveParam> get copyWith => __$ParseBatchArchiveParamCopyWithImpl<_ParseBatchArchiveParam>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseBatchArchiveParam&&(identical(other.archiveDirPath, archiveDirPath) || other.archiveDirPath == archiveDirPath)&&const DeepCollectionEquality().equals(other._archivePaths, _archivePaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,archiveDirPath,const DeepCollectionEquality().hash(_archivePaths)); + +@override +String toString() { + return 'ParseBatchArchiveParam(archiveDirPath: $archiveDirPath, archivePaths: $archivePaths)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseBatchArchiveParamCopyWith<$Res> implements $ParseBatchArchiveParamCopyWith<$Res> { + factory _$ParseBatchArchiveParamCopyWith(_ParseBatchArchiveParam value, $Res Function(_ParseBatchArchiveParam) _then) = __$ParseBatchArchiveParamCopyWithImpl; +@override @useResult +$Res call({ + String? archiveDirPath, List? archivePaths +}); + + + + +} +/// @nodoc +class __$ParseBatchArchiveParamCopyWithImpl<$Res> + implements _$ParseBatchArchiveParamCopyWith<$Res> { + __$ParseBatchArchiveParamCopyWithImpl(this._self, this._then); + + final _ParseBatchArchiveParam _self; + final $Res Function(_ParseBatchArchiveParam) _then; + +/// Create a copy of ParseBatchArchiveParam +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? archiveDirPath = freezed,Object? archivePaths = freezed,}) { + return _then(_ParseBatchArchiveParam( +archiveDirPath: freezed == archiveDirPath ? _self.archiveDirPath : archiveDirPath // ignore: cast_nullable_to_non_nullable +as String?,archivePaths: freezed == archivePaths ? _self._archivePaths : archivePaths // ignore: cast_nullable_to_non_nullable +as List?, + )); +} + + +} + +/// @nodoc +mixin _$ParseBatchArchiveState { + + List get parseBatchArchiveList; +/// Create a copy of ParseBatchArchiveState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseBatchArchiveStateCopyWith get copyWith => _$ParseBatchArchiveStateCopyWithImpl(this as ParseBatchArchiveState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseBatchArchiveState&&const DeepCollectionEquality().equals(other.parseBatchArchiveList, parseBatchArchiveList)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(parseBatchArchiveList)); + +@override +String toString() { + return 'ParseBatchArchiveState(parseBatchArchiveList: $parseBatchArchiveList)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseBatchArchiveStateCopyWith<$Res> { + factory $ParseBatchArchiveStateCopyWith(ParseBatchArchiveState value, $Res Function(ParseBatchArchiveState) _then) = _$ParseBatchArchiveStateCopyWithImpl; +@useResult +$Res call({ + List parseBatchArchiveList +}); + + + + +} +/// @nodoc +class _$ParseBatchArchiveStateCopyWithImpl<$Res> + implements $ParseBatchArchiveStateCopyWith<$Res> { + _$ParseBatchArchiveStateCopyWithImpl(this._self, this._then); + + final ParseBatchArchiveState _self; + final $Res Function(ParseBatchArchiveState) _then; + +/// Create a copy of ParseBatchArchiveState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? parseBatchArchiveList = null,}) { + return _then(_self.copyWith( +parseBatchArchiveList: null == parseBatchArchiveList ? _self.parseBatchArchiveList : parseBatchArchiveList // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseBatchArchiveState]. +extension ParseBatchArchiveStatePatterns on ParseBatchArchiveState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseBatchArchiveState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseBatchArchiveState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseBatchArchiveState value) $default,){ +final _that = this; +switch (_that) { +case _ParseBatchArchiveState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseBatchArchiveState value)? $default,){ +final _that = this; +switch (_that) { +case _ParseBatchArchiveState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( List parseBatchArchiveList)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseBatchArchiveState() when $default != null: +return $default(_that.parseBatchArchiveList);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( List parseBatchArchiveList) $default,) {final _that = this; +switch (_that) { +case _ParseBatchArchiveState(): +return $default(_that.parseBatchArchiveList);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( List parseBatchArchiveList)? $default,) {final _that = this; +switch (_that) { +case _ParseBatchArchiveState() when $default != null: +return $default(_that.parseBatchArchiveList);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseBatchArchiveState implements ParseBatchArchiveState { + const _ParseBatchArchiveState({required final List parseBatchArchiveList}): _parseBatchArchiveList = parseBatchArchiveList; + + + final List _parseBatchArchiveList; +@override List get parseBatchArchiveList { + if (_parseBatchArchiveList is EqualUnmodifiableListView) return _parseBatchArchiveList; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_parseBatchArchiveList); +} + + +/// Create a copy of ParseBatchArchiveState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseBatchArchiveStateCopyWith<_ParseBatchArchiveState> get copyWith => __$ParseBatchArchiveStateCopyWithImpl<_ParseBatchArchiveState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseBatchArchiveState&&const DeepCollectionEquality().equals(other._parseBatchArchiveList, _parseBatchArchiveList)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_parseBatchArchiveList)); + +@override +String toString() { + return 'ParseBatchArchiveState(parseBatchArchiveList: $parseBatchArchiveList)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseBatchArchiveStateCopyWith<$Res> implements $ParseBatchArchiveStateCopyWith<$Res> { + factory _$ParseBatchArchiveStateCopyWith(_ParseBatchArchiveState value, $Res Function(_ParseBatchArchiveState) _then) = __$ParseBatchArchiveStateCopyWithImpl; +@override @useResult +$Res call({ + List parseBatchArchiveList +}); + + + + +} +/// @nodoc +class __$ParseBatchArchiveStateCopyWithImpl<$Res> + implements _$ParseBatchArchiveStateCopyWith<$Res> { + __$ParseBatchArchiveStateCopyWithImpl(this._self, this._then); + + final _ParseBatchArchiveState _self; + final $Res Function(_ParseBatchArchiveState) _then; + +/// Create a copy of ParseBatchArchiveState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? parseBatchArchiveList = null,}) { + return _then(_ParseBatchArchiveState( +parseBatchArchiveList: null == parseBatchArchiveList ? _self._parseBatchArchiveList : parseBatchArchiveList // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +/// @nodoc +mixin _$ParseBatchArchiveProgress { + + int get completeCount; int get totalCount; String get currentFileName; int get currentFileProgress; int get currentFileTotal; +/// Create a copy of ParseBatchArchiveProgress +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseBatchArchiveProgressCopyWith get copyWith => _$ParseBatchArchiveProgressCopyWithImpl(this as ParseBatchArchiveProgress, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseBatchArchiveProgress&&(identical(other.completeCount, completeCount) || other.completeCount == completeCount)&&(identical(other.totalCount, totalCount) || other.totalCount == totalCount)&&(identical(other.currentFileName, currentFileName) || other.currentFileName == currentFileName)&&(identical(other.currentFileProgress, currentFileProgress) || other.currentFileProgress == currentFileProgress)&&(identical(other.currentFileTotal, currentFileTotal) || other.currentFileTotal == currentFileTotal)); +} + + +@override +int get hashCode => Object.hash(runtimeType,completeCount,totalCount,currentFileName,currentFileProgress,currentFileTotal); + +@override +String toString() { + return 'ParseBatchArchiveProgress(completeCount: $completeCount, totalCount: $totalCount, currentFileName: $currentFileName, currentFileProgress: $currentFileProgress, currentFileTotal: $currentFileTotal)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseBatchArchiveProgressCopyWith<$Res> { + factory $ParseBatchArchiveProgressCopyWith(ParseBatchArchiveProgress value, $Res Function(ParseBatchArchiveProgress) _then) = _$ParseBatchArchiveProgressCopyWithImpl; +@useResult +$Res call({ + int completeCount, int totalCount, String currentFileName, int currentFileProgress, int currentFileTotal +}); + + + + +} +/// @nodoc +class _$ParseBatchArchiveProgressCopyWithImpl<$Res> + implements $ParseBatchArchiveProgressCopyWith<$Res> { + _$ParseBatchArchiveProgressCopyWithImpl(this._self, this._then); + + final ParseBatchArchiveProgress _self; + final $Res Function(ParseBatchArchiveProgress) _then; + +/// Create a copy of ParseBatchArchiveProgress +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? completeCount = null,Object? totalCount = null,Object? currentFileName = null,Object? currentFileProgress = null,Object? currentFileTotal = null,}) { + return _then(_self.copyWith( +completeCount: null == completeCount ? _self.completeCount : completeCount // ignore: cast_nullable_to_non_nullable +as int,totalCount: null == totalCount ? _self.totalCount : totalCount // ignore: cast_nullable_to_non_nullable +as int,currentFileName: null == currentFileName ? _self.currentFileName : currentFileName // ignore: cast_nullable_to_non_nullable +as String,currentFileProgress: null == currentFileProgress ? _self.currentFileProgress : currentFileProgress // ignore: cast_nullable_to_non_nullable +as int,currentFileTotal: null == currentFileTotal ? _self.currentFileTotal : currentFileTotal // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseBatchArchiveProgress]. +extension ParseBatchArchiveProgressPatterns on ParseBatchArchiveProgress { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseBatchArchiveProgress value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseBatchArchiveProgress() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseBatchArchiveProgress value) $default,){ +final _that = this; +switch (_that) { +case _ParseBatchArchiveProgress(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseBatchArchiveProgress value)? $default,){ +final _that = this; +switch (_that) { +case _ParseBatchArchiveProgress() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( int completeCount, int totalCount, String currentFileName, int currentFileProgress, int currentFileTotal)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseBatchArchiveProgress() when $default != null: +return $default(_that.completeCount,_that.totalCount,_that.currentFileName,_that.currentFileProgress,_that.currentFileTotal);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( int completeCount, int totalCount, String currentFileName, int currentFileProgress, int currentFileTotal) $default,) {final _that = this; +switch (_that) { +case _ParseBatchArchiveProgress(): +return $default(_that.completeCount,_that.totalCount,_that.currentFileName,_that.currentFileProgress,_that.currentFileTotal);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( int completeCount, int totalCount, String currentFileName, int currentFileProgress, int currentFileTotal)? $default,) {final _that = this; +switch (_that) { +case _ParseBatchArchiveProgress() when $default != null: +return $default(_that.completeCount,_that.totalCount,_that.currentFileName,_that.currentFileProgress,_that.currentFileTotal);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseBatchArchiveProgress extends ParseBatchArchiveProgress { + const _ParseBatchArchiveProgress({required this.completeCount, required this.totalCount, required this.currentFileName, required this.currentFileProgress, required this.currentFileTotal}): super._(); + + +@override final int completeCount; +@override final int totalCount; +@override final String currentFileName; +@override final int currentFileProgress; +@override final int currentFileTotal; + +/// Create a copy of ParseBatchArchiveProgress +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseBatchArchiveProgressCopyWith<_ParseBatchArchiveProgress> get copyWith => __$ParseBatchArchiveProgressCopyWithImpl<_ParseBatchArchiveProgress>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseBatchArchiveProgress&&(identical(other.completeCount, completeCount) || other.completeCount == completeCount)&&(identical(other.totalCount, totalCount) || other.totalCount == totalCount)&&(identical(other.currentFileName, currentFileName) || other.currentFileName == currentFileName)&&(identical(other.currentFileProgress, currentFileProgress) || other.currentFileProgress == currentFileProgress)&&(identical(other.currentFileTotal, currentFileTotal) || other.currentFileTotal == currentFileTotal)); +} + + +@override +int get hashCode => Object.hash(runtimeType,completeCount,totalCount,currentFileName,currentFileProgress,currentFileTotal); + +@override +String toString() { + return 'ParseBatchArchiveProgress(completeCount: $completeCount, totalCount: $totalCount, currentFileName: $currentFileName, currentFileProgress: $currentFileProgress, currentFileTotal: $currentFileTotal)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseBatchArchiveProgressCopyWith<$Res> implements $ParseBatchArchiveProgressCopyWith<$Res> { + factory _$ParseBatchArchiveProgressCopyWith(_ParseBatchArchiveProgress value, $Res Function(_ParseBatchArchiveProgress) _then) = __$ParseBatchArchiveProgressCopyWithImpl; +@override @useResult +$Res call({ + int completeCount, int totalCount, String currentFileName, int currentFileProgress, int currentFileTotal +}); + + + + +} +/// @nodoc +class __$ParseBatchArchiveProgressCopyWithImpl<$Res> + implements _$ParseBatchArchiveProgressCopyWith<$Res> { + __$ParseBatchArchiveProgressCopyWithImpl(this._self, this._then); + + final _ParseBatchArchiveProgress _self; + final $Res Function(_ParseBatchArchiveProgress) _then; + +/// Create a copy of ParseBatchArchiveProgress +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? completeCount = null,Object? totalCount = null,Object? currentFileName = null,Object? currentFileProgress = null,Object? currentFileTotal = null,}) { + return _then(_ParseBatchArchiveProgress( +completeCount: null == completeCount ? _self.completeCount : completeCount // ignore: cast_nullable_to_non_nullable +as int,totalCount: null == totalCount ? _self.totalCount : totalCount // ignore: cast_nullable_to_non_nullable +as int,currentFileName: null == currentFileName ? _self.currentFileName : currentFileName // ignore: cast_nullable_to_non_nullable +as String,currentFileProgress: null == currentFileProgress ? _self.currentFileProgress : currentFileProgress // ignore: cast_nullable_to_non_nullable +as int,currentFileTotal: null == currentFileTotal ? _self.currentFileTotal : currentFileTotal // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +/// @nodoc +mixin _$ParseBatchArchiveSaveBookProgress { + + int get current; int get total; +/// Create a copy of ParseBatchArchiveSaveBookProgress +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseBatchArchiveSaveBookProgressCopyWith get copyWith => _$ParseBatchArchiveSaveBookProgressCopyWithImpl(this as ParseBatchArchiveSaveBookProgress, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseBatchArchiveSaveBookProgress&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); +} + + +@override +int get hashCode => Object.hash(runtimeType,current,total); + +@override +String toString() { + return 'ParseBatchArchiveSaveBookProgress(current: $current, total: $total)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseBatchArchiveSaveBookProgressCopyWith<$Res> { + factory $ParseBatchArchiveSaveBookProgressCopyWith(ParseBatchArchiveSaveBookProgress value, $Res Function(ParseBatchArchiveSaveBookProgress) _then) = _$ParseBatchArchiveSaveBookProgressCopyWithImpl; +@useResult +$Res call({ + int current, int total +}); + + + + +} +/// @nodoc +class _$ParseBatchArchiveSaveBookProgressCopyWithImpl<$Res> + implements $ParseBatchArchiveSaveBookProgressCopyWith<$Res> { + _$ParseBatchArchiveSaveBookProgressCopyWithImpl(this._self, this._then); + + final ParseBatchArchiveSaveBookProgress _self; + final $Res Function(ParseBatchArchiveSaveBookProgress) _then; + +/// Create a copy of ParseBatchArchiveSaveBookProgress +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? current = null,Object? total = null,}) { + return _then(_self.copyWith( +current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable +as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseBatchArchiveSaveBookProgress]. +extension ParseBatchArchiveSaveBookProgressPatterns on ParseBatchArchiveSaveBookProgress { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseBatchArchiveSaveBookProgress value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseBatchArchiveSaveBookProgress() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseBatchArchiveSaveBookProgress value) $default,){ +final _that = this; +switch (_that) { +case _ParseBatchArchiveSaveBookProgress(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseBatchArchiveSaveBookProgress value)? $default,){ +final _that = this; +switch (_that) { +case _ParseBatchArchiveSaveBookProgress() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( int current, int total)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseBatchArchiveSaveBookProgress() when $default != null: +return $default(_that.current,_that.total);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( int current, int total) $default,) {final _that = this; +switch (_that) { +case _ParseBatchArchiveSaveBookProgress(): +return $default(_that.current,_that.total);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( int current, int total)? $default,) {final _that = this; +switch (_that) { +case _ParseBatchArchiveSaveBookProgress() when $default != null: +return $default(_that.current,_that.total);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseBatchArchiveSaveBookProgress implements ParseBatchArchiveSaveBookProgress { + const _ParseBatchArchiveSaveBookProgress({this.current = 0, this.total = 0}); + + +@override@JsonKey() final int current; +@override@JsonKey() final int total; + +/// Create a copy of ParseBatchArchiveSaveBookProgress +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseBatchArchiveSaveBookProgressCopyWith<_ParseBatchArchiveSaveBookProgress> get copyWith => __$ParseBatchArchiveSaveBookProgressCopyWithImpl<_ParseBatchArchiveSaveBookProgress>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseBatchArchiveSaveBookProgress&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); +} + + +@override +int get hashCode => Object.hash(runtimeType,current,total); + +@override +String toString() { + return 'ParseBatchArchiveSaveBookProgress(current: $current, total: $total)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseBatchArchiveSaveBookProgressCopyWith<$Res> implements $ParseBatchArchiveSaveBookProgressCopyWith<$Res> { + factory _$ParseBatchArchiveSaveBookProgressCopyWith(_ParseBatchArchiveSaveBookProgress value, $Res Function(_ParseBatchArchiveSaveBookProgress) _then) = __$ParseBatchArchiveSaveBookProgressCopyWithImpl; +@override @useResult +$Res call({ + int current, int total +}); + + + + +} +/// @nodoc +class __$ParseBatchArchiveSaveBookProgressCopyWithImpl<$Res> + implements _$ParseBatchArchiveSaveBookProgressCopyWith<$Res> { + __$ParseBatchArchiveSaveBookProgressCopyWithImpl(this._self, this._then); + + final _ParseBatchArchiveSaveBookProgress _self; + final $Res Function(_ParseBatchArchiveSaveBookProgress) _then; + +/// Create a copy of ParseBatchArchiveSaveBookProgress +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? current = null,Object? total = null,}) { + return _then(_ParseBatchArchiveSaveBookProgress( +current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable +as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +// dart format on diff --git a/lib/feature/parse/ui/provider/parse_batch_archive_provider.g.dart b/lib/feature/parse/ui/provider/parse_batch_archive_provider.g.dart new file mode 100644 index 0000000..3d6138f --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_batch_archive_provider.g.dart @@ -0,0 +1,150 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'parse_batch_archive_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(ParseBatchArchive) +final parseBatchArchiveProvider = ParseBatchArchiveFamily._(); + +final class ParseBatchArchiveProvider + extends $AsyncNotifierProvider { + ParseBatchArchiveProvider._({ + required ParseBatchArchiveFamily super.from, + required ParseBatchArchiveParam super.argument, + }) : super( + retry: null, + name: r'parseBatchArchiveProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$parseBatchArchiveHash(); + + @override + String toString() { + return r'parseBatchArchiveProvider' + '' + '($argument)'; + } + + @$internal + @override + ParseBatchArchive create() => ParseBatchArchive(); + + @override + bool operator ==(Object other) { + return other is ParseBatchArchiveProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$parseBatchArchiveHash() => r'4c2d0a3ec18de530d0397b1453fe0bbdaf5e5639'; + +final class ParseBatchArchiveFamily extends $Family + with + $ClassFamilyOverride< + ParseBatchArchive, + AsyncValue, + ParseBatchArchiveState, + FutureOr, + ParseBatchArchiveParam + > { + ParseBatchArchiveFamily._() + : super( + retry: null, + name: r'parseBatchArchiveProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + ParseBatchArchiveProvider call(ParseBatchArchiveParam param) => + ParseBatchArchiveProvider._(argument: param, from: this); + + @override + String toString() => r'parseBatchArchiveProvider'; +} + +abstract class _$ParseBatchArchive + extends $AsyncNotifier { + late final _$args = ref.$arg as ParseBatchArchiveParam; + ParseBatchArchiveParam get param => _$args; + + FutureOr build(ParseBatchArchiveParam param); + @$mustCallSuper + @override + void runBuild() { + final ref = + this.ref + as $Ref, ParseBatchArchiveState>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier< + AsyncValue, + ParseBatchArchiveState + >, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} + +@ProviderFor(ParseBatchArchiveSaveBook) +final parseBatchArchiveSaveBookProvider = ParseBatchArchiveSaveBookProvider._(); + +final class ParseBatchArchiveSaveBookProvider + extends $AsyncNotifierProvider { + ParseBatchArchiveSaveBookProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'parseBatchArchiveSaveBookProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$parseBatchArchiveSaveBookHash(); + + @$internal + @override + ParseBatchArchiveSaveBook create() => ParseBatchArchiveSaveBook(); +} + +String _$parseBatchArchiveSaveBookHash() => + r'990b49c199855638b06aa45a0256fa98ba4a739f'; + +abstract class _$ParseBatchArchiveSaveBook extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, void>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, void>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/lib/feature/parse/ui/provider/parse_batch_image_folder.dart b/lib/feature/parse/ui/provider/parse_batch_image_folder.dart new file mode 100644 index 0000000..9bd525a --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_batch_image_folder.dart @@ -0,0 +1,213 @@ +import 'dart:io'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:tele_book/feature/book/model/dto/save_as_book_dto.dart'; +import 'package:tele_book/feature/book/repository/book_repository.dart'; +import 'package:tele_book/feature/parse/model/parse_batch_archive_vo.dart'; +import 'package:tele_book/feature/parse/service/parse_archive_service.dart'; + +part 'parse_batch_image_folder.freezed.dart'; + +part 'parse_batch_image_folder.g.dart'; + +@freezed +abstract class ParseBatchImageFolderState with _$ParseBatchImageFolderState { + const factory ParseBatchImageFolderState({ + @Default([]) List parseBatchFolderList, + @Default(0) int completeCount, + @Default(0) int totalCount, + @Default('') String currentFileName, + @Default(0) int currentFileProgress, + @Default(0) int currentFileTotal, + @Default(false) bool isParsing, + }) = _ParseBatchImageFolderState; + + const ParseBatchImageFolderState._(); + + String get currentFileProgressText { + if (currentFileName.isEmpty) return ''; + if (currentFileTotal <= 0) return '当前文件进度,准备中'; + return '当前文件进度,$currentFileProgress / $currentFileTotal'; + } +} + +@freezed +abstract class SaveBatchAsBookState with _$SaveBatchAsBookState { + const factory SaveBatchAsBookState({ + @Default(0) int saveAsBookCount, + @Default(0) int totalCount, + @Default(AsyncData(null)) AsyncValue submitState, + }) = _SaveBatchAsBookState; +} + +@freezed +abstract class ParseBatchImageFolderParam with _$ParseBatchImageFolderParam { + const factory ParseBatchImageFolderParam({ + String? parentDirPath, + List? imagePaths, + }) = _ParseBatchImageFolderParam; +} + +@riverpod +class ParseBatchImageFolder extends _$ParseBatchImageFolder { + ParseArchiveService get _parseArchiveService => + ref.read(parseArchiveServiceProvider); + + @override + FutureOr build(ParseBatchImageFolderParam param) { + Future.microtask(() => _parseBatchFolders(param)); + return const ParseBatchImageFolderState(isParsing: true); + } + + Future _requestStoragePermission() async { + if (!Platform.isAndroid) return true; + if (await Permission.manageExternalStorage.isGranted) return true; + final status = await Permission.manageExternalStorage.request(); + if (status.isGranted) return true; + + if (await Permission.storage.isGranted) return true; + final storageStatus = await Permission.storage.request(); + return storageStatus.isGranted; + } + + Future _parseBatchFolders(ParseBatchImageFolderParam param) async { + state = AsyncData( + state.value?.copyWith( + isParsing: true, + parseBatchFolderList: const [], + totalCount: 0, + completeCount: 0, + currentFileName: '', + currentFileProgress: 0, + currentFileTotal: 0, + ) ?? + const ParseBatchImageFolderState(isParsing: true), + ); + + final hasPermission = await _requestStoragePermission(); + if (!hasPermission) { + state = AsyncValue.error("需要存储权限才能读取图片文件夹", StackTrace.current); + return; + } + + var localState = state.value ?? + const ParseBatchImageFolderState(isParsing: true); + + final result = param.imagePaths != null && param.imagePaths!.isNotEmpty + ? await _parseArchiveService.parseBatchImageFoldersFromPaths( + param.imagePaths!, + (total) { + localState = localState.copyWith(totalCount: total); + state = AsyncData(localState); + }, + (count) { + localState = localState.copyWith(completeCount: count); + state = AsyncData(localState); + }, + onCurrentItemChanged: (fileName) { + localState = localState.copyWith( + currentFileName: fileName, + currentFileProgress: 0, + currentFileTotal: 0, + ); + state = AsyncData(localState); + }, + onCurrentItemProgress: (current, total) { + localState = localState.copyWith( + currentFileProgress: current, + currentFileTotal: total, + ); + state = AsyncData(localState); + }, + ) + : await _parseArchiveService.parseBatchImageFolders( + param.parentDirPath ?? '', + (total) { + localState = localState.copyWith(totalCount: total); + state = AsyncData(localState); + }, + (count) { + localState = localState.copyWith(completeCount: count); + state = AsyncData(localState); + }, + onCurrentItemChanged: (fileName) { + localState = localState.copyWith( + currentFileName: fileName, + currentFileProgress: 0, + currentFileTotal: 0, + ); + state = AsyncData(localState); + }, + onCurrentItemProgress: (current, total) { + localState = localState.copyWith( + currentFileProgress: current, + currentFileTotal: total, + ); + state = AsyncData(localState); + }, + ); + + result.fold( + onSuccess: (data) { + state = AsyncData(localState.copyWith( + parseBatchFolderList: data, + isParsing: false, + )); + }, + onError: (error) { + state = AsyncValue.error(error.message, StackTrace.current); + }, + ); + } + + Future refresh() async { + await _parseBatchFolders(param); + } +} + +@riverpod +class SaveBatchAsBook extends _$SaveBatchAsBook { + BookRepository get _bookRepository => ref.read(bookRepositoryProvider); + + @override + SaveBatchAsBookState build(ParseBatchImageFolderParam param) { + return const SaveBatchAsBookState(); + } + + Future submit(List parseList) async { + if (state.submitState.isLoading) return; + state = state.copyWith( + submitState: const AsyncLoading(), + saveAsBookCount: 0, + totalCount: parseList.length, + ); + + final dos = parseList + .map((e) => SaveAsBookDto(title: e.name, paths: e.tempPaths)) + .toList(); + + final result = await _bookRepository.saveBatchAsBooks(dos, (count) { + state = state.copyWith(saveAsBookCount: count); + }); + + result.fold( + onSuccess: (_) { + state = state.copyWith( + submitState: const AsyncData(null), + saveAsBookCount: 0, + ); + }, + onError: (error) { + state = state.copyWith( + submitState: AsyncValue.error( + error.message, + StackTrace.current, + ), + ); + }, + ); + } +} diff --git a/lib/feature/parse/ui/provider/parse_batch_image_folder.freezed.dart b/lib/feature/parse/ui/provider/parse_batch_image_folder.freezed.dart new file mode 100644 index 0000000..7241b02 --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_batch_image_folder.freezed.dart @@ -0,0 +1,826 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'parse_batch_image_folder.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$ParseBatchImageFolderState { + + List get parseBatchFolderList; int get completeCount; int get totalCount; String get currentFileName; int get currentFileProgress; int get currentFileTotal; bool get isParsing; +/// Create a copy of ParseBatchImageFolderState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseBatchImageFolderStateCopyWith get copyWith => _$ParseBatchImageFolderStateCopyWithImpl(this as ParseBatchImageFolderState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseBatchImageFolderState&&const DeepCollectionEquality().equals(other.parseBatchFolderList, parseBatchFolderList)&&(identical(other.completeCount, completeCount) || other.completeCount == completeCount)&&(identical(other.totalCount, totalCount) || other.totalCount == totalCount)&&(identical(other.currentFileName, currentFileName) || other.currentFileName == currentFileName)&&(identical(other.currentFileProgress, currentFileProgress) || other.currentFileProgress == currentFileProgress)&&(identical(other.currentFileTotal, currentFileTotal) || other.currentFileTotal == currentFileTotal)&&(identical(other.isParsing, isParsing) || other.isParsing == isParsing)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(parseBatchFolderList),completeCount,totalCount,currentFileName,currentFileProgress,currentFileTotal,isParsing); + +@override +String toString() { + return 'ParseBatchImageFolderState(parseBatchFolderList: $parseBatchFolderList, completeCount: $completeCount, totalCount: $totalCount, currentFileName: $currentFileName, currentFileProgress: $currentFileProgress, currentFileTotal: $currentFileTotal, isParsing: $isParsing)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseBatchImageFolderStateCopyWith<$Res> { + factory $ParseBatchImageFolderStateCopyWith(ParseBatchImageFolderState value, $Res Function(ParseBatchImageFolderState) _then) = _$ParseBatchImageFolderStateCopyWithImpl; +@useResult +$Res call({ + List parseBatchFolderList, int completeCount, int totalCount, String currentFileName, int currentFileProgress, int currentFileTotal, bool isParsing +}); + + + + +} +/// @nodoc +class _$ParseBatchImageFolderStateCopyWithImpl<$Res> + implements $ParseBatchImageFolderStateCopyWith<$Res> { + _$ParseBatchImageFolderStateCopyWithImpl(this._self, this._then); + + final ParseBatchImageFolderState _self; + final $Res Function(ParseBatchImageFolderState) _then; + +/// Create a copy of ParseBatchImageFolderState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? parseBatchFolderList = null,Object? completeCount = null,Object? totalCount = null,Object? currentFileName = null,Object? currentFileProgress = null,Object? currentFileTotal = null,Object? isParsing = null,}) { + return _then(_self.copyWith( +parseBatchFolderList: null == parseBatchFolderList ? _self.parseBatchFolderList : parseBatchFolderList // ignore: cast_nullable_to_non_nullable +as List,completeCount: null == completeCount ? _self.completeCount : completeCount // ignore: cast_nullable_to_non_nullable +as int,totalCount: null == totalCount ? _self.totalCount : totalCount // ignore: cast_nullable_to_non_nullable +as int,currentFileName: null == currentFileName ? _self.currentFileName : currentFileName // ignore: cast_nullable_to_non_nullable +as String,currentFileProgress: null == currentFileProgress ? _self.currentFileProgress : currentFileProgress // ignore: cast_nullable_to_non_nullable +as int,currentFileTotal: null == currentFileTotal ? _self.currentFileTotal : currentFileTotal // ignore: cast_nullable_to_non_nullable +as int,isParsing: null == isParsing ? _self.isParsing : isParsing // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseBatchImageFolderState]. +extension ParseBatchImageFolderStatePatterns on ParseBatchImageFolderState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseBatchImageFolderState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseBatchImageFolderState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseBatchImageFolderState value) $default,){ +final _that = this; +switch (_that) { +case _ParseBatchImageFolderState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseBatchImageFolderState value)? $default,){ +final _that = this; +switch (_that) { +case _ParseBatchImageFolderState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( List parseBatchFolderList, int completeCount, int totalCount, String currentFileName, int currentFileProgress, int currentFileTotal, bool isParsing)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseBatchImageFolderState() when $default != null: +return $default(_that.parseBatchFolderList,_that.completeCount,_that.totalCount,_that.currentFileName,_that.currentFileProgress,_that.currentFileTotal,_that.isParsing);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( List parseBatchFolderList, int completeCount, int totalCount, String currentFileName, int currentFileProgress, int currentFileTotal, bool isParsing) $default,) {final _that = this; +switch (_that) { +case _ParseBatchImageFolderState(): +return $default(_that.parseBatchFolderList,_that.completeCount,_that.totalCount,_that.currentFileName,_that.currentFileProgress,_that.currentFileTotal,_that.isParsing);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( List parseBatchFolderList, int completeCount, int totalCount, String currentFileName, int currentFileProgress, int currentFileTotal, bool isParsing)? $default,) {final _that = this; +switch (_that) { +case _ParseBatchImageFolderState() when $default != null: +return $default(_that.parseBatchFolderList,_that.completeCount,_that.totalCount,_that.currentFileName,_that.currentFileProgress,_that.currentFileTotal,_that.isParsing);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseBatchImageFolderState extends ParseBatchImageFolderState { + const _ParseBatchImageFolderState({final List parseBatchFolderList = const [], this.completeCount = 0, this.totalCount = 0, this.currentFileName = '', this.currentFileProgress = 0, this.currentFileTotal = 0, this.isParsing = false}): _parseBatchFolderList = parseBatchFolderList,super._(); + + + final List _parseBatchFolderList; +@override@JsonKey() List get parseBatchFolderList { + if (_parseBatchFolderList is EqualUnmodifiableListView) return _parseBatchFolderList; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_parseBatchFolderList); +} + +@override@JsonKey() final int completeCount; +@override@JsonKey() final int totalCount; +@override@JsonKey() final String currentFileName; +@override@JsonKey() final int currentFileProgress; +@override@JsonKey() final int currentFileTotal; +@override@JsonKey() final bool isParsing; + +/// Create a copy of ParseBatchImageFolderState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseBatchImageFolderStateCopyWith<_ParseBatchImageFolderState> get copyWith => __$ParseBatchImageFolderStateCopyWithImpl<_ParseBatchImageFolderState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseBatchImageFolderState&&const DeepCollectionEquality().equals(other._parseBatchFolderList, _parseBatchFolderList)&&(identical(other.completeCount, completeCount) || other.completeCount == completeCount)&&(identical(other.totalCount, totalCount) || other.totalCount == totalCount)&&(identical(other.currentFileName, currentFileName) || other.currentFileName == currentFileName)&&(identical(other.currentFileProgress, currentFileProgress) || other.currentFileProgress == currentFileProgress)&&(identical(other.currentFileTotal, currentFileTotal) || other.currentFileTotal == currentFileTotal)&&(identical(other.isParsing, isParsing) || other.isParsing == isParsing)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_parseBatchFolderList),completeCount,totalCount,currentFileName,currentFileProgress,currentFileTotal,isParsing); + +@override +String toString() { + return 'ParseBatchImageFolderState(parseBatchFolderList: $parseBatchFolderList, completeCount: $completeCount, totalCount: $totalCount, currentFileName: $currentFileName, currentFileProgress: $currentFileProgress, currentFileTotal: $currentFileTotal, isParsing: $isParsing)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseBatchImageFolderStateCopyWith<$Res> implements $ParseBatchImageFolderStateCopyWith<$Res> { + factory _$ParseBatchImageFolderStateCopyWith(_ParseBatchImageFolderState value, $Res Function(_ParseBatchImageFolderState) _then) = __$ParseBatchImageFolderStateCopyWithImpl; +@override @useResult +$Res call({ + List parseBatchFolderList, int completeCount, int totalCount, String currentFileName, int currentFileProgress, int currentFileTotal, bool isParsing +}); + + + + +} +/// @nodoc +class __$ParseBatchImageFolderStateCopyWithImpl<$Res> + implements _$ParseBatchImageFolderStateCopyWith<$Res> { + __$ParseBatchImageFolderStateCopyWithImpl(this._self, this._then); + + final _ParseBatchImageFolderState _self; + final $Res Function(_ParseBatchImageFolderState) _then; + +/// Create a copy of ParseBatchImageFolderState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? parseBatchFolderList = null,Object? completeCount = null,Object? totalCount = null,Object? currentFileName = null,Object? currentFileProgress = null,Object? currentFileTotal = null,Object? isParsing = null,}) { + return _then(_ParseBatchImageFolderState( +parseBatchFolderList: null == parseBatchFolderList ? _self._parseBatchFolderList : parseBatchFolderList // ignore: cast_nullable_to_non_nullable +as List,completeCount: null == completeCount ? _self.completeCount : completeCount // ignore: cast_nullable_to_non_nullable +as int,totalCount: null == totalCount ? _self.totalCount : totalCount // ignore: cast_nullable_to_non_nullable +as int,currentFileName: null == currentFileName ? _self.currentFileName : currentFileName // ignore: cast_nullable_to_non_nullable +as String,currentFileProgress: null == currentFileProgress ? _self.currentFileProgress : currentFileProgress // ignore: cast_nullable_to_non_nullable +as int,currentFileTotal: null == currentFileTotal ? _self.currentFileTotal : currentFileTotal // ignore: cast_nullable_to_non_nullable +as int,isParsing: null == isParsing ? _self.isParsing : isParsing // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + + +} + +/// @nodoc +mixin _$SaveBatchAsBookState { + + int get saveAsBookCount; int get totalCount; AsyncValue get submitState; +/// Create a copy of SaveBatchAsBookState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SaveBatchAsBookStateCopyWith get copyWith => _$SaveBatchAsBookStateCopyWithImpl(this as SaveBatchAsBookState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SaveBatchAsBookState&&(identical(other.saveAsBookCount, saveAsBookCount) || other.saveAsBookCount == saveAsBookCount)&&(identical(other.totalCount, totalCount) || other.totalCount == totalCount)&&(identical(other.submitState, submitState) || other.submitState == submitState)); +} + + +@override +int get hashCode => Object.hash(runtimeType,saveAsBookCount,totalCount,submitState); + +@override +String toString() { + return 'SaveBatchAsBookState(saveAsBookCount: $saveAsBookCount, totalCount: $totalCount, submitState: $submitState)'; +} + + +} + +/// @nodoc +abstract mixin class $SaveBatchAsBookStateCopyWith<$Res> { + factory $SaveBatchAsBookStateCopyWith(SaveBatchAsBookState value, $Res Function(SaveBatchAsBookState) _then) = _$SaveBatchAsBookStateCopyWithImpl; +@useResult +$Res call({ + int saveAsBookCount, int totalCount, AsyncValue submitState +}); + + + + +} +/// @nodoc +class _$SaveBatchAsBookStateCopyWithImpl<$Res> + implements $SaveBatchAsBookStateCopyWith<$Res> { + _$SaveBatchAsBookStateCopyWithImpl(this._self, this._then); + + final SaveBatchAsBookState _self; + final $Res Function(SaveBatchAsBookState) _then; + +/// Create a copy of SaveBatchAsBookState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? saveAsBookCount = null,Object? totalCount = null,Object? submitState = null,}) { + return _then(_self.copyWith( +saveAsBookCount: null == saveAsBookCount ? _self.saveAsBookCount : saveAsBookCount // ignore: cast_nullable_to_non_nullable +as int,totalCount: null == totalCount ? _self.totalCount : totalCount // ignore: cast_nullable_to_non_nullable +as int,submitState: null == submitState ? _self.submitState : submitState // ignore: cast_nullable_to_non_nullable +as AsyncValue, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SaveBatchAsBookState]. +extension SaveBatchAsBookStatePatterns on SaveBatchAsBookState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SaveBatchAsBookState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SaveBatchAsBookState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SaveBatchAsBookState value) $default,){ +final _that = this; +switch (_that) { +case _SaveBatchAsBookState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SaveBatchAsBookState value)? $default,){ +final _that = this; +switch (_that) { +case _SaveBatchAsBookState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( int saveAsBookCount, int totalCount, AsyncValue submitState)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SaveBatchAsBookState() when $default != null: +return $default(_that.saveAsBookCount,_that.totalCount,_that.submitState);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( int saveAsBookCount, int totalCount, AsyncValue submitState) $default,) {final _that = this; +switch (_that) { +case _SaveBatchAsBookState(): +return $default(_that.saveAsBookCount,_that.totalCount,_that.submitState);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( int saveAsBookCount, int totalCount, AsyncValue submitState)? $default,) {final _that = this; +switch (_that) { +case _SaveBatchAsBookState() when $default != null: +return $default(_that.saveAsBookCount,_that.totalCount,_that.submitState);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _SaveBatchAsBookState implements SaveBatchAsBookState { + const _SaveBatchAsBookState({this.saveAsBookCount = 0, this.totalCount = 0, this.submitState = const AsyncData(null)}); + + +@override@JsonKey() final int saveAsBookCount; +@override@JsonKey() final int totalCount; +@override@JsonKey() final AsyncValue submitState; + +/// Create a copy of SaveBatchAsBookState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SaveBatchAsBookStateCopyWith<_SaveBatchAsBookState> get copyWith => __$SaveBatchAsBookStateCopyWithImpl<_SaveBatchAsBookState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SaveBatchAsBookState&&(identical(other.saveAsBookCount, saveAsBookCount) || other.saveAsBookCount == saveAsBookCount)&&(identical(other.totalCount, totalCount) || other.totalCount == totalCount)&&(identical(other.submitState, submitState) || other.submitState == submitState)); +} + + +@override +int get hashCode => Object.hash(runtimeType,saveAsBookCount,totalCount,submitState); + +@override +String toString() { + return 'SaveBatchAsBookState(saveAsBookCount: $saveAsBookCount, totalCount: $totalCount, submitState: $submitState)'; +} + + +} + +/// @nodoc +abstract mixin class _$SaveBatchAsBookStateCopyWith<$Res> implements $SaveBatchAsBookStateCopyWith<$Res> { + factory _$SaveBatchAsBookStateCopyWith(_SaveBatchAsBookState value, $Res Function(_SaveBatchAsBookState) _then) = __$SaveBatchAsBookStateCopyWithImpl; +@override @useResult +$Res call({ + int saveAsBookCount, int totalCount, AsyncValue submitState +}); + + + + +} +/// @nodoc +class __$SaveBatchAsBookStateCopyWithImpl<$Res> + implements _$SaveBatchAsBookStateCopyWith<$Res> { + __$SaveBatchAsBookStateCopyWithImpl(this._self, this._then); + + final _SaveBatchAsBookState _self; + final $Res Function(_SaveBatchAsBookState) _then; + +/// Create a copy of SaveBatchAsBookState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? saveAsBookCount = null,Object? totalCount = null,Object? submitState = null,}) { + return _then(_SaveBatchAsBookState( +saveAsBookCount: null == saveAsBookCount ? _self.saveAsBookCount : saveAsBookCount // ignore: cast_nullable_to_non_nullable +as int,totalCount: null == totalCount ? _self.totalCount : totalCount // ignore: cast_nullable_to_non_nullable +as int,submitState: null == submitState ? _self.submitState : submitState // ignore: cast_nullable_to_non_nullable +as AsyncValue, + )); +} + + +} + +/// @nodoc +mixin _$ParseBatchImageFolderParam { + + String? get parentDirPath; List? get imagePaths; +/// Create a copy of ParseBatchImageFolderParam +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseBatchImageFolderParamCopyWith get copyWith => _$ParseBatchImageFolderParamCopyWithImpl(this as ParseBatchImageFolderParam, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseBatchImageFolderParam&&(identical(other.parentDirPath, parentDirPath) || other.parentDirPath == parentDirPath)&&const DeepCollectionEquality().equals(other.imagePaths, imagePaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,parentDirPath,const DeepCollectionEquality().hash(imagePaths)); + +@override +String toString() { + return 'ParseBatchImageFolderParam(parentDirPath: $parentDirPath, imagePaths: $imagePaths)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseBatchImageFolderParamCopyWith<$Res> { + factory $ParseBatchImageFolderParamCopyWith(ParseBatchImageFolderParam value, $Res Function(ParseBatchImageFolderParam) _then) = _$ParseBatchImageFolderParamCopyWithImpl; +@useResult +$Res call({ + String? parentDirPath, List? imagePaths +}); + + + + +} +/// @nodoc +class _$ParseBatchImageFolderParamCopyWithImpl<$Res> + implements $ParseBatchImageFolderParamCopyWith<$Res> { + _$ParseBatchImageFolderParamCopyWithImpl(this._self, this._then); + + final ParseBatchImageFolderParam _self; + final $Res Function(ParseBatchImageFolderParam) _then; + +/// Create a copy of ParseBatchImageFolderParam +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? parentDirPath = freezed,Object? imagePaths = freezed,}) { + return _then(_self.copyWith( +parentDirPath: freezed == parentDirPath ? _self.parentDirPath : parentDirPath // ignore: cast_nullable_to_non_nullable +as String?,imagePaths: freezed == imagePaths ? _self.imagePaths : imagePaths // ignore: cast_nullable_to_non_nullable +as List?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseBatchImageFolderParam]. +extension ParseBatchImageFolderParamPatterns on ParseBatchImageFolderParam { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseBatchImageFolderParam value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseBatchImageFolderParam() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseBatchImageFolderParam value) $default,){ +final _that = this; +switch (_that) { +case _ParseBatchImageFolderParam(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseBatchImageFolderParam value)? $default,){ +final _that = this; +switch (_that) { +case _ParseBatchImageFolderParam() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String? parentDirPath, List? imagePaths)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseBatchImageFolderParam() when $default != null: +return $default(_that.parentDirPath,_that.imagePaths);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String? parentDirPath, List? imagePaths) $default,) {final _that = this; +switch (_that) { +case _ParseBatchImageFolderParam(): +return $default(_that.parentDirPath,_that.imagePaths);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? parentDirPath, List? imagePaths)? $default,) {final _that = this; +switch (_that) { +case _ParseBatchImageFolderParam() when $default != null: +return $default(_that.parentDirPath,_that.imagePaths);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseBatchImageFolderParam implements ParseBatchImageFolderParam { + const _ParseBatchImageFolderParam({this.parentDirPath, final List? imagePaths}): _imagePaths = imagePaths; + + +@override final String? parentDirPath; + final List? _imagePaths; +@override List? get imagePaths { + final value = _imagePaths; + if (value == null) return null; + if (_imagePaths is EqualUnmodifiableListView) return _imagePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); +} + + +/// Create a copy of ParseBatchImageFolderParam +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseBatchImageFolderParamCopyWith<_ParseBatchImageFolderParam> get copyWith => __$ParseBatchImageFolderParamCopyWithImpl<_ParseBatchImageFolderParam>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseBatchImageFolderParam&&(identical(other.parentDirPath, parentDirPath) || other.parentDirPath == parentDirPath)&&const DeepCollectionEquality().equals(other._imagePaths, _imagePaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,parentDirPath,const DeepCollectionEquality().hash(_imagePaths)); + +@override +String toString() { + return 'ParseBatchImageFolderParam(parentDirPath: $parentDirPath, imagePaths: $imagePaths)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseBatchImageFolderParamCopyWith<$Res> implements $ParseBatchImageFolderParamCopyWith<$Res> { + factory _$ParseBatchImageFolderParamCopyWith(_ParseBatchImageFolderParam value, $Res Function(_ParseBatchImageFolderParam) _then) = __$ParseBatchImageFolderParamCopyWithImpl; +@override @useResult +$Res call({ + String? parentDirPath, List? imagePaths +}); + + + + +} +/// @nodoc +class __$ParseBatchImageFolderParamCopyWithImpl<$Res> + implements _$ParseBatchImageFolderParamCopyWith<$Res> { + __$ParseBatchImageFolderParamCopyWithImpl(this._self, this._then); + + final _ParseBatchImageFolderParam _self; + final $Res Function(_ParseBatchImageFolderParam) _then; + +/// Create a copy of ParseBatchImageFolderParam +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? parentDirPath = freezed,Object? imagePaths = freezed,}) { + return _then(_ParseBatchImageFolderParam( +parentDirPath: freezed == parentDirPath ? _self.parentDirPath : parentDirPath // ignore: cast_nullable_to_non_nullable +as String?,imagePaths: freezed == imagePaths ? _self._imagePaths : imagePaths // ignore: cast_nullable_to_non_nullable +as List?, + )); +} + + +} + +// dart format on diff --git a/lib/feature/parse/ui/provider/parse_batch_image_folder.g.dart b/lib/feature/parse/ui/provider/parse_batch_image_folder.g.dart new file mode 100644 index 0000000..c9970dc --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_batch_image_folder.g.dart @@ -0,0 +1,210 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'parse_batch_image_folder.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(ParseBatchImageFolder) +final parseBatchImageFolderProvider = ParseBatchImageFolderFamily._(); + +final class ParseBatchImageFolderProvider + extends + $AsyncNotifierProvider< + ParseBatchImageFolder, + ParseBatchImageFolderState + > { + ParseBatchImageFolderProvider._({ + required ParseBatchImageFolderFamily super.from, + required ParseBatchImageFolderParam super.argument, + }) : super( + retry: null, + name: r'parseBatchImageFolderProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$parseBatchImageFolderHash(); + + @override + String toString() { + return r'parseBatchImageFolderProvider' + '' + '($argument)'; + } + + @$internal + @override + ParseBatchImageFolder create() => ParseBatchImageFolder(); + + @override + bool operator ==(Object other) { + return other is ParseBatchImageFolderProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$parseBatchImageFolderHash() => + r'e1705d70a73923c87e96ca709fcfeb8903173595'; + +final class ParseBatchImageFolderFamily extends $Family + with + $ClassFamilyOverride< + ParseBatchImageFolder, + AsyncValue, + ParseBatchImageFolderState, + FutureOr, + ParseBatchImageFolderParam + > { + ParseBatchImageFolderFamily._() + : super( + retry: null, + name: r'parseBatchImageFolderProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + ParseBatchImageFolderProvider call(ParseBatchImageFolderParam param) => + ParseBatchImageFolderProvider._(argument: param, from: this); + + @override + String toString() => r'parseBatchImageFolderProvider'; +} + +abstract class _$ParseBatchImageFolder + extends $AsyncNotifier { + late final _$args = ref.$arg as ParseBatchImageFolderParam; + ParseBatchImageFolderParam get param => _$args; + + FutureOr build(ParseBatchImageFolderParam param); + @$mustCallSuper + @override + void runBuild() { + final ref = + this.ref + as $Ref< + AsyncValue, + ParseBatchImageFolderState + >; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier< + AsyncValue, + ParseBatchImageFolderState + >, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} + +@ProviderFor(SaveBatchAsBook) +final saveBatchAsBookProvider = SaveBatchAsBookFamily._(); + +final class SaveBatchAsBookProvider + extends $NotifierProvider { + SaveBatchAsBookProvider._({ + required SaveBatchAsBookFamily super.from, + required ParseBatchImageFolderParam super.argument, + }) : super( + retry: null, + name: r'saveBatchAsBookProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$saveBatchAsBookHash(); + + @override + String toString() { + return r'saveBatchAsBookProvider' + '' + '($argument)'; + } + + @$internal + @override + SaveBatchAsBook create() => SaveBatchAsBook(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(SaveBatchAsBookState value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } + + @override + bool operator ==(Object other) { + return other is SaveBatchAsBookProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$saveBatchAsBookHash() => r'aca3134e86c0e740a17d3e20b69c137043d9bb74'; + +final class SaveBatchAsBookFamily extends $Family + with + $ClassFamilyOverride< + SaveBatchAsBook, + SaveBatchAsBookState, + SaveBatchAsBookState, + SaveBatchAsBookState, + ParseBatchImageFolderParam + > { + SaveBatchAsBookFamily._() + : super( + retry: null, + name: r'saveBatchAsBookProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + SaveBatchAsBookProvider call(ParseBatchImageFolderParam param) => + SaveBatchAsBookProvider._(argument: param, from: this); + + @override + String toString() => r'saveBatchAsBookProvider'; +} + +abstract class _$SaveBatchAsBook extends $Notifier { + late final _$args = ref.$arg as ParseBatchImageFolderParam; + ParseBatchImageFolderParam get param => _$args; + + SaveBatchAsBookState build(ParseBatchImageFolderParam param); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + SaveBatchAsBookState, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} diff --git a/lib/feature/parse/ui/provider/parse_batch_pdf_provider.dart b/lib/feature/parse/ui/provider/parse_batch_pdf_provider.dart new file mode 100644 index 0000000..a1e7c8b --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_batch_pdf_provider.dart @@ -0,0 +1,214 @@ +import 'dart:io'; + +import 'package:flutter_riverpod/legacy.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:tele_book/feature/book/model/dto/save_as_book_dto.dart'; +import 'package:tele_book/feature/book/repository/book_repository.dart'; +import 'package:tele_book/feature/parse/model/parse_batch_archive_vo.dart'; +import 'package:tele_book/feature/parse/service/parse_pdf_service.dart'; + +part 'parse_batch_pdf_provider.freezed.dart'; + +part 'parse_batch_pdf_provider.g.dart'; + +@freezed +abstract class ParseBatchPdfProgress with _$ParseBatchPdfProgress { + const factory ParseBatchPdfProgress({ + required int completeCount, + required int totalCount, + required String currentFileName, + required int currentFileProgress, + required int currentFileTotal, + }) = _ParseBatchPdfProgress; +} + +@freezed +abstract class ParseBatchPdfState with _$ParseBatchPdfState { + const factory ParseBatchPdfState({ + required List parseBatchList, + }) = _ParseBatchPdfState; +} + +@freezed +abstract class ParseBatchPdfParam with _$ParseBatchPdfParam { + const factory ParseBatchPdfParam({ + required String? pdfDirPath, + required List? pdfPaths, + }) = _ParseBatchPdfParam; +} + +@freezed +abstract class ParseBatchPdfSaveBookProgressState + with _$ParseBatchPdfSaveBookProgressState { + const factory ParseBatchPdfSaveBookProgressState({ + @Default(0) int current, + @Default(0) int total, + }) = _ParseBatchPdfSaveBookProgressState; +} + +final parseBatchProgressProvider = StateProvider((ref) { + return ParseBatchPdfProgress( + completeCount: 0, + totalCount: 0, + currentFileName: '', + currentFileProgress: 0, + currentFileTotal: 0, + ); +}); + +@riverpod +class ParseBatchPdf extends _$ParseBatchPdf { + ParsePdfService get _service => ref.read(parsePdfServiceProvider); + + late final ParseBatchPdfParam _param; + + @override + FutureOr build(ParseBatchPdfParam param) async { + _param = param; + Future.microtask(_parseBatch); + return const ParseBatchPdfState(parseBatchList: []); + } + + Future _requestStoragePermission() async { + if (!Platform.isAndroid) return true; + if (await Permission.manageExternalStorage.isGranted) return true; + final status = await Permission.manageExternalStorage.request(); + if (status.isGranted) return true; + if (await Permission.storage.isGranted) return true; + final storageStatus = await Permission.storage.request(); + return storageStatus.isGranted; + } + + Future _parseBatch() async { + final current = state.value ?? const ParseBatchPdfState(parseBatchList: []); + ref + .read(parseBatchProgressProvider.notifier) + .state = const ParseBatchPdfProgress( + completeCount: 0, + totalCount: 0, + currentFileName: '', + currentFileProgress: 0, + currentFileTotal: 0, + ); + + state = const AsyncValue.loading(); + final hasPermission = await _requestStoragePermission(); + if (!hasPermission) { + state = AsyncValue.error("需要存储权限才能解析PDF", StackTrace.current); + return; + } + + final pdfPaths = _param.pdfPaths; + final pdfDirPath = _param.pdfDirPath; + final parseResult = pdfPaths != null && pdfPaths.isNotEmpty + ? await _service.parseBatchPdfsFromPaths( + pdfPaths, + (total) { + final progress = ref.read(parseBatchProgressProvider); + ref.read(parseBatchProgressProvider.notifier).state = progress + .copyWith(totalCount: total); + }, + (count) { + final progress = ref.read(parseBatchProgressProvider); + ref.read(parseBatchProgressProvider.notifier).state = progress + .copyWith(completeCount: count); + }, + onCurrentFileChanged: (fileName) { + final progress = ref.read(parseBatchProgressProvider); + ref.read(parseBatchProgressProvider.notifier).state = progress + .copyWith( + currentFileName: fileName, + currentFileProgress: 0, + currentFileTotal: 0, + ); + }, + onCurrentFileProgress: (current, total) { + final progress = ref.read(parseBatchProgressProvider); + ref.read(parseBatchProgressProvider.notifier).state = progress + .copyWith( + currentFileProgress: current, + currentFileTotal: total, + ); + }, + ) + : await _service.parseBatchPdfs( + pdfDirPath ?? '', + (total) { + final progress = ref.read(parseBatchProgressProvider); + ref.read(parseBatchProgressProvider.notifier).state = progress + .copyWith(totalCount: total); + }, + (count) { + final progress = ref.read(parseBatchProgressProvider); + ref.read(parseBatchProgressProvider.notifier).state = progress + .copyWith(completeCount: count); + }, + onCurrentFileChanged: (fileName) { + final progress = ref.read(parseBatchProgressProvider); + ref.read(parseBatchProgressProvider.notifier).state = progress + .copyWith( + currentFileName: fileName, + currentFileProgress: 0, + currentFileTotal: 0, + ); + }, + onCurrentFileProgress: (current, total) { + final progress = ref.read(parseBatchProgressProvider); + ref.read(parseBatchProgressProvider.notifier).state = progress + .copyWith( + currentFileProgress: current, + currentFileTotal: total, + ); + }, + ); + + parseResult.fold( + onSuccess: (data) { + state = AsyncValue.data(current.copyWith(parseBatchList: data)); + }, + onError: (error) { + state = AsyncValue.error(error.message, StackTrace.current); + }, + ); + } +} + +final parseBatchPdfSaveBookProgressProvider = + StateProvider((ref) { + return const ParseBatchPdfSaveBookProgressState(); + }); + +@riverpod +class ParseBatchPdfSaveBook extends _$ParseBatchPdfSaveBook { + BookRepository get _repository => ref.read(bookRepositoryProvider); + + @override + FutureOr build() => null; + + Future saveBatchAsBook(List parseBatchList) async { + if (parseBatchList.isEmpty) return; + + state = const AsyncValue.loading(); + + final dos = parseBatchList + .map((e) => SaveAsBookDto(title: e.name, paths: e.tempPaths)) + .toList(); + + final result = await _repository.saveBatchAsBooks(dos, (count) { + final progress = ref.read(parseBatchPdfSaveBookProgressProvider); + ref.read(parseBatchPdfSaveBookProgressProvider.notifier).state = progress + .copyWith(current: count); + }); + + result.fold( + onSuccess: (_) { + state = const AsyncValue.data(null); + }, + onError: (error) { + state = AsyncError(error.message, StackTrace.current); + }, + ); + } +} diff --git a/lib/feature/parse/ui/provider/parse_batch_pdf_provider.freezed.dart b/lib/feature/parse/ui/provider/parse_batch_pdf_provider.freezed.dart new file mode 100644 index 0000000..4f8374b --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_batch_pdf_provider.freezed.dart @@ -0,0 +1,1074 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'parse_batch_pdf_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$ParseBatchPdfProgress { + + int get completeCount; int get totalCount; String get currentFileName; int get currentFileProgress; int get currentFileTotal; +/// Create a copy of ParseBatchPdfProgress +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseBatchPdfProgressCopyWith get copyWith => _$ParseBatchPdfProgressCopyWithImpl(this as ParseBatchPdfProgress, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseBatchPdfProgress&&(identical(other.completeCount, completeCount) || other.completeCount == completeCount)&&(identical(other.totalCount, totalCount) || other.totalCount == totalCount)&&(identical(other.currentFileName, currentFileName) || other.currentFileName == currentFileName)&&(identical(other.currentFileProgress, currentFileProgress) || other.currentFileProgress == currentFileProgress)&&(identical(other.currentFileTotal, currentFileTotal) || other.currentFileTotal == currentFileTotal)); +} + + +@override +int get hashCode => Object.hash(runtimeType,completeCount,totalCount,currentFileName,currentFileProgress,currentFileTotal); + +@override +String toString() { + return 'ParseBatchPdfProgress(completeCount: $completeCount, totalCount: $totalCount, currentFileName: $currentFileName, currentFileProgress: $currentFileProgress, currentFileTotal: $currentFileTotal)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseBatchPdfProgressCopyWith<$Res> { + factory $ParseBatchPdfProgressCopyWith(ParseBatchPdfProgress value, $Res Function(ParseBatchPdfProgress) _then) = _$ParseBatchPdfProgressCopyWithImpl; +@useResult +$Res call({ + int completeCount, int totalCount, String currentFileName, int currentFileProgress, int currentFileTotal +}); + + + + +} +/// @nodoc +class _$ParseBatchPdfProgressCopyWithImpl<$Res> + implements $ParseBatchPdfProgressCopyWith<$Res> { + _$ParseBatchPdfProgressCopyWithImpl(this._self, this._then); + + final ParseBatchPdfProgress _self; + final $Res Function(ParseBatchPdfProgress) _then; + +/// Create a copy of ParseBatchPdfProgress +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? completeCount = null,Object? totalCount = null,Object? currentFileName = null,Object? currentFileProgress = null,Object? currentFileTotal = null,}) { + return _then(_self.copyWith( +completeCount: null == completeCount ? _self.completeCount : completeCount // ignore: cast_nullable_to_non_nullable +as int,totalCount: null == totalCount ? _self.totalCount : totalCount // ignore: cast_nullable_to_non_nullable +as int,currentFileName: null == currentFileName ? _self.currentFileName : currentFileName // ignore: cast_nullable_to_non_nullable +as String,currentFileProgress: null == currentFileProgress ? _self.currentFileProgress : currentFileProgress // ignore: cast_nullable_to_non_nullable +as int,currentFileTotal: null == currentFileTotal ? _self.currentFileTotal : currentFileTotal // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseBatchPdfProgress]. +extension ParseBatchPdfProgressPatterns on ParseBatchPdfProgress { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseBatchPdfProgress value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseBatchPdfProgress() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseBatchPdfProgress value) $default,){ +final _that = this; +switch (_that) { +case _ParseBatchPdfProgress(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseBatchPdfProgress value)? $default,){ +final _that = this; +switch (_that) { +case _ParseBatchPdfProgress() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( int completeCount, int totalCount, String currentFileName, int currentFileProgress, int currentFileTotal)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseBatchPdfProgress() when $default != null: +return $default(_that.completeCount,_that.totalCount,_that.currentFileName,_that.currentFileProgress,_that.currentFileTotal);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( int completeCount, int totalCount, String currentFileName, int currentFileProgress, int currentFileTotal) $default,) {final _that = this; +switch (_that) { +case _ParseBatchPdfProgress(): +return $default(_that.completeCount,_that.totalCount,_that.currentFileName,_that.currentFileProgress,_that.currentFileTotal);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( int completeCount, int totalCount, String currentFileName, int currentFileProgress, int currentFileTotal)? $default,) {final _that = this; +switch (_that) { +case _ParseBatchPdfProgress() when $default != null: +return $default(_that.completeCount,_that.totalCount,_that.currentFileName,_that.currentFileProgress,_that.currentFileTotal);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseBatchPdfProgress implements ParseBatchPdfProgress { + const _ParseBatchPdfProgress({required this.completeCount, required this.totalCount, required this.currentFileName, required this.currentFileProgress, required this.currentFileTotal}); + + +@override final int completeCount; +@override final int totalCount; +@override final String currentFileName; +@override final int currentFileProgress; +@override final int currentFileTotal; + +/// Create a copy of ParseBatchPdfProgress +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseBatchPdfProgressCopyWith<_ParseBatchPdfProgress> get copyWith => __$ParseBatchPdfProgressCopyWithImpl<_ParseBatchPdfProgress>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseBatchPdfProgress&&(identical(other.completeCount, completeCount) || other.completeCount == completeCount)&&(identical(other.totalCount, totalCount) || other.totalCount == totalCount)&&(identical(other.currentFileName, currentFileName) || other.currentFileName == currentFileName)&&(identical(other.currentFileProgress, currentFileProgress) || other.currentFileProgress == currentFileProgress)&&(identical(other.currentFileTotal, currentFileTotal) || other.currentFileTotal == currentFileTotal)); +} + + +@override +int get hashCode => Object.hash(runtimeType,completeCount,totalCount,currentFileName,currentFileProgress,currentFileTotal); + +@override +String toString() { + return 'ParseBatchPdfProgress(completeCount: $completeCount, totalCount: $totalCount, currentFileName: $currentFileName, currentFileProgress: $currentFileProgress, currentFileTotal: $currentFileTotal)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseBatchPdfProgressCopyWith<$Res> implements $ParseBatchPdfProgressCopyWith<$Res> { + factory _$ParseBatchPdfProgressCopyWith(_ParseBatchPdfProgress value, $Res Function(_ParseBatchPdfProgress) _then) = __$ParseBatchPdfProgressCopyWithImpl; +@override @useResult +$Res call({ + int completeCount, int totalCount, String currentFileName, int currentFileProgress, int currentFileTotal +}); + + + + +} +/// @nodoc +class __$ParseBatchPdfProgressCopyWithImpl<$Res> + implements _$ParseBatchPdfProgressCopyWith<$Res> { + __$ParseBatchPdfProgressCopyWithImpl(this._self, this._then); + + final _ParseBatchPdfProgress _self; + final $Res Function(_ParseBatchPdfProgress) _then; + +/// Create a copy of ParseBatchPdfProgress +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? completeCount = null,Object? totalCount = null,Object? currentFileName = null,Object? currentFileProgress = null,Object? currentFileTotal = null,}) { + return _then(_ParseBatchPdfProgress( +completeCount: null == completeCount ? _self.completeCount : completeCount // ignore: cast_nullable_to_non_nullable +as int,totalCount: null == totalCount ? _self.totalCount : totalCount // ignore: cast_nullable_to_non_nullable +as int,currentFileName: null == currentFileName ? _self.currentFileName : currentFileName // ignore: cast_nullable_to_non_nullable +as String,currentFileProgress: null == currentFileProgress ? _self.currentFileProgress : currentFileProgress // ignore: cast_nullable_to_non_nullable +as int,currentFileTotal: null == currentFileTotal ? _self.currentFileTotal : currentFileTotal // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +/// @nodoc +mixin _$ParseBatchPdfState { + + List get parseBatchList; +/// Create a copy of ParseBatchPdfState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseBatchPdfStateCopyWith get copyWith => _$ParseBatchPdfStateCopyWithImpl(this as ParseBatchPdfState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseBatchPdfState&&const DeepCollectionEquality().equals(other.parseBatchList, parseBatchList)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(parseBatchList)); + +@override +String toString() { + return 'ParseBatchPdfState(parseBatchList: $parseBatchList)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseBatchPdfStateCopyWith<$Res> { + factory $ParseBatchPdfStateCopyWith(ParseBatchPdfState value, $Res Function(ParseBatchPdfState) _then) = _$ParseBatchPdfStateCopyWithImpl; +@useResult +$Res call({ + List parseBatchList +}); + + + + +} +/// @nodoc +class _$ParseBatchPdfStateCopyWithImpl<$Res> + implements $ParseBatchPdfStateCopyWith<$Res> { + _$ParseBatchPdfStateCopyWithImpl(this._self, this._then); + + final ParseBatchPdfState _self; + final $Res Function(ParseBatchPdfState) _then; + +/// Create a copy of ParseBatchPdfState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? parseBatchList = null,}) { + return _then(_self.copyWith( +parseBatchList: null == parseBatchList ? _self.parseBatchList : parseBatchList // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseBatchPdfState]. +extension ParseBatchPdfStatePatterns on ParseBatchPdfState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseBatchPdfState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseBatchPdfState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseBatchPdfState value) $default,){ +final _that = this; +switch (_that) { +case _ParseBatchPdfState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseBatchPdfState value)? $default,){ +final _that = this; +switch (_that) { +case _ParseBatchPdfState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( List parseBatchList)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseBatchPdfState() when $default != null: +return $default(_that.parseBatchList);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( List parseBatchList) $default,) {final _that = this; +switch (_that) { +case _ParseBatchPdfState(): +return $default(_that.parseBatchList);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( List parseBatchList)? $default,) {final _that = this; +switch (_that) { +case _ParseBatchPdfState() when $default != null: +return $default(_that.parseBatchList);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseBatchPdfState implements ParseBatchPdfState { + const _ParseBatchPdfState({required final List parseBatchList}): _parseBatchList = parseBatchList; + + + final List _parseBatchList; +@override List get parseBatchList { + if (_parseBatchList is EqualUnmodifiableListView) return _parseBatchList; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_parseBatchList); +} + + +/// Create a copy of ParseBatchPdfState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseBatchPdfStateCopyWith<_ParseBatchPdfState> get copyWith => __$ParseBatchPdfStateCopyWithImpl<_ParseBatchPdfState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseBatchPdfState&&const DeepCollectionEquality().equals(other._parseBatchList, _parseBatchList)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_parseBatchList)); + +@override +String toString() { + return 'ParseBatchPdfState(parseBatchList: $parseBatchList)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseBatchPdfStateCopyWith<$Res> implements $ParseBatchPdfStateCopyWith<$Res> { + factory _$ParseBatchPdfStateCopyWith(_ParseBatchPdfState value, $Res Function(_ParseBatchPdfState) _then) = __$ParseBatchPdfStateCopyWithImpl; +@override @useResult +$Res call({ + List parseBatchList +}); + + + + +} +/// @nodoc +class __$ParseBatchPdfStateCopyWithImpl<$Res> + implements _$ParseBatchPdfStateCopyWith<$Res> { + __$ParseBatchPdfStateCopyWithImpl(this._self, this._then); + + final _ParseBatchPdfState _self; + final $Res Function(_ParseBatchPdfState) _then; + +/// Create a copy of ParseBatchPdfState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? parseBatchList = null,}) { + return _then(_ParseBatchPdfState( +parseBatchList: null == parseBatchList ? _self._parseBatchList : parseBatchList // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +/// @nodoc +mixin _$ParseBatchPdfParam { + + String? get pdfDirPath; List? get pdfPaths; +/// Create a copy of ParseBatchPdfParam +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseBatchPdfParamCopyWith get copyWith => _$ParseBatchPdfParamCopyWithImpl(this as ParseBatchPdfParam, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseBatchPdfParam&&(identical(other.pdfDirPath, pdfDirPath) || other.pdfDirPath == pdfDirPath)&&const DeepCollectionEquality().equals(other.pdfPaths, pdfPaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,pdfDirPath,const DeepCollectionEquality().hash(pdfPaths)); + +@override +String toString() { + return 'ParseBatchPdfParam(pdfDirPath: $pdfDirPath, pdfPaths: $pdfPaths)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseBatchPdfParamCopyWith<$Res> { + factory $ParseBatchPdfParamCopyWith(ParseBatchPdfParam value, $Res Function(ParseBatchPdfParam) _then) = _$ParseBatchPdfParamCopyWithImpl; +@useResult +$Res call({ + String? pdfDirPath, List? pdfPaths +}); + + + + +} +/// @nodoc +class _$ParseBatchPdfParamCopyWithImpl<$Res> + implements $ParseBatchPdfParamCopyWith<$Res> { + _$ParseBatchPdfParamCopyWithImpl(this._self, this._then); + + final ParseBatchPdfParam _self; + final $Res Function(ParseBatchPdfParam) _then; + +/// Create a copy of ParseBatchPdfParam +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? pdfDirPath = freezed,Object? pdfPaths = freezed,}) { + return _then(_self.copyWith( +pdfDirPath: freezed == pdfDirPath ? _self.pdfDirPath : pdfDirPath // ignore: cast_nullable_to_non_nullable +as String?,pdfPaths: freezed == pdfPaths ? _self.pdfPaths : pdfPaths // ignore: cast_nullable_to_non_nullable +as List?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseBatchPdfParam]. +extension ParseBatchPdfParamPatterns on ParseBatchPdfParam { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseBatchPdfParam value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseBatchPdfParam() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseBatchPdfParam value) $default,){ +final _that = this; +switch (_that) { +case _ParseBatchPdfParam(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseBatchPdfParam value)? $default,){ +final _that = this; +switch (_that) { +case _ParseBatchPdfParam() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String? pdfDirPath, List? pdfPaths)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseBatchPdfParam() when $default != null: +return $default(_that.pdfDirPath,_that.pdfPaths);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String? pdfDirPath, List? pdfPaths) $default,) {final _that = this; +switch (_that) { +case _ParseBatchPdfParam(): +return $default(_that.pdfDirPath,_that.pdfPaths);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? pdfDirPath, List? pdfPaths)? $default,) {final _that = this; +switch (_that) { +case _ParseBatchPdfParam() when $default != null: +return $default(_that.pdfDirPath,_that.pdfPaths);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseBatchPdfParam implements ParseBatchPdfParam { + const _ParseBatchPdfParam({required this.pdfDirPath, required final List? pdfPaths}): _pdfPaths = pdfPaths; + + +@override final String? pdfDirPath; + final List? _pdfPaths; +@override List? get pdfPaths { + final value = _pdfPaths; + if (value == null) return null; + if (_pdfPaths is EqualUnmodifiableListView) return _pdfPaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); +} + + +/// Create a copy of ParseBatchPdfParam +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseBatchPdfParamCopyWith<_ParseBatchPdfParam> get copyWith => __$ParseBatchPdfParamCopyWithImpl<_ParseBatchPdfParam>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseBatchPdfParam&&(identical(other.pdfDirPath, pdfDirPath) || other.pdfDirPath == pdfDirPath)&&const DeepCollectionEquality().equals(other._pdfPaths, _pdfPaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,pdfDirPath,const DeepCollectionEquality().hash(_pdfPaths)); + +@override +String toString() { + return 'ParseBatchPdfParam(pdfDirPath: $pdfDirPath, pdfPaths: $pdfPaths)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseBatchPdfParamCopyWith<$Res> implements $ParseBatchPdfParamCopyWith<$Res> { + factory _$ParseBatchPdfParamCopyWith(_ParseBatchPdfParam value, $Res Function(_ParseBatchPdfParam) _then) = __$ParseBatchPdfParamCopyWithImpl; +@override @useResult +$Res call({ + String? pdfDirPath, List? pdfPaths +}); + + + + +} +/// @nodoc +class __$ParseBatchPdfParamCopyWithImpl<$Res> + implements _$ParseBatchPdfParamCopyWith<$Res> { + __$ParseBatchPdfParamCopyWithImpl(this._self, this._then); + + final _ParseBatchPdfParam _self; + final $Res Function(_ParseBatchPdfParam) _then; + +/// Create a copy of ParseBatchPdfParam +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? pdfDirPath = freezed,Object? pdfPaths = freezed,}) { + return _then(_ParseBatchPdfParam( +pdfDirPath: freezed == pdfDirPath ? _self.pdfDirPath : pdfDirPath // ignore: cast_nullable_to_non_nullable +as String?,pdfPaths: freezed == pdfPaths ? _self._pdfPaths : pdfPaths // ignore: cast_nullable_to_non_nullable +as List?, + )); +} + + +} + +/// @nodoc +mixin _$ParseBatchPdfSaveBookProgressState { + + int get current; int get total; +/// Create a copy of ParseBatchPdfSaveBookProgressState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseBatchPdfSaveBookProgressStateCopyWith get copyWith => _$ParseBatchPdfSaveBookProgressStateCopyWithImpl(this as ParseBatchPdfSaveBookProgressState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseBatchPdfSaveBookProgressState&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); +} + + +@override +int get hashCode => Object.hash(runtimeType,current,total); + +@override +String toString() { + return 'ParseBatchPdfSaveBookProgressState(current: $current, total: $total)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseBatchPdfSaveBookProgressStateCopyWith<$Res> { + factory $ParseBatchPdfSaveBookProgressStateCopyWith(ParseBatchPdfSaveBookProgressState value, $Res Function(ParseBatchPdfSaveBookProgressState) _then) = _$ParseBatchPdfSaveBookProgressStateCopyWithImpl; +@useResult +$Res call({ + int current, int total +}); + + + + +} +/// @nodoc +class _$ParseBatchPdfSaveBookProgressStateCopyWithImpl<$Res> + implements $ParseBatchPdfSaveBookProgressStateCopyWith<$Res> { + _$ParseBatchPdfSaveBookProgressStateCopyWithImpl(this._self, this._then); + + final ParseBatchPdfSaveBookProgressState _self; + final $Res Function(ParseBatchPdfSaveBookProgressState) _then; + +/// Create a copy of ParseBatchPdfSaveBookProgressState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? current = null,Object? total = null,}) { + return _then(_self.copyWith( +current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable +as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseBatchPdfSaveBookProgressState]. +extension ParseBatchPdfSaveBookProgressStatePatterns on ParseBatchPdfSaveBookProgressState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseBatchPdfSaveBookProgressState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseBatchPdfSaveBookProgressState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseBatchPdfSaveBookProgressState value) $default,){ +final _that = this; +switch (_that) { +case _ParseBatchPdfSaveBookProgressState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseBatchPdfSaveBookProgressState value)? $default,){ +final _that = this; +switch (_that) { +case _ParseBatchPdfSaveBookProgressState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( int current, int total)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseBatchPdfSaveBookProgressState() when $default != null: +return $default(_that.current,_that.total);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( int current, int total) $default,) {final _that = this; +switch (_that) { +case _ParseBatchPdfSaveBookProgressState(): +return $default(_that.current,_that.total);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( int current, int total)? $default,) {final _that = this; +switch (_that) { +case _ParseBatchPdfSaveBookProgressState() when $default != null: +return $default(_that.current,_that.total);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseBatchPdfSaveBookProgressState implements ParseBatchPdfSaveBookProgressState { + const _ParseBatchPdfSaveBookProgressState({this.current = 0, this.total = 0}); + + +@override@JsonKey() final int current; +@override@JsonKey() final int total; + +/// Create a copy of ParseBatchPdfSaveBookProgressState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseBatchPdfSaveBookProgressStateCopyWith<_ParseBatchPdfSaveBookProgressState> get copyWith => __$ParseBatchPdfSaveBookProgressStateCopyWithImpl<_ParseBatchPdfSaveBookProgressState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseBatchPdfSaveBookProgressState&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); +} + + +@override +int get hashCode => Object.hash(runtimeType,current,total); + +@override +String toString() { + return 'ParseBatchPdfSaveBookProgressState(current: $current, total: $total)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseBatchPdfSaveBookProgressStateCopyWith<$Res> implements $ParseBatchPdfSaveBookProgressStateCopyWith<$Res> { + factory _$ParseBatchPdfSaveBookProgressStateCopyWith(_ParseBatchPdfSaveBookProgressState value, $Res Function(_ParseBatchPdfSaveBookProgressState) _then) = __$ParseBatchPdfSaveBookProgressStateCopyWithImpl; +@override @useResult +$Res call({ + int current, int total +}); + + + + +} +/// @nodoc +class __$ParseBatchPdfSaveBookProgressStateCopyWithImpl<$Res> + implements _$ParseBatchPdfSaveBookProgressStateCopyWith<$Res> { + __$ParseBatchPdfSaveBookProgressStateCopyWithImpl(this._self, this._then); + + final _ParseBatchPdfSaveBookProgressState _self; + final $Res Function(_ParseBatchPdfSaveBookProgressState) _then; + +/// Create a copy of ParseBatchPdfSaveBookProgressState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? current = null,Object? total = null,}) { + return _then(_ParseBatchPdfSaveBookProgressState( +current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable +as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +// dart format on diff --git a/lib/feature/parse/ui/provider/parse_batch_pdf_provider.g.dart b/lib/feature/parse/ui/provider/parse_batch_pdf_provider.g.dart new file mode 100644 index 0000000..858774f --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_batch_pdf_provider.g.dart @@ -0,0 +1,145 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'parse_batch_pdf_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(ParseBatchPdf) +final parseBatchPdfProvider = ParseBatchPdfFamily._(); + +final class ParseBatchPdfProvider + extends $AsyncNotifierProvider { + ParseBatchPdfProvider._({ + required ParseBatchPdfFamily super.from, + required ParseBatchPdfParam super.argument, + }) : super( + retry: null, + name: r'parseBatchPdfProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$parseBatchPdfHash(); + + @override + String toString() { + return r'parseBatchPdfProvider' + '' + '($argument)'; + } + + @$internal + @override + ParseBatchPdf create() => ParseBatchPdf(); + + @override + bool operator ==(Object other) { + return other is ParseBatchPdfProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$parseBatchPdfHash() => r'6523aa1d248e8d893e5e3700a7ab2b5b3bf62e62'; + +final class ParseBatchPdfFamily extends $Family + with + $ClassFamilyOverride< + ParseBatchPdf, + AsyncValue, + ParseBatchPdfState, + FutureOr, + ParseBatchPdfParam + > { + ParseBatchPdfFamily._() + : super( + retry: null, + name: r'parseBatchPdfProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + ParseBatchPdfProvider call(ParseBatchPdfParam param) => + ParseBatchPdfProvider._(argument: param, from: this); + + @override + String toString() => r'parseBatchPdfProvider'; +} + +abstract class _$ParseBatchPdf extends $AsyncNotifier { + late final _$args = ref.$arg as ParseBatchPdfParam; + ParseBatchPdfParam get param => _$args; + + FutureOr build(ParseBatchPdfParam param); + @$mustCallSuper + @override + void runBuild() { + final ref = + this.ref as $Ref, ParseBatchPdfState>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, ParseBatchPdfState>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} + +@ProviderFor(ParseBatchPdfSaveBook) +final parseBatchPdfSaveBookProvider = ParseBatchPdfSaveBookProvider._(); + +final class ParseBatchPdfSaveBookProvider + extends $AsyncNotifierProvider { + ParseBatchPdfSaveBookProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'parseBatchPdfSaveBookProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$parseBatchPdfSaveBookHash(); + + @$internal + @override + ParseBatchPdfSaveBook create() => ParseBatchPdfSaveBook(); +} + +String _$parseBatchPdfSaveBookHash() => + r'4934b2ba4dd24931ffcef41c591f0331bcc18294'; + +abstract class _$ParseBatchPdfSaveBook extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, void>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, void>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/lib/feature/parse/ui/provider/parse_form_provider.dart b/lib/feature/parse/ui/provider/parse_form_provider.dart new file mode 100644 index 0000000..6654fe3 --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_form_provider.dart @@ -0,0 +1,270 @@ +import 'dart:io'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:go_router/go_router.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:tele_book/core/route/app_route.dart'; + +part 'parse_form_provider.freezed.dart'; + +part 'parse_form_provider.g.dart'; + +@freezed +abstract class ParseFormState with _$ParseFormState { + const factory ParseFormState({ + @Default(ParseFormType.web) ParseFormType type, + @Default('') String url, + @Default('') String archivePath, + @Default('') String batchArchivePath, + @Default([]) List batchArchivePaths, + @Default('') String imageFolderPath, + @Default([]) List imagePaths, + @Default('') String batchImageFolderPath, + @Default([]) List batchImagePaths, + @Default('') String pdfPath, + @Default('') String batchPdfPath, + @Default([]) List batchPdfPaths, + }) = _ParseFormState; +} + +@riverpod +class ParseForm extends _$ParseForm { + late final TextEditingController urlController; + late final TextEditingController archivePathController; + late final TextEditingController batchArchivePathController; + late final TextEditingController imageFolderPathController; + late final TextEditingController batchImageFolderPathController; + late final TextEditingController pdfPathController; + late final TextEditingController batchPdfPathController; + + @override + ParseFormState build() { + urlController = TextEditingController(); + archivePathController = TextEditingController(); + batchArchivePathController = TextEditingController(); + imageFolderPathController = TextEditingController(); + batchImageFolderPathController = TextEditingController(); + pdfPathController = TextEditingController(); + batchPdfPathController = TextEditingController(); + + ref.onDispose(() { + urlController.dispose(); + archivePathController.dispose(); + batchArchivePathController.dispose(); + imageFolderPathController.dispose(); + batchImageFolderPathController.dispose(); + pdfPathController.dispose(); + batchPdfPathController.dispose(); + }); + + return const ParseFormState(); + } + + void setType(ParseFormType? type) { + if (type == null) return; + state = state.copyWith(type: type); + } + + void onParse(BuildContext context) { + switch (state.type) { + case ParseFormType.web: + context.push(AppRoute.parseWeb, extra: state.url); + break; + case ParseFormType.archive: + context.push(AppRoute.parseArchiveSingle, extra: state.archivePath); + break; + case ParseFormType.batchArchive: + context.push( + AppRoute.parseArchiveBatch, + extra: state.batchArchivePaths.isNotEmpty + ? state.batchArchivePaths + : state.batchArchivePath, + ); + break; + case ParseFormType.imageFolder: + context.push( + AppRoute.parseImageFolder, + extra: state.imagePaths.isNotEmpty + ? state.imagePaths + : state.imageFolderPath, + ); + break; + case ParseFormType.batchImageFolder: + context.push( + AppRoute.parseBatchImageFolder, + extra: state.batchImagePaths.isNotEmpty + ? state.batchImagePaths + : state.batchImageFolderPath, + ); + break; + case ParseFormType.pdf: + context.push(AppRoute.parsePdf, extra: state.pdfPath); + break; + case ParseFormType.batchPdf: + context.push( + AppRoute.parseBatchPdf, + extra: state.batchPdfPaths.isNotEmpty + ? state.batchPdfPaths + : state.batchPdfPath, + ); + break; + } + } + + Future getClipboardUrl() async { + final clipboardData = await Clipboard.getData('text/plain'); + final text = clipboardData?.text ?? ''; + if (Uri.tryParse(text)?.hasAbsolutePath == true) { + urlController.text = text; + state = state.copyWith(url: text); + } + } + + void onUrlChanged(String value) { + state = state.copyWith(url: value); + } + + Future pickerArchive() async { + final result = await FilePicker.platform.pickFiles( + dialogTitle: '选择 TeleBook 导出的书籍归档文件', + type: FileType.custom, + allowedExtensions: ['zip'], + ); + if (result != null && result.files.single.path != null) { + final path = result.files.single.path!; + archivePathController.text = path; + state = state.copyWith(archivePath: path); + } + } + + Future pickerBatchArchive() async { + if (Platform.isIOS) { + final result = await FilePicker.platform.pickFiles( + dialogTitle: '选择一个或多个 ZIP 压缩包', + type: FileType.custom, + allowedExtensions: ['zip'], + allowMultiple: true, + ); + if (result != null) { + final paths = result.paths.whereType().toList(); + final text = paths.isEmpty ? '' : '已选择 ${paths.length} 个 ZIP 文件'; + batchArchivePathController.text = text; + state = state.copyWith(batchArchivePaths: paths, batchArchivePath: text); + } + return; + } + + final result = await FilePicker.platform.getDirectoryPath( + dialogTitle: '选择 TeleBook 导出��书籍归档文件夹', + ); + if (result != null) { + batchArchivePathController.text = result; + state = state.copyWith(batchArchivePaths: const [], batchArchivePath: result); + } + } + + Future pickerImageFolder() async { + if (Platform.isIOS) { + final result = await FilePicker.platform.pickFiles( + dialogTitle: '选择图片文件', + type: FileType.custom, + allowedExtensions: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'], + allowMultiple: true, + ); + if (result != null) { + final paths = result.paths.whereType().toList(); + final text = paths.isEmpty ? '' : '已选择 ${paths.length} 张图片'; + imageFolderPathController.text = text; + state = state.copyWith(imagePaths: paths, imageFolderPath: text); + } + return; + } + + final result = await FilePicker.platform.getDirectoryPath( + dialogTitle: '选择包含图片的文件夹', + ); + if (result != null) { + imageFolderPathController.text = result; + state = state.copyWith(imagePaths: const [], imageFolderPath: result); + } + } + + Future pickerBatchImageFolder() async { + if (Platform.isIOS) { + final result = await FilePicker.platform.pickFiles( + dialogTitle: '选择批量图片文件', + type: FileType.custom, + allowedExtensions: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'], + allowMultiple: true, + ); + if (result != null) { + final paths = result.paths.whereType().toList(); + final text = paths.isEmpty ? '' : '已选择 ${paths.length} 张图片(按所在文件夹分组)'; + batchImageFolderPathController.text = text; + state = state.copyWith(batchImagePaths: paths, batchImageFolderPath: text); + } + return; + } + + final result = await FilePicker.platform.getDirectoryPath( + dialogTitle: '选择批量图片文件夹的父目录', + ); + if (result != null) { + batchImageFolderPathController.text = result; + state = state.copyWith(batchImagePaths: const [], batchImageFolderPath: result); + } + } + + Future pickerPdf() async { + final result = await FilePicker.platform.pickFiles( + dialogTitle: '选择 PDF 文件', + type: FileType.custom, + allowedExtensions: ['pdf'], + ); + if (result != null && result.files.single.path != null) { + final path = result.files.single.path!; + pdfPathController.text = path; + state = state.copyWith(pdfPath: path); + } + } + + Future pickerBatchPdf() async { + if (Platform.isIOS) { + final result = await FilePicker.platform.pickFiles( + dialogTitle: '选择一个或多个 PDF 文件', + type: FileType.custom, + allowedExtensions: ['pdf'], + allowMultiple: true, + ); + if (result != null) { + final paths = result.paths.whereType().toList(); + final text = paths.isEmpty ? '' : '已选择 ${paths.length} 个 PDF 文件'; + batchPdfPathController.text = text; + state = state.copyWith(batchPdfPaths: paths, batchPdfPath: text); + } + return; + } + + final result = await FilePicker.platform.getDirectoryPath( + dialogTitle: '选择包含 PDF 的文件夹', + ); + if (result != null) { + batchPdfPathController.text = result; + state = state.copyWith(batchPdfPaths: const [], batchPdfPath: result); + } + } +} + +enum ParseFormType { + web, + archive, + batchArchive, + imageFolder, + batchImageFolder, + pdf, + batchPdf, +} + diff --git a/lib/feature/parse/ui/provider/parse_form_provider.freezed.dart b/lib/feature/parse/ui/provider/parse_form_provider.freezed.dart new file mode 100644 index 0000000..ce5de6d --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_form_provider.freezed.dart @@ -0,0 +1,328 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'parse_form_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$ParseFormState { + + ParseFormType get type; String get url; String get archivePath; String get batchArchivePath; List get batchArchivePaths; String get imageFolderPath; List get imagePaths; String get batchImageFolderPath; List get batchImagePaths; String get pdfPath; String get batchPdfPath; List get batchPdfPaths; +/// Create a copy of ParseFormState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseFormStateCopyWith get copyWith => _$ParseFormStateCopyWithImpl(this as ParseFormState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseFormState&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)&&(identical(other.archivePath, archivePath) || other.archivePath == archivePath)&&(identical(other.batchArchivePath, batchArchivePath) || other.batchArchivePath == batchArchivePath)&&const DeepCollectionEquality().equals(other.batchArchivePaths, batchArchivePaths)&&(identical(other.imageFolderPath, imageFolderPath) || other.imageFolderPath == imageFolderPath)&&const DeepCollectionEquality().equals(other.imagePaths, imagePaths)&&(identical(other.batchImageFolderPath, batchImageFolderPath) || other.batchImageFolderPath == batchImageFolderPath)&&const DeepCollectionEquality().equals(other.batchImagePaths, batchImagePaths)&&(identical(other.pdfPath, pdfPath) || other.pdfPath == pdfPath)&&(identical(other.batchPdfPath, batchPdfPath) || other.batchPdfPath == batchPdfPath)&&const DeepCollectionEquality().equals(other.batchPdfPaths, batchPdfPaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,type,url,archivePath,batchArchivePath,const DeepCollectionEquality().hash(batchArchivePaths),imageFolderPath,const DeepCollectionEquality().hash(imagePaths),batchImageFolderPath,const DeepCollectionEquality().hash(batchImagePaths),pdfPath,batchPdfPath,const DeepCollectionEquality().hash(batchPdfPaths)); + +@override +String toString() { + return 'ParseFormState(type: $type, url: $url, archivePath: $archivePath, batchArchivePath: $batchArchivePath, batchArchivePaths: $batchArchivePaths, imageFolderPath: $imageFolderPath, imagePaths: $imagePaths, batchImageFolderPath: $batchImageFolderPath, batchImagePaths: $batchImagePaths, pdfPath: $pdfPath, batchPdfPath: $batchPdfPath, batchPdfPaths: $batchPdfPaths)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseFormStateCopyWith<$Res> { + factory $ParseFormStateCopyWith(ParseFormState value, $Res Function(ParseFormState) _then) = _$ParseFormStateCopyWithImpl; +@useResult +$Res call({ + ParseFormType type, String url, String archivePath, String batchArchivePath, List batchArchivePaths, String imageFolderPath, List imagePaths, String batchImageFolderPath, List batchImagePaths, String pdfPath, String batchPdfPath, List batchPdfPaths +}); + + + + +} +/// @nodoc +class _$ParseFormStateCopyWithImpl<$Res> + implements $ParseFormStateCopyWith<$Res> { + _$ParseFormStateCopyWithImpl(this._self, this._then); + + final ParseFormState _self; + final $Res Function(ParseFormState) _then; + +/// Create a copy of ParseFormState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? url = null,Object? archivePath = null,Object? batchArchivePath = null,Object? batchArchivePaths = null,Object? imageFolderPath = null,Object? imagePaths = null,Object? batchImageFolderPath = null,Object? batchImagePaths = null,Object? pdfPath = null,Object? batchPdfPath = null,Object? batchPdfPaths = null,}) { + return _then(_self.copyWith( +type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable +as ParseFormType,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String,archivePath: null == archivePath ? _self.archivePath : archivePath // ignore: cast_nullable_to_non_nullable +as String,batchArchivePath: null == batchArchivePath ? _self.batchArchivePath : batchArchivePath // ignore: cast_nullable_to_non_nullable +as String,batchArchivePaths: null == batchArchivePaths ? _self.batchArchivePaths : batchArchivePaths // ignore: cast_nullable_to_non_nullable +as List,imageFolderPath: null == imageFolderPath ? _self.imageFolderPath : imageFolderPath // ignore: cast_nullable_to_non_nullable +as String,imagePaths: null == imagePaths ? _self.imagePaths : imagePaths // ignore: cast_nullable_to_non_nullable +as List,batchImageFolderPath: null == batchImageFolderPath ? _self.batchImageFolderPath : batchImageFolderPath // ignore: cast_nullable_to_non_nullable +as String,batchImagePaths: null == batchImagePaths ? _self.batchImagePaths : batchImagePaths // ignore: cast_nullable_to_non_nullable +as List,pdfPath: null == pdfPath ? _self.pdfPath : pdfPath // ignore: cast_nullable_to_non_nullable +as String,batchPdfPath: null == batchPdfPath ? _self.batchPdfPath : batchPdfPath // ignore: cast_nullable_to_non_nullable +as String,batchPdfPaths: null == batchPdfPaths ? _self.batchPdfPaths : batchPdfPaths // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseFormState]. +extension ParseFormStatePatterns on ParseFormState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseFormState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseFormState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseFormState value) $default,){ +final _that = this; +switch (_that) { +case _ParseFormState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseFormState value)? $default,){ +final _that = this; +switch (_that) { +case _ParseFormState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( ParseFormType type, String url, String archivePath, String batchArchivePath, List batchArchivePaths, String imageFolderPath, List imagePaths, String batchImageFolderPath, List batchImagePaths, String pdfPath, String batchPdfPath, List batchPdfPaths)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseFormState() when $default != null: +return $default(_that.type,_that.url,_that.archivePath,_that.batchArchivePath,_that.batchArchivePaths,_that.imageFolderPath,_that.imagePaths,_that.batchImageFolderPath,_that.batchImagePaths,_that.pdfPath,_that.batchPdfPath,_that.batchPdfPaths);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( ParseFormType type, String url, String archivePath, String batchArchivePath, List batchArchivePaths, String imageFolderPath, List imagePaths, String batchImageFolderPath, List batchImagePaths, String pdfPath, String batchPdfPath, List batchPdfPaths) $default,) {final _that = this; +switch (_that) { +case _ParseFormState(): +return $default(_that.type,_that.url,_that.archivePath,_that.batchArchivePath,_that.batchArchivePaths,_that.imageFolderPath,_that.imagePaths,_that.batchImageFolderPath,_that.batchImagePaths,_that.pdfPath,_that.batchPdfPath,_that.batchPdfPaths);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( ParseFormType type, String url, String archivePath, String batchArchivePath, List batchArchivePaths, String imageFolderPath, List imagePaths, String batchImageFolderPath, List batchImagePaths, String pdfPath, String batchPdfPath, List batchPdfPaths)? $default,) {final _that = this; +switch (_that) { +case _ParseFormState() when $default != null: +return $default(_that.type,_that.url,_that.archivePath,_that.batchArchivePath,_that.batchArchivePaths,_that.imageFolderPath,_that.imagePaths,_that.batchImageFolderPath,_that.batchImagePaths,_that.pdfPath,_that.batchPdfPath,_that.batchPdfPaths);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseFormState implements ParseFormState { + const _ParseFormState({this.type = ParseFormType.web, this.url = '', this.archivePath = '', this.batchArchivePath = '', final List batchArchivePaths = const [], this.imageFolderPath = '', final List imagePaths = const [], this.batchImageFolderPath = '', final List batchImagePaths = const [], this.pdfPath = '', this.batchPdfPath = '', final List batchPdfPaths = const []}): _batchArchivePaths = batchArchivePaths,_imagePaths = imagePaths,_batchImagePaths = batchImagePaths,_batchPdfPaths = batchPdfPaths; + + +@override@JsonKey() final ParseFormType type; +@override@JsonKey() final String url; +@override@JsonKey() final String archivePath; +@override@JsonKey() final String batchArchivePath; + final List _batchArchivePaths; +@override@JsonKey() List get batchArchivePaths { + if (_batchArchivePaths is EqualUnmodifiableListView) return _batchArchivePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_batchArchivePaths); +} + +@override@JsonKey() final String imageFolderPath; + final List _imagePaths; +@override@JsonKey() List get imagePaths { + if (_imagePaths is EqualUnmodifiableListView) return _imagePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_imagePaths); +} + +@override@JsonKey() final String batchImageFolderPath; + final List _batchImagePaths; +@override@JsonKey() List get batchImagePaths { + if (_batchImagePaths is EqualUnmodifiableListView) return _batchImagePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_batchImagePaths); +} + +@override@JsonKey() final String pdfPath; +@override@JsonKey() final String batchPdfPath; + final List _batchPdfPaths; +@override@JsonKey() List get batchPdfPaths { + if (_batchPdfPaths is EqualUnmodifiableListView) return _batchPdfPaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_batchPdfPaths); +} + + +/// Create a copy of ParseFormState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseFormStateCopyWith<_ParseFormState> get copyWith => __$ParseFormStateCopyWithImpl<_ParseFormState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseFormState&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)&&(identical(other.archivePath, archivePath) || other.archivePath == archivePath)&&(identical(other.batchArchivePath, batchArchivePath) || other.batchArchivePath == batchArchivePath)&&const DeepCollectionEquality().equals(other._batchArchivePaths, _batchArchivePaths)&&(identical(other.imageFolderPath, imageFolderPath) || other.imageFolderPath == imageFolderPath)&&const DeepCollectionEquality().equals(other._imagePaths, _imagePaths)&&(identical(other.batchImageFolderPath, batchImageFolderPath) || other.batchImageFolderPath == batchImageFolderPath)&&const DeepCollectionEquality().equals(other._batchImagePaths, _batchImagePaths)&&(identical(other.pdfPath, pdfPath) || other.pdfPath == pdfPath)&&(identical(other.batchPdfPath, batchPdfPath) || other.batchPdfPath == batchPdfPath)&&const DeepCollectionEquality().equals(other._batchPdfPaths, _batchPdfPaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,type,url,archivePath,batchArchivePath,const DeepCollectionEquality().hash(_batchArchivePaths),imageFolderPath,const DeepCollectionEquality().hash(_imagePaths),batchImageFolderPath,const DeepCollectionEquality().hash(_batchImagePaths),pdfPath,batchPdfPath,const DeepCollectionEquality().hash(_batchPdfPaths)); + +@override +String toString() { + return 'ParseFormState(type: $type, url: $url, archivePath: $archivePath, batchArchivePath: $batchArchivePath, batchArchivePaths: $batchArchivePaths, imageFolderPath: $imageFolderPath, imagePaths: $imagePaths, batchImageFolderPath: $batchImageFolderPath, batchImagePaths: $batchImagePaths, pdfPath: $pdfPath, batchPdfPath: $batchPdfPath, batchPdfPaths: $batchPdfPaths)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseFormStateCopyWith<$Res> implements $ParseFormStateCopyWith<$Res> { + factory _$ParseFormStateCopyWith(_ParseFormState value, $Res Function(_ParseFormState) _then) = __$ParseFormStateCopyWithImpl; +@override @useResult +$Res call({ + ParseFormType type, String url, String archivePath, String batchArchivePath, List batchArchivePaths, String imageFolderPath, List imagePaths, String batchImageFolderPath, List batchImagePaths, String pdfPath, String batchPdfPath, List batchPdfPaths +}); + + + + +} +/// @nodoc +class __$ParseFormStateCopyWithImpl<$Res> + implements _$ParseFormStateCopyWith<$Res> { + __$ParseFormStateCopyWithImpl(this._self, this._then); + + final _ParseFormState _self; + final $Res Function(_ParseFormState) _then; + +/// Create a copy of ParseFormState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? url = null,Object? archivePath = null,Object? batchArchivePath = null,Object? batchArchivePaths = null,Object? imageFolderPath = null,Object? imagePaths = null,Object? batchImageFolderPath = null,Object? batchImagePaths = null,Object? pdfPath = null,Object? batchPdfPath = null,Object? batchPdfPaths = null,}) { + return _then(_ParseFormState( +type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable +as ParseFormType,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String,archivePath: null == archivePath ? _self.archivePath : archivePath // ignore: cast_nullable_to_non_nullable +as String,batchArchivePath: null == batchArchivePath ? _self.batchArchivePath : batchArchivePath // ignore: cast_nullable_to_non_nullable +as String,batchArchivePaths: null == batchArchivePaths ? _self._batchArchivePaths : batchArchivePaths // ignore: cast_nullable_to_non_nullable +as List,imageFolderPath: null == imageFolderPath ? _self.imageFolderPath : imageFolderPath // ignore: cast_nullable_to_non_nullable +as String,imagePaths: null == imagePaths ? _self._imagePaths : imagePaths // ignore: cast_nullable_to_non_nullable +as List,batchImageFolderPath: null == batchImageFolderPath ? _self.batchImageFolderPath : batchImageFolderPath // ignore: cast_nullable_to_non_nullable +as String,batchImagePaths: null == batchImagePaths ? _self._batchImagePaths : batchImagePaths // ignore: cast_nullable_to_non_nullable +as List,pdfPath: null == pdfPath ? _self.pdfPath : pdfPath // ignore: cast_nullable_to_non_nullable +as String,batchPdfPath: null == batchPdfPath ? _self.batchPdfPath : batchPdfPath // ignore: cast_nullable_to_non_nullable +as String,batchPdfPaths: null == batchPdfPaths ? _self._batchPdfPaths : batchPdfPaths // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +// dart format on diff --git a/lib/feature/parse/ui/provider/parse_form_provider.g.dart b/lib/feature/parse/ui/provider/parse_form_provider.g.dart new file mode 100644 index 0000000..5f9dd0b --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_form_provider.g.dart @@ -0,0 +1,62 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'parse_form_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(ParseForm) +final parseFormProvider = ParseFormProvider._(); + +final class ParseFormProvider + extends $NotifierProvider { + ParseFormProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'parseFormProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$parseFormHash(); + + @$internal + @override + ParseForm create() => ParseForm(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(ParseFormState value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$parseFormHash() => r'ee119b4fdf6d809a4cffeca8d5ce46ca9e2b17ff'; + +abstract class _$ParseForm extends $Notifier { + ParseFormState build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + ParseFormState, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/lib/feature/parse/ui/provider/parse_image_folder_provider.dart b/lib/feature/parse/ui/provider/parse_image_folder_provider.dart new file mode 100644 index 0000000..ab51493 --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_image_folder_provider.dart @@ -0,0 +1,156 @@ +import 'dart:io'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_riverpod/legacy.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:tele_book/feature/book/model/dto/save_as_book_dto.dart'; +import 'package:tele_book/feature/book/repository/book_repository.dart'; +import 'package:tele_book/feature/parse/service/parse_archive_service.dart'; + +part 'parse_image_folder_provider.freezed.dart'; + +part 'parse_image_folder_provider.g.dart'; + +@freezed +abstract class ParseImageFolderParam with _$ParseImageFolderParam { + const factory ParseImageFolderParam({ + String? folderPath, + List? imagePathsInput, + }) = _ParseImageFolderParam; +} + +@freezed +abstract class ParseImageFolderState with _$ParseImageFolderState { + const factory ParseImageFolderState({ + required String folderName, + required List imagePaths, + }) = _ParseImageFolderState; +} + +@freezed +abstract class ParseImageFolderSaveBookParam + with _$ParseImageFolderSaveBookParam { + const factory ParseImageFolderSaveBookParam({ + required String folderName, + required List imagePaths, + }) = _ParseImageFolderSaveBookParam; +} + +@freezed +abstract class ParseImageFolderSaveBookProgress + with _$ParseImageFolderSaveBookProgress { + const factory ParseImageFolderSaveBookProgress({ + @Default(0) int current, + @Default(0) int total, + }) = _ParseImageFolderSaveBookProgress; +} + +final parseImageFolderSaveBookProgressProvider = + StateProvider.family( + (_, __) => const ParseImageFolderSaveBookProgress(), + ); + +@riverpod +class ParseImageFolder extends _$ParseImageFolder { + ParseArchiveService get _parseArchiveService => + ref.read(parseArchiveServiceProvider); + + @override + FutureOr build(ParseImageFolderParam param) async { + final folderName = _resolveFolderName(param); + Future.microtask(() => _parseImageFolder(param)); + return ParseImageFolderState(folderName: folderName, imagePaths: const []); + } + + String _resolveFolderName(ParseImageFolderParam param) { + final folderPath = param.folderPath; + final imagePathsInput = param.imagePathsInput; + if (folderPath != null && folderPath.isNotEmpty) { + return folderPath.split(RegExp(r'[\\/]')).last; + } + + if (imagePathsInput != null && imagePathsInput.isNotEmpty) { + final parts = imagePathsInput.first.split(RegExp(r'[\\/]')); + return parts.length > 1 ? parts[parts.length - 2] : '导入图片'; + } + + return '导入图片'; + } + + Future _requestStoragePermission() async { + if (!Platform.isAndroid) return true; + if (await Permission.manageExternalStorage.isGranted) return true; + final status = await Permission.manageExternalStorage.request(); + if (status.isGranted) return true; + + if (await Permission.storage.isGranted) return true; + final storageStatus = await Permission.storage.request(); + return storageStatus.isGranted; + } + + Future _parseImageFolder(ParseImageFolderParam param) async { + state = const AsyncLoading(); + + final hasPermission = await _requestStoragePermission(); + if (!hasPermission) { + state = AsyncError('需要存储权限才能读取图片文件夹', StackTrace.current); + return; + } + + final folderName = _resolveFolderName(param); + final result = + param.imagePathsInput != null && param.imagePathsInput!.isNotEmpty + ? await _parseArchiveService.parseImagePaths(param.imagePathsInput!) + : await _parseArchiveService.parseImageFolder(param.folderPath ?? ''); + + result.fold( + onSuccess: (data) { + state = AsyncData( + ParseImageFolderState(folderName: folderName, imagePaths: data), + ); + }, + onError: (error) { + state = AsyncError(error.message, StackTrace.current); + }, + ); + } +} + +@riverpod +class ParseImageFolderSaveBook extends _$ParseImageFolderSaveBook { + BookRepository get _bookRepository => ref.read(bookRepositoryProvider); + + @override + FutureOr build(ParseImageFolderParam param) => null; + + Future submit(ParseImageFolderSaveBookParam submitParam) async { + if (state.isLoading || submitParam.imagePaths.isEmpty) return; + + state = const AsyncLoading(); + ref.read(parseImageFolderSaveBookProgressProvider(param).notifier).state = + ParseImageFolderSaveBookProgress( + current: 0, + total: submitParam.imagePaths.length, + ); + + final result = await _bookRepository.saveAsBook( + SaveAsBookDto(title: submitParam.folderName, paths: submitParam.imagePaths), + onProgress: (current, total) { + ref.read(parseImageFolderSaveBookProgressProvider(param).notifier).state = + ParseImageFolderSaveBookProgress(current: current, total: total); + }, + ); + + result.fold( + onSuccess: (_) { + state = const AsyncData(null); + }, + onError: (error) { + state = AsyncError(error.message, StackTrace.current); + }, + ); + } +} + diff --git a/lib/feature/parse/ui/provider/parse_image_folder_provider.freezed.dart b/lib/feature/parse/ui/provider/parse_image_folder_provider.freezed.dart new file mode 100644 index 0000000..2f42216 --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_image_folder_provider.freezed.dart @@ -0,0 +1,1074 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'parse_image_folder_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$ParseImageFolderParam { + + String? get folderPath; List? get imagePathsInput; +/// Create a copy of ParseImageFolderParam +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseImageFolderParamCopyWith get copyWith => _$ParseImageFolderParamCopyWithImpl(this as ParseImageFolderParam, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseImageFolderParam&&(identical(other.folderPath, folderPath) || other.folderPath == folderPath)&&const DeepCollectionEquality().equals(other.imagePathsInput, imagePathsInput)); +} + + +@override +int get hashCode => Object.hash(runtimeType,folderPath,const DeepCollectionEquality().hash(imagePathsInput)); + +@override +String toString() { + return 'ParseImageFolderParam(folderPath: $folderPath, imagePathsInput: $imagePathsInput)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseImageFolderParamCopyWith<$Res> { + factory $ParseImageFolderParamCopyWith(ParseImageFolderParam value, $Res Function(ParseImageFolderParam) _then) = _$ParseImageFolderParamCopyWithImpl; +@useResult +$Res call({ + String? folderPath, List? imagePathsInput +}); + + + + +} +/// @nodoc +class _$ParseImageFolderParamCopyWithImpl<$Res> + implements $ParseImageFolderParamCopyWith<$Res> { + _$ParseImageFolderParamCopyWithImpl(this._self, this._then); + + final ParseImageFolderParam _self; + final $Res Function(ParseImageFolderParam) _then; + +/// Create a copy of ParseImageFolderParam +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? folderPath = freezed,Object? imagePathsInput = freezed,}) { + return _then(_self.copyWith( +folderPath: freezed == folderPath ? _self.folderPath : folderPath // ignore: cast_nullable_to_non_nullable +as String?,imagePathsInput: freezed == imagePathsInput ? _self.imagePathsInput : imagePathsInput // ignore: cast_nullable_to_non_nullable +as List?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseImageFolderParam]. +extension ParseImageFolderParamPatterns on ParseImageFolderParam { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseImageFolderParam value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseImageFolderParam() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseImageFolderParam value) $default,){ +final _that = this; +switch (_that) { +case _ParseImageFolderParam(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseImageFolderParam value)? $default,){ +final _that = this; +switch (_that) { +case _ParseImageFolderParam() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String? folderPath, List? imagePathsInput)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseImageFolderParam() when $default != null: +return $default(_that.folderPath,_that.imagePathsInput);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String? folderPath, List? imagePathsInput) $default,) {final _that = this; +switch (_that) { +case _ParseImageFolderParam(): +return $default(_that.folderPath,_that.imagePathsInput);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? folderPath, List? imagePathsInput)? $default,) {final _that = this; +switch (_that) { +case _ParseImageFolderParam() when $default != null: +return $default(_that.folderPath,_that.imagePathsInput);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseImageFolderParam implements ParseImageFolderParam { + const _ParseImageFolderParam({this.folderPath, final List? imagePathsInput}): _imagePathsInput = imagePathsInput; + + +@override final String? folderPath; + final List? _imagePathsInput; +@override List? get imagePathsInput { + final value = _imagePathsInput; + if (value == null) return null; + if (_imagePathsInput is EqualUnmodifiableListView) return _imagePathsInput; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); +} + + +/// Create a copy of ParseImageFolderParam +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseImageFolderParamCopyWith<_ParseImageFolderParam> get copyWith => __$ParseImageFolderParamCopyWithImpl<_ParseImageFolderParam>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseImageFolderParam&&(identical(other.folderPath, folderPath) || other.folderPath == folderPath)&&const DeepCollectionEquality().equals(other._imagePathsInput, _imagePathsInput)); +} + + +@override +int get hashCode => Object.hash(runtimeType,folderPath,const DeepCollectionEquality().hash(_imagePathsInput)); + +@override +String toString() { + return 'ParseImageFolderParam(folderPath: $folderPath, imagePathsInput: $imagePathsInput)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseImageFolderParamCopyWith<$Res> implements $ParseImageFolderParamCopyWith<$Res> { + factory _$ParseImageFolderParamCopyWith(_ParseImageFolderParam value, $Res Function(_ParseImageFolderParam) _then) = __$ParseImageFolderParamCopyWithImpl; +@override @useResult +$Res call({ + String? folderPath, List? imagePathsInput +}); + + + + +} +/// @nodoc +class __$ParseImageFolderParamCopyWithImpl<$Res> + implements _$ParseImageFolderParamCopyWith<$Res> { + __$ParseImageFolderParamCopyWithImpl(this._self, this._then); + + final _ParseImageFolderParam _self; + final $Res Function(_ParseImageFolderParam) _then; + +/// Create a copy of ParseImageFolderParam +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? folderPath = freezed,Object? imagePathsInput = freezed,}) { + return _then(_ParseImageFolderParam( +folderPath: freezed == folderPath ? _self.folderPath : folderPath // ignore: cast_nullable_to_non_nullable +as String?,imagePathsInput: freezed == imagePathsInput ? _self._imagePathsInput : imagePathsInput // ignore: cast_nullable_to_non_nullable +as List?, + )); +} + + +} + +/// @nodoc +mixin _$ParseImageFolderState { + + String get folderName; List get imagePaths; +/// Create a copy of ParseImageFolderState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseImageFolderStateCopyWith get copyWith => _$ParseImageFolderStateCopyWithImpl(this as ParseImageFolderState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseImageFolderState&&(identical(other.folderName, folderName) || other.folderName == folderName)&&const DeepCollectionEquality().equals(other.imagePaths, imagePaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,folderName,const DeepCollectionEquality().hash(imagePaths)); + +@override +String toString() { + return 'ParseImageFolderState(folderName: $folderName, imagePaths: $imagePaths)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseImageFolderStateCopyWith<$Res> { + factory $ParseImageFolderStateCopyWith(ParseImageFolderState value, $Res Function(ParseImageFolderState) _then) = _$ParseImageFolderStateCopyWithImpl; +@useResult +$Res call({ + String folderName, List imagePaths +}); + + + + +} +/// @nodoc +class _$ParseImageFolderStateCopyWithImpl<$Res> + implements $ParseImageFolderStateCopyWith<$Res> { + _$ParseImageFolderStateCopyWithImpl(this._self, this._then); + + final ParseImageFolderState _self; + final $Res Function(ParseImageFolderState) _then; + +/// Create a copy of ParseImageFolderState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? folderName = null,Object? imagePaths = null,}) { + return _then(_self.copyWith( +folderName: null == folderName ? _self.folderName : folderName // ignore: cast_nullable_to_non_nullable +as String,imagePaths: null == imagePaths ? _self.imagePaths : imagePaths // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseImageFolderState]. +extension ParseImageFolderStatePatterns on ParseImageFolderState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseImageFolderState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseImageFolderState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseImageFolderState value) $default,){ +final _that = this; +switch (_that) { +case _ParseImageFolderState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseImageFolderState value)? $default,){ +final _that = this; +switch (_that) { +case _ParseImageFolderState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String folderName, List imagePaths)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseImageFolderState() when $default != null: +return $default(_that.folderName,_that.imagePaths);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String folderName, List imagePaths) $default,) {final _that = this; +switch (_that) { +case _ParseImageFolderState(): +return $default(_that.folderName,_that.imagePaths);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String folderName, List imagePaths)? $default,) {final _that = this; +switch (_that) { +case _ParseImageFolderState() when $default != null: +return $default(_that.folderName,_that.imagePaths);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseImageFolderState implements ParseImageFolderState { + const _ParseImageFolderState({required this.folderName, required final List imagePaths}): _imagePaths = imagePaths; + + +@override final String folderName; + final List _imagePaths; +@override List get imagePaths { + if (_imagePaths is EqualUnmodifiableListView) return _imagePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_imagePaths); +} + + +/// Create a copy of ParseImageFolderState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseImageFolderStateCopyWith<_ParseImageFolderState> get copyWith => __$ParseImageFolderStateCopyWithImpl<_ParseImageFolderState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseImageFolderState&&(identical(other.folderName, folderName) || other.folderName == folderName)&&const DeepCollectionEquality().equals(other._imagePaths, _imagePaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,folderName,const DeepCollectionEquality().hash(_imagePaths)); + +@override +String toString() { + return 'ParseImageFolderState(folderName: $folderName, imagePaths: $imagePaths)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseImageFolderStateCopyWith<$Res> implements $ParseImageFolderStateCopyWith<$Res> { + factory _$ParseImageFolderStateCopyWith(_ParseImageFolderState value, $Res Function(_ParseImageFolderState) _then) = __$ParseImageFolderStateCopyWithImpl; +@override @useResult +$Res call({ + String folderName, List imagePaths +}); + + + + +} +/// @nodoc +class __$ParseImageFolderStateCopyWithImpl<$Res> + implements _$ParseImageFolderStateCopyWith<$Res> { + __$ParseImageFolderStateCopyWithImpl(this._self, this._then); + + final _ParseImageFolderState _self; + final $Res Function(_ParseImageFolderState) _then; + +/// Create a copy of ParseImageFolderState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? folderName = null,Object? imagePaths = null,}) { + return _then(_ParseImageFolderState( +folderName: null == folderName ? _self.folderName : folderName // ignore: cast_nullable_to_non_nullable +as String,imagePaths: null == imagePaths ? _self._imagePaths : imagePaths // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +/// @nodoc +mixin _$ParseImageFolderSaveBookParam { + + String get folderName; List get imagePaths; +/// Create a copy of ParseImageFolderSaveBookParam +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseImageFolderSaveBookParamCopyWith get copyWith => _$ParseImageFolderSaveBookParamCopyWithImpl(this as ParseImageFolderSaveBookParam, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseImageFolderSaveBookParam&&(identical(other.folderName, folderName) || other.folderName == folderName)&&const DeepCollectionEquality().equals(other.imagePaths, imagePaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,folderName,const DeepCollectionEquality().hash(imagePaths)); + +@override +String toString() { + return 'ParseImageFolderSaveBookParam(folderName: $folderName, imagePaths: $imagePaths)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseImageFolderSaveBookParamCopyWith<$Res> { + factory $ParseImageFolderSaveBookParamCopyWith(ParseImageFolderSaveBookParam value, $Res Function(ParseImageFolderSaveBookParam) _then) = _$ParseImageFolderSaveBookParamCopyWithImpl; +@useResult +$Res call({ + String folderName, List imagePaths +}); + + + + +} +/// @nodoc +class _$ParseImageFolderSaveBookParamCopyWithImpl<$Res> + implements $ParseImageFolderSaveBookParamCopyWith<$Res> { + _$ParseImageFolderSaveBookParamCopyWithImpl(this._self, this._then); + + final ParseImageFolderSaveBookParam _self; + final $Res Function(ParseImageFolderSaveBookParam) _then; + +/// Create a copy of ParseImageFolderSaveBookParam +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? folderName = null,Object? imagePaths = null,}) { + return _then(_self.copyWith( +folderName: null == folderName ? _self.folderName : folderName // ignore: cast_nullable_to_non_nullable +as String,imagePaths: null == imagePaths ? _self.imagePaths : imagePaths // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseImageFolderSaveBookParam]. +extension ParseImageFolderSaveBookParamPatterns on ParseImageFolderSaveBookParam { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseImageFolderSaveBookParam value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseImageFolderSaveBookParam() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseImageFolderSaveBookParam value) $default,){ +final _that = this; +switch (_that) { +case _ParseImageFolderSaveBookParam(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseImageFolderSaveBookParam value)? $default,){ +final _that = this; +switch (_that) { +case _ParseImageFolderSaveBookParam() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String folderName, List imagePaths)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseImageFolderSaveBookParam() when $default != null: +return $default(_that.folderName,_that.imagePaths);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String folderName, List imagePaths) $default,) {final _that = this; +switch (_that) { +case _ParseImageFolderSaveBookParam(): +return $default(_that.folderName,_that.imagePaths);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String folderName, List imagePaths)? $default,) {final _that = this; +switch (_that) { +case _ParseImageFolderSaveBookParam() when $default != null: +return $default(_that.folderName,_that.imagePaths);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseImageFolderSaveBookParam implements ParseImageFolderSaveBookParam { + const _ParseImageFolderSaveBookParam({required this.folderName, required final List imagePaths}): _imagePaths = imagePaths; + + +@override final String folderName; + final List _imagePaths; +@override List get imagePaths { + if (_imagePaths is EqualUnmodifiableListView) return _imagePaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_imagePaths); +} + + +/// Create a copy of ParseImageFolderSaveBookParam +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseImageFolderSaveBookParamCopyWith<_ParseImageFolderSaveBookParam> get copyWith => __$ParseImageFolderSaveBookParamCopyWithImpl<_ParseImageFolderSaveBookParam>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseImageFolderSaveBookParam&&(identical(other.folderName, folderName) || other.folderName == folderName)&&const DeepCollectionEquality().equals(other._imagePaths, _imagePaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,folderName,const DeepCollectionEquality().hash(_imagePaths)); + +@override +String toString() { + return 'ParseImageFolderSaveBookParam(folderName: $folderName, imagePaths: $imagePaths)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseImageFolderSaveBookParamCopyWith<$Res> implements $ParseImageFolderSaveBookParamCopyWith<$Res> { + factory _$ParseImageFolderSaveBookParamCopyWith(_ParseImageFolderSaveBookParam value, $Res Function(_ParseImageFolderSaveBookParam) _then) = __$ParseImageFolderSaveBookParamCopyWithImpl; +@override @useResult +$Res call({ + String folderName, List imagePaths +}); + + + + +} +/// @nodoc +class __$ParseImageFolderSaveBookParamCopyWithImpl<$Res> + implements _$ParseImageFolderSaveBookParamCopyWith<$Res> { + __$ParseImageFolderSaveBookParamCopyWithImpl(this._self, this._then); + + final _ParseImageFolderSaveBookParam _self; + final $Res Function(_ParseImageFolderSaveBookParam) _then; + +/// Create a copy of ParseImageFolderSaveBookParam +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? folderName = null,Object? imagePaths = null,}) { + return _then(_ParseImageFolderSaveBookParam( +folderName: null == folderName ? _self.folderName : folderName // ignore: cast_nullable_to_non_nullable +as String,imagePaths: null == imagePaths ? _self._imagePaths : imagePaths // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +/// @nodoc +mixin _$ParseImageFolderSaveBookProgress { + + int get current; int get total; +/// Create a copy of ParseImageFolderSaveBookProgress +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseImageFolderSaveBookProgressCopyWith get copyWith => _$ParseImageFolderSaveBookProgressCopyWithImpl(this as ParseImageFolderSaveBookProgress, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseImageFolderSaveBookProgress&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); +} + + +@override +int get hashCode => Object.hash(runtimeType,current,total); + +@override +String toString() { + return 'ParseImageFolderSaveBookProgress(current: $current, total: $total)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseImageFolderSaveBookProgressCopyWith<$Res> { + factory $ParseImageFolderSaveBookProgressCopyWith(ParseImageFolderSaveBookProgress value, $Res Function(ParseImageFolderSaveBookProgress) _then) = _$ParseImageFolderSaveBookProgressCopyWithImpl; +@useResult +$Res call({ + int current, int total +}); + + + + +} +/// @nodoc +class _$ParseImageFolderSaveBookProgressCopyWithImpl<$Res> + implements $ParseImageFolderSaveBookProgressCopyWith<$Res> { + _$ParseImageFolderSaveBookProgressCopyWithImpl(this._self, this._then); + + final ParseImageFolderSaveBookProgress _self; + final $Res Function(ParseImageFolderSaveBookProgress) _then; + +/// Create a copy of ParseImageFolderSaveBookProgress +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? current = null,Object? total = null,}) { + return _then(_self.copyWith( +current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable +as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseImageFolderSaveBookProgress]. +extension ParseImageFolderSaveBookProgressPatterns on ParseImageFolderSaveBookProgress { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseImageFolderSaveBookProgress value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseImageFolderSaveBookProgress() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseImageFolderSaveBookProgress value) $default,){ +final _that = this; +switch (_that) { +case _ParseImageFolderSaveBookProgress(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseImageFolderSaveBookProgress value)? $default,){ +final _that = this; +switch (_that) { +case _ParseImageFolderSaveBookProgress() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( int current, int total)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseImageFolderSaveBookProgress() when $default != null: +return $default(_that.current,_that.total);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( int current, int total) $default,) {final _that = this; +switch (_that) { +case _ParseImageFolderSaveBookProgress(): +return $default(_that.current,_that.total);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( int current, int total)? $default,) {final _that = this; +switch (_that) { +case _ParseImageFolderSaveBookProgress() when $default != null: +return $default(_that.current,_that.total);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseImageFolderSaveBookProgress implements ParseImageFolderSaveBookProgress { + const _ParseImageFolderSaveBookProgress({this.current = 0, this.total = 0}); + + +@override@JsonKey() final int current; +@override@JsonKey() final int total; + +/// Create a copy of ParseImageFolderSaveBookProgress +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseImageFolderSaveBookProgressCopyWith<_ParseImageFolderSaveBookProgress> get copyWith => __$ParseImageFolderSaveBookProgressCopyWithImpl<_ParseImageFolderSaveBookProgress>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseImageFolderSaveBookProgress&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); +} + + +@override +int get hashCode => Object.hash(runtimeType,current,total); + +@override +String toString() { + return 'ParseImageFolderSaveBookProgress(current: $current, total: $total)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseImageFolderSaveBookProgressCopyWith<$Res> implements $ParseImageFolderSaveBookProgressCopyWith<$Res> { + factory _$ParseImageFolderSaveBookProgressCopyWith(_ParseImageFolderSaveBookProgress value, $Res Function(_ParseImageFolderSaveBookProgress) _then) = __$ParseImageFolderSaveBookProgressCopyWithImpl; +@override @useResult +$Res call({ + int current, int total +}); + + + + +} +/// @nodoc +class __$ParseImageFolderSaveBookProgressCopyWithImpl<$Res> + implements _$ParseImageFolderSaveBookProgressCopyWith<$Res> { + __$ParseImageFolderSaveBookProgressCopyWithImpl(this._self, this._then); + + final _ParseImageFolderSaveBookProgress _self; + final $Res Function(_ParseImageFolderSaveBookProgress) _then; + +/// Create a copy of ParseImageFolderSaveBookProgress +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? current = null,Object? total = null,}) { + return _then(_ParseImageFolderSaveBookProgress( +current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable +as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +// dart format on diff --git a/lib/feature/parse/ui/provider/parse_image_folder_provider.g.dart b/lib/feature/parse/ui/provider/parse_image_folder_provider.g.dart new file mode 100644 index 0000000..8f61810 --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_image_folder_provider.g.dart @@ -0,0 +1,196 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'parse_image_folder_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(ParseImageFolder) +final parseImageFolderProvider = ParseImageFolderFamily._(); + +final class ParseImageFolderProvider + extends $AsyncNotifierProvider { + ParseImageFolderProvider._({ + required ParseImageFolderFamily super.from, + required ParseImageFolderParam super.argument, + }) : super( + retry: null, + name: r'parseImageFolderProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$parseImageFolderHash(); + + @override + String toString() { + return r'parseImageFolderProvider' + '' + '($argument)'; + } + + @$internal + @override + ParseImageFolder create() => ParseImageFolder(); + + @override + bool operator ==(Object other) { + return other is ParseImageFolderProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$parseImageFolderHash() => r'dcffb0f2ea9ae5610fe4b0de8fdb4a6e8c25e1dc'; + +final class ParseImageFolderFamily extends $Family + with + $ClassFamilyOverride< + ParseImageFolder, + AsyncValue, + ParseImageFolderState, + FutureOr, + ParseImageFolderParam + > { + ParseImageFolderFamily._() + : super( + retry: null, + name: r'parseImageFolderProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + ParseImageFolderProvider call(ParseImageFolderParam param) => + ParseImageFolderProvider._(argument: param, from: this); + + @override + String toString() => r'parseImageFolderProvider'; +} + +abstract class _$ParseImageFolder + extends $AsyncNotifier { + late final _$args = ref.$arg as ParseImageFolderParam; + ParseImageFolderParam get param => _$args; + + FutureOr build(ParseImageFolderParam param); + @$mustCallSuper + @override + void runBuild() { + final ref = + this.ref + as $Ref, ParseImageFolderState>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier< + AsyncValue, + ParseImageFolderState + >, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} + +@ProviderFor(ParseImageFolderSaveBook) +final parseImageFolderSaveBookProvider = ParseImageFolderSaveBookFamily._(); + +final class ParseImageFolderSaveBookProvider + extends $AsyncNotifierProvider { + ParseImageFolderSaveBookProvider._({ + required ParseImageFolderSaveBookFamily super.from, + required ParseImageFolderParam super.argument, + }) : super( + retry: null, + name: r'parseImageFolderSaveBookProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$parseImageFolderSaveBookHash(); + + @override + String toString() { + return r'parseImageFolderSaveBookProvider' + '' + '($argument)'; + } + + @$internal + @override + ParseImageFolderSaveBook create() => ParseImageFolderSaveBook(); + + @override + bool operator ==(Object other) { + return other is ParseImageFolderSaveBookProvider && + other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$parseImageFolderSaveBookHash() => + r'c95340ec81291d91a439c61940e0a656dfa47dae'; + +final class ParseImageFolderSaveBookFamily extends $Family + with + $ClassFamilyOverride< + ParseImageFolderSaveBook, + AsyncValue, + void, + FutureOr, + ParseImageFolderParam + > { + ParseImageFolderSaveBookFamily._() + : super( + retry: null, + name: r'parseImageFolderSaveBookProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + ParseImageFolderSaveBookProvider call(ParseImageFolderParam param) => + ParseImageFolderSaveBookProvider._(argument: param, from: this); + + @override + String toString() => r'parseImageFolderSaveBookProvider'; +} + +abstract class _$ParseImageFolderSaveBook extends $AsyncNotifier { + late final _$args = ref.$arg as ParseImageFolderParam; + ParseImageFolderParam get param => _$args; + + FutureOr build(ParseImageFolderParam param); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, void>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, void>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} diff --git a/lib/feature/parse/ui/provider/parse_pdf_provider.dart b/lib/feature/parse/ui/provider/parse_pdf_provider.dart new file mode 100644 index 0000000..4c14138 --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_pdf_provider.dart @@ -0,0 +1,102 @@ +import 'package:flutter_riverpod/experimental/mutation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_riverpod/legacy.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:tele_book/feature/book/model/dto/save_as_book_dto.dart'; +import 'package:tele_book/feature/book/repository/book_repository.dart'; +import 'package:tele_book/feature/parse/service/parse_pdf_service.dart'; + +part 'parse_pdf_provider.freezed.dart'; + +part 'parse_pdf_provider.g.dart'; + +@freezed +abstract class ParsePdfState with _$ParsePdfState { + const factory ParsePdfState({ + required String pdfName, + required List tempPaths, + }) = _ParsePdfProgressState; +} + +final parsePdfProgressProvider = + StateProvider.family<(int current, int total), String>( + (ref, pdfPath) => (0, 0), + ); + +@riverpod +class ParsePdf extends _$ParsePdf { + ParsePdfService get _service => ref.read(parsePdfServiceProvider); + + @override + FutureOr build(String pdfPath) async { + final pdfName = pdfPath.split(RegExp(r'[\\/]')).last.replaceAll('.pdf', ''); + Future.microtask(() => _parsePdf(pdfPath)); + return ParsePdfState(tempPaths: const [], pdfName: pdfName); + } + + Future _parsePdf(String pdfPath) async { + final pdfName = pdfPath.split(RegExp(r'[\\/]')).last.replaceAll('.pdf', ''); + + ref.read(parsePdfProgressProvider(pdfPath).notifier).state = (0, 0); + + state = AsyncValue.loading(); + final result = await _service.parsePdf( + pdfName, + onProgress: (current, total) { + ref.read(parsePdfProgressProvider(pdfPath).notifier).state = ( + current, + total, + ); + }, + ); + result.fold( + onSuccess: (data) { + state = AsyncValue.data( + ParsePdfState(tempPaths: data, pdfName: pdfName), + ); + }, + onError: (error) { + state = AsyncValue.error(error.message, StackTrace.current); + }, + ); + } +} + +final parsePdfSaveBookProgressProvider = StateProvider<(int current, int total)>( + (_) => (0, 0), +); + +@riverpod +class ParsePdfSaveBook extends _$ParsePdfSaveBook { + BookRepository get _repository => ref.read(bookRepositoryProvider); + + @override + FutureOr build() async => null; + + Future onSave( + WidgetRef ref, + List tempPaths, + String pdfName, + ) async { + state = AsyncValue.loading(); + + ref.read(parsePdfSaveBookProgressProvider.notifier).state = (0, tempPaths.length); + + final result = await _repository.saveAsBook( + SaveAsBookDto(title: pdfName, paths: tempPaths), + onProgress: (current, total) { + ref.read(parsePdfSaveBookProgressProvider.notifier).state = (current, total); + }, + ); + + result.fold( + onSuccess: (_) { + state = AsyncValue.data(null); + }, + onError: (e) { + state = AsyncValue.error(e.message, StackTrace.current); + }, + ); + } +} diff --git a/lib/feature/parse/ui/provider/parse_pdf_provider.freezed.dart b/lib/feature/parse/ui/provider/parse_pdf_provider.freezed.dart new file mode 100644 index 0000000..41bc883 --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_pdf_provider.freezed.dart @@ -0,0 +1,280 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'parse_pdf_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$ParsePdfState { + + String get pdfName; List get tempPaths; +/// Create a copy of ParsePdfState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParsePdfStateCopyWith get copyWith => _$ParsePdfStateCopyWithImpl(this as ParsePdfState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParsePdfState&&(identical(other.pdfName, pdfName) || other.pdfName == pdfName)&&const DeepCollectionEquality().equals(other.tempPaths, tempPaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,pdfName,const DeepCollectionEquality().hash(tempPaths)); + +@override +String toString() { + return 'ParsePdfState(pdfName: $pdfName, tempPaths: $tempPaths)'; +} + + +} + +/// @nodoc +abstract mixin class $ParsePdfStateCopyWith<$Res> { + factory $ParsePdfStateCopyWith(ParsePdfState value, $Res Function(ParsePdfState) _then) = _$ParsePdfStateCopyWithImpl; +@useResult +$Res call({ + String pdfName, List tempPaths +}); + + + + +} +/// @nodoc +class _$ParsePdfStateCopyWithImpl<$Res> + implements $ParsePdfStateCopyWith<$Res> { + _$ParsePdfStateCopyWithImpl(this._self, this._then); + + final ParsePdfState _self; + final $Res Function(ParsePdfState) _then; + +/// Create a copy of ParsePdfState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? pdfName = null,Object? tempPaths = null,}) { + return _then(_self.copyWith( +pdfName: null == pdfName ? _self.pdfName : pdfName // ignore: cast_nullable_to_non_nullable +as String,tempPaths: null == tempPaths ? _self.tempPaths : tempPaths // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParsePdfState]. +extension ParsePdfStatePatterns on ParsePdfState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParsePdfProgressState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParsePdfProgressState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParsePdfProgressState value) $default,){ +final _that = this; +switch (_that) { +case _ParsePdfProgressState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParsePdfProgressState value)? $default,){ +final _that = this; +switch (_that) { +case _ParsePdfProgressState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String pdfName, List tempPaths)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParsePdfProgressState() when $default != null: +return $default(_that.pdfName,_that.tempPaths);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String pdfName, List tempPaths) $default,) {final _that = this; +switch (_that) { +case _ParsePdfProgressState(): +return $default(_that.pdfName,_that.tempPaths);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String pdfName, List tempPaths)? $default,) {final _that = this; +switch (_that) { +case _ParsePdfProgressState() when $default != null: +return $default(_that.pdfName,_that.tempPaths);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParsePdfProgressState implements ParsePdfState { + const _ParsePdfProgressState({required this.pdfName, required final List tempPaths}): _tempPaths = tempPaths; + + +@override final String pdfName; + final List _tempPaths; +@override List get tempPaths { + if (_tempPaths is EqualUnmodifiableListView) return _tempPaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_tempPaths); +} + + +/// Create a copy of ParsePdfState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParsePdfProgressStateCopyWith<_ParsePdfProgressState> get copyWith => __$ParsePdfProgressStateCopyWithImpl<_ParsePdfProgressState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParsePdfProgressState&&(identical(other.pdfName, pdfName) || other.pdfName == pdfName)&&const DeepCollectionEquality().equals(other._tempPaths, _tempPaths)); +} + + +@override +int get hashCode => Object.hash(runtimeType,pdfName,const DeepCollectionEquality().hash(_tempPaths)); + +@override +String toString() { + return 'ParsePdfState(pdfName: $pdfName, tempPaths: $tempPaths)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParsePdfProgressStateCopyWith<$Res> implements $ParsePdfStateCopyWith<$Res> { + factory _$ParsePdfProgressStateCopyWith(_ParsePdfProgressState value, $Res Function(_ParsePdfProgressState) _then) = __$ParsePdfProgressStateCopyWithImpl; +@override @useResult +$Res call({ + String pdfName, List tempPaths +}); + + + + +} +/// @nodoc +class __$ParsePdfProgressStateCopyWithImpl<$Res> + implements _$ParsePdfProgressStateCopyWith<$Res> { + __$ParsePdfProgressStateCopyWithImpl(this._self, this._then); + + final _ParsePdfProgressState _self; + final $Res Function(_ParsePdfProgressState) _then; + +/// Create a copy of ParsePdfState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? pdfName = null,Object? tempPaths = null,}) { + return _then(_ParsePdfProgressState( +pdfName: null == pdfName ? _self.pdfName : pdfName // ignore: cast_nullable_to_non_nullable +as String,tempPaths: null == tempPaths ? _self._tempPaths : tempPaths // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +// dart format on diff --git a/lib/feature/parse/ui/provider/parse_pdf_provider.g.dart b/lib/feature/parse/ui/provider/parse_pdf_provider.g.dart new file mode 100644 index 0000000..317c776 --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_pdf_provider.g.dart @@ -0,0 +1,143 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'parse_pdf_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(ParsePdf) +final parsePdfProvider = ParsePdfFamily._(); + +final class ParsePdfProvider + extends $AsyncNotifierProvider { + ParsePdfProvider._({ + required ParsePdfFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'parsePdfProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$parsePdfHash(); + + @override + String toString() { + return r'parsePdfProvider' + '' + '($argument)'; + } + + @$internal + @override + ParsePdf create() => ParsePdf(); + + @override + bool operator ==(Object other) { + return other is ParsePdfProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$parsePdfHash() => r'2b3746c5d1962646ecf4a6e600d7e92c093fe77b'; + +final class ParsePdfFamily extends $Family + with + $ClassFamilyOverride< + ParsePdf, + AsyncValue, + ParsePdfState, + FutureOr, + String + > { + ParsePdfFamily._() + : super( + retry: null, + name: r'parsePdfProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + ParsePdfProvider call(String pdfPath) => + ParsePdfProvider._(argument: pdfPath, from: this); + + @override + String toString() => r'parsePdfProvider'; +} + +abstract class _$ParsePdf extends $AsyncNotifier { + late final _$args = ref.$arg as String; + String get pdfPath => _$args; + + FutureOr build(String pdfPath); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, ParsePdfState>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, ParsePdfState>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} + +@ProviderFor(ParsePdfSaveBook) +final parsePdfSaveBookProvider = ParsePdfSaveBookProvider._(); + +final class ParsePdfSaveBookProvider + extends $AsyncNotifierProvider { + ParsePdfSaveBookProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'parsePdfSaveBookProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$parsePdfSaveBookHash(); + + @$internal + @override + ParsePdfSaveBook create() => ParsePdfSaveBook(); +} + +String _$parsePdfSaveBookHash() => r'a7d10d9c9b6aeeb2e470926be2aa25898dbefd2e'; + +abstract class _$ParsePdfSaveBook extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, void>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, void>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/lib/feature/parse/ui/provider/parse_web_provider.dart b/lib/feature/parse/ui/provider/parse_web_provider.dart new file mode 100644 index 0000000..1fba543 --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_web_provider.dart @@ -0,0 +1,66 @@ +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:tele_book/feature/download/service/download_service.dart'; +import 'package:tele_book/feature/parse/service/parse_web_service.dart'; + +part 'parse_web_provider.g.dart'; + +part 'parse_web_provider.freezed.dart'; + +@freezed +abstract class ParseWebState with _$ParseWebState { + const factory ParseWebState({ + required String title, + required List urls, + required int progress, + }) = _ParseWebState; +} + +@riverpod +class ParseWeb extends _$ParseWeb { + ParseWebService get _parseWebService => ref.read(parseWebServiceProvider); + + DownloadService get _downloadService => ref.read(downloadServiceProvider); + + InAppWebViewController? _webViewController; + + @override + ParseWebState build(String url) { + Future.microtask(() => _initialize(url)); + return const ParseWebState(title: '加载中...', urls: [], progress: 0); + } + + Future _initialize(String url) async { + state = state.copyWith(progress: 0); + await Future.delayed(Duration.zero); + } + + void onLoadStart(InAppWebViewController controller) { + _webViewController = controller; + } + + void onTitleChanged(InAppWebViewController controller, String? title) { + state = state.copyWith(title: title ?? '未知标题'); + } + + Future onProgressChange( + InAppWebViewController controller, + int progress, + ) async { + _webViewController = controller; + final urls = await _parseWebService.extractImagesFromWebView( + onExtractImages: (js) async { + final result = await controller.evaluateJavascript(source: js); + return result?.toString(); + }, + ); + state = state.copyWith(urls: urls, progress: progress); + } + + Future startDownload() async { + final current = state; + if (current.urls.isEmpty) return; + _downloadService.startDownload(current.urls, current.title); + } +} diff --git a/lib/feature/parse/ui/provider/parse_web_provider.freezed.dart b/lib/feature/parse/ui/provider/parse_web_provider.freezed.dart new file mode 100644 index 0000000..0ab9ec1 --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_web_provider.freezed.dart @@ -0,0 +1,283 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'parse_web_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$ParseWebState { + + String get title; List get urls; int get progress; +/// Create a copy of ParseWebState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParseWebStateCopyWith get copyWith => _$ParseWebStateCopyWithImpl(this as ParseWebState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseWebState&&(identical(other.title, title) || other.title == title)&&const DeepCollectionEquality().equals(other.urls, urls)&&(identical(other.progress, progress) || other.progress == progress)); +} + + +@override +int get hashCode => Object.hash(runtimeType,title,const DeepCollectionEquality().hash(urls),progress); + +@override +String toString() { + return 'ParseWebState(title: $title, urls: $urls, progress: $progress)'; +} + + +} + +/// @nodoc +abstract mixin class $ParseWebStateCopyWith<$Res> { + factory $ParseWebStateCopyWith(ParseWebState value, $Res Function(ParseWebState) _then) = _$ParseWebStateCopyWithImpl; +@useResult +$Res call({ + String title, List urls, int progress +}); + + + + +} +/// @nodoc +class _$ParseWebStateCopyWithImpl<$Res> + implements $ParseWebStateCopyWith<$Res> { + _$ParseWebStateCopyWithImpl(this._self, this._then); + + final ParseWebState _self; + final $Res Function(ParseWebState) _then; + +/// Create a copy of ParseWebState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? title = null,Object? urls = null,Object? progress = null,}) { + return _then(_self.copyWith( +title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,urls: null == urls ? _self.urls : urls // ignore: cast_nullable_to_non_nullable +as List,progress: null == progress ? _self.progress : progress // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ParseWebState]. +extension ParseWebStatePatterns on ParseWebState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ParseWebState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ParseWebState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ParseWebState value) $default,){ +final _that = this; +switch (_that) { +case _ParseWebState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ParseWebState value)? $default,){ +final _that = this; +switch (_that) { +case _ParseWebState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String title, List urls, int progress)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ParseWebState() when $default != null: +return $default(_that.title,_that.urls,_that.progress);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String title, List urls, int progress) $default,) {final _that = this; +switch (_that) { +case _ParseWebState(): +return $default(_that.title,_that.urls,_that.progress);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String title, List urls, int progress)? $default,) {final _that = this; +switch (_that) { +case _ParseWebState() when $default != null: +return $default(_that.title,_that.urls,_that.progress);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ParseWebState implements ParseWebState { + const _ParseWebState({required this.title, required final List urls, required this.progress}): _urls = urls; + + +@override final String title; + final List _urls; +@override List get urls { + if (_urls is EqualUnmodifiableListView) return _urls; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_urls); +} + +@override final int progress; + +/// Create a copy of ParseWebState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ParseWebStateCopyWith<_ParseWebState> get copyWith => __$ParseWebStateCopyWithImpl<_ParseWebState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseWebState&&(identical(other.title, title) || other.title == title)&&const DeepCollectionEquality().equals(other._urls, _urls)&&(identical(other.progress, progress) || other.progress == progress)); +} + + +@override +int get hashCode => Object.hash(runtimeType,title,const DeepCollectionEquality().hash(_urls),progress); + +@override +String toString() { + return 'ParseWebState(title: $title, urls: $urls, progress: $progress)'; +} + + +} + +/// @nodoc +abstract mixin class _$ParseWebStateCopyWith<$Res> implements $ParseWebStateCopyWith<$Res> { + factory _$ParseWebStateCopyWith(_ParseWebState value, $Res Function(_ParseWebState) _then) = __$ParseWebStateCopyWithImpl; +@override @useResult +$Res call({ + String title, List urls, int progress +}); + + + + +} +/// @nodoc +class __$ParseWebStateCopyWithImpl<$Res> + implements _$ParseWebStateCopyWith<$Res> { + __$ParseWebStateCopyWithImpl(this._self, this._then); + + final _ParseWebState _self; + final $Res Function(_ParseWebState) _then; + +/// Create a copy of ParseWebState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? title = null,Object? urls = null,Object? progress = null,}) { + return _then(_ParseWebState( +title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,urls: null == urls ? _self._urls : urls // ignore: cast_nullable_to_non_nullable +as List,progress: null == progress ? _self.progress : progress // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +// dart format on diff --git a/lib/feature/parse/ui/provider/parse_web_provider.g.dart b/lib/feature/parse/ui/provider/parse_web_provider.g.dart new file mode 100644 index 0000000..c54c0bc --- /dev/null +++ b/lib/feature/parse/ui/provider/parse_web_provider.g.dart @@ -0,0 +1,107 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'parse_web_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(ParseWeb) +final parseWebProvider = ParseWebFamily._(); + +final class ParseWebProvider + extends $NotifierProvider { + ParseWebProvider._({ + required ParseWebFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'parseWebProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$parseWebHash(); + + @override + String toString() { + return r'parseWebProvider' + '' + '($argument)'; + } + + @$internal + @override + ParseWeb create() => ParseWeb(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(ParseWebState value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } + + @override + bool operator ==(Object other) { + return other is ParseWebProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$parseWebHash() => r'676e20c1d434b2780a7a4e71d5c43ac9f6929b28'; + +final class ParseWebFamily extends $Family + with + $ClassFamilyOverride< + ParseWeb, + ParseWebState, + ParseWebState, + ParseWebState, + String + > { + ParseWebFamily._() + : super( + retry: null, + name: r'parseWebProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + ParseWebProvider call(String url) => + ParseWebProvider._(argument: url, from: this); + + @override + String toString() => r'parseWebProvider'; +} + +abstract class _$ParseWeb extends $Notifier { + late final _$args = ref.$arg as String; + String get url => _$args; + + ParseWebState build(String url); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + ParseWebState, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} diff --git a/lib/feature/parse/ui/view/parse_archive_view.dart b/lib/feature/parse/ui/view/parse_archive_view.dart index c27d539..230243a 100644 --- a/lib/feature/parse/ui/view/parse_archive_view.dart +++ b/lib/feature/parse/ui/view/parse_archive_view.dart @@ -1,62 +1,118 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:tele_book/core/util/state_util.dart'; -import 'package:tele_book/feature/parse/ui/viewmodel/parse_archive_viewmodel.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:tele_book/common/widget/error_widget.dart'; +import 'package:tele_book/core/route/app_route.dart'; +import 'package:tele_book/feature/parse/ui/provider/parse_archive_provider.dart'; -class ParseArchiveView extends StatelessWidget { +class ParseArchiveView extends ConsumerWidget { final String archivePath; const ParseArchiveView({super.key, required this.archivePath}); @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (context) => ParseArchiveViewmodel( - archivePath: archivePath, - parseArchiveService: context.read(), - bookRepository: context.read(), - ), - child: _ParseArchiveContent(), + Widget build(BuildContext context, WidgetRef ref) { + final provider = parseArchiveProvider(archivePath); + final asyncState = ref.watch(provider); + final parseProgress = ref.watch(parseArchiveProgressProvider(archivePath)); + + final saveState = ref.watch(parseArchiveSaveBookProvider); + final saveNotifier = ref.watch(parseArchiveSaveBookProvider.notifier); + final saveProgress = ref.watch(parseArchiveSaveBookProgressProvider); + + ref.listen(parseArchiveSaveBookProvider, (previous, next) { + if (next.hasError) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('保存失败:${next.error}'))); + } else if (previous?.isLoading == true && next.hasValue) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('保存成功'))); + context.go(AppRoute.main); + } + }); + + return _ParseArchiveContent( + asyncState: asyncState, + parseProgress: parseProgress, + saveState: saveState, + saveProgress: saveProgress, + onSave: (data) { + saveNotifier.submit( + ParseArchiveSaveBookParam( + archiveName: data.archiveName, + tempPaths: data.tempPaths, + ), + ); + }, ); } } class _ParseArchiveContent extends StatelessWidget { + final AsyncValue asyncState; + final (int current, int total) parseProgress; + final AsyncValue saveState; + final ParseArchiveSaveBookProgress saveProgress; + final void Function(ParseArchiveState data) onSave; + + const _ParseArchiveContent({ + required this.asyncState, + required this.parseProgress, + required this.saveState, + required this.saveProgress, + required this.onSave, + }); + @override Widget build(BuildContext context) { - final vm = context.watch(); return Scaffold( - appBar: AppBar(title: Text("解析压缩包")), - body: vm.parseState.when>( - success: (images) => GridView.builder( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - mainAxisSpacing: 4, - crossAxisSpacing: 4, + appBar: AppBar(title: const Text('解析压缩包')), + body: asyncState.when( + loading: () => Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 12), + Text('正在解析 ${parseProgress.$1}/${parseProgress.$2}'), + ], ), - itemBuilder: (context, index) { - final image = images[index]; - return Image.file(File(image), fit: BoxFit.cover); - }, - itemCount: images.length, ), + error: (error, stack) => Center( + child: CustomErrorWidget( + errorMessage: error.toString(), + stackTrace: stack, + ), + ), + data: (state) => state.tempPaths.isEmpty + ? const Center(child: Text('未解析到图片')) + : GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + mainAxisSpacing: 4, + crossAxisSpacing: 4, + ), + itemBuilder: (context, index) { + final image = state.tempPaths[index]; + return Image.file(File(image), fit: BoxFit.cover); + }, + itemCount: state.tempPaths.length, + ), ), - bottomNavigationBar: vm.parseState.isSuccess + bottomNavigationBar: asyncState.value?.tempPaths.isNotEmpty == true ? Padding( padding: EdgeInsets.all(16), child: FilledButton( - onPressed: vm.saveToBookState.isLoading + onPressed: saveState.isLoading ? null - : () => vm.saveToBook(context), - child: vm.saveToBookState.isLoading - ? SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : Text("保存到书架"), + : () => onSave(asyncState.value!), + child: saveState.isLoading + ? Text('保存中 ${saveProgress.current}/${saveProgress.total}') + : const Text('保存到书架'), ), ) : null, diff --git a/lib/feature/parse/ui/view/parse_batch_archive_view.dart b/lib/feature/parse/ui/view/parse_batch_archive_view.dart index abff88f..e613009 100644 --- a/lib/feature/parse/ui/view/parse_batch_archive_view.dart +++ b/lib/feature/parse/ui/view/parse_batch_archive_view.dart @@ -1,14 +1,16 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; -import 'package:provider/provider.dart'; +import 'package:go_router/go_router.dart'; +import 'package:tele_book/common/widget/error_widget.dart'; import 'package:tele_book/common/widget/local_image_widget.dart'; -import 'package:tele_book/core/util/state_util.dart'; +import 'package:tele_book/core/route/app_route.dart'; import 'package:tele_book/feature/parse/model/parse_batch_archive_vo.dart'; -import 'package:tele_book/feature/parse/ui/viewmodel/parse_batch_archive_viewmodel.dart'; +import 'package:tele_book/feature/parse/ui/provider/parse_batch_archive_provider.dart'; -class ParseBatchArchiveView extends StatelessWidget { +class ParseBatchArchiveView extends ConsumerWidget { final String? archiveDirPath; final List? archivePaths; @@ -19,82 +21,123 @@ class ParseBatchArchiveView extends StatelessWidget { }); @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (context) => ParseBatchArchiveViewmodel( + Widget build(BuildContext context, WidgetRef ref) { + final provider = parseBatchArchiveProvider( + ParseBatchArchiveParam( archiveDirPath: archiveDirPath, archivePaths: archivePaths, - parseArchiveService: context.read(), - bookRepository: context.read(), ), - child: _ParseBatchArchiveContentView(), + ); + final asyncState = ref.watch(provider); + final parseProgress = ref.watch(parseBatchArchiveProgressProvider); + + final saveState = ref.watch(parseBatchArchiveSaveBookProvider); + final saveProgress = ref.watch(parseBatchArchiveSaveBookProgressProvider); + final saveNotifier = ref.watch(parseBatchArchiveSaveBookProvider.notifier); + + ref.listen(parseBatchArchiveSaveBookProvider, (previous, next) { + if (next.hasError) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('保存失败:${next.error}'))); + } else if (previous?.isLoading == true && next.hasValue) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('保存成功'))); + context.go(AppRoute.main); + } + }); + + return _ParseBatchArchiveContentView( + asyncState: asyncState, + parseProgress: parseProgress, + saveState: saveState, + saveProgress: saveProgress, + onSave: (data) => saveNotifier.saveBatchAsBook(data), ); } } class _ParseBatchArchiveContentView extends StatelessWidget { + final AsyncValue asyncState; + final ParseBatchArchiveProgress parseProgress; + final AsyncValue saveState; + final ParseBatchArchiveSaveBookProgress saveProgress; + final void Function(List data) onSave; + + const _ParseBatchArchiveContentView({ + required this.asyncState, + required this.parseProgress, + required this.saveState, + required this.saveProgress, + required this.onSave, + }); + @override Widget build(BuildContext context) { - final viewmodel = context.watch(); return Scaffold( appBar: AppBar(title: const Text('批量解析'), elevation: 0), - body: viewmodel.parseBatchArchiveState.when>( - loading: () { - if (viewmodel.totalCount == 0 && viewmodel.completeCount == 0) { - return Center(child: CircularProgressIndicator()); + body: asyncState.when( + loading: () => Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 8), + Text('正���处理:${parseProgress.completeCount}/${parseProgress.totalCount}'), + if (parseProgress.currentFileName.isNotEmpty) + Text('当前文件:${parseProgress.currentFileName}'), + if (parseProgress.currentFileProgressText.isNotEmpty) + Text(parseProgress.currentFileProgressText), + ], + ), + ), + error: (error, stack) => Center( + child: CustomErrorWidget( + errorMessage: error.toString(), + stackTrace: stack, + ), + ), + data: (state) { + final data = state.parseBatchArchiveList; + if (data.isEmpty) { + return const Center(child: Text('暂无可解析内容')); } - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - CircularProgressIndicator(), - SizedBox(height: 8), - Text("正在处理:${viewmodel.completeCount}/${viewmodel.totalCount}"), - if (viewmodel.currentFileName.isNotEmpty) - Text("当前文件:${viewmodel.currentFileName}"), - if (viewmodel.currentFileProgressText.isNotEmpty) - Text(viewmodel.currentFileProgressText), - ], - ), - ); - }, - success: (data) { return ListView.builder( - padding: EdgeInsets.all(16), - itemCount: data.length, - itemBuilder: (context, index) { - final archive = data[index]; - return Row( - children: [ - LocalImageWidget(imagePath: archive.tempPaths.first), - Expanded( - child: ListTile( - title: Text(archive.name), - subtitle: Text("图片数: ${archive.tempPaths.length}"), - onTap: () { - _buildArchiveImageList(context, archive.tempPaths); - }, + padding: const EdgeInsets.all(16), + itemCount: data.length, + itemBuilder: (context, index) { + final archive = data[index]; + return Row( + children: [ + LocalImageWidget(imagePath: archive.tempPaths.first), + Expanded( + child: ListTile( + title: Text(archive.name), + subtitle: Text('图片数: ${archive.tempPaths.length}'), + onTap: () { + _buildArchiveImageList(context, archive.tempPaths); + }, + ), ), - ), - ], - ); - }, - ); + ], + ); + }, + ); }, ), - bottomNavigationBar: viewmodel.parseBatchArchiveState.isSuccess + bottomNavigationBar: + asyncState.value?.parseBatchArchiveList.isNotEmpty == true ? Padding( - padding: EdgeInsets.all(16), + padding: const EdgeInsets.all(16), child: FilledButton( - onPressed: viewmodel.saveBatchAsBookState.isLoading + onPressed: saveState.isLoading ? null - : () => viewmodel.saveBatchAsBook(context), - child: viewmodel.saveBatchAsBookState.isLoading - ? Text( - "正在保存:${viewmodel.saveAsBookCount}/${viewmodel.parseBatchArchiveList.length}", - ) - : Text("保存到书架"), + : () => onSave(asyncState.value!.parseBatchArchiveList), + child: saveState.isLoading + ? Text('正在保存:${saveProgress.current}/${saveProgress.total}') + : const Text('保存到书架'), ), ) : null, diff --git a/lib/feature/parse/ui/view/parse_batch_image_folder_view.dart b/lib/feature/parse/ui/view/parse_batch_image_folder_view.dart index 473fe8a..9779f8b 100644 --- a/lib/feature/parse/ui/view/parse_batch_image_folder_view.dart +++ b/lib/feature/parse/ui/view/parse_batch_image_folder_view.dart @@ -1,14 +1,14 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; -import 'package:provider/provider.dart'; +import 'package:go_router/go_router.dart'; +import 'package:tele_book/common/widget/error_widget.dart'; import 'package:tele_book/common/widget/local_image_widget.dart'; -import 'package:tele_book/core/util/state_util.dart'; -import 'package:tele_book/feature/parse/model/parse_batch_archive_vo.dart'; -import 'package:tele_book/feature/parse/ui/viewmodel/parse_batch_image_folder_viewmodel.dart'; +import 'package:tele_book/feature/parse/ui/provider/parse_batch_image_folder.dart'; -class ParseBatchImageFolderView extends StatelessWidget { +class ParseBatchImageFolderView extends ConsumerWidget { final String? parentDirPath; final List? imagePaths; @@ -19,54 +19,98 @@ class ParseBatchImageFolderView extends StatelessWidget { }); @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (context) => ParseBatchImageFolderViewmodel( - parentDirPath: parentDirPath, - imagePaths: imagePaths, - parseArchiveService: context.read(), - bookRepository: context.read(), - ), - child: const _ParseBatchImageFolderContentView(), + Widget build(BuildContext context, WidgetRef ref) { + final param = ParseBatchImageFolderParam( + parentDirPath: parentDirPath, + imagePaths: imagePaths, ); - } -} -class _ParseBatchImageFolderContentView extends StatelessWidget { - const _ParseBatchImageFolderContentView(); + final parseProvider = parseBatchImageFolderProvider(param); + final saveProvider = saveBatchAsBookProvider(param); + final parseAsync = ref.watch(parseProvider); + final parseNotifier = ref.read(parseProvider.notifier); + final saveState = ref.watch(saveProvider); + final saveNotifier = ref.read(saveProvider.notifier); + + ref.listen(saveBatchAsBookProvider(param).select((s) => s.submitState), ( + previous, + next, + ) { + if (next.hasError) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text("保存失败:${next.error}"))); + } else if (previous?.isLoading == true && + next.isLoading == false && + next.error == null) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text("保存成功!"))); + context.pop(); + } + }); - @override - Widget build(BuildContext context) { - final viewmodel = context.watch(); return Scaffold( appBar: AppBar(title: const Text('批量解析文件夹'), elevation: 0), - body: viewmodel.parseBatchFolderState.when>( + body: parseAsync.when( + error: (error, stack) => Center( + child: CustomErrorWidget( + errorMessage: error.toString(), + stackTrace: stack, + ), + ), loading: () { - if (viewmodel.totalCount == 0 && viewmodel.completeCount == 0) { - return const Center(child: CircularProgressIndicator()); + final current = parseAsync.value; + if (current != null && current.totalCount > 0) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 8), + Text("正在处理:${current.completeCount}/${current.totalCount}"), + if (current.currentFileName.isNotEmpty) + Text("当前文件:${current.currentFileName}"), + if (current.currentFileProgressText.isNotEmpty) + Text(current.currentFileProgressText), + ], + ), + ); } - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 8), - Text("正在处理:${viewmodel.completeCount}/${viewmodel.totalCount}"), - if (viewmodel.currentFileName.isNotEmpty) - Text("当前文件:${viewmodel.currentFileName}"), - if (viewmodel.currentFileProgressText.isNotEmpty) - Text(viewmodel.currentFileProgressText), - ], - ), - ); + return const Center(child: CircularProgressIndicator()); }, - success: (data) { + data: (state) { + if (state.isParsing) { + if (state.totalCount == 0 && state.completeCount == 0) { + return const Center(child: CircularProgressIndicator()); + } + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 8), + Text("正在处理:${state.completeCount}/${state.totalCount}"), + if (state.currentFileName.isNotEmpty) + Text("当前文件:${state.currentFileName}"), + if (state.currentFileProgressText.isNotEmpty) + Text(state.currentFileProgressText), + ], + ), + ); + } + + if (state.parseBatchFolderList.isEmpty) { + return const Center(child: Text('未解析到可用图片文件夹')); + } + return ListView.builder( padding: const EdgeInsets.all(16), - itemCount: data.length, + itemCount: state.parseBatchFolderList.length, itemBuilder: (context, index) { - final folder = data[index]; + final folder = state.parseBatchFolderList[index]; return Row( children: [ LocalImageWidget(imagePath: folder.tempPaths.first), @@ -85,21 +129,32 @@ class _ParseBatchImageFolderContentView extends StatelessWidget { ); }, ), - bottomNavigationBar: viewmodel.parseBatchFolderState.isSuccess + bottomNavigationBar: + parseAsync.value?.parseBatchFolderList.isNotEmpty == true ? Padding( padding: const EdgeInsets.all(16), child: FilledButton( - onPressed: viewmodel.saveBatchAsBookState.isLoading + onPressed: saveState.submitState.isLoading ? null - : () => viewmodel.saveBatchAsBook(context), - child: viewmodel.saveBatchAsBookState.isLoading + : () => saveNotifier.submit( + parseAsync.value!.parseBatchFolderList, + ), + child: saveState.submitState.isLoading ? Text( - "正在保存:${viewmodel.saveAsBookCount}/${viewmodel.parseBatchFolderList.length}", + "正在保存:${saveState.saveAsBookCount}/${saveState.totalCount}", ) : const Text("保存到书架"), ), ) : null, + floatingActionButton: parseAsync.value?.isParsing == false + ? FloatingActionButton( + onPressed: () { + parseNotifier.refresh(); + }, + child: const Icon(Icons.refresh), + ) + : null, ); } @@ -159,4 +214,3 @@ class _ParseBatchImageFolderContentView extends StatelessWidget { ); } } - diff --git a/lib/feature/parse/ui/view/parse_batch_pdf_view.dart b/lib/feature/parse/ui/view/parse_batch_pdf_view.dart index e9c8999..77602da 100644 --- a/lib/feature/parse/ui/view/parse_batch_pdf_view.dart +++ b/lib/feature/parse/ui/view/parse_batch_pdf_view.dart @@ -1,68 +1,58 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; -import 'package:provider/provider.dart'; import 'package:tele_book/common/widget/local_image_widget.dart'; -import 'package:tele_book/core/util/state_util.dart'; import 'package:tele_book/feature/parse/model/parse_batch_archive_vo.dart'; -import 'package:tele_book/feature/parse/ui/viewmodel/parse_batch_pdf_viewmodel.dart'; -import 'dart:io'; +import 'package:tele_book/feature/parse/ui/provider/parse_batch_pdf_provider.dart'; -class ParseBatchPdfView extends StatelessWidget { +class ParseBatchPdfView extends ConsumerWidget { final String? pdfDirPath; final List? pdfPaths; - const ParseBatchPdfView({ - super.key, - this.pdfDirPath, - this.pdfPaths, - }); + const ParseBatchPdfView({super.key, this.pdfDirPath, this.pdfPaths}); @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (context) => ParseBatchPdfViewmodel( - pdfDirPath: pdfDirPath, - pdfPaths: pdfPaths, - parsePdfService: context.read(), - bookRepository: context.read(), - ), - child: const _ParseBatchPdfContent(), + Widget build(BuildContext context, WidgetRef ref) { + final param = ParseBatchPdfParam( + pdfDirPath: pdfDirPath ?? '', + pdfPaths: pdfPaths ?? [], ); - } -} -class _ParseBatchPdfContent extends StatelessWidget { - const _ParseBatchPdfContent(); + final asyncState = ref.watch(parseBatchPdfProvider(param)); + final parseProgress = ref.watch(parseBatchProgressProvider); - @override - Widget build(BuildContext context) { - final vm = context.watch(); + final saveBatchBookState = ref.watch(parseBatchPdfSaveBookProvider); + final saveBatchBookNotifier = ref.watch( + parseBatchPdfSaveBookProvider.notifier, + ); + final saveBookProgress = ref.watch(parseBatchPdfSaveBookProgressProvider); return Scaffold( appBar: AppBar(title: const Text('批量解析 PDF'), elevation: 0), - body: vm.parseBatchState.when>( - loading: () { - if (vm.totalCount == 0 && vm.completeCount == 0) { - return const Center(child: CircularProgressIndicator()); - } - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 8), - Text('正在处理:${vm.completeCount} / ${vm.totalCount}'), - if (vm.currentFileName.isNotEmpty) Text('当前文件:${vm.currentFileName}'), - if (vm.currentFileProgressText.isNotEmpty) Text(vm.currentFileProgressText), - ], - ), - ); - }, - success: (data) => ListView.builder( + body: asyncState.when( + loading: () => Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + SizedBox(height: 8), + Text( + '正在处理:${parseProgress.completeCount} / ${parseProgress.totalCount}', + ), + if (parseProgress.currentFileName.isNotEmpty) + Text('当前文件:${parseProgress.currentFileName}'), + Text("当前文件进度:${parseProgress.currentFileProgress}%"), + ], + ), + ), + error: (error, stack) => Center(child: Text(error.toString())), + data: (state) => ListView.builder( padding: const EdgeInsets.all(16), - itemCount: data.length, + itemCount: state.parseBatchList.length, itemBuilder: (context, index) { - final item = data[index]; + final item = state.parseBatchList[index]; return Row( children: [ LocalImageWidget(imagePath: item.tempPaths.first), @@ -78,16 +68,18 @@ class _ParseBatchPdfContent extends StatelessWidget { }, ), ), - bottomNavigationBar: vm.parseBatchState.isSuccess + bottomNavigationBar: asyncState.hasValue ? Padding( padding: const EdgeInsets.all(16), child: FilledButton( - onPressed: vm.saveBatchAsBookState.isLoading + onPressed: saveBatchBookState.isLoading == true ? null - : () => vm.saveBatchAsBook(context), - child: vm.saveBatchAsBookState.isLoading + : () => saveBatchBookNotifier.saveBatchAsBook( + asyncState.value!.parseBatchList, + ), + child: saveBatchBookState.isLoading == true ? Text( - '正在保存:${vm.saveAsBookCount} / ${vm.parseBatchList.length}', + '正在保存:${saveBookProgress.current} / ${saveBookProgress.total}', ) : const Text('保存到书架'), ), @@ -145,4 +137,3 @@ class _ParseBatchPdfContent extends StatelessWidget { ); } } - diff --git a/lib/feature/parse/ui/view/parse_form_view.dart b/lib/feature/parse/ui/view/parse_form_view.dart index 28f3921..aba3cd2 100644 --- a/lib/feature/parse/ui/view/parse_form_view.dart +++ b/lib/feature/parse/ui/view/parse_form_view.dart @@ -1,32 +1,16 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:tele_book/feature/parse/ui/viewmodel/parse_form_viewmodel.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:tele_book/feature/parse/ui/provider/parse_form_provider.dart'; -class ParseFormView extends StatelessWidget { +class ParseFormView extends ConsumerWidget { const ParseFormView({super.key}); @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (_) => ParseFormViewmodel(), - child: const _ParseFormContent(), - ); - } -} - -class _ParseFormContent extends StatefulWidget { - const _ParseFormContent(); - - @override - State<_ParseFormContent> createState() => _ParseFormContentState(); -} - -class _ParseFormContentState extends State<_ParseFormContent> { - @override - Widget build(BuildContext context) { - final vm = context.watch(); + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(parseFormProvider); + final notifier = ref.read(parseFormProvider.notifier); return Scaffold( appBar: AppBar(title: const Text("解析表单")), @@ -39,7 +23,7 @@ class _ParseFormContentState extends State<_ParseFormContent> { builder: (context, constraints) { return DropdownMenu( width: constraints.maxWidth, - initialSelection: vm.type, + initialSelection: state.type, decorationBuilder: (context, state) { return const InputDecoration( labelText: "选择解析来源", @@ -88,17 +72,17 @@ class _ParseFormContentState extends State<_ParseFormContent> { ), ], onSelected: (value) { - vm.setType(value); + notifier.setType(value); }, ); }, ), const SizedBox(height: 16), - _buildSubForm(context, vm.type), + _buildSubForm(context, notifier, state.type), const Spacer(), FilledButton( onPressed: () { - vm.onParse(context); + notifier.onParse(context); }, child: const Text("解析"), ), @@ -109,34 +93,38 @@ class _ParseFormContentState extends State<_ParseFormContent> { ); } - Widget _buildSubForm(BuildContext context, ParseFormType type) { - final vm = context.read(); + Widget _buildSubForm( + BuildContext context, + ParseForm notifier, + ParseFormType type, + ) { switch (type) { case ParseFormType.web: - return _buildWebForm(context, vm); + return _buildWebForm(context, notifier); case ParseFormType.archive: - return _buildArchiveForm(context, vm); + return _buildArchiveForm(context, notifier); case ParseFormType.batchArchive: - return _buildBatchArchiveForm(context, vm); + return _buildBatchArchiveForm(context, notifier); case ParseFormType.imageFolder: - return _buildImageFolderForm(context, vm); + return _buildImageFolderForm(context, notifier); case ParseFormType.batchImageFolder: - return _buildBatchImageFolderForm(context, vm); + return _buildBatchImageFolderForm(context, notifier); case ParseFormType.pdf: - return _buildPdfForm(context, vm); + return _buildPdfForm(context, notifier); case ParseFormType.batchPdf: - return _buildBatchPdfForm(context, vm); + return _buildBatchPdfForm(context, notifier); } } - Widget _buildWebForm(BuildContext context, ParseFormViewmodel vm) { + Widget _buildWebForm(BuildContext context, ParseForm notifier) { return TextField( - controller: vm.urlController, + controller: notifier.urlController, + onChanged: notifier.onUrlChanged, decoration: InputDecoration( labelText: "输入文本", prefixIcon: const Icon(Icons.web), suffixIcon: IconButton( - onPressed: () => vm.getClipboardUrl(), + onPressed: notifier.getClipboardUrl, icon: const Icon(Icons.paste), ), border: const OutlineInputBorder(), @@ -144,14 +132,14 @@ class _ParseFormContentState extends State<_ParseFormContent> { ); } - Widget _buildArchiveForm(BuildContext context, ParseFormViewmodel vm) { + Widget _buildArchiveForm(BuildContext context, ParseForm notifier) { return TextField( - controller: vm.archivePathController, + controller: notifier.archivePathController, decoration: InputDecoration( labelText: "请选择压缩包文件", prefixIcon: const Icon(Icons.archive), suffixIcon: IconButton( - onPressed: () => vm.pickerArchive(context), + onPressed: notifier.pickerArchive, icon: const Icon(Icons.folder_open), ), border: const OutlineInputBorder(), @@ -159,14 +147,14 @@ class _ParseFormContentState extends State<_ParseFormContent> { ); } - Widget _buildBatchArchiveForm(BuildContext context, ParseFormViewmodel vm) { + Widget _buildBatchArchiveForm(BuildContext context, ParseForm notifier) { return TextField( - controller: vm.batchArchivePathController, + controller: notifier.batchArchivePathController, decoration: InputDecoration( labelText: Platform.isIOS ? "请选择一个或多个 ZIP 文件" : "请选择压缩包文件夹", prefixIcon: const Icon(Icons.folder), suffixIcon: IconButton( - onPressed: () => vm.pickerBatchArchive(context), + onPressed: notifier.pickerBatchArchive, icon: const Icon(Icons.folder_open), ), border: const OutlineInputBorder(), @@ -174,14 +162,14 @@ class _ParseFormContentState extends State<_ParseFormContent> { ); } - Widget _buildImageFolderForm(BuildContext context, ParseFormViewmodel vm) { + Widget _buildImageFolderForm(BuildContext context, ParseForm notifier) { return TextField( - controller: vm.imageFolderPathController, + controller: notifier.imageFolderPathController, decoration: InputDecoration( labelText: Platform.isIOS ? "请选择一个或多个图片文件" : "请选择图片文件夹", prefixIcon: const Icon(Icons.photo_library), suffixIcon: IconButton( - onPressed: () => vm.pickerImageFolder(context), + onPressed: notifier.pickerImageFolder, icon: const Icon(Icons.folder_open), ), border: const OutlineInputBorder(), @@ -189,16 +177,16 @@ class _ParseFormContentState extends State<_ParseFormContent> { ); } - Widget _buildBatchImageFolderForm(BuildContext context, ParseFormViewmodel vm) { + Widget _buildBatchImageFolderForm(BuildContext context, ParseForm notifier) { return TextField( - controller: vm.batchImageFolderPathController, + controller: notifier.batchImageFolderPathController, decoration: InputDecoration( labelText: Platform.isIOS ? "请选择多个图片文件(按文件夹分组)" : "请选择批量图片文件夹父目录", prefixIcon: const Icon(Icons.folder_copy), suffixIcon: IconButton( - onPressed: () => vm.pickerBatchImageFolder(context), + onPressed: notifier.pickerBatchImageFolder, icon: const Icon(Icons.folder_open), ), border: const OutlineInputBorder(), @@ -206,14 +194,14 @@ class _ParseFormContentState extends State<_ParseFormContent> { ); } - Widget _buildPdfForm(BuildContext context, ParseFormViewmodel vm) { + Widget _buildPdfForm(BuildContext context, ParseForm notifier) { return TextField( - controller: vm.pdfPathController, + controller: notifier.pdfPathController, decoration: InputDecoration( labelText: "请选择 PDF 文件", prefixIcon: const Icon(Icons.picture_as_pdf), suffixIcon: IconButton( - onPressed: () => vm.pickerPdf(context), + onPressed: notifier.pickerPdf, icon: const Icon(Icons.folder_open), ), border: const OutlineInputBorder(), @@ -221,14 +209,14 @@ class _ParseFormContentState extends State<_ParseFormContent> { ); } - Widget _buildBatchPdfForm(BuildContext context, ParseFormViewmodel vm) { + Widget _buildBatchPdfForm(BuildContext context, ParseForm notifier) { return TextField( - controller: vm.batchPdfPathController, + controller: notifier.batchPdfPathController, decoration: InputDecoration( labelText: Platform.isIOS ? "请选择一个或多个 PDF 文件" : "请选择包含 PDF 的文件夹", prefixIcon: const Icon(Icons.folder_special), suffixIcon: IconButton( - onPressed: () => vm.pickerBatchPdf(context), + onPressed: notifier.pickerBatchPdf, icon: const Icon(Icons.folder_open), ), border: const OutlineInputBorder(), diff --git a/lib/feature/parse/ui/view/parse_image_folder_view.dart b/lib/feature/parse/ui/view/parse_image_folder_view.dart index 6d33575..c6af774 100644 --- a/lib/feature/parse/ui/view/parse_image_folder_view.dart +++ b/lib/feature/parse/ui/view/parse_image_folder_view.dart @@ -1,11 +1,13 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:tele_book/core/util/state_util.dart'; -import 'package:tele_book/feature/parse/ui/viewmodel/parse_image_folder_viewmodel.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:tele_book/common/widget/error_widget.dart'; +import 'package:tele_book/core/route/app_route.dart'; +import 'package:tele_book/feature/parse/ui/provider/parse_image_folder_provider.dart'; -class ParseImageFolderView extends StatelessWidget { +class ParseImageFolderView extends ConsumerWidget { final String? folderPath; final List? imagePaths; @@ -16,54 +18,96 @@ class ParseImageFolderView extends StatelessWidget { }); @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (context) => ParseImageFolderViewmodel( - folderPath: folderPath, - imagePathsInput: imagePaths, - parseArchiveService: context.read(), - bookRepository: context.read(), - ), - child: const _ParseImageFolderContent(), + Widget build(BuildContext context, WidgetRef ref) { + final param = ParseImageFolderParam( + folderPath: folderPath, + imagePathsInput: imagePaths, + ); + final provider = parseImageFolderProvider(param); + final asyncState = ref.watch(provider); + + final saveState = ref.watch(parseImageFolderSaveBookProvider(param)); + final saveNotifier = ref.watch(parseImageFolderSaveBookProvider(param).notifier); + final saveProgress = ref.watch(parseImageFolderSaveBookProgressProvider(param)); + + ref.listen(parseImageFolderSaveBookProvider(param), (previous, next) { + if (next.hasError) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('保存失败:${next.error}'))); + } else if (previous?.isLoading == true && next.hasValue) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('保存成功'))); + context.go(AppRoute.main); + } + }); + + return _ParseImageFolderContent( + asyncState: asyncState, + saveState: saveState, + saveProgress: saveProgress, + onSave: (data) { + saveNotifier.submit( + ParseImageFolderSaveBookParam( + folderName: data.folderName, + imagePaths: data.imagePaths, + ), + ); + }, ); } } class _ParseImageFolderContent extends StatelessWidget { - const _ParseImageFolderContent(); + final AsyncValue asyncState; + final AsyncValue saveState; + final ParseImageFolderSaveBookProgress saveProgress; + final void Function(ParseImageFolderState data) onSave; + + const _ParseImageFolderContent({ + required this.asyncState, + required this.saveState, + required this.saveProgress, + required this.onSave, + }); @override Widget build(BuildContext context) { - final vm = context.watch(); return Scaffold( - appBar: AppBar(title: const Text("解析文件夹")), - body: vm.parseState.when>( - success: (images) => GridView.builder( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - mainAxisSpacing: 4, - crossAxisSpacing: 4, + appBar: AppBar(title: const Text('解析文件夹')), + body: asyncState.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => Center( + child: CustomErrorWidget( + errorMessage: error.toString(), + stackTrace: stack, ), - itemBuilder: (context, index) { - final image = images[index]; - return Image.file(File(image), fit: BoxFit.cover); - }, - itemCount: images.length, ), + data: (state) => state.imagePaths.isEmpty + ? const Center(child: Text('未解析到图片')) + : GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + mainAxisSpacing: 4, + crossAxisSpacing: 4, + ), + itemBuilder: (context, index) { + final image = state.imagePaths[index]; + return Image.file(File(image), fit: BoxFit.cover); + }, + itemCount: state.imagePaths.length, + ), ), - bottomNavigationBar: vm.parseState.isSuccess + bottomNavigationBar: asyncState.value?.imagePaths.isNotEmpty == true ? Padding( padding: const EdgeInsets.all(16), child: FilledButton( - onPressed: vm.saveToBookState.isLoading + onPressed: saveState.isLoading ? null - : () => vm.saveToBook(context), - child: vm.saveToBookState.isLoading - ? const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(strokeWidth: 2), - ) + : () => onSave(asyncState.value!), + child: saveState.isLoading + ? Text('保存中 ${saveProgress.current}/${saveProgress.total}') : const Text("保存到书架"), ), ) diff --git a/lib/feature/parse/ui/view/parse_pdf_view.dart b/lib/feature/parse/ui/view/parse_pdf_view.dart index b5a068e..dc8cdc2 100644 --- a/lib/feature/parse/ui/view/parse_pdf_view.dart +++ b/lib/feature/parse/ui/view/parse_pdf_view.dart @@ -1,72 +1,62 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:tele_book/core/util/state_util.dart'; -import 'package:tele_book/feature/parse/ui/viewmodel/parse_pdf_viewmodel.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:tele_book/feature/parse/ui/provider/parse_pdf_provider.dart'; -class ParsePdfView extends StatelessWidget { +class ParsePdfView extends ConsumerWidget { final String pdfPath; const ParsePdfView({super.key, required this.pdfPath}); @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (context) => ParsePdfViewmodel( - pdfPath: pdfPath, - parsePdfService: context.read(), - bookRepository: context.read(), - ), - child: const _ParsePdfContent(), - ); - } -} + Widget build(BuildContext context, WidgetRef ref) { + final asyncState = ref.watch(parsePdfProvider(pdfPath)); + final progress = ref.watch(parsePdfProgressProvider(pdfPath)); -class _ParsePdfContent extends StatelessWidget { - const _ParsePdfContent(); - - @override - Widget build(BuildContext context) { - final vm = context.watch(); + final saveBookProgress = ref.watch(parsePdfSaveBookProgressProvider); + final saveBookState = ref.watch(parsePdfSaveBookProvider); + final saveBookNotifier = ref.watch(parsePdfSaveBookProvider.notifier); return Scaffold( - appBar: AppBar(title: Text('解析 PDF:${vm.pdfName}')), - body: vm.parseState.when>( + appBar: AppBar(title: Text('解析 PDF:${asyncState.value?.pdfName ?? ''}')), + body: asyncState.when( loading: () => Center( child: Column( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, children: [ - const CircularProgressIndicator(), - const SizedBox(height: 12), - if (vm.totalPages > 0) - Text('正在渲染:${vm.currentPage} / ${vm.totalPages} 页'), + CircularProgressIndicator(), + SizedBox(height: 16), + Text('正在解析 ${progress.$1}/${progress.$2}'), ], ), ), - success: (images) => GridView.builder( + error: (error, stack) => Center(child: Text(error.toString())), + data: (state) => GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisSpacing: 4, crossAxisSpacing: 4, ), - itemCount: images.length, + itemCount: state.tempPaths.length, itemBuilder: (context, index) => - Image.file(File(images[index]), fit: BoxFit.cover), + Image.file(File(state.tempPaths[index]), fit: BoxFit.cover), ), ), - bottomNavigationBar: vm.parseState.isSuccess + bottomNavigationBar: asyncState.hasValue == true ? Padding( padding: const EdgeInsets.all(16), child: FilledButton( - onPressed: vm.saveToBookState.isLoading + onPressed: saveBookState.isLoading ? null - : () => vm.saveToBook(context), - child: vm.saveToBookState.isLoading - ? const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(strokeWidth: 2), + : () => saveBookNotifier.onSave( + ref, + asyncState.value!.tempPaths, + asyncState.value!.pdfName, + ), + child: saveBookState.isLoading + ? Text( + '保存中... ${saveBookProgress.$1}/${saveBookProgress.$2}', ) : const Text('保存到书架'), ), @@ -75,4 +65,3 @@ class _ParsePdfContent extends StatelessWidget { ); } } - diff --git a/lib/feature/parse/ui/view/parse_web_view.dart b/lib/feature/parse/ui/view/parse_web_view.dart index a133b93..ab4340b 100644 --- a/lib/feature/parse/ui/view/parse_web_view.dart +++ b/lib/feature/parse/ui/view/parse_web_view.dart @@ -1,67 +1,49 @@ import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:provider/provider.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:tele_book/common/widget/network_image_widget.dart'; -import 'package:tele_book/feature/parse/ui/viewmodel/parse_web_viewmodel.dart'; +import 'package:tele_book/core/route/app_route.dart'; +import 'package:tele_book/feature/parse/ui/provider/parse_web_provider.dart'; -class ParseWebView extends StatelessWidget { +class ParseWebView extends ConsumerWidget { final String url; const ParseWebView({super.key, required this.url}); @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (context) => ParseWebViewmodel( - context.read(), - context.read(), - ), - child: _ParseWebContent(url: url), - ); - } -} - -class _ParseWebContent extends StatefulWidget { - final String url; + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(parseWebProvider(url)); + final notifier = ref.read(parseWebProvider(url).notifier); - const _ParseWebContent({super.key, required this.url}); - - @override - State<_ParseWebContent> createState() => __ParseWebContentState(); -} - -class __ParseWebContentState extends State<_ParseWebContent> { - @override - Widget build(BuildContext context) { - final vm = context.watch(); return Scaffold( - appBar: AppBar(title: const Text("解析网页")), + appBar: AppBar(title: Text(state.title)), floatingActionButton: FloatingActionButton( child: Badge( - label: Text(vm.urls.length.toString()), - child: Icon(Icons.photo), + label: Text((state.urls.length).toString()), + child: const Icon(Icons.photo), ), onPressed: () { showModalBottomSheet( context: context, - builder: (context) => _buildBottomSheet(context, vm), + builder: (context) => _buildBottomSheet(context, state, notifier), ); }, ), body: Column( children: [ - LinearProgressIndicator(value: vm.progress / 100), + LinearProgressIndicator(value: (state.progress) / 100), Expanded( child: InAppWebView( - initialUrlRequest: URLRequest(url: WebUri(widget.url)), + initialUrlRequest: URLRequest(url: WebUri(url)), onLoadStart: (controller, url) { - vm.onLoadStart(controller); + notifier.onLoadStart(controller); }, onTitleChanged: (controller, title) { - vm.onTitleChanged(controller, title); + notifier.onTitleChanged(controller, title); }, onProgressChanged: (controller, progress) { - vm.onProgressChange(controller, progress); + notifier.onProgressChange(controller, progress); }, ), ), @@ -70,7 +52,11 @@ class __ParseWebContentState extends State<_ParseWebContent> { ); } - Widget _buildBottomSheet(BuildContext context, ParseWebViewmodel vm) { + Widget _buildBottomSheet( + BuildContext context, + ParseWebState state, + ParseWeb notifier, + ) { return Container( padding: EdgeInsets.all(16), height: MediaQuery.of(context).size.height * 0.7, @@ -80,9 +66,9 @@ class __ParseWebContentState extends State<_ParseWebContent> { SizedBox(height: 16), Expanded( child: ListView.builder( - itemCount: vm.urls.length, + itemCount: state.urls.length, itemBuilder: (context, index) { - final url = vm.urls[index]; + final url = state.urls[index]; return Row( children: [ NetworkImageWidget(imageUrl: url), @@ -112,7 +98,8 @@ class __ParseWebContentState extends State<_ParseWebContent> { width: double.infinity, child: FilledButton( onPressed: () { - vm.startDownload(context); + notifier.startDownload(); + context.push(AppRoute.download); }, child: Text("下载"), ), diff --git a/lib/feature/parse/ui/viewmodel/parse_archive_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_archive_viewmodel.dart deleted file mode 100644 index fd9a3cd..0000000 --- a/lib/feature/parse/ui/viewmodel/parse_archive_viewmodel.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:tele_book/core/route/app_route.dart'; -import 'package:tele_book/core/util/state_util.dart'; -import 'package:tele_book/feature/book/model/dto/save_as_book_dto.dart'; -import 'package:tele_book/feature/book/repository/book_repository.dart'; -import 'package:tele_book/feature/parse/service/parse_archive_service.dart'; - -class ParseArchiveViewmodel extends ChangeNotifier { - final String archivePath; - final ParseArchiveService parseArchiveService; - final BookRepository bookRepository; - EventState parseState = IdleEventState(); - EventState saveToBookState = IdleEventState(); - List tempPaths = []; - String archiveName = ""; - - ParseArchiveViewmodel({ - required this.archivePath, - required this.parseArchiveService, - required this.bookRepository, - }) { - archiveName = archivePath.split(RegExp(r'[\\/]')).last; - paseArchive(); - } - - Future paseArchive() async { - parseState = LoadingEventState(); - notifyListeners(); - - final result = await parseArchiveService.parseArchive(archivePath); - - result.fold( - onSuccess: (data) { - tempPaths = data; - parseState = SuccessEventState(data); - notifyListeners(); - }, - onError: (error) { - parseState = ErrorEventState(error.message); - notifyListeners(); - }, - ); - } - - Future saveToBook(BuildContext context) async { - if (tempPaths.isEmpty || saveToBookState.isLoading) return; - saveToBookState = LoadingEventState(); - notifyListeners(); - try { - final result = await bookRepository.saveAsBook( - SaveAsBookDto(title: archiveName, paths: tempPaths), - ); - result.fold( - onSuccess: (data) { - saveToBookState = SuccessEventState(data); - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text("保存成功"))); - notifyListeners(); - context.go(AppRoute.main); - }, - onError: (error) { - saveToBookState = ErrorEventState(error.message); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text("保存失败: ${error.message}"))); - notifyListeners(); - }, - ); - } catch (e) { - saveToBookState = ErrorEventState(e.toString()); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text("保存失败: $e"))); - notifyListeners(); - } - } -} diff --git a/lib/feature/parse/ui/viewmodel/parse_batch_archive_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_batch_archive_viewmodel.dart deleted file mode 100644 index 2c9da58..0000000 --- a/lib/feature/parse/ui/viewmodel/parse_batch_archive_viewmodel.dart +++ /dev/null @@ -1,163 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/cupertino.dart'; -import 'package:go_router/go_router.dart'; -import 'package:permission_handler/permission_handler.dart'; -import 'package:tele_book/core/route/app_route.dart'; -import 'package:tele_book/core/util/state_util.dart'; -import 'package:tele_book/feature/book/model/dto/save_as_book_dto.dart'; -import 'package:tele_book/feature/book/repository/book_repository.dart'; -import 'package:tele_book/feature/parse/model/parse_batch_archive_vo.dart'; -import 'package:tele_book/feature/parse/service/parse_archive_service.dart'; - -class ParseBatchArchiveViewmodel extends ChangeNotifier { - final String? archiveDirPath; - final List? archivePaths; - final ParseArchiveService parseArchiveService; - final BookRepository bookRepository; - List parseBatchArchiveList = []; - int completeCount = 0; - int totalCount = 0; - String currentFileName = ''; - int currentFileProgress = 0; - int currentFileTotal = 0; - int saveAsBookCount = 0; - EventState parseBatchArchiveState = IdleEventState(); - EventState saveBatchAsBookState = IdleEventState(); - - String get currentFileProgressText { - if (currentFileName.isEmpty) return ''; - if (currentFileTotal <= 0) return '当前文件进度:准备中'; - return '当前文件进度:$currentFileProgress / $currentFileTotal'; - } - - ParseBatchArchiveViewmodel({ - this.archiveDirPath, - this.archivePaths, - required this.parseArchiveService, - required this.bookRepository, - }) { - parseBatchArchive(); - } - - /// 请求外部存储权限,返回是否已获得 - Future _requestStoragePermission() async { - if (!Platform.isAndroid) return true; - - // Android 11+(API 30+)需要 MANAGE_EXTERNAL_STORAGE 才能读取外部目录 - if (await Permission.manageExternalStorage.isGranted) return true; - final status = await Permission.manageExternalStorage.request(); - if (status.isGranted) return true; - - // 降级:尝试普通 storage 权限(Android 10 及以下) - if (await Permission.storage.isGranted) return true; - final storageStatus = await Permission.storage.request(); - return storageStatus.isGranted; - } - - Future parseBatchArchive() async { - parseBatchArchiveState = LoadingEventState(); - notifyListeners(); - - // 先请求权限,未授权则直接返回错误状态 - final hasPermission = await _requestStoragePermission(); - if (!hasPermission) { - parseBatchArchiveState = ErrorEventState( - "需要「所有文件访问权限」才能读取外部目录,请在系统设置中授权后重试。", - ); - notifyListeners(); - return; - } - - final result = archivePaths != null && archivePaths!.isNotEmpty - ? await parseArchiveService.parseBatchArchivesFromPaths( - archivePaths!, - (total) { - totalCount = total; - notifyListeners(); - }, - (count) { - completeCount = count; - notifyListeners(); - }, - onCurrentItemChanged: (fileName) { - currentFileName = fileName; - currentFileProgress = 0; - currentFileTotal = 0; - notifyListeners(); - }, - onCurrentItemProgress: (current, total) { - currentFileProgress = current; - currentFileTotal = total; - notifyListeners(); - }, - ) - : await parseArchiveService.parseBatchArchives( - archiveDirPath ?? '', - (total) { - totalCount = total; - notifyListeners(); - }, - (count) { - completeCount = count; - notifyListeners(); - }, - onCurrentItemChanged: (fileName) { - currentFileName = fileName; - currentFileProgress = 0; - currentFileTotal = 0; - notifyListeners(); - }, - onCurrentItemProgress: (current, total) { - currentFileProgress = current; - currentFileTotal = total; - notifyListeners(); - }, - ); - - result.fold( - onSuccess: (data) { - if (data.isEmpty) { - parseBatchArchiveState = EmptyEventState(); - notifyListeners(); - return; - } - - parseBatchArchiveList = data; - parseBatchArchiveState = SuccessEventState(data); - notifyListeners(); - }, - onError: (error) { - parseBatchArchiveState = ErrorEventState(error.message); - notifyListeners(); - }, - ); - } - - Future saveBatchAsBook(BuildContext context) async { - saveBatchAsBookState = LoadingEventState(); - notifyListeners(); - - final dos = parseBatchArchiveList - .map((e) => SaveAsBookDto(title: e.name, paths: e.tempPaths)) - .toList(); - - final result = await bookRepository.saveBatchAsBooks(dos, (count) { - saveAsBookCount = count; - }); - - result.fold( - onSuccess: (data) { - saveBatchAsBookState = SuccessEventState(data); - notifyListeners(); - saveAsBookCount = 0; - context.go(AppRoute.main); - }, - onError: (error) { - saveBatchAsBookState = ErrorEventState(error.message); - notifyListeners(); - saveAsBookCount = 0; - }, - ); - } -} diff --git a/lib/feature/parse/ui/viewmodel/parse_batch_image_folder_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_batch_image_folder_viewmodel.dart deleted file mode 100644 index af75055..0000000 --- a/lib/feature/parse/ui/viewmodel/parse_batch_image_folder_viewmodel.dart +++ /dev/null @@ -1,160 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/cupertino.dart'; -import 'package:go_router/go_router.dart'; -import 'package:permission_handler/permission_handler.dart'; -import 'package:tele_book/core/route/app_route.dart'; -import 'package:tele_book/core/util/state_util.dart'; -import 'package:tele_book/feature/book/model/dto/save_as_book_dto.dart'; -import 'package:tele_book/feature/book/repository/book_repository.dart'; -import 'package:tele_book/feature/parse/model/parse_batch_archive_vo.dart'; -import 'package:tele_book/feature/parse/service/parse_archive_service.dart'; - -class ParseBatchImageFolderViewmodel extends ChangeNotifier { - final String? parentDirPath; - final List? imagePaths; - final ParseArchiveService parseArchiveService; - final BookRepository bookRepository; - - List parseBatchFolderList = []; - int completeCount = 0; - int totalCount = 0; - String currentFileName = ''; - int currentFileProgress = 0; - int currentFileTotal = 0; - int saveAsBookCount = 0; - EventState parseBatchFolderState = IdleEventState(); - EventState saveBatchAsBookState = IdleEventState(); - - String get currentFileProgressText { - if (currentFileName.isEmpty) return ''; - if (currentFileTotal <= 0) return '当前文件进度:准备中'; - return '当前文件进度:$currentFileProgress / $currentFileTotal'; - } - - ParseBatchImageFolderViewmodel({ - this.parentDirPath, - this.imagePaths, - required this.parseArchiveService, - required this.bookRepository, - }) { - parseBatchFolders(); - } - - Future _requestStoragePermission() async { - if (!Platform.isAndroid) return true; - if (await Permission.manageExternalStorage.isGranted) return true; - final status = await Permission.manageExternalStorage.request(); - if (status.isGranted) return true; - - if (await Permission.storage.isGranted) return true; - final storageStatus = await Permission.storage.request(); - return storageStatus.isGranted; - } - - Future parseBatchFolders() async { - parseBatchFolderState = LoadingEventState(); - notifyListeners(); - - final hasPermission = await _requestStoragePermission(); - if (!hasPermission) { - parseBatchFolderState = ErrorEventState( - "需要「所有文件访问权限」才能读取外部目录,请在系统设置中授权后重试。", - ); - notifyListeners(); - return; - } - - final result = imagePaths != null && imagePaths!.isNotEmpty - ? await parseArchiveService.parseBatchImageFoldersFromPaths( - imagePaths!, - (total) { - totalCount = total; - notifyListeners(); - }, - (count) { - completeCount = count; - notifyListeners(); - }, - onCurrentItemChanged: (fileName) { - currentFileName = fileName; - currentFileProgress = 0; - currentFileTotal = 0; - notifyListeners(); - }, - onCurrentItemProgress: (current, total) { - currentFileProgress = current; - currentFileTotal = total; - notifyListeners(); - }, - ) - : await parseArchiveService.parseBatchImageFolders( - parentDirPath ?? '', - (total) { - totalCount = total; - notifyListeners(); - }, - (count) { - completeCount = count; - notifyListeners(); - }, - onCurrentItemChanged: (fileName) { - currentFileName = fileName; - currentFileProgress = 0; - currentFileTotal = 0; - notifyListeners(); - }, - onCurrentItemProgress: (current, total) { - currentFileProgress = current; - currentFileTotal = total; - notifyListeners(); - }, - ); - - result.fold( - onSuccess: (data) { - if (data.isEmpty) { - parseBatchFolderState = EmptyEventState(); - notifyListeners(); - return; - } - parseBatchFolderList = data; - parseBatchFolderState = SuccessEventState(data); - notifyListeners(); - }, - onError: (error) { - parseBatchFolderState = ErrorEventState(error.message); - notifyListeners(); - }, - ); - } - - Future saveBatchAsBook(BuildContext context) async { - saveBatchAsBookState = LoadingEventState(); - notifyListeners(); - - final dos = parseBatchFolderList - .map((e) => SaveAsBookDto(title: e.name, paths: e.tempPaths)) - .toList(); - - final result = await bookRepository.saveBatchAsBooks(dos, (count) { - saveAsBookCount = count; - notifyListeners(); - }); - - result.fold( - onSuccess: (_) { - saveBatchAsBookState = SuccessEventState(null); - saveAsBookCount = 0; - notifyListeners(); - context.go(AppRoute.main); - }, - onError: (error) { - saveBatchAsBookState = ErrorEventState(error.message); - saveAsBookCount = 0; - notifyListeners(); - }, - ); - } -} - diff --git a/lib/feature/parse/ui/viewmodel/parse_batch_pdf_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_batch_pdf_viewmodel.dart deleted file mode 100644 index 5c8b98f..0000000 --- a/lib/feature/parse/ui/viewmodel/parse_batch_pdf_viewmodel.dart +++ /dev/null @@ -1,159 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:permission_handler/permission_handler.dart'; -import 'package:tele_book/core/route/app_route.dart'; -import 'package:tele_book/core/util/state_util.dart'; -import 'package:tele_book/feature/book/model/dto/save_as_book_dto.dart'; -import 'package:tele_book/feature/book/repository/book_repository.dart'; -import 'package:tele_book/feature/parse/model/parse_batch_archive_vo.dart'; -import 'package:tele_book/feature/parse/service/parse_pdf_service.dart'; - -class ParseBatchPdfViewmodel extends ChangeNotifier { - final String? pdfDirPath; - final List? pdfPaths; - final ParsePdfService parsePdfService; - final BookRepository bookRepository; - - List parseBatchList = []; - int completeCount = 0; - int totalCount = 0; - String currentFileName = ''; - int currentFileProgress = 0; - int currentFileTotal = 0; - int saveAsBookCount = 0; - - EventState parseBatchState = const IdleEventState(); - EventState saveBatchAsBookState = const IdleEventState(); - - String get currentFileProgressText { - if (currentFileName.isEmpty) return ''; - if (currentFileTotal <= 0) return '当前文件进度:准备中'; - return '当前文件进度:$currentFileProgress / $currentFileTotal'; - } - - ParseBatchPdfViewmodel({ - this.pdfDirPath, - this.pdfPaths, - required this.parsePdfService, - required this.bookRepository, - }) { - _parseBatch(); - } - - Future _requestStoragePermission() async { - if (!Platform.isAndroid) return true; - if (await Permission.manageExternalStorage.isGranted) return true; - final status = await Permission.manageExternalStorage.request(); - if (status.isGranted) return true; - if (await Permission.storage.isGranted) return true; - final storageStatus = await Permission.storage.request(); - return storageStatus.isGranted; - } - - Future _parseBatch() async { - parseBatchState = const LoadingEventState(); - notifyListeners(); - - final hasPermission = await _requestStoragePermission(); - if (!hasPermission) { - parseBatchState = const ErrorEventState( - '需要「所有文件访问权限」才能读取外部目录,请在系统设置中授权后重试。', - ); - notifyListeners(); - return; - } - - final result = pdfPaths != null && pdfPaths!.isNotEmpty - ? await parsePdfService.parseBatchPdfsFromPaths( - pdfPaths!, - (total) { - totalCount = total; - notifyListeners(); - }, - (count) { - completeCount = count; - notifyListeners(); - }, - onCurrentFileChanged: (fileName) { - currentFileName = fileName; - currentFileProgress = 0; - currentFileTotal = 0; - notifyListeners(); - }, - onCurrentFileProgress: (current, total) { - currentFileProgress = current; - currentFileTotal = total; - notifyListeners(); - }, - ) - : await parsePdfService.parseBatchPdfs( - pdfDirPath ?? '', - (total) { - totalCount = total; - notifyListeners(); - }, - (count) { - completeCount = count; - notifyListeners(); - }, - onCurrentFileChanged: (fileName) { - currentFileName = fileName; - currentFileProgress = 0; - currentFileTotal = 0; - notifyListeners(); - }, - onCurrentFileProgress: (current, total) { - currentFileProgress = current; - currentFileTotal = total; - notifyListeners(); - }, - ); - - result.fold( - onSuccess: (data) { - if (data.isEmpty) { - parseBatchState = const EmptyEventState(); - notifyListeners(); - return; - } - parseBatchList = data; - parseBatchState = SuccessEventState(data); - notifyListeners(); - }, - onError: (error) { - parseBatchState = ErrorEventState(error.message); - notifyListeners(); - }, - ); - } - - Future saveBatchAsBook(BuildContext context) async { - saveBatchAsBookState = const LoadingEventState(); - notifyListeners(); - - final dos = parseBatchList - .map((e) => SaveAsBookDto(title: e.name, paths: e.tempPaths)) - .toList(); - - final result = await bookRepository.saveBatchAsBooks(dos, (count) { - saveAsBookCount = count; - }); - - result.fold( - onSuccess: (_) { - saveBatchAsBookState = const SuccessEventState(null); - saveAsBookCount = 0; - notifyListeners(); - context.go(AppRoute.main); - }, - onError: (error) { - saveBatchAsBookState = ErrorEventState(error.message); - saveAsBookCount = 0; - notifyListeners(); - }, - ); - } -} - diff --git a/lib/feature/parse/ui/viewmodel/parse_form_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_form_viewmodel.dart deleted file mode 100644 index 8bf091a..0000000 --- a/lib/feature/parse/ui/viewmodel/parse_form_viewmodel.dart +++ /dev/null @@ -1,254 +0,0 @@ -import 'dart:io'; - -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:go_router/go_router.dart'; -import 'package:tele_book/core/route/app_route.dart'; - -class ParseFormViewmodel extends ChangeNotifier { - ParseFormType type = ParseFormType.web; - final TextEditingController urlController = TextEditingController(); - final TextEditingController archivePathController = TextEditingController(); - final TextEditingController batchArchivePathController = - TextEditingController(); - List batchArchivePaths = []; - final TextEditingController imageFolderPathController = - TextEditingController(); - List imagePaths = []; - final TextEditingController batchImageFolderPathController = - TextEditingController(); - List batchImagePaths = []; - final TextEditingController pdfPathController = TextEditingController(); - final TextEditingController batchPdfPathController = TextEditingController(); - List batchPdfPaths = []; - - void setType(ParseFormType? newType) { - if (newType != null) { - type = newType; - notifyListeners(); - } - } - - void onParse(BuildContext context) { - switch (type) { - case ParseFormType.web: - context.push(AppRoute.parseWeb, extra: urlController.text); - break; - case ParseFormType.archive: - context.push( - AppRoute.parseArchiveSingle, - extra: archivePathController.text, - ); - break; - case ParseFormType.batchArchive: - context.push( - AppRoute.parseArchiveBatch, - extra: batchArchivePaths.isNotEmpty - ? batchArchivePaths - : batchArchivePathController.text, - ); - break; - case ParseFormType.imageFolder: - context.push( - AppRoute.parseImageFolder, - extra: imagePaths.isNotEmpty - ? imagePaths - : imageFolderPathController.text, - ); - break; - case ParseFormType.batchImageFolder: - context.push( - AppRoute.parseBatchImageFolder, - extra: batchImagePaths.isNotEmpty - ? batchImagePaths - : batchImageFolderPathController.text, - ); - break; - case ParseFormType.pdf: - context.push(AppRoute.parsePdf, extra: pdfPathController.text); - break; - case ParseFormType.batchPdf: - context.push( - AppRoute.parseBatchPdf, - extra: batchPdfPaths.isNotEmpty - ? batchPdfPaths - : batchPdfPathController.text, - ); - break; - } - } - - Future getClipboardUrl() async { - final clipboardData = await Clipboard.getData('text/plain'); - final text = clipboardData?.text ?? ''; - if (Uri.tryParse(text)?.hasAbsolutePath == true) { - urlController.text = text; - notifyListeners(); - } - } - - Future pickerArchive(BuildContext context) async { - final result = await FilePicker.platform.pickFiles( - dialogTitle: "选择 TeleBook 导出的书籍归档文件", - type: FileType.custom, - allowedExtensions: ['zip'], - ); - if (result != null && result.files.single.path != null) { - final path = result.files.single.path!; - archivePathController.text = path; - notifyListeners(); - } - } - - Future pickerBatchArchive(BuildContext context) async { - if (Platform.isIOS) { - final result = await FilePicker.platform.pickFiles( - dialogTitle: "选择一个或多个 ZIP 压缩包", - type: FileType.custom, - allowedExtensions: ['zip'], - allowMultiple: true, - ); - if (result != null) { - final paths = result.paths.whereType().toList(); - batchArchivePaths = paths; - batchArchivePathController.text = paths.isEmpty - ? '' - : '已选择 ${paths.length} 个 ZIP 文件'; - notifyListeners(); - } - return; - } - - final result = await FilePicker.platform.getDirectoryPath( - dialogTitle: "选择 TeleBook 导出的书籍归档文件夹", - ); - if (result != null) { - batchArchivePaths = []; - batchArchivePathController.text = result; - notifyListeners(); - } - } - - Future pickerImageFolder(BuildContext context) async { - if (Platform.isIOS) { - final result = await FilePicker.platform.pickFiles( - dialogTitle: "选择图片文件", - type: FileType.custom, - allowedExtensions: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'], - allowMultiple: true, - ); - if (result != null) { - final paths = result.paths.whereType().toList(); - imagePaths = paths; - imageFolderPathController.text = paths.isEmpty - ? '' - : '已选择 ${paths.length} 张图片'; - notifyListeners(); - } - return; - } - - final result = await FilePicker.platform.getDirectoryPath( - dialogTitle: "选择包含图片的文件夹", - ); - if (result != null) { - imagePaths = []; - imageFolderPathController.text = result; - notifyListeners(); - } - } - - Future pickerBatchImageFolder(BuildContext context) async { - if (Platform.isIOS) { - final result = await FilePicker.platform.pickFiles( - dialogTitle: "选择批量图片文件", - type: FileType.custom, - allowedExtensions: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'], - allowMultiple: true, - ); - if (result != null) { - final paths = result.paths.whereType().toList(); - batchImagePaths = paths; - batchImageFolderPathController.text = paths.isEmpty - ? '' - : '已选择 ${paths.length} 张图片(按所在文件夹分组)'; - notifyListeners(); - } - return; - } - - final result = await FilePicker.platform.getDirectoryPath( - dialogTitle: "选择批量图片文件夹的父目录", - ); - if (result != null) { - batchImagePaths = []; - batchImageFolderPathController.text = result; - notifyListeners(); - } - } - - Future pickerPdf(BuildContext context) async { - final result = await FilePicker.platform.pickFiles( - dialogTitle: "选择 PDF 文件", - type: FileType.custom, - allowedExtensions: ['pdf'], - ); - if (result != null && result.files.single.path != null) { - pdfPathController.text = result.files.single.path!; - notifyListeners(); - } - } - - Future pickerBatchPdf(BuildContext context) async { - if (Platform.isIOS) { - final result = await FilePicker.platform.pickFiles( - dialogTitle: "选择一个或多个 PDF 文件", - type: FileType.custom, - allowedExtensions: ['pdf'], - allowMultiple: true, - ); - if (result != null) { - final paths = result.paths.whereType().toList(); - batchPdfPaths = paths; - batchPdfPathController.text = paths.isEmpty - ? '' - : '已选择 ${paths.length} 个 PDF 文件'; - notifyListeners(); - } - return; - } - - final result = await FilePicker.platform.getDirectoryPath( - dialogTitle: "选择包含 PDF 的文件夹", - ); - if (result != null) { - batchPdfPaths = []; - batchPdfPathController.text = result; - notifyListeners(); - } - } - - @override - void dispose() { - urlController.dispose(); - archivePathController.dispose(); - batchArchivePathController.dispose(); - imageFolderPathController.dispose(); - batchImageFolderPathController.dispose(); - pdfPathController.dispose(); - batchPdfPathController.dispose(); - super.dispose(); - } -} - -enum ParseFormType { - web, - archive, - batchArchive, - imageFolder, - batchImageFolder, - pdf, - batchPdf, -} diff --git a/lib/feature/parse/ui/viewmodel/parse_image_folder_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_image_folder_viewmodel.dart deleted file mode 100644 index 372bf0a..0000000 --- a/lib/feature/parse/ui/viewmodel/parse_image_folder_viewmodel.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:permission_handler/permission_handler.dart'; -import 'package:tele_book/core/route/app_route.dart'; -import 'package:tele_book/core/util/state_util.dart'; -import 'package:tele_book/feature/book/model/dto/save_as_book_dto.dart'; -import 'package:tele_book/feature/book/repository/book_repository.dart'; -import 'package:tele_book/feature/parse/service/parse_archive_service.dart'; - -class ParseImageFolderViewmodel extends ChangeNotifier { - final String? folderPath; - final List? imagePathsInput; - final ParseArchiveService parseArchiveService; - final BookRepository bookRepository; - - EventState parseState = IdleEventState(); - EventState saveToBookState = IdleEventState(); - List imagePaths = []; - String folderName = ""; - - ParseImageFolderViewmodel({ - this.folderPath, - this.imagePathsInput, - required this.parseArchiveService, - required this.bookRepository, - }) { - if (folderPath != null && folderPath!.isNotEmpty) { - folderName = folderPath!.split(RegExp(r'[\\/]')).last; - } else if (imagePathsInput != null && imagePathsInput!.isNotEmpty) { - final first = imagePathsInput!.first; - final parts = first.split(RegExp(r'[\\/]')); - folderName = parts.length > 1 ? parts[parts.length - 2] : '导入图片'; - } else { - folderName = '导入图片'; - } - parseImageFolder(); - } - - Future _requestStoragePermission() async { - if (!Platform.isAndroid) return true; - if (await Permission.manageExternalStorage.isGranted) return true; - final status = await Permission.manageExternalStorage.request(); - if (status.isGranted) return true; - - if (await Permission.storage.isGranted) return true; - final storageStatus = await Permission.storage.request(); - return storageStatus.isGranted; - } - - Future parseImageFolder() async { - parseState = LoadingEventState(); - notifyListeners(); - - final hasPermission = await _requestStoragePermission(); - if (!hasPermission) { - parseState = ErrorEventState("需要存储权限才能读取图片文件夹"); - notifyListeners(); - return; - } - - final result = imagePathsInput != null && imagePathsInput!.isNotEmpty - ? await parseArchiveService.parseImagePaths(imagePathsInput!) - : await parseArchiveService.parseImageFolder(folderPath ?? ''); - result.fold( - onSuccess: (data) { - imagePaths = data; - parseState = data.isEmpty ? EmptyEventState() : SuccessEventState(data); - notifyListeners(); - }, - onError: (error) { - parseState = ErrorEventState(error.message); - notifyListeners(); - }, - ); - } - - Future saveToBook(BuildContext context) async { - if (imagePaths.isEmpty || saveToBookState.isLoading) return; - saveToBookState = LoadingEventState(); - notifyListeners(); - - final result = await bookRepository.saveAsBook( - SaveAsBookDto(title: folderName, paths: imagePaths), - ); - result.fold( - onSuccess: (_) { - saveToBookState = SuccessEventState(null); - notifyListeners(); - context.go(AppRoute.main); - }, - onError: (error) { - saveToBookState = ErrorEventState(error.message); - notifyListeners(); - }, - ); - } -} - diff --git a/lib/feature/parse/ui/viewmodel/parse_pdf_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_pdf_viewmodel.dart deleted file mode 100644 index 94cbb56..0000000 --- a/lib/feature/parse/ui/viewmodel/parse_pdf_viewmodel.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:tele_book/core/route/app_route.dart'; -import 'package:tele_book/core/util/state_util.dart'; -import 'package:tele_book/feature/book/model/dto/save_as_book_dto.dart'; -import 'package:tele_book/feature/book/repository/book_repository.dart'; -import 'package:tele_book/feature/parse/service/parse_pdf_service.dart'; - -class ParsePdfViewmodel extends ChangeNotifier { - final String pdfPath; - final ParsePdfService parsePdfService; - final BookRepository bookRepository; - - EventState parseState = const IdleEventState(); - EventState saveToBookState = const IdleEventState(); - - List tempPaths = []; - String pdfName = ''; - int currentPage = 0; - int totalPages = 0; - - ParsePdfViewmodel({ - required this.pdfPath, - required this.parsePdfService, - required this.bookRepository, - }) { - pdfName = pdfPath.split(RegExp(r'[\\/]')).last.replaceAll('.pdf', ''); - _parsePdf(); - } - - Future _parsePdf() async { - parseState = const LoadingEventState(); - notifyListeners(); - - final result = await parsePdfService.parsePdf( - pdfPath, - onProgress: (current, total) { - currentPage = current; - totalPages = total; - notifyListeners(); - }, - ); - - result.fold( - onSuccess: (data) { - tempPaths = data; - parseState = SuccessEventState(data); - notifyListeners(); - }, - onError: (error) { - parseState = ErrorEventState(error.message); - notifyListeners(); - }, - ); - } - - Future saveToBook(BuildContext context) async { - if (tempPaths.isEmpty || saveToBookState.isLoading) return; - saveToBookState = const LoadingEventState(); - notifyListeners(); - - try { - final result = await bookRepository.saveAsBook( - SaveAsBookDto(title: pdfName, paths: tempPaths), - ); - result.fold( - onSuccess: (_) { - saveToBookState = const SuccessEventState(null); - notifyListeners(); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('保存成功')), - ); - context.go(AppRoute.main); - }, - onError: (error) { - saveToBookState = ErrorEventState(error.message); - notifyListeners(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('保存失败: ${error.message}')), - ); - }, - ); - } catch (e) { - saveToBookState = ErrorEventState(e.toString()); - notifyListeners(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('保存失败: $e')), - ); - } - } -} - diff --git a/lib/feature/parse/ui/viewmodel/parse_web_viewmodel.dart b/lib/feature/parse/ui/viewmodel/parse_web_viewmodel.dart deleted file mode 100644 index 88510dd..0000000 --- a/lib/feature/parse/ui/viewmodel/parse_web_viewmodel.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:go_router/go_router.dart'; -import 'package:tele_book/core/route/app_route.dart'; -import 'package:tele_book/feature/download/service/download_service.dart'; -import 'package:tele_book/feature/parse/service/parse_web_service.dart'; - - -class ParseWebViewmodel extends ChangeNotifier { - final ParseWebService _parseWebService; - final DownloadService _downloadService; - - ParseWebViewmodel(this._parseWebService, this._downloadService); - - InAppWebViewController? webViewController; - InAppWebViewSettings settings = InAppWebViewSettings( - isInspectable: kDebugMode, - mediaPlaybackRequiresUserGesture: false, - allowsInlineMediaPlayback: true, - iframeAllow: "camera; microphone", - iframeAllowFullscreen: true, - ); - String title = "加载中..."; - List urls = []; - int progress = 0; - - void onLoadStart(InAppWebViewController controller) { - webViewController = controller; - notifyListeners(); - } - - void onTitleChanged(InAppWebViewController controller, String? title) { - this.title = title ?? "无标题"; - print("提取到的标题: $title"); - notifyListeners(); - } - - void onProgressChange(InAppWebViewController controller, int progress) async { - final urls = await _parseWebService.extractImagesFromWebView( - onExtractImages: (js) async { - final urls = await controller.evaluateJavascript(source: js); - return urls; - }, - ); - this.urls = urls; - this.progress = progress; - notifyListeners(); - } - - void startDownload(BuildContext context) { - if (urls.isEmpty) return; - _downloadService.startDownload(urls, title); - context.go(AppRoute.main); - } -} diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index ad279a8..75d875c 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/pubspec.lock b/pubspec.lock deleted file mode 100644 index 4b5e008..0000000 --- a/pubspec.lock +++ /dev/null @@ -1,1602 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: "5b7468c326d2f8a4f630056404ca0d291ade42918f4a3c6233618e724f39da8e" - url: "https://pub.flutter-io.cn" - source: hosted - version: "92.0.0" - analysis_server_plugin: - dependency: transitive - description: - name: analysis_server_plugin - sha256: "44adba4d74a2541173bad4c11531d2a4d22810c29c5ddb458a38e9f4d0e5eac7" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.3.4" - analyzer: - dependency: transitive - description: - name: analyzer - sha256: "70e4b1ef8003c64793a9e268a551a82869a8a96f39deb73dea28084b0e8bf75e" - url: "https://pub.flutter-io.cn" - source: hosted - version: "9.0.0" - analyzer_buffer: - dependency: transitive - description: - name: analyzer_buffer - sha256: ff4bd291778c7417fe53fe24ee0d0a1f1ffe281a2d4ea887e7094f16e36eace7 - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.3.0" - analyzer_plugin: - dependency: transitive - description: - name: analyzer_plugin - sha256: "6645a029da947ffd823d98118f385d4bd26b54eb069c006b22e0b94e451814b5" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.13.11" - archive: - dependency: "direct main" - description: - name: archive - sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.0.9" - args: - dependency: transitive - description: - name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.7.0" - async: - dependency: transitive - description: - name: async - sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.13.1" - background_downloader: - dependency: "direct main" - description: - name: background_downloader - sha256: "4cb23d9ad4f5060944f38164e7b90d4bf99b57b2472a3bd4676e59b2db4afd06" - url: "https://pub.flutter-io.cn" - source: hosted - version: "9.5.4" - barcode: - dependency: transitive - description: - name: barcode - sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.2.9" - bidi: - dependency: transitive - description: - name: bidi - sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.0.13" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.2" - build: - dependency: transitive - description: - name: build - sha256: aadd943f4f8cc946882c954c187e6115a84c98c81ad1d9c6cbf0895a8c85da9c - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.0.5" - build_config: - dependency: transitive - description: - name: build_config - sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.3.0" - build_daemon: - dependency: transitive - description: - name: build_daemon - sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.1.1" - build_runner: - dependency: "direct dev" - description: - name: build_runner - sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.15.0" - built_collection: - dependency: transitive - description: - name: built_collection - sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" - url: "https://pub.flutter-io.cn" - source: hosted - version: "5.1.1" - built_value: - dependency: transitive - description: - name: built_value - sha256: "0730c18c770d05636a8f945c32a4d7d81cb6e0f0148c8db4ad12e7748f7e49af" - url: "https://pub.flutter-io.cn" - source: hosted - version: "8.12.5" - characters: - dependency: transitive - description: - name: characters - sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.4.1" - charcode: - dependency: transitive - description: - name: charcode - sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.4.0" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.0.4" - cli_config: - dependency: transitive - description: - name: cli_config - sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.2.0" - cli_util: - dependency: transitive - description: - name: cli_util - sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.4.2" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - 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.flutter-io.cn" - source: hosted - version: "1.0.0" - code_builder: - dependency: transitive - description: - name: code_builder - sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.11.1" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.19.1" - convert: - dependency: transitive - description: - name: convert - sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.1.2" - coverage: - dependency: transitive - description: - name: coverage - sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.15.0" - cross_file: - dependency: transitive - description: - name: cross_file - sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.3.5+2" - crypto: - dependency: transitive - description: - name: crypto - sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.7" - csslib: - dependency: transitive - description: - name: csslib - sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.2" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.9" - dart_style: - dependency: transitive - description: - name: dart_style - sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.1.3" - dbus: - dependency: transitive - description: - name: dbus - sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.7.12" - dio: - dependency: "direct main" - description: - name: dio - sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c - url: "https://pub.flutter-io.cn" - source: hosted - version: "5.9.2" - dio_web_adapter: - dependency: transitive - description: - name: dio_web_adapter - sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.2" - dk_util: - dependency: "direct main" - description: - name: dk_util - sha256: e7b67ab113a0ac83411559f37401c3b4d81cc16b0510b637b2bcaa2a133dd597 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.0" - drift: - dependency: "direct main" - description: - name: drift - sha256: "970cd188fddb111b26ea6a9b07a62bf5c2432d74147b8122c67044ae3b97e99e" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.31.0" - drift_dev: - dependency: "direct dev" - description: - name: drift_dev - sha256: "917184b2fb867b70a548a83bf0d36268423b38d39968c06cce4905683da49587" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.31.0" - drift_flutter: - dependency: "direct main" - description: - name: drift_flutter - sha256: c07120854742a0cae2f7501a0da02493addde550db6641d284983c08762e60a7 - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.2.8" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.3.3" - ffi: - dependency: transitive - description: - name: ffi - sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.2.0" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.flutter-io.cn" - source: hosted - version: "7.0.1" - file_picker: - dependency: "direct main" - description: - name: file_picker - sha256: "57d9a1dd5063f85fa3107fb42d1faffda52fdc948cefd5fe5ea85267a5fc7343" - url: "https://pub.flutter-io.cn" - source: hosted - version: "10.3.10" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.1" - flex_color_scheme: - dependency: "direct main" - description: - name: flex_color_scheme - sha256: ab854146f201d2d62cc251fd525ef023b84182c4a0bfe4ae4c18ffc505b412d3 - url: "https://pub.flutter-io.cn" - source: hosted - version: "8.4.0" - flex_seed_scheme: - dependency: transitive - description: - name: flex_seed_scheme - sha256: a3183753bbcfc3af106224bff3ab3e1844b73f58062136b7499919f49f3667e7 - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.0.1" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_inappwebview: - dependency: "direct main" - description: - name: flutter_inappwebview - sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" - url: "https://pub.flutter-io.cn" - source: hosted - version: "6.1.5" - flutter_inappwebview_android: - dependency: transitive - description: - name: flutter_inappwebview_android - sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.3" - flutter_inappwebview_internal_annotations: - dependency: transitive - description: - name: flutter_inappwebview_internal_annotations - sha256: e30fba942e3debea7b7e6cdd4f0f59ce89dd403a9865193e3221293b6d1544c6 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.3.0" - flutter_inappwebview_ios: - dependency: transitive - description: - name: flutter_inappwebview_ios - sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.2" - flutter_inappwebview_macos: - dependency: transitive - description: - name: flutter_inappwebview_macos - sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.2" - flutter_inappwebview_platform_interface: - dependency: transitive - description: - name: flutter_inappwebview_platform_interface - sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.3.0+1" - flutter_inappwebview_web: - dependency: transitive - description: - name: flutter_inappwebview_web - sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.2" - flutter_inappwebview_windows: - dependency: transitive - description: - name: flutter_inappwebview_windows - sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.6.0" - flutter_launcher_icons: - dependency: "direct main" - description: - name: flutter_launcher_icons - sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.14.4" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" - url: "https://pub.flutter-io.cn" - source: hosted - version: "6.0.0" - flutter_plugin_android_lifecycle: - dependency: transitive - description: - name: flutter_plugin_android_lifecycle - sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.0.34" - flutter_reorderable_grid_view: - dependency: "direct main" - description: - name: flutter_reorderable_grid_view - sha256: "4b7752425dba3f5dec03dd8d11c63027592535c7b7ece65e2c9573a2ea310206" - url: "https://pub.flutter-io.cn" - source: hosted - version: "5.6.0" - flutter_riverpod: - dependency: "direct main" - description: - name: flutter_riverpod - sha256: "4e166be88e1dbbaa34a280bdb744aeae73b7ef25fdf8db7a3bb776760a3648e2" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.3.1" - flutter_staggered_grid_view: - dependency: "direct main" - description: - name: flutter_staggered_grid_view - sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.7.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - freezed_annotation: - dependency: transitive - description: - name: freezed_annotation - sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.1.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.0.0" - get: - dependency: transitive - description: - name: get - sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a" - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.7.3" - glob: - dependency: transitive - description: - name: glob - sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.3" - go_router: - dependency: "direct main" - description: - name: go_router - sha256: "5540e4a3f416dd4a93458257b908eb88353cbd0fb5b0a3d1bd7d849ba1e88735" - url: "https://pub.flutter-io.cn" - source: hosted - version: "17.2.1" - graphs: - dependency: transitive - description: - name: graphs - sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.3.2" - hooks: - dependency: transitive - description: - name: hooks - sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.3" - html: - dependency: "direct main" - description: - name: html - sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.15.6" - http: - dependency: "direct main" - description: - name: http - sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.6.0" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.2.2" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.1.2" - image: - dependency: transitive - description: - name: image - sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.8.0" - intl: - dependency: "direct main" - description: - name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.20.2" - io: - dependency: transitive - description: - name: io - sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.5" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.11.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.flutter-io.cn" - source: hosted - version: "11.0.2" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.10" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.2" - lints: - dependency: transitive - description: - name: lints - sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" - url: "https://pub.flutter-io.cn" - source: hosted - version: "6.1.0" - logging: - dependency: transitive - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.3.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.12.19" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.13.0" - meta: - dependency: transitive - description: - name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.17.0" - mime: - dependency: transitive - description: - name: mime - sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.0.0" - mockito: - dependency: transitive - description: - name: mockito - sha256: eff30d002f0c8bf073b6f929df4483b543133fcafce056870163587b03f1d422 - url: "https://pub.flutter-io.cn" - source: hosted - version: "5.6.4" - native_toolchain_c: - dependency: transitive - description: - name: native_toolchain_c - sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.17.6" - nested: - dependency: transitive - description: - name: nested - sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.0" - node_preamble: - dependency: transitive - description: - name: node_preamble - sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.0.2" - nsd: - dependency: transitive - description: - name: nsd - sha256: "1611a5c9f61d56ff2973e1488ae04112103e5203b4a7a1fb594b48cfb366fc14" - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.1.0" - nsd_android: - dependency: transitive - description: - name: nsd_android - sha256: "96d2d451c5db0319c37b1b2a38f2d55eb56ae54c0b0d3144c03c19c97436de4a" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.2.0" - nsd_ios: - dependency: transitive - description: - name: nsd_ios - sha256: "562fffe753543a65190344d3acd0ed80d96c571ac1a05bf10780c5584b5055ac" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.0.1" - nsd_macos: - dependency: transitive - description: - name: nsd_macos - sha256: "47cd355d84009befe02710c72bf1c1999b24d5f3fb3a3d914f8b77bcaca42542" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.0.1" - nsd_platform_interface: - dependency: transitive - description: - name: nsd_platform_interface - sha256: b1a5ace6f01ea2ce37f373e52c3b7af4fd7c11de2582ddcc89f4fc00615d9dff - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.2.0" - nsd_windows: - dependency: transitive - description: - name: nsd_windows - sha256: "68b4a256b0be258dbbad0ae789f2e8838d0935a353dac86b17c14c1a05df4ecd" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.1" - objective_c: - dependency: transitive - description: - name: objective_c - sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" - url: "https://pub.flutter-io.cn" - source: hosted - version: "9.3.0" - open_filex: - dependency: "direct main" - description: - name: open_filex - sha256: "9976da61b6a72302cf3b1efbce259200cd40232643a467aac7370addf94d6900" - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.7.0" - 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: transitive - description: - name: package_info_plus - sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d - url: "https://pub.flutter-io.cn" - source: hosted - version: "9.0.0" - package_info_plus_platform_interface: - dependency: transitive - description: - name: package_info_plus_platform_interface - sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.2.1" - path: - dependency: "direct main" - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.9.1" - path_parsing: - dependency: transitive - description: - name: path_parsing - sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.0" - path_provider: - dependency: "direct main" - description: - name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.5" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: "149441ca6e4f38193b2e004c0ca6376a3d11f51fa5a77552d8bd4d2b0c0912ba" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.2.23" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.6.0" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - 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.flutter-io.cn" - source: hosted - version: "2.1.2" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.3.0" - pdf: - dependency: "direct main" - description: - name: pdf - sha256: e47a275b267873d5944ad5f5ff0dcc7ac2e36c02b3046a0ffac9b72fd362c44b - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.12.0" - pdfium_dart: - dependency: transitive - description: - name: pdfium_dart - sha256: "58ad7325da54fd6b36860b0dcda8193b26214a3e8494036404bb3954c7795b07" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.2.0" - pdfium_flutter: - dependency: transitive - description: - name: pdfium_flutter - sha256: "05b15269ddeb81f0c1e4748c9f9fd61bc2109ae5e193962456ec15d885332af3" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.2.0" - pdfrx: - dependency: "direct main" - description: - name: pdfrx - sha256: ba1003bc45454de82ba4911791561533108cfb5ebecb54d8d06489ff6b7f8ebc - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.3.2" - pdfrx_engine: - dependency: transitive - description: - name: pdfrx_engine - sha256: bbb71414a7828128e87a017c97f3c392e379b9e171beaaa37aa225c597ca4f6e - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.4.1" - permission_handler: - dependency: "direct main" - description: - name: permission_handler - sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 - url: "https://pub.flutter-io.cn" - source: hosted - version: "12.0.1" - permission_handler_android: - dependency: transitive - description: - name: permission_handler_android - sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" - url: "https://pub.flutter-io.cn" - source: hosted - version: "13.0.1" - permission_handler_apple: - dependency: transitive - description: - name: permission_handler_apple - sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 - url: "https://pub.flutter-io.cn" - source: hosted - version: "9.4.7" - permission_handler_html: - dependency: transitive - description: - name: permission_handler_html - sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.1.3+5" - permission_handler_platform_interface: - dependency: transitive - description: - name: permission_handler_platform_interface - sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.3.0" - permission_handler_windows: - dependency: transitive - description: - name: permission_handler_windows - sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.2.1" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" - url: "https://pub.flutter-io.cn" - source: hosted - version: "7.0.2" - platform: - dependency: transitive - description: - name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.1.6" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.8" - pool: - dependency: transitive - description: - name: pool - sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.5.2" - posix: - dependency: transitive - description: - name: posix - sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" - url: "https://pub.flutter-io.cn" - source: hosted - version: "6.5.0" - provider: - dependency: transitive - description: - name: provider - sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" - url: "https://pub.flutter-io.cn" - source: hosted - version: "6.1.5+1" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.2.0" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.5.0" - qr: - dependency: transitive - description: - name: qr - sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.2" - recase: - dependency: transitive - description: - name: recase - sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.1.0" - record_use: - dependency: transitive - description: - name: record_use - sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.6.0" - responsive_framework: - dependency: "direct main" - description: - name: responsive_framework - sha256: a8e1c13d4ba980c60cbf6fa1e9907cd60662bf2585184d7c96ca46c43de91552 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.5.1" - riverpod: - dependency: "direct main" - description: - name: riverpod - sha256: "8c22216be8ad3ef2b44af3a329693558c98eca7b8bd4ef495c92db0bba279f83" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.2.1" - riverpod_analyzer_utils: - dependency: transitive - description: - name: riverpod_analyzer_utils - sha256: e55bc08c084a424e1bbdc303fe8ea75daafe4269b68fd0e0f6f1678413715b66 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.0-dev.9" - riverpod_annotation: - dependency: "direct main" - description: - name: riverpod_annotation - sha256: "16471a1260b94e939394d78f1c63a9350936ac4a68c9fbdab40be47268c0b04f" - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.0.2" - riverpod_generator: - dependency: "direct dev" - description: - name: riverpod_generator - sha256: "6f9220534d7a353b53c875ea191a84d28cb4e52ac420a66a1bd7318329d977b0" - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.0.3" - riverpod_lint: - dependency: "direct dev" - description: - name: riverpod_lint - sha256: "64e8debf5b719a37d48b9785dd595d34133fdcd84b8fd07157a621c54ab2156f" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.1.3" - rxdart: - dependency: transitive - description: - name: rxdart - sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.28.0" - share_plus: - dependency: "direct main" - description: - name: share_plus - sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840" - url: "https://pub.flutter-io.cn" - source: hosted - version: "12.0.1" - share_plus_platform_interface: - dependency: transitive - description: - name: share_plus_platform_interface - sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" - url: "https://pub.flutter-io.cn" - source: hosted - version: "6.1.0" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.5.5" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.4.23" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.5.6" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - 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: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.4.2" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.4.3" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.4.1" - shelf: - dependency: transitive - description: - name: shelf - sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.4.2" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.2" - shelf_static: - dependency: transitive - description: - name: shelf_static - sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.3" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.0" - shimmer: - dependency: "direct main" - description: - name: shimmer - sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - source_gen: - dependency: transitive - description: - name: source_gen - sha256: "732792cfd197d2161a65bb029606a46e0a18ff30ef9e141a7a82172b05ea8ecd" - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.2.2" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.2" - source_maps: - dependency: transitive - description: - name: source_maps - sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.10.13" - source_span: - dependency: transitive - description: - name: source_span - sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.10.2" - sqlite3: - dependency: transitive - description: - name: sqlite3 - sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.9.4" - sqlite3_flutter_libs: - dependency: transitive - description: - name: sqlite3_flutter_libs - sha256: eeb9e3a45207649076b808f8a5a74d68770d0b7f26ccef6d5f43106eee5375ad - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.5.42" - sqlparser: - dependency: transitive - description: - name: sqlparser - sha256: "337e9997f7141ffdd054259128553c348635fa318f7ca492f07a4ab76f850d19" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.43.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.12.1" - state_notifier: - dependency: transitive - description: - name: state_notifier - sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.4" - stream_transform: - dependency: transitive - description: - name: stream_transform - sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.1" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.4.1" - synchronized: - dependency: "direct main" - description: - name: synchronized - sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.4.0+1" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.2.2" - test: - dependency: transitive - description: - name: test - sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.30.0" - test_api: - dependency: transitive - description: - name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.7.10" - test_core: - dependency: transitive - description: - name: test_core - sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.6.16" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.4.0" - url_launcher: - dependency: transitive - description: - name: url_launcher - sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 - url: "https://pub.flutter-io.cn" - source: hosted - version: "6.3.2" - url_launcher_android: - dependency: transitive - description: - name: url_launcher_android - sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" - url: "https://pub.flutter-io.cn" - source: hosted - version: "6.3.29" - url_launcher_ios: - dependency: transitive - description: - name: url_launcher_ios - sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" - url: "https://pub.flutter-io.cn" - source: hosted - version: "6.4.1" - url_launcher_linux: - dependency: transitive - description: - name: url_launcher_linux - sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.2.2" - url_launcher_macos: - dependency: transitive - description: - name: url_launcher_macos - sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.2.5" - url_launcher_platform_interface: - dependency: transitive - description: - name: url_launcher_platform_interface - sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - 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.flutter-io.cn" - source: hosted - version: "2.4.2" - url_launcher_windows: - dependency: transitive - description: - name: url_launcher_windows - sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.1.5" - uuid: - dependency: "direct main" - description: - name: uuid - sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.5.3" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - 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.flutter-io.cn" - source: hosted - version: "15.0.2" - wakelock_plus: - dependency: "direct main" - description: - name: wakelock_plus - sha256: "8b12256f616346910c519a35606fb69b1fe0737c06b6a447c6df43888b097f39" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.5.1" - wakelock_plus_platform_interface: - dependency: transitive - description: - name: wakelock_plus_platform_interface - sha256: "24b84143787220a403491c2e5de0877fbbb87baf3f0b18a2a988973863db4b03" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.4.0" - watcher: - dependency: transitive - description: - name: watcher - sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.2.1" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.1" - web_socket: - dependency: transitive - description: - name: web_socket - sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.1" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.3" - webkit_inspection_protocol: - dependency: transitive - description: - name: webkit_inspection_protocol - sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.2.1" - webview_flutter: - dependency: "direct main" - description: - name: webview_flutter - sha256: a3da219916aba44947d3a5478b1927876a09781174b5a2b67fa5be0555154bf9 - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.13.1" - webview_flutter_android: - dependency: transitive - description: - name: webview_flutter_android - sha256: "0f7fcd2c86bf36bdcf94881f7941ce0cbc4f8d104b9fdcd5fcbef90e2199db76" - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.10.15" - webview_flutter_platform_interface: - dependency: transitive - description: - name: webview_flutter_platform_interface - sha256: "1221c1b12f5278791042f2ec2841743784cf25c5a644e23d6680e5d718824f04" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.15.1" - webview_flutter_wkwebview: - dependency: transitive - description: - name: webview_flutter_wkwebview - sha256: d7219cfabc6f5fc2032e0fa980ec36d71f308a35a823395af1abc34d9a2ede83 - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.24.2" - win32: - dependency: transitive - description: - name: win32 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.flutter-io.cn" - source: hosted - version: "5.15.0" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.0" - xml: - dependency: transitive - description: - name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" - url: "https://pub.flutter-io.cn" - source: hosted - version: "6.6.1" - yaml: - dependency: transitive - description: - name: yaml - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.1.3" - yaml_edit: - dependency: transitive - description: - name: yaml_edit - sha256: "07c9e63ba42519745182b88ca12264a7ba2484d8239958778dfe4d44fe760488" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.2.4" -sdks: - dart: ">=3.11.0 <4.0.0" - flutter: ">=3.41.0" diff --git a/pubspec.yaml b/pubspec.yaml index 9d236ac..a7bdb69 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,7 @@ dependencies: pdfrx: ^2.3.2 flutter_riverpod: ^3.3.1 riverpod_annotation: ^4.0.2 + freezed_annotation: ^3.1.0 dev_dependencies: build_runner: ^2.15.0 @@ -51,6 +52,7 @@ dev_dependencies: sdk: flutter riverpod_generator: ^4.0.3 riverpod_lint: ^3.1.3 + freezed: ^3.2.5 flutter: uses-material-design: true diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 165cb71..0546ea5 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -12,6 +12,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni ) set(PLUGIN_BUNDLED_LIBRARIES) From 783a6d8ffdf61c3133990dc097be75a45d3806cf Mon Sep 17 00:00:00 2001 From: warren Date: Tue, 26 May 2026 17:42:32 +0800 Subject: [PATCH 09/16] =?UTF-8?q?=E4=BD=BF=E7=94=A8riverpod=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/feature/book/enum/book_menu_type.dart | 1 + lib/feature/book/ui/view/book_list_view.dart | 22 +++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/feature/book/enum/book_menu_type.dart b/lib/feature/book/enum/book_menu_type.dart index b8632d5..28ae5f6 100644 --- a/lib/feature/book/enum/book_menu_type.dart +++ b/lib/feature/book/enum/book_menu_type.dart @@ -17,6 +17,7 @@ enum BookTopMenuType { /// 单本书条目菜单 enum BookItemMenuType { + edit(icon: Icons.edit, title: '编辑'), export(icon: Icons.move_to_inbox, title: '导出'), delete(icon: Icons.delete, title: '删除'); diff --git a/lib/feature/book/ui/view/book_list_view.dart b/lib/feature/book/ui/view/book_list_view.dart index 6ba8e26..35de1e0 100644 --- a/lib/feature/book/ui/view/book_list_view.dart +++ b/lib/feature/book/ui/view/book_list_view.dart @@ -120,13 +120,17 @@ class _BookListViewState extends ConsumerState { final results = bookVos.where((vo) => vo.book.name.toLowerCase().contains(query)); return results.map( - (vo) => ListTile( - leading: LocalImageWidget(imagePath: vo.coverImagePath), - title: Text(vo.book.name), - onTap: () { - searchController.closeView(vo.book.name); - context.push(AppRoute.bookPage, extra: vo.book); - }, + (vo) => Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: + ListTile( + leading: LocalImageWidget(imagePath: vo.coverImagePath), + title: Text(vo.book.name), + onTap: () { + searchController.closeView(vo.book.name); + context.push(AppRoute.bookPage, extra: vo.book); + }, + ) ), ); }, @@ -392,6 +396,8 @@ class _BookListTile extends ConsumerWidget { void _onItemSelected( BuildContext context, WidgetRef ref, BookItemMenuType type) { switch (type) { + case BookItemMenuType.edit: + context.push(AppRoute.bookForm, extra: book.book); case BookItemMenuType.export: Navigator.of(context).push(MaterialPageRoute( builder: (_) => ExportSingleFormView(book: book.book))); @@ -573,6 +579,8 @@ class _BookGridTile extends ConsumerWidget { void _onItemSelected( BuildContext context, WidgetRef ref, BookItemMenuType type) { switch (type) { + case BookItemMenuType.edit: + context.push(AppRoute.bookForm, extra: book.book); case BookItemMenuType.export: Navigator.of(context).push(MaterialPageRoute( builder: (_) => ExportSingleFormView(book: book.book))); From 7ca9e106eb30bdf05dce4581c33887f9dbdc9d4b Mon Sep 17 00:00:00 2001 From: warren Date: Wed, 27 May 2026 14:43:10 +0800 Subject: [PATCH 10/16] =?UTF-8?q?=E8=BF=81=E7=A7=BB=E5=88=B0riverpod?= =?UTF-8?q?=EF=BC=8C=E5=9F=BA=E6=9C=AC=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/gradle.properties | 4 + lib/common/widget/task_item_widget.dart | 49 ++- lib/core/route/app_route.dart | 37 +- .../local/book_local_datasource.dart | 4 + .../book/repository/book_repository.dart | 4 + .../book/ui/provider/book_provider.dart | 6 + lib/feature/book/ui/view/book_list_view.dart | 9 - .../book/ui/view/book_picker_view.dart | 174 ++++++++ .../collection_book_local_datasource.dart | 19 + .../local/collection_local_datasource.dart | 4 + .../model/vo/collection_list_item_vo.dart | 2 + .../repository/collection_repository.dart | 29 ++ .../ui/provider/collection_book_provider.dart | 51 +++ .../collection_book_provider.freezed.dart | 277 +++++++++++++ .../provider/collection_book_provider.g.dart | 96 +++++ .../ui/provider/collection_provider.dart | 85 +++- .../ui/provider/collection_provider.g.dart | 92 +++++ .../ui/view/collection_book_view.dart | 129 ++++++ .../collection/ui/view/collection_view.dart | 382 +++++++++++++++--- .../download/ui/view/download_list_view.dart | 234 +++++++---- lib/feature/main/provider/main_provider.dart | 24 ++ .../main/provider/main_provider.freezed.dart | 271 +++++++++++++ .../main/provider/main_provider.g.dart | 61 +++ lib/feature/main/view/main_view.dart | 33 +- .../main/viewmodel/main_viewmodel.dart | 12 - lib/feature/parse/ui/view/parse_web_view.dart | 35 +- 26 files changed, 1903 insertions(+), 220 deletions(-) create mode 100644 lib/feature/book/ui/view/book_picker_view.dart create mode 100644 lib/feature/collection/ui/provider/collection_book_provider.dart create mode 100644 lib/feature/collection/ui/provider/collection_book_provider.freezed.dart create mode 100644 lib/feature/collection/ui/provider/collection_book_provider.g.dart create mode 100644 lib/feature/collection/ui/view/collection_book_view.dart create mode 100644 lib/feature/main/provider/main_provider.dart create mode 100644 lib/feature/main/provider/main_provider.freezed.dart create mode 100644 lib/feature/main/provider/main_provider.g.dart delete mode 100644 lib/feature/main/viewmodel/main_viewmodel.dart diff --git a/android/gradle.properties b/android/gradle.properties index 598d13f..f2122b2 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,7 @@ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true +# This builtInKotlin flag was added automatically by Flutter migrator +android.builtInKotlin=false +# This newDsl flag was added automatically by Flutter migrator +android.newDsl=false diff --git a/lib/common/widget/task_item_widget.dart b/lib/common/widget/task_item_widget.dart index b56ed29..7aae294 100644 --- a/lib/common/widget/task_item_widget.dart +++ b/lib/common/widget/task_item_widget.dart @@ -21,35 +21,40 @@ class TaskItemWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return GestureDetector( + return ListTile( onTap: onTap, - child: Row( + contentPadding: EdgeInsets.zero, + leading: NetworkImageWidget(imageUrl: coverUrl), + title: Text( + title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyLarge, + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - NetworkImageWidget(imageUrl: coverUrl), - Expanded( - child: ListTile( - title: Text( - title, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyLarge, + Row( + children: [ + Text( + "状态: $status", + style: Theme.of(context).textTheme.bodySmall, ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "状态: $status", - style: Theme.of(context).textTheme.bodyMedium, - ), - SizedBox(height: 8), - LinearProgressIndicator(value: progress,borderRadius: BorderRadius.circular(4),), - ], + SizedBox(width: 8), + Text( + "进度: ${(progress * 100).toStringAsFixed(1)}%", + style: Theme.of(context).textTheme.bodySmall, ), - trailing: trailing, - ), + ], + ), + SizedBox(height: 8), + LinearProgressIndicator( + value: progress, + borderRadius: BorderRadius.circular(4), ), ], ), + trailing: trailing, ); } } diff --git a/lib/core/route/app_route.dart b/lib/core/route/app_route.dart index f0e8221..d29fc3e 100644 --- a/lib/core/route/app_route.dart +++ b/lib/core/route/app_route.dart @@ -3,7 +3,8 @@ import 'package:go_router/go_router.dart'; import 'package:tele_book/core/db/app_database.dart'; import 'package:tele_book/feature/book/ui/view/book_form_view.dart'; import 'package:tele_book/feature/book/ui/view/book_page_view.dart'; -import 'package:tele_book/feature/book/ui/view/book_list_view.dart'; +import 'package:tele_book/feature/book/ui/view/book_picker_view.dart'; +import 'package:tele_book/feature/collection/ui/view/collection_book_view.dart'; import 'package:tele_book/feature/download/ui/view/download_list_view.dart'; import 'package:tele_book/feature/export/ui/view/export_batch_form_view.dart'; import 'package:tele_book/feature/export/ui/view/export_single_form_view.dart'; @@ -28,10 +29,15 @@ class AppRoute { // 书籍相关 static const bookForm = '/book/form'; static const bookPage = '/book/page'; + static const bookPicker = '/book/picker'; // 下载 static const download = '/download'; + + static const collection = '/collection'; + static const collectionBook = '/collection/book'; + // 解析 static const parseForm = '/parse/form'; static const parseWeb = '/parse/web'; @@ -72,12 +78,41 @@ class AppRoute { return MaterialPage(child: BookPageView(book: book)); }, ), + GoRoute( + path: bookPicker, + pageBuilder: (context, state) { + final extra = state.extra; + Set disabledBookIds = {}; + if (extra is List) { + disabledBookIds = extra.toSet(); + } else if (extra is Set) { + disabledBookIds = extra; + } else if (extra is List) { + disabledBookIds = extra.whereType().toSet(); + } + return MaterialPage( + child: BookPickerView(disabledBookIds: disabledBookIds), + ); + }, + ), GoRoute( path: download, pageBuilder: (context, state) { return MaterialPage(child: Scaffold(body: DownloadListView())); }, ), + GoRoute( + path: collectionBook, + pageBuilder: (context, state) { + final collectionId = state.extra as int?; + if (collectionId == null) { + return MaterialPage(child: ErrorRoutePage(message: "缺少书籍收藏夹ID参数")); + } + return MaterialPage( + child: CollectionBookView(collectionId: collectionId), + ); + }, + ), GoRoute( path: exportSingle, pageBuilder: (context, state) { diff --git a/lib/feature/book/datasource/local/book_local_datasource.dart b/lib/feature/book/datasource/local/book_local_datasource.dart index d11891d..3a2fb0d 100644 --- a/lib/feature/book/datasource/local/book_local_datasource.dart +++ b/lib/feature/book/datasource/local/book_local_datasource.dart @@ -11,6 +11,10 @@ class BookLocalDatasource extends DatabaseAccessor with _$BookLocalDatasourceMixin { BookLocalDatasource(super.attachedDatabase); + Stream> watchAllBooks() { + return (select(bookTable)..orderBy([(t) => OrderingTerm(expression: t.createdAt, mode: OrderingMode.desc)])).watch(); + } + Stream> watchBooks({ int? page, int? pageSize, diff --git a/lib/feature/book/repository/book_repository.dart b/lib/feature/book/repository/book_repository.dart index 8152182..799d71f 100644 --- a/lib/feature/book/repository/book_repository.dart +++ b/lib/feature/book/repository/book_repository.dart @@ -68,6 +68,10 @@ class BookRepository { BookRepository(this._db); + Stream> watchAllBooks() { + return _bookLocalDatasource.watchAllBooks(); + } + Future> getPagedBooks({ int? page, int pageSize = 20, diff --git a/lib/feature/book/ui/provider/book_provider.dart b/lib/feature/book/ui/provider/book_provider.dart index 8077797..b51eec0 100644 --- a/lib/feature/book/ui/provider/book_provider.dart +++ b/lib/feature/book/ui/provider/book_provider.dart @@ -1,3 +1,4 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:tele_book/common/config/global_config.dart'; import 'package:tele_book/core/db/app_database.dart'; @@ -7,6 +8,11 @@ import 'package:tele_book/feature/book/repository/book_repository.dart'; part 'book_provider.g.dart'; +final booksProvider = StreamProvider.autoDispose>((ref){ + final bookRepository = ref.watch(bookRepositoryProvider); + return bookRepository.watchAllBooks(); +}); + @riverpod class BookList extends _$BookList { final int _pageSize = 20; diff --git a/lib/feature/book/ui/view/book_list_view.dart b/lib/feature/book/ui/view/book_list_view.dart index 35de1e0..bf3caf0 100644 --- a/lib/feature/book/ui/view/book_list_view.dart +++ b/lib/feature/book/ui/view/book_list_view.dart @@ -103,7 +103,6 @@ class _BookListViewState extends ConsumerState { AppBar _buildNormalAppBar(BuildContext context) { final bookVos = ref.watch(bookListProvider.select((s) => s.value?.bookVos ?? [])); - final taskCount = ref.watch(downloadTasksProvider).value?.length ?? 0; final state = ref.watch(bookListProvider).value; return AppBar( @@ -135,14 +134,6 @@ class _BookListViewState extends ConsumerState { ); }, ), - if (taskCount > 0) - IconButton( - onPressed: () => context.push(AppRoute.download), - icon: Badge( - label: Text('$taskCount'), - child: const Icon(Icons.download), - ), - ), IconButton(onPressed: (){ context.push(AppRoute.parseForm); }, icon: const Icon(Icons.add)), diff --git a/lib/feature/book/ui/view/book_picker_view.dart b/lib/feature/book/ui/view/book_picker_view.dart new file mode 100644 index 0000000..719e8c4 --- /dev/null +++ b/lib/feature/book/ui/view/book_picker_view.dart @@ -0,0 +1,174 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:tele_book/common/config/global_config.dart'; +import 'package:tele_book/common/widget/error_widget.dart'; +import 'package:tele_book/common/widget/local_image_widget.dart'; +import 'package:tele_book/core/db/app_database.dart'; +import 'package:tele_book/feature/book/ui/provider/book_provider.dart'; + +class BookPickerView extends ConsumerStatefulWidget { + final Set disabledBookIds; + + const BookPickerView({ + super.key, + this.disabledBookIds = const {}, + }); + + @override + ConsumerState createState() => _BookPickerViewState(); +} + +class _BookPickerViewState extends ConsumerState { + final Set _selectedBookIds = {}; + final TextEditingController _searchController = TextEditingController(); + String _keyword = ''; + + @override + void dispose() { + _searchController.dispose(); + super.dispose(); + } + + void _toggleBook(int bookId) { + if (widget.disabledBookIds.contains(bookId)) return; + setState(() { + if (_selectedBookIds.contains(bookId)) { + _selectedBookIds.remove(bookId); + } else { + _selectedBookIds.add(bookId); + } + }); + } + + @override + Widget build(BuildContext context) { + final asyncState = ref.watch(booksProvider); + + return Scaffold( + appBar: AppBar(title: Text("选择书籍")), + body: asyncState.when( + data: (books) { + final filteredBooks = books.where((book) { + if (_keyword.isEmpty) return true; + return book.name.toLowerCase().contains(_keyword.toLowerCase()); + }).toList(); + + if (filteredBooks.isEmpty) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(12, 12, 12, 8), + child: TextField( + controller: _searchController, + decoration: const InputDecoration( + hintText: '搜索书名', + prefixIcon: Icon(Icons.search), + border: OutlineInputBorder(), + ), + onChanged: (value) { + setState(() { + _keyword = value.trim(); + }); + }, + ), + ), + const Expanded(child: Center(child: Text('无匹配书籍'))), + ], + ); + } + + return Column( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(12, 12, 12, 8), + child: TextField( + controller: _searchController, + decoration: const InputDecoration( + hintText: '搜索书名', + prefixIcon: Icon(Icons.search), + border: OutlineInputBorder(), + ), + onChanged: (value) { + setState(() { + _keyword = value.trim(); + }); + }, + ), + ), + Expanded( + child: ListView.separated( + padding: const EdgeInsets.all(12), + itemCount: filteredBooks.length, + separatorBuilder: (_, __) => const SizedBox(height: 8), + itemBuilder: (context, index) { + final book = filteredBooks[index]; + final isDisabled = widget.disabledBookIds.contains(book.id); + final isSelected = isDisabled || _selectedBookIds.contains(book.id); + final coverPath = book.localSubPaths.isNotEmpty + ? GlobalConfig.resolveBookPath(book.localSubPaths.first) + : ''; + + return ListTile( + onTap: () => _toggleBook(book.id), + leading: coverPath.isEmpty + ? Container( + width: 64, + height: 64, + color: Colors.grey.shade300, + child: Icon( + Icons.menu_book_outlined, + color: Colors.grey.shade600, + ), + ) + : LocalImageWidget(imagePath: coverPath), + title: Text( + book.name, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + subtitle: Text( + isDisabled + ? '已在当前收藏夹中' + : '共 ${book.localSubPaths.length} 页', + ), + trailing: Checkbox( + value: isSelected, + onChanged: isDisabled ? null : (_) => _toggleBook(book.id), + ), + ); + }, + ), + ), + ], + ); + }, + error: (e, st) => Center( + child: CustomErrorWidget(errorMessage: e.toString(), stackTrace: st), + ), + loading: () => Center(child: CircularProgressIndicator()), + ), + bottomNavigationBar: asyncState.when( + data: (books) { + final selectedBooks = books + .where((book) => _selectedBookIds.contains(book.id)) + .toList(); + + return SafeArea( + minimum: const EdgeInsets.fromLTRB(16, 8, 16, 16), + child: FilledButton( + onPressed: selectedBooks.isEmpty + ? null + : () { + context.pop>(selectedBooks); + }, + child: Text('确认选择 (${selectedBooks.length})'), + ), + ); + }, + error: (_, __) => const SizedBox.shrink(), + loading: () => const SizedBox.shrink(), + ), + ); + } +} diff --git a/lib/feature/collection/datasource/local/collection_book_local_datasource.dart b/lib/feature/collection/datasource/local/collection_book_local_datasource.dart index 789a5dc..4c2c1af 100644 --- a/lib/feature/collection/datasource/local/collection_book_local_datasource.dart +++ b/lib/feature/collection/datasource/local/collection_book_local_datasource.dart @@ -21,6 +21,25 @@ class CollectionBookLocalDatasource extends DatabaseAccessor return into(collectionBookTable).insert(entry); } + Future updateCollectionBook(CollectionBookTableData entry) { + return update(collectionBookTable).replace(entry); + } + + Future insertCollectionBooks(List entries) { + return batch((batch) { + batch.insertAll(collectionBookTable, entries); + }); + } + + Future removeBookFromCollection(int collection, int bookId) { + return (delete( + collectionBookTable, + ) + ..where((tbl) => tbl.collectionId.equals(collection) & tbl.bookId.equals(bookId))) + .go(); + } + + Future deleteCollectionBook(int id) { return (delete( collectionBookTable, diff --git a/lib/feature/collection/datasource/local/collection_local_datasource.dart b/lib/feature/collection/datasource/local/collection_local_datasource.dart index 3a42303..8166530 100644 --- a/lib/feature/collection/datasource/local/collection_local_datasource.dart +++ b/lib/feature/collection/datasource/local/collection_local_datasource.dart @@ -21,6 +21,10 @@ class CollectionLocalDatasource extends DatabaseAccessor return into(collectionTable).insert(collection); } + Future updateCollection(CollectionTableData collection) { + return update(collectionTable).replace(collection); + } + Future deleteCollectionById(int id) { return (delete(collectionTable)..where((tbl) => tbl.id.equals(id))).go(); } diff --git a/lib/feature/collection/model/vo/collection_list_item_vo.dart b/lib/feature/collection/model/vo/collection_list_item_vo.dart index b531854..a45ad3d 100644 --- a/lib/feature/collection/model/vo/collection_list_item_vo.dart +++ b/lib/feature/collection/model/vo/collection_list_item_vo.dart @@ -3,9 +3,11 @@ import 'package:tele_book/core/db/app_database.dart'; class CollectionListItemVo { final CollectionTableData collection; final int count; + final List coverImages; CollectionListItemVo({ required this.collection, required this.count, + required this.coverImages, }); } \ No newline at end of file diff --git a/lib/feature/collection/repository/collection_repository.dart b/lib/feature/collection/repository/collection_repository.dart index 7cb8bbb..9dabe02 100644 --- a/lib/feature/collection/repository/collection_repository.dart +++ b/lib/feature/collection/repository/collection_repository.dart @@ -29,4 +29,33 @@ class CollectionRepository { ), ); } + + Future addBooksToCollection({ + required int collectionId, + required List bookIds, + }) async { + final entries = bookIds.map((bookId) => CollectionBookTableCompanion.insert( + collectionId: collectionId, + bookId: bookId, + )).toList(); + await _db.collectionBookLocalDatasource.insertCollectionBooks(entries); + } + + Future removeBookFromCollection({ + required int collectionId, + required int bookId, + }) async { + await _db.collectionBookLocalDatasource.removeBookFromCollection( + collectionId, + bookId, + ); + } + + Future updateCollection(CollectionTableData collection) async { + await _db.collectionLocalDatasource.updateCollection(collection); + } + + Future deleteCollection(int id) async { + await _db.collectionLocalDatasource.deleteCollectionById(id); + } } diff --git a/lib/feature/collection/ui/provider/collection_book_provider.dart b/lib/feature/collection/ui/provider/collection_book_provider.dart new file mode 100644 index 0000000..180f74e --- /dev/null +++ b/lib/feature/collection/ui/provider/collection_book_provider.dart @@ -0,0 +1,51 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:tele_book/common/config/global_config.dart'; +import 'package:tele_book/feature/book/model/state/book_list_state.dart'; +import 'package:tele_book/feature/book/ui/provider/book_provider.dart'; +import 'package:tele_book/feature/collection/ui/provider/collection_provider.dart'; + +part 'collection_book_provider.freezed.dart'; + +part 'collection_book_provider.g.dart'; + +@freezed +abstract class CollectionBookState with _$CollectionBookState { + const factory CollectionBookState({@Default([]) List books}) = + _CollectionBookState; +} + +@riverpod +AsyncValue collectionBookView(Ref ref,int collectionId) { + + final collectionsBooksAsync = ref.watch(collectionBooksProvider); + final booksAsync = ref.watch(booksProvider); + + if (collectionsBooksAsync.hasError) { + return AsyncValue.error(collectionsBooksAsync.error!, StackTrace.current); + } + if (booksAsync.hasError) { + return AsyncValue.error(booksAsync.error!, StackTrace.current); + } + + if (collectionsBooksAsync.isLoading || booksAsync.isLoading) { + return const AsyncValue.loading(); + } + + final collectionsBooks = collectionsBooksAsync.value!.where((e) => e.collectionId == collectionId).toList(); + final books = booksAsync.value!; + + final collectionBookIds = collectionsBooks.map((e) => e.bookId).toSet(); + final collectionBooks = books + .where((book) => collectionBookIds.contains(book.id)) + .toList(); + + final items = collectionBooks.map((book) { + final coverImagePath = GlobalConfig.resolveBookPath( + book.localSubPaths.first, + ); + return BookListItemVo(book: book, coverImagePath: coverImagePath); + }).toList(); + + return AsyncValue.data(CollectionBookState(books: items)); +} diff --git a/lib/feature/collection/ui/provider/collection_book_provider.freezed.dart b/lib/feature/collection/ui/provider/collection_book_provider.freezed.dart new file mode 100644 index 0000000..0963fd9 --- /dev/null +++ b/lib/feature/collection/ui/provider/collection_book_provider.freezed.dart @@ -0,0 +1,277 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'collection_book_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$CollectionBookState { + + List get books; +/// Create a copy of CollectionBookState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$CollectionBookStateCopyWith get copyWith => _$CollectionBookStateCopyWithImpl(this as CollectionBookState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is CollectionBookState&&const DeepCollectionEquality().equals(other.books, books)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(books)); + +@override +String toString() { + return 'CollectionBookState(books: $books)'; +} + + +} + +/// @nodoc +abstract mixin class $CollectionBookStateCopyWith<$Res> { + factory $CollectionBookStateCopyWith(CollectionBookState value, $Res Function(CollectionBookState) _then) = _$CollectionBookStateCopyWithImpl; +@useResult +$Res call({ + List books +}); + + + + +} +/// @nodoc +class _$CollectionBookStateCopyWithImpl<$Res> + implements $CollectionBookStateCopyWith<$Res> { + _$CollectionBookStateCopyWithImpl(this._self, this._then); + + final CollectionBookState _self; + final $Res Function(CollectionBookState) _then; + +/// Create a copy of CollectionBookState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? books = null,}) { + return _then(_self.copyWith( +books: null == books ? _self.books : books // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [CollectionBookState]. +extension CollectionBookStatePatterns on CollectionBookState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _CollectionBookState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _CollectionBookState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _CollectionBookState value) $default,){ +final _that = this; +switch (_that) { +case _CollectionBookState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _CollectionBookState value)? $default,){ +final _that = this; +switch (_that) { +case _CollectionBookState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( List books)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _CollectionBookState() when $default != null: +return $default(_that.books);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( List books) $default,) {final _that = this; +switch (_that) { +case _CollectionBookState(): +return $default(_that.books);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( List books)? $default,) {final _that = this; +switch (_that) { +case _CollectionBookState() when $default != null: +return $default(_that.books);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _CollectionBookState implements CollectionBookState { + const _CollectionBookState({final List books = const []}): _books = books; + + + final List _books; +@override@JsonKey() List get books { + if (_books is EqualUnmodifiableListView) return _books; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_books); +} + + +/// Create a copy of CollectionBookState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$CollectionBookStateCopyWith<_CollectionBookState> get copyWith => __$CollectionBookStateCopyWithImpl<_CollectionBookState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _CollectionBookState&&const DeepCollectionEquality().equals(other._books, _books)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_books)); + +@override +String toString() { + return 'CollectionBookState(books: $books)'; +} + + +} + +/// @nodoc +abstract mixin class _$CollectionBookStateCopyWith<$Res> implements $CollectionBookStateCopyWith<$Res> { + factory _$CollectionBookStateCopyWith(_CollectionBookState value, $Res Function(_CollectionBookState) _then) = __$CollectionBookStateCopyWithImpl; +@override @useResult +$Res call({ + List books +}); + + + + +} +/// @nodoc +class __$CollectionBookStateCopyWithImpl<$Res> + implements _$CollectionBookStateCopyWith<$Res> { + __$CollectionBookStateCopyWithImpl(this._self, this._then); + + final _CollectionBookState _self; + final $Res Function(_CollectionBookState) _then; + +/// Create a copy of CollectionBookState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? books = null,}) { + return _then(_CollectionBookState( +books: null == books ? _self._books : books // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +// dart format on diff --git a/lib/feature/collection/ui/provider/collection_book_provider.g.dart b/lib/feature/collection/ui/provider/collection_book_provider.g.dart new file mode 100644 index 0000000..4e4808e --- /dev/null +++ b/lib/feature/collection/ui/provider/collection_book_provider.g.dart @@ -0,0 +1,96 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'collection_book_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(collectionBookView) +final collectionBookViewProvider = CollectionBookViewFamily._(); + +final class CollectionBookViewProvider + extends + $FunctionalProvider< + AsyncValue, + AsyncValue, + AsyncValue + > + with $Provider> { + CollectionBookViewProvider._({ + required CollectionBookViewFamily super.from, + required int super.argument, + }) : super( + retry: null, + name: r'collectionBookViewProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$collectionBookViewHash(); + + @override + String toString() { + return r'collectionBookViewProvider' + '' + '($argument)'; + } + + @$internal + @override + $ProviderElement> $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + AsyncValue create(Ref ref) { + final argument = this.argument as int; + return collectionBookView(ref, argument); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(AsyncValue value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider>( + value, + ), + ); + } + + @override + bool operator ==(Object other) { + return other is CollectionBookViewProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$collectionBookViewHash() => + r'35e6922990efcd8afaa6ab1d59d5da989122b2fa'; + +final class CollectionBookViewFamily extends $Family + with $FunctionalFamilyOverride, int> { + CollectionBookViewFamily._() + : super( + retry: null, + name: r'collectionBookViewProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + CollectionBookViewProvider call(int collectionId) => + CollectionBookViewProvider._(argument: collectionId, from: this); + + @override + String toString() => r'collectionBookViewProvider'; +} diff --git a/lib/feature/collection/ui/provider/collection_provider.dart b/lib/feature/collection/ui/provider/collection_provider.dart index 8b98100..8e4a299 100644 --- a/lib/feature/collection/ui/provider/collection_provider.dart +++ b/lib/feature/collection/ui/provider/collection_provider.dart @@ -1,5 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:tele_book/common/config/global_config.dart'; import 'package:tele_book/core/db/app_database.dart'; +import 'package:tele_book/feature/book/ui/provider/book_provider.dart'; import 'package:tele_book/feature/collection/model/vo/collection_list_item_vo.dart'; import 'package:tele_book/feature/collection/repository/collection_repository.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -21,7 +23,8 @@ final collectionBooksProvider = @riverpod AsyncValue> collectionList(Ref ref) { final collectionsAsync = ref.watch(collectionsProvider); - final booksAsync = ref.watch(collectionBooksProvider); + final collectionBooksAsync = ref.watch(collectionBooksProvider); + final booksAsync = ref.watch(booksProvider); if (collectionsAsync.hasError) { return AsyncError( @@ -29,27 +32,55 @@ AsyncValue> collectionList(Ref ref) { collectionsAsync.stackTrace ?? StackTrace.current, ); } + if (collectionBooksAsync.hasError) { + return AsyncError( + collectionBooksAsync.error!, + collectionBooksAsync.stackTrace ?? StackTrace.current, + ); + } if (booksAsync.hasError) { return AsyncError( booksAsync.error!, booksAsync.stackTrace ?? StackTrace.current, ); } - if (collectionsAsync.isLoading || booksAsync.isLoading) { + if (collectionsAsync.isLoading || + collectionBooksAsync.isLoading || + booksAsync.isLoading) { return const AsyncLoading(); } final collections = collectionsAsync.value ?? const []; - final books = booksAsync.value ?? const []; + final collectionBooks = + collectionBooksAsync.value ?? const []; + final books = booksAsync.value ?? const []; final bookCountMap = {}; - for (final cb in books) { + final bookIdsByCollectionId = >{}; + for (final cb in collectionBooks) { bookCountMap[cb.collectionId] = (bookCountMap[cb.collectionId] ?? 0) + 1; + bookIdsByCollectionId + .putIfAbsent(cb.collectionId, () => []) + .add(cb.bookId); } + final bookById = {for (final b in books) b.id: b}; + final list = collections.map((c) { final count = bookCountMap[c.id] ?? 0; - return CollectionListItemVo(collection: c, count: count); + final coverImages = (bookIdsByCollectionId[c.id] ?? const []) + .map((bookId) => bookById[bookId]) + .whereType() + .where((book) => book.localSubPaths.isNotEmpty) + .map((book) => GlobalConfig.resolveBookPath(book.localSubPaths.first)) + .take(4) + .toList(); + + return CollectionListItemVo( + collection: c, + count: count, + coverImages: coverImages, + ); }).toList(); return AsyncData(list); @@ -79,3 +110,47 @@ class CreateCollectionController extends _$CreateCollectionController { }); } } + +@riverpod +class UpdateCollectionController extends _$UpdateCollectionController { + @override + FutureOr build() => null; + + Future updateCollection({ + required int collectionId, + required String name, + String? description, + }) async { + final trimmedName = name.trim(); + if (trimmedName.isEmpty) { + state = AsyncError('Collection name cannot be empty', StackTrace.current); + return; + } + + state = const AsyncLoading(); + state = await AsyncValue.guard(() async { + final repo = ref.read(collectionRepositoryProvider); + await repo.updateCollection( + CollectionTableData( + id: collectionId, + name: name, + description: description, + ), + ); + }); + } +} + +@riverpod +class DeleteCollectionController extends _$DeleteCollectionController { + @override + FutureOr build() => null; + + Future deleteCollection({required int collectionId}) async { + state = const AsyncLoading(); + state = await AsyncValue.guard(() async { + final repo = ref.read(collectionRepositoryProvider); + await repo.deleteCollection(collectionId); + }); + } +} diff --git a/lib/feature/collection/ui/provider/collection_provider.g.dart b/lib/feature/collection/ui/provider/collection_provider.g.dart index 0a0a73f..56a5152 100644 --- a/lib/feature/collection/ui/provider/collection_provider.g.dart +++ b/lib/feature/collection/ui/provider/collection_provider.g.dart @@ -102,3 +102,95 @@ abstract class _$CreateCollectionController extends $AsyncNotifier { element.handleCreate(ref, build); } } + +@ProviderFor(UpdateCollectionController) +final updateCollectionControllerProvider = + UpdateCollectionControllerProvider._(); + +final class UpdateCollectionControllerProvider + extends $AsyncNotifierProvider { + UpdateCollectionControllerProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'updateCollectionControllerProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$updateCollectionControllerHash(); + + @$internal + @override + UpdateCollectionController create() => UpdateCollectionController(); +} + +String _$updateCollectionControllerHash() => + r'13fda0c5619b384b3d7f1d9181b11f99002db90a'; + +abstract class _$UpdateCollectionController extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, void>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, void>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} + +@ProviderFor(DeleteCollectionController) +final deleteCollectionControllerProvider = + DeleteCollectionControllerProvider._(); + +final class DeleteCollectionControllerProvider + extends $AsyncNotifierProvider { + DeleteCollectionControllerProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'deleteCollectionControllerProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$deleteCollectionControllerHash(); + + @$internal + @override + DeleteCollectionController create() => DeleteCollectionController(); +} + +String _$deleteCollectionControllerHash() => + r'ec60e3546bdc4526f1c3e5ab5540655728761406'; + +abstract class _$DeleteCollectionController extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, void>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, void>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/lib/feature/collection/ui/view/collection_book_view.dart b/lib/feature/collection/ui/view/collection_book_view.dart new file mode 100644 index 0000000..f9fe309 --- /dev/null +++ b/lib/feature/collection/ui/view/collection_book_view.dart @@ -0,0 +1,129 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:tele_book/common/widget/empty_widget.dart'; +import 'package:tele_book/common/widget/error_widget.dart'; +import 'package:tele_book/common/widget/local_image_widget.dart'; +import 'package:tele_book/core/db/app_database.dart'; +import 'package:tele_book/core/route/app_route.dart'; +import 'package:tele_book/feature/collection/repository/collection_repository.dart'; +import 'package:tele_book/feature/collection/ui/provider/collection_book_provider.dart'; + +class CollectionBookView extends ConsumerWidget { + final int collectionId; + + const CollectionBookView({super.key, required this.collectionId}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final asyncState = ref.watch(collectionBookViewProvider(collectionId)); + + return Scaffold( + appBar: AppBar( + title: Text("书籍列表"), + actions: [ + IconButton( + icon: Icon(Icons.add), + onPressed: () async { + final disabledBookIds = asyncState.value?.books + .map((item) => item.book.id) + .toList() ?? + []; + + final result = await context.push>( + AppRoute.bookPicker, + extra: disabledBookIds, + ); + if (result != null && result.isNotEmpty) { + await ref + .read(collectionRepositoryProvider) + .addBooksToCollection( + collectionId: collectionId, + bookIds: result.map((e) => e.id).toList(), + ); + } + }, + ), + ], + ), + body: asyncState.when( + data: (data) { + if (data.books.isEmpty) { + return Center( + child: CustomEmptyWidget(icon: CupertinoIcons.book, text: "暂无书籍"), + ); + } + return ListView.builder( + itemCount: data.books.length, + itemBuilder: (context, index) { + final item = data.books[index]; + return GestureDetector( + onTap: () => context.push(AppRoute.bookPage, extra: item.book), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: Row( + children: [ + LocalImageWidget(imagePath: item.coverImagePath), + Expanded( + child: ListTile( + title: Text( + item.book.name, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + subtitle: Text( + '共 ${item.book.localSubPaths.length} 页', + ), + trailing: IconButton( + onPressed: () async { + final confirm = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text("确认删除吗?"), + content: Text("将从书架中移除该书籍,但不会删除本地文件"), + actions: [ + TextButton( + onPressed: () => + Navigator.pop(context, false), + child: Text("取消"), + ), + TextButton( + onPressed: () => + Navigator.pop(context, true), + child: Text("确认"), + ), + ], + ), + ); + if (confirm == true) { + await ref + .read(collectionRepositoryProvider) + .removeBookFromCollection( + collectionId: collectionId, + bookId: item.book.id, + ); + } + }, + icon: Icon(Icons.delete), + ), + ), + ), + ], + ), + ), + ); + }, + ); + }, + loading: () => Center(child: CircularProgressIndicator()), + error: (e, st) => Center( + child: CustomErrorWidget(errorMessage: e.toString(), stackTrace: st), + ), + ), + ); + } +} diff --git a/lib/feature/collection/ui/view/collection_view.dart b/lib/feature/collection/ui/view/collection_view.dart index 476f6d1..1fed5cd 100644 --- a/lib/feature/collection/ui/view/collection_view.dart +++ b/lib/feature/collection/ui/view/collection_view.dart @@ -1,7 +1,11 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:tele_book/common/widget/empty_widget.dart'; import 'package:tele_book/common/widget/error_widget.dart'; +import 'package:tele_book/core/route/app_route.dart'; import 'package:tele_book/feature/collection/ui/provider/collection_provider.dart'; class CollectionView extends ConsumerWidget { @@ -10,13 +14,14 @@ class CollectionView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final listAsync = ref.watch(collectionListProvider); - final createState = ref.watch(createCollectionControllerProvider); - ref.listen>(createCollectionControllerProvider, (prev, next) { + ref.listen>(createCollectionControllerProvider, ( + prev, + next, + ) { next.whenOrNull( data: (_) { if (prev?.isLoading == true) { - Navigator.of(context, rootNavigator: true).maybePop(); ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text("创建收藏夹成功"))); @@ -30,21 +35,53 @@ class CollectionView extends ConsumerWidget { ); }); + ref.listen>(updateCollectionControllerProvider, ( + prev, + next, + ) { + next.whenOrNull( + data: (_) { + if (prev?.isLoading == true) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text("更新收藏夹成功"))); + } + }, + error: (e, _) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text("更新收藏夹失败: $e"))); + }, + ); + }); + + ref.listen>(deleteCollectionControllerProvider, ( + prev, + next, + ) { + next.whenOrNull( + data: (_) { + if (prev?.isLoading == true) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text("删除收藏夹成功"))); + } + }, + error: (e, _) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text("删除收藏夹失败: $e"))); + }, + ); + }); + return Scaffold( appBar: AppBar( title: Text("收藏夹"), actions: [ IconButton( - onPressed: createState.isLoading - ? null - : () => _showCreateDialog(context, ref), - icon: createState.isLoading - ? const SizedBox( - width: 18, - height: 18, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : const Icon(Icons.add), + onPressed: () => _showCreateBottomSheet(context, ref), + icon: const Icon(Icons.add), ), ], ), @@ -58,13 +95,101 @@ class CollectionView extends ConsumerWidget { ); } - return ListView.builder( + return GridView.builder( + padding: const EdgeInsets.all(16), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 8, + crossAxisSpacing: 8, + childAspectRatio: 0.75, + ), itemCount: list.length, itemBuilder: (context, index) { final item = list[index]; - return ListTile( - title: Text(item.collection.name), - subtitle: Text(item.count.toString()), + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => context.push( + AppRoute.collectionBook, + extra: item.collection.id, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (item.coverImages.isEmpty) + AspectRatio( + aspectRatio: 1, + child: SizedBox( + child: Icon( + Icons.collections_bookmark_outlined, + size: 48, + color: Colors.grey[400], + ), + ), + ) + else + AspectRatio( + aspectRatio: 1, + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: GridView.count( + crossAxisCount: 2, + physics: const NeverScrollableScrollPhysics(), + mainAxisSpacing: 2, + crossAxisSpacing: 2, + children: item.coverImages.take(4).map((url) { + return Image.file( + File(url), + fit: BoxFit.cover, + cacheWidth: 200, + cacheHeight: 200, + ); + }).toList(), + ), + ), + ), + const SizedBox(height: 4), + Expanded( + child: ListTile( + contentPadding: const EdgeInsets.symmetric( + horizontal: 8, + ), + minVerticalPadding: 0, + + title: Text( + item.collection.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + subtitle: Text("${item.count} 本书"), + trailing: PopupMenuButton( + onSelected: (value) { + if (value == 'update') { + _showUpdateBottomSheet( + context, + ref, + collectionId: item.collection.id, + initialName: item.collection.name, + initialDescription: + item.collection.description ?? '', + ); + } + if (value == 'delete') { + _showDeleteConfirmDialog( + context, + item.collection.id, + ref, + ); + } + }, + itemBuilder: (_) => const [ + PopupMenuItem(value: 'update', child: Text('编辑')), + PopupMenuItem(value: 'delete', child: Text('删除')), + ], + ), + ), + ), + ], + ), ); }, ); @@ -82,71 +207,194 @@ class CollectionView extends ConsumerWidget { ); } - Future _showCreateDialog(BuildContext context, WidgetRef ref) async { + Future _showCreateBottomSheet( + BuildContext context, + WidgetRef ref, + ) async { final nameController = TextEditingController(); final descController = TextEditingController(); - await showDialog( + final formData = await showModalBottomSheet<_CollectionFormData>( context: context, - builder: (dialogContext) { - return AlertDialog( - title: const Text("创建收藏夹"), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextField( - controller: nameController, - decoration: const InputDecoration( - labelText: "名称", - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.bookmark_outline), + enableDrag: true, + isScrollControlled: true, + builder: (sheetContext) { + return Padding( + padding: MediaQuery.of(sheetContext).viewInsets, + child: Container( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text("创建收藏夹", style: Theme.of(context).textTheme.headlineSmall), + const SizedBox(height: 16), + TextField( + controller: nameController, + decoration: const InputDecoration( + labelText: "收藏夹名称", + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.collections_bookmark_outlined), + ), ), - ), - SizedBox(height: 16), - TextField( - controller: descController, - decoration: const InputDecoration( - labelText: "描述", - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.description_outlined), + SizedBox(height: 16), + TextField( + controller: descController, + decoration: const InputDecoration( + labelText: "描述", + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.description_outlined), + ), ), - ), - ], + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: () { + final name = nameController.text.trim(); + final desc = descController.text.trim(); + if (name.isEmpty) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text("名称不能为空"))); + return; + } + Navigator.of( + sheetContext, + ).pop(_CollectionFormData(name: name, description: desc)); + }, + child: const Text("创建"), + ), + ), + ], + ), ), + ); + }, + ); + + if (formData == null) return; + ref + .read(createCollectionControllerProvider.notifier) + .createCollection( + name: formData.name, + description: formData.description, + ); + } + + Future _showUpdateBottomSheet( + BuildContext context, + WidgetRef ref, { + required int collectionId, + required String initialName, + required String initialDescription, + }) async { + final nameController = TextEditingController(text: initialName); + final descController = TextEditingController(text: initialDescription); + + final formData = await showModalBottomSheet<_CollectionFormData>( + isScrollControlled: true, + enableDrag: true, + context: context, + builder: (sheetContext) { + return Padding( + padding: MediaQuery.of(sheetContext).viewInsets, + child: Container( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text("编辑收藏夹", style: Theme.of(context).textTheme.headlineSmall), + SizedBox(height: 16), + TextField( + controller: nameController, + decoration: const InputDecoration( + labelText: "收藏夹名称", + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.collections_bookmark_outlined), + ), + ), + SizedBox(height: 16), + TextField( + controller: descController, + decoration: const InputDecoration( + labelText: "描述", + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.description_outlined), + ), + ), + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: () { + final name = nameController.text.trim(); + final desc = descController.text.trim(); + if (name.isEmpty) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text("名称不能为空"))); + return; + } + Navigator.of( + sheetContext, + ).pop(_CollectionFormData(name: name, description: desc)); + }, + child: const Text("更新"), + ), + ), + ], + ), + ), + ); + }, + ); + + if (formData == null) return; + ref + .read(updateCollectionControllerProvider.notifier) + .updateCollection( + collectionId: collectionId, + name: formData.name, + description: formData.description, + ); + } + + Future _showDeleteConfirmDialog( + BuildContext context, + int collectionId, + WidgetRef ref, + ) async { + final confirmed = await showDialog( + context: context, + builder: (dialogContext) { + return AlertDialog( + title: const Text("删除收藏夹"), + content: const Text("确定要删除这个收藏夹吗?"), actions: [ TextButton( - onPressed: () => Navigator.of(dialogContext).pop(), + onPressed: () => Navigator.of(dialogContext).pop(false), child: const Text("取消"), ), - Consumer( - builder: (context, ref, _) { - final creating = ref - .watch(createCollectionControllerProvider) - .isLoading; - return TextButton( - onPressed: creating - ? null - : () { - ref - .read(createCollectionControllerProvider.notifier) - .createCollection( - name: nameController.text, - description: descController.text, - ); - }, - child: creating - ? const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : const Text("创建"), - ); - }, + ElevatedButton( + onPressed: () => Navigator.of(dialogContext).pop(true), + child: const Text("删除"), ), ], ); }, ); + + if (confirmed == true) { + ref + .read(deleteCollectionControllerProvider.notifier) + .deleteCollection(collectionId: collectionId); + } } } + +class _CollectionFormData { + final String name; + final String description; + + const _CollectionFormData({required this.name, required this.description}); +} diff --git a/lib/feature/download/ui/view/download_list_view.dart b/lib/feature/download/ui/view/download_list_view.dart index 312136c..c8ffed4 100644 --- a/lib/feature/download/ui/view/download_list_view.dart +++ b/lib/feature/download/ui/view/download_list_view.dart @@ -1,29 +1,18 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:provider/provider.dart'; +import 'package:tele_book/common/widget/error_widget.dart'; import 'package:tele_book/common/widget/task_item_widget.dart'; import 'package:tele_book/core/route/app_route.dart'; import 'package:tele_book/feature/download/enum/download_status.dart'; import 'package:tele_book/feature/download/model/bo/download_bo.dart'; -import 'package:tele_book/feature/download/store/download_store.dart'; +import 'package:tele_book/feature/download/service/download_service.dart'; +import 'package:tele_book/feature/download/ui/provider/download_provider.dart'; -class DownloadListView extends StatelessWidget { +class DownloadListView extends ConsumerWidget { const DownloadListView({super.key}); - @override - Widget build(BuildContext context) { - return const _DownloadListContent(); - } -} - -class _DownloadListContent extends StatefulWidget { - const _DownloadListContent(); - - @override - State<_DownloadListContent> createState() => _DownloadListContentState(); -} - -class _DownloadListContentState extends State<_DownloadListContent> { double _groupProgressPercent(List items) { if (items.isEmpty) return 0; final total = items.fold(0, (sum, item) => sum + item.progress); @@ -31,67 +20,101 @@ class _DownloadListContentState extends State<_DownloadListContent> { } @override - Widget build(BuildContext context) { - final store = context.watch(); + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(downloadTasksProvider); return Scaffold( appBar: AppBar( title: Text("下载任务"), - leading: BackButton( - onPressed: () { - context.go(AppRoute.main); - }, - ), + actions: [ + IconButton( + onPressed: () async { + await _showDownloadForm(context); + }, + icon: Icon(Icons.add), + ), + ], ), - body: store.tasks.isEmpty - ? Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.download, size: 64, color: Colors.grey), - SizedBox(height: 16), - Text( - "暂无下载任务", - style: TextStyle(fontSize: 16, color: Colors.grey), + + body: state.when( + loading: () => Center(child: CircularProgressIndicator()), + error: (e, st) => Center( + child: CustomErrorWidget(errorMessage: "加载下载任务失败", stackTrace: st), + ), + data: (tasks) { + return tasks.isEmpty + ? Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.download, size: 64, color: Colors.grey), + SizedBox(height: 16), + Text( + "暂无下载任务", + style: TextStyle(fontSize: 16, color: Colors.grey), + ), + ], ), - ], - ), - ) - : ListView.separated( - padding: EdgeInsets.all(16), - separatorBuilder: (context, index) => SizedBox(height: 16), - itemBuilder: (context, index) { - final item = store.tasks[index]; - final progressPercent = _groupProgressPercent( - item.downloadItemBoList, - ); - return TaskItemWidget( - title: item.downloadGroupBo.name, - coverUrl: item.downloadItemBoList.first.url, - status: item.downloadGroupBo.status.description, - progress: progressPercent, - onTap: () => - _showDownloadTaskList(context, item.downloadGroupBo.id), + ) + : ListView.separated( + padding: EdgeInsets.all(16), + separatorBuilder: (context, index) => SizedBox(height: 16), + itemBuilder: (context, index) { + final item = tasks[index]; + final progressPercent = _groupProgressPercent( + item.downloadItemBoList, + ); + return TaskItemWidget( + title: item.downloadGroupBo.name, + coverUrl: item.downloadItemBoList.first.url, + status: item.downloadGroupBo.status.description, + progress: progressPercent, + onTap: () => _showDownloadTaskList( + context, + item.downloadGroupBo.id, + ref, + ), + ); + }, + itemCount: tasks.length, ); - }, - itemCount: store.tasks.length, - ), + }, + ), ); } - void _showDownloadTaskList(BuildContext context, String groupId) { + void _showDownloadTaskList( + BuildContext context, + String groupId, + WidgetRef ref, + ) { showModalBottomSheet( context: context, builder: (context) { - return Consumer( - builder: (context, store, _) { - final tasks = store.tasks - .where((task) => task.downloadGroupBo.id == groupId) - .expand((task) => task.downloadItemBoList) + final asyncTasks = ref.watch(downloadTasksProvider); + + return asyncTasks.when( + loading: () => const Padding( + padding: EdgeInsets.all(16), + child: Center(child: CircularProgressIndicator()), + ), + error: (e, st) => Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Text( + "加载任务失败", + style: TextStyle(fontSize: 16, color: Colors.grey), + ), + ), + ), + data: (groups) { + final tasks = groups + .where((g) => g.downloadGroupBo.id == groupId) + .expand((g) => g.downloadItemBoList) .toList(); if (tasks.isEmpty) { - return Container( - padding: const EdgeInsets.all(16), + return const Padding( + padding: EdgeInsets.all(16), child: Center( child: Text( "处理中", @@ -101,7 +124,7 @@ class _DownloadListContentState extends State<_DownloadListContent> { ); } - return Container( + return Padding( padding: const EdgeInsets.all(16), child: Column( children: [ @@ -112,11 +135,10 @@ class _DownloadListContentState extends State<_DownloadListContent> { const SizedBox(height: 16), Expanded( child: ListView.separated( - separatorBuilder: (context, index) => - const SizedBox(height: 16), + itemCount: tasks.length, + separatorBuilder: (_, __) => const SizedBox(height: 16), itemBuilder: (context, index) { final task = tasks[index]; - return TaskItemWidget( title: task.url, coverUrl: task.url, @@ -125,14 +147,15 @@ class _DownloadListContentState extends State<_DownloadListContent> { trailing: task.status == DownloadStatus.failed ? IconButton( onPressed: () { - store.retryDownload(task.id); + ref + .read(downloadServiceProvider) + .retryTask(task.id); }, - icon: Icon(Icons.refresh), + icon: const Icon(Icons.refresh), ) : null, ); }, - itemCount: tasks.length, ), ), ], @@ -143,4 +166,75 @@ class _DownloadListContentState extends State<_DownloadListContent> { }, ); } + + Future _showDownloadForm(BuildContext context) async { + final TextEditingController urlController = TextEditingController(); + final url = await showModalBottomSheet( + isScrollControlled: true, + enableDrag: true, + context: context, + builder: (sheetContext) { + final bottomInset = MediaQuery.of(sheetContext).viewInsets.bottom; + return AnimatedPadding( + duration: const Duration(milliseconds: 100), + curve: Curves.easeOut, + padding: EdgeInsets.fromLTRB(16, 16, 16, 16 + bottomInset), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "添加下载任务", + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 16), + TextField( + controller: urlController, + decoration: InputDecoration( + labelText: "下载链接", + border: const OutlineInputBorder(), + prefixIcon: const Icon(Icons.link), + suffixIcon: IconButton( + onPressed: () { + Clipboard.getData(Clipboard.kTextPlain).then(( + clipData, + ) { + if (clipData != null && clipData.text != null) { + urlController.text = clipData.text!; + } + }); + }, + icon: const Icon(Icons.paste), + ), + ), + keyboardType: TextInputType.url, + inputFormatters: [ + FilteringTextInputFormatter.deny(RegExp(r'\s')), + ], + ), + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: () { + final inputUrl = urlController.text.trim(); + Navigator.of(sheetContext).pop(inputUrl); + }, + child: const Text("添加"), + ), + ), + ], + ), + ), + ); + }, + ); + + WidgetsBinding.instance.addPostFrameCallback((_) { + urlController.dispose(); + }); + + if (url == null || url.isEmpty || !context.mounted) return; + context.push(AppRoute.parseWeb, extra: url); + } } diff --git a/lib/feature/main/provider/main_provider.dart b/lib/feature/main/provider/main_provider.dart new file mode 100644 index 0000000..fa62f1f --- /dev/null +++ b/lib/feature/main/provider/main_provider.dart @@ -0,0 +1,24 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'main_provider.freezed.dart'; +part 'main_provider.g.dart'; + +@freezed +abstract class MainState with _$MainState { + const factory MainState({ + @Default(0) int currentIndex, + }) = _MainState; +} + +@riverpod +class Main extends _$Main { + @override + MainState build() { + return const MainState(); + } + + void updateCurrentIndex(int index) { + state = state.copyWith(currentIndex: index); + } +} \ No newline at end of file diff --git a/lib/feature/main/provider/main_provider.freezed.dart b/lib/feature/main/provider/main_provider.freezed.dart new file mode 100644 index 0000000..0a0565e --- /dev/null +++ b/lib/feature/main/provider/main_provider.freezed.dart @@ -0,0 +1,271 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'main_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$MainState { + + int get currentIndex; +/// Create a copy of MainState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$MainStateCopyWith get copyWith => _$MainStateCopyWithImpl(this as MainState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is MainState&&(identical(other.currentIndex, currentIndex) || other.currentIndex == currentIndex)); +} + + +@override +int get hashCode => Object.hash(runtimeType,currentIndex); + +@override +String toString() { + return 'MainState(currentIndex: $currentIndex)'; +} + + +} + +/// @nodoc +abstract mixin class $MainStateCopyWith<$Res> { + factory $MainStateCopyWith(MainState value, $Res Function(MainState) _then) = _$MainStateCopyWithImpl; +@useResult +$Res call({ + int currentIndex +}); + + + + +} +/// @nodoc +class _$MainStateCopyWithImpl<$Res> + implements $MainStateCopyWith<$Res> { + _$MainStateCopyWithImpl(this._self, this._then); + + final MainState _self; + final $Res Function(MainState) _then; + +/// Create a copy of MainState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? currentIndex = null,}) { + return _then(_self.copyWith( +currentIndex: null == currentIndex ? _self.currentIndex : currentIndex // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [MainState]. +extension MainStatePatterns on MainState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _MainState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _MainState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _MainState value) $default,){ +final _that = this; +switch (_that) { +case _MainState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _MainState value)? $default,){ +final _that = this; +switch (_that) { +case _MainState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( int currentIndex)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _MainState() when $default != null: +return $default(_that.currentIndex);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( int currentIndex) $default,) {final _that = this; +switch (_that) { +case _MainState(): +return $default(_that.currentIndex);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( int currentIndex)? $default,) {final _that = this; +switch (_that) { +case _MainState() when $default != null: +return $default(_that.currentIndex);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _MainState implements MainState { + const _MainState({this.currentIndex = 0}); + + +@override@JsonKey() final int currentIndex; + +/// Create a copy of MainState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$MainStateCopyWith<_MainState> get copyWith => __$MainStateCopyWithImpl<_MainState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _MainState&&(identical(other.currentIndex, currentIndex) || other.currentIndex == currentIndex)); +} + + +@override +int get hashCode => Object.hash(runtimeType,currentIndex); + +@override +String toString() { + return 'MainState(currentIndex: $currentIndex)'; +} + + +} + +/// @nodoc +abstract mixin class _$MainStateCopyWith<$Res> implements $MainStateCopyWith<$Res> { + factory _$MainStateCopyWith(_MainState value, $Res Function(_MainState) _then) = __$MainStateCopyWithImpl; +@override @useResult +$Res call({ + int currentIndex +}); + + + + +} +/// @nodoc +class __$MainStateCopyWithImpl<$Res> + implements _$MainStateCopyWith<$Res> { + __$MainStateCopyWithImpl(this._self, this._then); + + final _MainState _self; + final $Res Function(_MainState) _then; + +/// Create a copy of MainState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? currentIndex = null,}) { + return _then(_MainState( +currentIndex: null == currentIndex ? _self.currentIndex : currentIndex // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +// dart format on diff --git a/lib/feature/main/provider/main_provider.g.dart b/lib/feature/main/provider/main_provider.g.dart new file mode 100644 index 0000000..f113f8d --- /dev/null +++ b/lib/feature/main/provider/main_provider.g.dart @@ -0,0 +1,61 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'main_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(Main) +final mainProvider = MainProvider._(); + +final class MainProvider extends $NotifierProvider { + MainProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'mainProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$mainHash(); + + @$internal + @override + Main create() => Main(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(MainState value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$mainHash() => r'd66107f193872b3658dcb2e8d98aa82eead28fca'; + +abstract class _$Main extends $Notifier { + MainState build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + MainState, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/lib/feature/main/view/main_view.dart b/lib/feature/main/view/main_view.dart index bd465ae..c461193 100644 --- a/lib/feature/main/view/main_view.dart +++ b/lib/feature/main/view/main_view.dart @@ -1,37 +1,28 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:tele_book/feature/book/ui/view/book_list_view.dart'; import 'package:tele_book/feature/collection/ui/view/collection_view.dart'; -import 'package:tele_book/feature/main/viewmodel/main_viewmodel.dart'; +import 'package:tele_book/feature/download/ui/view/download_list_view.dart'; +import 'package:tele_book/feature/main/provider/main_provider.dart'; -class MainView extends StatelessWidget { +class MainView extends ConsumerWidget { const MainView({super.key}); @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (context) => MainViewmodel(), - child: const _MainContent(), - ); - } -} - -class _MainContent extends StatelessWidget { - const _MainContent(); - - @override - Widget build(BuildContext context) { - final vm = context.watch(); + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(mainProvider); + final notifier = ref.read(mainProvider.notifier); return Scaffold( body: IndexedStack( - index: vm.currentIndex, - children: [BookListView(), CollectionView()], + index: state.currentIndex, + children: [BookListView(), DownloadListView(), CollectionView()], ), bottomNavigationBar: BottomNavigationBar( - currentIndex: vm.currentIndex, - onTap: (index) => vm.onTabChange(index), + currentIndex: state.currentIndex, + onTap: (index) => notifier.updateCurrentIndex(index), items: const [ BottomNavigationBarItem(icon: Icon(Icons.book), label: "书籍"), + BottomNavigationBarItem(icon: Icon(Icons.download), label: "下载"), BottomNavigationBarItem(icon: Icon(Icons.collections), label: "收藏夹"), ], ), diff --git a/lib/feature/main/viewmodel/main_viewmodel.dart b/lib/feature/main/viewmodel/main_viewmodel.dart deleted file mode 100644 index 1dc5753..0000000 --- a/lib/feature/main/viewmodel/main_viewmodel.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter/material.dart'; - -class MainViewmodel extends ChangeNotifier { - int currentIndex = 0; - - void onTabChange(int index) { - if (currentIndex != index) { - currentIndex = index; - notifyListeners(); - } - } -} diff --git a/lib/feature/parse/ui/view/parse_web_view.dart b/lib/feature/parse/ui/view/parse_web_view.dart index ab4340b..1c86d74 100644 --- a/lib/feature/parse/ui/view/parse_web_view.dart +++ b/lib/feature/parse/ui/view/parse_web_view.dart @@ -1,9 +1,12 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:tele_book/common/widget/network_image_widget.dart'; import 'package:tele_book/core/route/app_route.dart'; +import 'package:tele_book/feature/main/provider/main_provider.dart'; import 'package:tele_book/feature/parse/ui/provider/parse_web_provider.dart'; class ParseWebView extends ConsumerWidget { @@ -24,10 +27,17 @@ class ParseWebView extends ConsumerWidget { child: const Icon(Icons.photo), ), onPressed: () { - showModalBottomSheet( + showModalBottomSheet( context: context, - builder: (context) => _buildBottomSheet(context, state, notifier), - ); + builder: (sheetContext) => + _buildBottomSheet(sheetContext: sheetContext, state: state), + ).then((confirmed) { + if (confirmed != true || !context.mounted) return; + + ref.read(mainProvider.notifier).updateCurrentIndex(1); + context.pop(); + unawaited(notifier.startDownload()); + }); }, ), body: Column( @@ -52,17 +62,16 @@ class ParseWebView extends ConsumerWidget { ); } - Widget _buildBottomSheet( - BuildContext context, - ParseWebState state, - ParseWeb notifier, - ) { + Widget _buildBottomSheet({ + required BuildContext sheetContext, + required ParseWebState state, + }) { return Container( padding: EdgeInsets.all(16), - height: MediaQuery.of(context).size.height * 0.7, + height: MediaQuery.of(sheetContext).size.height * 0.7, child: Column( children: [ - Text("解析到的图片链接", style: Theme.of(context).textTheme.titleMedium), + Text("解析到的图片链接", style: Theme.of(sheetContext).textTheme.titleMedium), SizedBox(height: 16), Expanded( child: ListView.builder( @@ -81,7 +90,7 @@ class ParseWebView extends ConsumerWidget { ), onTap: () { Navigator.push( - context, + sheetContext, MaterialPageRoute( builder: (context) => _buildImagePreview(url), ), @@ -98,8 +107,8 @@ class ParseWebView extends ConsumerWidget { width: double.infinity, child: FilledButton( onPressed: () { - notifier.startDownload(); - context.push(AppRoute.download); + if (state.urls.isEmpty) return; + Navigator.of(sheetContext).pop(true); }, child: Text("下载"), ), From 2a47b528d1c1f7483006d23c1f4122d6607a890f Mon Sep 17 00:00:00 2001 From: warren Date: Wed, 27 May 2026 17:41:21 +0800 Subject: [PATCH 11/16] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B9=A6=E7=B1=8D?= =?UTF-8?q?=E6=B5=81=E7=9A=84=E5=93=8D=E5=BA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .metadata | 45 ++ android/.gitignore | 3 +- android/app/build.gradle | 115 --- android/app/build.gradle.kts | 45 ++ android/app/src/main/AndroidManifest.xml | 43 +- .../com/dorkytiger/tele_book/MainActivity.kt | 42 -- .../com/example/tele_book/MainActivity.kt | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 2387 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2387 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 2387 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 2387 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 2387 -> 1443 bytes android/app/src/profile/AndroidManifest.xml | 5 - android/build.gradle | 22 - android/build.gradle.kts | 24 + .../reports/problems/problems-report.html | 659 ------------------ android/gradle.properties | 9 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/key.properties.example | 4 - android/settings.gradle | 30 - android/settings.gradle.kts | 26 + .../book/ui/provider/book_provider.dart | 343 +++++---- lib/feature/book/ui/view/book_list_view.dart | 1 - linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + pubspec.yaml | 2 +- res/change-log.md | 10 + windows/runner/resources/app_icon.ico | Bin 33772 -> 1806 bytes 28 files changed, 370 insertions(+), 1070 deletions(-) create mode 100644 .metadata delete mode 100644 android/app/build.gradle create mode 100644 android/app/build.gradle.kts delete mode 100644 android/app/src/main/kotlin/com/dorkytiger/tele_book/MainActivity.kt create mode 100644 android/app/src/main/kotlin/com/example/tele_book/MainActivity.kt delete mode 100644 android/build.gradle create mode 100644 android/build.gradle.kts delete mode 100644 android/build/reports/problems/problems-report.html delete mode 100644 android/key.properties.example delete mode 100644 android/settings.gradle create mode 100644 android/settings.gradle.kts diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..768732a --- /dev/null +++ b/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "559ffa3f75e7402d65a8def9c28389a9b2e6fe42" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + - platform: android + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + - platform: ios + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + - platform: linux + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + - platform: macos + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + - platform: web + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + - platform: windows + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/android/.gitignore b/android/.gitignore index 6f56801..be3943c 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -5,9 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle deleted file mode 100644 index adacd45..0000000 --- a/android/app/build.gradle +++ /dev/null @@ -1,115 +0,0 @@ -plugins { - id "com.android.application" - id "kotlin-android" - id "dev.flutter.flutter-gradle-plugin" -} - -def localProperties = new java.util.Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def keystoreProperties = new java.util.Properties() -def keystorePropertiesFile = rootProject.file('key.properties') -if (keystorePropertiesFile.exists()) { - keystoreProperties.load(new java.io.FileInputStream(keystorePropertiesFile)) -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -def updatePubspecVersion() { - def versionPropsFile = file('../../version.properties') - if (versionPropsFile.canRead()) { - def versionProps = new java.util.Properties() - versionProps.load(new java.io.FileInputStream(versionPropsFile)) - - def versionName = versionProps['VERSION_NAME'] - def versionCode = versionProps['VERSION_CODE'] - - def pubspecFile = file('../../pubspec.yaml') - def pubspecContent = pubspecFile.text - def newContent = pubspecContent.replaceAll( - /version:\s*[\d.]+\+\d+/, - "version: ${versionName}+${versionCode}" - ) - pubspecFile.text = newContent - } -} - -preBuild.doFirst { - updatePubspecVersion() -} - -android { - namespace "com.dorkytiger.tele_book" - compileSdkVersion flutter.compileSdkVersion - ndkVersion "28.2.13676358" - - compileOptions { - sourceCompatibility JavaVersion.VERSION_21 - targetCompatibility JavaVersion.VERSION_21 - } - - kotlinOptions { - jvmTarget = '21' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.dorkytiger.tele_book" - // You can update the following values to match your application needs. - // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - signingConfigs { - release { - if (keystorePropertiesFile.exists()) { - keyAlias keystoreProperties['keyAlias'] - keyPassword keystoreProperties['keyPassword'] - storeFile file(keystoreProperties['storeFile']) - storePassword keystoreProperties['storePassword'] - } - } - } - - buildTypes { - release { - // Only assign the release signingConfig when a release keystore is present. - // This avoids Gradle trying to use an empty 'release' signingConfig when - // key.properties is not provided (which causes the missing storeFile error). - if (keystorePropertiesFile.exists()) { - signingConfig signingConfigs.release - } - } - debug { - // Use the default debug signing (do not force the release signingConfig). - // When key.properties is absent, this prevents the missing storeFile error - // during debug builds. - } - } -} - -flutter { - source '../..' -} - -dependencies {} diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..93d9c29 --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + id("com.android.application") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.tele_book" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.tele_book" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + +flutter { + source = "../.." +} diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 2fa2f43..ed13ab7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + @@ -13,30 +12,18 @@ - - - - - - - - - - - - - - + + + + + + + diff --git a/android/app/src/main/kotlin/com/dorkytiger/tele_book/MainActivity.kt b/android/app/src/main/kotlin/com/dorkytiger/tele_book/MainActivity.kt deleted file mode 100644 index eb0eba8..0000000 --- a/android/app/src/main/kotlin/com/dorkytiger/tele_book/MainActivity.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.dorkytiger.tele_book - -import android.app.NotificationChannel -import android.app.NotificationManager -import android.os.Build -import android.os.Bundle -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - // 创建下载通知渠道 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager - - // 创建下载通知渠道 - val downloadChannel = NotificationChannel( - "background_downloader", - "下载管理", - NotificationManager.IMPORTANCE_LOW - ).apply { - description = "显示文件下载进度和状态" - setShowBadge(false) - enableVibration(false) - } - notificationManager.createNotificationChannel(downloadChannel) - - // 创建前台服务通知渠道 - val foregroundChannel = NotificationChannel( - "background_downloader_foreground", - "后台下载服务", - NotificationManager.IMPORTANCE_LOW - ).apply { - description = "保持后台下载服务运行" - setShowBadge(false) - enableVibration(false) - } - notificationManager.createNotificationChannel(foregroundChannel) - } - } -} diff --git a/android/app/src/main/kotlin/com/example/tele_book/MainActivity.kt b/android/app/src/main/kotlin/com/example/tele_book/MainActivity.kt new file mode 100644 index 0000000..06da5b9 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/tele_book/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.tele_book + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index 0e22fd126a244c5c9ae9b4741681df5196d1c62e..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 100644 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 2387 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 literal 2387 zcmeHJ`!|#c7#`z`8Dc+_4dF{JmD@I0$tB-3leskRw_Fm1U5ZvrZiS&TY-Vz;Ibn5B zDkDR>jG9_?8VX@gLoPLp%eb3dmY8;&{U7#+J^k>$?=R0e&pFR|-sgSk?yfE>O4>>Y z1VV*Kz=YE|U$pbDl0(1iNFW7=b{m5OJ_~;_(@lM~sf`HhgQFa=o9r z-Lg}Z<*qyHx?lU1I9Uh2KF~gI)P=pz(e<&Q5t=ORVv)uxs6Flw!|h8)sl2L9$KDC6 zZ~l1ohvv6adUds9EaP|M^!9@# zi-Hl4r@5btqA#bgm)DzuN^TA@rY}L32pn^g8x4+r@%K1_#!PEBP}T}PS$-i>DH4F0 z>HWXdy-v0w->&5n8z6hT=hhA+M@2apJf#F*E^CR2OG+!!kK+AtjM)&bCCcu#D`RWH zVCAy3wkGx?kp(1utq2lvE;Qz>Lns<<$n8q9<9*H5#xXJrunR1%%V79UL)fsL-CGFQ zRvzzc=?8}>A7xC}Z2bjSQRL9L#t~3BDNT&tyR~z6 z3L4VAEk7vFzyGm5^Oewrd7yYO9?N{Z2Qsy{cF<7!aOh1l#thMHYl@n%QrWP%s3e0NKQ4%Q*J79@6~NG;5)KwIW;Vm zaiWyGmYP;ncl$&pG~+F}hj;olMEa>s54pp>rDDm0*1X|v5!ctYFlyDPwR4Jf{D4;dH~G&6vtUmQ&m=!eJ!W z2!+Ym+=iw%oM0_uG_BDj1A0lDRviiXxVtYf=}~~_?fwn0vQ&=-D0QD5axc(Huz02D^jTv07pmyzrX18NMac6OhS+sg26$>r4?qU1w_6tih)HN%O-*OJAfAF{?(mb6FDp=oyrYLz*ERoM0)nrN;#K7Nl8G#Qw>q#QVvSTICl&m~Od z_q3O%iMT?HeZfdtTqiws@wx@c)h2eqge(smAMVGG+~zfIs3|Ud$as{d)f_k=S$@8h zQk8%*2))HGLeL%?cMhn_0oQ)=5RnjYP(fr29YG^p-xFt)4zXLU9^nwM;n8hR)SW?r z=tiJHI;3u#6NHAcqb2Xm)to`J2A&OouWXXWicwH@7Tv%Pi5g}(oFfAu?oZq`Q#s%_ zO4$=5WTL<=dYi%D?8If!<8Qfag?i1Bak+Vi$e0p93R{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 2387 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 literal 2387 zcmeHJ`!|#c7#`z`8Dc+_4dF{JmD@I0$tB-3leskRw_Fm1U5ZvrZiS&TY-Vz;Ibn5B zDkDR>jG9_?8VX@gLoPLp%eb3dmY8;&{U7#+J^k>$?=R0e&pFR|-sgSk?yfE>O4>>Y z1VV*Kz=YE|U$pbDl0(1iNFW7=b{m5OJ_~;_(@lM~sf`HhgQFa=o9r z-Lg}Z<*qyHx?lU1I9Uh2KF~gI)P=pz(e<&Q5t=ORVv)uxs6Flw!|h8)sl2L9$KDC6 zZ~l1ohvv6adUds9EaP|M^!9@# zi-Hl4r@5btqA#bgm)DzuN^TA@rY}L32pn^g8x4+r@%K1_#!PEBP}T}PS$-i>DH4F0 z>HWXdy-v0w->&5n8z6hT=hhA+M@2apJf#F*E^CR2OG+!!kK+AtjM)&bCCcu#D`RWH zVCAy3wkGx?kp(1utq2lvE;Qz>Lns<<$n8q9<9*H5#xXJrunR1%%V79UL)fsL-CGFQ zRvzzc=?8}>A7xC}Z2bjSQRL9L#t~3BDNT&tyR~z6 z3L4VAEk7vFzyGm5^Oewrd7yYO9?N{Z2Qsy{cF<7!aOh1l#thMHYl@n%QrWP%s3e0NKQ4%Q*J79@6~NG;5)KwIW;Vm zaiWyGmYP;ncl$&pG~+F}hj;olMEa>s54pp>rDDm0*1X|v5!ctYFlyDPwR4Jf{D4;dH~G&6vtUmQ&m=!eJ!W z2!+Ym+=iw%oM0_uG_BDj1A0lDRviiXxVtYf=}~~_?fwn0vQ&=-D0QD5axc(Huz02D^jTv07pmyzrX18NMac6OhS+sg26$>r4?qU1w_6tih)HN%O-*OJAfAF{?(mb6FDp=oyrYLz*ERoM0)nrN;#K7Nl8G#Qw>q#QVvSTICl&m~Od z_q3O%iMT?HeZfdtTqiws@wx@c)h2eqge(smAMVGG+~zfIs3|Ud$as{d)f_k=S$@8h zQk8%*2))HGLeL%?cMhn_0oQ)=5RnjYP(fr29YG^p-xFt)4zXLU9^nwM;n8hR)SW?r z=tiJHI;3u#6NHAcqb2Xm)to`J2A&OouWXXWicwH@7Tv%Pi5g}(oFfAu?oZq`Q#s%_ zO4$=5WTL<=dYi%D?8If!<8Qfag?i1Bak+Vi$e0p93R - - - - - diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index 930a713..0000000 --- a/android/build.gradle +++ /dev/null @@ -1,22 +0,0 @@ -buildscript { - ext.kotlin_version = '2.3.0' - repositories { - google() - mavenCentral() - - } - -} - - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html deleted file mode 100644 index 6589a90..0000000 --- a/android/build/reports/problems/problems-report.html +++ /dev/null @@ -1,659 +0,0 @@ - - - - - - - - - - - - - Gradle Configuration Cache - - - -
- -
- Loading... -
- - - - - - diff --git a/android/gradle.properties b/android/gradle.properties index f2122b2..e96108c 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,7 +1,6 @@ -org.gradle.jvmargs=-Xmx4G +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true -android.enableJetifier=true -# This builtInKotlin flag was added automatically by Flutter migrator -android.builtInKotlin=false -# This newDsl flag was added automatically by Flutter migrator +# This newDsl flag was added by the Flutter template android.newDsl=false +# This builtInKotlin flag was added by the Flutter template +android.builtInKotlin=false diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 6388f31..2d428bf 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip diff --git a/android/key.properties.example b/android/key.properties.example deleted file mode 100644 index c9d3d0d..0000000 --- a/android/key.properties.example +++ /dev/null @@ -1,4 +0,0 @@ -storePassword=1234abcD -keyPassword=1234abcD -keyAlias=dorkytiger -storeFile=upload-keystore.jks \ No newline at end of file diff --git a/android/settings.gradle b/android/settings.gradle deleted file mode 100644 index bd05419..0000000 --- a/android/settings.gradle +++ /dev/null @@ -1,30 +0,0 @@ -pluginManagement { - def flutterSdkPath = { - def properties = new Properties() - file("local.properties").withInputStream { properties.load(it) } - def flutterSdkPath = properties.getProperty("flutter.sdk") - assert flutterSdkPath != null, "flutter.sdk not set in local.properties" - return flutterSdkPath - } - settings.ext.flutterSdkPath = flutterSdkPath() - - includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } - - plugins { - id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false - } -} - -plugins { - id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id("com.android.application") version "8.9.1" apply false - id "org.jetbrains.kotlin.android" version "2.3.0" apply false -} - -include ":app" diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..c21f0c5 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "9.0.1" apply false + id("org.jetbrains.kotlin.android") version "2.3.20" apply false +} + +include(":app") diff --git a/lib/feature/book/ui/provider/book_provider.dart b/lib/feature/book/ui/provider/book_provider.dart index b51eec0..2313462 100644 --- a/lib/feature/book/ui/provider/book_provider.dart +++ b/lib/feature/book/ui/provider/book_provider.dart @@ -13,219 +13,252 @@ final booksProvider = StreamProvider.autoDispose>((ref){ return bookRepository.watchAllBooks(); }); -@riverpod -class BookList extends _$BookList { - final int _pageSize = 20; +class _BookListQueryState { + final String name; + final BookSort? sort; + final int page; + final int pageSize; + + const _BookListQueryState({ + this.name = '', + this.sort, + this.page = 1, + this.pageSize = 20, + }); + + _BookListQueryState copyWith({ + String? name, + BookSort? sort, + int? page, + int? pageSize, + }) { + return _BookListQueryState( + name: name ?? this.name, + sort: sort ?? this.sort, + page: page ?? this.page, + pageSize: pageSize ?? this.pageSize, + ); + } +} + +class _BookListUiState { + final bool isSelectionMode; + final Set selectedBookIds; + final BookLayout layout; + + const _BookListUiState({ + this.isSelectionMode = false, + this.selectedBookIds = const {}, + this.layout = BookLayout.list, + }); + + _BookListUiState copyWith({ + bool? isSelectionMode, + Set? selectedBookIds, + BookLayout? layout, + }) { + return _BookListUiState( + isSelectionMode: isSelectionMode ?? this.isSelectionMode, + selectedBookIds: selectedBookIds ?? this.selectedBookIds, + layout: layout ?? this.layout, + ); + } +} +class _BookListQueryNotifier extends Notifier<_BookListQueryState> { @override - Future build() async { - return _fetchPage( - name: '', - sort: null, - isFirstPage: true, - previousBooks: [], + _BookListQueryState build() => const _BookListQueryState(); + + void updateSearch(String name) { + state = state.copyWith(name: name, page: 1); + } + + void updateSort(BookSort? sort) { + state = state.copyWith(sort: sort, page: 1); + } + + void loadNextPage() { + state = state.copyWith(page: state.page + 1); + } +} + +final bookListQueryProvider = + NotifierProvider<_BookListQueryNotifier, _BookListQueryState>( + _BookListQueryNotifier.new, ); + +class _BookListUiNotifier extends Notifier<_BookListUiState> { + @override + _BookListUiState build() => const _BookListUiState(); + + void enterSelectionMode(BookTableData firstBook) { + state = state.copyWith(isSelectionMode: true, selectedBookIds: {firstBook.id}); + } + + void exitSelectionMode() { + state = state.copyWith(isSelectionMode: false, selectedBookIds: {}); + } + + void toggleSelection(int bookId) { + final updatedIds = Set.from(state.selectedBookIds); + if (updatedIds.contains(bookId)) { + updatedIds.remove(bookId); + if (updatedIds.isEmpty) { + state = state.copyWith(isSelectionMode: false, selectedBookIds: {}); + return; + } + } else { + updatedIds.add(bookId); + } + + state = state.copyWith(selectedBookIds: updatedIds); } - Future _fetchPage({ - required String name, - required BookSort? sort, - required bool isFirstPage, - required List previousBooks, - }) async { - final bookRepository = ref.watch(bookRepositoryProvider); - - final lastCreatedAt = isFirstPage - ? null - : previousBooks.last.book.createdAt; - - final newBooks = await bookRepository.getPagedBooks( - page: isFirstPage ? 1 : null, - pageSize: _pageSize, - name: name, - sort: sort, - lastCreatedAt: lastCreatedAt, + void selectAll(Iterable ids) { + state = state.copyWith( + isSelectionMode: true, + selectedBookIds: ids.toSet(), ); + } - final bookVos = newBooks - .map( - (book) => BookListItemVo( - book: book, - coverImagePath: GlobalConfig.resolveBookPath( - book.localSubPaths.first, - ), - ), - ) - .toList(); + void toggleLayout() { + final nextLayout = + state.layout == BookLayout.list ? BookLayout.grid : BookLayout.list; + state = state.copyWith(layout: nextLayout); + } + + void clearSelectedIds(Iterable ids) { + final updated = Set.from(state.selectedBookIds) + ..removeAll(ids); + if (updated.isEmpty) { + state = state.copyWith(isSelectionMode: false, selectedBookIds: {}); + return; + } + state = state.copyWith(selectedBookIds: updated); + } +} + +final bookListUiProvider = NotifierProvider<_BookListUiNotifier, _BookListUiState>( + _BookListUiNotifier.new, +); + +@riverpod +class BookList extends _$BookList { + @override + Future build() async { + final booksAsync = ref.watch(booksProvider); + final query = ref.watch(bookListQueryProvider); + final ui = ref.watch(bookListUiProvider); + + if (booksAsync.hasError) { + throw booksAsync.error!; + } + final books = + (booksAsync.value ?? await ref.watch(booksProvider.future)) ?? + const []; + final keyword = query.name.toLowerCase(); + + final filtered = books.where((book) { + if (keyword.isEmpty) return true; + return book.name.toLowerCase().contains(keyword); + }).toList(); + + if (query.sort != null) { + filtered.sort((a, b) { + final cmp = switch (query.sort!.type) { + BookSortType.title => + a.name.toLowerCase().compareTo(b.name.toLowerCase()), + BookSortType.lastCreatedAt => a.createdAt.compareTo(b.createdAt), + }; + return query.sort!.order == BookSortOrder.asc ? cmp : -cmp; + }); + } else { + filtered.sort((a, b) => b.createdAt.compareTo(a.createdAt)); + } + + final visibleCount = query.page * query.pageSize; + final visibleBooks = filtered.take(visibleCount).toList(); + + final bookVos = visibleBooks.map((book) { + final coverPath = book.localSubPaths.isNotEmpty + ? GlobalConfig.resolveBookPath(book.localSubPaths.first) + : ''; + return BookListItemVo(book: book, coverImagePath: coverPath); + }).toList(); + + final visibleIds = visibleBooks.map((e) => e.id).toSet(); + final selectedIds = ui.selectedBookIds.where(visibleIds.contains).toSet(); return BookListState( - bookVos: isFirstPage ? bookVos : [...previousBooks, ...bookVos], - hasMore: newBooks.length == _pageSize, - name: name, - sort: sort, + bookVos: bookVos, + hasMore: filtered.length > visibleBooks.length, + name: query.name, + sort: query.sort, isLoadingMore: false, + isSelectionMode: ui.isSelectionMode, + selectedBookIds: selectedIds, + layout: ui.layout, ); } Future loadNextPage() async { if (!state.hasValue || state.isLoading) return; final current = state.requireValue; - if (!current.hasMore || current.isLoadingMore) return; - state = AsyncData(current.copyWith(isLoadingMore: true)); - - try { - final nextState = await _fetchPage( - name: current.name, - sort: current.sort, - isFirstPage: false, - previousBooks: current.bookVos, - ); - state = AsyncData(nextState); - } catch (e, st) { - state = AsyncError(e, st); - } + ref.read(bookListQueryProvider.notifier).loadNextPage(); } Future updateSearch(String name) async { - if (!state.hasValue) return; - - state = const AsyncLoading(); - try { - final nextState = await _fetchPage( - name: name, - sort: state.requireValue.sort, - isFirstPage: true, - previousBooks: [], - ); - state = AsyncData(nextState); - } catch (e, st) { - state = AsyncError(e, st); - } + ref.read(bookListQueryProvider.notifier).updateSearch(name); } Future updateSort(BookSort? sort) async { - if (!state.hasValue) return; - state = const AsyncLoading(); - try { - final nextState = await _fetchPage( - name: state.requireValue.name, - sort: sort, - isFirstPage: true, - previousBooks: [], - ); - state = AsyncData(nextState); - } catch (e, st) { - state = AsyncError(e, st); - } + ref.read(bookListQueryProvider.notifier).updateSort(sort); } // ➡️ 进入多选模式并勾选第一本书 void enterSelectionMode(BookTableData firstBook) { - if (!state.hasValue) return; - state = AsyncData(state.requireValue.copyWith( - isSelectionMode: true, - selectedBookIds: {firstBook.id}, - )); + ref.read(bookListUiProvider.notifier).enterSelectionMode(firstBook); } // ➡️ 退出多选模式 void exitSelectionMode() { - if (!state.hasValue) return; - state = AsyncData(state.requireValue.copyWith( - isSelectionMode: false, - selectedBookIds: {}, // 清空勾选 - )); + ref.read(bookListUiProvider.notifier).exitSelectionMode(); } // ➡️ 切换某本书的选中状态 void toggleSelection(int bookId) { - if (!state.hasValue) return; - final updatedIds = Set.from(state.requireValue.selectedBookIds); - if (updatedIds.contains(bookId)) { - updatedIds.remove(bookId); - // 如果全取消了,可以选择自动退出多选模式 - if (updatedIds.isEmpty) { - exitSelectionMode(); - return; - } - } else { - updatedIds.add(bookId); - } - state = AsyncData(state.requireValue.copyWith(selectedBookIds: updatedIds)); + ref.read(bookListUiProvider.notifier).toggleSelection(bookId); } // ➡️ 全选 void selectAll() { - if (!state.hasValue) return; - final allIds = state.requireValue.bookVos.map((vo) => vo.book.id).toSet(); - state = AsyncData(state.requireValue.copyWith(selectedBookIds: allIds)); + final ids = + state.asData?.value.bookVos.map((vo) => vo.book.id) ?? const []; + ref.read(bookListUiProvider.notifier).selectAll(ids); } // ➡️ 切换布局 void toggleLayout() { - if (!state.hasValue) return; - final nextLayout = state.requireValue.layout == BookLayout.list - ? BookLayout.grid - : BookLayout.list; - state = AsyncData(state.requireValue.copyWith(layout: nextLayout)); + ref.read(bookListUiProvider.notifier).toggleLayout(); } // ➡️ 删除单本书并刷新列表 Future deleteBook(int bookId) async { - if (!state.hasValue) return; - final current = state.requireValue; final bookRepository = ref.read(bookRepositoryProvider); - - // 乐观更新:先从 UI 中移除 - state = AsyncData(current.copyWith( - bookVos: current.bookVos.where((v) => v.book.id != bookId).toList(), - )); - await bookRepository.deleteBook(bookId); - - // 重新拉取第一页确保数据一致 - try { - final nextState = await _fetchPage( - name: current.name, - sort: current.sort, - isFirstPage: true, - previousBooks: [], - ); - state = AsyncData(nextState.copyWith(layout: current.layout)); - } catch (e, st) { - state = AsyncError(e, st); - } + ref.read(bookListUiProvider.notifier).clearSelectedIds([bookId]); } // ➡️ 批量删除选中书籍并刷新列表 Future deleteSelected() async { if (!state.hasValue) return; - final current = state.requireValue; - final selectedIds = Set.from(current.selectedBookIds); + final selectedIds = Set.from(state.requireValue.selectedBookIds); final bookRepository = ref.read(bookRepositoryProvider); - // 乐观更新 - state = AsyncData(current.copyWith( - bookVos: current.bookVos.where((v) => !selectedIds.contains(v.book.id)).toList(), - isSelectionMode: false, - selectedBookIds: {}, - )); - for (final id in selectedIds) { await bookRepository.deleteBook(id); } - - try { - final nextState = await _fetchPage( - name: current.name, - sort: current.sort, - isFirstPage: true, - previousBooks: [], - ); - state = AsyncData(nextState.copyWith(layout: current.layout)); - } catch (e, st) { - state = AsyncError(e, st); - } + ref.read(bookListUiProvider.notifier).exitSelectionMode(); } } diff --git a/lib/feature/book/ui/view/book_list_view.dart b/lib/feature/book/ui/view/book_list_view.dart index bf3caf0..0d81330 100644 --- a/lib/feature/book/ui/view/book_list_view.dart +++ b/lib/feature/book/ui/view/book_list_view.dart @@ -10,7 +10,6 @@ import 'package:tele_book/feature/book/enum/book_menu_type.dart'; import 'package:tele_book/feature/book/enum/book_sort.dart'; import 'package:tele_book/feature/book/model/state/book_list_state.dart'; import 'package:tele_book/feature/book/ui/provider/book_provider.dart'; -import 'package:tele_book/feature/download/ui/provider/download_provider.dart'; import 'package:tele_book/feature/export/ui/view/export_batch_form_view.dart'; import 'package:tele_book/feature/export/ui/view/export_single_form_view.dart'; diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 4c0025f..08fbce7 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,10 +6,14 @@ #include "generated_plugin_registrant.h" +#include #include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_inappwebview_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterInappwebviewLinuxPlugin"); + flutter_inappwebview_linux_plugin_register_with_registrar(flutter_inappwebview_linux_registrar); g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 75d875c..f0a73fa 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_inappwebview_linux sqlite3_flutter_libs url_launcher_linux ) diff --git a/pubspec.yaml b/pubspec.yaml index a7bdb69..ced3cc2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: http: ^1.3.0 wakelock_plus: ^1.2.10 webview_flutter: ^4.13.0 - flutter_inappwebview: ^6.1.5 + flutter_inappwebview: ^6.2.0-beta.3 open_filex: ^4.4.0 share_plus: ^12.0.1 dk_util: ^1.0.0 diff --git a/res/change-log.md b/res/change-log.md index 60db9c7..e442a28 100644 --- a/res/change-log.md +++ b/res/change-log.md @@ -34,3 +34,13 @@ - **优化** 内存泄漏防护 +# 2026.05.27 + +## 3.1.5 - riverpod 重构 + +### 🛠️ 主要更新 +- **重构** 使用 Riverpod 替代 Provider 进行状态管理 +- **优化** 代码结构,提升可维护性和扩展性 +- **增强** 组件解耦,提升性能和响应速度 +- **修复** 相关状态管理的潜在bug,提升稳定性 + diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico index c04e20caf6370ebb9253ad831cc31de4a9c965f6..32de11cf7bc906ad909c2095394de9c87936dc7c 100644 GIT binary patch literal 1806 zcmV+p2l4m-0096205C8B0000W0Qd#~02TlM0EtjeM-2)Z3IG5A4M|8uQUCw|FaQ7m zFbDzw007uvZqNV#2ER!}K~#90?U!v#Q&}2-Pul_8k}0?(Gef8`Ks1U4OIs|FVK8Mx zM;2|2EtHm6T6W~4+F*i$L8JZvcEK3af&~>}Yg<4|w}wweMusr0owjycoIw<=iB73a z5GduF-d%fl38Nbd1KHh4cz!g^$vN+N&N=se?>(P@|1&=MUkiN5x{x>4g}kvYxTsZxZQ4@hx6yppFVxspf_Y>WN0)R06=8qPMPdhXJ=68WcZzx`(a ze)ZqfE|+V`*_ISSqENXxIm5%lPN$R2X4`Bw0DwRs7#$r|tJNbTBZY;9N~N-~vElgf z6O)sZH8nL{F8ATXhww6zl9Gagf%HwA?Y=&~$;rtUizOf+AU{7pEiJ9P zySt&G0eD8j0TUAwX*60(OUvNk;BOHE0K9$sHak1}#fujUEgFrcv$GS8MrUOm(`vQ( z`T0#vP1RM^Xf)blu_PoUNF!b&(Dm!r zsZ^?5E*FVJO-(AbTK(?bI|zc@Za0}ss;H@si>$ZDJi*o_wJ!XhX8m#*@g`pPj89^c&DTvu0j>eQ*s%*?K?E}2ZW zXklw4gaC-ef4aNBfAF9Shr{LN<>~eM?Cfm)kNWtpzJe2QqI9zRQt)xm~Fc{+E;$~)MVq;?g1X-=txVX5Xp&=|5%j5Af zGc#vrXS=(*`F#GG2>}RlLy*6}e{F5;z_Wp_@4hQ8F825LKXKxCpT$yCbXF>r{;MYf zG3kNHWR{ngx3sh{7!14J9upHoB9UrpYJ!4-+-~=(gghCBn3|lVQmLV#p4G& zSFir^%bVM`@1&%pUc7kmz<~oi9uF?VzP`RFRxzrtD zDIu53wQ19)&6_uq$z%$JLZ{P-M54`R)9dv*oi05+oe)7NEGYQwv(HH+G62BG$H#8B z|D~+#szeeQ5g`x=qNAhnc)Z18(dl&eI_{MemsqXVrK^DVEPLglsi_G*#E_7XsHiB| zFFZVaVqyY=!61=HxZX@pPZt&x^!N80jmG=;@0XUA4h{~kWgYqe2!dXCP%4#h3xPmT zsZ?QMVE}+PZ{Bb?oPU1*uPElKgg&OPuW#G7Z98`CK%tO*eNSd*XIB|;4X)Dj^Ydo2 wdG&E%e$RLN{|@Uy-dGp%#=4L<)`h(B4|4Af$83DsBLDyZ07*qoM6N<$f~aP1NB{r; literal 33772 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 From 73b97f3d634554d6edb746870fc6b14c34d8512d Mon Sep 17 00:00:00 2001 From: warren Date: Thu, 28 May 2026 15:33:21 +0800 Subject: [PATCH 12/16] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=83=A8=E5=88=86provi?= =?UTF-8?q?der=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/dart.yml | 8 +- .../main/res/mipmap-hdpi/launcher_icon.png | Bin 3210 -> 0 bytes .../main/res/mipmap-mdpi/launcher_icon.png | Bin 1784 -> 0 bytes .../main/res/mipmap-xhdpi/launcher_icon.png | Bin 4860 -> 0 bytes .../main/res/mipmap-xxhdpi/launcher_icon.png | Bin 8558 -> 0 bytes .../main/res/mipmap-xxxhdpi/launcher_icon.png | Bin 12517 -> 0 bytes ios/Runner.xcodeproj/project.pbxproj | 6 +- ios/Runner/Info.plist | 4 +- lib/common/widget/task_item_widget.dart | 17 +- .../book/ui/provider/book_provider.g.dart | 2 +- .../ui/provider/collection_provider.g.dart | 2 +- .../runtime/download_runtime_datasource.dart | 2 + .../repository/download_repository.dart | 4 + .../download/service/download_service.dart | 71 +- .../download/ui/view/download_list_view.dart | 199 ++--- .../download_form_bottom_sheet_widget.dart | 85 ++ .../ui/widget/download_task_sheet_widget.dart | 162 ++++ .../parse/service/parse_pdf_service.dart | 13 +- .../ui/provider/parse_archive_provider.dart | 28 +- .../ui/provider/parse_archive_provider.g.dart | 2 +- .../ui/provider/parse_form_provider.dart | 4 +- .../ui/provider/parse_form_provider.g.dart | 2 +- .../parse/ui/provider/parse_pdf_provider.dart | 52 +- .../ui/provider/parse_pdf_provider.g.dart | 4 +- .../view/parse_batch_image_folder_view.dart | 5 +- lib/feature/parse/ui/view/parse_pdf_view.dart | 25 +- lib/feature/parse/ui/view/parse_web_view.dart | 2 +- lib/main.dart | 2 +- macos/.gitignore | 7 - macos/Flutter/Flutter-Debug.xcconfig | 2 - macos/Flutter/Flutter-Release.xcconfig | 2 - macos/Flutter/GeneratedPluginRegistrant.swift | 30 - macos/Podfile | 43 - macos/Runner.xcodeproj/project.pbxproj | 791 ------------------ .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/xcschemes/Runner.xcscheme | 99 --- .../contents.xcworkspacedata | 10 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - macos/Runner/AppDelegate.swift | 13 - .../AppIcon.appiconset/Contents.json | 68 -- .../AppIcon.appiconset/app_icon_1024.png | Bin 168683 -> 0 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 7279 -> 0 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 351 -> 0 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 18586 -> 0 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 969 -> 0 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 80181 -> 0 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 2739 -> 0 bytes macos/Runner/Base.lproj/MainMenu.xib | 343 -------- macos/Runner/Configs/AppInfo.xcconfig | 14 - macos/Runner/Configs/Debug.xcconfig | 2 - macos/Runner/Configs/Release.xcconfig | 2 - macos/Runner/Configs/Warnings.xcconfig | 13 - macos/Runner/DebugProfile.entitlements | 16 - macos/Runner/Info.plist | 32 - macos/Runner/MainFlutterWindow.swift | 15 - macos/Runner/Release.entitlements | 12 - macos/RunnerTests/RunnerTests.swift | 12 - pubspec.yaml | 4 +- tools/install_nuget.ps1 | 2 +- web/favicon.png | Bin 351 -> 0 bytes web/icons/Icon-192.png | Bin 12517 -> 0 bytes web/icons/Icon-512.png | Bin 80181 -> 0 bytes web/icons/Icon-maskable-192.png | Bin 12517 -> 0 bytes web/icons/Icon-maskable-512.png | Bin 80181 -> 0 bytes web/index.html | 59 -- web/manifest.json | 35 - windows/.gitignore | 17 - windows/CMakeLists.txt | 108 --- windows/flutter/CMakeLists.txt | 109 --- .../flutter/generated_plugin_registrant.cc | 29 - windows/flutter/generated_plugin_registrant.h | 15 - windows/flutter/generated_plugins.cmake | 30 - windows/runner/CMakeLists.txt | 40 - windows/runner/Runner.rc | 121 --- windows/runner/flutter_window.cpp | 71 -- windows/runner/flutter_window.h | 33 - windows/runner/main.cpp | 43 - windows/runner/resource.h | 16 - windows/runner/resources/app_icon.ico | Bin 1806 -> 0 bytes windows/runner/runner.exe.manifest | 14 - windows/runner/utils.cpp | 65 -- windows/runner/utils.h | 19 - windows/runner/win32_window.cpp | 288 ------- windows/runner/win32_window.h | 102 --- 84 files changed, 503 insertions(+), 2960 deletions(-) delete mode 100644 android/app/src/main/res/mipmap-hdpi/launcher_icon.png delete mode 100644 android/app/src/main/res/mipmap-mdpi/launcher_icon.png delete mode 100644 android/app/src/main/res/mipmap-xhdpi/launcher_icon.png delete mode 100644 android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png delete mode 100644 android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png create mode 100644 lib/feature/download/ui/widget/download_form_bottom_sheet_widget.dart create mode 100644 lib/feature/download/ui/widget/download_task_sheet_widget.dart delete mode 100644 macos/.gitignore delete mode 100644 macos/Flutter/Flutter-Debug.xcconfig delete mode 100644 macos/Flutter/Flutter-Release.xcconfig delete mode 100644 macos/Flutter/GeneratedPluginRegistrant.swift delete mode 100644 macos/Podfile delete mode 100644 macos/Runner.xcodeproj/project.pbxproj delete mode 100644 macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme delete mode 100644 macos/Runner.xcworkspace/contents.xcworkspacedata delete mode 100644 macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 macos/Runner/AppDelegate.swift delete mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png delete mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png delete mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png delete mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png delete mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png delete mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png delete mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png delete mode 100644 macos/Runner/Base.lproj/MainMenu.xib delete mode 100644 macos/Runner/Configs/AppInfo.xcconfig delete mode 100644 macos/Runner/Configs/Debug.xcconfig delete mode 100644 macos/Runner/Configs/Release.xcconfig delete mode 100644 macos/Runner/Configs/Warnings.xcconfig delete mode 100644 macos/Runner/DebugProfile.entitlements delete mode 100644 macos/Runner/Info.plist delete mode 100644 macos/Runner/MainFlutterWindow.swift delete mode 100644 macos/Runner/Release.entitlements delete mode 100644 macos/RunnerTests/RunnerTests.swift delete mode 100644 web/favicon.png delete mode 100644 web/icons/Icon-192.png delete mode 100644 web/icons/Icon-512.png delete mode 100644 web/icons/Icon-maskable-192.png delete mode 100644 web/icons/Icon-maskable-512.png delete mode 100644 web/index.html delete mode 100644 web/manifest.json delete mode 100644 windows/.gitignore delete mode 100644 windows/CMakeLists.txt delete mode 100644 windows/flutter/CMakeLists.txt delete mode 100644 windows/flutter/generated_plugin_registrant.cc delete mode 100644 windows/flutter/generated_plugin_registrant.h delete mode 100644 windows/flutter/generated_plugins.cmake delete mode 100644 windows/runner/CMakeLists.txt delete mode 100644 windows/runner/Runner.rc delete mode 100644 windows/runner/flutter_window.cpp delete mode 100644 windows/runner/flutter_window.h delete mode 100644 windows/runner/main.cpp delete mode 100644 windows/runner/resource.h delete mode 100644 windows/runner/resources/app_icon.ico delete mode 100644 windows/runner/runner.exe.manifest delete mode 100644 windows/runner/utils.cpp delete mode 100644 windows/runner/utils.h delete mode 100644 windows/runner/win32_window.cpp delete mode 100644 windows/runner/win32_window.h diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index d62d376..3bdd500 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -241,12 +241,12 @@ jobs: prerelease: false generate_release_notes: true body: | - ## 🎉 TeleBook ${{ steps.version.outputs.VERSION }} + ## 🎉 tele_book ${{ steps.version.outputs.VERSION }} ### 📦 下载 - - **Android**: [TeleBook-android-${{ steps.version.outputs.VERSION }}.apk] - - **Windows**: [TeleBook-windows-${{ steps.version.outputs.VERSION }}.zip] - - **iOS**: [TeleBook-ios-${{ steps.version.outputs.VERSION }}.ipa] + - **Android**: [tele_book-android-${{ steps.version.outputs.VERSION }}.apk] + - **Windows**: [tele_book-windows-${{ steps.version.outputs.VERSION }}.zip] + - **iOS**: [tele_book-ios-${{ steps.version.outputs.VERSION }}.ipa] ### 📝 更新内容 详见下方自动生成的更新日志。 diff --git a/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png deleted file mode 100644 index f88a11960a3e084168672047b0a87d24dfb42b3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3210 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 ayzvim?+wRneA*)b0000v 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 diff --git a/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png deleted file mode 100644 index 149563a092c245a007c845ffb644bad670fdf11d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8558 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 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png deleted file mode 100644 index 825ee76f6e891a789fc51ec0ee27aa3d1eaf89d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12517 zcmds8Wmi>g*G0NRIu50TF!Qxkdy}L?&i7fpYgtqA)XJw z-uv2X%{62BSy@pU1C<071_lO0=7YpX@NegTf01Fq*X8yHWf&M}sf>iEnrFsQCW@Z? z!OOsSsx_-bQzU`xKOLP~osSLw5~lL#tKFD$@!eGzCKKhp(#vUAhyGwPA0l5=kD3am zm$|l+8NGYi5#T81m>%KWbfOd9dGglx;B!sd$+$?}xf5DFx$z>$*_DP7hr&{22yqCn z3-#=FM6MxK`-R`G1o>3)o{W^e?X9@Icylg37PXc|bzu49!g4kimJHe1OI`oCvu%E< z-jxOAjPd#Lk?7UeNA@qU)Ku{AC|`^HU;WUlL`B&T%k!Jz_tOKG=i{xjsw!?-rqt?f zM-?X~de5%@_3os&^OwAo6fI}xs>gdIknW(TRb0iYy_&`_uLO z^QiOc>gsc!fB*3RQ3(47f5{eCfTF9GsHv-~+e0!{@}++Mvi*TcoL5lrr^UmOsHlPo zpOkcG^N9J-DExf{M!Jcut%Ze!o!wHtbbOp+rTg^6>Tm{cjh~LvBE&C z%ZMn-oY`VHRU`P@6+J@&5jPli84!pkRtK1q^&R zKW8ZA;4mR810y367P)}a1f|=XBTKep|JScXp%$yY_m@LsE0|(AM7sy1 zF~p_pdAtIq`XOpcs<`e!nc_+k4-TbsMO z+F`;F9UVhmmE)ynscW+9&mVaS33v+~f(?_6>0*U+u2EiA)_|-1ndAz+=iC#Et--I1 z8l_I3J`v#KA6Z%ANDU1S537Fs$j-rmhI-!NZ!cas!>M9DX~kLjSIPJZ#-HRB8x!KC zVSPnl!Ox#RfBpRFak#V~T|Q0`@W^&H{&{xx+x-m%Od2pEd;YNOTwK>! zr^O#j>gwudW@bE2l9HF1{Vwh?!o-63`50O~pZ2dlZ_+7}2~$y1v%ByA34{sIhv-ZF zMeNxeNROd#{HU$nWVcZ5x-;gN%>K*PN2H(s` zb?%MV8$A(NFHbkCp!C(htDE`U2)W%V|31TAEgV^mKBl2`M)|B-tOU+y+AmbTCPFHI zW^QhHaF7zayEq)i-rjy74kD+h$i+rXfsGBdo_b4E6sTF6oBK5@%YS<$l_pD$B#X<} zHHhZz+hFnFWM*9!Di%dT3{^pVe0&v^uQD>yIyySHf`rFoW@cs#pii-gnZobeDpBXB zx;Li!qEA-ZAGTowJerdO@2?Y+lfOmaL@re2ITm~@`ThGhgdc^H3b}RcV6FxUWjtz~$8yahBO7L{me(OuJ_L_H5J5 z@VgPhQR90Y92^CO(Xo~fGBWQN-zB#*KZQ>{Q-bT#}e=Z=;_XcKtkbdsfb2q4qu5!|S!R^9QXSZ}DQ zML1}gnG=)JER3yD{Nkl=+wa-}0$+}oTg1i0^z`&5Y_Q1R+o*~9uWZoBVUqIEhV{; z5|YL2U$>@C0LIxuzPFt}#yf@+cbA_TxValtixrPnI}AU5d>wB5&U)|Z;o9293LYr} zCK;VnLnDb!G2_eEE)y+fdHIp^f5Qk|9*y{@oa)%}`IEa4?$ECeBDTfuL>~$Q8@u

+{Gwa(=u$1F!}l_~)-*`TBmpf2s$eqQVg}X?1SU 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 $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - TeleBook + tele_book CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -15,7 +15,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - TeleBook + tele_book CFBundlePackageType APPL CFBundleShortVersionString diff --git a/lib/common/widget/task_item_widget.dart b/lib/common/widget/task_item_widget.dart index 7aae294..141eda6 100644 --- a/lib/common/widget/task_item_widget.dart +++ b/lib/common/widget/task_item_widget.dart @@ -34,24 +34,23 @@ class TaskItemWidget extends StatelessWidget { subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text("状态: $status", style: Theme.of(context).textTheme.bodySmall), + SizedBox(height: 8), Row( children: [ - Text( - "状态: $status", - style: Theme.of(context).textTheme.bodySmall, + Expanded( + child: LinearProgressIndicator( + value: progress, + borderRadius: BorderRadius.circular(4), + ), ), SizedBox(width: 8), Text( - "进度: ${(progress * 100).toStringAsFixed(1)}%", + "${(progress * 100).toStringAsFixed(1)}%", style: Theme.of(context).textTheme.bodySmall, ), ], ), - SizedBox(height: 8), - LinearProgressIndicator( - value: progress, - borderRadius: BorderRadius.circular(4), - ), ], ), trailing: trailing, diff --git a/lib/feature/book/ui/provider/book_provider.g.dart b/lib/feature/book/ui/provider/book_provider.g.dart index 4b0f466..df88c9f 100644 --- a/lib/feature/book/ui/provider/book_provider.g.dart +++ b/lib/feature/book/ui/provider/book_provider.g.dart @@ -33,7 +33,7 @@ final class BookListProvider BookList create() => BookList(); } -String _$bookListHash() => r'd582e10acb9b6a19e0a98a36ae1456008a03e235'; +String _$bookListHash() => r'8cc7188f30e463cd9d76918cd02c58d4cbbd98e0'; abstract class _$BookList extends $AsyncNotifier { FutureOr build(); diff --git a/lib/feature/collection/ui/provider/collection_provider.g.dart b/lib/feature/collection/ui/provider/collection_provider.g.dart index 56a5152..21e8f2d 100644 --- a/lib/feature/collection/ui/provider/collection_provider.g.dart +++ b/lib/feature/collection/ui/provider/collection_provider.g.dart @@ -55,7 +55,7 @@ final class CollectionListProvider } } -String _$collectionListHash() => r'8dee32d769cba75cfab03c365a6de455a8222e29'; +String _$collectionListHash() => r'5bfd649d7f9d74b075041ce8e4afa9c3535400f9'; @ProviderFor(CreateCollectionController) final createCollectionControllerProvider = diff --git a/lib/feature/download/datasource/runtime/download_runtime_datasource.dart b/lib/feature/download/datasource/runtime/download_runtime_datasource.dart index e92eb9a..73b41cb 100644 --- a/lib/feature/download/datasource/runtime/download_runtime_datasource.dart +++ b/lib/feature/download/datasource/runtime/download_runtime_datasource.dart @@ -37,6 +37,8 @@ class DownloadRuntimeDatasource { DownloadGroupBo? getGroup(String groupId) => _groupMap[groupId]; + List getGroups() => _groupMap.values.toList(); + List getItemsByGroup(String groupId) { return _itemMap.values.where((item) => item.groupId == groupId).toList(); } diff --git a/lib/feature/download/repository/download_repository.dart b/lib/feature/download/repository/download_repository.dart index ed76f24..3b190b0 100644 --- a/lib/feature/download/repository/download_repository.dart +++ b/lib/feature/download/repository/download_repository.dart @@ -25,6 +25,10 @@ class DownloadRepository { return _downloadRuntimeDatasource.getGroup(groupId); } + List getDownloadGroups() { + return _downloadRuntimeDatasource.getGroups(); + } + List getDownloadItemsByGroup(String groupId) { return _downloadRuntimeDatasource.getItemsByGroup(groupId); } diff --git a/lib/feature/download/service/download_service.dart b/lib/feature/download/service/download_service.dart index 78a7f8f..f417231 100644 --- a/lib/feature/download/service/download_service.dart +++ b/lib/feature/download/service/download_service.dart @@ -5,8 +5,6 @@ import 'package:background_downloader/background_downloader.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path_provider/path_provider.dart'; import 'package:synchronized/synchronized.dart'; -import 'package:tele_book/common/config/global_config.dart'; -import 'package:tele_book/core/db/app_database.dart'; import 'package:tele_book/feature/book/model/dto/save_as_book_dto.dart'; import 'package:tele_book/feature/book/repository/book_repository.dart'; import 'package:tele_book/feature/download/enum/download_status.dart'; @@ -205,7 +203,6 @@ class DownloadService { }); }, onStatus: (status) async { - String? groupIdToUpdate; await _stateLock.synchronized(() { switch (status) { case TaskStatus.failed: @@ -214,8 +211,6 @@ class DownloadService { ); currentItem = failedItem; _downloadRepository.upsertItem(failedItem); - - groupIdToUpdate = item.groupId; break; case TaskStatus.complete: final completedItem = currentItem.copyWith( @@ -224,7 +219,6 @@ class DownloadService { ); currentItem = completedItem; _downloadRepository.upsertItem(completedItem); - groupIdToUpdate = item.groupId; break; case TaskStatus.running || TaskStatus.enqueued: final downloadingItem = currentItem.copyWith( @@ -236,23 +230,72 @@ class DownloadService { default: } }); - // 在 lock 外更新组状态和检查自动保存条件 - if (groupIdToUpdate != null) { - await _updateGroupStatus(groupIdToUpdate!); - } - await _checkAndAutoSave(item.groupId); + // 在 lock 外统一刷新组状态并检查自动保存条件 + await _refreshGroupState(item.groupId); }, ); } + /// 删除单个下载项:会同步清理临时文件、重算组状态,并在满足条件时自动保存为书籍 + Future deleteTaskItem(String itemId) async { + final item = _downloadRepository.getDownloadTask(itemId); + if (item == null) return; + + // 删除进行中的任务会和后台回调打架,先不支持,避免状态回写冲突 + if (item.status == DownloadStatus.pending || + item.status == DownloadStatus.downloading || + item.status == DownloadStatus.paused) { + throw StateError('下载中的任务暂不支持删除,请稍后再试'); + } + + final group = _downloadRepository.getDownloadGroup(item.groupId); + if (group == null) return; + + final file = File('${group.saveParentPath}/${item.saveSubPath}'); + if (await file.exists()) { + await file.delete(); + } + + _downloadRepository.deleteItem(itemId); + await _refreshGroupState(group.id); + } + + /// 清空所有已完成的下载组 + Future clearCompletedTasks() async { + final completedGroups = []; + + await _stateLock.synchronized(() async { + final groups = _downloadRepository.getDownloadGroups(); + for (final group in groups) { + if (group.status != DownloadStatus.completed) continue; + completedGroups.add(group); + _autoSavedGroups.remove(group.id); + _downloadRepository.deleteGroup(group.id); + } + }); + + for (final group in completedGroups) { + final dir = Directory(group.saveParentPath); + if (await dir.exists()) { + await dir.delete(recursive: true); + } + } + + return completedGroups.length; + } + /// 根据组内所有项的状态更新组状态 - Future _updateGroupStatus(String groupId) async { + Future _refreshGroupState(String groupId) async { final group = _downloadRepository.getDownloadGroup(groupId); if (group == null) return; // 直接读取仓库中的当前快照,避免 stream.first 时序带来的状态回读延迟 final items = _downloadRepository.getDownloadItemsByGroup(groupId); - if (items.isEmpty) return; + if (items.isEmpty) { + _autoSavedGroups.remove(groupId); + _downloadRepository.deleteGroup(groupId); + return; + } // 统计各状态的任务数 final completedCount = items @@ -289,6 +332,8 @@ class DownloadService { status: newStatus, ), ); + + await _checkAndAutoSave(groupId); } /// 检查组是否已完成且无失败,若满足则自动保存为书籍 diff --git a/lib/feature/download/ui/view/download_list_view.dart b/lib/feature/download/ui/view/download_list_view.dart index c8ffed4..61b18d6 100644 --- a/lib/feature/download/ui/view/download_list_view.dart +++ b/lib/feature/download/ui/view/download_list_view.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:tele_book/common/widget/error_widget.dart'; @@ -9,6 +8,8 @@ import 'package:tele_book/feature/download/enum/download_status.dart'; import 'package:tele_book/feature/download/model/bo/download_bo.dart'; import 'package:tele_book/feature/download/service/download_service.dart'; import 'package:tele_book/feature/download/ui/provider/download_provider.dart'; +import 'package:tele_book/feature/download/ui/widget/download_form_bottom_sheet_widget.dart'; +import 'package:tele_book/feature/download/ui/widget/download_task_sheet_widget.dart'; class DownloadListView extends ConsumerWidget { const DownloadListView({super.key}); @@ -26,6 +27,13 @@ class DownloadListView extends ConsumerWidget { appBar: AppBar( title: Text("下载任务"), actions: [ + IconButton( + tooltip: '清空已完成任务', + onPressed: () async { + await _clearCompletedTasks(context, ref); + }, + icon: const Icon(Icons.delete_sweep), + ), IconButton( onPressed: () async { await _showDownloadForm(context); @@ -82,6 +90,60 @@ class DownloadListView extends ConsumerWidget { ); } + Future _clearCompletedTasks(BuildContext context, WidgetRef ref) async { + final tasks = await ref.read(downloadTasksProvider.future); + final hasCompleted = tasks.any( + (group) => group.downloadGroupBo.status == DownloadStatus.completed, + ); + if (!hasCompleted) { + if (!context.mounted) return; + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('没有可清空的已完成任务'))); + return; + } + + final confirmed = await showDialog( + context: context, + builder: (dialogContext) { + return AlertDialog( + title: const Text('清空已完成任务'), + content: const Text('将移除所有已完成的下载任务组及其临时文件,是否继续?'), + actions: [ + TextButton( + onPressed: () => Navigator.of(dialogContext).pop(false), + child: const Text('取消'), + ), + ElevatedButton( + onPressed: () => Navigator.of(dialogContext).pop(true), + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.error, + ), + child: const Text('清空', style: TextStyle(color: Colors.white)), + ), + ], + ); + }, + ); + + if (confirmed != true || !context.mounted) return; + + try { + final clearedCount = await ref + .read(downloadServiceProvider) + .clearCompletedTasks(); + if (!context.mounted) return; + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('已清空 $clearedCount 个已完成任务'))); + } catch (e) { + if (!context.mounted) return; + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(e.toString()))); + } + } + void _showDownloadTaskList( BuildContext context, String groupId, @@ -89,151 +151,20 @@ class DownloadListView extends ConsumerWidget { ) { showModalBottomSheet( context: context, - builder: (context) { - final asyncTasks = ref.watch(downloadTasksProvider); - - return asyncTasks.when( - loading: () => const Padding( - padding: EdgeInsets.all(16), - child: Center(child: CircularProgressIndicator()), - ), - error: (e, st) => Padding( - padding: const EdgeInsets.all(16), - child: Center( - child: Text( - "加载任务失败", - style: TextStyle(fontSize: 16, color: Colors.grey), - ), - ), - ), - data: (groups) { - final tasks = groups - .where((g) => g.downloadGroupBo.id == groupId) - .expand((g) => g.downloadItemBoList) - .toList(); - - if (tasks.isEmpty) { - return const Padding( - padding: EdgeInsets.all(16), - child: Center( - child: Text( - "处理中", - style: TextStyle(fontSize: 16, color: Colors.grey), - ), - ), - ); - } - - return Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - const Text( - "下载任务详情", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 16), - Expanded( - child: ListView.separated( - itemCount: tasks.length, - separatorBuilder: (_, __) => const SizedBox(height: 16), - itemBuilder: (context, index) { - final task = tasks[index]; - return TaskItemWidget( - title: task.url, - coverUrl: task.url, - status: task.status.description, - progress: task.progress, - trailing: task.status == DownloadStatus.failed - ? IconButton( - onPressed: () { - ref - .read(downloadServiceProvider) - .retryTask(task.id); - }, - icon: const Icon(Icons.refresh), - ) - : null, - ); - }, - ), - ), - ], - ), - ); - }, - ); - }, + builder: (_) => DownloadTaskSheetWidget(groupId: groupId), ); } Future _showDownloadForm(BuildContext context) async { - final TextEditingController urlController = TextEditingController(); final url = await showModalBottomSheet( isScrollControlled: true, enableDrag: true, context: context, builder: (sheetContext) { - final bottomInset = MediaQuery.of(sheetContext).viewInsets.bottom; - return AnimatedPadding( - duration: const Duration(milliseconds: 100), - curve: Curves.easeOut, - padding: EdgeInsets.fromLTRB(16, 16, 16, 16 + bottomInset), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "添加下载任务", - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 16), - TextField( - controller: urlController, - decoration: InputDecoration( - labelText: "下载链接", - border: const OutlineInputBorder(), - prefixIcon: const Icon(Icons.link), - suffixIcon: IconButton( - onPressed: () { - Clipboard.getData(Clipboard.kTextPlain).then(( - clipData, - ) { - if (clipData != null && clipData.text != null) { - urlController.text = clipData.text!; - } - }); - }, - icon: const Icon(Icons.paste), - ), - ), - keyboardType: TextInputType.url, - inputFormatters: [ - FilteringTextInputFormatter.deny(RegExp(r'\s')), - ], - ), - const SizedBox(height: 16), - SizedBox( - width: double.infinity, - child: FilledButton( - onPressed: () { - final inputUrl = urlController.text.trim(); - Navigator.of(sheetContext).pop(inputUrl); - }, - child: const Text("添加"), - ), - ), - ], - ), - ), - ); + return DownloadFormBottomSheetWidget(); }, ); - WidgetsBinding.instance.addPostFrameCallback((_) { - urlController.dispose(); - }); - if (url == null || url.isEmpty || !context.mounted) return; context.push(AppRoute.parseWeb, extra: url); } diff --git a/lib/feature/download/ui/widget/download_form_bottom_sheet_widget.dart b/lib/feature/download/ui/widget/download_form_bottom_sheet_widget.dart new file mode 100644 index 0000000..8a94759 --- /dev/null +++ b/lib/feature/download/ui/widget/download_form_bottom_sheet_widget.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class DownloadFormBottomSheetWidget extends ConsumerStatefulWidget { + const DownloadFormBottomSheetWidget({super.key}); + + @override + ConsumerState createState() => _DownloadFormBottomSheetState(); +} + +class _DownloadFormBottomSheetState extends ConsumerState { + // 将控制器绑定到 State 的生命周期中 + late final TextEditingController _urlController; + + @override + void initState() { + super.initState(); + _urlController = TextEditingController(); + } + + @override + void dispose() { + // 规范:在此处安全销毁,当 BottomSheet 彻底消失在屏幕上时触发 + _urlController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // 处理键盘避让 + final bottomInset = MediaQuery.of(context).viewInsets.bottom; + + return AnimatedPadding( + duration: const Duration(milliseconds: 100), + curve: Curves.easeOut, + padding: EdgeInsets.fromLTRB(16, 16, 16, 16 + bottomInset), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "添加下载任务", + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 16), + TextField( + controller: _urlController, + decoration: InputDecoration( + labelText: "下载链接", + border: const OutlineInputBorder(), + prefixIcon: const Icon(Icons.link), + suffixIcon: IconButton( + onPressed: () async { + final clipData = await Clipboard.getData(Clipboard.kTextPlain); + if (clipData != null && clipData.text != null) { + _urlController.text = clipData.text!; + } + }, + icon: const Icon(Icons.paste), + ), + ), + keyboardType: TextInputType.url, + inputFormatters: [ + FilteringTextInputFormatter.deny(RegExp(r'\s')), + ], + ), + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: () { + final inputUrl = _urlController.text.trim(); + // 规范:直接使用当前组件的 context 返回数据 + Navigator.of(context).pop(inputUrl); + }, + child: const Text("添加"), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/feature/download/ui/widget/download_task_sheet_widget.dart b/lib/feature/download/ui/widget/download_task_sheet_widget.dart new file mode 100644 index 0000000..0ab8e47 --- /dev/null +++ b/lib/feature/download/ui/widget/download_task_sheet_widget.dart @@ -0,0 +1,162 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:tele_book/common/widget/task_item_widget.dart'; +import 'package:tele_book/feature/download/enum/download_status.dart'; +import 'package:tele_book/feature/download/service/download_service.dart'; +import 'package:tele_book/feature/download/ui/provider/download_provider.dart'; + +class DownloadTaskSheetWidget extends ConsumerWidget { + final String groupId; + + const DownloadTaskSheetWidget({required this.groupId}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final asyncTasks = ref.watch(downloadTasksProvider); + + return asyncTasks.when( + loading: () => const Padding( + padding: EdgeInsets.all(16), + child: Center(child: CircularProgressIndicator()), + ), + error: (e, st) => Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Text( + "加载任务失败", + style: TextStyle(fontSize: 16, color: Colors.grey), + ), + ), + ), + data: (groups) { + final tasks = groups + .where((g) => g.downloadGroupBo.id == groupId) + .expand((g) => g.downloadItemBoList) + .toList(); + + if (tasks.isEmpty) { + return const Padding( + padding: EdgeInsets.all(16), + child: Center( + child: Text( + "处理中", + style: TextStyle(fontSize: 16, color: Colors.grey), + ), + ), + ); + } + + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + const Text( + "下载任务详情", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 16), + Expanded( + child: ListView.separated( + itemCount: tasks.length, + separatorBuilder: (_, __) => const SizedBox(height: 16), + itemBuilder: (context, index) { + final task = tasks[index]; + return TaskItemWidget( + title: task.url, + coverUrl: task.url, + status: task.status.description, + progress: task.progress, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (task.status == DownloadStatus.failed) + IconButton( + tooltip: '重试', + onPressed: () { + ref + .read(downloadServiceProvider) + .retryTask(task.id); + }, + icon: const Icon(Icons.refresh), + ), + IconButton( + tooltip: + task.status == DownloadStatus.pending || + task.status == DownloadStatus.downloading || + task.status == DownloadStatus.paused + ? '下载中暂不支持删除' + : '删除任务项', + onPressed: + task.status == DownloadStatus.pending || + task.status == DownloadStatus.downloading || + task.status == DownloadStatus.paused + ? null + : () async { + final confirmed = await showDialog( + context: context, + builder: (dialogContext) { + return AlertDialog( + title: const Text('删除任务项'), + content: const Text( + '删除后会重新检查剩余任务,若已经全部完成则会自动保存书籍,是否继续?', + ), + actions: [ + TextButton( + onPressed: () => Navigator.of( + dialogContext, + ).pop(false), + child: const Text('取消'), + ), + ElevatedButton( + onPressed: () => Navigator.of( + dialogContext, + ).pop(true), + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of( + context, + ).colorScheme.error, + ), + child: const Text( + '删除', + style: TextStyle( + color: Colors.white, + ), + ), + ), + ], + ); + }, + ); + + if (confirmed != true || !context.mounted) { + return; + } + + try { + await ref + .read(downloadServiceProvider) + .deleteTaskItem(task.id); + } catch (e) { + if (!context.mounted) return; + ScaffoldMessenger.of( + context, + ).showSnackBar( + SnackBar(content: Text(e.toString())), + ); + } + }, + icon: const Icon(Icons.delete), + ), + ], + ), + ); + }, + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/feature/parse/service/parse_pdf_service.dart b/lib/feature/parse/service/parse_pdf_service.dart index e406120..661385e 100644 --- a/lib/feature/parse/service/parse_pdf_service.dart +++ b/lib/feature/parse/service/parse_pdf_service.dart @@ -3,13 +3,13 @@ import 'dart:io'; import 'dart:typed_data'; import 'dart:ui' as ui; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:path/path.dart' as p; -import 'package:pdfrx/pdfrx.dart'; import 'package:tele_book/common/config/global_config.dart'; import 'package:tele_book/core/util/failure_util.dart'; import 'package:tele_book/core/util/result_util.dart'; import 'package:tele_book/feature/parse/model/parse_batch_archive_vo.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:path/path.dart' as p; +import 'package:pdfrx/pdfrx.dart'; import 'package:uuid/uuid.dart'; final parsePdfServiceProvider = Provider((ref) => ParsePdfService()); @@ -47,6 +47,13 @@ class ParsePdfService { String pdfPath, { void Function(int current, int total)? onProgress, }) async { + final sourceFile = File(pdfPath); + if (!await sourceFile.exists()) { + return Result.failure( + BusinessFailure(message: 'PDF 文件不存在或无访问权限: $pdfPath'), + ); + } + final tempDir = p.join(GlobalConfig.appTempDir.path, const Uuid().v4()); await Directory(tempDir).create(recursive: true); diff --git a/lib/feature/parse/ui/provider/parse_archive_provider.dart b/lib/feature/parse/ui/provider/parse_archive_provider.dart index edda691..b2a14cb 100644 --- a/lib/feature/parse/ui/provider/parse_archive_provider.dart +++ b/lib/feature/parse/ui/provider/parse_archive_provider.dart @@ -1,6 +1,9 @@ +import 'dart:io'; + import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/legacy.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:tele_book/feature/book/model/dto/save_as_book_dto.dart'; import 'package:tele_book/feature/book/repository/book_repository.dart'; @@ -27,7 +30,8 @@ abstract class ParseArchiveSaveBookParam with _$ParseArchiveSaveBookParam { } @freezed -abstract class ParseArchiveSaveBookProgress with _$ParseArchiveSaveBookProgress { +abstract class ParseArchiveSaveBookProgress + with _$ParseArchiveSaveBookProgress { const factory ParseArchiveSaveBookProgress({ @Default(0) int current, @Default(0) int total, @@ -54,7 +58,23 @@ class ParseArchive extends _$ParseArchive { return ParseArchiveState(archiveName: archiveName, tempPaths: const []); } + Future _requestStoragePermission() async { + if (!Platform.isAndroid) return true; + if (await Permission.manageExternalStorage.isGranted) return true; + final status = await Permission.manageExternalStorage.request(); + if (status.isGranted) return true; + if (await Permission.storage.isGranted) return true; + final storageStatus = await Permission.storage.request(); + return storageStatus.isGranted; + } + Future _parseArchive(String archivePath) async { + final hasPermission = await _requestStoragePermission(); + if (!hasPermission) { + state = AsyncError('需要存储权限才能解析压缩包', StackTrace.current); + return; + } + final archiveName = archivePath.split(RegExp(r'[\\/]')).last; ref.read(parseArchiveProgressProvider(archivePath).notifier).state = (0, 0); @@ -72,8 +92,9 @@ class ParseArchive extends _$ParseArchive { result.fold( onSuccess: (data) { - state = - AsyncData(ParseArchiveState(archiveName: archiveName, tempPaths: data)); + state = AsyncData( + ParseArchiveState(archiveName: archiveName, tempPaths: data), + ); }, onError: (error) { state = AsyncError(error.message, StackTrace.current); @@ -114,4 +135,3 @@ class ParseArchiveSaveBook extends _$ParseArchiveSaveBook { ); } } - diff --git a/lib/feature/parse/ui/provider/parse_archive_provider.g.dart b/lib/feature/parse/ui/provider/parse_archive_provider.g.dart index adc5b8f..33ed94a 100644 --- a/lib/feature/parse/ui/provider/parse_archive_provider.g.dart +++ b/lib/feature/parse/ui/provider/parse_archive_provider.g.dart @@ -50,7 +50,7 @@ final class ParseArchiveProvider } } -String _$parseArchiveHash() => r'abd52c0a9e981404affd3711f8cbd7f3194ee84e'; +String _$parseArchiveHash() => r'15200f8f1b3d556233c5d24a6775874fe06071d7'; final class ParseArchiveFamily extends $Family with diff --git a/lib/feature/parse/ui/provider/parse_form_provider.dart b/lib/feature/parse/ui/provider/parse_form_provider.dart index 6654fe3..72889d6 100644 --- a/lib/feature/parse/ui/provider/parse_form_provider.dart +++ b/lib/feature/parse/ui/provider/parse_form_provider.dart @@ -129,7 +129,7 @@ class ParseForm extends _$ParseForm { Future pickerArchive() async { final result = await FilePicker.platform.pickFiles( - dialogTitle: '选择 TeleBook 导出的书籍归档文件', + dialogTitle: '选择 tele_book 导出的书籍归档文件', type: FileType.custom, allowedExtensions: ['zip'], ); @@ -158,7 +158,7 @@ class ParseForm extends _$ParseForm { } final result = await FilePicker.platform.getDirectoryPath( - dialogTitle: '选择 TeleBook 导出��书籍归档文件夹', + dialogTitle: '选择 tele_book 导出��书籍归档文件夹', ); if (result != null) { batchArchivePathController.text = result; diff --git a/lib/feature/parse/ui/provider/parse_form_provider.g.dart b/lib/feature/parse/ui/provider/parse_form_provider.g.dart index 5f9dd0b..c02e94e 100644 --- a/lib/feature/parse/ui/provider/parse_form_provider.g.dart +++ b/lib/feature/parse/ui/provider/parse_form_provider.g.dart @@ -41,7 +41,7 @@ final class ParseFormProvider } } -String _$parseFormHash() => r'ee119b4fdf6d809a4cffeca8d5ce46ca9e2b17ff'; +String _$parseFormHash() => r'd3c29d56619ed0e61eaf8817baf9a355a374555a'; abstract class _$ParseForm extends $Notifier { ParseFormState build(); diff --git a/lib/feature/parse/ui/provider/parse_pdf_provider.dart b/lib/feature/parse/ui/provider/parse_pdf_provider.dart index 4c14138..8037075 100644 --- a/lib/feature/parse/ui/provider/parse_pdf_provider.dart +++ b/lib/feature/parse/ui/provider/parse_pdf_provider.dart @@ -1,7 +1,10 @@ -import 'package:flutter_riverpod/experimental/mutation.dart'; +import 'dart:io'; + +import 'package:dk_util/dk_util.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/legacy.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:tele_book/feature/book/model/dto/save_as_book_dto.dart'; import 'package:tele_book/feature/book/repository/book_repository.dart'; @@ -28,6 +31,16 @@ final parsePdfProgressProvider = class ParsePdf extends _$ParsePdf { ParsePdfService get _service => ref.read(parsePdfServiceProvider); + Future _requestStoragePermission() async { + if (!Platform.isAndroid) return true; + if (await Permission.manageExternalStorage.isGranted) return true; + final status = await Permission.manageExternalStorage.request(); + if (status.isGranted) return true; + if (await Permission.storage.isGranted) return true; + final storageStatus = await Permission.storage.request(); + return storageStatus.isGranted; + } + @override FutureOr build(String pdfPath) async { final pdfName = pdfPath.split(RegExp(r'[\\/]')).last.replaceAll('.pdf', ''); @@ -40,9 +53,15 @@ class ParsePdf extends _$ParsePdf { ref.read(parsePdfProgressProvider(pdfPath).notifier).state = (0, 0); + final hasPermission = await _requestStoragePermission(); + if (!hasPermission) { + state = AsyncValue.error('需要存储权限才能解析 PDF', StackTrace.current); + return; + } + state = AsyncValue.loading(); final result = await _service.parsePdf( - pdfName, + pdfPath, onProgress: (current, total) { ref.read(parsePdfProgressProvider(pdfPath).notifier).state = ( current, @@ -63,38 +82,45 @@ class ParsePdf extends _$ParsePdf { } } -final parsePdfSaveBookProgressProvider = StateProvider<(int current, int total)>( - (_) => (0, 0), -); +final parsePdfSaveBookProgressProvider = + StateProvider<(int current, int total)>((_) => (0, 0)); @riverpod class ParsePdfSaveBook extends _$ParsePdfSaveBook { BookRepository get _repository => ref.read(bookRepositoryProvider); @override - FutureOr build() async => null; + FutureOr build() async { + return null; + } - Future onSave( - WidgetRef ref, - List tempPaths, - String pdfName, - ) async { + Future onSave(List tempPaths, String pdfName) async { + DKLog.i('开始保存解析结果为书籍,tempPaths: $tempPaths, pdfName: $pdfName'); state = AsyncValue.loading(); - ref.read(parsePdfSaveBookProgressProvider.notifier).state = (0, tempPaths.length); + ref.read(parsePdfSaveBookProgressProvider.notifier).state = ( + 0, + tempPaths.length, + ); + DKLog.i('调用 repository.saveAsBook,开始文件复制和 DB 写入'); final result = await _repository.saveAsBook( SaveAsBookDto(title: pdfName, paths: tempPaths), onProgress: (current, total) { - ref.read(parsePdfSaveBookProgressProvider.notifier).state = (current, total); + ref.read(parsePdfSaveBookProgressProvider.notifier).state = ( + current, + total, + ); }, ); result.fold( onSuccess: (_) { + DKLog.i('保存书籍成功'); state = AsyncValue.data(null); }, onError: (e) { + DKLog.e('保存书籍失败,错误信息:${e.message}'); state = AsyncValue.error(e.message, StackTrace.current); }, ); diff --git a/lib/feature/parse/ui/provider/parse_pdf_provider.g.dart b/lib/feature/parse/ui/provider/parse_pdf_provider.g.dart index 317c776..d8c703c 100644 --- a/lib/feature/parse/ui/provider/parse_pdf_provider.g.dart +++ b/lib/feature/parse/ui/provider/parse_pdf_provider.g.dart @@ -50,7 +50,7 @@ final class ParsePdfProvider } } -String _$parsePdfHash() => r'2b3746c5d1962646ecf4a6e600d7e92c093fe77b'; +String _$parsePdfHash() => r'6b20494d7df97ff721a555fc1d5c8dee0b99d85f'; final class ParsePdfFamily extends $Family with @@ -122,7 +122,7 @@ final class ParsePdfSaveBookProvider ParsePdfSaveBook create() => ParsePdfSaveBook(); } -String _$parsePdfSaveBookHash() => r'a7d10d9c9b6aeeb2e470926be2aa25898dbefd2e'; +String _$parsePdfSaveBookHash() => r'7851b1d22cd47f9be7cf7e6ed5d5bbda7f8c8c8c'; abstract class _$ParsePdfSaveBook extends $AsyncNotifier { FutureOr build(); diff --git a/lib/feature/parse/ui/view/parse_batch_image_folder_view.dart b/lib/feature/parse/ui/view/parse_batch_image_folder_view.dart index 9779f8b..2b0d051 100644 --- a/lib/feature/parse/ui/view/parse_batch_image_folder_view.dart +++ b/lib/feature/parse/ui/view/parse_batch_image_folder_view.dart @@ -6,6 +6,7 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:go_router/go_router.dart'; import 'package:tele_book/common/widget/error_widget.dart'; import 'package:tele_book/common/widget/local_image_widget.dart'; +import 'package:tele_book/core/route/app_route.dart'; import 'package:tele_book/feature/parse/ui/provider/parse_batch_image_folder.dart'; class ParseBatchImageFolderView extends ConsumerWidget { @@ -46,7 +47,7 @@ class ParseBatchImageFolderView extends ConsumerWidget { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text("保存成功!"))); - context.pop(); + context.go(AppRoute.main); } }); @@ -72,8 +73,6 @@ class ParseBatchImageFolderView extends ConsumerWidget { Text("正在处理:${current.completeCount}/${current.totalCount}"), if (current.currentFileName.isNotEmpty) Text("当前文件:${current.currentFileName}"), - if (current.currentFileProgressText.isNotEmpty) - Text(current.currentFileProgressText), ], ), ); diff --git a/lib/feature/parse/ui/view/parse_pdf_view.dart b/lib/feature/parse/ui/view/parse_pdf_view.dart index dc8cdc2..8fd427a 100644 --- a/lib/feature/parse/ui/view/parse_pdf_view.dart +++ b/lib/feature/parse/ui/view/parse_pdf_view.dart @@ -1,7 +1,10 @@ import 'dart:io'; +import 'package:dk_util/dk_util.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:tele_book/core/route/app_route.dart'; import 'package:tele_book/feature/parse/ui/provider/parse_pdf_provider.dart'; class ParsePdfView extends ConsumerWidget { @@ -16,7 +19,24 @@ class ParsePdfView extends ConsumerWidget { final saveBookProgress = ref.watch(parsePdfSaveBookProgressProvider); final saveBookState = ref.watch(parsePdfSaveBookProvider); - final saveBookNotifier = ref.watch(parsePdfSaveBookProvider.notifier); + + ref.listen(parsePdfSaveBookProvider, (previous, next) { + DKLog.d('保存状态变化:previous=$previous, next=$next'); + + if (next.hasError && !next.isLoading) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('保存失败:${next.error}'))); + } + + if (previous?.isLoading == true && next.hasValue) { + DKLog.d('保存成功,返回书架'); + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('保存成功'))); + context.go(AppRoute.main); + } + }); return Scaffold( appBar: AppBar(title: Text('解析 PDF:${asyncState.value?.pdfName ?? ''}')), @@ -49,8 +69,7 @@ class ParsePdfView extends ConsumerWidget { child: FilledButton( onPressed: saveBookState.isLoading ? null - : () => saveBookNotifier.onSave( - ref, + : () => ref.read(parsePdfSaveBookProvider.notifier).onSave( asyncState.value!.tempPaths, asyncState.value!.pdfName, ), diff --git a/lib/feature/parse/ui/view/parse_web_view.dart b/lib/feature/parse/ui/view/parse_web_view.dart index 1c86d74..b8721fe 100644 --- a/lib/feature/parse/ui/view/parse_web_view.dart +++ b/lib/feature/parse/ui/view/parse_web_view.dart @@ -35,7 +35,7 @@ class ParseWebView extends ConsumerWidget { if (confirmed != true || !context.mounted) return; ref.read(mainProvider.notifier).updateCurrentIndex(1); - context.pop(); + context.go(AppRoute.main); unawaited(notifier.startDownload()); }); }, diff --git a/lib/main.dart b/lib/main.dart index 909174d..d296d89 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -41,7 +41,7 @@ Future _init() async { appProviders = ProviderScope( child: MaterialApp.router( - title: 'TeleBook', + title: 'tele_book', routerConfig: AppRoute.router, debugShowCheckedModeBanner: false, theme: AppTheme.light, diff --git a/macos/.gitignore b/macos/.gitignore deleted file mode 100644 index 746adbb..0000000 --- a/macos/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Flutter-related -**/Flutter/ephemeral/ -**/Pods/ - -# Xcode-related -**/dgph -**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig deleted file mode 100644 index 4b81f9b..0000000 --- a/macos/Flutter/Flutter-Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig deleted file mode 100644 index 5caa9d1..0000000 --- a/macos/Flutter/Flutter-Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift deleted file mode 100644 index 0a7d3b8..0000000 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// Generated file. Do not edit. -// - -import FlutterMacOS -import Foundation - -import file_picker -import flutter_inappwebview_macos -import nsd_macos -import package_info_plus -import share_plus -import shared_preferences_foundation -import sqlite3_flutter_libs -import url_launcher_macos -import wakelock_plus -import webview_flutter_wkwebview - -func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) - InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) - NsdMacosPlugin.register(with: registry.registrar(forPlugin: "NsdMacosPlugin")) - FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) - SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) - SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) - Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) - UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) - WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) - WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) -} diff --git a/macos/Podfile b/macos/Podfile deleted file mode 100644 index b52666a..0000000 --- a/macos/Podfile +++ /dev/null @@ -1,43 +0,0 @@ -platform :osx, '10.15' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_macos_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do - inherit! :search_paths - end -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_macos_build_settings(target) - end -end diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index 147bb0f..0000000 --- a/macos/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,791 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXAggregateTarget section */ - 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; - buildPhases = ( - 33CC111E2044C6BF0003C045 /* ShellScript */, - ); - dependencies = ( - ); - name = "Flutter Assemble"; - productName = FLX; - }; -/* End PBXAggregateTarget section */ - -/* Begin PBXBuildFile section */ - 068057681EBC0164278138EE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52C3F6C80C354EAC194FF017 /* Pods_Runner.framework */; }; - 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - 47872D1CD946E0B05FF0865B /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E2D04AE39B8CD9253FACF36F /* Pods_RunnerTests.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 33CC10E52044A3C60003C045 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 33CC10EC2044A3C60003C045; - remoteInfo = Runner; - }; - 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 33CC10E52044A3C60003C045 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 33CC111A2044C6BA0003C045; - remoteInfo = FLX; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 33CC110E2044A8840003C045 /* Bundle Framework */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Bundle Framework"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 0D5418905133AF2E183332ED /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* wo_nas.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = wo_nas.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; - 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; - 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; - 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; - 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; - 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 39F4CCAB4B2333991EE870B5 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - 52C3F6C80C354EAC194FF017 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 61815FF484F50BF3C434C847 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - 751E53A3618EF980C2B80835 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - E2D04AE39B8CD9253FACF36F /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F7C816C2674A757608F1B430 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - F86AB340ED7EBD2475AC8D37 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 331C80D2294CF70F00263BE5 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 47872D1CD946E0B05FF0865B /* Pods_RunnerTests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 33CC10EA2044A3C60003C045 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 068057681EBC0164278138EE /* Pods_Runner.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 331C80D6294CF71000263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C80D7294CF71000263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; - 33BA886A226E78AF003329D5 /* Configs */ = { - isa = PBXGroup; - children = ( - 33E5194F232828860026EE4D /* AppInfo.xcconfig */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, - ); - path = Configs; - sourceTree = ""; - }; - 33CC10E42044A3C60003C045 = { - isa = PBXGroup; - children = ( - 33FAB671232836740065AC1E /* Runner */, - 33CEB47122A05771004F2AC0 /* Flutter */, - 331C80D6294CF71000263BE5 /* RunnerTests */, - 33CC10EE2044A3C60003C045 /* Products */, - D73912EC22F37F3D000D13A0 /* Frameworks */, - 4A325688A39558AB9A5FDDF5 /* Pods */, - ); - sourceTree = ""; - }; - 33CC10EE2044A3C60003C045 /* Products */ = { - isa = PBXGroup; - children = ( - 33CC10ED2044A3C60003C045 /* wo_nas.app */, - 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 33CC11242044D66E0003C045 /* Resources */ = { - isa = PBXGroup; - children = ( - 33CC10F22044A3C60003C045 /* Assets.xcassets */, - 33CC10F42044A3C60003C045 /* MainMenu.xib */, - 33CC10F72044A3C60003C045 /* Info.plist */, - ); - name = Resources; - path = ..; - sourceTree = ""; - }; - 33CEB47122A05771004F2AC0 /* Flutter */ = { - isa = PBXGroup; - children = ( - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, - ); - path = Flutter; - sourceTree = ""; - }; - 33FAB671232836740065AC1E /* Runner */ = { - isa = PBXGroup; - children = ( - 33CC10F02044A3C60003C045 /* AppDelegate.swift */, - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, - 33E51913231747F40026EE4D /* DebugProfile.entitlements */, - 33E51914231749380026EE4D /* Release.entitlements */, - 33CC11242044D66E0003C045 /* Resources */, - 33BA886A226E78AF003329D5 /* Configs */, - ); - path = Runner; - sourceTree = ""; - }; - 4A325688A39558AB9A5FDDF5 /* Pods */ = { - isa = PBXGroup; - children = ( - 751E53A3618EF980C2B80835 /* Pods-Runner.debug.xcconfig */, - 0D5418905133AF2E183332ED /* Pods-Runner.release.xcconfig */, - F86AB340ED7EBD2475AC8D37 /* Pods-Runner.profile.xcconfig */, - 39F4CCAB4B2333991EE870B5 /* Pods-RunnerTests.debug.xcconfig */, - 61815FF484F50BF3C434C847 /* Pods-RunnerTests.release.xcconfig */, - F7C816C2674A757608F1B430 /* Pods-RunnerTests.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; - D73912EC22F37F3D000D13A0 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 52C3F6C80C354EAC194FF017 /* Pods_Runner.framework */, - E2D04AE39B8CD9253FACF36F /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 331C80D4294CF70F00263BE5 /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - 0D01681597DA0B7381D9BEF4 /* [CP] Check Pods Manifest.lock */, - 331C80D1294CF70F00263BE5 /* Sources */, - 331C80D2294CF70F00263BE5 /* Frameworks */, - 331C80D3294CF70F00263BE5 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 331C80DA294CF71000263BE5 /* PBXTargetDependency */, - ); - name = RunnerTests; - productName = RunnerTests; - productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 33CC10EC2044A3C60003C045 /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - D3606AACA5A32258415D76F6 /* [CP] Check Pods Manifest.lock */, - 33CC10E92044A3C60003C045 /* Sources */, - 33CC10EA2044A3C60003C045 /* Frameworks */, - 33CC10EB2044A3C60003C045 /* Resources */, - 33CC110E2044A8840003C045 /* Bundle Framework */, - 3399D490228B24CF009A79C7 /* ShellScript */, - 0C11A0C5FB4735CAA6B427E9 /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 33CC11202044C79F0003C045 /* PBXTargetDependency */, - ); - name = Runner; - productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* wo_nas.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 33CC10E52044A3C60003C045 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1510; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 331C80D4294CF70F00263BE5 = { - CreatedOnToolsVersion = 14.0; - TestTargetID = 33CC10EC2044A3C60003C045; - }; - 33CC10EC2044A3C60003C045 = { - CreatedOnToolsVersion = 9.2; - LastSwiftMigration = 1100; - ProvisioningStyle = Automatic; - SystemCapabilities = { - com.apple.Sandbox = { - enabled = 1; - }; - }; - }; - 33CC111A2044C6BA0003C045 = { - CreatedOnToolsVersion = 9.2; - ProvisioningStyle = Manual; - }; - }; - }; - buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 33CC10E42044A3C60003C045; - productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 33CC10EC2044A3C60003C045 /* Runner */, - 331C80D4294CF70F00263BE5 /* RunnerTests */, - 33CC111A2044C6BA0003C045 /* Flutter Assemble */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 331C80D3294CF70F00263BE5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 33CC10EB2044A3C60003C045 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 0C11A0C5FB4735CAA6B427E9 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 0D01681597DA0B7381D9BEF4 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 3399D490228B24CF009A79C7 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; - }; - 33CC111E2044C6BF0003C045 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - Flutter/ephemeral/FlutterInputs.xcfilelist, - ); - inputPaths = ( - Flutter/ephemeral/tripwire, - ); - outputFileListPaths = ( - Flutter/ephemeral/FlutterOutputs.xcfilelist, - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; - }; - D3606AACA5A32258415D76F6 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 331C80D1294CF70F00263BE5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 33CC10E92044A3C60003C045 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 33CC10EC2044A3C60003C045 /* Runner */; - targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; - }; - 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; - targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - 33CC10F52044A3C60003C045 /* Base */, - ); - name = MainMenu.xib; - path = Runner; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 331C80DB294CF71000263BE5 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 39F4CCAB4B2333991EE870B5 /* Pods-RunnerTests.debug.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.dorkytiger.woNas.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/wo_nas.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/wo_nas"; - }; - name = Debug; - }; - 331C80DC294CF71000263BE5 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 61815FF484F50BF3C434C847 /* Pods-RunnerTests.release.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.dorkytiger.woNas.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/wo_nas.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/wo_nas"; - }; - name = Release; - }; - 331C80DD294CF71000263BE5 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = F7C816C2674A757608F1B430 /* Pods-RunnerTests.profile.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.dorkytiger.woNas.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/wo_nas.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/wo_nas"; - }; - name = Profile; - }; - 338D0CE9231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Profile; - }; - 338D0CEA231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Profile; - }; - 338D0CEB231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Profile; - }; - 33CC10F92044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 33CC10FA2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Release; - }; - 33CC10FC2044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 33CC10FD2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - 33CC111C2044C6BA0003C045 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 33CC111D2044C6BA0003C045 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 331C80DB294CF71000263BE5 /* Debug */, - 331C80DC294CF71000263BE5 /* Release */, - 331C80DD294CF71000263BE5 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10F92044A3C60003C045 /* Debug */, - 33CC10FA2044A3C60003C045 /* Release */, - 338D0CE9231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10FC2044A3C60003C045 /* Debug */, - 33CC10FD2044A3C60003C045 /* Release */, - 338D0CEA231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC111C2044C6BA0003C045 /* Debug */, - 33CC111D2044C6BA0003C045 /* Release */, - 338D0CEB231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 33CC10E52044A3C60003C045 /* Project object */; -} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index 0e0bb2d..0000000 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 21a3cc1..0000000 --- a/macos/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift deleted file mode 100644 index b3c1761..0000000 --- a/macos/Runner/AppDelegate.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Cocoa -import FlutterMacOS - -@main -class AppDelegate: FlutterAppDelegate { - override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - return true - } - - override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { - return true - } -} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 96d3fee..0000000 --- a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "info": { - "version": 1, - "author": "xcode" - }, - "images": [ - { - "size": "16x16", - "idiom": "mac", - "filename": "app_icon_16.png", - "scale": "1x" - }, - { - "size": "16x16", - "idiom": "mac", - "filename": "app_icon_32.png", - "scale": "2x" - }, - { - "size": "32x32", - "idiom": "mac", - "filename": "app_icon_32.png", - "scale": "1x" - }, - { - "size": "32x32", - "idiom": "mac", - "filename": "app_icon_64.png", - "scale": "2x" - }, - { - "size": "128x128", - "idiom": "mac", - "filename": "app_icon_128.png", - "scale": "1x" - }, - { - "size": "128x128", - "idiom": "mac", - "filename": "app_icon_256.png", - "scale": "2x" - }, - { - "size": "256x256", - "idiom": "mac", - "filename": "app_icon_256.png", - "scale": "1x" - }, - { - "size": "256x256", - "idiom": "mac", - "filename": "app_icon_512.png", - "scale": "2x" - }, - { - "size": "512x512", - "idiom": "mac", - "filename": "app_icon_512.png", - "scale": "1x" - }, - { - "size": "512x512", - "idiom": "mac", - "filename": "app_icon_1024.png", - "scale": "2x" - } - ] -} \ No newline at end of file diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png deleted file mode 100644 index 87a61d0c8ed4480f5ce3ffd269bedc1345706cf3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 168683 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�x(tK=#P>YZtU?(T>=H_Bpg2`V(s=uuqmso@ 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 SrUWk%A!{_BLXyQY;=p)2I(EBqFnr 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 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png deleted file mode 100644 index 59b2256193ab7415573d04eecfc3444ebcbfde3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80181 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 diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib deleted file mode 100644 index 80e867a..0000000 --- a/macos/Runner/Base.lproj/MainMenu.xib +++ /dev/null @@ -1,343 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig deleted file mode 100644 index 5e3a4f5..0000000 --- a/macos/Runner/Configs/AppInfo.xcconfig +++ /dev/null @@ -1,14 +0,0 @@ -// Application-level settings for the Runner target. -// -// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the -// future. If not, the values below would default to using the project name when this becomes a -// 'flutter create' template. - -// The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = TeleBook - -// The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.dorkytiger.telebook - -// The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2023 com.dorkytiger. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig deleted file mode 100644 index 36b0fd9..0000000 --- a/macos/Runner/Configs/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../Flutter/Flutter-Debug.xcconfig" -#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig deleted file mode 100644 index dff4f49..0000000 --- a/macos/Runner/Configs/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../Flutter/Flutter-Release.xcconfig" -#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig deleted file mode 100644 index 42bcbf4..0000000 --- a/macos/Runner/Configs/Warnings.xcconfig +++ /dev/null @@ -1,13 +0,0 @@ -WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings -GCC_WARN_UNDECLARED_SELECTOR = YES -CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES -CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE -CLANG_WARN__DUPLICATE_METHOD_MATCH = YES -CLANG_WARN_PRAGMA_PACK = YES -CLANG_WARN_STRICT_PROTOTYPES = YES -CLANG_WARN_COMMA = YES -GCC_WARN_STRICT_SELECTOR_MATCH = YES -CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES -CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES -GCC_WARN_SHADOW = YES -CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements deleted file mode 100644 index 0eaccf1..0000000 --- a/macos/Runner/DebugProfile.entitlements +++ /dev/null @@ -1,16 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.cs.allow-jit - - com.apple.security.files.user-selected.read-write - - com.apple.security.network.client - - com.apple.security.network.server - - - diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist deleted file mode 100644 index 4789daa..0000000 --- a/macos/Runner/Info.plist +++ /dev/null @@ -1,32 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - NSHumanReadableCopyright - $(PRODUCT_COPYRIGHT) - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - - diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift deleted file mode 100644 index 3cc05eb..0000000 --- a/macos/Runner/MainFlutterWindow.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Cocoa -import FlutterMacOS - -class MainFlutterWindow: NSWindow { - override func awakeFromNib() { - let flutterViewController = FlutterViewController() - let windowFrame = self.frame - self.contentViewController = flutterViewController - self.setFrame(windowFrame, display: true) - - RegisterGeneratedPlugins(registry: flutterViewController) - - super.awakeFromNib() - } -} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements deleted file mode 100644 index a046386..0000000 --- a/macos/Runner/Release.entitlements +++ /dev/null @@ -1,12 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-write - - com.apple.security.network.client - - - diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift deleted file mode 100644 index 5418c9f..0000000 --- a/macos/RunnerTests/RunnerTests.swift +++ /dev/null @@ -1,12 +0,0 @@ -import FlutterMacOS -import Cocoa -import XCTest - -class RunnerTests: XCTestCase { - - func testExample() { - // If you add code to the Runner application, consider adding tests here. - // See https://developer.apple.com/documentation/xctest for more information about using XCTest. - } - -} diff --git a/pubspec.yaml b/pubspec.yaml index ced3cc2..163f94e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,8 +38,8 @@ dependencies: uuid: ^4.5.3 synchronized: ^3.4.0+1 shimmer: ^3.0.0 - pdf: ^3.11.0 - pdfrx: ^2.3.2 + pdf: ^3.12.0 + pdfrx: ^2.4.3 flutter_riverpod: ^3.3.1 riverpod_annotation: ^4.0.2 freezed_annotation: ^3.1.0 diff --git a/tools/install_nuget.ps1 b/tools/install_nuget.ps1 index 4f31559..283bcb4 100644 --- a/tools/install_nuget.ps1 +++ b/tools/install_nuget.ps1 @@ -19,7 +19,7 @@ if (Test-Path $targetPath) { Write-Host "" Write-Host "请将该目录加入系统 PATH,或在系统环境变量中设置 NUGET_EXECUTABLE 指向完整路径:" Write-Host " 例如(PowerShell 临时):$env:NUGET_EXECUTABLE = '$targetPath'" - Write-Host " 或永久在 系统 属性 -> 环境变量 中添加:D:\\StudioProjects\\TeleBook\\tools\\nuget" + Write-Host " 或永久在 系统 属性 -> 环境变量 中添加:D:\\StudioProjects\\tele_book\\tools\\nuget" Write-Host "" Write-Host "然后重新运行 Flutter 构建(例如:flutter build windows 或 flutter run -d windows)。" } else { diff --git a/web/favicon.png b/web/favicon.png deleted file mode 100644 index dfa7cee39e6f19e82c90ae917615118b0fac4b43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 351 zcmV-l0igbgP){_BLXyQY;=p)2I(EBqFnr zz;4LC4W!w>{Fx7CU?g}k(*FWVDF6(^@ZHF=ObEGeegGk4yWPfdyjU!b$0H)H*Xyq9 z9LEWQ01+R7=XsRUvMg0qwQZXaav3jGRlUiw@B91x{tg*G0NRIu50TF!Qxkdy}L?&i7fpYgtqA)XJw z-uv2X%{62BSy@pU1C<071_lO0=7YpX@NegTf01Fq*X8yHWf&M}sf>iEnrFsQCW@Z? z!OOsSsx_-bQzU`xKOLP~osSLw5~lL#tKFD$@!eGzCKKhp(#vUAhyGwPA0l5=kD3am zm$|l+8NGYi5#T81m>%KWbfOd9dGglx;B!sd$+$?}xf5DFx$z>$*_DP7hr&{22yqCn z3-#=FM6MxK`-R`G1o>3)o{W^e?X9@Icylg37PXc|bzu49!g4kimJHe1OI`oCvu%E< z-jxOAjPd#Lk?7UeNA@qU)Ku{AC|`^HU;WUlL`B&T%k!Jz_tOKG=i{xjsw!?-rqt?f zM-?X~de5%@_3os&^OwAo6fI}xs>gdIknW(TRb0iYy_&`_uLO z^QiOc>gsc!fB*3RQ3(47f5{eCfTF9GsHv-~+e0!{@}++Mvi*TcoL5lrr^UmOsHlPo zpOkcG^N9J-DExf{M!Jcut%Ze!o!wHtbbOp+rTg^6>Tm{cjh~LvBE&C z%ZMn-oY`VHRU`P@6+J@&5jPli84!pkRtK1q^&R zKW8ZA;4mR810y367P)}a1f|=XBTKep|JScXp%$yY_m@LsE0|(AM7sy1 zF~p_pdAtIq`XOpcs<`e!nc_+k4-TbsMO z+F`;F9UVhmmE)ynscW+9&mVaS33v+~f(?_6>0*U+u2EiA)_|-1ndAz+=iC#Et--I1 z8l_I3J`v#KA6Z%ANDU1S537Fs$j-rmhI-!NZ!cas!>M9DX~kLjSIPJZ#-HRB8x!KC zVSPnl!Ox#RfBpRFak#V~T|Q0`@W^&H{&{xx+x-m%Od2pEd;YNOTwK>! zr^O#j>gwudW@bE2l9HF1{Vwh?!o-63`50O~pZ2dlZ_+7}2~$y1v%ByA34{sIhv-ZF zMeNxeNROd#{HU$nWVcZ5x-;gN%>K*PN2H(s` zb?%MV8$A(NFHbkCp!C(htDE`U2)W%V|31TAEgV^mKBl2`M)|B-tOU+y+AmbTCPFHI zW^QhHaF7zayEq)i-rjy74kD+h$i+rXfsGBdo_b4E6sTF6oBK5@%YS<$l_pD$B#X<} zHHhZz+hFnFWM*9!Di%dT3{^pVe0&v^uQD>yIyySHf`rFoW@cs#pii-gnZobeDpBXB zx;Li!qEA-ZAGTowJerdO@2?Y+lfOmaL@re2ITm~@`ThGhgdc^H3b}RcV6FxUWjtz~$8yahBO7L{me(OuJ_L_H5J5 z@VgPhQR90Y92^CO(Xo~fGBWQN-zB#*KZQ>{Q-bT#}e=Z=;_XcKtkbdsfb2q4qu5!|S!R^9QXSZ}DQ zML1}gnG=)JER3yD{Nkl=+wa-}0$+}oTg1i0^z`&5Y_Q1R+o*~9uWZoBVUqIEhV{; z5|YL2U$>@C0LIxuzPFt}#yf@+cbA_TxValtixrPnI}AU5d>wB5&U)|Z;o9293LYr} zCK;VnLnDb!G2_eEE)y+fdHIp^f5Qk|9*y{@oa)%}`IEa4?$ECeBDTfuL>~$Q8@u

+{Gwa(=u$1F!}l_~)-*`TBmpf2s$eqQVg}X?1SU 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

+{Gwa(=u$1F!}l_~)-*`TBmpf2s$eqQVg}X?1SU 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 - - - - - - - - - - - - - - - - - - - wo_nas - - - - - - - - - - diff --git a/web/manifest.json b/web/manifest.json deleted file mode 100644 index 941a902..0000000 --- a/web/manifest.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "wo_nas", - "short_name": "wo_nas", - "start_url": ".", - "display": "standalone", - "background_color": "#hexcode", - "theme_color": "#hexcode", - "description": "A new Flutter project.", - "orientation": "portrait-primary", - "prefer_related_applications": false, - "icons": [ - { - "src": "icons/Icon-192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "icons/Icon-512.png", - "sizes": "512x512", - "type": "image/png" - }, - { - "src": "icons/Icon-maskable-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "maskable" - }, - { - "src": "icons/Icon-maskable-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "maskable" - } - ] -} \ No newline at end of file diff --git a/windows/.gitignore b/windows/.gitignore deleted file mode 100644 index d492d0d..0000000 --- a/windows/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -flutter/ephemeral/ - -# Visual Studio user-specific files. -*.suo -*.user -*.userosscache -*.sln.docstates - -# Visual Studio build-related files. -x64/ -x86/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt deleted file mode 100644 index 0ee9434..0000000 --- a/windows/CMakeLists.txt +++ /dev/null @@ -1,108 +0,0 @@ -# Project-level configuration. -cmake_minimum_required(VERSION 3.14) -project(tele_book LANGUAGES CXX) - -# The name of the executable created for the application. Change this to change -# the on-disk name of your application. -set(BINARY_NAME "tele_book") - -# Explicitly opt in to modern CMake behaviors to avoid warnings with recent -# versions of CMake. -cmake_policy(VERSION 3.14...3.25) - -# Define build configuration option. -get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(IS_MULTICONFIG) - set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" - CACHE STRING "" FORCE) -else() - if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug" CACHE - STRING "Flutter build mode" FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Profile" "Release") - endif() -endif() -# Define settings for the Profile build mode. -set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") -set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") -set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") -set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") - -# Use Unicode for all projects. -add_definitions(-DUNICODE -D_UNICODE) - -# Compilation settings that should be applied to most targets. -# -# Be cautious about adding new options here, as plugins use this function by -# default. In most cases, you should add new options to specific targets instead -# of modifying this function. -function(APPLY_STANDARD_SETTINGS TARGET) - target_compile_features(${TARGET} PUBLIC cxx_std_17) - target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") - target_compile_options(${TARGET} PRIVATE /EHsc) - target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") - target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") -endfunction() - -# Flutter library and tool build rules. -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") -add_subdirectory(${FLUTTER_MANAGED_DIR}) - -# Application build; see runner/CMakeLists.txt. -add_subdirectory("runner") - - -# Generated plugin build rules, which manage building the plugins and adding -# them to the application. -include(flutter/generated_plugins.cmake) - - -# === Installation === -# Support files are copied into place next to the executable, so that it can -# run in place. This is done instead of making a separate bundle (as on Linux) -# so that building and running from within Visual Studio will work. -set(BUILD_BUNDLE_DIR "$") -# Make the "install" step default, as it's required to run. -set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) -endif() - -set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") -set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") - -install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -if(PLUGIN_BUNDLED_LIBRARIES) - install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endif() - -# Copy the native assets provided by the build.dart from all packages. -set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") -install(DIRECTORY "${NATIVE_ASSETS_DIR}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -# Fully re-copy the assets directory on each build to avoid having stale files -# from a previous install. -set(FLUTTER_ASSET_DIR_NAME "flutter_assets") -install(CODE " - file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") - " COMPONENT Runtime) -install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" - DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) - -# Install the AOT library on non-Debug builds only. -install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - CONFIGURATIONS Profile;Release - COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt deleted file mode 100644 index 903f489..0000000 --- a/windows/flutter/CMakeLists.txt +++ /dev/null @@ -1,109 +0,0 @@ -# This file controls Flutter-level build steps. It should not be edited. -cmake_minimum_required(VERSION 3.14) - -set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") - -# Configuration provided via flutter tool. -include(${EPHEMERAL_DIR}/generated_config.cmake) - -# TODO: Move the rest of this into files in ephemeral. See -# https://github.com/flutter/flutter/issues/57146. -set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") - -# Set fallback configurations for older versions of the flutter tool. -if (NOT DEFINED FLUTTER_TARGET_PLATFORM) - set(FLUTTER_TARGET_PLATFORM "windows-x64") -endif() - -# === Flutter Library === -set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") - -# Published to parent scope for install step. -set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) -set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) -set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) -set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) - -list(APPEND FLUTTER_LIBRARY_HEADERS - "flutter_export.h" - "flutter_windows.h" - "flutter_messenger.h" - "flutter_plugin_registrar.h" - "flutter_texture_registrar.h" -) -list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") -add_library(flutter INTERFACE) -target_include_directories(flutter INTERFACE - "${EPHEMERAL_DIR}" -) -target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") -add_dependencies(flutter flutter_assemble) - -# === Wrapper === -list(APPEND CPP_WRAPPER_SOURCES_CORE - "core_implementations.cc" - "standard_codec.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_PLUGIN - "plugin_registrar.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_APP - "flutter_engine.cc" - "flutter_view_controller.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") - -# Wrapper sources needed for a plugin. -add_library(flutter_wrapper_plugin STATIC - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_PLUGIN} -) -apply_standard_settings(flutter_wrapper_plugin) -set_target_properties(flutter_wrapper_plugin PROPERTIES - POSITION_INDEPENDENT_CODE ON) -set_target_properties(flutter_wrapper_plugin PROPERTIES - CXX_VISIBILITY_PRESET hidden) -target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) -target_include_directories(flutter_wrapper_plugin PUBLIC - "${WRAPPER_ROOT}/include" -) -add_dependencies(flutter_wrapper_plugin flutter_assemble) - -# Wrapper sources needed for the runner. -add_library(flutter_wrapper_app STATIC - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_APP} -) -apply_standard_settings(flutter_wrapper_app) -target_link_libraries(flutter_wrapper_app PUBLIC flutter) -target_include_directories(flutter_wrapper_app PUBLIC - "${WRAPPER_ROOT}/include" -) -add_dependencies(flutter_wrapper_app flutter_assemble) - -# === Flutter tool backend === -# _phony_ is a non-existent file to force this command to run every time, -# since currently there's no way to get a full input/output list from the -# flutter tool. -set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") -set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) -add_custom_command( - OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} - ${CPP_WRAPPER_SOURCES_APP} - ${PHONY_OUTPUT} - COMMAND ${CMAKE_COMMAND} -E env - ${FLUTTER_TOOL_ENVIRONMENT} - "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - ${FLUTTER_TARGET_PLATFORM} $ - VERBATIM -) -add_custom_target(flutter_assemble DEPENDS - "${FLUTTER_LIBRARY}" - ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_PLUGIN} - ${CPP_WRAPPER_SOURCES_APP} -) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc deleted file mode 100644 index 0d1cf9d..0000000 --- a/windows/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,29 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - -#include -#include -#include -#include -#include -#include - -void RegisterPlugins(flutter::PluginRegistry* registry) { - FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); - NsdWindowsPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("NsdWindowsPluginCApi")); - PermissionHandlerWindowsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); - SharePlusWindowsPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); - Sqlite3FlutterLibsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); - UrlLauncherWindowsRegisterWithRegistrar( - registry->GetRegistrarForPlugin("UrlLauncherWindows")); -} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h deleted file mode 100644 index dc139d8..0000000 --- a/windows/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include - -// Registers Flutter plugins. -void RegisterPlugins(flutter::PluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake deleted file mode 100644 index 0546ea5..0000000 --- a/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,30 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST - flutter_inappwebview_windows - nsd_windows - permission_handler_windows - share_plus - sqlite3_flutter_libs - url_launcher_windows -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST - jni -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt deleted file mode 100644 index 394917c..0000000 --- a/windows/runner/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -cmake_minimum_required(VERSION 3.14) -project(runner LANGUAGES CXX) - -# Define the application target. To change its name, change BINARY_NAME in the -# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer -# work. -# -# Any new source files that you add to the application should be added here. -add_executable(${BINARY_NAME} WIN32 - "flutter_window.cpp" - "main.cpp" - "utils.cpp" - "win32_window.cpp" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" - "Runner.rc" - "runner.exe.manifest" -) - -# Apply the standard set of build settings. This can be removed for applications -# that need different build settings. -apply_standard_settings(${BINARY_NAME}) - -# Add preprocessor definitions for the build version. -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") - -# Disable Windows macros that collide with C++ standard library functions. -target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") - -# Add dependency libraries and include directories. Add any application-specific -# dependencies here. -target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) -target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") -target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") - -# Run the Flutter tool portions of the build. This must not be removed. -add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc deleted file mode 100644 index f71b184..0000000 --- a/windows/runner/Runner.rc +++ /dev/null @@ -1,121 +0,0 @@ -// Microsoft Visual C++ generated resource script. -// -#pragma code_page(65001) -#include "resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -IDI_APP_ICON ICON "resources\\app_icon.ico" - - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) -#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD -#else -#define VERSION_AS_NUMBER 1,0,0,0 -#endif - -#if defined(FLUTTER_VERSION) -#define VERSION_AS_STRING FLUTTER_VERSION -#else -#define VERSION_AS_STRING "1.0.0" -#endif - -VS_VERSION_INFO VERSIONINFO - FILEVERSION VERSION_AS_NUMBER - PRODUCTVERSION VERSION_AS_NUMBER - FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS__WINDOWS32 - FILETYPE VFT_APP - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904e4" - BEGIN - VALUE "CompanyName", "com.dorkytiger" "\0" - VALUE "FileDescription", "tele_book" "\0" - VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "tele_book" "\0" - VALUE "LegalCopyright", "Copyright (C) 2026 com.dorkytiger. All rights reserved." "\0" - VALUE "OriginalFilename", "tele_book.exe" "\0" - VALUE "ProductName", "tele_book" "\0" - VALUE "ProductVersion", VERSION_AS_STRING "\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1252 - END -END - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp deleted file mode 100644 index 955ee30..0000000 --- a/windows/runner/flutter_window.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "flutter_window.h" - -#include - -#include "flutter/generated_plugin_registrant.h" - -FlutterWindow::FlutterWindow(const flutter::DartProject& project) - : project_(project) {} - -FlutterWindow::~FlutterWindow() {} - -bool FlutterWindow::OnCreate() { - if (!Win32Window::OnCreate()) { - return false; - } - - RECT frame = GetClientArea(); - - // The size here must match the window dimensions to avoid unnecessary surface - // creation / destruction in the startup path. - flutter_controller_ = std::make_unique( - frame.right - frame.left, frame.bottom - frame.top, project_); - // Ensure that basic setup of the controller was successful. - if (!flutter_controller_->engine() || !flutter_controller_->view()) { - return false; - } - RegisterPlugins(flutter_controller_->engine()); - SetChildContent(flutter_controller_->view()->GetNativeWindow()); - - flutter_controller_->engine()->SetNextFrameCallback([&]() { - this->Show(); - }); - - // Flutter can complete the first frame before the "show window" callback is - // registered. The following call ensures a frame is pending to ensure the - // window is shown. It is a no-op if the first frame hasn't completed yet. - flutter_controller_->ForceRedraw(); - - return true; -} - -void FlutterWindow::OnDestroy() { - if (flutter_controller_) { - flutter_controller_ = nullptr; - } - - Win32Window::OnDestroy(); -} - -LRESULT -FlutterWindow::MessageHandler(HWND hwnd, UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - // Give Flutter, including plugins, an opportunity to handle window messages. - if (flutter_controller_) { - std::optional result = - flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, - lparam); - if (result) { - return *result; - } - } - - switch (message) { - case WM_FONTCHANGE: - flutter_controller_->engine()->ReloadSystemFonts(); - break; - } - - return Win32Window::MessageHandler(hwnd, message, wparam, lparam); -} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h deleted file mode 100644 index 6da0652..0000000 --- a/windows/runner/flutter_window.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef RUNNER_FLUTTER_WINDOW_H_ -#define RUNNER_FLUTTER_WINDOW_H_ - -#include -#include - -#include - -#include "win32_window.h" - -// A window that does nothing but host a Flutter view. -class FlutterWindow : public Win32Window { - public: - // Creates a new FlutterWindow hosting a Flutter view running |project|. - explicit FlutterWindow(const flutter::DartProject& project); - virtual ~FlutterWindow(); - - protected: - // Win32Window: - bool OnCreate() override; - void OnDestroy() override; - LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, - LPARAM const lparam) noexcept override; - - private: - // The project to run. - flutter::DartProject project_; - - // The Flutter instance hosted by this window. - std::unique_ptr flutter_controller_; -}; - -#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp deleted file mode 100644 index a8259fb..0000000 --- a/windows/runner/main.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include -#include -#include - -#include "flutter_window.h" -#include "utils.h" - -int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, - _In_ wchar_t *command_line, _In_ int show_command) { - // Attach to console when present (e.g., 'flutter run') or create a - // new console when running with a debugger. - if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { - CreateAndAttachConsole(); - } - - // Initialize COM, so that it is available for use in the library and/or - // plugins. - ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - - flutter::DartProject project(L"data"); - - std::vector command_line_arguments = - GetCommandLineArguments(); - - project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); - - FlutterWindow window(project); - Win32Window::Point origin(10, 10); - Win32Window::Size size(1280, 720); - if (!window.Create(L"tele_book", origin, size)) { - return EXIT_FAILURE; - } - window.SetQuitOnClose(true); - - ::MSG msg; - while (::GetMessage(&msg, nullptr, 0, 0)) { - ::TranslateMessage(&msg); - ::DispatchMessage(&msg); - } - - ::CoUninitialize(); - return EXIT_SUCCESS; -} diff --git a/windows/runner/resource.h b/windows/runner/resource.h deleted file mode 100644 index 66a65d1..0000000 --- a/windows/runner/resource.h +++ /dev/null @@ -1,16 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by Runner.rc -// -#define IDI_APP_ICON 101 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico deleted file mode 100644 index 32de11cf7bc906ad909c2095394de9c87936dc7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1806 zcmV+p2l4m-0096205C8B0000W0Qd#~02TlM0EtjeM-2)Z3IG5A4M|8uQUCw|FaQ7m zFbDzw007uvZqNV#2ER!}K~#90?U!v#Q&}2-Pul_8k}0?(Gef8`Ks1U4OIs|FVK8Mx zM;2|2EtHm6T6W~4+F*i$L8JZvcEK3af&~>}Yg<4|w}wweMusr0owjycoIw<=iB73a z5GduF-d%fl38Nbd1KHh4cz!g^$vN+N&N=se?>(P@|1&=MUkiN5x{x>4g}kvYxTsZxZQ4@hx6yppFVxspf_Y>WN0)R06=8qPMPdhXJ=68WcZzx`(a ze)ZqfE|+V`*_ISSqENXxIm5%lPN$R2X4`Bw0DwRs7#$r|tJNbTBZY;9N~N-~vElgf z6O)sZH8nL{F8ATXhww6zl9Gagf%HwA?Y=&~$;rtUizOf+AU{7pEiJ9P zySt&G0eD8j0TUAwX*60(OUvNk;BOHE0K9$sHak1}#fujUEgFrcv$GS8MrUOm(`vQ( z`T0#vP1RM^Xf)blu_PoUNF!b&(Dm!r zsZ^?5E*FVJO-(AbTK(?bI|zc@Za0}ss;H@si>$ZDJi*o_wJ!XhX8m#*@g`pPj89^c&DTvu0j>eQ*s%*?K?E}2ZW zXklw4gaC-ef4aNBfAF9Shr{LN<>~eM?Cfm)kNWtpzJe2QqI9zRQt)xm~Fc{+E;$~)MVq;?g1X-=txVX5Xp&=|5%j5Af zGc#vrXS=(*`F#GG2>}RlLy*6}e{F5;z_Wp_@4hQ8F825LKXKxCpT$yCbXF>r{;MYf zG3kNHWR{ngx3sh{7!14J9upHoB9UrpYJ!4-+-~=(gghCBn3|lVQmLV#p4G& zSFir^%bVM`@1&%pUc7kmz<~oi9uF?VzP`RFRxzrtD zDIu53wQ19)&6_uq$z%$JLZ{P-M54`R)9dv*oi05+oe)7NEGYQwv(HH+G62BG$H#8B z|D~+#szeeQ5g`x=qNAhnc)Z18(dl&eI_{MemsqXVrK^DVEPLglsi_G*#E_7XsHiB| zFFZVaVqyY=!61=HxZX@pPZt&x^!N80jmG=;@0XUA4h{~kWgYqe2!dXCP%4#h3xPmT zsZ?QMVE}+PZ{Bb?oPU1*uPElKgg&OPuW#G7Z98`CK%tO*eNSd*XIB|;4X)Dj^Ydo2 wdG&E%e$RLN{|@Uy-dGp%#=4L<)`h(B4|4Af$83DsBLDyZ07*qoM6N<$f~aP1NB{r; diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest deleted file mode 100644 index 153653e..0000000 --- a/windows/runner/runner.exe.manifest +++ /dev/null @@ -1,14 +0,0 @@ - - - - - PerMonitorV2 - - - - - - - - - diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp deleted file mode 100644 index 3a0b465..0000000 --- a/windows/runner/utils.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "utils.h" - -#include -#include -#include -#include - -#include - -void CreateAndAttachConsole() { - if (::AllocConsole()) { - FILE *unused; - if (freopen_s(&unused, "CONOUT$", "w", stdout)) { - _dup2(_fileno(stdout), 1); - } - if (freopen_s(&unused, "CONOUT$", "w", stderr)) { - _dup2(_fileno(stdout), 2); - } - std::ios::sync_with_stdio(); - FlutterDesktopResyncOutputStreams(); - } -} - -std::vector GetCommandLineArguments() { - // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. - int argc; - wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); - if (argv == nullptr) { - return std::vector(); - } - - std::vector command_line_arguments; - - // Skip the first argument as it's the binary name. - for (int i = 1; i < argc; i++) { - command_line_arguments.push_back(Utf8FromUtf16(argv[i])); - } - - ::LocalFree(argv); - - return command_line_arguments; -} - -std::string Utf8FromUtf16(const wchar_t* utf16_string) { - if (utf16_string == nullptr) { - return std::string(); - } - unsigned int target_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - -1, nullptr, 0, nullptr, nullptr) - -1; // remove the trailing null character - int input_length = (int)wcslen(utf16_string); - std::string utf8_string; - if (target_length == 0 || target_length > utf8_string.max_size()) { - return utf8_string; - } - utf8_string.resize(target_length); - int converted_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - input_length, utf8_string.data(), target_length, nullptr, nullptr); - if (converted_length == 0) { - return std::string(); - } - return utf8_string; -} diff --git a/windows/runner/utils.h b/windows/runner/utils.h deleted file mode 100644 index 3879d54..0000000 --- a/windows/runner/utils.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef RUNNER_UTILS_H_ -#define RUNNER_UTILS_H_ - -#include -#include - -// Creates a console for the process, and redirects stdout and stderr to -// it for both the runner and the Flutter library. -void CreateAndAttachConsole(); - -// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string -// encoded in UTF-8. Returns an empty std::string on failure. -std::string Utf8FromUtf16(const wchar_t* utf16_string); - -// Gets the command line arguments passed in as a std::vector, -// encoded in UTF-8. Returns an empty std::vector on failure. -std::vector GetCommandLineArguments(); - -#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp deleted file mode 100644 index 60608d0..0000000 --- a/windows/runner/win32_window.cpp +++ /dev/null @@ -1,288 +0,0 @@ -#include "win32_window.h" - -#include -#include - -#include "resource.h" - -namespace { - -/// Window attribute that enables dark mode window decorations. -/// -/// Redefined in case the developer's machine has a Windows SDK older than -/// version 10.0.22000.0. -/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute -#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE -#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 -#endif - -constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; - -/// Registry key for app theme preference. -/// -/// A value of 0 indicates apps should use dark mode. A non-zero or missing -/// value indicates apps should use light mode. -constexpr const wchar_t kGetPreferredBrightnessRegKey[] = - L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; -constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; - -// The number of Win32Window objects that currently exist. -static int g_active_window_count = 0; - -using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); - -// Scale helper to convert logical scaler values to physical using passed in -// scale factor -int Scale(int source, double scale_factor) { - return static_cast(source * scale_factor); -} - -// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. -// This API is only needed for PerMonitor V1 awareness mode. -void EnableFullDpiSupportIfAvailable(HWND hwnd) { - HMODULE user32_module = LoadLibraryA("User32.dll"); - if (!user32_module) { - return; - } - auto enable_non_client_dpi_scaling = - reinterpret_cast( - GetProcAddress(user32_module, "EnableNonClientDpiScaling")); - if (enable_non_client_dpi_scaling != nullptr) { - enable_non_client_dpi_scaling(hwnd); - } - FreeLibrary(user32_module); -} - -} // namespace - -// Manages the Win32Window's window class registration. -class WindowClassRegistrar { - public: - ~WindowClassRegistrar() = default; - - // Returns the singleton registrar instance. - static WindowClassRegistrar* GetInstance() { - if (!instance_) { - instance_ = new WindowClassRegistrar(); - } - return instance_; - } - - // Returns the name of the window class, registering the class if it hasn't - // previously been registered. - const wchar_t* GetWindowClass(); - - // Unregisters the window class. Should only be called if there are no - // instances of the window. - void UnregisterWindowClass(); - - private: - WindowClassRegistrar() = default; - - static WindowClassRegistrar* instance_; - - bool class_registered_ = false; -}; - -WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; - -const wchar_t* WindowClassRegistrar::GetWindowClass() { - if (!class_registered_) { - WNDCLASS window_class{}; - window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); - window_class.lpszClassName = kWindowClassName; - window_class.style = CS_HREDRAW | CS_VREDRAW; - window_class.cbClsExtra = 0; - window_class.cbWndExtra = 0; - window_class.hInstance = GetModuleHandle(nullptr); - window_class.hIcon = - LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); - window_class.hbrBackground = 0; - window_class.lpszMenuName = nullptr; - window_class.lpfnWndProc = Win32Window::WndProc; - RegisterClass(&window_class); - class_registered_ = true; - } - return kWindowClassName; -} - -void WindowClassRegistrar::UnregisterWindowClass() { - UnregisterClass(kWindowClassName, nullptr); - class_registered_ = false; -} - -Win32Window::Win32Window() { - ++g_active_window_count; -} - -Win32Window::~Win32Window() { - --g_active_window_count; - Destroy(); -} - -bool Win32Window::Create(const std::wstring& title, - const Point& origin, - const Size& size) { - Destroy(); - - const wchar_t* window_class = - WindowClassRegistrar::GetInstance()->GetWindowClass(); - - const POINT target_point = {static_cast(origin.x), - static_cast(origin.y)}; - HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); - UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); - double scale_factor = dpi / 96.0; - - HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW, - Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), - Scale(size.width, scale_factor), Scale(size.height, scale_factor), - nullptr, nullptr, GetModuleHandle(nullptr), this); - - if (!window) { - return false; - } - - UpdateTheme(window); - - return OnCreate(); -} - -bool Win32Window::Show() { - return ShowWindow(window_handle_, SW_SHOWNORMAL); -} - -// static -LRESULT CALLBACK Win32Window::WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - if (message == WM_NCCREATE) { - auto window_struct = reinterpret_cast(lparam); - SetWindowLongPtr(window, GWLP_USERDATA, - reinterpret_cast(window_struct->lpCreateParams)); - - auto that = static_cast(window_struct->lpCreateParams); - EnableFullDpiSupportIfAvailable(window); - that->window_handle_ = window; - } else if (Win32Window* that = GetThisFromHandle(window)) { - return that->MessageHandler(window, message, wparam, lparam); - } - - return DefWindowProc(window, message, wparam, lparam); -} - -LRESULT -Win32Window::MessageHandler(HWND hwnd, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - switch (message) { - case WM_DESTROY: - window_handle_ = nullptr; - Destroy(); - if (quit_on_close_) { - PostQuitMessage(0); - } - return 0; - - case WM_DPICHANGED: { - auto newRectSize = reinterpret_cast(lparam); - LONG newWidth = newRectSize->right - newRectSize->left; - LONG newHeight = newRectSize->bottom - newRectSize->top; - - SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, - newHeight, SWP_NOZORDER | SWP_NOACTIVATE); - - return 0; - } - case WM_SIZE: { - RECT rect = GetClientArea(); - if (child_content_ != nullptr) { - // Size and position the child window. - MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, - rect.bottom - rect.top, TRUE); - } - return 0; - } - - case WM_ACTIVATE: - if (child_content_ != nullptr) { - SetFocus(child_content_); - } - return 0; - - case WM_DWMCOLORIZATIONCOLORCHANGED: - UpdateTheme(hwnd); - return 0; - } - - return DefWindowProc(window_handle_, message, wparam, lparam); -} - -void Win32Window::Destroy() { - OnDestroy(); - - if (window_handle_) { - DestroyWindow(window_handle_); - window_handle_ = nullptr; - } - if (g_active_window_count == 0) { - WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); - } -} - -Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { - return reinterpret_cast( - GetWindowLongPtr(window, GWLP_USERDATA)); -} - -void Win32Window::SetChildContent(HWND content) { - child_content_ = content; - SetParent(content, window_handle_); - RECT frame = GetClientArea(); - - MoveWindow(content, frame.left, frame.top, frame.right - frame.left, - frame.bottom - frame.top, true); - - SetFocus(child_content_); -} - -RECT Win32Window::GetClientArea() { - RECT frame; - GetClientRect(window_handle_, &frame); - return frame; -} - -HWND Win32Window::GetHandle() { - return window_handle_; -} - -void Win32Window::SetQuitOnClose(bool quit_on_close) { - quit_on_close_ = quit_on_close; -} - -bool Win32Window::OnCreate() { - // No-op; provided for subclasses. - return true; -} - -void Win32Window::OnDestroy() { - // No-op; provided for subclasses. -} - -void Win32Window::UpdateTheme(HWND const window) { - DWORD light_mode; - DWORD light_mode_size = sizeof(light_mode); - LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, - kGetPreferredBrightnessRegValue, - RRF_RT_REG_DWORD, nullptr, &light_mode, - &light_mode_size); - - if (result == ERROR_SUCCESS) { - BOOL enable_dark_mode = light_mode == 0; - DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, - &enable_dark_mode, sizeof(enable_dark_mode)); - } -} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h deleted file mode 100644 index e901dde..0000000 --- a/windows/runner/win32_window.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef RUNNER_WIN32_WINDOW_H_ -#define RUNNER_WIN32_WINDOW_H_ - -#include - -#include -#include -#include - -// A class abstraction for a high DPI-aware Win32 Window. Intended to be -// inherited from by classes that wish to specialize with custom -// rendering and input handling -class Win32Window { - public: - struct Point { - unsigned int x; - unsigned int y; - Point(unsigned int x, unsigned int y) : x(x), y(y) {} - }; - - struct Size { - unsigned int width; - unsigned int height; - Size(unsigned int width, unsigned int height) - : width(width), height(height) {} - }; - - Win32Window(); - virtual ~Win32Window(); - - // Creates a win32 window with |title| that is positioned and sized using - // |origin| and |size|. New windows are created on the default monitor. Window - // sizes are specified to the OS in physical pixels, hence to ensure a - // consistent size this function will scale the inputted width and height as - // as appropriate for the default monitor. The window is invisible until - // |Show| is called. Returns true if the window was created successfully. - bool Create(const std::wstring& title, const Point& origin, const Size& size); - - // Show the current window. Returns true if the window was successfully shown. - bool Show(); - - // Release OS resources associated with window. - void Destroy(); - - // Inserts |content| into the window tree. - void SetChildContent(HWND content); - - // Returns the backing Window handle to enable clients to set icon and other - // window properties. Returns nullptr if the window has been destroyed. - HWND GetHandle(); - - // If true, closing this window will quit the application. - void SetQuitOnClose(bool quit_on_close); - - // Return a RECT representing the bounds of the current client area. - RECT GetClientArea(); - - protected: - // Processes and route salient window messages for mouse handling, - // size change and DPI. Delegates handling of these to member overloads that - // inheriting classes can handle. - virtual LRESULT MessageHandler(HWND window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Called when CreateAndShow is called, allowing subclass window-related - // setup. Subclasses should return false if setup fails. - virtual bool OnCreate(); - - // Called when Destroy is called. - virtual void OnDestroy(); - - private: - friend class WindowClassRegistrar; - - // OS callback called by message pump. Handles the WM_NCCREATE message which - // is passed when the non-client area is being created and enables automatic - // non-client DPI scaling so that the non-client area automatically - // responds to changes in DPI. All other messages are handled by - // MessageHandler. - static LRESULT CALLBACK WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Retrieves a class instance pointer for |window| - static Win32Window* GetThisFromHandle(HWND const window) noexcept; - - // Update the window frame's theme to match the system theme. - static void UpdateTheme(HWND const window); - - bool quit_on_close_ = false; - - // window handle for top level window. - HWND window_handle_ = nullptr; - - // window handle for hosted content. - HWND child_content_ = nullptr; -}; - -#endif // RUNNER_WIN32_WINDOW_H_ From 6de8e35ba7df13edf880a886317815659868d332 Mon Sep 17 00:00:00 2001 From: warren Date: Thu, 28 May 2026 15:34:32 +0800 Subject: [PATCH 13/16] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=83=A8=E5=88=86provi?= =?UTF-8?q?der=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- .../main/res/mipmap-hdpi/launcher_icon.png | Bin 0 -> 3210 bytes .../main/res/mipmap-mdpi/launcher_icon.png | Bin 0 -> 1784 bytes .../main/res/mipmap-xhdpi/launcher_icon.png | Bin 0 -> 4860 bytes .../main/res/mipmap-xxhdpi/launcher_icon.png | Bin 0 -> 8558 bytes .../main/res/mipmap-xxxhdpi/launcher_icon.png | Bin 0 -> 12517 bytes .../AppIcon.appiconset/Icon-App-50x50@1x.png | Bin 0 -> 1903 bytes .../AppIcon.appiconset/Icon-App-50x50@2x.png | Bin 0 -> 5153 bytes .../AppIcon.appiconset/Icon-App-57x57@1x.png | Bin 0 -> 2266 bytes .../AppIcon.appiconset/Icon-App-57x57@2x.png | Bin 0 -> 6023 bytes .../AppIcon.appiconset/Icon-App-72x72@1x.png | Bin 0 -> 3210 bytes .../AppIcon.appiconset/Icon-App-72x72@2x.png | Bin 0 -> 8558 bytes ios/Runner/SceneDelegate.swift | 6 + macos/.gitignore | 7 + macos/Flutter/Flutter-Debug.xcconfig | 1 + macos/Flutter/Flutter-Release.xcconfig | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 30 + macos/Runner.xcodeproj/project.pbxproj | 729 ++++++++++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 117 +++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + macos/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 68 ++ .../AppIcon.appiconset/app_icon_1024.png | Bin 0 -> 168683 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 0 -> 7279 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 0 -> 351 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 0 -> 18586 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 0 -> 969 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 0 -> 80181 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 0 -> 2739 bytes macos/Runner/Base.lproj/MainMenu.xib | 343 ++++++++ macos/Runner/Configs/AppInfo.xcconfig | 14 + macos/Runner/Configs/Debug.xcconfig | 2 + macos/Runner/Configs/Release.xcconfig | 2 + macos/Runner/Configs/Warnings.xcconfig | 13 + macos/Runner/DebugProfile.entitlements | 12 + macos/Runner/Info.plist | 32 + macos/Runner/MainFlutterWindow.swift | 15 + macos/Runner/Release.entitlements | 8 + macos/RunnerTests/RunnerTests.swift | 12 + web/favicon.png | Bin 0 -> 351 bytes web/icons/Icon-192.png | Bin 0 -> 12517 bytes web/icons/Icon-512.png | Bin 0 -> 80181 bytes web/icons/Icon-maskable-192.png | Bin 0 -> 12517 bytes web/icons/Icon-maskable-512.png | Bin 0 -> 80181 bytes web/index.html | 46 ++ web/manifest.json | 35 + windows/.gitignore | 17 + windows/CMakeLists.txt | 108 +++ windows/flutter/CMakeLists.txt | 109 +++ .../flutter/generated_plugin_registrant.cc | 29 + windows/flutter/generated_plugin_registrant.h | 15 + windows/flutter/generated_plugins.cmake | 30 + windows/runner/CMakeLists.txt | 40 + windows/runner/Runner.rc | 121 +++ windows/runner/flutter_window.cpp | 71 ++ windows/runner/flutter_window.h | 33 + windows/runner/main.cpp | 43 ++ windows/runner/resource.h | 16 + windows/runner/resources/app_icon.ico | Bin 0 -> 1806 bytes windows/runner/runner.exe.manifest | 14 + windows/runner/utils.cpp | 69 ++ windows/runner/utils.h | 19 + windows/runner/win32_window.cpp | 288 +++++++ windows/runner/win32_window.h | 102 +++ 66 files changed, 2654 insertions(+), 1 deletion(-) create mode 100644 android/app/src/main/res/mipmap-hdpi/launcher_icon.png create mode 100644 android/app/src/main/res/mipmap-mdpi/launcher_icon.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/launcher_icon.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png create mode 100644 ios/Runner/SceneDelegate.swift create mode 100644 macos/.gitignore create mode 100644 macos/Flutter/Flutter-Debug.xcconfig create mode 100644 macos/Flutter/Flutter-Release.xcconfig create mode 100644 macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 macos/Runner.xcodeproj/project.pbxproj create mode 100644 macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 macos/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 macos/Runner/AppDelegate.swift create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png create mode 100644 macos/Runner/Base.lproj/MainMenu.xib create mode 100644 macos/Runner/Configs/AppInfo.xcconfig create mode 100644 macos/Runner/Configs/Debug.xcconfig create mode 100644 macos/Runner/Configs/Release.xcconfig create mode 100644 macos/Runner/Configs/Warnings.xcconfig create mode 100644 macos/Runner/DebugProfile.entitlements create mode 100644 macos/Runner/Info.plist create mode 100644 macos/Runner/MainFlutterWindow.swift create mode 100644 macos/Runner/Release.entitlements create mode 100644 macos/RunnerTests/RunnerTests.swift create mode 100644 web/favicon.png create mode 100644 web/icons/Icon-192.png create mode 100644 web/icons/Icon-512.png create mode 100644 web/icons/Icon-maskable-192.png create mode 100644 web/icons/Icon-maskable-512.png create mode 100644 web/index.html create mode 100644 web/manifest.json create mode 100644 windows/.gitignore create mode 100644 windows/CMakeLists.txt create mode 100644 windows/flutter/CMakeLists.txt create mode 100644 windows/flutter/generated_plugin_registrant.cc create mode 100644 windows/flutter/generated_plugin_registrant.h create mode 100644 windows/flutter/generated_plugins.cmake create mode 100644 windows/runner/CMakeLists.txt create mode 100644 windows/runner/Runner.rc create mode 100644 windows/runner/flutter_window.cpp create mode 100644 windows/runner/flutter_window.h create mode 100644 windows/runner/main.cpp create mode 100644 windows/runner/resource.h create mode 100644 windows/runner/resources/app_icon.ico create mode 100644 windows/runner/runner.exe.manifest create mode 100644 windows/runner/utils.cpp create mode 100644 windows/runner/utils.h create mode 100644 windows/runner/win32_window.cpp create mode 100644 windows/runner/win32_window.h diff --git a/.gitignore b/.gitignore index 9b00b0e..9f8609c 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,4 @@ app.*.map.json keystore_base64.txt pubspec.lock - +*.sqlite diff --git a/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f88a11960a3e084168672047b0a87d24dfb42b3e GIT binary patch literal 3210 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 ayzvim?+wRneA*)b0000v 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..149563a092c245a007c845ffb644bad670fdf11d GIT binary patch literal 8558 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..825ee76f6e891a789fc51ec0ee27aa3d1eaf89d1 GIT binary patch literal 12517 zcmds8Wmi>g*G0NRIu50TF!Qxkdy}L?&i7fpYgtqA)XJw z-uv2X%{62BSy@pU1C<071_lO0=7YpX@NegTf01Fq*X8yHWf&M}sf>iEnrFsQCW@Z? z!OOsSsx_-bQzU`xKOLP~osSLw5~lL#tKFD$@!eGzCKKhp(#vUAhyGwPA0l5=kD3am zm$|l+8NGYi5#T81m>%KWbfOd9dGglx;B!sd$+$?}xf5DFx$z>$*_DP7hr&{22yqCn z3-#=FM6MxK`-R`G1o>3)o{W^e?X9@Icylg37PXc|bzu49!g4kimJHe1OI`oCvu%E< z-jxOAjPd#Lk?7UeNA@qU)Ku{AC|`^HU;WUlL`B&T%k!Jz_tOKG=i{xjsw!?-rqt?f zM-?X~de5%@_3os&^OwAo6fI}xs>gdIknW(TRb0iYy_&`_uLO z^QiOc>gsc!fB*3RQ3(47f5{eCfTF9GsHv-~+e0!{@}++Mvi*TcoL5lrr^UmOsHlPo zpOkcG^N9J-DExf{M!Jcut%Ze!o!wHtbbOp+rTg^6>Tm{cjh~LvBE&C z%ZMn-oY`VHRU`P@6+J@&5jPli84!pkRtK1q^&R zKW8ZA;4mR810y367P)}a1f|=XBTKep|JScXp%$yY_m@LsE0|(AM7sy1 zF~p_pdAtIq`XOpcs<`e!nc_+k4-TbsMO z+F`;F9UVhmmE)ynscW+9&mVaS33v+~f(?_6>0*U+u2EiA)_|-1ndAz+=iC#Et--I1 z8l_I3J`v#KA6Z%ANDU1S537Fs$j-rmhI-!NZ!cas!>M9DX~kLjSIPJZ#-HRB8x!KC zVSPnl!Ox#RfBpRFak#V~T|Q0`@W^&H{&{xx+x-m%Od2pEd;YNOTwK>! zr^O#j>gwudW@bE2l9HF1{Vwh?!o-63`50O~pZ2dlZ_+7}2~$y1v%ByA34{sIhv-ZF zMeNxeNROd#{HU$nWVcZ5x-;gN%>K*PN2H(s` zb?%MV8$A(NFHbkCp!C(htDE`U2)W%V|31TAEgV^mKBl2`M)|B-tOU+y+AmbTCPFHI zW^QhHaF7zayEq)i-rjy74kD+h$i+rXfsGBdo_b4E6sTF6oBK5@%YS<$l_pD$B#X<} zHHhZz+hFnFWM*9!Di%dT3{^pVe0&v^uQD>yIyySHf`rFoW@cs#pii-gnZobeDpBXB zx;Li!qEA-ZAGTowJerdO@2?Y+lfOmaL@re2ITm~@`ThGhgdc^H3b}RcV6FxUWjtz~$8yahBO7L{me(OuJ_L_H5J5 z@VgPhQR90Y92^CO(Xo~fGBWQN-zB#*KZQ>{Q-bT#}e=Z=;_XcKtkbdsfb2q4qu5!|S!R^9QXSZ}DQ zML1}gnG=)JER3yD{Nkl=+wa-}0$+}oTg1i0^z`&5Y_Q1R+o*~9uWZoBVUqIEhV{; z5|YL2U$>@C0LIxuzPFt}#yf@+cbA_TxValtixrPnI}AU5d>wB5&U)|Z;o9293LYr} zCK;VnLnDb!G2_eEE)y+fdHIp^f5Qk|9*y{@oa)%}`IEa4?$ECeBDTfuL>~$Q8@u

+{Gwa(=u$1F!}l_~)-*`TBmpf2s$eqQVg}X?1SU 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..405fbe014d8594ab1ae90d36d5291c15c9e6158e GIT binary patch literal 2266 zcmV<02qpK4P)m79x1ATlyCN=r*8CMI;h&&FMdRva<5>^62R3yu7^P;^L&Fr0MBtGcz+LlbM*9NT<_tb8}~AW`37#XmC)W zP>@LEr3)M@NT7qv7#*_4W1T z<>exg=Kp^;1 zR}ch;hK7QJgMWGYi{X5))?#5{5fv46AHC{{1a2EsJV=(O!_6nuQKR%!D<>ggdTU$_&zdX^U zx`H6MaN`VkpvA(%;^@(%IXO9Tad9y*G5!7h`hb#>QX3l^9*-9i5|WXT0YT9F_wPT+ ztd$0qB)ufp&kg{C!9XIBRaI5@?%gXbE!EaQOGpSwyDZEHY}>X?B9U}-bYQXA;o)H{ z4wsOSfI^{|Os1jjr=##it{?GR+NP$aBS(%%Bod`k$!4>uR4NR^o}Qi^9UZy=4-XG6 zmz$B1v3KuY27|F@&mOT@TvSwKbdSq-1r~0Yt_IGWITIBXm6MZm>eQ*2n3#cq0ex6S zMTL!xO@4lUaBy&XdU{w`*y!l!$A5cC!}&$7x*nKJCK8Dh2n9cKe=I2}$;!Ig+S*DY zk@Vy0g2B{OSIgycJRUzZG=#(9AP7RE(F_JdS69ZF8a1~TYF4u_6&^l(2tiP3N$H0V zA2KsD&z$-D^JmWq1cKhx-OcUJojcjt*`cAK$;rtW3`QsvmX(zmW?q;HOS3j&Tz{AF zsh0ES&xeMFrlqA(snp2G$ex}aeL!VprMZPq+%d3HT z?VOw(D{Je@iVCqKz$KxG3bcn;@AP@*Pn@yw9J4%q=V|KoFdtqoCp6Ur|>808}cKrKP1>t!`;)si~>q^Z81pa%yVo!i5VjU%tfS z@p{)CJ9cDeXLC3l-KB$rLqtSGSXh|%x8MF}WJIY{T3T8f)wOZ1W@cuuU%%eGdGp4N z8v_CYAPAz-Xw}u#mo8nB%jJcIg@(Z*kx2K&#l?k2qfsansZ`42@uH)ndwY8U00;~W zG^${i_B^2deE05MVPRorW@dMHcUyaV!_%k7k01Z)E9lMXf*odvJ7cj} zFE6j_*RMM}JJ;9O7ZenDdwbu!d9$IRAv`=>rBdly+1lEcmzS?yyVig2UMDBVtgNg9 z2M!Dj3`nI?fk4oeo~}?R44%stmtEX-Y;4TQ$*HZqT?WJLaC=)@TTxLFm&*kJz}eXu zf}o2RFWTGN-@bjzdF!^u#>T*)AV4!;okF4TRaaMieZ8QnYI1Ut$K$1@rs`h5&}j6| zojdpK+eaV}h(zKy?%#}$jje#IOeWjDeLF!5nM7tX7+-&FE0@b#TU%imZfI!8{UO)$ zub#=t$==@HQ&W>a*VQ2qW@53}-rj!Qx^;Hz*T=@jK6?BJkH`D_`zIwO5sAdzyLWx~ zFxKAQ4#V)ZYu8{I{Oj9aSHKkj0Hv17%1Yg(qoX5H3yDOc)9JRhwq0Faund0kL`ou& z9URag2)equ&d$y@H8u71^|4s2%3YPCqoXhkx3;!kzI^$`ix*0z!pPTxW$gtjEkdDC zuTCI%cz6(eeSOhrv_1@hK&)A_28Q7)SFXS?Y`9G`mFd5WYyI) oth$S`KRT}{J(0j^!_YDM#qHvj+t07*qoM6N<$g2N_r!vFvP literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..70a62bf40e33b79d5eb6446ab3f1246badd16685 GIT binary patch literal 6023 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..f88a11960a3e084168672047b0a87d24dfb42b3e GIT binary patch literal 3210 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/SceneDelegate.swift b/ios/Runner/SceneDelegate.swift new file mode 100644 index 0000000..b9ce8ea --- /dev/null +++ b/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/macos/.gitignore b/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..0a7d3b8 --- /dev/null +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,30 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import file_picker +import flutter_inappwebview_macos +import nsd_macos +import package_info_plus +import share_plus +import shared_preferences_foundation +import sqlite3_flutter_libs +import url_launcher_macos +import wakelock_plus +import webview_flutter_wkwebview + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) + InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) + NsdMacosPlugin.register(with: registry.registrar(forPlugin: "NsdMacosPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) + WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) +} diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..5bfa030 --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,729 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* tele_book.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "tele_book.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* tele_book.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* tele_book.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.teleBook.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/tele_book.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/tele_book"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.teleBook.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/tele_book.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/tele_book"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.teleBook.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/tele_book.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/tele_book"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..df5fec8 --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..b3c1761 --- /dev/null +++ b/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..96d3fee --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "info": { + "version": 1, + "author": "xcode" + }, + "images": [ + { + "size": "16x16", + "idiom": "mac", + "filename": "app_icon_16.png", + "scale": "1x" + }, + { + "size": "16x16", + "idiom": "mac", + "filename": "app_icon_32.png", + "scale": "2x" + }, + { + "size": "32x32", + "idiom": "mac", + "filename": "app_icon_32.png", + "scale": "1x" + }, + { + "size": "32x32", + "idiom": "mac", + "filename": "app_icon_64.png", + "scale": "2x" + }, + { + "size": "128x128", + "idiom": "mac", + "filename": "app_icon_128.png", + "scale": "1x" + }, + { + "size": "128x128", + "idiom": "mac", + "filename": "app_icon_256.png", + "scale": "2x" + }, + { + "size": "256x256", + "idiom": "mac", + "filename": "app_icon_256.png", + "scale": "1x" + }, + { + "size": "256x256", + "idiom": "mac", + "filename": "app_icon_512.png", + "scale": "2x" + }, + { + "size": "512x512", + "idiom": "mac", + "filename": "app_icon_512.png", + "scale": "1x" + }, + { + "size": "512x512", + "idiom": "mac", + "filename": "app_icon_1024.png", + "scale": "2x" + } + ] +} \ No newline at end of file diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..87a61d0c8ed4480f5ce3ffd269bedc1345706cf3 GIT binary patch literal 168683 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�x(tK=#P>YZtU?(T>=H_Bpg2`V(s=uuqmso@ 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 SrUWk%A!{_BLXyQY;=p)2I(EBqFnr zz;4LC4W!w>{Fx7CU?g}k(*FWVDF6(^@ZHF=ObEGeegGk4yWPfdyjU!b$0H)H*Xyq9 z9LEWQ01+R7=XsRUvMg0qwQZXaav3jGRlUiw@B91x{t8w>|&u@2o8ir?5>NKls4v)n@`R=ChtoEeu3N;;0$KE+oL?D7plCP1F zIZ*|!-Q?(ZtblkK9x(vS+B5_D9u<1OXf&Hl3K6oy6PJ&VcK%F>l1 zx~O#%#*B&b-N=>VYAr%x7iZ=CwW1RJoObiqE4e_k*EH$bmC3h3;X)K+$rP#!?Hu%y zC|!=xx0inY12D&@4arn_LWEC1c}f>n}auN!ONj%slVYhLFNY6TOr zYSzF^7N(?BwiHoNuuKR&0xb$%7J*HS_Xct20r?#yCIw!2(K{Lmb>q97&ZFc zT%8BrHst^KVcKYcF0CpS$crE$#2sGa{TWNe=X<;{S#H@+Eyl;o+nD$P)o$+U{BU)C zwmmaiA{l{+s4%-c`4xSS>-N`GT+P2HgfI0jbF#7)mh+QTTwG}^EmxP9#rzK{zI;KI zOEf=vvPO`BgoGFx8cIz~of4ixIYM7M86F)P*%)ikv;8`DX3B{gQ%b`#&{bVfuyx9J zlKYq75dv>O-{29FWT$`Um)8Xab_9&I)41hQ<8Ll}I-*xOcIKKqFHg27CMMp$cMQZw zNuI&r9vZUvW-&H4rm3mf(a~{ma8N*#!iqpe89N$4knI$H=d;>dI7<^qs}WjyV068R z8iaVjzz}kYD^1BkP8*nf^zME3@!;4_&aCB4y|;#j#{T~Pcy>-sPVrsR0P~Tmmt0r_ zR&WvNaNPS60rfdKW}UHADQ#4fp4mBsB{W#HqGU%dh%aB(X7U}7+bxf7&ryTuB3~yQ zaA9Fc;A@kJNj90`;|K|P`(BosZEbIEo;+k^{3PbfFDiP~Jq_4M8Q_uK*ko*wSb z9xgA`G)+8X`8p;uC8gsK(O5l$Q8#Z0vu{-98Q|7wdj#&@z5AK&vp-SZ_wR#Nykwmi zcP5&UP=oH2JSnjqFB-N~-kqu0)G?%f+>wTg2&xahnh|on$7VQEF0b-@uGy0?4I|ko zn}Is~4=amdgV#Clu63rk{}CRpDta?!fSG3i(v1}|Ek{NI;-jHrA}u|as+z`n@wDUK zf|SL|N=FBZh})W^F>=7?cL@n;B9?u2RgO7^{uYebF+#KqD2-7)j`{^WVR+tCcuwxJ=H%h+v{7>om*#*NKQ z8c{E&jgjx&NgRRa2PvXobSUKlj7tJ7@ciE(OmSOG1!*x`g69~ymBuJZn(gspyN<^Fh_`RwHZ)w% z)VS`Ki+}5q>fD*Ga&+vNe|s%((w8Cf>({SV&0gw#1Y*Uec{M6(nO)&!g%vVqL{55o zdTxYopjgB1*LkC+fa|MYhpXoth7AGNIYp$iT|?GxWwCn6wVRt8!!Oj~SC!rDl$6G3 zcX#*jaE|-t*hFeDuE;>4o`8fz(`ddH1{#|ERK@z`>2B}58YLozKj5mm@b&9dG2cZ? z|L1+LY1cMJ^YyA6{9z|Cn3FxR*1Se!4_dLX{FwE1+b#S)TBuj0ysat->2 z+X?IPBaOTidFx6>pv@zCvLl%`X8)Y1S9 zwJ$^IZku2(CV9-t_I7XnW85|rblic#!M@(#_Crd6tZ)|}bXmC`8kdD7RWSTJ{HhRS za@HAFtDf7JF5)@&`!_Q!?ZPcRA%eFc0MY#KcLD|hHLv5`)pXS^@4g4qhmRkjc6XPS zp6onPQtE99!9YPqe)Hx{>Y_4d`?ih?N7OTYmOm`LPV@5-iAsnQD+Hc8+xqBYSmrnU zK#v$g)X~w=Op!o3e*TlpK>pqo9*nycoJU)etnBO_{c~A-`C8*6ymO`q1epcT*4h5w zj8TyUU8L2O6?g`xi)|4xMVZrVQJ$~KD~wg!4%2Y-1^5;pUHlwCQFE1ZTeEhj`-%~ci@} z$Uqqu!piHR#LJ`ad9JH{h0_%~^G5v>T?#5H{mspiDk^buIi=B8q377))URIQQSjSA zk9+<4^^YGvCTdX;=5}^hoI;ar!-q%T)#*oneE#gb(sSR{m8bqG{uBMQzI35-yUDVZ zcyCOEOyM9@D9yJoqQ854|NQyW^M~az(?pH!_!E`>m8N6ICpeD~0n|?{jL5*Cl`GEQqmS`r8@$6)9${gicd zHH&Ix>L%6Ah(MwYc+9D&K{qmN!V~p#)pm;3_u${}EUA!?5CRb)p>q2QUh*L5`^3b= zTU%R(`<&E+>qpzuRo>p-Qi7u_xNYzm(?z`xX6roDhFEY5*dF(c%5_xSWcOv~eGEBL zNSSZ8E97~AUbn#5hiR&UK>Swdwz9Fg$;)SDI#X3sdyV~r5MlmMWKjpRMtFGLtv~D& zgWOvtcJ_eetG%1+OK#g=~j;Hb4 z(>p#;HIl-Q6MlhoW%f#p>2WY3+?rQ+n&1AT#>>V;zv~x|cj$hZDj^Wf#_9<;t9|b4 z*+^0YaWp@}!ovD9o4UGvFSc}UhI3VH14ouVj^R2CQutY59`SMLEZrrm#=AZ(NGYeq z^f^CxB_q>*yg5!nLbAEBK`zkQ%;~q#{JN*7Co&Qfz)s|=+Tc33b(hN%Ha)%Rtu4C( zx-xF=-2U`W3u}>{Qeh^7OiWhi2g|Knluiw;@pnoQmGn%m`wTEGj1M%I&yanJv|CjS7oI_X?^|lm>4Ao2llCA zDCii9)gUdmvC&a)U*FyNCZjKp&`TpY5t34B&cQi3n-8?oO!%$znM|JYa&fIslrmV` zzdrY)m-q)yDYAhCk^bt-m#^c6Iuw_?O;K1;^ruT&QUzE8MFt{lN^ApJTb&m2t@j=~ zHB$7*zMoG82{@n6W!l8&ykqA|r*y zkKalhU0>Z$3y}$#gsJ&(~HJD+KwDa*=#U6Fl^`uL~_X_ey7GvJLjfxe3`GtR~IO%_clj z@i|%(w;G@o69f3+c6^KnV@}?Hf;_mB$Zc!FdOP)JcOrX(%R`pOTQYWblxmNB@^2LwGS#9YN{o_4)bv(b0;AhI2-NYzhhrcQ&Zn2BrlZ?ZT(qXR1xC zg+i4K(i_pjSy@@Px3_m^h+4vwVT;aA{fs8%c%PIYkHI94PRypRt4mHr<-gP(1(;`a zeEiU!ja5xQ<>P;4JYaVzf z_O!eMJqF#)?P8|>e&9kgQefb1u~Cz$=K@dT50-q&j+iEQ);Y;}M3cO{XM_5;=S0>= z6y)U8aOuF?8@#``0vDT`n~t-!?pqV3d3kw%{yba!@sE4%+^EF3C0K8NA1@S1)xbaF zEY25~EeLTYM11cz4i~M(8GYjyLY`754s?s(jbUhEzNhGDXlQukyjE@DcsX*DrK?&M zEU6W|_WMh}ii+5y?{VN0G(GMKHqzA<9-F(7aw$VsubQU)#in1)$-?q1Z$(NKS};Gr zRpS=G56aZ`40erloy zP6F4vD!er=!Ew z)*ISC z)y+0Vg@t0yOEQv@i1EV0k_Fc4^0Hi~fX;ZUkXdz?4AcLvA4r%>j=!q<&r$oIfMn%2 zB5^3ZsfmfV6awO$oT}>TB#w2X0|V90e>;|bMdMS7cFilHEPaYeP;@N`gt03pD{F0Q zOH%NQ6IWhV7BKk|U>W-3536kF;VOGcyMdZ(zf`0Z8G`ay__7Jyc)tHLW`cpRGM`K} zT~&9x9Q&&!OQ`Z>2k(Wt`r+YaTD~oM`+MFAB{M7z~;29or{5m-~S!~!~I>K{yAzDJ3 zwDH46r9W)7-)Qgfu)M6yG~xk5IHNM)+Q-&*6AkC)Pn$~=@f4ob*~(!GpWaWKpMK|1 zZd%>He=i;*0UD;Gqr17eX_fEDx^cR`INcokIo+v}xij0KSZTIrjWs^_yi$lT;O^nF zWC2!l8@2Ss$Npc)va~CohWIz0-5EXzb6NqOsHUct#BNaQf9auwr`3rOTf8${Ctx>G z0%FeAOpQ8`vzVkjz<^ga&;C@b57RRgFYI)X=7dp@+1S_+5fQ<%OlYERE9>e`9jy;% ziup2@pu3^kSXot9*9t48TbY~7O7vvjnLpaefk5$Tu;D07a zIWS4Mu-rC^-^^w)*ETDytBXDDke`=VT~pJW%9sD;%fG3gws4DYqU&_@6wQ&pc}d;k zV_`j2RtAFQ))Y{i%%x!{wFu(TaBhFK*FJrp;IEnI$e5(n3db9xJd7*zpnsTp%Af{x zH2Poq`T3!COraoZ-PV_uHa52YnVZ`^M?qLVut@z4V}ASZUv)!6WiPwd!t@!6e*5J+ zw&;-pNSqBa!|baBCvPVfFv3H*ho-6=CCobBt`FxXeei4W+8=^TfEHAe-<9?^J)P3k z^>AQ7^-*qA-p70UM<2ujuFi6RB8vHTG@`B+yuFU*^&GOQ0H6Rjm{0eZD!lv8pOu~z z*Y%-?H)8J-<3BA3w%Avu^>R{Fcs@uHB=_+lPZSHf=M~La|M1}r-<$jGpAdd6qrLfy zIavpJlEl?)PHO|7;RZOHhnUM-y$89sr&&3)OPYJ_$3Q|m-I+~GOzcx6X5!$;@2Sx<;D2P(Y2&Sv|(4C+XzT!|sOggmj zP5qr~@V&UWaBy&_tgHk+DfvkDeF(2Bd#14a#s_BgrN4ix9Pr-8#jX0Nqla)=_NB*C zje_)rjKCYcj((SxI9pb>`)c{)@*C`DRt!J@sHmvCV**xc$PNx%S9)|B{k&mkRFk`v ze*6bw01(XRs3;+F;wME5-k3aa!HSS|r?^WusUF&W;{!`0Z zV#FpdI{Gk~>m+XsjWGZ4&_KeL&L-U z8Diaq+gDu%YC4Mzq4xbRTzsi_!QRl_h zFbV;OFJHb~o$ZHo#%h)vmLqz5eme*G0MiB^dCUS3^&HuP0- zwLeqk`E&D*a+0B2SKm7^=+Q6PtgoUakemqf)Je4^_x`mXYy~$3a)<=pT-6+|PFLEC z_?^IBZAQ5UA=VkajsMd zI-p{ebV0Re&#)aS~ zk7?JUVQ5Z2ME&uD1yrfSRSv89MykYca)d?DZms(!DVuJE&vEL)LR)Jn4d&1ScF>O> z8$||n-=3$u?f=yJgDgB#BCyeB^iO1DSI2x12dYaD@#|E)>{@|Crg_oboa(bfXfv=G zkM{PoEA4dPV$O%`!xG-NH~waw+$#1&EMZS`lCcn=)ezw0YpSa=y!FKlJ~%uSu%B`} z*_vEYA$wDYQ=>+Sh8Ps(Sg`{XhlzTthKi%{X7`V>>Vs0(CNvBT*j8h+vl-qFFWN}& zSC*G|cXc^AIW7D&vBx%l^$L{2oyEo11U@o)H|xWXKv)1g9F-_3i-}3+;c`c=f97wH z7mwr?R6Ip=PAl8*BnSv0W`x*8!Ai{ebg}SKm*|-n|o3>K| zi@ix892{x;`bBj|d+RCX;kc#JP5lR-E)F3fA?bqft@)}z(7LpdZQ7m2#DP+@wH*Ru zs&~Lh(Cr^2>+^*<5E>DYgS2afRiOcfKnkC&rkh*E0zR>goJd!%V%+@bOwu!{IIa5i z0c1>C?CSc#aKUFoX_E7K+1X1cRMYaYl*h;2Nl=ipjs5~RntnouK@2B0_=mVh*M=AV z9dGXs4h~}AQ#QXBLP6;Md=IE9@7uSPa6Af3<-x~7NGqHN`%g>E%+1}PR+KY@iT>!< zxcr)(HE{ZOPsE>WNxjfk%x#U3mDS#U+jI9uZ@v6|lR=7Bf`Y(;kz~Q(drVrV3!>xS zIoQ2GYx~kf?jZ&WZM9HPu4SW1lRkblsPov~7|o;L;+k(t!f~k$>CcqNOi%aQ7-3dd zSMTwV%XtlYocs0i;-AO~lGeOR)$Dv|hYu#kmGr4Ey2;O>3-g&>~v)0;C#7+D`J%gM=s8MCN< zWSm`@p1w8kS-P+w+-_zqEhS}f_g3ZT*vfFV6=X8Fr!^=(XXjG?3oEl}Qy8Dt!?`rH z7Uy>)4ZMYFX6rl@6&1}G=W1((X=pM*fjQii;Ca&Y7d!JV&I$*Z18pfCC3U9wfldJd zja|ym{Fqxb@K{!CB^7BAzUh0~8T&p~4&w*DNIjXJJ2M(uT1^AP6Pnp zwn+jkoccFq>xa|W<7h`lgpEHyp#YA=W>EXmSSFeF^{4JP&wjjfL~Ej8dyNRbq97OR z%p#!+X_;v7J%zUfk;2Bo!oiHk?r4pRWh-=LjZDUfAx3U^>no7HBKQxM$`$D2^4(=^ zehWJ7dl);1uvcYt9XW11*N+N91oPOuuvCJZVZoA zrMrXzaS(^2++@Y`_1@Pjz3V)d(4Xw>?G5zP!I1)Z7Rt!*i&wO#%~|6mzx`Z=(;)q6 z(@m|kL7aTvxO%oU^8{FKPIh*7k)g(m7nBkX^=WC-gxz$!!g#y)@T4Rg)N>;vBmZ{B z6&uu@fP@u_Jvcl~k&5(p2Q4&}(S)oqJV@&`D7I8WE?>at(Dts#&7*u_Xczz!dthMT zKM~uXqdOnn!J%MD?4J;zn}w$6<(X$L?kpBDxN~>+yF!1xc_tJlDX-_7Gv5(Kl2MX} zIJ$Kc_J^T>kB_gI#4hH4!COS#9KRJ>&yCfIWC&QT4Zs16CgcfAv+TRsp74AyC(4#V zG^bCC2qUXaS44_9ZlGJM`ONNoF9Oh>pwrW;j<$<#HDt_*-~0WcVF8E#*44GfpMvmZ zSZ>jq%w>l8S5j8?ygiDfudfe>^s%w+U2lSi1EEie2TQA~t5MO=i12xviL+ow2f_;K zn^J|LH2r)s@fU^Jb1gGS&Wq4h<5{GKke;yoZ3a~9!oomPV3YUZ3cNQsF#KnzJ1~rX zR#eQuV*R%|zltTdltlCdn5T#`!GA(WMFsQs?UlNZ&(va@JiC6ir0|jKop+3HI=Ym% zV(!){ms$i(kRUkf1cpd51eLITEoVsY-}-~DHoo*Q@OJz5bRMLU-rV7J&bsH&S8vW& zX%rQ^u}BzSYG`29B6UkkC2JxfR(jJs7Z#An#p#%bx&Soz`ugHz>eTKNuB>C}zW;@1 zsuIK~oh6Vb`R>&NgcJ<@lcS@0poyhsozp*UBQ#m7WL0PQ`=t<2=5FS6CY;&zNBB}A~+Q&(Igb^)YjAktG) z7qFkOqOh31f9D$=opBvBVfH~NGS>O9!Tt^a%2QTF(|sT;O>S*x_aQx9)FEA(eCG<^ zPvJqSWBv5R4~={P*~Y6FX2;;cT%MhwVqhHhNIpZybJNtKv9T{4Zwi!>GTB>fo8S%` z55d|SShyu6pZPrb_@1L!o)-z?bWPH=9W?<5ftZ<@iKP^3om*&GUcP&KL*aVv0@BqC zJRaJaNGk>Q;N`)H#n#riza1j%$r6Ep+`xI+%9PAi&HxAwikr)I2OcKHDdm)Qi=V@I z^Eg|=LEb9HvUl`*hf3+L>@|5I}^2p#|3vN#$DJh6f>9XfLb2Wv; zXB>h!s=h%ha)(8TYP0sMP$P3>5oO~ z2m%^q1qDIhlgEdTUuwSB&-yYV%*{PC+s5#3O)WB{-<AnAmXksJowhOwsUtfge+PZvwCiAO)ojG z6Jbjmfr=SAvUr?g^3#YjNkJ0Bg&t&6>{7-cNkX|7W{H>x`$=MBGqd4Jd%eDd%(V2JgrV63axRXCsW^>;K7Wq?@dMj72U{T7-j2YQBq+&xEJ6J6l&oPQG@J!>~SC(Am_( zgTKl)g%aRS3YS?2h&7;8(>lnpzV!RgE6~-|RaWi;r>rJj;kReSAtQ?IX0tBtQ|hNz zEZ4JupChrUgKjO2or% zd+H~kOu7**J-s?Gh|&Zci}dP1(_DmYtVJ(We)O(kOd;k+K>;O~-x;t2u+>~Q#|l5P zYPs<3Ad{1ccxCjUPyqXO%<)u(M&e)hfF@G%s$R02z;vl+*)UDkxA)cSYWK zbY+Ko)~_BQk=5$qU$TV!N-d z{6G~39mN@I?%ppBxoWAoN1!O>wDx*o+=yT;Bv# zn-gbc&1XJ+C@CkmGV)zrrXTIW&}s8)l*Y!!7<|M+Xjg*7%|48){rS6*Vo0b>MD^R) zyQxgA(=Sz3*NSts4ApK#Z-scGJ~|b%@$d|{w~usp+d_?lI~k$vt!nJ6e}Dh{e5StkL`UhwP}Y&T1baP#d507U`sG578=c8F zAEaF~5CZ{Tuc?5P%n(jM<6u?}7#Spwy}doaSa+d1e~^nEfYRU9(qaN8B5)j7$_W*y zC>Z!4Id21tbY2e2V1T{`@Qakg@Hyc{d3pIGrPQONb@iJhhRK~~FoYozCZv=eDJ*X+ zF=#vm3Z3Wfq`^tU+hPKyCXt2msKw(FHcW6!n_c9L{qKy@HueN~B@8xJ7M8QO z0S@P1f|{Bn0*-RV4Qt)30YHWh7ygcuX-K%}N#V&=O8Zt=*z2*&AdaB}Gyv@vXk#;l zb^kE!*taoCB2%rLL$paGX+wuMFqvGTZJ+9~u!JKf}UqX9c z@JKHZ!dqsh7Q}%(jWDIR$~R~Y3}&r|zfl2q>|dZ&*8&hcS@!B|u-}-I-)@4Ckg&Ol zI;M!+cDx`%$Tku8{mTzYgvf19Po5wPaWTmkJ)l1SAh^?Ys%2tx)&T9B_ZLePN(9b> zbIW$-vi~WOtgJjM)I%9lj%#zLMr7sWxGuHtv+40;plyqI?a>xb9BOeGDWwbU_hn2! zdsaA^)CR5z(RT(be%a?c@{6mhRo;gd;{Lni#hGz&aplp`kLkl(s@=n~QoP|IgRTJ2 zjs}O9jw{FM(`Poji+i5PydV4QA-vfR(g;`VJu&GSEI-x+D>|0gMI z1WS7KF7wE?aK2PmPfJg~0*I%SCO|VJVHk-g?cPnLCPpA_f_sj+2b)&yws5oBO@a)oS=O> z!BG0!Aw*mE^nqZIfe~;94ue{^>x*MHy(&g$>iYx;ufvrXXzcw(Saukwqa!154-w6F$9jnEb>S{b~421Kt^@cb+` zM|Sp58vw-jgata~9Z5^Xt6!O(6~e zAL!=ha`!(m_!R;Hrpe;sBB)yX2M0?#NG&>+47Ge#16e$leMjch|B&`5G|9ppXR1qG zUYX%H{3Xct#M*sTLudIs8d(B4QYtWYamUUYjo67dV*oCJgN0S&wVz#Fyqhf>T{3Jm z@YNWG5OhhfLl<|GCnMdkfD8h9_z)l8V(BX%_vrB8;7J^_mGIeDY(|~8LaU6=(`}zp zF31PXi(L%%;HK+MbZBoQB~mxkkOFuI)dl2rA3#XpnlG85&|;=?nzWAQt3f=)fssJT zqI|U{rQC5|9NN(n9st6Yy{Q^%QX?Jw7s~&X&~7YehUH}ii@qte#@2hMo+QxD(GxyM z)p~SGsh#JkbjbHM3R?L3`g&M+_!Mq=%We$_3=nFeq@*-#@Ok*W?fi<7jg5+oY;Ae@ zmp78WnqjHO_7p%Y0lurS8S-R5L)uP7<6S?@O|nIEgudA?vLzJ+(Td%T5KaI(nDjni zd~~Vh_-PhQR68?lU`5zGadUH{7F8gZm{aree8qj|?p*-Sj+U14sw>}eb5+v#w;__; zJA#EO8f-k_`RdXA1+GTtG^80vQ}Np>RAk<)sP9B3WZ#C@A=ZTa9{UZxoc}fS4+!vj zZcl|V?#ieA_%XEz6xDXDU@@mpzPZ83-`%}GQ>3%v>0f>`px&p)$6`(klIkH%)x5}A z)GllCN3oRZ=&YMqhLZFe$bz9S5enalf8heKL~eP7f+2Wk6!r{^ksdIxVNfWlCg_9NDHIuN$0RKPYLI(87o=7v2#r2f7-@mNb$Yfhha9GsUp}PstL5R@8i) z#2bW{F^QUi0o7ctl8%ly88X@L$;717Bhc(lcevD%J|-mz#A9)y;A3JMbCLn*?pRSq zyp}@2h{vQwz_@CcqduNeN<7e66WZ6+t- z%V=zo?=b7r9=Uj8wq-9LyVcHbx<|gxw26&_Bb5JoLON6E%;czq&`*pFFYpdhwJD&} zBzD8>VQbVF7O6qHM~@^ZTR;bS|Nea}6;6YnP&OpAxNMPow13(Fp5+)T)Z=*g5Maq2 z5|TEZpbQbxTfEQsWS_wJ0#JK=bOgkEc6PS3l!+dZp-ds_wKwB^sBfST+WdqO&sS;j z9MbSbh$q$8i~6C|eCYJnuad#PQbS$1K&EE^PBv9rQ*)oxjl8VIo=HJ1~2qdap_yH`Hn``;%RfEs*o7dg9 zeGc5uPnK4m*t;NtwBrIx9|bi>ikPrId6VeJDn&d$C;w;xpGp{>*jp-*v5pS(DBKl= z!`@G;LEr~eZcmf4og6^zxt{dI=_cN&z02=%R1HKR+}zy(19U~)7vROQcJ=qa2ABlT z*UZ$ES|Z>%rsoRs$=ai-7pNyM>Xnd|lo2-`#?8_Nt6BzacIc4rT?bqSw(-|NtEs6e zNT0z|g~I{l3Yr0rvxsVl8uZ4CI-kUyUtVTPc<#t(YlBje@KTOaY1yYb+sbOXvM@Tj z8z=z)M#R(eVE&)qzA-+0xD47bJoewee?wFxn>YFRUW{A_MI&u+CTqO*lNl_;VtV+w zgwo5p@jMYuswh_?N->{12u{E1-qtW2`51C$Hn!;D1kT&y&!0=}&JVtJCmJ;PxL=1K zPsY_`YSR(+GTmchVtTAoW)77CN$3^&H3kAUiQCDWYmC;`*3>N7yPS17G{W`Q>Ktgk<4IUC;F)S;#`CWW_e1V)l?en?y=QjUOV+!VopAc0#(=*3>+ihtnkihZoS% z`VTW9>T9vO;>XHNr|o(+qOYx84HN`uP#m>=mDS)9NW6=`j4XC&t?wJnj#* zkO0H@{TmX)ua;l=4UuQvgM^^0ESH(>wp`rQPuHZxL{fgkK};zIhMdsQOYnFnOH8EU z7y*M1(*|1PJO2N0;^Qi`K7P2<-O2LK{{G^Eg6GLU272m*g@r@V35IC0ii?ZqjZ{>m z3_q!c`wlxd5_|o8ohNn?19+TJ|9e;e&#aOx*r%YETs=@d;aIaB8ys}@@R0iz0ylVe zb~aI64Sj6rMZ0IFz5nx&y^=#gE^LcpN@4F%K>W&3DtzhAjK5r=cgqnS7S+)U#{iE2=MXY;o!9S zh3-|uIgo?p?(;zcqDfdn9@Dc(qRL80k#0{_KJ4VhwnJYzLT}rH2<>^a_EIAXp)0ae z0!I{tC_N$B0=EwE79t~HGk{Tk4^Vz^^Ruoaxja=!tNe^D)NZV=ufKZsawKKg(qX>Q zKuP%{zBJ_Rp}m@znE0M--3PWD;CdD4><1DhEEbPNkClh#GDxcs97IhxD}4<+Edn;I zLA_VJis-wdq8%^;4D|Iuc|-eb9z@aVSuzmdg6vk$1L)sdN=gcHJ0>QJb+>cN|5nn4 zT*LLtUO`Ay5_NnXHJq5RBswcr%!P_j+--fPCLjGmUK&^|>{t_&NW!g%XS|%atSmo2 z3BTGH`K`{%!b%MX08Zy-r>h)tzkM4W8Iu%@n}@aypw*)_6l=N?%>$0nXy6=l!q|mV z3T=H<6H#;3yX(*=N8imEa|VL>z5FXZb<$6>pS;X#*x~jY!rY!lbMO zoma1x)5gJ!3Q)>>W@u=rrR6!0B?a9U@<-(4b<6BZEoo&hsUhvnl8X! z#C%VTqx#f*bsVrU_=wVkBQ>s;K=$Cg5ReV1?*h(T z<7R!b|MCYQvH;iV?CAI;=)45d10Ub0)k`mTrDN>OkIv2l($dl+qatSISsy-(d{at; zN}Um#v9-0Ku+Z^v+%x!xeWXxdQo;#`nOYyfbKQZOa(ZC&8Ob1{5$g3nt9RftN+-$R zm3>VOgN)So#E{gcph(Nen0<^)F9!IQ z6G;9{=;aPt!5u(L%-}SkXHVk|5|k5@d}R8B0SN(8+f%_xs?5m01)AhSADk`c5Q~|Q8`mUc8oCX;mpl|T z$kTO+`$FL2ywIpBmNPo{Yp#+@#?i(x>(Miv`#J;!aUaFSsYXV+%J2gI^!N5cM-mBK z6g|{0vAFY8Ti5tIlqDW7KR-A8;vE?u@4WX}4b=*JFPb?+dAIQEsGcH@61ic&u_z2D zpzz>S0~Pq)`@JB~*qc^hhl+un1cVGCA~?LI1%w7XOfX!`EiE6#k%eP+*H%;+?4zmOo6Dtw*kEr)k zgNE|gjg3*?qf%08UN?$wgtxPg5^mAnsBxPr1uZQtL5jzXT&_v#m~0It(G;DR`}NIB zJ-xJ~qzH$9bNAKMCV=?Fy^GAS4v+UlVBt zWENB7`j2YB;hd?xOG8$@4Iv@e_}91LBAlFr8o%gevLK7}pH@=bbYsf7F_~M{%KL(z z3K8`4C*L0$zc%Osa8^NA!(jA#!bKURpnB5)dCDBjIPC38ge^czqn-bZVLkPYGMVYM6Qcljo8|GthG zDzJM*kfy%=)tZ{7uC5OxXf-v*HU)||14c}Woa+$@m86`wfnYmKl$x2CJUiv!npl?G@QQ+%ob+za(`ca@?Hj>86f&6j+ zBgp%k0SG|iD3rKuz}{22TrKi7%k(Pk_MJ|fPudHjxzsSi=NbaiS18TILAt7yGwhfwaW*P!)S1d4P$J~c>=Kh13I`I zdX-zia-h7gFV;8K)|m41hO|q)Iq7~7s{dmM!OHvbgPyLDVc=9zEYY{43r!c;TNd`w!^p=^-`%`CeI`Ck&q1gX;#uo;$s60h_aW z`Bg0)uo*J6=0zo*L_Q(sl#r%IaEpHq-;Xk z!w0JBn|b3+62oSSMk+@0s)~v@Kk`cwI&K2iJNEfg~t*N>hj z!ztb}kL!!O-ASN?hwxc|-3nSM7VgZSQdG$S(41l^u{xe4(yb`4fCYZ@j!qhELB^Pj z#y^u^pgjIdH`38bJy45~%W;R1GcfRuxjij)9dAJWg-uLRY`wk@CJ+9NN9G&xSMXzD zF79*0#K7=(baVXeg-^WxKp;bV2EwZu_sq;>v9q;xVnW+Z4mvWGsMovKjo=}g+}_ke zII2@G=ZuCdWP=@G44>HrE~Y7U{51fZqqlDX4F5;)R5G*GnHLiSC%)>|+M4)@ zbNA2PyB{Zb#JYnxz4jK8DaK*S!fqcKA$R#6cb`XBN-EZV(Ku0ni%(~79NBH#bT-I( z;~8kk#(M2$7uLE=a5s_ zwEuHO#fRmWX&*iuEwua>$H(Usw1%E-{=)fF^gHx@ttL5Mp!dZu;9>IMd%q16@XHrs&2g|IL4tV;NcHe>1 z*4HEF&J*C44id6JM-Ep z7q3&msmRK5qPVV^^p(o~`UTeIWR>Hs+PfsUTILJI87TKyZWYdu-nUr0#0w~##LAv9s8L7auuB@?&P z0o^Q9)cXz9r-uv-!}(eqC@3f->Ljk}JQP%5{8iw8N%-5A&A-MP5zhs zAwp{0N`xsXDdR=54C|y?Ufygiow$)RekXG3lmJ4wCLSY|-= z5_kXsaaUK@2Tna%jz(1NB9T@xjCC~KT?#oXHF93RvcyOK{lS{{9Lut726X3pb>{5j zW|2aa9f4jQyD zFVHoj5{9V12}34l`}96F^?ho%_G=T_hY!`&6KY&n;LsEuL*xFV!_O*NSy{#aUqSa{ zW?``ztGuO-98J4xgdEw=LrngdF1MiIF+V?aezhti33yWa?`J!@ByiE&AV12^zVnFp z>{PN~uHcL~D-7AU4k$P{Tkr)2K(MwL8lBnO{qDb}p%)hy4;)&%C!mUY7S+|?FTleC zQImq2uV|NDxHEFew`&-x>s2q{U<;)AZ^D!k1>uD?!0y@xOvb=x*8vy@&}*Z9c7dfw zJOFXr-@~v!cGHOz#VvgBg%8W8=vXv zSnvEzzr;FqoQZs@pkP&l-@%3h|Aq=$F+PpBkKh@TF=MeN%tUnvI#}`QYG{n+#$}4% zy?f0i-ob_mFXhr+Qn6n5)Rbflo5%h?{oFN)7Rtin|6#uP-}5`? z%;8=Xd}pC|fIGl<1&Bl<2%+Ea|B6Q_6lP~<`+UA{0pK_u2n0kT(dp^w)YR1VC}m}3 zDwV3VwAAnSi$tQ1j*i;_P!!$X-pCDZ|y??(L$BE_TWwlz}(9ke5GXo*CSS%z-K6?B} zqtT3wjk#SehG8ZrCru{P#>U3YfFH%;rKP3D#zp`@FnFTTXr4X$*<>=|IPP#bP!#og zy+)(a;c#fRT83dhH4_40Hk%(ltjGv}01$~plFvm&MS8uS<2Z_9Xf%6!d&!SJpRd2aU$57T#bS=* zz8)bB;CdR7aHOE1fF#M?-QB~(!@Rsa0KoC_F-_AXN!o0-a47U|-AJZ@>xja_Lb+W2 zyV01%pRCX>mAhKBfj{@U7_R4Tn)1@U-XC=@<<`ZN#- z&@?TR$@=>GMn*=Gft*g~?Ck8p!GYawmrA8SDHK24yBCYa)6P*E0L!vUrP6A((li|o zhvjm)L?Yq1$o%}g&2IY;|1j|Dz&qc&%F4>~^Yev;g`Aw6>gwtj0|P5|dq#l8#l_Xt z)vBs0g+ftLQGuc;%d*jE^udF&H{=@tz|ql>*XzBwxTveEtEs8k-`{t;-7i$CM0PEJlT rS#=9QN@r(hxAOnjXYw83e**Xj(9fVueQZ6n00000NkvXXu0mjfWcS!= literal 0 HcmV?d00001 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000000000000000000000000000000000000..59b2256193ab7415573d04eecfc3444ebcbfde3c GIT binary patch literal 80181 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 literal 0 HcmV?d00001 diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..a57b18f --- /dev/null +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = tele_book + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.teleBook + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..dfa7cee39e6f19e82c90ae917615118b0fac4b43 GIT binary patch literal 351 zcmV-l0igbgP){_BLXyQY;=p)2I(EBqFnr zz;4LC4W!w>{Fx7CU?g}k(*FWVDF6(^@ZHF=ObEGeegGk4yWPfdyjU!b$0H)H*Xyq9 z9LEWQ01+R7=XsRUvMg0qwQZXaav3jGRlUiw@B91x{tg*G0NRIu50TF!Qxkdy}L?&i7fpYgtqA)XJw z-uv2X%{62BSy@pU1C<071_lO0=7YpX@NegTf01Fq*X8yHWf&M}sf>iEnrFsQCW@Z? z!OOsSsx_-bQzU`xKOLP~osSLw5~lL#tKFD$@!eGzCKKhp(#vUAhyGwPA0l5=kD3am zm$|l+8NGYi5#T81m>%KWbfOd9dGglx;B!sd$+$?}xf5DFx$z>$*_DP7hr&{22yqCn z3-#=FM6MxK`-R`G1o>3)o{W^e?X9@Icylg37PXc|bzu49!g4kimJHe1OI`oCvu%E< z-jxOAjPd#Lk?7UeNA@qU)Ku{AC|`^HU;WUlL`B&T%k!Jz_tOKG=i{xjsw!?-rqt?f zM-?X~de5%@_3os&^OwAo6fI}xs>gdIknW(TRb0iYy_&`_uLO z^QiOc>gsc!fB*3RQ3(47f5{eCfTF9GsHv-~+e0!{@}++Mvi*TcoL5lrr^UmOsHlPo zpOkcG^N9J-DExf{M!Jcut%Ze!o!wHtbbOp+rTg^6>Tm{cjh~LvBE&C z%ZMn-oY`VHRU`P@6+J@&5jPli84!pkRtK1q^&R zKW8ZA;4mR810y367P)}a1f|=XBTKep|JScXp%$yY_m@LsE0|(AM7sy1 zF~p_pdAtIq`XOpcs<`e!nc_+k4-TbsMO z+F`;F9UVhmmE)ynscW+9&mVaS33v+~f(?_6>0*U+u2EiA)_|-1ndAz+=iC#Et--I1 z8l_I3J`v#KA6Z%ANDU1S537Fs$j-rmhI-!NZ!cas!>M9DX~kLjSIPJZ#-HRB8x!KC zVSPnl!Ox#RfBpRFak#V~T|Q0`@W^&H{&{xx+x-m%Od2pEd;YNOTwK>! zr^O#j>gwudW@bE2l9HF1{Vwh?!o-63`50O~pZ2dlZ_+7}2~$y1v%ByA34{sIhv-ZF zMeNxeNROd#{HU$nWVcZ5x-;gN%>K*PN2H(s` zb?%MV8$A(NFHbkCp!C(htDE`U2)W%V|31TAEgV^mKBl2`M)|B-tOU+y+AmbTCPFHI zW^QhHaF7zayEq)i-rjy74kD+h$i+rXfsGBdo_b4E6sTF6oBK5@%YS<$l_pD$B#X<} zHHhZz+hFnFWM*9!Di%dT3{^pVe0&v^uQD>yIyySHf`rFoW@cs#pii-gnZobeDpBXB zx;Li!qEA-ZAGTowJerdO@2?Y+lfOmaL@re2ITm~@`ThGhgdc^H3b}RcV6FxUWjtz~$8yahBO7L{me(OuJ_L_H5J5 z@VgPhQR90Y92^CO(Xo~fGBWQN-zB#*KZQ>{Q-bT#}e=Z=;_XcKtkbdsfb2q4qu5!|S!R^9QXSZ}DQ zML1}gnG=)JER3yD{Nkl=+wa-}0$+}oTg1i0^z`&5Y_Q1R+o*~9uWZoBVUqIEhV{; z5|YL2U$>@C0LIxuzPFt}#yf@+cbA_TxValtixrPnI}AU5d>wB5&U)|Z;o9293LYr} zCK;VnLnDb!G2_eEE)y+fdHIp^f5Qk|9*y{@oa)%}`IEa4?$ECeBDTfuL>~$Q8@u

+{Gwa(=u$1F!}l_~)-*`TBmpf2s$eqQVg}X?1SU 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

+{Gwa(=u$1F!}l_~)-*`TBmpf2s$eqQVg}X?1SU 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 + + + + + + + + + + + + + + + + + + + tele_book + + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..654758a --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "tele_book", + "short_name": "tele_book", + "start_url": ".", + "display": "standalone", + "background_color": "#hexcode", + "theme_color": "#hexcode", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} \ No newline at end of file diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 0000000..0ee9434 --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(tele_book LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "tele_book") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..0d1cf9d --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,29 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); + NsdWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("NsdWindowsPluginCApi")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + Sqlite3FlutterLibsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..0546ea5 --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,30 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + flutter_inappwebview_windows + nsd_windows + permission_handler_windows + share_plus + sqlite3_flutter_libs + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 0000000..9fc1eab --- /dev/null +++ b/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "tele_book" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "tele_book" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "tele_book.exe" "\0" + VALUE "ProductName", "tele_book" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 0000000..a8259fb --- /dev/null +++ b/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"tele_book", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..32de11cf7bc906ad909c2095394de9c87936dc7c GIT binary patch literal 1806 zcmV+p2l4m-0096205C8B0000W0Qd#~02TlM0EtjeM-2)Z3IG5A4M|8uQUCw|FaQ7m zFbDzw007uvZqNV#2ER!}K~#90?U!v#Q&}2-Pul_8k}0?(Gef8`Ks1U4OIs|FVK8Mx zM;2|2EtHm6T6W~4+F*i$L8JZvcEK3af&~>}Yg<4|w}wweMusr0owjycoIw<=iB73a z5GduF-d%fl38Nbd1KHh4cz!g^$vN+N&N=se?>(P@|1&=MUkiN5x{x>4g}kvYxTsZxZQ4@hx6yppFVxspf_Y>WN0)R06=8qPMPdhXJ=68WcZzx`(a ze)ZqfE|+V`*_ISSqENXxIm5%lPN$R2X4`Bw0DwRs7#$r|tJNbTBZY;9N~N-~vElgf z6O)sZH8nL{F8ATXhww6zl9Gagf%HwA?Y=&~$;rtUizOf+AU{7pEiJ9P zySt&G0eD8j0TUAwX*60(OUvNk;BOHE0K9$sHak1}#fujUEgFrcv$GS8MrUOm(`vQ( z`T0#vP1RM^Xf)blu_PoUNF!b&(Dm!r zsZ^?5E*FVJO-(AbTK(?bI|zc@Za0}ss;H@si>$ZDJi*o_wJ!XhX8m#*@g`pPj89^c&DTvu0j>eQ*s%*?K?E}2ZW zXklw4gaC-ef4aNBfAF9Shr{LN<>~eM?Cfm)kNWtpzJe2QqI9zRQt)xm~Fc{+E;$~)MVq;?g1X-=txVX5Xp&=|5%j5Af zGc#vrXS=(*`F#GG2>}RlLy*6}e{F5;z_Wp_@4hQ8F825LKXKxCpT$yCbXF>r{;MYf zG3kNHWR{ngx3sh{7!14J9upHoB9UrpYJ!4-+-~=(gghCBn3|lVQmLV#p4G& zSFir^%bVM`@1&%pUc7kmz<~oi9uF?VzP`RFRxzrtD zDIu53wQ19)&6_uq$z%$JLZ{P-M54`R)9dv*oi05+oe)7NEGYQwv(HH+G62BG$H#8B z|D~+#szeeQ5g`x=qNAhnc)Z18(dl&eI_{MemsqXVrK^DVEPLglsi_G*#E_7XsHiB| zFFZVaVqyY=!61=HxZX@pPZt&x^!N80jmG=;@0XUA4h{~kWgYqe2!dXCP%4#h3xPmT zsZ?QMVE}+PZ{Bb?oPU1*uPElKgg&OPuW#G7Z98`CK%tO*eNSd*XIB|;4X)Dj^Ydo2 wdG&E%e$RLN{|@Uy-dGp%#=4L<)`h(B4|4Af$83DsBLDyZ07*qoM6N<$f~aP1NB{r; literal 0 HcmV?d00001 diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 0000000..3cb7146 --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,69 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + // First, find the length of the string with a safe upper bound (CWE-126). + // UNICODE_STRING_MAX_CHARS (32767) is the maximum length of a UNICODE_STRING. + int input_length = static_cast(wcsnlen(utf16_string, UNICODE_STRING_MAX_CHARS)); + // Now use that bounded length to determine the required buffer size. + // When an explicit length is passed, WideCharToMultiByte does not include + // the null terminator in its returned size. + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, nullptr, 0, nullptr, nullptr); + std::string utf8_string; + if (target_length == 0 || static_cast(target_length) > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ From 6d90790fb3f52c247246280292f74564f89bacc8 Mon Sep 17 00:00:00 2001 From: warren Date: Mon, 3 Aug 2026 16:33:03 +0800 Subject: [PATCH 14/16] =?UTF-8?q?pref(forui)=20=E6=9B=B4=E6=8D=A2ui?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=EF=BC=8C=E6=94=B9=E4=B8=BA=E6=9B=B4=E4=BC=98?= =?UTF-8?q?=E9=9B=85=E7=9A=84forui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Runner.xcodeproj/project.pbxproj | 48 +- .../xcshareddata/xcschemes/Runner.xcscheme | 18 + lib/common/widget/error_widget.dart | 64 +- lib/common/widget/f_adaptive_dialog.dart | 103 +++ lib/common/widget/f_sheet_content.dart | 67 ++ lib/common/widget/f_text.dart | 24 + lib/common/widget/task_item_widget.dart | 40 +- lib/core/db/app_database.dart | 16 +- lib/core/db/app_database.g.dart | 169 ++++- lib/feature/book/model/table/book_table.dart | 5 + .../book/repository/book_repository.dart | 231 +++--- .../book/service/book_image_service.dart | 97 +++ .../book/ui/provider/book_form_provider.dart | 4 + .../ui/provider/book_form_provider.g.dart | 2 +- .../book/ui/provider/book_page_provider.dart | 24 +- .../ui/provider/book_page_provider.g.dart | 2 +- .../book/ui/provider/book_provider.dart | 34 +- .../book/ui/provider/book_provider.g.dart | 2 +- lib/feature/book/ui/view/book_form_view.dart | 171 +++-- lib/feature/book/ui/view/book_list_view.dart | 667 +++++++++++------- lib/feature/book/ui/view/book_page_view.dart | 150 ++-- .../book/ui/view/book_picker_view.dart | 118 ++-- .../ui/provider/collection_book_provider.dart | 6 +- .../provider/collection_book_provider.g.dart | 2 +- .../ui/provider/collection_provider.dart | 4 +- .../ui/provider/collection_provider.g.dart | 2 +- .../ui/view/collection_book_view.dart | 166 +++-- .../collection/ui/view/collection_view.dart | 532 +++++++++----- .../download/ui/view/download_list_view.dart | 164 +++-- .../download_form_bottom_sheet_widget.dart | 85 --- .../ui/widget/download_task_sheet_widget.dart | 36 +- .../ui/provider/export_batch_provider.dart | 113 +++ .../export_batch_provider.freezed.dart | 295 ++++++++ .../ui/provider/export_batch_provider.g.dart | 107 +++ .../ui/provider/export_single_provider.dart | 82 +++ .../export_single_provider.freezed.dart | 289 ++++++++ .../ui/provider/export_single_provider.g.dart | 107 +++ .../ui/view/export_batch_form_view.dart | 383 +++++----- .../ui/view/export_single_form_view.dart | 218 +++--- .../ui/viewmodel/export_batch_viewmodel.dart | 4 +- lib/feature/main/view/main_view.dart | 29 +- .../ui/provider/parse_archive_provider.dart | 26 +- .../parse_archive_provider.freezed.dart | 45 +- .../ui/provider/parse_archive_provider.g.dart | 4 +- .../parse_batch_archive_provider.dart | 107 +-- .../parse_batch_archive_provider.freezed.dart | 50 +- .../parse_batch_archive_provider.g.dart | 2 +- .../ui/provider/parse_batch_image_folder.dart | 35 +- .../parse_batch_image_folder.freezed.dart | 50 +- .../provider/parse_batch_image_folder.g.dart | 2 +- .../ui/provider/parse_batch_pdf_provider.dart | 63 +- .../parse_batch_pdf_provider.freezed.dart | 50 +- .../provider/parse_batch_pdf_provider.g.dart | 2 +- .../ui/provider/parse_form_provider.dart | 39 +- .../provider/parse_image_folder_provider.dart | 73 +- .../parse_image_folder_provider.freezed.dart | 45 +- .../parse_image_folder_provider.g.dart | 4 +- .../parse/ui/provider/parse_pdf_provider.dart | 26 +- .../ui/provider/parse_pdf_provider.g.dart | 4 +- .../parse/ui/provider/parse_web_provider.dart | 17 + .../ui/provider/parse_web_provider.g.dart | 2 +- .../parse/ui/view/parse_archive_view.dart | 126 +++- .../ui/view/parse_batch_archive_view.dart | 265 ++++--- .../view/parse_batch_image_folder_view.dart | 238 ++++--- .../parse/ui/view/parse_batch_pdf_view.dart | 242 ++++--- .../parse/ui/view/parse_form_view.dart | 301 ++++---- .../ui/view/parse_image_folder_view.dart | 140 ++-- lib/feature/parse/ui/view/parse_pdf_view.dart | 129 +++- lib/feature/parse/ui/view/parse_web_view.dart | 205 ++++-- lib/main.dart | 27 +- macos/Flutter/Flutter-Debug.xcconfig | 1 + macos/Flutter/Flutter-Release.xcconfig | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + macos/Runner.xcodeproj/project.pbxproj | 102 ++- .../contents.xcworkspacedata | 3 + pubspec.yaml | 2 + 76 files changed, 4881 insertions(+), 2229 deletions(-) create mode 100644 lib/common/widget/f_adaptive_dialog.dart create mode 100644 lib/common/widget/f_sheet_content.dart create mode 100644 lib/common/widget/f_text.dart create mode 100644 lib/feature/book/service/book_image_service.dart delete mode 100644 lib/feature/download/ui/widget/download_form_bottom_sheet_widget.dart create mode 100644 lib/feature/export/ui/provider/export_batch_provider.dart create mode 100644 lib/feature/export/ui/provider/export_batch_provider.freezed.dart create mode 100644 lib/feature/export/ui/provider/export_batch_provider.g.dart create mode 100644 lib/feature/export/ui/provider/export_single_provider.dart create mode 100644 lib/feature/export/ui/provider/export_single_provider.freezed.dart create mode 100644 lib/feature/export/ui/provider/export_single_provider.g.dart diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 2d346ce..bc7cb92 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 7A07A4ADD59CDFBF626C66F9 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2313121B4CEE6A8FDF62CC94 /* Pods_RunnerTests.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -52,6 +53,7 @@ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7761669B9BB3378222FA1675 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7A02C1B51900805359BB2217 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; @@ -72,6 +74,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, B6B4894B7BD412271BA4F633 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -98,6 +101,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -198,13 +202,15 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 0FAAAE3FD367FFA7A864DA02 /* [CP] Embed Pods Frameworks */, - B0CA94AB8D604043F6F08669 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -238,6 +244,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -318,23 +327,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - B0CA94AB8D604043F6F08669 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; CEDD2AE7CF389940B93D98E0 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -500,7 +492,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.1; - PRODUCT_BUNDLE_IDENTIFIER = com.dorkytiger.tele_book; + PRODUCT_BUNDLE_IDENTIFIER = com.dorkytiger.telebook; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -688,7 +680,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.1; - PRODUCT_BUNDLE_IDENTIFIER = com.dorkytiger.tele_book; + PRODUCT_BUNDLE_IDENTIFIER = com.dorkytiger.telebook; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -716,7 +708,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.1; - PRODUCT_BUNDLE_IDENTIFIER = com.dorkytiger.tele_book; + PRODUCT_BUNDLE_IDENTIFIER = com.dorkytiger.telebook; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -758,6 +750,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e3773d4..c3fedb2 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + AlertDialog( - title: Text('错误详情'), - content: SingleChildScrollView( - child: Text(stackTrace.toString()), + Expanded( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: .start, + + children: [ + Text('发生错误',style: context.theme.typography.body.xl2.copyWith( + fontWeight: .w600, + color: context.theme.colors.error, + height: 1.5 + ),), + SizedBox(height: 8), + Text('$errorMessage',style: context.theme.typography.body.sm.copyWith( + color: context.theme.colors.destructive + ),), + SizedBox(height: 8), + if (stackTrace != null) + FButton( + onPress: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('错误详情'), + content: SingleChildScrollView( + child: Text(stackTrace.toString()), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('关闭'), + ), + ], + ), + ); + }, + child: Text("查看详情"), ), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: Text('关闭'), - ), - ], - ), - ); - }, - child: Text("查看详情"), + ], + ), ), + ), + SizedBox(height: 8), if (onRetry != null) ElevatedButton(onPressed: onRetry, child: Text('重试')), diff --git a/lib/common/widget/f_adaptive_dialog.dart b/lib/common/widget/f_adaptive_dialog.dart new file mode 100644 index 0000000..d2eb276 --- /dev/null +++ b/lib/common/widget/f_adaptive_dialog.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:forui/forui.dart'; + +class FAdaptiveDialog extends StatelessWidget { + final FDialogStyleDelta style; + final Animation? animation; + final Widget title; + final Widget body; + final List actions; + const FAdaptiveDialog({ + required this.title, + required this.body, + required this.actions, + this.style = const .context(), + this.animation, + super.key, + }); + @override + Widget build(BuildContext context) => FDialog.adaptive( + style: style, + animation: animation, + horizontalBuilder: (context, style) { + final touch = context.platformVariant.touch; + return Padding( + padding: touch + ? const .symmetric(horizontal: 16, vertical: 18) + : const .symmetric(horizontal: 16, vertical: 14), + child: Column( + crossAxisAlignment: .start, + mainAxisSize: .min, + children: [ + Padding( + padding: touch + ? const .only(left: 8, right: 8, bottom: 9) + : const .only(bottom: 5), + child: DefaultTextStyle.merge( + style: style.titleTextStyle, + child: title, + ), + ), + Flexible( + child: Padding( + padding: touch + ? const .only(left: 8, right: 8, bottom: 20) + : const .only(bottom: 16), + child: DefaultTextStyle.merge( + style: style.bodyTextStyle, + child: body, + ), + ), + ), + Row( + mainAxisAlignment: .end, + spacing: touch ? 10 : 8, + children: touch + ? [for (final action in actions) Expanded(child: action)] + : actions, + ), + ], + ), + ); + }, + verticalBuilder: (context, style) { + final touch = context.platformVariant.touch; + return Padding( + padding: touch + ? const .symmetric(horizontal: 16, vertical: 18) + : const .symmetric(horizontal: 16, vertical: 14), + child: Column( + crossAxisAlignment: .start, + mainAxisSize: .min, + children: [ + Padding( + padding: touch + ? const .only(left: 8, right: 8, bottom: 9) + : const .only(left: 8, right: 8, bottom: 5), + child: DefaultTextStyle.merge( + style: style.titleTextStyle, + child: title, + ), + ), + Flexible( + child: Padding( + padding: touch + ? const .only(left: 8, right: 8, bottom: 20) + : const .only(left: 8, right: 8, bottom: 16), + child: DefaultTextStyle.merge( + style: style.bodyTextStyle, + child: body, + ), + ), + ), + Column( + mainAxisSize: .min, + spacing: touch ? 10 : 8, + children: actions, + ), + ], + ), + ); + }, + ); +} \ No newline at end of file diff --git a/lib/common/widget/f_sheet_content.dart b/lib/common/widget/f_sheet_content.dart new file mode 100644 index 0000000..a980241 --- /dev/null +++ b/lib/common/widget/f_sheet_content.dart @@ -0,0 +1,67 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:forui/forui.dart'; + +class FSheetContent extends StatelessWidget { + final FLayout side; + final Widget child; + + const FSheetContent({super.key, required this.side, required this.child}); + + @override + Widget build(BuildContext context) { + return Container( + height: .infinity, + width: .infinity, + decoration: BoxDecoration( + color: context.theme.colors.background, + border: side.vertical + ? .symmetric( + horizontal: BorderSide(color: context.theme.colors.border), + ) + : .symmetric( + vertical: BorderSide(color: context.theme.colors.border), + ), + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const .symmetric(horizontal: 15, vertical: 8.0), + child: child, + ), + ); + } + + static Widget drag() { + return Center( + child: Container( + margin: const EdgeInsets.only(top: 8, bottom: 4), + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.grey[400], + borderRadius: BorderRadius.circular(2), + ), + ), + ); + } + + static Widget title(BuildContext context, String title) { + return Text( + title, + style: context.theme.typography.display.xl2.copyWith( + fontWeight: .w600, + color: context.theme.colors.foreground, + height: 1.5, + ), + ); + } + + static Widget subTitle(BuildContext context, String subTitle) { + return Text( + subTitle, + style: context.theme.typography.body.sm.copyWith( + color: context.theme.colors.mutedForeground, + ), + ); + } +} diff --git a/lib/common/widget/f_text.dart b/lib/common/widget/f_text.dart new file mode 100644 index 0000000..a984836 --- /dev/null +++ b/lib/common/widget/f_text.dart @@ -0,0 +1,24 @@ +import 'package:flutter/cupertino.dart'; +import 'package:forui/forui.dart'; + +class FText { +static Widget title(BuildContext context,String title){ + return Text( + title, + style: context.theme.typography.display.xl2.copyWith( + fontWeight: .w600, + color: context.theme.colors.foreground, + height: 1.5, + ), + ); + } + + static Widget subTitle(BuildContext context, String subTitle) { + return Text( + subTitle, + style: context.theme.typography.body.sm.copyWith( + color: context.theme.colors.mutedForeground, + ), + ); + } +} \ No newline at end of file diff --git a/lib/common/widget/task_item_widget.dart b/lib/common/widget/task_item_widget.dart index 141eda6..c3430bc 100644 --- a/lib/common/widget/task_item_widget.dart +++ b/lib/common/widget/task_item_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:forui/forui.dart'; import 'package:tele_book/common/widget/network_image_widget.dart'; class TaskItemWidget extends StatelessWidget { @@ -21,39 +22,12 @@ class TaskItemWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return ListTile( - onTap: onTap, - contentPadding: EdgeInsets.zero, - leading: NetworkImageWidget(imageUrl: coverUrl), - title: Text( - title, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyLarge, - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("状态: $status", style: Theme.of(context).textTheme.bodySmall), - SizedBox(height: 8), - Row( - children: [ - Expanded( - child: LinearProgressIndicator( - value: progress, - borderRadius: BorderRadius.circular(4), - ), - ), - SizedBox(width: 8), - Text( - "${(progress * 100).toStringAsFixed(1)}%", - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - ], - ), - trailing: trailing, + return FItem( + onPress: onTap, + prefix: NetworkImageWidget(imageUrl: coverUrl), + title: Text(title, maxLines: 2), + subtitle: Text("$status ${(progress * 100).toStringAsFixed(1)}%"), + suffix: trailing, ); } } diff --git a/lib/core/db/app_database.dart b/lib/core/db/app_database.dart index cdc1333..f01bd61 100644 --- a/lib/core/db/app_database.dart +++ b/lib/core/db/app_database.dart @@ -29,7 +29,21 @@ class AppDatabase extends _$AppDatabase { : super((executor ?? _openConnection())); @override - int get schemaVersion => 1; + int get schemaVersion => 2; + + @override + MigrationStrategy get migration => MigrationStrategy( + onUpgrade: (migrator, from, to) async { + // 破坏性更新:删除所有表并重建 + await customStatement('PRAGMA foreign_keys = OFF'); + final tableNames = allTables.map((t) => t.actualTableName).toList(); + for (final name in tableNames) { + await customStatement('DROP TABLE IF EXISTS "$name"'); + } + await migrator.createAll(); + await customStatement('PRAGMA foreign_keys = ON'); + }, + ); static QueryExecutor _openConnection() { return driftDatabase( diff --git a/lib/core/db/app_database.g.dart b/lib/core/db/app_database.g.dart index f09640c..2539a3c 100644 --- a/lib/core/db/app_database.g.dart +++ b/lib/core/db/app_database.g.dart @@ -36,6 +36,26 @@ class $BookTableTable extends BookTable type: DriftSqlType.string, requiredDuringInsert: true, ).withConverter>($BookTableTable.$converterlocalSubPaths); + static const VerificationMeta _coverSubPathMeta = const VerificationMeta( + 'coverSubPath', + ); + @override + late final GeneratedColumn coverSubPath = GeneratedColumn( + 'cover_sub_path', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + late final GeneratedColumnWithTypeConverter?, String> + previewSubPaths = GeneratedColumn( + 'preview_sub_paths', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($BookTableTable.$converterpreviewSubPathsn); static const VerificationMeta _readCountMeta = const VerificationMeta( 'readCount', ); @@ -77,6 +97,8 @@ class $BookTableTable extends BookTable id, name, localSubPaths, + coverSubPath, + previewSubPaths, readCount, currentPage, createdAt, @@ -104,6 +126,15 @@ class $BookTableTable extends BookTable } else if (isInserting) { context.missing(_nameMeta); } + if (data.containsKey('cover_sub_path')) { + context.handle( + _coverSubPathMeta, + coverSubPath.isAcceptableOrUnknown( + data['cover_sub_path']!, + _coverSubPathMeta, + ), + ); + } if (data.containsKey('read_count')) { context.handle( _readCountMeta, @@ -148,6 +179,16 @@ class $BookTableTable extends BookTable data['${effectivePrefix}local_sub_paths'], )!, ), + coverSubPath: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}cover_sub_path'], + ), + previewSubPaths: $BookTableTable.$converterpreviewSubPathsn.fromSql( + attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}preview_sub_paths'], + ), + ), readCount: attachedDatabase.typeMapping.read( DriftSqlType.int, data['${effectivePrefix}read_count'], @@ -170,12 +211,20 @@ class $BookTableTable extends BookTable static JsonTypeConverter2, String, List> $converterlocalSubPaths = const StringListConverter(); + static JsonTypeConverter2, String, List> + $converterpreviewSubPaths = const StringListConverter(); + static JsonTypeConverter2?, String?, List?> + $converterpreviewSubPathsn = JsonTypeConverter2.asNullable( + $converterpreviewSubPaths, + ); } class BookTableData extends DataClass implements Insertable { final int id; final String name; final List localSubPaths; + final String? coverSubPath; + final List? previewSubPaths; final int readCount; final int currentPage; final DateTime createdAt; @@ -183,6 +232,8 @@ class BookTableData extends DataClass implements Insertable { required this.id, required this.name, required this.localSubPaths, + this.coverSubPath, + this.previewSubPaths, required this.readCount, required this.currentPage, required this.createdAt, @@ -197,6 +248,14 @@ class BookTableData extends DataClass implements Insertable { $BookTableTable.$converterlocalSubPaths.toSql(localSubPaths), ); } + if (!nullToAbsent || coverSubPath != null) { + map['cover_sub_path'] = Variable(coverSubPath); + } + if (!nullToAbsent || previewSubPaths != null) { + map['preview_sub_paths'] = Variable( + $BookTableTable.$converterpreviewSubPathsn.toSql(previewSubPaths), + ); + } map['read_count'] = Variable(readCount); map['current_page'] = Variable(currentPage); map['created_at'] = Variable(createdAt); @@ -208,6 +267,12 @@ class BookTableData extends DataClass implements Insertable { id: Value(id), name: Value(name), localSubPaths: Value(localSubPaths), + coverSubPath: coverSubPath == null && nullToAbsent + ? const Value.absent() + : Value(coverSubPath), + previewSubPaths: previewSubPaths == null && nullToAbsent + ? const Value.absent() + : Value(previewSubPaths), readCount: Value(readCount), currentPage: Value(currentPage), createdAt: Value(createdAt), @@ -225,6 +290,10 @@ class BookTableData extends DataClass implements Insertable { localSubPaths: $BookTableTable.$converterlocalSubPaths.fromJson( serializer.fromJson>(json['localSubPaths']), ), + coverSubPath: serializer.fromJson(json['coverSubPath']), + previewSubPaths: $BookTableTable.$converterpreviewSubPathsn.fromJson( + serializer.fromJson?>(json['previewSubPaths']), + ), readCount: serializer.fromJson(json['readCount']), currentPage: serializer.fromJson(json['currentPage']), createdAt: serializer.fromJson(json['createdAt']), @@ -239,6 +308,10 @@ class BookTableData extends DataClass implements Insertable { 'localSubPaths': serializer.toJson>( $BookTableTable.$converterlocalSubPaths.toJson(localSubPaths), ), + 'coverSubPath': serializer.toJson(coverSubPath), + 'previewSubPaths': serializer.toJson?>( + $BookTableTable.$converterpreviewSubPathsn.toJson(previewSubPaths), + ), 'readCount': serializer.toJson(readCount), 'currentPage': serializer.toJson(currentPage), 'createdAt': serializer.toJson(createdAt), @@ -249,6 +322,8 @@ class BookTableData extends DataClass implements Insertable { int? id, String? name, List? localSubPaths, + Value coverSubPath = const Value.absent(), + Value?> previewSubPaths = const Value.absent(), int? readCount, int? currentPage, DateTime? createdAt, @@ -256,6 +331,10 @@ class BookTableData extends DataClass implements Insertable { id: id ?? this.id, name: name ?? this.name, localSubPaths: localSubPaths ?? this.localSubPaths, + coverSubPath: coverSubPath.present ? coverSubPath.value : this.coverSubPath, + previewSubPaths: previewSubPaths.present + ? previewSubPaths.value + : this.previewSubPaths, readCount: readCount ?? this.readCount, currentPage: currentPage ?? this.currentPage, createdAt: createdAt ?? this.createdAt, @@ -267,6 +346,12 @@ class BookTableData extends DataClass implements Insertable { localSubPaths: data.localSubPaths.present ? data.localSubPaths.value : this.localSubPaths, + coverSubPath: data.coverSubPath.present + ? data.coverSubPath.value + : this.coverSubPath, + previewSubPaths: data.previewSubPaths.present + ? data.previewSubPaths.value + : this.previewSubPaths, readCount: data.readCount.present ? data.readCount.value : this.readCount, currentPage: data.currentPage.present ? data.currentPage.value @@ -281,6 +366,8 @@ class BookTableData extends DataClass implements Insertable { ..write('id: $id, ') ..write('name: $name, ') ..write('localSubPaths: $localSubPaths, ') + ..write('coverSubPath: $coverSubPath, ') + ..write('previewSubPaths: $previewSubPaths, ') ..write('readCount: $readCount, ') ..write('currentPage: $currentPage, ') ..write('createdAt: $createdAt') @@ -289,8 +376,16 @@ class BookTableData extends DataClass implements Insertable { } @override - int get hashCode => - Object.hash(id, name, localSubPaths, readCount, currentPage, createdAt); + int get hashCode => Object.hash( + id, + name, + localSubPaths, + coverSubPath, + previewSubPaths, + readCount, + currentPage, + createdAt, + ); @override bool operator ==(Object other) => identical(this, other) || @@ -298,6 +393,8 @@ class BookTableData extends DataClass implements Insertable { other.id == this.id && other.name == this.name && other.localSubPaths == this.localSubPaths && + other.coverSubPath == this.coverSubPath && + other.previewSubPaths == this.previewSubPaths && other.readCount == this.readCount && other.currentPage == this.currentPage && other.createdAt == this.createdAt); @@ -307,6 +404,8 @@ class BookTableCompanion extends UpdateCompanion { final Value id; final Value name; final Value> localSubPaths; + final Value coverSubPath; + final Value?> previewSubPaths; final Value readCount; final Value currentPage; final Value createdAt; @@ -314,6 +413,8 @@ class BookTableCompanion extends UpdateCompanion { this.id = const Value.absent(), this.name = const Value.absent(), this.localSubPaths = const Value.absent(), + this.coverSubPath = const Value.absent(), + this.previewSubPaths = const Value.absent(), this.readCount = const Value.absent(), this.currentPage = const Value.absent(), this.createdAt = const Value.absent(), @@ -322,6 +423,8 @@ class BookTableCompanion extends UpdateCompanion { this.id = const Value.absent(), required String name, required List localSubPaths, + this.coverSubPath = const Value.absent(), + this.previewSubPaths = const Value.absent(), this.readCount = const Value.absent(), this.currentPage = const Value.absent(), this.createdAt = const Value.absent(), @@ -331,6 +434,8 @@ class BookTableCompanion extends UpdateCompanion { Expression? id, Expression? name, Expression? localSubPaths, + Expression? coverSubPath, + Expression? previewSubPaths, Expression? readCount, Expression? currentPage, Expression? createdAt, @@ -339,6 +444,8 @@ class BookTableCompanion extends UpdateCompanion { if (id != null) 'id': id, if (name != null) 'name': name, if (localSubPaths != null) 'local_sub_paths': localSubPaths, + if (coverSubPath != null) 'cover_sub_path': coverSubPath, + if (previewSubPaths != null) 'preview_sub_paths': previewSubPaths, if (readCount != null) 'read_count': readCount, if (currentPage != null) 'current_page': currentPage, if (createdAt != null) 'created_at': createdAt, @@ -349,6 +456,8 @@ class BookTableCompanion extends UpdateCompanion { Value? id, Value? name, Value>? localSubPaths, + Value? coverSubPath, + Value?>? previewSubPaths, Value? readCount, Value? currentPage, Value? createdAt, @@ -357,6 +466,8 @@ class BookTableCompanion extends UpdateCompanion { id: id ?? this.id, name: name ?? this.name, localSubPaths: localSubPaths ?? this.localSubPaths, + coverSubPath: coverSubPath ?? this.coverSubPath, + previewSubPaths: previewSubPaths ?? this.previewSubPaths, readCount: readCount ?? this.readCount, currentPage: currentPage ?? this.currentPage, createdAt: createdAt ?? this.createdAt, @@ -377,6 +488,14 @@ class BookTableCompanion extends UpdateCompanion { $BookTableTable.$converterlocalSubPaths.toSql(localSubPaths.value), ); } + if (coverSubPath.present) { + map['cover_sub_path'] = Variable(coverSubPath.value); + } + if (previewSubPaths.present) { + map['preview_sub_paths'] = Variable( + $BookTableTable.$converterpreviewSubPathsn.toSql(previewSubPaths.value), + ); + } if (readCount.present) { map['read_count'] = Variable(readCount.value); } @@ -395,6 +514,8 @@ class BookTableCompanion extends UpdateCompanion { ..write('id: $id, ') ..write('name: $name, ') ..write('localSubPaths: $localSubPaths, ') + ..write('coverSubPath: $coverSubPath, ') + ..write('previewSubPaths: $previewSubPaths, ') ..write('readCount: $readCount, ') ..write('currentPage: $currentPage, ') ..write('createdAt: $createdAt') @@ -1015,6 +1136,8 @@ typedef $$BookTableTableCreateCompanionBuilder = Value id, required String name, required List localSubPaths, + Value coverSubPath, + Value?> previewSubPaths, Value readCount, Value currentPage, Value createdAt, @@ -1024,6 +1147,8 @@ typedef $$BookTableTableUpdateCompanionBuilder = Value id, Value name, Value> localSubPaths, + Value coverSubPath, + Value?> previewSubPaths, Value readCount, Value currentPage, Value createdAt, @@ -1054,6 +1179,17 @@ class $$BookTableTableFilterComposer builder: (column) => ColumnWithTypeConverterFilters(column), ); + ColumnFilters get coverSubPath => $composableBuilder( + column: $table.coverSubPath, + builder: (column) => ColumnFilters(column), + ); + + ColumnWithTypeConverterFilters?, List, String> + get previewSubPaths => $composableBuilder( + column: $table.previewSubPaths, + builder: (column) => ColumnWithTypeConverterFilters(column), + ); + ColumnFilters get readCount => $composableBuilder( column: $table.readCount, builder: (column) => ColumnFilters(column), @@ -1094,6 +1230,16 @@ class $$BookTableTableOrderingComposer builder: (column) => ColumnOrderings(column), ); + ColumnOrderings get coverSubPath => $composableBuilder( + column: $table.coverSubPath, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get previewSubPaths => $composableBuilder( + column: $table.previewSubPaths, + builder: (column) => ColumnOrderings(column), + ); + ColumnOrderings get readCount => $composableBuilder( column: $table.readCount, builder: (column) => ColumnOrderings(column), @@ -1131,6 +1277,17 @@ class $$BookTableTableAnnotationComposer builder: (column) => column, ); + GeneratedColumn get coverSubPath => $composableBuilder( + column: $table.coverSubPath, + builder: (column) => column, + ); + + GeneratedColumnWithTypeConverter?, String> get previewSubPaths => + $composableBuilder( + column: $table.previewSubPaths, + builder: (column) => column, + ); + GeneratedColumn get readCount => $composableBuilder(column: $table.readCount, builder: (column) => column); @@ -1177,6 +1334,8 @@ class $$BookTableTableTableManager Value id = const Value.absent(), Value name = const Value.absent(), Value> localSubPaths = const Value.absent(), + Value coverSubPath = const Value.absent(), + Value?> previewSubPaths = const Value.absent(), Value readCount = const Value.absent(), Value currentPage = const Value.absent(), Value createdAt = const Value.absent(), @@ -1184,6 +1343,8 @@ class $$BookTableTableTableManager id: id, name: name, localSubPaths: localSubPaths, + coverSubPath: coverSubPath, + previewSubPaths: previewSubPaths, readCount: readCount, currentPage: currentPage, createdAt: createdAt, @@ -1193,6 +1354,8 @@ class $$BookTableTableTableManager Value id = const Value.absent(), required String name, required List localSubPaths, + Value coverSubPath = const Value.absent(), + Value?> previewSubPaths = const Value.absent(), Value readCount = const Value.absent(), Value currentPage = const Value.absent(), Value createdAt = const Value.absent(), @@ -1200,6 +1363,8 @@ class $$BookTableTableTableManager id: id, name: name, localSubPaths: localSubPaths, + coverSubPath: coverSubPath, + previewSubPaths: previewSubPaths, readCount: readCount, currentPage: currentPage, createdAt: createdAt, diff --git a/lib/feature/book/model/table/book_table.dart b/lib/feature/book/model/table/book_table.dart index f7eb341..029cff0 100644 --- a/lib/feature/book/model/table/book_table.dart +++ b/lib/feature/book/model/table/book_table.dart @@ -8,6 +8,11 @@ class BookTable extends Table { TextColumn get localSubPaths => text().map(const StringListConverter())(); + TextColumn get coverSubPath => text().nullable()(); + + TextColumn get previewSubPaths => + text().nullable().map(const StringListConverter())(); + IntColumn get readCount => integer().withDefault(const Constant(0))(); IntColumn get currentPage => integer().withDefault(const Constant(0))(); diff --git a/lib/feature/book/repository/book_repository.dart b/lib/feature/book/repository/book_repository.dart index 799d71f..9740b4f 100644 --- a/lib/feature/book/repository/book_repository.dart +++ b/lib/feature/book/repository/book_repository.dart @@ -1,50 +1,32 @@ import 'dart:io'; +import 'package:drift/drift.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path/path.dart' as p; +import 'package:tele_book/common/config/global_config.dart'; import 'package:tele_book/core/db/app_database.dart'; import 'package:tele_book/core/util/failure_util.dart'; import 'package:tele_book/core/util/result_util.dart'; import 'package:tele_book/feature/book/datasource/local/book_local_datasource.dart'; import 'package:tele_book/feature/book/enum/book_sort.dart'; import 'package:tele_book/feature/book/model/dto/save_as_book_dto.dart'; +import 'package:tele_book/feature/book/service/book_image_service.dart'; import 'package:uuid/uuid.dart'; -import '../../../common/config/global_config.dart'; +/// 保存步骤枚举,用于 UI 分步展示进度 +enum SaveStep { + generateCover, + generatePreview, + saveOriginal, + saveDatabase; -// ── 顶层函数(供 compute() 在 Isolate 中调用)───────────── - -class _CopyBookArgs { - final String bookId; - final String destDirPath; - final List srcPaths; - final Function(int current,int total)? onProgress; - - _CopyBookArgs({ - required this.bookId, - required this.destDirPath, - required this.srcPaths, - this.onProgress, - }); -} - -/// 在后台 Isolate 中复制文件,返回存储用的相对路径列表 -Future> _copyBookFiles(_CopyBookArgs args) async { - final dir = Directory(args.destDirPath); - await dir.create(recursive: true); - final relPaths = []; - for (var i = 0; i < args.srcPaths.length; i++) { - final src = File(args.srcPaths[i]); - if (!await src.exists()) { - throw FileSystemException('source file not found', args.srcPaths[i]); - } - final fileName = i.toString().padLeft(7, '0'); - await src.copy('${args.destDirPath}/$fileName'); - relPaths.add('${args.bookId}/$fileName'); - args.onProgress?.call(i + 1, args.srcPaths.length); - } - return relPaths; + String get label => switch (this) { + SaveStep.generateCover => '生成封面图', + SaveStep.generatePreview => '生成预览图', + SaveStep.saveOriginal => '保存原图', + SaveStep.saveDatabase => '保存数据', + }; } /// 在后台 Isolate 中递归删除目录列表 @@ -64,7 +46,8 @@ final bookRepositoryProvider = Provider((ref) { class BookRepository { final AppDatabase _db; - late final BookLocalDatasource _bookLocalDatasource = _db.bookLocalDatasource; + late final BookLocalDatasource _bookLocalDatasource = + _db.bookLocalDatasource; BookRepository(this._db); @@ -100,29 +83,20 @@ class BookRepository { final book = await _bookLocalDatasource.getById(id); if (book == null) return Result.failure(BusinessFailure(message: '书籍不存在')); - // 先删 DB 记录 await _db.transaction(() async { await _bookLocalDatasource.deleteById(id); }); - // 计算要删的目录(把文件清理放到后台 Isolate) + // 通过 localSubPaths 推算 bookId 目录 final bookDirs = {}; for (final subPath in book.localSubPaths) { - if (p.isAbsolute(subPath)) { - bookDirs.add(p.dirname(subPath)); - } else { - final normalized = subPath.replaceAll('\\', '/'); - final segments = normalized - .split('/') - .where((e) => e.isNotEmpty) - .toList(); - if (segments.isNotEmpty) { - bookDirs.add(p.join(GlobalConfig.booksDir.path, segments.first)); - } + final normalized = subPath.replaceAll('\\', '/'); + final segments = normalized.split('/').where((e) => e.isNotEmpty).toList(); + if (segments.isNotEmpty) { + bookDirs.add(p.join(GlobalConfig.booksDir.path, segments.first)); } } - // 后台 Isolate 删除目录,不阻塞主线程 if (bookDirs.isNotEmpty) { await compute(_deleteBookDirs, bookDirs.toList()); } @@ -130,85 +104,134 @@ class BookRepository { return Result.success(null); } - /// 保存单本书:文件复制在后台 Isolate,DB 写入在主线程 + /// 保存单本书:封面 → 预览图 → 原图 → DB Future> saveAsBook( SaveAsBookDto dto, { - Function(int current,int total)? onProgress, + void Function(SaveStep step, int current, int total)? onStepProgress, }) async { final bookId = const Uuid().v4(); - final destDirPath = '${GlobalConfig.booksDir.path}/$bookId'; + final bookDir = '${GlobalConfig.booksDir.path}/$bookId'; + final originalDir = '$bookDir/original'; + final previewDir = '$bookDir/preview'; + final coverPath = '$bookDir/cover.jpg'; try { - // ① 文件复制放后台 Isolate - final relPaths = await compute( - _copyBookFiles, - _CopyBookArgs( - bookId: bookId, - destDirPath: destDirPath, - srcPaths: dto.paths, - onProgress: onProgress, - ), + // ① 生成封面图 + onStepProgress?.call(SaveStep.generateCover, 0, 1); + await BookImageService.generateCover(dto.paths.first, coverPath); + onStepProgress?.call(SaveStep.generateCover, 1, 1); + + // ② 生成预览图 + onStepProgress?.call(SaveStep.generatePreview, 0, dto.paths.length); + await BookImageService.generatePreviewBatch( + srcPaths: dto.paths, + destDir: previewDir, + onProgress: (current, total) { + onStepProgress?.call(SaveStep.generatePreview, current, total); + }, + ); + + // ③ 复制原图(Isolate) + onStepProgress?.call(SaveStep.saveOriginal, 0, dto.paths.length); + final relPaths = await BookImageService.copyOriginals( + dto.paths, + originalDir, + bookId, ); + onStepProgress?.call(SaveStep.saveOriginal, dto.paths.length, dto.paths.length); - // ② DB 写入(快,主线程即可) + // ④ 写入数据库 + onStepProgress?.call(SaveStep.saveDatabase, 0, 1); + final coverSubPath = '$bookId/cover.jpg'; + final previewSubPaths = List.generate( + dto.paths.length, + (i) => '$bookId/preview/${i.toString().padLeft(7, '0')}.jpg', + ); await _bookLocalDatasource.insertBook( - BookTableCompanion.insert(name: dto.title, localSubPaths: relPaths), + BookTableCompanion.insert( + name: dto.title, + localSubPaths: relPaths, + coverSubPath: Value(coverSubPath), + previewSubPaths: Value(previewSubPaths), + ), ); + onStepProgress?.call(SaveStep.saveDatabase, 1, 1); return Result.success(null); } catch (e, st) { - // 清理已创建的目录(后台 Isolate) - await compute(_deleteBookDirs, [destDirPath]); + await compute(_deleteBookDirs, [bookDir]); return Result.failure( BusinessFailure(message: '保存书籍失败', details: e, stackTrace: st), ); } } - /// 批量保存: - /// ① 按书逐一复制文件(后台 Isolate,循环间让出 UI 线程) - /// ② 全部复制成功后,批量 DB 写入(单事务) - /// ③ 任意环节失败,清理已复制的目录 + /// 批量保存:逐本执行 封面→预览→原图,最后批量 DB 写入 Future> saveBatchAsBooks( List dos, Function(int count) onProgress, ) async { - // 记录已创建目录,用于失败回滚 final createdDirs = []; - // 记录每本书的(相对路径列表, 标题) - final bookData = <({String title, List relPaths})>[]; + final bookData = <({ + String title, + List relPaths, + String coverSubPath, + List previewSubPaths, + })>[]; try { - // ── 阶段一:文件复制(在后台 Isolate,不在 DB 事务内)── for (var i = 0; i < dos.length; i++) { final dto = dos[i]; final bookId = const Uuid().v4(); - final destDirPath = '${GlobalConfig.booksDir.path}/$bookId'; + final bookDir = '${GlobalConfig.booksDir.path}/$bookId'; + final originalDir = '$bookDir/original'; + final previewDir = '$bookDir/preview'; + final coverPath = '$bookDir/cover.jpg'; - // 每本书让出一次事件循环,保持 UI 刷新 await Future.delayed(Duration.zero); - final relPaths = await compute( - _copyBookFiles, - _CopyBookArgs( - bookId: bookId, - destDirPath: destDirPath, - srcPaths: dto.paths, - ), + // ① 封面 + await BookImageService.generateCover(dto.paths.first, coverPath); + + // ② 预览图 + await BookImageService.generatePreviewBatch( + srcPaths: dto.paths, + destDir: previewDir, ); - createdDirs.add(destDirPath); - bookData.add((title: dto.title, relPaths: relPaths)); + // ③ 原图 + final relPaths = await BookImageService.copyOriginals( + dto.paths, + originalDir, + bookId, + ); + + createdDirs.add(bookDir); + + final coverSubPath = '$bookId/cover.jpg'; + final previewSubPaths = List.generate( + dto.paths.length, + (j) => '$bookId/preview/${j.toString().padLeft(7, '0')}.jpg', + ); + bookData.add(( + title: dto.title, + relPaths: relPaths, + coverSubPath: coverSubPath, + previewSubPaths: previewSubPaths, + )); + onProgress(i + 1); } - // ── 阶段二:批量 DB 写入(单事务,仅 DB 操作,耗时极短)── + // ④ 批量 DB 写入 await _db.transaction(() async { for (final book in bookData) { await _bookLocalDatasource.insertBook( BookTableCompanion.insert( name: book.title, localSubPaths: book.relPaths, + coverSubPath: Value(book.coverSubPath), + previewSubPaths: Value(book.previewSubPaths), ), ); } @@ -216,7 +239,6 @@ class BookRepository { return Result.success(null); } catch (e, st) { - // DB 若已写入则由 Drift 事务回滚,清理已复制的文件目录 if (createdDirs.isNotEmpty) { await compute(_deleteBookDirs, createdDirs); } @@ -225,4 +247,43 @@ class BookRepository { ); } } + + /// 为已存在的书籍重新生成封面和预览图(编辑场景) + Future regenerateImages(BookTableData book) async { + final bookId = book.localSubPaths.first.split('/').first; + final bookDir = '${GlobalConfig.booksDir.path}/$bookId'; + final previewDir = '$bookDir/preview'; + final coverPath = '$bookDir/cover.jpg'; + + // 解析原图绝对路径 + final originalPaths = book.localSubPaths + .map((sub) => GlobalConfig.resolveBookPath(sub)) + .toList(); + if (originalPaths.isEmpty) return; + + // 生成封面 + await BookImageService.generateCover(originalPaths.first, coverPath); + + // 生成预览图 + // 先清空旧的预览图目录 + final previewDirectory = Directory(previewDir); + if (await previewDirectory.exists()) { + await previewDirectory.delete(recursive: true); + } + await BookImageService.generatePreviewBatch( + srcPaths: originalPaths, + destDir: previewDir, + ); + + // 更新数据库 + final previewSubPaths = List.generate( + originalPaths.length, + (i) => '$bookId/preview/${i.toString().padLeft(7, '0')}.jpg', + ); + final updatedBook = book.copyWith( + coverSubPath: Value('$bookId/cover.jpg'), + previewSubPaths: Value(previewSubPaths), + ); + await _bookLocalDatasource.updateBook(updatedBook); + } } diff --git a/lib/feature/book/service/book_image_service.dart b/lib/feature/book/service/book_image_service.dart new file mode 100644 index 0000000..5c362c1 --- /dev/null +++ b/lib/feature/book/service/book_image_service.dart @@ -0,0 +1,97 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_image_compress/flutter_image_compress.dart'; + +/// 书籍图片处理服务:生成封面缩略图和预览图 +class BookImageService { + /// 封面缩略图宽度 + static const int coverWidth = 300; + + /// 预览图宽度 + static const int previewWidth = 1080; + + /// JPEG 压缩质量 (0-100) + static const int jpegQuality = 80; + + + /// 从原图生成封面缩略图 + /// [srcPath] 原图绝对路径 + /// [destPath] 封面输出绝对路径 (如 books/{bookId}/cover.jpg) + static Future generateCover(String srcPath, String destPath) async { + final result = await FlutterImageCompress.compressAndGetFile( + srcPath, + destPath, + minWidth: coverWidth, + minHeight: coverWidth, + quality: jpegQuality, + ); + if (result == null) { + throw Exception('封面生成失败: $srcPath'); + } + } + + /// 从原图生成单张预览图 + /// [srcPath] 原图绝对路径 + /// [destPath] 预览图输出绝对路径 + static Future generatePreview(String srcPath, String destPath) async { + final result = await FlutterImageCompress.compressAndGetFile( + srcPath, + destPath, + minWidth: previewWidth, + minHeight: previewWidth, + quality: jpegQuality, + ); + if (result == null) { + throw Exception('预览图生成失败: $srcPath'); + } + } + + /// 批量生成预览图 + /// [srcPaths] 原图绝对路径列表 + /// [destDir] 预览图输出目录 + /// [onProgress] 逐张完成回调 (current, total) + static Future> generatePreviewBatch({ + required List srcPaths, + required String destDir, + void Function(int current, int total)? onProgress, + }) async { + await Directory(destDir).create(recursive: true); + final previewPaths = []; + for (var i = 0; i < srcPaths.length; i++) { + final fileName = '${i.toString().padLeft(7, '0')}.jpg'; + final destPath = '$destDir/$fileName'; + await generatePreview(srcPaths[i], destPath); + previewPaths.add(destPath); + onProgress?.call(i + 1, srcPaths.length); + } + return previewPaths; + } + + /// 在 Isolate 中批量复制原图 + static Future> copyOriginals( + List srcPaths, + String destDir, + String bookId, + ) async { + return compute(_copyOriginalsIsolate, (srcPaths, destDir, bookId)); + } +} + +Future> _copyOriginalsIsolate( + (List srcPaths, String destDir, String bookId) args, +) async { + final (srcPaths, destDir, bookId) = args; + await Directory(destDir).create(recursive: true); + final relPaths = []; + for (var i = 0; i < srcPaths.length; i++) { + final src = File(srcPaths[i]); + if (!await src.exists()) { + throw FileSystemException('source file not found', srcPaths[i]); + } + final fileName = i.toString().padLeft(7, '0'); + await src.copy('$destDir/$fileName'); + relPaths.add('$bookId/original/$fileName'); + } + return relPaths; +} diff --git a/lib/feature/book/ui/provider/book_form_provider.dart b/lib/feature/book/ui/provider/book_form_provider.dart index 4967a27..b4aabc1 100644 --- a/lib/feature/book/ui/provider/book_form_provider.dart +++ b/lib/feature/book/ui/provider/book_form_provider.dart @@ -3,6 +3,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:tele_book/common/config/global_config.dart'; import 'package:tele_book/core/db/app_database.dart'; +import 'package:tele_book/feature/book/repository/book_repository.dart'; part 'book_form_provider.freezed.dart'; @@ -119,6 +120,9 @@ class BookFormSubmit extends _$BookFormSubmit { .read(databaseProvider) .bookLocalDatasource .updateBook(updatedBook); + + // 重新生成封面和预览图 + await ref.read(bookRepositoryProvider).regenerateImages(updatedBook); }); return !state.hasError; diff --git a/lib/feature/book/ui/provider/book_form_provider.g.dart b/lib/feature/book/ui/provider/book_form_provider.g.dart index ac39171..3aea42c 100644 --- a/lib/feature/book/ui/provider/book_form_provider.g.dart +++ b/lib/feature/book/ui/provider/book_form_provider.g.dart @@ -122,7 +122,7 @@ final class BookFormSubmitProvider BookFormSubmit create() => BookFormSubmit(); } -String _$bookFormSubmitHash() => r'6e1d62948fc4e5a71a99ddc07525ba1aa5e47a86'; +String _$bookFormSubmitHash() => r'fd7d09a1cb4d7435ae908d34fbb02868792da325'; abstract class _$BookFormSubmit extends $AsyncNotifier { FutureOr build(); diff --git a/lib/feature/book/ui/provider/book_page_provider.dart b/lib/feature/book/ui/provider/book_page_provider.dart index 310e8bf..644d8ec 100644 --- a/lib/feature/book/ui/provider/book_page_provider.dart +++ b/lib/feature/book/ui/provider/book_page_provider.dart @@ -36,7 +36,7 @@ class BookPageState { @riverpod class BookPage extends _$BookPage { - late final PageController pageController; + late PageController pageController; @override BookPageState build(int bookId) { @@ -50,14 +50,13 @@ class BookPage extends _$BookPage { if (book == null) { throw Exception("Book not found"); } - pageController = PageController(initialPage: book.currentPage); - ref.onDispose(() { - pageController.dispose(); - }); - final fullPaths = book.localSubPaths - .map((e) => GlobalConfig.resolveBookPath(e)) - .toList(); + final previewPaths = book.previewSubPaths; + final fullPaths = (previewPaths != null && previewPaths.isNotEmpty) + ? previewPaths.map((e) => GlobalConfig.resolveBookPath(e)).toList() + : book.localSubPaths + .map((e) => GlobalConfig.resolveBookPath(e)) + .toList(); return BookPageState( book: book, paths: fullPaths, @@ -66,6 +65,10 @@ class BookPage extends _$BookPage { ); } + void initController(PageController controller) { + pageController = controller; + } + void onPageChanged(int index) { state = state.copyWith(currentPage: index); } @@ -73,9 +76,4 @@ class BookPage extends _$BookPage { void toggleBar() { state = state.copyWith(isShowBar: !state.isShowBar); } - - void jumpToPage(int index) { - state = state.copyWith(currentPage: index); - pageController.jumpToPage(index); - } } diff --git a/lib/feature/book/ui/provider/book_page_provider.g.dart b/lib/feature/book/ui/provider/book_page_provider.g.dart index 8839ee9..1c382ba 100644 --- a/lib/feature/book/ui/provider/book_page_provider.g.dart +++ b/lib/feature/book/ui/provider/book_page_provider.g.dart @@ -58,7 +58,7 @@ final class BookPageProvider } } -String _$bookPageHash() => r'a1503c3304f4c51ee417945992ed4de071a27061'; +String _$bookPageHash() => r'74e8ded41f495fbfaf78d9cdbda58fa537da26a7'; final class BookPageFamily extends $Family with diff --git a/lib/feature/book/ui/provider/book_provider.dart b/lib/feature/book/ui/provider/book_provider.dart index 2313462..0b6a9f6 100644 --- a/lib/feature/book/ui/provider/book_provider.dart +++ b/lib/feature/book/ui/provider/book_provider.dart @@ -121,6 +121,14 @@ class _BookListUiNotifier extends Notifier<_BookListUiState> { ); } + void toggleSelections(Set bookIds) { + if (bookIds.isEmpty) { + state = state.copyWith(isSelectionMode: false, selectedBookIds: {}); + return; + } + state = state.copyWith(selectedBookIds: bookIds); + } + void toggleLayout() { final nextLayout = state.layout == BookLayout.list ? BookLayout.grid : BookLayout.list; @@ -148,7 +156,6 @@ class BookList extends _$BookList { Future build() async { final booksAsync = ref.watch(booksProvider); final query = ref.watch(bookListQueryProvider); - final ui = ref.watch(bookListUiProvider); if (booksAsync.hasError) { throw booksAsync.error!; @@ -180,24 +187,20 @@ class BookList extends _$BookList { final visibleBooks = filtered.take(visibleCount).toList(); final bookVos = visibleBooks.map((book) { - final coverPath = book.localSubPaths.isNotEmpty - ? GlobalConfig.resolveBookPath(book.localSubPaths.first) - : ''; + final coverPath = book.coverSubPath != null + ? GlobalConfig.resolveBookPath(book.coverSubPath!) + : book.localSubPaths.isNotEmpty + ? GlobalConfig.resolveBookPath(book.localSubPaths.first) + : ''; return BookListItemVo(book: book, coverImagePath: coverPath); }).toList(); - final visibleIds = visibleBooks.map((e) => e.id).toSet(); - final selectedIds = ui.selectedBookIds.where(visibleIds.contains).toSet(); - return BookListState( bookVos: bookVos, hasMore: filtered.length > visibleBooks.length, name: query.name, sort: query.sort, isLoadingMore: false, - isSelectionMode: ui.isSelectionMode, - selectedBookIds: selectedIds, - layout: ui.layout, ); } @@ -231,6 +234,12 @@ class BookList extends _$BookList { ref.read(bookListUiProvider.notifier).toggleSelection(bookId); } + // 批量选择 + void toggleSelections(Set bookIds) { + ref.read(bookListUiProvider.notifier).toggleSelections(bookIds); + + } + // ➡️ 全选 void selectAll() { final ids = @@ -252,8 +261,9 @@ class BookList extends _$BookList { // ➡️ 批量删除选中书籍并刷新列表 Future deleteSelected() async { - if (!state.hasValue) return; - final selectedIds = Set.from(state.requireValue.selectedBookIds); + final selectedIds = Set.from( + ref.read(bookListUiProvider).selectedBookIds, + ); final bookRepository = ref.read(bookRepositoryProvider); for (final id in selectedIds) { diff --git a/lib/feature/book/ui/provider/book_provider.g.dart b/lib/feature/book/ui/provider/book_provider.g.dart index df88c9f..065c5e2 100644 --- a/lib/feature/book/ui/provider/book_provider.g.dart +++ b/lib/feature/book/ui/provider/book_provider.g.dart @@ -33,7 +33,7 @@ final class BookListProvider BookList create() => BookList(); } -String _$bookListHash() => r'8cc7188f30e463cd9d76918cd02c58d4cbbd98e0'; +String _$bookListHash() => r'67c643b9b8edf49a19d2f6c2f8dfc07755d1c08f'; abstract class _$BookList extends $AsyncNotifier { FutureOr build(); diff --git a/lib/feature/book/ui/view/book_form_view.dart b/lib/feature/book/ui/view/book_form_view.dart index 6e25632..05a9f96 100644 --- a/lib/feature/book/ui/view/book_form_view.dart +++ b/lib/feature/book/ui/view/book_form_view.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:forui/forui.dart'; +import 'package:go_router/go_router.dart'; import 'package:tele_book/common/widget/error_widget.dart'; import 'package:tele_book/common/widget/local_image_widget.dart'; import 'package:tele_book/core/db/app_database.dart'; @@ -29,9 +31,18 @@ class BookFormView extends ConsumerWidget { } }); - return Scaffold( - appBar: AppBar(title: Text("编辑书籍")), - body: formState.when( + return FScaffold( + header: FHeader.nested( + title: Text("编辑书籍"), + prefixes: [ + FHeaderAction.back( + onPress: () { + context.pop(); + }, + ), + ], + ), + child: formState.when( error: (error, stack) => Center( child: CustomErrorWidget( errorMessage: error.toString(), @@ -43,80 +54,96 @@ class BookFormView extends ConsumerWidget { final formNotifier = ref.read(bookFormProvider(book.id).notifier); final isSubmitting = submitState.isLoading; - return Column( - children: [ - Padding( - padding: EdgeInsets.all(16), - child: TextField( - controller: formNotifier.titleController, - decoration: InputDecoration(labelText: "书籍名称"), + return Padding( + padding: .all(16), + child: Column( + crossAxisAlignment: .start, + children: [ + FTextFormField( + control: FTextFieldControl.managed( + controller: formNotifier.titleController, + ), + label: Text("书籍名称"), + hint: "请输入书籍名称", ), - ), - Expanded( - child: ReorderableListView.builder( - onReorder: formNotifier.reorderImages, - itemCount: formState.imagePaths.length, - itemBuilder: (context, index) { - final imagePath = formState.imagePaths[index]; - return Padding( - key: ObjectKey(imagePath), - padding: EdgeInsets.symmetric(vertical: 8.0), - child: ListTile( - leading: LocalImageWidget( - imagePath: imagePath.fullPath, - ), - title: Text("图片 ${index + 1}"), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: Icon(Icons.delete), - onPressed: () { - formNotifier.deleteImage(imagePath); - }, - ), - ReorderableDragStartListener( - index: index, - child: const Padding( - padding: EdgeInsets.symmetric(horizontal: 8), - child: Icon(Icons.drag_handle), + const SizedBox(height: 16), + Text( + "图片排序", + style: context.theme.typography.body.xs.copyWith( + fontWeight: .w500, + ), + ), + const SizedBox(height: 8), + Expanded( + child: ReorderableListView.builder( + onReorderItem: formNotifier.reorderImages, + itemCount: formState.imagePaths.length, + itemBuilder: (context, index) { + final imagePath = formState.imagePaths[index]; + return Padding( + key: ObjectKey(imagePath), + padding: EdgeInsets.symmetric(vertical: 8.0), + child: FItem( + prefix: LocalImageWidget( + imagePath: imagePath.fullPath, + ), + title: Text("图片 ${index + 1}"), + suffix: Row( + mainAxisSize: MainAxisSize.min, + children: [ + FButton.icon( + variant: .ghost, + onPress: () { + formNotifier.deleteImage(imagePath); + }, + child: Icon( + Icons.delete, + color: context.theme.colors.destructive, + ), + ), + ReorderableDragStartListener( + index: index, + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: Icon(Icons.drag_handle), + ), ), - ), - ], + ], + ), ), - ), - ); - }, + ); + }, + ), ), - ), - Container( - padding: EdgeInsets.all(16), - width: double.infinity, - child: FilledButton( - onPressed: isSubmitting - ? null - : () async { - final success = await ref - .read(bookFormSubmitProvider.notifier) - .submit( - bookId: book.id, - title: formState.title, - imagePaths: formState.imagePaths, - ); - if (success && context.mounted) { - Navigator.pop(context); - } - }, - child: isSubmitting - ? SizedBox( - width: 24, - height: 24, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : Text("保存"), + Padding( + padding: EdgeInsets.symmetric(vertical: 16), + + child: FButton( + onPress: isSubmitting + ? null + : () async { + final success = await ref + .read(bookFormSubmitProvider.notifier) + .submit( + bookId: book.id, + title: formState.title, + imagePaths: formState.imagePaths, + ); + if (success && context.mounted) { + Navigator.pop(context); + } + }, + child: isSubmitting + ? SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : Text("保存"), + ), ), - ), - ], + ], + ), ); }, ), diff --git a/lib/feature/book/ui/view/book_list_view.dart b/lib/feature/book/ui/view/book_list_view.dart index 0d81330..454bf51 100644 --- a/lib/feature/book/ui/view/book_list_view.dart +++ b/lib/feature/book/ui/view/book_list_view.dart @@ -2,9 +2,12 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:forui/forui.dart'; import 'package:go_router/go_router.dart'; import 'package:responsive_framework/responsive_framework.dart'; +import 'package:tele_book/common/widget/f_adaptive_dialog.dart'; import 'package:tele_book/common/widget/local_image_widget.dart'; +import 'package:tele_book/core/db/app_database.dart'; import 'package:tele_book/core/route/app_route.dart'; import 'package:tele_book/feature/book/enum/book_menu_type.dart'; import 'package:tele_book/feature/book/enum/book_sort.dart'; @@ -47,8 +50,9 @@ class _BookListViewState extends ConsumerState { @override Widget build(BuildContext context) { final bookState = ref.watch(bookListProvider); - final isSelectionMode = bookState.value?.isSelectionMode ?? false; - final layout = bookState.value?.layout ?? BookLayout.list; + final uiState = ref.watch(bookListUiProvider); + final isSelectionMode = uiState.isSelectionMode; + final layout = uiState.layout; return PopScope( canPop: !isSelectionMode, @@ -57,11 +61,12 @@ class _BookListViewState extends ConsumerState { ref.read(bookListProvider.notifier).exitSelectionMode(); } }, - child: Scaffold( - appBar: isSelectionMode + child: FScaffold( + header: isSelectionMode ? _buildSelectionAppBar(context) : _buildNormalAppBar(context), - body: bookState.when( + footer: isSelectionMode ? _buildSelectionBottomBar(context) : null, + child: bookState.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, _) => Center(child: Text('加载失败: $e')), data: (state) { @@ -79,8 +84,6 @@ class _BookListViewState extends ConsumerState { ); }, ), - bottomNavigationBar: - isSelectionMode ? _buildSelectionBottomBar(context) : null, ), ); } @@ -92,144 +95,183 @@ class _BookListViewState extends ConsumerState { children: [ Icon(Icons.library_books_outlined, size: 64, color: Colors.grey[400]), const SizedBox(height: 16), - Text('暂无书籍', - style: TextStyle(fontSize: 16, color: Colors.grey[600])), + Text('暂无书籍', style: TextStyle(fontSize: 16, color: Colors.grey[600])), ], ), ); } - AppBar _buildNormalAppBar(BuildContext context) { - final bookVos = - ref.watch(bookListProvider.select((s) => s.value?.bookVos ?? [])); + FHeader _buildNormalAppBar(BuildContext context) { + final bookVos = ref.watch( + bookListProvider.select((s) => s.value?.bookVos ?? []), + ); final state = ref.watch(bookListProvider).value; - return AppBar( + return FHeader( title: const Text('书籍'), - elevation: 0, - actions: [ + suffixes: [ SearchAnchor( - builder: (context, controller) => IconButton( - onPressed: controller.openView, + builder: (context, controller) => FHeaderAction( + onPress: controller.openView, icon: const Icon(Icons.search), ), suggestionsBuilder: (context, searchController) { final query = searchController.text.toLowerCase(); - final results = - bookVos.where((vo) => vo.book.name.toLowerCase().contains(query)); + final results = bookVos.where( + (vo) => vo.book.name.toLowerCase().contains(query), + ); return results.map( - (vo) => Padding( - padding: EdgeInsets.symmetric(vertical: 8), - child: - ListTile( - leading: LocalImageWidget(imagePath: vo.coverImagePath), - title: Text(vo.book.name), - onTap: () { - searchController.closeView(vo.book.name); - context.push(AppRoute.bookPage, extra: vo.book); - }, - ) + (vo) => FItem( + prefix: LocalImageWidget(imagePath: vo.coverImagePath), + title: Text(vo.book.name), + onPress: () { + searchController.closeView(vo.book.name); + context.push(AppRoute.bookPage, extra: vo.book); + }, ), ); }, ), - IconButton(onPressed: (){ + FHeaderAction( + onPress: () { context.push(AppRoute.parseForm); - }, icon: const Icon(Icons.add)), + }, + icon: const Icon(Icons.add), + ), _buildTopMenuButton(context, state), ], ); } - AppBar _buildSelectionAppBar(BuildContext context) { + FHeader _buildSelectionAppBar(BuildContext context) { final selectedCount = ref.watch( - bookListProvider.select((s) => s.value?.selectedBookIds.length ?? 0), + bookListUiProvider.select((s) => s.selectedBookIds.length), ); final notifier = ref.read(bookListProvider.notifier); - return AppBar( - leading: IconButton( - icon: const Icon(Icons.close), - onPressed: notifier.exitSelectionMode, - ), + return FHeader.nested( + prefixes: [ + FHeaderAction( + icon: const Icon(Icons.close), + onPress: notifier.exitSelectionMode, + ), + ], title: Text('已选 $selectedCount 本'), - actions: [ - TextButton(onPressed: notifier.selectAll, child: const Text('全选')), + suffixes: [ + FHeaderAction(onPress: notifier.selectAll, icon: const Text('全选')), ], ); } Widget _buildSelectionBottomBar(BuildContext context) { - final selectedBooks = ref.watch( - bookListProvider.select((s) => s.value?.selectedBooks ?? []), + final selectedIds = ref.watch( + bookListUiProvider.select((s) => s.selectedBookIds), ); - return SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: selectedBooks.isNotEmpty - ? Row( - children: [ - IconButton( - tooltip: '导出选中', - onPressed: () => _onExportSelected(context), - icon: Icon(Icons.move_to_inbox, - color: Theme.of(context).colorScheme.primary), - ), - IconButton( - tooltip: '删除选中', - onPressed: () => _deleteSelected(context), - icon: Icon(Icons.delete, - color: Theme.of(context).colorScheme.error), - ), - ], - ) - : const SizedBox.shrink(), - ), + return Padding( + padding: .all(8), + child: selectedIds.isNotEmpty + ? Row( + children: [ + FButton( + variant: .outline, + onPress: () => _onExportSelected(context), + prefix: Icon(FLucideIcons.move), + child: Text("批量导出"), + ), + SizedBox(width: 8), + FButton( + variant: .destructive, + onPress: () => _deleteSelected(context), + prefix: Icon(FLucideIcons.trash), + child: Text("批量删除"), + ), + ], + ) + : const SizedBox.shrink(), ); } Widget _buildTopMenuButton(BuildContext context, BookListState? state) { - return PopupMenuButton( - onSelected: (type) => _onTopMenuSelected(type), - itemBuilder: (context) => BookTopMenuType.values.map((type) { - return PopupMenuItem( - value: type, - child: Row( - children: [ - Icon(type.icon, size: 16, color: Colors.grey[600]), - const SizedBox(width: 8), - Text(type.title), - const SizedBox(width: 8), - if (type == BookTopMenuType.asc && - state?.sort?.order == BookSortOrder.asc) - const Icon(Icons.check, size: 16), - if (type == BookTopMenuType.desc && - state?.sort?.order == BookSortOrder.desc) - const Icon(Icons.check, size: 16), - if (type == BookTopMenuType.name && - state?.sort?.type == BookSortType.title) - const Icon(Icons.check, size: 16), - if (type == BookTopMenuType.lastCreatedAt && - state?.sort?.type == BookSortType.lastCreatedAt) - const Icon(Icons.check, size: 16), - if (type == BookTopMenuType.list && - state?.layout == BookLayout.list) - const Icon(Icons.check, size: 16), - if (type == BookTopMenuType.grid && - state?.layout == BookLayout.grid) - const Icon(Icons.check, size: 16), - ], - ), - ); - }).toList(), + return FPopoverMenu( + autofocus: true, + menuAnchor: .topRight, + childAnchor: .bottomRight, + menu: [ + .group( + children: [ + .item( + prefix: const Icon(FLucideIcons.arrowUp), + title: const Text('升序'), + suffix: state?.sort?.order == BookSortOrder.asc + ? const Icon(FLucideIcons.check, size: 16) + : null, + onPress: () => _onTopMenuSelected(BookTopMenuType.asc), + ), + .item( + prefix: const Icon(FLucideIcons.arrowDown), + title: const Text('降序'), + suffix: state?.sort?.order == BookSortOrder.desc + ? const Icon(FLucideIcons.check, size: 16) + : null, + onPress: () => _onTopMenuSelected(BookTopMenuType.desc), + ), + ], + ), + .group( + children: [ + .item( + prefix: const Icon(FLucideIcons.type), + title: const Text('按书名'), + suffix: state?.sort?.type == BookSortType.title + ? const Icon(FLucideIcons.check, size: 16) + : null, + onPress: () => _onTopMenuSelected(BookTopMenuType.name), + ), + .item( + prefix: const Icon(FLucideIcons.clock), + title: const Text('按添加时间'), + suffix: state?.sort?.type == BookSortType.lastCreatedAt + ? const Icon(FLucideIcons.check, size: 16) + : null, + onPress: () => _onTopMenuSelected(BookTopMenuType.lastCreatedAt), + ), + ], + ), + .group( + children: [ + .item( + prefix: const Icon(FLucideIcons.list), + title: const Text('列表视图'), + suffix: ref.watch(bookListUiProvider).layout == BookLayout.list + ? const Icon(FLucideIcons.check, size: 16) + : null, + onPress: () => _onTopMenuSelected(BookTopMenuType.list), + ), + .item( + prefix: const Icon(FLucideIcons.layoutGrid), + title: const Text('网格视图'), + suffix: ref.watch(bookListUiProvider).layout == BookLayout.grid + ? const Icon(FLucideIcons.check, size: 16) + : null, + onPress: () => _onTopMenuSelected(BookTopMenuType.grid), + ), + ], + ), + ], + builder: (_, controller, _) => FHeaderAction( + icon: const Icon(FLucideIcons.ellipsis), + onPress: controller.toggle, + ), ); } void _onTopMenuSelected(BookTopMenuType type) { final notifier = ref.read(bookListProvider.notifier); final current = ref.read(bookListProvider).value; - final currentSort = current?.sort ?? + final currentLayout = ref.read(bookListUiProvider).layout; + final currentSort = + current?.sort ?? BookSort(order: BookSortOrder.desc, type: BookSortType.lastCreatedAt); switch (type) { @@ -240,48 +282,60 @@ class _BookListViewState extends ConsumerState { case BookTopMenuType.name: notifier.updateSort(currentSort.copyWith(type: BookSortType.title)); case BookTopMenuType.lastCreatedAt: - notifier - .updateSort(currentSort.copyWith(type: BookSortType.lastCreatedAt)); + notifier.updateSort( + currentSort.copyWith(type: BookSortType.lastCreatedAt), + ); case BookTopMenuType.list: - if (current?.layout != BookLayout.list) notifier.toggleLayout(); + if (currentLayout != BookLayout.list) notifier.toggleLayout(); case BookTopMenuType.grid: - if (current?.layout != BookLayout.grid) notifier.toggleLayout(); + if (currentLayout != BookLayout.grid) notifier.toggleLayout(); } } void _onExportSelected(BuildContext context) { - final selectedBooks = - ref.read(bookListProvider).value?.selectedBooks ?? []; + final selectedIds = ref.read(bookListUiProvider).selectedBookIds; + final bookVos = ref.read(bookListProvider).value?.bookVos ?? []; + final selectedBooks = bookVos + .where((v) => selectedIds.contains(v.book.id)) + .toList(); if (selectedBooks.isEmpty) return; - Navigator.of(context).push(MaterialPageRoute( - builder: (_) => - ExportBatchFormView(books: selectedBooks.map((v) => v.book).toList()), - )); + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => ExportBatchFormView( + books: selectedBooks.map((v) => v.book).toList(), + ), + ), + ); } Future _deleteSelected(BuildContext context) async { - final count = - ref.read(bookListProvider).value?.selectedBookIds.length ?? 0; - final confirmed = await showDialog( + final count = ref.read(bookListUiProvider).selectedBookIds.length; + final confirmed = await showFDialog( context: context, - builder: (_) => AlertDialog( + builder: (context, style, animate) => FAdaptiveDialog( title: const Text('确认删除'), - content: Text('将删除选中的 $count 本书籍,删除后不可恢复,是否继续?'), + body: Text('将删除选中的 $count 本书籍,删除后不可恢复,是否继续?'), actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: const Text('取消')), - ElevatedButton( - onPressed: () => Navigator.pop(context, true), - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.error), - child: const Text('删除', style: TextStyle(color: Colors.white)), + FButton( + variant: .destructive, + size: .sm, + onPress: () => Navigator.pop(context, true), + child: const Text('删除'), + ), + FButton( + variant: .outline, + size: .sm, + onPress: () => Navigator.pop(context, false), + child: const Text('取消'), ), ], ), ); + + final rootContext = Navigator.of(context).context; if (confirmed == true && context.mounted) { await ref.read(bookListProvider.notifier).deleteSelected(); + showFToast(context: rootContext, title: Text("删除成功")); } } } @@ -301,122 +355,151 @@ class _BookListContent extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final itemCount = bookVos.length + (isLoadingMore ? 1 : 0); - return ListView.separated( - controller: scrollController, - padding: const EdgeInsets.symmetric(vertical: 8), - separatorBuilder: (_, __) => const SizedBox(height: 4), - itemCount: itemCount, + final isSelectionMode = ref.watch( + bookListUiProvider.select((s) => s.isSelectionMode), + ); + return isSelectionMode ? _selectList(ref) : _listView(ref); + } + + Widget _listView(WidgetRef ref) { + final count = bookVos.length + (isLoadingMore ? 1 : 0); + final notifier = ref.read(bookListProvider.notifier); + return FItemGroup.builder( + scrollController: scrollController, + count: count, itemBuilder: (context, index) { - if (index == bookVos.length) { - return const Padding( - padding: EdgeInsets.symmetric(vertical: 16), + final bookVo = bookVos[index]; + if (index >= bookVos.length) { + return const SizedBox( + height: 48, child: Center(child: CircularProgressIndicator(strokeWidth: 2)), ); } - return _BookListTile(book: bookVos[index]); + return FTile( + title: Text(bookVo.book.name), + prefix: LocalImageWidget(imagePath: bookVo.coverImagePath), + subtitle: Text('共 ${bookVo.book.localSubPaths.length} 页'), + suffix: FPopoverMenu.tiles( + style: .delta(), + menu: [ + .group( + children: [ + for (var item in BookItemMenuType.values) + .tile( + variant: item == BookItemMenuType.delete + ? .destructive + : .primary, + title: Text(item.title), + prefix: Icon(item.icon), + onPress: () { + _onItemSelected(context, ref, item, bookVo); + }, + ), + ], + ), + ], + builder: (context, controller, child) { + return FButton.icon( + variant: .ghost, + child: Icon(FLucideIcons.moreHorizontal), + onPress: () { + controller.show(); + }, + ); + }, + ), + onPress: () { + context.push(AppRoute.bookPage, extra: bookVo.book); + }, + onLongPress: () { + notifier.enterSelectionMode(bookVo.book); + }, + ); }, ); } -} - -class _BookListTile extends ConsumerWidget { - final BookListItemVo book; - const _BookListTile({required this.book}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final isSelectionMode = ref.watch( - bookListProvider.select((s) => s.value?.isSelectionMode ?? false)); - final isSelected = ref.watch(bookListProvider.select( - (s) => s.value?.selectedBookIds.contains(book.book.id) ?? false)); + Widget _selectList(WidgetRef ref) { + final count = bookVos.length + (isLoadingMore ? 1 : 0); final notifier = ref.read(bookListProvider.notifier); - - return GestureDetector( - onTap: () => isSelectionMode - ? notifier.toggleSelection(book.book.id) - : context.push(AppRoute.bookPage, extra: book.book), - onLongPress: () { - if (!isSelectionMode) notifier.enterSelectionMode(book.book); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Row( - children: [ - if (isSelectionMode) - Padding( - padding: const EdgeInsets.only(right: 8), - child: Checkbox( - value: isSelected, - onChanged: (_) => notifier.toggleSelection(book.book.id), - ), - ), - LocalImageWidget(imagePath: book.coverImagePath), - Expanded( - child: ListTile( - title: Text(book.book.name, - maxLines: 2, overflow: TextOverflow.ellipsis), - subtitle: Text('共 ${book.book.localSubPaths.length} 页'), - ), - ), - if (!isSelectionMode) _buildItemMenu(context, ref), - ], - ), - ), + final selectedIds = ref.watch( + bookListUiProvider.select((s) => s.selectedBookIds), ); - } + return FSelectTileGroup.builder( + control: FMultiValueControl.managed( + initial: selectedIds, + onChange: (value) { + notifier.toggleSelections(value); + }, + ), + scrollController: scrollController, + count: count, + tileBuilder: (context, index) { + final bookVo = bookVos[index]; + if (index >= bookVos.length) { + return .tile(title: Text("加载中"), value: -1); + } - Widget _buildItemMenu(BuildContext context, WidgetRef ref) { - return PopupMenuButton( - onSelected: (type) => _onItemSelected(context, ref, type), - itemBuilder: (context) => BookItemMenuType.values - .map((type) => PopupMenuItem( - value: type, - child: Row(children: [ - Icon(type.icon, size: 16, color: Colors.grey[600]), - const SizedBox(width: 8), - Text(type.title), - ]), - )) - .toList(), + return .suffix( + title: Text(bookVo.book.name), + prefix: LocalImageWidget(imagePath: bookVo.coverImagePath), + value: bookVo.book.id, + ); + }, ); } void _onItemSelected( - BuildContext context, WidgetRef ref, BookItemMenuType type) { + BuildContext context, + WidgetRef ref, + BookItemMenuType type, + BookListItemVo bookVo, + ) { switch (type) { case BookItemMenuType.edit: - context.push(AppRoute.bookForm, extra: book.book); + context.push(AppRoute.bookForm, extra: bookVo.book); case BookItemMenuType.export: - Navigator.of(context).push(MaterialPageRoute( - builder: (_) => ExportSingleFormView(book: book.book))); + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => ExportSingleFormView(book: bookVo.book), + ), + ); case BookItemMenuType.delete: - _confirmDelete(context, ref); + _confirmDelete(context, ref, bookVo); } } - Future _confirmDelete(BuildContext context, WidgetRef ref) async { - final ok = await showDialog( + Future _confirmDelete( + BuildContext context, + WidgetRef ref, + BookListItemVo bookVo, + ) async { + final ok = await showFDialog( context: context, - builder: (_) => AlertDialog( - title: const Text('确认删除'), - content: Text('删除《${book.book.name}》后不可恢复,是否继续?'), + builder: (context, style, animate) => FAdaptiveDialog( + title: Text('确认删除'), + body: Text('删除《${bookVo.book.name}》后不可恢复,是否继续?'), actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: const Text('取消')), - ElevatedButton( - onPressed: () => Navigator.pop(context, true), - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.error), - child: const Text('删除', style: TextStyle(color: Colors.white)), + FButton( + size: .sm, + variant: .destructive, + child: const Text('确定'), + onPress: () => Navigator.of(context).pop(true), + ), + FButton( + variant: .outline, + size: .sm, + child: const Text('取消'), + onPress: () => Navigator.of(context).pop(false), ), ], ), ); - if (ok == true && context.mounted) { - await ref.read(bookListProvider.notifier).deleteBook(book.book.id); + final rootContext = Navigator.of(context).context; + + if (ok == true) { + await ref.read(bookListProvider.notifier).deleteBook(bookVo.book.id); + showFToast(context: rootContext, title: Text("删除成功")); } } } @@ -436,57 +519,50 @@ class _BookGridContent extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final isTablet = ResponsiveBreakpoints.of(context).isTablet; - final gridDelegate = isTablet - ? const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 180, - crossAxisSpacing: 12, - mainAxisSpacing: 12, - childAspectRatio: 0.65, - ) - : const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 12, - mainAxisSpacing: 12, - childAspectRatio: 0.65, - ); - final itemCount = bookVos.length + (isLoadingMore ? 1 : 0); return GridView.builder( controller: scrollController, - padding: const EdgeInsets.all(12), - gridDelegate: gridDelegate, + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 180, + mainAxisSpacing: 8, + crossAxisSpacing: 8, + mainAxisExtent: 240, + ), itemCount: itemCount, itemBuilder: (context, index) { if (index == bookVos.length) { return const Center(child: CircularProgressIndicator(strokeWidth: 2)); } - return _BookGridTile(book: bookVos[index]); + return _BookGridTile(bookVo: bookVos[index]); }, ); } } class _BookGridTile extends ConsumerWidget { - final BookListItemVo book; + final BookListItemVo bookVo; - const _BookGridTile({required this.book}); + const _BookGridTile({required this.bookVo}); @override Widget build(BuildContext context, WidgetRef ref) { final isSelectionMode = ref.watch( - bookListProvider.select((s) => s.value?.isSelectionMode ?? false)); - final isSelected = ref.watch(bookListProvider.select( - (s) => s.value?.selectedBookIds.contains(book.book.id) ?? false)); + bookListUiProvider.select((s) => s.isSelectionMode), + ); + final isSelected = ref.watch( + bookListUiProvider.select( + (s) => s.selectedBookIds.contains(bookVo.book.id), + ), + ); final notifier = ref.read(bookListProvider.notifier); return GestureDetector( onTap: () => isSelectionMode - ? notifier.toggleSelection(book.book.id) - : context.push(AppRoute.bookPage, extra: book.book), + ? notifier.toggleSelection(bookVo.book.id) + : context.push(AppRoute.bookPage, extra: bookVo.book), onLongPress: () => isSelectionMode ? _showItemMenu(context, ref) - : notifier.enterSelectionMode(book.book), + : notifier.enterSelectionMode(bookVo.book), child: Stack( children: [ Column( @@ -495,9 +571,9 @@ class _BookGridTile extends ConsumerWidget { Expanded( child: ClipRRect( borderRadius: BorderRadius.circular(8), - child: book.coverImagePath.isNotEmpty + child: bookVo.coverImagePath.isNotEmpty ? Image.file( - File(book.coverImagePath), + File(bookVo.coverImagePath), width: double.infinity, fit: BoxFit.cover, cacheWidth: 300, @@ -507,12 +583,39 @@ class _BookGridTile extends ConsumerWidget { ), ), const SizedBox(height: 4), - Text(book.book.name, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 12)), - Text('${book.book.localSubPaths.length} 页', - style: TextStyle(fontSize: 11, color: Colors.grey[600])), + FItem( + title: Text(bookVo.book.name, maxLines: 2), + subtitle: Text('${bookVo.book.localSubPaths.length} 页'), + suffix: FPopoverMenu.tiles( + style: .delta(), + menu: [ + .group( + children: [ + for (var item in BookItemMenuType.values) + .tile( + variant: item == BookItemMenuType.delete + ? .destructive + : .primary, + title: Text(item.title), + prefix: Icon(item.icon), + onPress: () { + _onItemSelected(context, ref, item); + }, + ), + ], + ), + ], + builder: (context, controller, child) { + return FButton.icon( + variant: .ghost, + child: Icon(FLucideIcons.moreHorizontal), + onPress: () { + controller.show(); + }, + ); + }, + ), + ), ], ), if (isSelectionMode) @@ -526,7 +629,9 @@ class _BookGridTile extends ConsumerWidget { : Colors.white.withValues(alpha: 0.8), shape: BoxShape.circle, border: Border.all( - color: Theme.of(context).colorScheme.primary, width: 2), + color: Theme.of(context).colorScheme.primary, + width: 2, + ), ), child: isSelected ? const Icon(Icons.check, color: Colors.white, size: 18) @@ -539,11 +644,11 @@ class _BookGridTile extends ConsumerWidget { } Widget _placeholder() => Container( - width: double.infinity, - height: double.infinity, - color: Colors.grey[200], - child: Icon(Icons.book, color: Colors.grey[400], size: 40), - ); + width: double.infinity, + height: double.infinity, + color: Colors.grey[200], + child: Icon(Icons.book, color: Colors.grey[400], size: 40), + ); void _showItemMenu(BuildContext context, WidgetRef ref) { showModalBottomSheet( @@ -552,14 +657,16 @@ class _BookGridTile extends ConsumerWidget { child: Column( mainAxisSize: MainAxisSize.min, children: BookItemMenuType.values - .map((type) => ListTile( - leading: Icon(type.icon), - title: Text(type.title), - onTap: () { - Navigator.pop(context); - _onItemSelected(context, ref, type); - }, - )) + .map( + (type) => ListTile( + leading: Icon(type.icon), + title: Text(type.title), + onTap: () { + Navigator.pop(context); + _onItemSelected(context, ref, type); + }, + ), + ) .toList(), ), ), @@ -567,39 +674,51 @@ class _BookGridTile extends ConsumerWidget { } void _onItemSelected( - BuildContext context, WidgetRef ref, BookItemMenuType type) { + BuildContext context, + WidgetRef ref, + BookItemMenuType type, + ) { switch (type) { case BookItemMenuType.edit: - context.push(AppRoute.bookForm, extra: book.book); + context.push(AppRoute.bookForm, extra: bookVo.book); case BookItemMenuType.export: - Navigator.of(context).push(MaterialPageRoute( - builder: (_) => ExportSingleFormView(book: book.book))); + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => ExportSingleFormView(book: bookVo.book), + ), + ); case BookItemMenuType.delete: _confirmDelete(context, ref); } } Future _confirmDelete(BuildContext context, WidgetRef ref) async { - final ok = await showDialog( + final ok = await showFDialog( context: context, - builder: (_) => AlertDialog( + builder: (context, style, animate) => FAdaptiveDialog( title: const Text('确认删除'), - content: Text('删除《${book.book.name}》后不可恢复,是否继续?'), + body: Text('删除《${bookVo.book.name}》后不可恢复,是否继续?'), actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: const Text('取消')), - ElevatedButton( - onPressed: () => Navigator.pop(context, true), - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.error), - child: const Text('删除', style: TextStyle(color: Colors.white)), + FButton( + variant: .destructive, + size: .sm, + onPress: () => Navigator.pop(context, true), + child: const Text('删除'), + ), + FButton( + size: .sm, + variant: .outline, + onPress: () => Navigator.pop(context, false), + child: const Text('取消'), ), ], ), ); + + final rootContext = Navigator.of(context).context; if (ok == true && context.mounted) { - await ref.read(bookListProvider.notifier).deleteBook(book.book.id); + await ref.read(bookListProvider.notifier).deleteBook(bookVo.book.id); + showFToast(context: rootContext, title: Text("删除成功")); } } } diff --git a/lib/feature/book/ui/view/book_page_view.dart b/lib/feature/book/ui/view/book_page_view.dart index 5609424..5412906 100644 --- a/lib/feature/book/ui/view/book_page_view.dart +++ b/lib/feature/book/ui/view/book_page_view.dart @@ -2,82 +2,104 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:forui/forui.dart'; +import 'package:go_router/go_router.dart'; import 'package:tele_book/core/db/app_database.dart'; import 'package:tele_book/feature/book/ui/provider/book_page_provider.dart'; -class BookPageView extends ConsumerWidget { +class BookPageView extends ConsumerStatefulWidget { final BookTableData book; const BookPageView({super.key, required this.book}); @override - Widget build(BuildContext context,WidgetRef ref) { - final state =ref.watch(bookPageProvider(book.id)); - final notifier = ref.watch(bookPageProvider(book.id).notifier); + ConsumerState createState() => _BookPageViewState(); +} - final fullWidth = MediaQuery.sizeOf(context).width; - final boxWidth = fullWidth / 3; - return Scaffold( - appBar: AppBar(title: Text(state.book.name)), - body: Stack( - children: [ - PageView.builder( - controller: notifier.pageController, - onPageChanged: notifier.onPageChanged, - itemBuilder: (context, index) { - final page = state.paths[index]; - return Image.file(File(page), fit: BoxFit.contain); - }, - itemCount: state.paths.length, - ), - Positioned.fill( - child: Align( - alignment: Alignment.center, - child: GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: notifier.toggleBar, - child: SizedBox(width: boxWidth, height: double.infinity), - ), +class _BookPageViewState extends ConsumerState { + late final PageController _pageController; + late final FPaginationController _paginationController; + + @override + void initState() { + super.initState(); + final notifier = ref.read(bookPageProvider(widget.book.id).notifier); + final state = ref.read(bookPageProvider(widget.book.id)); + final initialPage = state.currentPage; + _pageController = PageController(initialPage: initialPage); + _paginationController = FPaginationController( + pages: state.paths.length - 1, + siblings: 0, + ); + _paginationController.value = initialPage; + notifier.initController(_pageController); + } + + @override + void dispose() { + _paginationController.dispose(); + _pageController.dispose(); + super.dispose(); + } + + void _handlePageChange(int page) { + final old = _pageController.page?.round(); + if (old == null || old == page) return; + if (page == old + 1 || page == old - 1) { + _pageController.animateToPage( + page, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + + } else { + _pageController.jumpToPage(page); + } + } + + + @override + Widget build(BuildContext context) { + final state = ref.watch(bookPageProvider(widget.book.id)); + final notifier = ref.watch(bookPageProvider(widget.book.id).notifier); + + return FScaffold( + header: FHeader.nested( + title: Text(state.book.name), + prefixes: [FHeaderAction.back(onPress: () => context.pop())], + ), + footer: Padding( + padding: const EdgeInsets.all(16), + child: FPagination( + style: .delta( + itemConstraints: const BoxConstraints.tightFor( + width: 32, + height: 32, ), ), - Positioned.fill( - - child: Align( - alignment: Alignment.bottomCenter, - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: state.isShowBar && state.paths.isNotEmpty - ? Container( - key: const ValueKey('progress_slider'), - height: 80, - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - child: Row( - children: [ - Text('${state.currentPage + 1}'), - Expanded( - child: Slider( - value: state.currentPage.toDouble(), - min: 0, - max: (state.paths.length - 1).toDouble(), - divisions: state.paths.length > 1 - ? state.paths.length - 1 - : 1, - onChanged: (value) { - notifier.jumpToPage(value.toInt()); - }, - ), - ), - Text('${state.paths.length}'), - ], - ), - ) - : const SizedBox.shrink(), - ),), + control: FPaginationControl.managed( + controller: _paginationController, + onChange: _handlePageChange, ), - ], + ), + ), + child: NotificationListener( + onNotification: (notification) { + if (_pageController.hasClients) { + _paginationController.value = _pageController.page!.round(); + notifier.onPageChanged(_pageController.page!.round()); + return true; + } + return false; + }, + child: PageView.builder( + controller: _pageController, + itemCount: state.paths.length, + itemBuilder: (context, index) { + final page = state.paths[index]; + return Image.file(File(page), fit: BoxFit.contain); + }, + ), ), ); } diff --git a/lib/feature/book/ui/view/book_picker_view.dart b/lib/feature/book/ui/view/book_picker_view.dart index 719e8c4..042e0f2 100644 --- a/lib/feature/book/ui/view/book_picker_view.dart +++ b/lib/feature/book/ui/view/book_picker_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:forui/forui.dart'; import 'package:go_router/go_router.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:tele_book/common/config/global_config.dart'; @@ -10,17 +11,14 @@ import 'package:tele_book/feature/book/ui/provider/book_provider.dart'; class BookPickerView extends ConsumerStatefulWidget { final Set disabledBookIds; - const BookPickerView({ - super.key, - this.disabledBookIds = const {}, - }); + const BookPickerView({super.key, this.disabledBookIds = const {}}); @override ConsumerState createState() => _BookPickerViewState(); } class _BookPickerViewState extends ConsumerState { - final Set _selectedBookIds = {}; + Set _selectedBookIds = {}; final TextEditingController _searchController = TextEditingController(); String _keyword = ''; @@ -41,19 +39,29 @@ class _BookPickerViewState extends ConsumerState { }); } + void _selectBooks(Set bookIds) { + setState(() { + _selectedBookIds = bookIds; + }); + } + @override Widget build(BuildContext context) { final asyncState = ref.watch(booksProvider); - return Scaffold( - appBar: AppBar(title: Text("选择书籍")), - body: asyncState.when( + return FScaffold( + header: FHeader.nested(title: Text("选择书籍")), + child: asyncState.when( data: (books) { final filteredBooks = books.where((book) { if (_keyword.isEmpty) return true; return book.name.toLowerCase().contains(_keyword.toLowerCase()); }).toList(); + final selectedBooks = books + .where((book) => _selectedBookIds.contains(book.id)) + .toList(); + if (filteredBooks.isEmpty) { return Column( children: [ @@ -79,39 +87,57 @@ class _BookPickerViewState extends ConsumerState { } return Column( + crossAxisAlignment: .start, children: [ - Padding( - padding: const EdgeInsets.fromLTRB(12, 12, 12, 8), - child: TextField( + FTextField( + control: FTextFieldControl.managed( controller: _searchController, - decoration: const InputDecoration( - hintText: '搜索书名', - prefixIcon: Icon(Icons.search), - border: OutlineInputBorder(), - ), - onChanged: (value) { + onChange: (value) { setState(() { - _keyword = value.trim(); + _keyword = value.text.trim(); }); }, ), + label: Text('搜索书名'), + hint: "请输入要搜索的书名", + prefixBuilder: (context, style, variant) { + return FButton.icon( + style: style.obscureButtonStyle, + child: Icon(Icons.search), + onPress: () {}, + ); + }, ), + SizedBox(height: 8), + Text( + "书籍列表", + style: context.theme.typography.body.xs.copyWith( + fontWeight: .w500, + ), + ), + SizedBox(height: 8), Expanded( - child: ListView.separated( - padding: const EdgeInsets.all(12), - itemCount: filteredBooks.length, - separatorBuilder: (_, __) => const SizedBox(height: 8), - itemBuilder: (context, index) { + child: FSelectTileGroup.builder( + count: filteredBooks.length, + control: FMultiValueControl.managed( + initial: _selectedBookIds, + onChange: (value) { + _selectBooks(value); + }, + ), + tileBuilder: (context, index) { final book = filteredBooks[index]; final isDisabled = widget.disabledBookIds.contains(book.id); - final isSelected = isDisabled || _selectedBookIds.contains(book.id); - final coverPath = book.localSubPaths.isNotEmpty + final coverPath = book.coverSubPath != null + ? GlobalConfig.resolveBookPath(book.coverSubPath!) + : book.localSubPaths.isNotEmpty ? GlobalConfig.resolveBookPath(book.localSubPaths.first) : ''; - return ListTile( - onTap: () => _toggleBook(book.id), - leading: coverPath.isEmpty + return .suffix( + value: book.id, + enabled: !isDisabled, + prefix: coverPath.isEmpty ? Container( width: 64, height: 64, @@ -132,14 +158,21 @@ class _BookPickerViewState extends ConsumerState { ? '已在当前收藏夹中' : '共 ${book.localSubPaths.length} 页', ), - trailing: Checkbox( - value: isSelected, - onChanged: isDisabled ? null : (_) => _toggleBook(book.id), - ), ); }, ), ), + Padding( + padding: .symmetric(vertical: 16), + child: FButton( + onPress: _selectedBookIds.isEmpty + ? null + : () { + context.pop>(selectedBooks); + }, + child: Text('确认选择 (${selectedBooks.length})'), + ), + ), ], ); }, @@ -148,27 +181,6 @@ class _BookPickerViewState extends ConsumerState { ), loading: () => Center(child: CircularProgressIndicator()), ), - bottomNavigationBar: asyncState.when( - data: (books) { - final selectedBooks = books - .where((book) => _selectedBookIds.contains(book.id)) - .toList(); - - return SafeArea( - minimum: const EdgeInsets.fromLTRB(16, 8, 16, 16), - child: FilledButton( - onPressed: selectedBooks.isEmpty - ? null - : () { - context.pop>(selectedBooks); - }, - child: Text('确认选择 (${selectedBooks.length})'), - ), - ); - }, - error: (_, __) => const SizedBox.shrink(), - loading: () => const SizedBox.shrink(), - ), ); } } diff --git a/lib/feature/collection/ui/provider/collection_book_provider.dart b/lib/feature/collection/ui/provider/collection_book_provider.dart index 180f74e..0f5532d 100644 --- a/lib/feature/collection/ui/provider/collection_book_provider.dart +++ b/lib/feature/collection/ui/provider/collection_book_provider.dart @@ -41,9 +41,9 @@ AsyncValue collectionBookView(Ref ref,int collectionId) { .toList(); final items = collectionBooks.map((book) { - final coverImagePath = GlobalConfig.resolveBookPath( - book.localSubPaths.first, - ); + final coverImagePath = book.coverSubPath != null + ? GlobalConfig.resolveBookPath(book.coverSubPath!) + : GlobalConfig.resolveBookPath(book.localSubPaths.first); return BookListItemVo(book: book, coverImagePath: coverImagePath); }).toList(); diff --git a/lib/feature/collection/ui/provider/collection_book_provider.g.dart b/lib/feature/collection/ui/provider/collection_book_provider.g.dart index 4e4808e..3d8fbd3 100644 --- a/lib/feature/collection/ui/provider/collection_book_provider.g.dart +++ b/lib/feature/collection/ui/provider/collection_book_provider.g.dart @@ -75,7 +75,7 @@ final class CollectionBookViewProvider } String _$collectionBookViewHash() => - r'35e6922990efcd8afaa6ab1d59d5da989122b2fa'; + r'c03f3388df2a89f51a6a06a98d302c2dd9f4402b'; final class CollectionBookViewFamily extends $Family with $FunctionalFamilyOverride, int> { diff --git a/lib/feature/collection/ui/provider/collection_provider.dart b/lib/feature/collection/ui/provider/collection_provider.dart index 8e4a299..35c8d16 100644 --- a/lib/feature/collection/ui/provider/collection_provider.dart +++ b/lib/feature/collection/ui/provider/collection_provider.dart @@ -72,7 +72,9 @@ AsyncValue> collectionList(Ref ref) { .map((bookId) => bookById[bookId]) .whereType() .where((book) => book.localSubPaths.isNotEmpty) - .map((book) => GlobalConfig.resolveBookPath(book.localSubPaths.first)) + .map((book) => book.coverSubPath != null + ? GlobalConfig.resolveBookPath(book.coverSubPath!) + : GlobalConfig.resolveBookPath(book.localSubPaths.first)) .take(4) .toList(); diff --git a/lib/feature/collection/ui/provider/collection_provider.g.dart b/lib/feature/collection/ui/provider/collection_provider.g.dart index 21e8f2d..f005379 100644 --- a/lib/feature/collection/ui/provider/collection_provider.g.dart +++ b/lib/feature/collection/ui/provider/collection_provider.g.dart @@ -55,7 +55,7 @@ final class CollectionListProvider } } -String _$collectionListHash() => r'5bfd649d7f9d74b075041ce8e4afa9c3535400f9'; +String _$collectionListHash() => r'b121eda013ea7f8b0fd9013e6ea4694967e302a1'; @ProviderFor(CreateCollectionController) final createCollectionControllerProvider = diff --git a/lib/feature/collection/ui/view/collection_book_view.dart b/lib/feature/collection/ui/view/collection_book_view.dart index f9fe309..3c60d7b 100644 --- a/lib/feature/collection/ui/view/collection_book_view.dart +++ b/lib/feature/collection/ui/view/collection_book_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:forui/forui.dart'; import 'package:go_router/go_router.dart'; import 'package:tele_book/common/widget/empty_widget.dart'; import 'package:tele_book/common/widget/error_widget.dart'; @@ -19,14 +20,22 @@ class CollectionBookView extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final asyncState = ref.watch(collectionBookViewProvider(collectionId)); - return Scaffold( - appBar: AppBar( + return FScaffold( + header: FHeader.nested( title: Text("书籍列表"), - actions: [ - IconButton( + prefixes: [ + FHeaderAction.back( + onPress: () { + context.pop(); + }, + ), + ], + suffixes: [ + FHeaderAction( icon: Icon(Icons.add), - onPressed: () async { - final disabledBookIds = asyncState.value?.books + onPress: () async { + final disabledBookIds = + asyncState.value?.books .map((item) => item.book.id) .toList() ?? []; @@ -42,78 +51,123 @@ class CollectionBookView extends ConsumerWidget { collectionId: collectionId, bookIds: result.map((e) => e.id).toList(), ); + showFToast(context: context, title: Text("添加书籍到收藏成功")); } }, ), ], ), - body: asyncState.when( + child: asyncState.when( data: (data) { if (data.books.isEmpty) { return Center( child: CustomEmptyWidget(icon: CupertinoIcons.book, text: "暂无书籍"), ); } - return ListView.builder( - itemCount: data.books.length, + return FItemGroup.builder( + count: data.books.length, itemBuilder: (context, index) { final item = data.books[index]; - return GestureDetector( - onTap: () => context.push(AppRoute.bookPage, extra: item.book), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - child: Row( - children: [ - LocalImageWidget(imagePath: item.coverImagePath), - Expanded( - child: ListTile( - title: Text( - item.book.name, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - subtitle: Text( - '共 ${item.book.localSubPaths.length} 页', - ), - trailing: IconButton( - onPressed: () async { - final confirm = await showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text("确认删除吗?"), - content: Text("将从书架中移除该书籍,但不会删除本地文件"), - actions: [ - TextButton( - onPressed: () => + return FItem( + title: Text( + item.book.name, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + prefix: LocalImageWidget(imagePath: item.coverImagePath), + subtitle: Text('共 ${item.book.localSubPaths.length} 页'), + suffix: FButton.icon( + variant: .ghost, + onPress: () async { + final confirm = await showFDialog( + context: context, + builder: (context, style, animate) => FDialog.adaptive( + verticalBuilder: (context, style) { + return Padding( + padding: .all(16), + child: Column( + crossAxisAlignment: .start, + mainAxisSize: .min, + children: [ + Text("确认删除吗?", style: style.titleTextStyle), + SizedBox(width: 8), + Text( + "将从书架中移除该书籍,但不会删除本地文件", + style: style.bodyTextStyle, + ), + SizedBox(width: 8), + Row( + mainAxisAlignment: .end, + children: [ + FButton( + variant: .ghost, + onPress: () => Navigator.pop(context, false), child: Text("取消"), ), - TextButton( - onPressed: () => + SizedBox(width: 8), + FButton( + variant: .destructive, + onPress: () => Navigator.pop(context, true), child: Text("确认"), ), ], ), - ); - if (confirm == true) { - await ref - .read(collectionRepositoryProvider) - .removeBookFromCollection( - collectionId: collectionId, - bookId: item.book.id, - ); - } - }, - icon: Icon(Icons.delete), - ), - ), + ], + ), + ); + }, + horizontalBuilder: (context, style) { + return Padding( + padding: .all(16), + child: Column( + crossAxisAlignment: .start, + mainAxisSize: .min, + children: [ + Text("确认删除吗?", style: style.titleTextStyle), + SizedBox(width: 8), + Text( + "将从书架中移除该书籍,但不会删除本地文件", + style: style.bodyTextStyle, + ), + SizedBox(width: 8), + Row( + mainAxisAlignment: .end, + children: [ + FButton( + variant: .ghost, + onPress: () => + Navigator.pop(context, false), + child: Text("取消"), + ), + SizedBox(width: 8), + FButton( + variant: .destructive, + onPress: () { + Navigator.pop(context, true); + }, + child: Text("确认"), + ), + ], + ), + ], + ), + ); + }, ), - ], - ), + ); + if (confirm == true) { + await ref + .read(collectionRepositoryProvider) + .removeBookFromCollection( + collectionId: collectionId, + bookId: item.book.id, + ); + showFToast(context: context, title: Text("删除收藏书籍成功")); + } + }, + child: Icon(FLucideIcons.trash), ), ); }, diff --git a/lib/feature/collection/ui/view/collection_view.dart b/lib/feature/collection/ui/view/collection_view.dart index 1fed5cd..f141353 100644 --- a/lib/feature/collection/ui/view/collection_view.dart +++ b/lib/feature/collection/ui/view/collection_view.dart @@ -2,17 +2,29 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:forui/forui.dart'; import 'package:go_router/go_router.dart'; import 'package:tele_book/common/widget/empty_widget.dart'; import 'package:tele_book/common/widget/error_widget.dart'; import 'package:tele_book/core/route/app_route.dart'; import 'package:tele_book/feature/collection/ui/provider/collection_provider.dart'; -class CollectionView extends ConsumerWidget { +class CollectionView extends ConsumerStatefulWidget { const CollectionView({super.key}); @override - Widget build(BuildContext context, WidgetRef ref) { + ConsumerState createState() => _CollectionViewState(); +} + +class _CollectionViewState extends ConsumerState + with TickerProviderStateMixin { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { final listAsync = ref.watch(collectionListProvider); ref.listen>(createCollectionControllerProvider, ( @@ -22,15 +34,15 @@ class CollectionView extends ConsumerWidget { next.whenOrNull( data: (_) { if (prev?.isLoading == true) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text("创建收藏夹成功"))); + showFToast(context: context, title: Text("创建收藏夹成功")); } }, error: (e, _) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text("创建收藏夹失败: $e"))); + showFToast( + context: context, + title: Text("创建收藏夹失败"), + description: Text(e.toString()), + ); }, ); }); @@ -42,15 +54,15 @@ class CollectionView extends ConsumerWidget { next.whenOrNull( data: (_) { if (prev?.isLoading == true) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text("更新收藏夹成功"))); + showFToast(context: context, title: Text("修改收藏夹成功")); } }, error: (e, _) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text("更新收藏夹失败: $e"))); + showFToast( + context: context, + title: Text("修改收藏夹失败"), + description: Text(e.toString()), + ); }, ); }); @@ -62,30 +74,30 @@ class CollectionView extends ConsumerWidget { next.whenOrNull( data: (_) { if (prev?.isLoading == true) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text("删除收藏夹成功"))); + showFToast(context: context, title: Text("删除收藏夹成功")); } }, error: (e, _) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text("删除收藏夹失败: $e"))); + showFToast( + context: context, + title: Text("删除收藏夹失败"), + description: Text(e.toString()), + ); }, ); }); - return Scaffold( - appBar: AppBar( + return FScaffold( + header: FHeader( title: Text("收藏夹"), - actions: [ - IconButton( - onPressed: () => _showCreateBottomSheet(context, ref), + suffixes: [ + FHeaderAction( + onPress: () => _showCreateBottomSheet(context, ref), icon: const Icon(Icons.add), ), ], ), - body: listAsync.when( + child: listAsync.when( data: (list) { if (list.isEmpty) { return const Center( @@ -96,16 +108,16 @@ class CollectionView extends ConsumerWidget { } return GridView.builder( - padding: const EdgeInsets.all(16), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 200, mainAxisSpacing: 8, crossAxisSpacing: 8, - childAspectRatio: 0.75, + mainAxisExtent: 230, ), itemCount: list.length, - itemBuilder: (context, index) { + itemBuilder: (listContext, index) { final item = list[index]; + final controller = FPopoverController(vsync: this); return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => context.push( @@ -140,52 +152,69 @@ class CollectionView extends ConsumerWidget { return Image.file( File(url), fit: BoxFit.cover, - cacheWidth: 200, - cacheHeight: 200, + cacheWidth: 300, ); }).toList(), ), ), ), const SizedBox(height: 4), - Expanded( - child: ListTile( - contentPadding: const EdgeInsets.symmetric( - horizontal: 8, - ), - minVerticalPadding: 0, - title: Text( - item.collection.name, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - subtitle: Text("${item.count} 本书"), - trailing: PopupMenuButton( - onSelected: (value) { - if (value == 'update') { - _showUpdateBottomSheet( - context, - ref, - collectionId: item.collection.id, - initialName: item.collection.name, - initialDescription: - item.collection.description ?? '', - ); - } - if (value == 'delete') { - _showDeleteConfirmDialog( - context, - item.collection.id, - ref, - ); - } - }, - itemBuilder: (_) => const [ - PopupMenuItem(value: 'update', child: Text('编辑')), - PopupMenuItem(value: 'delete', child: Text('删除')), - ], + FItem( + title: Text( + item.collection.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + subtitle: Text("${item.count} 本书"), + suffix: FPopoverMenu( + control: FPopoverControl.managed( + controller: controller, ), + autofocus: true, + menu: [ + .group( + children: [ + .item( + title: Text('编辑'), + prefix: Icon(FLucideIcons.edit), + onPress: () { + controller.hide(); + _showUpdateBottomSheet( + context, + ref, + collectionId: item.collection.id, + initialName: item.collection.name, + initialDescription: + item.collection.description ?? '', + ); + }, + ), + .item( + variant: .destructive, + title: Text('删除'), + prefix: Icon(FLucideIcons.delete), + onPress: () { + controller.hide(); + _showDeleteConfirmDialog( + context, + item.collection.id, + ref, + ); + }, + ), + ], + ), + ], + builder: (context, controller, child) { + return FButton.icon( + onPress: () { + controller.show(); + }, + variant: .ghost, + child: Icon(FLucideIcons.moreHorizontal), + ); + }, ), ), ], @@ -211,74 +240,113 @@ class CollectionView extends ConsumerWidget { BuildContext context, WidgetRef ref, ) async { + final formKey = GlobalKey(); final nameController = TextEditingController(); final descController = TextEditingController(); - - final formData = await showModalBottomSheet<_CollectionFormData>( + final formData = await showFSheet<_CollectionFormData>( context: context, - enableDrag: true, - isScrollControlled: true, + side: .btt, + mainAxisMaxRatio: null, builder: (sheetContext) { - return Padding( - padding: MediaQuery.of(sheetContext).viewInsets, + return Form( + key: formKey, child: Container( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text("创建收藏夹", style: Theme.of(context).textTheme.headlineSmall), - const SizedBox(height: 16), - TextField( - controller: nameController, - decoration: const InputDecoration( - labelText: "收藏夹名称", - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.collections_bookmark_outlined), + decoration: BoxDecoration( + color: context.theme.colors.background, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(16), + ), + border: .symmetric( + horizontal: BorderSide(color: context.theme.colors.border), + ), + ), + child: Padding( + padding: .all(16), + child: Column( + mainAxisAlignment: .center, + mainAxisSize: .min, + crossAxisAlignment: .start, + children: [ + Text( + "创建收藏夹", + style: context.theme.typography.display.xl2.copyWith( + fontWeight: .w600, + color: context.theme.colors.foreground, + height: 1.5, + ), ), - ), - SizedBox(height: 16), - TextField( - controller: descController, - decoration: const InputDecoration( - labelText: "描述", - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.description_outlined), + const SizedBox(height: 8), + Text( + "按自己的习惯创建收藏夹,方便管理书籍", + style: context.theme.typography.body.sm.copyWith( + color: context.theme.colors.mutedForeground, + ), ), - ), - const SizedBox(height: 16), - SizedBox( - width: double.infinity, - child: FilledButton( - onPressed: () { - final name = nameController.text.trim(); - final desc = descController.text.trim(); - if (name.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text("名称不能为空"))); - return; - } - Navigator.of( - sheetContext, - ).pop(_CollectionFormData(name: name, description: desc)); + const SizedBox(height: 16), + FTextFormField( + control: FTextFieldControl.managed( + controller: nameController, + ), + label: Text("收藏夹名称"), + hint: "请输入收藏夹名称", + prefixBuilder: (context, style, _) { + return FButton.icon( + onPress: () {}, + style: style.obscureButtonStyle, + child: Icon(Icons.collections_bookmark_outlined), + ); }, - child: const Text("创建"), + validator: (v) => (v?.isEmpty ?? true) ? "请输入收藏夹名称" : null, ), - ), - ], + SizedBox(height: 16), + FTextFormField( + control: FTextFieldControl.managed( + controller: descController, + ), + label: Text("描述"), + hint: "请输入收藏夹描述(可选)", + prefixBuilder: (context, style, _) { + return FButton.icon( + onPress: () {}, + style: style.obscureButtonStyle, + child: Icon(Icons.description_outlined), + ); + }, + ), + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: FButton( + onPress: () { + if (formKey.currentState!.validate()) { + context.pop( + _CollectionFormData( + name: nameController.text, + description: descController.text, + ), + ); + } + }, + prefix: Icon(FLucideIcons.plus), + child: const Text("创建"), + ), + ), + ], + ), ), ), ); }, ); - if (formData == null) return; - ref - .read(createCollectionControllerProvider.notifier) - .createCollection( - name: formData.name, - description: formData.description, - ); + if (formData != null) { + await ref + .read(createCollectionControllerProvider.notifier) + .createCollection( + name: formData.name, + description: formData.description, + ); + } } Future _showUpdateBottomSheet( @@ -288,75 +356,114 @@ class CollectionView extends ConsumerWidget { required String initialName, required String initialDescription, }) async { + final formKey = GlobalKey(); final nameController = TextEditingController(text: initialName); final descController = TextEditingController(text: initialDescription); - - final formData = await showModalBottomSheet<_CollectionFormData>( - isScrollControlled: true, - enableDrag: true, + final formData = await showFSheet<_CollectionFormData>( context: context, + side: .btt, + mainAxisMaxRatio: null, builder: (sheetContext) { - return Padding( - padding: MediaQuery.of(sheetContext).viewInsets, + return Form( + key: formKey, child: Container( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text("编辑收藏夹", style: Theme.of(context).textTheme.headlineSmall), - SizedBox(height: 16), - TextField( - controller: nameController, - decoration: const InputDecoration( - labelText: "收藏夹名称", - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.collections_bookmark_outlined), + decoration: BoxDecoration( + color: context.theme.colors.background, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(16), + ), + border: .symmetric( + horizontal: BorderSide(color: context.theme.colors.border), + ), + ), + child: Padding( + padding: .all(16), + child: Column( + mainAxisAlignment: .center, + mainAxisSize: .min, + crossAxisAlignment: .start, + children: [ + Text( + "编辑收藏夹", + style: context.theme.typography.display.xl2.copyWith( + fontWeight: .w600, + color: context.theme.colors.foreground, + height: 1.5, + ), ), - ), - SizedBox(height: 16), - TextField( - controller: descController, - decoration: const InputDecoration( - labelText: "描述", - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.description_outlined), + const SizedBox(height: 8), + Text( + "按自己的习惯创建收藏夹,方便管理书籍", + style: context.theme.typography.body.sm.copyWith( + color: context.theme.colors.mutedForeground, + ), ), - ), - const SizedBox(height: 16), - SizedBox( - width: double.infinity, - child: FilledButton( - onPressed: () { - final name = nameController.text.trim(); - final desc = descController.text.trim(); - if (name.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text("名称不能为空"))); - return; - } - Navigator.of( - sheetContext, - ).pop(_CollectionFormData(name: name, description: desc)); + const SizedBox(height: 16), + FTextFormField( + control: FTextFieldControl.managed( + controller: nameController, + ), + label: Text("收藏夹名称"), + hint: "请输入收藏夹名称", + prefixBuilder: (context, style, _) { + return FButton.icon( + onPress: () {}, + style: style.obscureButtonStyle, + child: Icon(Icons.collections_bookmark_outlined), + ); }, - child: const Text("更新"), + validator: (v) => (v?.isEmpty ?? true) ? "请输入收藏夹名称" : null, ), - ), - ], + SizedBox(height: 16), + FTextFormField( + control: FTextFieldControl.managed( + controller: descController, + ), + label: Text("描述"), + hint: "请输入收藏夹描述(可选)", + prefixBuilder: (context, style, _) { + return FButton.icon( + onPress: () {}, + style: style.obscureButtonStyle, + child: Icon(Icons.description_outlined), + ); + }, + ), + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: FButton( + onPress: () { + if (formKey.currentState!.validate()) { + context.pop( + _CollectionFormData( + name: nameController.text, + description: descController.text, + ), + ); + } + }, + prefix: Icon(FLucideIcons.edit), + child: const Text("修改"), + ), + ), + ], + ), ), ), ); }, ); - if (formData == null) return; - ref - .read(updateCollectionControllerProvider.notifier) - .updateCollection( - collectionId: collectionId, - name: formData.name, - description: formData.description, - ); + if (formData != null) { + ref + .read(updateCollectionControllerProvider.notifier) + .updateCollection( + collectionId: collectionId, + name: nameController.text, + description: descController.text, + ); + } } Future _showDeleteConfirmDialog( @@ -364,26 +471,67 @@ class CollectionView extends ConsumerWidget { int collectionId, WidgetRef ref, ) async { - final confirmed = await showDialog( + final confirmed = await showFDialog( context: context, - builder: (dialogContext) { - return AlertDialog( - title: const Text("删除收藏夹"), - content: const Text("确定要删除这个收藏夹吗?"), - actions: [ - TextButton( - onPressed: () => Navigator.of(dialogContext).pop(false), - child: const Text("取消"), + builder: (dialogContext, style, animate) => FDialog.adaptive( + horizontalBuilder: (context, style) { + return Padding( + padding: .all(16), + child: Column( + mainAxisSize: .min, + children: [ + const Text("删除收藏夹"), + const Text("确定要删除这个收藏夹吗?"), + Row( + children: [ + TextButton( + onPressed: () => Navigator.of(dialogContext).pop(false), + child: const Text("取消"), + ), + ElevatedButton( + onPressed: () => Navigator.of(dialogContext).pop(true), + child: const Text("删除"), + ), + ], + ), + ], ), - ElevatedButton( - onPressed: () => Navigator.of(dialogContext).pop(true), - child: const Text("删除"), + ); + }, + + verticalBuilder: (context, style) { + return Padding( + padding: .all(16), + child: Column( + mainAxisSize: .min, + crossAxisAlignment: .start, + children: [ + Text("删除收藏夹", style: style.titleTextStyle), + SizedBox(height: 8), + Text("确定要删除这个收藏夹吗?", style: style.bodyTextStyle), + SizedBox(height: 8), + Row( + mainAxisAlignment: .end, + children: [ + FButton( + variant: .ghost, + onPress: () => Navigator.of(dialogContext).pop(false), + child: const Text("取消"), + ), + SizedBox(width: 8), + FButton( + variant: .destructive, + onPress: () => Navigator.of(dialogContext).pop(true), + child: const Text("删除"), + ), + ], + ), + ], ), - ], - ); - }, + ); + }, + ), ); - if (confirmed == true) { ref .read(deleteCollectionControllerProvider.notifier) diff --git a/lib/feature/download/ui/view/download_list_view.dart b/lib/feature/download/ui/view/download_list_view.dart index 61b18d6..4fb8e5b 100644 --- a/lib/feature/download/ui/view/download_list_view.dart +++ b/lib/feature/download/ui/view/download_list_view.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:forui/forui.dart'; import 'package:go_router/go_router.dart'; import 'package:tele_book/common/widget/error_widget.dart'; import 'package:tele_book/common/widget/task_item_widget.dart'; @@ -8,7 +10,6 @@ import 'package:tele_book/feature/download/enum/download_status.dart'; import 'package:tele_book/feature/download/model/bo/download_bo.dart'; import 'package:tele_book/feature/download/service/download_service.dart'; import 'package:tele_book/feature/download/ui/provider/download_provider.dart'; -import 'package:tele_book/feature/download/ui/widget/download_form_bottom_sheet_widget.dart'; import 'package:tele_book/feature/download/ui/widget/download_task_sheet_widget.dart'; class DownloadListView extends ConsumerWidget { @@ -23,28 +24,27 @@ class DownloadListView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(downloadTasksProvider); - return Scaffold( - appBar: AppBar( + return FScaffold( + header: FHeader( title: Text("下载任务"), - actions: [ - IconButton( - tooltip: '清空已完成任务', - onPressed: () async { + suffixes: [ + FHeaderAction( + onPress: () async { await _clearCompletedTasks(context, ref); }, - icon: const Icon(Icons.delete_sweep), + icon: Icon(FLucideIcons.trash), ), - IconButton( - onPressed: () async { + + FHeaderAction( + onPress: () async { await _showDownloadForm(context); }, - icon: Icon(Icons.add), + icon: Icon(FLucideIcons.plus), ), ], ), - - body: state.when( - loading: () => Center(child: CircularProgressIndicator()), + child: state.when( + loading: () => Center(child: FProgress()), error: (e, st) => Center( child: CustomErrorWidget(errorMessage: "加载下载任务失败", stackTrace: st), ), @@ -63,9 +63,7 @@ class DownloadListView extends ConsumerWidget { ], ), ) - : ListView.separated( - padding: EdgeInsets.all(16), - separatorBuilder: (context, index) => SizedBox(height: 16), + : FItemGroup.builder( itemBuilder: (context, index) { final item = tasks[index]; final progressPercent = _groupProgressPercent( @@ -79,11 +77,13 @@ class DownloadListView extends ConsumerWidget { onTap: () => _showDownloadTaskList( context, item.downloadGroupBo.id, + item.downloadGroupBo.name, ref, ), + trailing: Icon(FLucideIcons.chevronRight), ); }, - itemCount: tasks.length, + count: tasks.length, ); }, ), @@ -97,33 +97,20 @@ class DownloadListView extends ConsumerWidget { ); if (!hasCompleted) { if (!context.mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('没有可清空的已完成任务'))); + showFToast(context: context, title: Text('没有可清空的已完成任务')); return; } - final confirmed = await showDialog( + final confirmed = await showFDialog( context: context, - builder: (dialogContext) { - return AlertDialog( - title: const Text('清空已完成任务'), - content: const Text('将移除所有已完成的下载任务组及其临时文件,是否继续?'), - actions: [ - TextButton( - onPressed: () => Navigator.of(dialogContext).pop(false), - child: const Text('取消'), - ), - ElevatedButton( - onPressed: () => Navigator.of(dialogContext).pop(true), - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.error, - ), - child: const Text('清空', style: TextStyle(color: Colors.white)), - ), - ], - ); - }, + builder: (dialogContext, style, animate) => FDialog.adaptive( + style: style, + animation: animate, + horizontalBuilder: (context, style) => + Text('清空已完成任务', style: style.titleTextStyle), + verticalBuilder: (context, style) => + Text('将移除所有已完成的下载任务组及其临时文件,是否继续?', style: style.bodyTextStyle), + ), ); if (confirmed != true || !context.mounted) return; @@ -133,39 +120,104 @@ class DownloadListView extends ConsumerWidget { .read(downloadServiceProvider) .clearCompletedTasks(); if (!context.mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('已清空 $clearedCount 个已完成任务'))); + showFToast(context: context, title: Text('已清空 $clearedCount 个已完成任务')); } catch (e) { if (!context.mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(e.toString()))); + showFToast( + context: context, + title: Text("清楚失败"), + description: Text(e.toString()), + ); } } void _showDownloadTaskList( BuildContext context, String groupId, + String name, WidgetRef ref, ) { - showModalBottomSheet( + showFSheet( context: context, - builder: (_) => DownloadTaskSheetWidget(groupId: groupId), + side: .btt, + builder: (_) => DownloadTaskSheetWidget(groupId: groupId,name: name,), ); } Future _showDownloadForm(BuildContext context) async { - final url = await showModalBottomSheet( - isScrollControlled: true, - enableDrag: true, + final urlController = TextEditingController(); + final url = await showFSheet( context: context, - builder: (sheetContext) { - return DownloadFormBottomSheetWidget(); + side: .btt, + style: const .delta(flingVelocity: 700), + builder: (context) { + return Container( + decoration: BoxDecoration( + color: context.theme.colors.background, + borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), + border: .symmetric( + horizontal: BorderSide(color: context.theme.colors.border), + ), + ), + child: Padding( + padding: .all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "下载链接", + style: context.theme.typography.display.xl2.copyWith( + fontWeight: .w600, + color: context.theme.colors.foreground, + height: 1.5, + ), + ), + SizedBox(height: 8), + Text( + '要下载的网页图片链接,如https://www.google.com', + style: context.theme.typography.body.sm.copyWith( + color: context.theme.colors.mutedForeground, + ), + ), + SizedBox(height: 16), + FTextFormField( + control: FTextFieldControl.managed(controller: urlController), + label: Text("URL"), + hint: "请输入网址URL", + suffixBuilder: (context, style, _) { + return FButton.icon( + onPress: () async { + final clipData = await Clipboard.getData( + Clipboard.kTextPlain, + ); + if (clipData?.text != null) { + urlController.text = clipData!.text!; + } + }, + child: Icon(FLucideIcons.clipboardPaste), + style: style.obscureButtonStyle, + ); + }, + ), + Padding( + padding: .symmetric(vertical: 16), + child: FButton( + onPress: () { + context.pop(urlController.text); + }, + child: Text("解析"), + ), + ), + ], + ), + ), + ); }, ); - if (url == null || url.isEmpty || !context.mounted) return; - context.push(AppRoute.parseWeb, extra: url); + if (url != null && url.isNotEmpty) { + await context.push(AppRoute.parseWeb, extra: urlController.text); + } } } diff --git a/lib/feature/download/ui/widget/download_form_bottom_sheet_widget.dart b/lib/feature/download/ui/widget/download_form_bottom_sheet_widget.dart deleted file mode 100644 index 8a94759..0000000 --- a/lib/feature/download/ui/widget/download_form_bottom_sheet_widget.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class DownloadFormBottomSheetWidget extends ConsumerStatefulWidget { - const DownloadFormBottomSheetWidget({super.key}); - - @override - ConsumerState createState() => _DownloadFormBottomSheetState(); -} - -class _DownloadFormBottomSheetState extends ConsumerState { - // 将控制器绑定到 State 的生命周期中 - late final TextEditingController _urlController; - - @override - void initState() { - super.initState(); - _urlController = TextEditingController(); - } - - @override - void dispose() { - // 规范:在此处安全销毁,当 BottomSheet 彻底消失在屏幕上时触发 - _urlController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - // 处理键盘避让 - final bottomInset = MediaQuery.of(context).viewInsets.bottom; - - return AnimatedPadding( - duration: const Duration(milliseconds: 100), - curve: Curves.easeOut, - padding: EdgeInsets.fromLTRB(16, 16, 16, 16 + bottomInset), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "添加下载任务", - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 16), - TextField( - controller: _urlController, - decoration: InputDecoration( - labelText: "下载链接", - border: const OutlineInputBorder(), - prefixIcon: const Icon(Icons.link), - suffixIcon: IconButton( - onPressed: () async { - final clipData = await Clipboard.getData(Clipboard.kTextPlain); - if (clipData != null && clipData.text != null) { - _urlController.text = clipData.text!; - } - }, - icon: const Icon(Icons.paste), - ), - ), - keyboardType: TextInputType.url, - inputFormatters: [ - FilteringTextInputFormatter.deny(RegExp(r'\s')), - ], - ), - const SizedBox(height: 16), - SizedBox( - width: double.infinity, - child: FilledButton( - onPressed: () { - final inputUrl = _urlController.text.trim(); - // 规范:直接使用当前组件的 context 返回数据 - Navigator.of(context).pop(inputUrl); - }, - child: const Text("添加"), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/feature/download/ui/widget/download_task_sheet_widget.dart b/lib/feature/download/ui/widget/download_task_sheet_widget.dart index 0ab8e47..76c14a8 100644 --- a/lib/feature/download/ui/widget/download_task_sheet_widget.dart +++ b/lib/feature/download/ui/widget/download_task_sheet_widget.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:forui/forui.dart'; +import 'package:tele_book/common/widget/f_sheet_content.dart'; import 'package:tele_book/common/widget/task_item_widget.dart'; import 'package:tele_book/feature/download/enum/download_status.dart'; import 'package:tele_book/feature/download/service/download_service.dart'; @@ -7,8 +9,9 @@ import 'package:tele_book/feature/download/ui/provider/download_provider.dart'; class DownloadTaskSheetWidget extends ConsumerWidget { final String groupId; + final String name; - const DownloadTaskSheetWidget({required this.groupId}); + const DownloadTaskSheetWidget({required this.groupId, required this.name}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -17,7 +20,7 @@ class DownloadTaskSheetWidget extends ConsumerWidget { return asyncTasks.when( loading: () => const Padding( padding: EdgeInsets.all(16), - child: Center(child: CircularProgressIndicator()), + child: Center(child: FCircularProgress()), ), error: (e, st) => Padding( padding: const EdgeInsets.all(16), @@ -46,19 +49,18 @@ class DownloadTaskSheetWidget extends ConsumerWidget { ); } - return Padding( - padding: const EdgeInsets.all(16), + return FSheetContent( + side: .btt, child: Column( + mainAxisSize: .min, + crossAxisAlignment: .start, children: [ - const Text( - "下载任务详情", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 16), + FSheetContent.title(context, "下载任务详情"), + FSheetContent.subTitle(context,name), + const SizedBox(height: 12), Expanded( - child: ListView.separated( - itemCount: tasks.length, - separatorBuilder: (_, __) => const SizedBox(height: 16), + child: FItemGroup.builder( + count: tasks.length, itemBuilder: (context, index) { final task = tasks[index]; return TaskItemWidget( @@ -138,14 +140,14 @@ class DownloadTaskSheetWidget extends ConsumerWidget { .deleteTaskItem(task.id); } catch (e) { if (!context.mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar( - SnackBar(content: Text(e.toString())), + showFToast( + context: context, + title: Text("删除失败"), + description: Text(e.toString()), ); } }, - icon: const Icon(Icons.delete), + icon: const Icon(FLucideIcons.trash), ), ], ), diff --git a/lib/feature/export/ui/provider/export_batch_provider.dart b/lib/feature/export/ui/provider/export_batch_provider.dart new file mode 100644 index 0000000..1a7d8ca --- /dev/null +++ b/lib/feature/export/ui/provider/export_batch_provider.dart @@ -0,0 +1,113 @@ +import 'package:dk_util/dk_util.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:tele_book/common/config/global_config.dart'; +import 'package:tele_book/feature/book/ui/provider/book_provider.dart'; +import 'package:tele_book/feature/export/enum/export_format.dart'; +import 'package:tele_book/feature/export/model/export_item.dart'; +import 'package:tele_book/feature/export/service/export_service.dart'; + +part 'export_batch_provider.freezed.dart'; + +part 'export_batch_provider.g.dart'; + +@freezed +abstract class ExportBatchState with _$ExportBatchState { + const factory ExportBatchState({ + required ExportFormat format, + required bool isExporting, + required bool isDone, + required int progress, + required List items, + required TextEditingController outputPathController, + String? errorMessage, + }) = _ExportBatchState; +} + +@riverpod +class ExportBatch extends _$ExportBatch { + final ExportService _exportService = ExportService(); + + @override + ExportBatchState build(List bookIds) { + final books = ref + .read(bookListProvider) + .value + ?.bookVos + .where((e) => bookIds.contains(e.book.id)) + .map((e) => e.book) + .toList(); + if (books == null || books.isEmpty) { + throw Exception('Books not found'); + } + + final items = books + .map( + (b) => ExportItem( + book: b, + coverPath: b.coverSubPath != null + ? '${GlobalConfig.booksDir.path}/${b.coverSubPath}' + : '${GlobalConfig.booksDir.path}/${b.localSubPaths.first}', + ), + ) + .toList(); + + ref.onDispose(() { + for (final item in items) { + item.dispose(); + } + }); + + return ExportBatchState( + format: ExportFormat.folder, + isExporting: false, + isDone: false, + progress: 0, + items: items, + outputPathController: TextEditingController() + ); + } + + void setFormat(ExportFormat fmt) { + state = state.copyWith(format: fmt); + } + + void setError(String message) { + state = state.copyWith(errorMessage: message); + } + + Future pickOutputDir() async { + final result = await FilePicker.platform.getDirectoryPath(); + if (result != null) { + state.outputPathController.text=result; + } + } + + Future doExport() async { + final path = state.outputPathController.text; + + state = state.copyWith(isExporting: true, progress: 0, errorMessage: null); + try { + final exportList = state.items + .map((i) => (book: i.book, fileName: i.nameController.text.trim())) + .toList(); + + await _exportService.exportBatch( + items: exportList, + outputDirPath: path, + format: state.format, + onProgress: (current, total) { + if (!ref.mounted) return; + state = state.copyWith(progress: current); + }, + ); + if (!ref.mounted) return; + state = state.copyWith(isDone: true, isExporting: false); + } catch (e) { + if (!ref.mounted) return; + state = state.copyWith(isExporting: false, errorMessage: '导出失败:$e'); + } + } +} diff --git a/lib/feature/export/ui/provider/export_batch_provider.freezed.dart b/lib/feature/export/ui/provider/export_batch_provider.freezed.dart new file mode 100644 index 0000000..fc4de6e --- /dev/null +++ b/lib/feature/export/ui/provider/export_batch_provider.freezed.dart @@ -0,0 +1,295 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'export_batch_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$ExportBatchState { + + ExportFormat get format; bool get isExporting; bool get isDone; int get progress; List get items; TextEditingController get outputPathController; String? get errorMessage; +/// Create a copy of ExportBatchState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ExportBatchStateCopyWith get copyWith => _$ExportBatchStateCopyWithImpl(this as ExportBatchState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ExportBatchState&&(identical(other.format, format) || other.format == format)&&(identical(other.isExporting, isExporting) || other.isExporting == isExporting)&&(identical(other.isDone, isDone) || other.isDone == isDone)&&(identical(other.progress, progress) || other.progress == progress)&&const DeepCollectionEquality().equals(other.items, items)&&(identical(other.outputPathController, outputPathController) || other.outputPathController == outputPathController)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)); +} + + +@override +int get hashCode => Object.hash(runtimeType,format,isExporting,isDone,progress,const DeepCollectionEquality().hash(items),outputPathController,errorMessage); + +@override +String toString() { + return 'ExportBatchState(format: $format, isExporting: $isExporting, isDone: $isDone, progress: $progress, items: $items, outputPathController: $outputPathController, errorMessage: $errorMessage)'; +} + + +} + +/// @nodoc +abstract mixin class $ExportBatchStateCopyWith<$Res> { + factory $ExportBatchStateCopyWith(ExportBatchState value, $Res Function(ExportBatchState) _then) = _$ExportBatchStateCopyWithImpl; +@useResult +$Res call({ + ExportFormat format, bool isExporting, bool isDone, int progress, List items, TextEditingController outputPathController, String? errorMessage +}); + + + + +} +/// @nodoc +class _$ExportBatchStateCopyWithImpl<$Res> + implements $ExportBatchStateCopyWith<$Res> { + _$ExportBatchStateCopyWithImpl(this._self, this._then); + + final ExportBatchState _self; + final $Res Function(ExportBatchState) _then; + +/// Create a copy of ExportBatchState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? format = null,Object? isExporting = null,Object? isDone = null,Object? progress = null,Object? items = null,Object? outputPathController = null,Object? errorMessage = freezed,}) { + return _then(_self.copyWith( +format: null == format ? _self.format : format // ignore: cast_nullable_to_non_nullable +as ExportFormat,isExporting: null == isExporting ? _self.isExporting : isExporting // ignore: cast_nullable_to_non_nullable +as bool,isDone: null == isDone ? _self.isDone : isDone // ignore: cast_nullable_to_non_nullable +as bool,progress: null == progress ? _self.progress : progress // ignore: cast_nullable_to_non_nullable +as int,items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable +as List,outputPathController: null == outputPathController ? _self.outputPathController : outputPathController // ignore: cast_nullable_to_non_nullable +as TextEditingController,errorMessage: freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ExportBatchState]. +extension ExportBatchStatePatterns on ExportBatchState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ExportBatchState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ExportBatchState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ExportBatchState value) $default,){ +final _that = this; +switch (_that) { +case _ExportBatchState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ExportBatchState value)? $default,){ +final _that = this; +switch (_that) { +case _ExportBatchState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( ExportFormat format, bool isExporting, bool isDone, int progress, List items, TextEditingController outputPathController, String? errorMessage)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ExportBatchState() when $default != null: +return $default(_that.format,_that.isExporting,_that.isDone,_that.progress,_that.items,_that.outputPathController,_that.errorMessage);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( ExportFormat format, bool isExporting, bool isDone, int progress, List items, TextEditingController outputPathController, String? errorMessage) $default,) {final _that = this; +switch (_that) { +case _ExportBatchState(): +return $default(_that.format,_that.isExporting,_that.isDone,_that.progress,_that.items,_that.outputPathController,_that.errorMessage);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( ExportFormat format, bool isExporting, bool isDone, int progress, List items, TextEditingController outputPathController, String? errorMessage)? $default,) {final _that = this; +switch (_that) { +case _ExportBatchState() when $default != null: +return $default(_that.format,_that.isExporting,_that.isDone,_that.progress,_that.items,_that.outputPathController,_that.errorMessage);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ExportBatchState implements ExportBatchState { + const _ExportBatchState({required this.format, required this.isExporting, required this.isDone, required this.progress, required final List items, required this.outputPathController, this.errorMessage}): _items = items; + + +@override final ExportFormat format; +@override final bool isExporting; +@override final bool isDone; +@override final int progress; + final List _items; +@override List get items { + if (_items is EqualUnmodifiableListView) return _items; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_items); +} + +@override final TextEditingController outputPathController; +@override final String? errorMessage; + +/// Create a copy of ExportBatchState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ExportBatchStateCopyWith<_ExportBatchState> get copyWith => __$ExportBatchStateCopyWithImpl<_ExportBatchState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ExportBatchState&&(identical(other.format, format) || other.format == format)&&(identical(other.isExporting, isExporting) || other.isExporting == isExporting)&&(identical(other.isDone, isDone) || other.isDone == isDone)&&(identical(other.progress, progress) || other.progress == progress)&&const DeepCollectionEquality().equals(other._items, _items)&&(identical(other.outputPathController, outputPathController) || other.outputPathController == outputPathController)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)); +} + + +@override +int get hashCode => Object.hash(runtimeType,format,isExporting,isDone,progress,const DeepCollectionEquality().hash(_items),outputPathController,errorMessage); + +@override +String toString() { + return 'ExportBatchState(format: $format, isExporting: $isExporting, isDone: $isDone, progress: $progress, items: $items, outputPathController: $outputPathController, errorMessage: $errorMessage)'; +} + + +} + +/// @nodoc +abstract mixin class _$ExportBatchStateCopyWith<$Res> implements $ExportBatchStateCopyWith<$Res> { + factory _$ExportBatchStateCopyWith(_ExportBatchState value, $Res Function(_ExportBatchState) _then) = __$ExportBatchStateCopyWithImpl; +@override @useResult +$Res call({ + ExportFormat format, bool isExporting, bool isDone, int progress, List items, TextEditingController outputPathController, String? errorMessage +}); + + + + +} +/// @nodoc +class __$ExportBatchStateCopyWithImpl<$Res> + implements _$ExportBatchStateCopyWith<$Res> { + __$ExportBatchStateCopyWithImpl(this._self, this._then); + + final _ExportBatchState _self; + final $Res Function(_ExportBatchState) _then; + +/// Create a copy of ExportBatchState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? format = null,Object? isExporting = null,Object? isDone = null,Object? progress = null,Object? items = null,Object? outputPathController = null,Object? errorMessage = freezed,}) { + return _then(_ExportBatchState( +format: null == format ? _self.format : format // ignore: cast_nullable_to_non_nullable +as ExportFormat,isExporting: null == isExporting ? _self.isExporting : isExporting // ignore: cast_nullable_to_non_nullable +as bool,isDone: null == isDone ? _self.isDone : isDone // ignore: cast_nullable_to_non_nullable +as bool,progress: null == progress ? _self.progress : progress // ignore: cast_nullable_to_non_nullable +as int,items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable +as List,outputPathController: null == outputPathController ? _self.outputPathController : outputPathController // ignore: cast_nullable_to_non_nullable +as TextEditingController,errorMessage: freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + +// dart format on diff --git a/lib/feature/export/ui/provider/export_batch_provider.g.dart b/lib/feature/export/ui/provider/export_batch_provider.g.dart new file mode 100644 index 0000000..8d3300e --- /dev/null +++ b/lib/feature/export/ui/provider/export_batch_provider.g.dart @@ -0,0 +1,107 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'export_batch_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(ExportBatch) +final exportBatchProvider = ExportBatchFamily._(); + +final class ExportBatchProvider + extends $NotifierProvider { + ExportBatchProvider._({ + required ExportBatchFamily super.from, + required List super.argument, + }) : super( + retry: null, + name: r'exportBatchProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$exportBatchHash(); + + @override + String toString() { + return r'exportBatchProvider' + '' + '($argument)'; + } + + @$internal + @override + ExportBatch create() => ExportBatch(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(ExportBatchState value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } + + @override + bool operator ==(Object other) { + return other is ExportBatchProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$exportBatchHash() => r'2db90e6ebc7e97ab5eb9f60e13c388f69815b9a3'; + +final class ExportBatchFamily extends $Family + with + $ClassFamilyOverride< + ExportBatch, + ExportBatchState, + ExportBatchState, + ExportBatchState, + List + > { + ExportBatchFamily._() + : super( + retry: null, + name: r'exportBatchProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + ExportBatchProvider call(List bookIds) => + ExportBatchProvider._(argument: bookIds, from: this); + + @override + String toString() => r'exportBatchProvider'; +} + +abstract class _$ExportBatch extends $Notifier { + late final _$args = ref.$arg as List; + List get bookIds => _$args; + + ExportBatchState build(List bookIds); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + ExportBatchState, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} diff --git a/lib/feature/export/ui/provider/export_single_provider.dart b/lib/feature/export/ui/provider/export_single_provider.dart new file mode 100644 index 0000000..dd4fb0b --- /dev/null +++ b/lib/feature/export/ui/provider/export_single_provider.dart @@ -0,0 +1,82 @@ +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/widgets.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:tele_book/core/db/app_database.dart'; +import 'package:tele_book/feature/book/ui/provider/book_provider.dart'; +import 'package:tele_book/feature/export/enum/export_format.dart'; +import 'package:tele_book/feature/export/service/export_service.dart'; + +part 'export_single_provider.freezed.dart'; + +part 'export_single_provider.g.dart'; + +@freezed +abstract class ExportSingleState with _$ExportSingleState { + const factory ExportSingleState({ + required BookTableData book, + required ExportFormat format, + required TextEditingController fileNameCrl, + required bool isExporting, + required bool isDone, + required TextEditingController outputPathCrl, + String? errorMsg + }) = _ExportSingleState; +} + +@riverpod +class ExportSingle extends _$ExportSingle { + final ExportService _exportService = ExportService(); + + @override + ExportSingleState build(int bookId) { + final book = ref + .watch(bookListProvider) + .value + ?.bookVos + .where((e) => e.book.id == bookId) + .first + .book; + if (book == null) { + throw Exception('Book not found'); + } + return ExportSingleState( + book: book, + format: ExportFormat.folder, + fileNameCrl: TextEditingController(text: book.name), + isExporting: false, + isDone: false, + outputPathCrl: TextEditingController(), + ); + } + + void setFormat(ExportFormat fmt) { + state = state.copyWith(format: fmt); + } + + + Future pickOutputDir() async { + final result = await FilePicker.platform.getDirectoryPath(); + if (result != null) { + state.outputPathCrl.text = result; + } + } + + Future doExport() async { + final path = state.outputPathCrl.text; + final name = state.fileNameCrl.text.trim(); + + state = state.copyWith(isExporting: true); + try { + await _exportService.exportSingle( + book: state.book, + outputDirPath: path, + fileName: name, + format: state.format, + ); + state = state.copyWith(isDone: true, isExporting: false); + } catch (e) { + state = state.copyWith(isExporting: false, errorMsg: '导出失败:$e'); + } + } +} diff --git a/lib/feature/export/ui/provider/export_single_provider.freezed.dart b/lib/feature/export/ui/provider/export_single_provider.freezed.dart new file mode 100644 index 0000000..ad253de --- /dev/null +++ b/lib/feature/export/ui/provider/export_single_provider.freezed.dart @@ -0,0 +1,289 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'export_single_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$ExportSingleState { + + BookTableData get book; ExportFormat get format; TextEditingController get fileNameCrl; bool get isExporting; bool get isDone; TextEditingController get outputPathCrl; String? get errorMsg; +/// Create a copy of ExportSingleState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ExportSingleStateCopyWith get copyWith => _$ExportSingleStateCopyWithImpl(this as ExportSingleState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ExportSingleState&&(identical(other.book, book) || other.book == book)&&(identical(other.format, format) || other.format == format)&&(identical(other.fileNameCrl, fileNameCrl) || other.fileNameCrl == fileNameCrl)&&(identical(other.isExporting, isExporting) || other.isExporting == isExporting)&&(identical(other.isDone, isDone) || other.isDone == isDone)&&(identical(other.outputPathCrl, outputPathCrl) || other.outputPathCrl == outputPathCrl)&&(identical(other.errorMsg, errorMsg) || other.errorMsg == errorMsg)); +} + + +@override +int get hashCode => Object.hash(runtimeType,book,format,fileNameCrl,isExporting,isDone,outputPathCrl,errorMsg); + +@override +String toString() { + return 'ExportSingleState(book: $book, format: $format, fileNameCrl: $fileNameCrl, isExporting: $isExporting, isDone: $isDone, outputPathCrl: $outputPathCrl, errorMsg: $errorMsg)'; +} + + +} + +/// @nodoc +abstract mixin class $ExportSingleStateCopyWith<$Res> { + factory $ExportSingleStateCopyWith(ExportSingleState value, $Res Function(ExportSingleState) _then) = _$ExportSingleStateCopyWithImpl; +@useResult +$Res call({ + BookTableData book, ExportFormat format, TextEditingController fileNameCrl, bool isExporting, bool isDone, TextEditingController outputPathCrl, String? errorMsg +}); + + + + +} +/// @nodoc +class _$ExportSingleStateCopyWithImpl<$Res> + implements $ExportSingleStateCopyWith<$Res> { + _$ExportSingleStateCopyWithImpl(this._self, this._then); + + final ExportSingleState _self; + final $Res Function(ExportSingleState) _then; + +/// Create a copy of ExportSingleState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? book = null,Object? format = null,Object? fileNameCrl = null,Object? isExporting = null,Object? isDone = null,Object? outputPathCrl = null,Object? errorMsg = freezed,}) { + return _then(_self.copyWith( +book: null == book ? _self.book : book // ignore: cast_nullable_to_non_nullable +as BookTableData,format: null == format ? _self.format : format // ignore: cast_nullable_to_non_nullable +as ExportFormat,fileNameCrl: null == fileNameCrl ? _self.fileNameCrl : fileNameCrl // ignore: cast_nullable_to_non_nullable +as TextEditingController,isExporting: null == isExporting ? _self.isExporting : isExporting // ignore: cast_nullable_to_non_nullable +as bool,isDone: null == isDone ? _self.isDone : isDone // ignore: cast_nullable_to_non_nullable +as bool,outputPathCrl: null == outputPathCrl ? _self.outputPathCrl : outputPathCrl // ignore: cast_nullable_to_non_nullable +as TextEditingController,errorMsg: freezed == errorMsg ? _self.errorMsg : errorMsg // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ExportSingleState]. +extension ExportSingleStatePatterns on ExportSingleState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ExportSingleState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ExportSingleState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ExportSingleState value) $default,){ +final _that = this; +switch (_that) { +case _ExportSingleState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ExportSingleState value)? $default,){ +final _that = this; +switch (_that) { +case _ExportSingleState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( BookTableData book, ExportFormat format, TextEditingController fileNameCrl, bool isExporting, bool isDone, TextEditingController outputPathCrl, String? errorMsg)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ExportSingleState() when $default != null: +return $default(_that.book,_that.format,_that.fileNameCrl,_that.isExporting,_that.isDone,_that.outputPathCrl,_that.errorMsg);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( BookTableData book, ExportFormat format, TextEditingController fileNameCrl, bool isExporting, bool isDone, TextEditingController outputPathCrl, String? errorMsg) $default,) {final _that = this; +switch (_that) { +case _ExportSingleState(): +return $default(_that.book,_that.format,_that.fileNameCrl,_that.isExporting,_that.isDone,_that.outputPathCrl,_that.errorMsg);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( BookTableData book, ExportFormat format, TextEditingController fileNameCrl, bool isExporting, bool isDone, TextEditingController outputPathCrl, String? errorMsg)? $default,) {final _that = this; +switch (_that) { +case _ExportSingleState() when $default != null: +return $default(_that.book,_that.format,_that.fileNameCrl,_that.isExporting,_that.isDone,_that.outputPathCrl,_that.errorMsg);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ExportSingleState implements ExportSingleState { + const _ExportSingleState({required this.book, required this.format, required this.fileNameCrl, required this.isExporting, required this.isDone, required this.outputPathCrl, this.errorMsg}); + + +@override final BookTableData book; +@override final ExportFormat format; +@override final TextEditingController fileNameCrl; +@override final bool isExporting; +@override final bool isDone; +@override final TextEditingController outputPathCrl; +@override final String? errorMsg; + +/// Create a copy of ExportSingleState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ExportSingleStateCopyWith<_ExportSingleState> get copyWith => __$ExportSingleStateCopyWithImpl<_ExportSingleState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ExportSingleState&&(identical(other.book, book) || other.book == book)&&(identical(other.format, format) || other.format == format)&&(identical(other.fileNameCrl, fileNameCrl) || other.fileNameCrl == fileNameCrl)&&(identical(other.isExporting, isExporting) || other.isExporting == isExporting)&&(identical(other.isDone, isDone) || other.isDone == isDone)&&(identical(other.outputPathCrl, outputPathCrl) || other.outputPathCrl == outputPathCrl)&&(identical(other.errorMsg, errorMsg) || other.errorMsg == errorMsg)); +} + + +@override +int get hashCode => Object.hash(runtimeType,book,format,fileNameCrl,isExporting,isDone,outputPathCrl,errorMsg); + +@override +String toString() { + return 'ExportSingleState(book: $book, format: $format, fileNameCrl: $fileNameCrl, isExporting: $isExporting, isDone: $isDone, outputPathCrl: $outputPathCrl, errorMsg: $errorMsg)'; +} + + +} + +/// @nodoc +abstract mixin class _$ExportSingleStateCopyWith<$Res> implements $ExportSingleStateCopyWith<$Res> { + factory _$ExportSingleStateCopyWith(_ExportSingleState value, $Res Function(_ExportSingleState) _then) = __$ExportSingleStateCopyWithImpl; +@override @useResult +$Res call({ + BookTableData book, ExportFormat format, TextEditingController fileNameCrl, bool isExporting, bool isDone, TextEditingController outputPathCrl, String? errorMsg +}); + + + + +} +/// @nodoc +class __$ExportSingleStateCopyWithImpl<$Res> + implements _$ExportSingleStateCopyWith<$Res> { + __$ExportSingleStateCopyWithImpl(this._self, this._then); + + final _ExportSingleState _self; + final $Res Function(_ExportSingleState) _then; + +/// Create a copy of ExportSingleState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? book = null,Object? format = null,Object? fileNameCrl = null,Object? isExporting = null,Object? isDone = null,Object? outputPathCrl = null,Object? errorMsg = freezed,}) { + return _then(_ExportSingleState( +book: null == book ? _self.book : book // ignore: cast_nullable_to_non_nullable +as BookTableData,format: null == format ? _self.format : format // ignore: cast_nullable_to_non_nullable +as ExportFormat,fileNameCrl: null == fileNameCrl ? _self.fileNameCrl : fileNameCrl // ignore: cast_nullable_to_non_nullable +as TextEditingController,isExporting: null == isExporting ? _self.isExporting : isExporting // ignore: cast_nullable_to_non_nullable +as bool,isDone: null == isDone ? _self.isDone : isDone // ignore: cast_nullable_to_non_nullable +as bool,outputPathCrl: null == outputPathCrl ? _self.outputPathCrl : outputPathCrl // ignore: cast_nullable_to_non_nullable +as TextEditingController,errorMsg: freezed == errorMsg ? _self.errorMsg : errorMsg // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + +// dart format on diff --git a/lib/feature/export/ui/provider/export_single_provider.g.dart b/lib/feature/export/ui/provider/export_single_provider.g.dart new file mode 100644 index 0000000..a6c438c --- /dev/null +++ b/lib/feature/export/ui/provider/export_single_provider.g.dart @@ -0,0 +1,107 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'export_single_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(ExportSingle) +final exportSingleProvider = ExportSingleFamily._(); + +final class ExportSingleProvider + extends $NotifierProvider { + ExportSingleProvider._({ + required ExportSingleFamily super.from, + required int super.argument, + }) : super( + retry: null, + name: r'exportSingleProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$exportSingleHash(); + + @override + String toString() { + return r'exportSingleProvider' + '' + '($argument)'; + } + + @$internal + @override + ExportSingle create() => ExportSingle(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(ExportSingleState value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } + + @override + bool operator ==(Object other) { + return other is ExportSingleProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$exportSingleHash() => r'e33ec12c57f13a8edef79fefd40746dd6e2aef00'; + +final class ExportSingleFamily extends $Family + with + $ClassFamilyOverride< + ExportSingle, + ExportSingleState, + ExportSingleState, + ExportSingleState, + int + > { + ExportSingleFamily._() + : super( + retry: null, + name: r'exportSingleProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + ExportSingleProvider call(int bookId) => + ExportSingleProvider._(argument: bookId, from: this); + + @override + String toString() => r'exportSingleProvider'; +} + +abstract class _$ExportSingle extends $Notifier { + late final _$args = ref.$arg as int; + int get bookId => _$args; + + ExportSingleState build(int bookId); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + ExportSingleState, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} diff --git a/lib/feature/export/ui/view/export_batch_form_view.dart b/lib/feature/export/ui/view/export_batch_form_view.dart index e030aca..f164089 100644 --- a/lib/feature/export/ui/view/export_batch_form_view.dart +++ b/lib/feature/export/ui/view/export_batch_form_view.dart @@ -1,199 +1,260 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:forui/forui.dart'; +import 'package:go_router/go_router.dart'; import 'package:tele_book/common/widget/local_image_widget.dart'; import 'package:tele_book/core/db/app_database.dart'; import 'package:tele_book/feature/export/enum/export_format.dart'; -import 'package:tele_book/feature/export/ui/viewmodel/export_batch_viewmodel.dart'; +import 'package:tele_book/feature/export/model/export_item.dart'; +import 'package:tele_book/feature/export/ui/provider/export_batch_provider.dart'; -class ExportBatchFormView extends StatelessWidget { +class ExportBatchFormView extends ConsumerStatefulWidget { final List books; + const ExportBatchFormView({super.key, required this.books}); @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (_) => ExportBatchViewmodel(books: books), - child: const _ExportBatchFormContent(), - ); - } + ConsumerState createState() => + _ExportBatchFormViewState(); } -class _ExportBatchFormContent extends StatelessWidget { - const _ExportBatchFormContent(); +class _ExportBatchFormViewState extends ConsumerState { + final _formKey = GlobalKey(); + late final List _bookIds; + + @override + void initState() { + super.initState(); + _bookIds = widget.books.map((b) => b.id).toList(); + } @override Widget build(BuildContext context) { - final vm = context.watch(); + final state = ref.watch( + exportBatchProvider(_bookIds), + ); + final notifier = ref.read( + exportBatchProvider(_bookIds).notifier, + ); - if (vm.isDone) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('全部导出成功!'), - backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest, - ), + ref.listen(exportBatchProvider(_bookIds), ( + prev, + next, + ) { + if (prev?.isDone == false && next.isDone) { + showFToast( + context: context, + icon: const Icon(FLucideIcons.check), + title: const Text('导出成功'), + description: Text('全部 ${next.items.length} 本书已导出'), + swipeToDismiss: const [.right], + duration: const Duration(seconds: 3), ); Navigator.of(context).pop(); - }); + } + }); - } - - return Scaffold( - appBar: AppBar(title: Text('批量导出(${vm.items.length} 本)')), - body: Column( - children: [ - // 顶部设置区 - Padding( - padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // 导出格式 - LayoutBuilder( - builder: (context, constraints) => DropdownMenu( - width: constraints.maxWidth, - initialSelection: vm.format, - label: const Text('导出格式'), - leadingIcon: const Icon(Icons.file_present), - onSelected: (v) { - if (v != null) vm.setFormat(v); - }, - dropdownMenuEntries: ExportFormat.values - .map((f) => DropdownMenuEntry(value: f, label: f.label)) - .toList(), - ), - ), - const SizedBox(height: 12), - - // 导出路径 - TextField( - readOnly: true, - controller: TextEditingController(text: vm.outputPath ?? ''), - decoration: InputDecoration( - labelText: '导出路径', - hintText: '请选择导出目录', - prefixIcon: const Icon(Icons.folder_open), - border: const OutlineInputBorder(), - suffixIcon: IconButton( - icon: const Icon(Icons.drive_folder_upload), - onPressed: vm.isExporting ? null : vm.pickOutputDir, + return FScaffold( + header: FHeader.nested( + title: Text('批量导出(${state.items.length} 本)'), + prefixes: [FHeaderAction.back(onPress: () => context.pop())], + ), + child: Form( + key: _formKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: Column( + children: [ + // 顶部设置区 + Padding( + padding: .all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // 导出格式 + FSelect.rich( + control: FSelectControl.managed( + onChange: (value) { + if (value != null) notifier.setFormat(value); + }, + initial: state.format, ), + label: Text("导出格式"), + hint: "请选择导出格式", + format: (s) => s.label, + children: [ + for (final format in ExportFormat.values) + .item(title: Text(format.label), value: format), + ], + validator: (v) => (v == null) ? '请选择导出格式' : null, ), - ), + const SizedBox(height: 12), - if (vm.errorMessage != null) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Text( - vm.errorMessage!, - style: TextStyle( - color: Theme.of(context).colorScheme.error, - ), + // 导出路径 + FTextFormField( + readOnly: true, + control: FTextFieldControl.managed( + controller: state.outputPathController, ), + label: Text('导出路径'), + hint: '请选择导出目录', + suffixBuilder: (context, style, variants) { + return FButton.icon( + style: style.obscureButtonStyle, + onPress: state.isExporting + ? null + : () => notifier.pickOutputDir(), + child: Icon(FLucideIcons.folderOpen), + ); + }, + validator: (v) => (v?.isEmpty ?? true) ? '请选择路径' : null, + onTap: state.isExporting + ? null + : () => notifier.pickOutputDir(), ), - - // 进度条 - if (vm.isExporting) ...[ - const SizedBox(height: 12), - LinearProgressIndicator( - value: vm.items.isNotEmpty - ? vm.progress / vm.items.length - : null, - ), - const SizedBox(height: 4), - Text( - '正在导出 ${vm.progress} / ${vm.items.length}', - style: Theme.of(context).textTheme.bodySmall, - ), + // 进度条 + if (state.isExporting) ...[ + const SizedBox(height: 12), + FDeterminateProgress( + value: state.items.isNotEmpty + ? state.progress / state.items.length + : 0, + ), + const SizedBox(height: 4), + Text( + '正在导出 ${state.progress} / ${state.items.length}', + style: Theme.of(context).textTheme.bodySmall, + ), + ], ], - ], + ), ), - ), - const Divider(height: 1), + const Divider(height: 1), - // 导出项列表 - Expanded( - child: ListView.separated( - padding: const EdgeInsets.all(16), - separatorBuilder: (_, __) => const SizedBox(height: 16), - itemCount: vm.items.length, - itemBuilder: (context, index) { - final item = vm.items[index]; - return Row( - children: [ - LocalImageWidget(imagePath: item.coverPath), - Expanded( - child: ListTile( - title: Text( - item.book.name, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - subtitle: Text('${item.book.localSubPaths.length} 页'), - ), + // 导出项列表 + Expanded( + child: FItemGroup.builder( + count: state.items.length, + itemBuilder: (context, index) { + final item = state.items[index]; + return FItem( + prefix: LocalImageWidget(imagePath: item.coverPath), + title: Text( + item.book.name, + maxLines: 2, + overflow: TextOverflow.ellipsis, ), - IconButton( - onPressed: () { - showGeneralDialog( - context: context, - pageBuilder: (_, _, _) { - return AlertDialog( - title: Text('编辑导出文件名'), - content: TextField( - controller: item.nameController, - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: '导出文件名', - ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: Text('取消'), - ), - ElevatedButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text('确定'), - ), - ], - ); - }, - ); - }, - icon: const Icon(Icons.edit), + subtitle: Text('${item.book.localSubPaths.length} 页'), + suffix: FButton.icon( + variant: .ghost, + onPress: () => _editFileName(context, item), + child: const Icon(FLucideIcons.pencil), ), - ], - ); - }, + ); + }, + ), ), - ), - // 底部导出按钮 - SafeArea( - child: Padding( - padding: const EdgeInsets.all(16), - child: FilledButton.icon( - onPressed: vm.isExporting ? null : () => vm.export(), - icon: vm.isExporting - ? const SizedBox( - width: 18, - height: 18, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Colors.white, - ), - ) - : const Icon(Icons.upload), - label: Text(vm.isExporting ? '导出中...' : '开始批量导出'), + // 底部导出按钮 + SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: FButton( + onPress: state.isExporting + ? null + : () { + if (_formKey.currentState!.validate()) { + notifier.doExport(); + } + }, + prefix: state.isExporting + ? const SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : const Icon(Icons.upload), + child: Text(state.isExporting ? '导出中...' : '开始批量导出'), + ), ), ), + ], + ), + ), + ); + } + + void _editFileName(BuildContext context, ExportItem item) { + showFDialog( + context: context, + builder: (dialogContext, style, animate) => FDialog.adaptive( + style: style, + animation: animate, + horizontalBuilder: (context, dStyle) => Padding( + padding: .all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('编辑导出文件名', style: dStyle.titleTextStyle), + const SizedBox(height: 12), + FTextFormField( + control: FTextFieldControl.managed( + controller: item.nameController, + ), + label: Text('导出文件名'), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FButton( + variant: .outline, + onPress: () => Navigator.of(dialogContext).pop(), + child: Text('取消'), + ), + const SizedBox(width: 8), + FButton( + onPress: () => Navigator.of(dialogContext).pop(), + child: Text('确定'), + ), + ], + ), + ], + ), + ), + verticalBuilder: (context, dStyle) => Padding( + padding: .all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('编辑导出文件名', style: dStyle.titleTextStyle), + const SizedBox(height: 12), + FTextFormField( + control: FTextFieldControl.managed( + controller: item.nameController, + ), + label: Text('导出文件名'), + ), + const SizedBox(height: 16), + FButton( + onPress: () => Navigator.of(dialogContext).pop(), + child: Text('确定'), + ), + const SizedBox(height: 8), + FButton( + variant: .outline, + onPress: () => Navigator.of(dialogContext).pop(), + child: Text('取消'), + ), + ], ), - ], + ), ), ); } diff --git a/lib/feature/export/ui/view/export_single_form_view.dart b/lib/feature/export/ui/view/export_single_form_view.dart index c3dc316..9817c33 100644 --- a/lib/feature/export/ui/view/export_single_form_view.dart +++ b/lib/feature/export/ui/view/export_single_form_view.dart @@ -1,138 +1,146 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:forui/forui.dart'; +import 'package:go_router/go_router.dart'; +import 'package:tele_book/common/config/global_config.dart'; +import 'package:tele_book/common/widget/local_image_widget.dart'; import 'package:tele_book/core/db/app_database.dart'; import 'package:tele_book/feature/export/enum/export_format.dart'; -import 'package:tele_book/feature/export/ui/viewmodel/export_single_viewmodel.dart'; +import 'package:tele_book/feature/export/ui/provider/export_single_provider.dart'; -class ExportSingleFormView extends StatelessWidget { +class ExportSingleFormView extends ConsumerStatefulWidget { final BookTableData book; const ExportSingleFormView({super.key, required this.book}); @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (_) => ExportSingleViewmodel(book: book), - child: const _ExportSingleFormContent(), - ); - } + ConsumerState createState() => + _ExportSingleFormViewState(); } -class _ExportSingleFormContent extends StatelessWidget { - const _ExportSingleFormContent(); +class _ExportSingleFormViewState extends ConsumerState { + final _formKey = GlobalKey(); @override Widget build(BuildContext context) { - final vm = context.watch(); + final bookId = widget.book.id; + final state = ref.watch(exportSingleProvider(bookId)); + final notifier = ref.read(exportSingleProvider(bookId).notifier); - // 成功后弹提示并返回 - if (vm.isDone) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('导出成功!'), - backgroundColor: Theme.of( - context, - ).colorScheme.surfaceContainerHighest, - ), + ref.listen(exportSingleProvider(bookId), (prev, next) { + if (prev?.isDone == false && next.isDone) { + showFToast( + context: context, + icon: const Icon(FLucideIcons.check), + title: const Text('导出成功'), + description: Text('${state.book.name} 已导出'), + swipeToDismiss: const [.right], + duration: const Duration(seconds: 3), ); Navigator.of(context).pop(); - }); - } + } + }); - return Scaffold( - appBar: AppBar(title: const Text('导出书籍')), - body: Padding( + return FScaffold( + header: FHeader.nested( + title: const Text('导出书籍'), + prefixes: [FHeaderAction.back(onPress: () => context.pop())], + ), + child: Padding( padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // 书籍名提示 - ListTile( - contentPadding: EdgeInsets.zero, - leading: const Icon(Icons.book), - title: Text(vm.book.name), - subtitle: Text('共 ${vm.book.localSubPaths.length} 页'), - ), - const Divider(), - const SizedBox(height: 8), - - // 导出格式 - LayoutBuilder( - builder: (context, constraints) => DropdownMenu( - width: constraints.maxWidth, - initialSelection: vm.format, - label: const Text('导出格式'), - leadingIcon: const Icon(Icons.file_present), - onSelected: (v) { - if (v != null) vm.setFormat(v); - }, - dropdownMenuEntries: ExportFormat.values - .map((f) => DropdownMenuEntry(value: f, label: f.label)) - .toList(), + child: Form( + key: _formKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + FLabel( + layout: .horizontalTrailing, + label: Text(state.book.name), + description: Text('共 ${state.book.localSubPaths.length} 页'), + child: LocalImageWidget( + imagePath: GlobalConfig.resolveBookPath( + state.book.coverSubPath!, + ), + ), ), - ), - const SizedBox(height: 16), + const Divider(), + const SizedBox(height: 8), - // 导出路径 - TextField( - readOnly: true, - controller: TextEditingController(text: vm.outputPath ?? ''), - decoration: InputDecoration( - labelText: '导出路径', - hintText: '请选择导出目录', - prefixIcon: const Icon(Icons.folder_open), - border: const OutlineInputBorder(), - suffixIcon: IconButton( - icon: const Icon(Icons.drive_folder_upload), - onPressed: vm.isExporting ? null : vm.pickOutputDir, + // 导出格式 + FSelect.rich( + control: FSelectControl.managed( + onChange: (value) { + if (value != null) notifier.setFormat(value); + }, ), + label: Text("导出格式"), + hint: "请选择导出格式", + format: (s) => s.label, + children: [ + for (final format in ExportFormat.values) + .item(title: Text(format.label), value: format), + ], ), - ), - const SizedBox(height: 16), + const SizedBox(height: 16), - // 导出文件名 - TextField( - controller: vm.fileNameController, - enabled: !vm.isExporting, - decoration: const InputDecoration( - labelText: '导出文件名', - prefixIcon: Icon(Icons.drive_file_rename_outline), - border: OutlineInputBorder(), + // 导出路径 + FTextFormField( + readOnly: true, + control: FTextFieldControl.managed( + controller: state.outputPathCrl, + ), + validator: (v) => (v == null || v.isEmpty) ? '请选择导出目录' : null, + label: Text('导出路径'), + hint: '请选择导出目录', + suffixBuilder: (context, style, variants) { + return FButton.icon( + style: style.obscureButtonStyle, + onPress: () => notifier.pickOutputDir(), + child: Icon(FLucideIcons.fileOutput), + ); + }, + onTap: () => notifier.pickOutputDir(), ), - ), - const SizedBox(height: 8), + const SizedBox(height: 16), - // 错误提示 - if (vm.errorMessage != null) - Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Text( - vm.errorMessage!, - style: TextStyle(color: Theme.of(context).colorScheme.error), + // 导出文件名 + FTextFormField( + control: FTextFieldControl.managed( + controller: state.fileNameCrl, ), + validator: (v) => + (v == null || v.trim().isEmpty) ? '请输入文件名' : null, + enabled: !state.isExporting, + label: Text('导出文件名'), + hint: "请输入导出文件名", ), + const SizedBox(height: 8), - const Spacer(), + const Spacer(), - // 导出按钮 - FilledButton.icon( - onPressed: vm.isExporting ? null : () => vm.export(), - icon: vm.isExporting - ? const SizedBox( - width: 18, - height: 18, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Colors.white, - ), - ) - : const Icon(Icons.upload), - label: Text(vm.isExporting ? '导出中...' : '开始导出'), - ), - const SizedBox(height: 16), - ], + // 导出按钮 + FButton( + onPress: () { + if (_formKey.currentState!.validate()) { + notifier.doExport(); + } + }, + prefix: state.isExporting + ? const SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : const Icon(Icons.upload), + child: Text(state.isExporting ? '导出中...' : '开始导出'), + ), + const SizedBox(height: 16), + ], + ), ), ), ); diff --git a/lib/feature/export/ui/viewmodel/export_batch_viewmodel.dart b/lib/feature/export/ui/viewmodel/export_batch_viewmodel.dart index 1294679..78452b2 100644 --- a/lib/feature/export/ui/viewmodel/export_batch_viewmodel.dart +++ b/lib/feature/export/ui/viewmodel/export_batch_viewmodel.dart @@ -23,7 +23,9 @@ class ExportBatchViewmodel extends ChangeNotifier { .map( (b) => ExportItem( book: b, - coverPath: "${GlobalConfig.booksDir.path}/${b.localSubPaths.first}", + coverPath: b.coverSubPath != null + ? '${GlobalConfig.booksDir.path}/${b.coverSubPath}' + : '${GlobalConfig.booksDir.path}/${b.localSubPaths.first}', ), ) .toList(); diff --git a/lib/feature/main/view/main_view.dart b/lib/feature/main/view/main_view.dart index c461193..c329a2f 100644 --- a/lib/feature/main/view/main_view.dart +++ b/lib/feature/main/view/main_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:forui/forui.dart'; import 'package:tele_book/feature/book/ui/view/book_list_view.dart'; import 'package:tele_book/feature/collection/ui/view/collection_view.dart'; import 'package:tele_book/feature/download/ui/view/download_list_view.dart'; @@ -12,20 +13,26 @@ class MainView extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(mainProvider); final notifier = ref.read(mainProvider.notifier); - return Scaffold( - body: IndexedStack( + return FScaffold( + footer: FBottomNavigationBar( index: state.currentIndex, - children: [BookListView(), DownloadListView(), CollectionView()], - ), - bottomNavigationBar: BottomNavigationBar( - currentIndex: state.currentIndex, - onTap: (index) => notifier.updateCurrentIndex(index), - items: const [ - BottomNavigationBarItem(icon: Icon(Icons.book), label: "书籍"), - BottomNavigationBarItem(icon: Icon(Icons.download), label: "下载"), - BottomNavigationBarItem(icon: Icon(Icons.collections), label: "收藏夹"), + onChange: (index) => notifier.updateCurrentIndex(index), + children: [ + FBottomNavigationBarItem(icon: Icon(Icons.book), label: Text("书籍")), + FBottomNavigationBarItem( + icon: Icon(Icons.download), + label: Text("下载"), + ), + FBottomNavigationBarItem( + icon: Icon(Icons.collections), + label: Text("收藏夹"), + ), ], ), + child: IndexedStack( + index: state.currentIndex, + children: [BookListView(), DownloadListView(), CollectionView()], + ), ); } } diff --git a/lib/feature/parse/ui/provider/parse_archive_provider.dart b/lib/feature/parse/ui/provider/parse_archive_provider.dart index b2a14cb..95b7919 100644 --- a/lib/feature/parse/ui/provider/parse_archive_provider.dart +++ b/lib/feature/parse/ui/provider/parse_archive_provider.dart @@ -33,9 +33,19 @@ abstract class ParseArchiveSaveBookParam with _$ParseArchiveSaveBookParam { abstract class ParseArchiveSaveBookProgress with _$ParseArchiveSaveBookProgress { const factory ParseArchiveSaveBookProgress({ + @Default(SaveStep.generateCover) SaveStep step, @Default(0) int current, @Default(0) int total, }) = _ParseArchiveSaveBookProgress; + + const ParseArchiveSaveBookProgress._(); + + String get stepText => switch (step) { + SaveStep.generateCover => '生成封面图...', + SaveStep.generatePreview => '生成预览图... ($current/$total)', + SaveStep.saveOriginal => '保存原图... ($current/$total)', + SaveStep.saveDatabase => '保存中...', + }; } final parseArchiveProgressProvider = @@ -70,6 +80,7 @@ class ParseArchive extends _$ParseArchive { Future _parseArchive(String archivePath) async { final hasPermission = await _requestStoragePermission(); + if (!ref.mounted) return; if (!hasPermission) { state = AsyncError('需要存储权限才能解析压缩包', StackTrace.current); return; @@ -90,6 +101,7 @@ class ParseArchive extends _$ParseArchive { }, ); + if (!ref.mounted) return; result.fold( onSuccess: (data) { state = AsyncData( @@ -115,13 +127,21 @@ class ParseArchiveSaveBook extends _$ParseArchiveSaveBook { state = const AsyncLoading(); ref.read(parseArchiveSaveBookProgressProvider.notifier).state = - ParseArchiveSaveBookProgress(current: 0, total: param.tempPaths.length); + ParseArchiveSaveBookProgress( + step: SaveStep.generateCover, + current: 0, + total: param.tempPaths.length, + ); final result = await _bookRepository.saveAsBook( SaveAsBookDto(title: param.archiveName, paths: param.tempPaths), - onProgress: (current, total) { + onStepProgress: (step, current, total) { ref.read(parseArchiveSaveBookProgressProvider.notifier).state = - ParseArchiveSaveBookProgress(current: current, total: total); + ParseArchiveSaveBookProgress( + step: step, + current: current, + total: total, + ); }, ); diff --git a/lib/feature/parse/ui/provider/parse_archive_provider.freezed.dart b/lib/feature/parse/ui/provider/parse_archive_provider.freezed.dart index 732eed2..cdfaa13 100644 --- a/lib/feature/parse/ui/provider/parse_archive_provider.freezed.dart +++ b/lib/feature/parse/ui/provider/parse_archive_provider.freezed.dart @@ -546,7 +546,7 @@ as List, /// @nodoc mixin _$ParseArchiveSaveBookProgress { - int get current; int get total; + SaveStep get step; int get current; int get total; /// Create a copy of ParseArchiveSaveBookProgress /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -557,16 +557,16 @@ $ParseArchiveSaveBookProgressCopyWith get copyWith @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseArchiveSaveBookProgress&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseArchiveSaveBookProgress&&(identical(other.step, step) || other.step == step)&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); } @override -int get hashCode => Object.hash(runtimeType,current,total); +int get hashCode => Object.hash(runtimeType,step,current,total); @override String toString() { - return 'ParseArchiveSaveBookProgress(current: $current, total: $total)'; + return 'ParseArchiveSaveBookProgress(step: $step, current: $current, total: $total)'; } @@ -577,7 +577,7 @@ abstract mixin class $ParseArchiveSaveBookProgressCopyWith<$Res> { factory $ParseArchiveSaveBookProgressCopyWith(ParseArchiveSaveBookProgress value, $Res Function(ParseArchiveSaveBookProgress) _then) = _$ParseArchiveSaveBookProgressCopyWithImpl; @useResult $Res call({ - int current, int total + SaveStep step, int current, int total }); @@ -594,9 +594,10 @@ class _$ParseArchiveSaveBookProgressCopyWithImpl<$Res> /// Create a copy of ParseArchiveSaveBookProgress /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? current = null,Object? total = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? step = null,Object? current = null,Object? total = null,}) { return _then(_self.copyWith( -current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable +step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable +as SaveStep,current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable as int, )); @@ -683,10 +684,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( int current, int total)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( SaveStep step, int current, int total)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _ParseArchiveSaveBookProgress() when $default != null: -return $default(_that.current,_that.total);case _: +return $default(_that.step,_that.current,_that.total);case _: return orElse(); } @@ -704,10 +705,10 @@ return $default(_that.current,_that.total);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( int current, int total) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( SaveStep step, int current, int total) $default,) {final _that = this; switch (_that) { case _ParseArchiveSaveBookProgress(): -return $default(_that.current,_that.total);case _: +return $default(_that.step,_that.current,_that.total);case _: throw StateError('Unexpected subclass'); } @@ -724,10 +725,10 @@ return $default(_that.current,_that.total);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( int current, int total)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( SaveStep step, int current, int total)? $default,) {final _that = this; switch (_that) { case _ParseArchiveSaveBookProgress() when $default != null: -return $default(_that.current,_that.total);case _: +return $default(_that.step,_that.current,_that.total);case _: return null; } @@ -738,10 +739,11 @@ return $default(_that.current,_that.total);case _: /// @nodoc -class _ParseArchiveSaveBookProgress implements ParseArchiveSaveBookProgress { - const _ParseArchiveSaveBookProgress({this.current = 0, this.total = 0}); +class _ParseArchiveSaveBookProgress extends ParseArchiveSaveBookProgress { + const _ParseArchiveSaveBookProgress({this.step = SaveStep.generateCover, this.current = 0, this.total = 0}): super._(); +@override@JsonKey() final SaveStep step; @override@JsonKey() final int current; @override@JsonKey() final int total; @@ -755,16 +757,16 @@ _$ParseArchiveSaveBookProgressCopyWith<_ParseArchiveSaveBookProgress> get copyWi @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseArchiveSaveBookProgress&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseArchiveSaveBookProgress&&(identical(other.step, step) || other.step == step)&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); } @override -int get hashCode => Object.hash(runtimeType,current,total); +int get hashCode => Object.hash(runtimeType,step,current,total); @override String toString() { - return 'ParseArchiveSaveBookProgress(current: $current, total: $total)'; + return 'ParseArchiveSaveBookProgress(step: $step, current: $current, total: $total)'; } @@ -775,7 +777,7 @@ abstract mixin class _$ParseArchiveSaveBookProgressCopyWith<$Res> implements $Pa factory _$ParseArchiveSaveBookProgressCopyWith(_ParseArchiveSaveBookProgress value, $Res Function(_ParseArchiveSaveBookProgress) _then) = __$ParseArchiveSaveBookProgressCopyWithImpl; @override @useResult $Res call({ - int current, int total + SaveStep step, int current, int total }); @@ -792,9 +794,10 @@ class __$ParseArchiveSaveBookProgressCopyWithImpl<$Res> /// Create a copy of ParseArchiveSaveBookProgress /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? current = null,Object? total = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? step = null,Object? current = null,Object? total = null,}) { return _then(_ParseArchiveSaveBookProgress( -current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable +step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable +as SaveStep,current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable as int, )); diff --git a/lib/feature/parse/ui/provider/parse_archive_provider.g.dart b/lib/feature/parse/ui/provider/parse_archive_provider.g.dart index 33ed94a..b6f95b8 100644 --- a/lib/feature/parse/ui/provider/parse_archive_provider.g.dart +++ b/lib/feature/parse/ui/provider/parse_archive_provider.g.dart @@ -50,7 +50,7 @@ final class ParseArchiveProvider } } -String _$parseArchiveHash() => r'15200f8f1b3d556233c5d24a6775874fe06071d7'; +String _$parseArchiveHash() => r'550d2a3343eafbb33bc33f18bb234ffd12ead4d7'; final class ParseArchiveFamily extends $Family with @@ -124,7 +124,7 @@ final class ParseArchiveSaveBookProvider } String _$parseArchiveSaveBookHash() => - r'41bf64ce2c4064ea3a2d35995742e750a7d10c9e'; + r'731cd951752090666be1330594b8248c21d50f2f'; abstract class _$ParseArchiveSaveBook extends $AsyncNotifier { FutureOr build(); diff --git a/lib/feature/parse/ui/provider/parse_batch_archive_provider.dart b/lib/feature/parse/ui/provider/parse_batch_archive_provider.dart index 31a36a5..5b8f076 100644 --- a/lib/feature/parse/ui/provider/parse_batch_archive_provider.dart +++ b/lib/feature/parse/ui/provider/parse_batch_archive_provider.dart @@ -54,19 +54,35 @@ abstract class ParseBatchArchiveSaveBookProgress const factory ParseBatchArchiveSaveBookProgress({ @Default(0) int current, @Default(0) int total, + @Default(SaveStep.generateCover) SaveStep step, + @Default(0) int stepCurrent, + @Default(0) int stepTotal, + @Default(0) int bookIndex, }) = _ParseBatchArchiveSaveBookProgress; + + const ParseBatchArchiveSaveBookProgress._(); + + String get stepText { + final bookInfo = total > 0 ? '(${bookIndex + 1}/$total) ' : ''; + return switch (step) { + SaveStep.generateCover => '$bookInfo生成封面图...', + SaveStep.generatePreview => '$bookInfo生成预览图... ($stepCurrent/$stepTotal)', + SaveStep.saveOriginal => '$bookInfo保存原图... ($stepCurrent/$stepTotal)', + SaveStep.saveDatabase => '保存数据...', + }; + } } -final parseBatchArchiveProgressProvider = StateProvider( - (_) => - const ParseBatchArchiveProgress( +final parseBatchArchiveProgressProvider = + StateProvider( + (_) => const ParseBatchArchiveProgress( completeCount: 0, totalCount: 0, currentFileName: '', currentFileProgress: 0, currentFileTotal: 0, ), -); + ); final parseBatchArchiveSaveBookProgressProvider = StateProvider( @@ -83,8 +99,7 @@ class ParseBatchArchive extends _$ParseBatchArchive { @override FutureOr build(ParseBatchArchiveParam param) async { _param = param; - Future.microtask(_parseBatchArchive); - return const ParseBatchArchiveState(parseBatchArchiveList: []); + return _parseBatchArchive(); } Future _requestStoragePermission() async { @@ -98,28 +113,15 @@ class ParseBatchArchive extends _$ParseBatchArchive { return storageStatus.isGranted; } - Future _parseBatchArchive() async { + Future _parseBatchArchive() async { state = const AsyncLoading(); - ref.read(parseBatchArchiveProgressProvider.notifier).state = - const ParseBatchArchiveProgress( - completeCount: 0, - totalCount: 0, - currentFileName: '', - currentFileProgress: 0, - currentFileTotal: 0, - ); - final hasPermission = await _requestStoragePermission(); if (!hasPermission) { - state = AsyncError( - '需要「所有文件访问权限」才能读取外部目录,请在系统设置中授权后重试。', - StackTrace.current, - ); - return; + throw Exception('需要「所有文件访问权限」才能读取外部目录,请在系统设置中授权后重试。'); } - final parseResult = _param.archivePaths != null && - _param.archivePaths!.isNotEmpty + final parseResult = + _param.archivePaths != null && _param.archivePaths!.isNotEmpty ? await _parseArchiveService.parseBatchArchivesFromPaths( _param.archivePaths!, _onStart, @@ -135,32 +137,32 @@ class ParseBatchArchive extends _$ParseBatchArchive { onCurrentItemProgress: _onCurrentItemProgress, ); - parseResult.fold( - onSuccess: (data) { - state = AsyncData(ParseBatchArchiveState(parseBatchArchiveList: data)); - }, - onError: (error) { - state = AsyncError(error.message, StackTrace.current); - }, - ); + if (parseResult.isError) { + throw Exception(parseResult.error?.message); + } + + return ParseBatchArchiveState(parseBatchArchiveList: parseResult.data!); } void _onStart(int total) { + if (!ref.mounted) return; final current = ref.read(parseBatchArchiveProgressProvider); - ref.read(parseBatchArchiveProgressProvider.notifier).state = - current.copyWith(totalCount: total); + ref.read(parseBatchArchiveProgressProvider.notifier).state = current + .copyWith(totalCount: total); } void _onProgress(int count) { + if (!ref.mounted) return; final current = ref.read(parseBatchArchiveProgressProvider); - ref.read(parseBatchArchiveProgressProvider.notifier).state = - current.copyWith(completeCount: count); + ref.read(parseBatchArchiveProgressProvider.notifier).state = current + .copyWith(completeCount: count); } void _onCurrentItemChanged(String fileName) { + if (!ref.mounted) return; final current = ref.read(parseBatchArchiveProgressProvider); - ref.read(parseBatchArchiveProgressProvider.notifier).state = - current.copyWith( + ref.read(parseBatchArchiveProgressProvider.notifier).state = current + .copyWith( currentFileName: fileName, currentFileProgress: 0, currentFileTotal: 0, @@ -168,12 +170,10 @@ class ParseBatchArchive extends _$ParseBatchArchive { } void _onCurrentItemProgress(int currentCount, int total) { + if (!ref.mounted) return; final current = ref.read(parseBatchArchiveProgressProvider); - ref.read(parseBatchArchiveProgressProvider.notifier).state = - current.copyWith( - currentFileProgress: currentCount, - currentFileTotal: total, - ); + ref.read(parseBatchArchiveProgressProvider.notifier).state = current + .copyWith(currentFileProgress: currentCount, currentFileTotal: total); } } @@ -188,22 +188,24 @@ class ParseBatchArchiveSaveBook extends _$ParseBatchArchiveSaveBook { if (state.isLoading || parseBatchList.isEmpty) return; state = const AsyncLoading(); - ref.read(parseBatchArchiveSaveBookProgressProvider.notifier).state = - ParseBatchArchiveSaveBookProgress( - current: 0, - total: parseBatchList.length, - ); + ref + .read(parseBatchArchiveSaveBookProgressProvider.notifier) + .state = ParseBatchArchiveSaveBookProgress( + current: 0, + total: parseBatchList.length, + ); final dos = parseBatchList .map((e) => SaveAsBookDto(title: e.name, paths: e.tempPaths)) .toList(); final result = await _bookRepository.saveBatchAsBooks(dos, (count) { - ref.read(parseBatchArchiveSaveBookProgressProvider.notifier).state = - ParseBatchArchiveSaveBookProgress( - current: count, - total: parseBatchList.length, - ); + ref + .read(parseBatchArchiveSaveBookProgressProvider.notifier) + .state = ParseBatchArchiveSaveBookProgress( + current: count, + total: parseBatchList.length, + ); }); result.fold( @@ -216,4 +218,3 @@ class ParseBatchArchiveSaveBook extends _$ParseBatchArchiveSaveBook { ); } } - diff --git a/lib/feature/parse/ui/provider/parse_batch_archive_provider.freezed.dart b/lib/feature/parse/ui/provider/parse_batch_archive_provider.freezed.dart index 588ac4f..311584a 100644 --- a/lib/feature/parse/ui/provider/parse_batch_archive_provider.freezed.dart +++ b/lib/feature/parse/ui/provider/parse_batch_archive_provider.freezed.dart @@ -814,7 +814,7 @@ as int, /// @nodoc mixin _$ParseBatchArchiveSaveBookProgress { - int get current; int get total; + int get current; int get total; SaveStep get step; int get stepCurrent; int get stepTotal; int get bookIndex; /// Create a copy of ParseBatchArchiveSaveBookProgress /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -825,16 +825,16 @@ $ParseBatchArchiveSaveBookProgressCopyWith ge @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseBatchArchiveSaveBookProgress&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseBatchArchiveSaveBookProgress&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)&&(identical(other.step, step) || other.step == step)&&(identical(other.stepCurrent, stepCurrent) || other.stepCurrent == stepCurrent)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.bookIndex, bookIndex) || other.bookIndex == bookIndex)); } @override -int get hashCode => Object.hash(runtimeType,current,total); +int get hashCode => Object.hash(runtimeType,current,total,step,stepCurrent,stepTotal,bookIndex); @override String toString() { - return 'ParseBatchArchiveSaveBookProgress(current: $current, total: $total)'; + return 'ParseBatchArchiveSaveBookProgress(current: $current, total: $total, step: $step, stepCurrent: $stepCurrent, stepTotal: $stepTotal, bookIndex: $bookIndex)'; } @@ -845,7 +845,7 @@ abstract mixin class $ParseBatchArchiveSaveBookProgressCopyWith<$Res> { factory $ParseBatchArchiveSaveBookProgressCopyWith(ParseBatchArchiveSaveBookProgress value, $Res Function(ParseBatchArchiveSaveBookProgress) _then) = _$ParseBatchArchiveSaveBookProgressCopyWithImpl; @useResult $Res call({ - int current, int total + int current, int total, SaveStep step, int stepCurrent, int stepTotal, int bookIndex }); @@ -862,10 +862,14 @@ class _$ParseBatchArchiveSaveBookProgressCopyWithImpl<$Res> /// Create a copy of ParseBatchArchiveSaveBookProgress /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? current = null,Object? total = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? current = null,Object? total = null,Object? step = null,Object? stepCurrent = null,Object? stepTotal = null,Object? bookIndex = null,}) { return _then(_self.copyWith( current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable +as int,step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable +as SaveStep,stepCurrent: null == stepCurrent ? _self.stepCurrent : stepCurrent // ignore: cast_nullable_to_non_nullable +as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable +as int,bookIndex: null == bookIndex ? _self.bookIndex : bookIndex // ignore: cast_nullable_to_non_nullable as int, )); } @@ -951,10 +955,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( int current, int total)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( int current, int total, SaveStep step, int stepCurrent, int stepTotal, int bookIndex)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _ParseBatchArchiveSaveBookProgress() when $default != null: -return $default(_that.current,_that.total);case _: +return $default(_that.current,_that.total,_that.step,_that.stepCurrent,_that.stepTotal,_that.bookIndex);case _: return orElse(); } @@ -972,10 +976,10 @@ return $default(_that.current,_that.total);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( int current, int total) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( int current, int total, SaveStep step, int stepCurrent, int stepTotal, int bookIndex) $default,) {final _that = this; switch (_that) { case _ParseBatchArchiveSaveBookProgress(): -return $default(_that.current,_that.total);case _: +return $default(_that.current,_that.total,_that.step,_that.stepCurrent,_that.stepTotal,_that.bookIndex);case _: throw StateError('Unexpected subclass'); } @@ -992,10 +996,10 @@ return $default(_that.current,_that.total);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( int current, int total)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( int current, int total, SaveStep step, int stepCurrent, int stepTotal, int bookIndex)? $default,) {final _that = this; switch (_that) { case _ParseBatchArchiveSaveBookProgress() when $default != null: -return $default(_that.current,_that.total);case _: +return $default(_that.current,_that.total,_that.step,_that.stepCurrent,_that.stepTotal,_that.bookIndex);case _: return null; } @@ -1006,12 +1010,16 @@ return $default(_that.current,_that.total);case _: /// @nodoc -class _ParseBatchArchiveSaveBookProgress implements ParseBatchArchiveSaveBookProgress { - const _ParseBatchArchiveSaveBookProgress({this.current = 0, this.total = 0}); +class _ParseBatchArchiveSaveBookProgress extends ParseBatchArchiveSaveBookProgress { + const _ParseBatchArchiveSaveBookProgress({this.current = 0, this.total = 0, this.step = SaveStep.generateCover, this.stepCurrent = 0, this.stepTotal = 0, this.bookIndex = 0}): super._(); @override@JsonKey() final int current; @override@JsonKey() final int total; +@override@JsonKey() final SaveStep step; +@override@JsonKey() final int stepCurrent; +@override@JsonKey() final int stepTotal; +@override@JsonKey() final int bookIndex; /// Create a copy of ParseBatchArchiveSaveBookProgress /// with the given fields replaced by the non-null parameter values. @@ -1023,16 +1031,16 @@ _$ParseBatchArchiveSaveBookProgressCopyWith<_ParseBatchArchiveSaveBookProgress> @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseBatchArchiveSaveBookProgress&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseBatchArchiveSaveBookProgress&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)&&(identical(other.step, step) || other.step == step)&&(identical(other.stepCurrent, stepCurrent) || other.stepCurrent == stepCurrent)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.bookIndex, bookIndex) || other.bookIndex == bookIndex)); } @override -int get hashCode => Object.hash(runtimeType,current,total); +int get hashCode => Object.hash(runtimeType,current,total,step,stepCurrent,stepTotal,bookIndex); @override String toString() { - return 'ParseBatchArchiveSaveBookProgress(current: $current, total: $total)'; + return 'ParseBatchArchiveSaveBookProgress(current: $current, total: $total, step: $step, stepCurrent: $stepCurrent, stepTotal: $stepTotal, bookIndex: $bookIndex)'; } @@ -1043,7 +1051,7 @@ abstract mixin class _$ParseBatchArchiveSaveBookProgressCopyWith<$Res> implement factory _$ParseBatchArchiveSaveBookProgressCopyWith(_ParseBatchArchiveSaveBookProgress value, $Res Function(_ParseBatchArchiveSaveBookProgress) _then) = __$ParseBatchArchiveSaveBookProgressCopyWithImpl; @override @useResult $Res call({ - int current, int total + int current, int total, SaveStep step, int stepCurrent, int stepTotal, int bookIndex }); @@ -1060,10 +1068,14 @@ class __$ParseBatchArchiveSaveBookProgressCopyWithImpl<$Res> /// Create a copy of ParseBatchArchiveSaveBookProgress /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? current = null,Object? total = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? current = null,Object? total = null,Object? step = null,Object? stepCurrent = null,Object? stepTotal = null,Object? bookIndex = null,}) { return _then(_ParseBatchArchiveSaveBookProgress( current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable +as int,step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable +as SaveStep,stepCurrent: null == stepCurrent ? _self.stepCurrent : stepCurrent // ignore: cast_nullable_to_non_nullable +as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable +as int,bookIndex: null == bookIndex ? _self.bookIndex : bookIndex // ignore: cast_nullable_to_non_nullable as int, )); } diff --git a/lib/feature/parse/ui/provider/parse_batch_archive_provider.g.dart b/lib/feature/parse/ui/provider/parse_batch_archive_provider.g.dart index 3d6138f..11b6bcd 100644 --- a/lib/feature/parse/ui/provider/parse_batch_archive_provider.g.dart +++ b/lib/feature/parse/ui/provider/parse_batch_archive_provider.g.dart @@ -50,7 +50,7 @@ final class ParseBatchArchiveProvider } } -String _$parseBatchArchiveHash() => r'4c2d0a3ec18de530d0397b1453fe0bbdaf5e5639'; +String _$parseBatchArchiveHash() => r'd0ea0561ebb2ca43ab7f2fae08254bf67582edfe'; final class ParseBatchArchiveFamily extends $Family with diff --git a/lib/feature/parse/ui/provider/parse_batch_image_folder.dart b/lib/feature/parse/ui/provider/parse_batch_image_folder.dart index 9bd525a..93c4b82 100644 --- a/lib/feature/parse/ui/provider/parse_batch_image_folder.dart +++ b/lib/feature/parse/ui/provider/parse_batch_image_folder.dart @@ -39,8 +39,24 @@ abstract class SaveBatchAsBookState with _$SaveBatchAsBookState { const factory SaveBatchAsBookState({ @Default(0) int saveAsBookCount, @Default(0) int totalCount, + @Default(SaveStep.generateCover) SaveStep step, + @Default(0) int stepCurrent, + @Default(0) int stepTotal, + @Default(0) int bookIndex, @Default(AsyncData(null)) AsyncValue submitState, }) = _SaveBatchAsBookState; + + const SaveBatchAsBookState._(); + + String get stepText { + final bookInfo = totalCount > 0 ? '(${bookIndex + 1}/$totalCount) ' : ''; + return switch (step) { + SaveStep.generateCover => '$bookInfo生成封面图...', + SaveStep.generatePreview => '$bookInfo生成预览图... ($stepCurrent/$stepTotal)', + SaveStep.saveOriginal => '$bookInfo保存原图... ($stepCurrent/$stepTotal)', + SaveStep.saveDatabase => '保存数据...', + }; + } } @freezed @@ -88,6 +104,7 @@ class ParseBatchImageFolder extends _$ParseBatchImageFolder { ); final hasPermission = await _requestStoragePermission(); + if (!ref.mounted) return; if (!hasPermission) { state = AsyncValue.error("需要存储权限才能读取图片文件夹", StackTrace.current); return; @@ -101,10 +118,12 @@ class ParseBatchImageFolder extends _$ParseBatchImageFolder { param.imagePaths!, (total) { localState = localState.copyWith(totalCount: total); + if (!ref.mounted) return; state = AsyncData(localState); }, (count) { localState = localState.copyWith(completeCount: count); + if (!ref.mounted) return; state = AsyncData(localState); }, onCurrentItemChanged: (fileName) { @@ -113,6 +132,7 @@ class ParseBatchImageFolder extends _$ParseBatchImageFolder { currentFileProgress: 0, currentFileTotal: 0, ); + if (!ref.mounted) return; state = AsyncData(localState); }, onCurrentItemProgress: (current, total) { @@ -120,6 +140,7 @@ class ParseBatchImageFolder extends _$ParseBatchImageFolder { currentFileProgress: current, currentFileTotal: total, ); + if (!ref.mounted) return; state = AsyncData(localState); }, ) @@ -127,10 +148,12 @@ class ParseBatchImageFolder extends _$ParseBatchImageFolder { param.parentDirPath ?? '', (total) { localState = localState.copyWith(totalCount: total); + if (!ref.mounted) return; state = AsyncData(localState); }, (count) { localState = localState.copyWith(completeCount: count); + if (!ref.mounted) return; state = AsyncData(localState); }, onCurrentItemChanged: (fileName) { @@ -139,6 +162,7 @@ class ParseBatchImageFolder extends _$ParseBatchImageFolder { currentFileProgress: 0, currentFileTotal: 0, ); + if (!ref.mounted) return; state = AsyncData(localState); }, onCurrentItemProgress: (current, total) { @@ -146,10 +170,12 @@ class ParseBatchImageFolder extends _$ParseBatchImageFolder { currentFileProgress: current, currentFileTotal: total, ); + if (!ref.mounted) return; state = AsyncData(localState); }, ); + if (!ref.mounted) return; result.fold( onSuccess: (data) { state = AsyncData(localState.copyWith( @@ -189,9 +215,12 @@ class SaveBatchAsBook extends _$SaveBatchAsBook { .map((e) => SaveAsBookDto(title: e.name, paths: e.tempPaths)) .toList(); - final result = await _bookRepository.saveBatchAsBooks(dos, (count) { - state = state.copyWith(saveAsBookCount: count); - }); + final result = await _bookRepository.saveBatchAsBooks( + dos, + (count) { + state = state.copyWith(saveAsBookCount: count); + }, + ); result.fold( onSuccess: (_) { diff --git a/lib/feature/parse/ui/provider/parse_batch_image_folder.freezed.dart b/lib/feature/parse/ui/provider/parse_batch_image_folder.freezed.dart index 7241b02..24b96a0 100644 --- a/lib/feature/parse/ui/provider/parse_batch_image_folder.freezed.dart +++ b/lib/feature/parse/ui/provider/parse_batch_image_folder.freezed.dart @@ -295,7 +295,7 @@ as bool, /// @nodoc mixin _$SaveBatchAsBookState { - int get saveAsBookCount; int get totalCount; AsyncValue get submitState; + int get saveAsBookCount; int get totalCount; SaveStep get step; int get stepCurrent; int get stepTotal; int get bookIndex; AsyncValue get submitState; /// Create a copy of SaveBatchAsBookState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -306,16 +306,16 @@ $SaveBatchAsBookStateCopyWith get copyWith => _$SaveBatchA @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is SaveBatchAsBookState&&(identical(other.saveAsBookCount, saveAsBookCount) || other.saveAsBookCount == saveAsBookCount)&&(identical(other.totalCount, totalCount) || other.totalCount == totalCount)&&(identical(other.submitState, submitState) || other.submitState == submitState)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is SaveBatchAsBookState&&(identical(other.saveAsBookCount, saveAsBookCount) || other.saveAsBookCount == saveAsBookCount)&&(identical(other.totalCount, totalCount) || other.totalCount == totalCount)&&(identical(other.step, step) || other.step == step)&&(identical(other.stepCurrent, stepCurrent) || other.stepCurrent == stepCurrent)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.bookIndex, bookIndex) || other.bookIndex == bookIndex)&&(identical(other.submitState, submitState) || other.submitState == submitState)); } @override -int get hashCode => Object.hash(runtimeType,saveAsBookCount,totalCount,submitState); +int get hashCode => Object.hash(runtimeType,saveAsBookCount,totalCount,step,stepCurrent,stepTotal,bookIndex,submitState); @override String toString() { - return 'SaveBatchAsBookState(saveAsBookCount: $saveAsBookCount, totalCount: $totalCount, submitState: $submitState)'; + return 'SaveBatchAsBookState(saveAsBookCount: $saveAsBookCount, totalCount: $totalCount, step: $step, stepCurrent: $stepCurrent, stepTotal: $stepTotal, bookIndex: $bookIndex, submitState: $submitState)'; } @@ -326,7 +326,7 @@ abstract mixin class $SaveBatchAsBookStateCopyWith<$Res> { factory $SaveBatchAsBookStateCopyWith(SaveBatchAsBookState value, $Res Function(SaveBatchAsBookState) _then) = _$SaveBatchAsBookStateCopyWithImpl; @useResult $Res call({ - int saveAsBookCount, int totalCount, AsyncValue submitState + int saveAsBookCount, int totalCount, SaveStep step, int stepCurrent, int stepTotal, int bookIndex, AsyncValue submitState }); @@ -343,10 +343,14 @@ class _$SaveBatchAsBookStateCopyWithImpl<$Res> /// Create a copy of SaveBatchAsBookState /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? saveAsBookCount = null,Object? totalCount = null,Object? submitState = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? saveAsBookCount = null,Object? totalCount = null,Object? step = null,Object? stepCurrent = null,Object? stepTotal = null,Object? bookIndex = null,Object? submitState = null,}) { return _then(_self.copyWith( saveAsBookCount: null == saveAsBookCount ? _self.saveAsBookCount : saveAsBookCount // ignore: cast_nullable_to_non_nullable as int,totalCount: null == totalCount ? _self.totalCount : totalCount // ignore: cast_nullable_to_non_nullable +as int,step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable +as SaveStep,stepCurrent: null == stepCurrent ? _self.stepCurrent : stepCurrent // ignore: cast_nullable_to_non_nullable +as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable +as int,bookIndex: null == bookIndex ? _self.bookIndex : bookIndex // ignore: cast_nullable_to_non_nullable as int,submitState: null == submitState ? _self.submitState : submitState // ignore: cast_nullable_to_non_nullable as AsyncValue, )); @@ -433,10 +437,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( int saveAsBookCount, int totalCount, AsyncValue submitState)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( int saveAsBookCount, int totalCount, SaveStep step, int stepCurrent, int stepTotal, int bookIndex, AsyncValue submitState)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _SaveBatchAsBookState() when $default != null: -return $default(_that.saveAsBookCount,_that.totalCount,_that.submitState);case _: +return $default(_that.saveAsBookCount,_that.totalCount,_that.step,_that.stepCurrent,_that.stepTotal,_that.bookIndex,_that.submitState);case _: return orElse(); } @@ -454,10 +458,10 @@ return $default(_that.saveAsBookCount,_that.totalCount,_that.submitState);case _ /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( int saveAsBookCount, int totalCount, AsyncValue submitState) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( int saveAsBookCount, int totalCount, SaveStep step, int stepCurrent, int stepTotal, int bookIndex, AsyncValue submitState) $default,) {final _that = this; switch (_that) { case _SaveBatchAsBookState(): -return $default(_that.saveAsBookCount,_that.totalCount,_that.submitState);case _: +return $default(_that.saveAsBookCount,_that.totalCount,_that.step,_that.stepCurrent,_that.stepTotal,_that.bookIndex,_that.submitState);case _: throw StateError('Unexpected subclass'); } @@ -474,10 +478,10 @@ return $default(_that.saveAsBookCount,_that.totalCount,_that.submitState);case _ /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( int saveAsBookCount, int totalCount, AsyncValue submitState)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( int saveAsBookCount, int totalCount, SaveStep step, int stepCurrent, int stepTotal, int bookIndex, AsyncValue submitState)? $default,) {final _that = this; switch (_that) { case _SaveBatchAsBookState() when $default != null: -return $default(_that.saveAsBookCount,_that.totalCount,_that.submitState);case _: +return $default(_that.saveAsBookCount,_that.totalCount,_that.step,_that.stepCurrent,_that.stepTotal,_that.bookIndex,_that.submitState);case _: return null; } @@ -488,12 +492,16 @@ return $default(_that.saveAsBookCount,_that.totalCount,_that.submitState);case _ /// @nodoc -class _SaveBatchAsBookState implements SaveBatchAsBookState { - const _SaveBatchAsBookState({this.saveAsBookCount = 0, this.totalCount = 0, this.submitState = const AsyncData(null)}); +class _SaveBatchAsBookState extends SaveBatchAsBookState { + const _SaveBatchAsBookState({this.saveAsBookCount = 0, this.totalCount = 0, this.step = SaveStep.generateCover, this.stepCurrent = 0, this.stepTotal = 0, this.bookIndex = 0, this.submitState = const AsyncData(null)}): super._(); @override@JsonKey() final int saveAsBookCount; @override@JsonKey() final int totalCount; +@override@JsonKey() final SaveStep step; +@override@JsonKey() final int stepCurrent; +@override@JsonKey() final int stepTotal; +@override@JsonKey() final int bookIndex; @override@JsonKey() final AsyncValue submitState; /// Create a copy of SaveBatchAsBookState @@ -506,16 +514,16 @@ _$SaveBatchAsBookStateCopyWith<_SaveBatchAsBookState> get copyWith => __$SaveBat @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _SaveBatchAsBookState&&(identical(other.saveAsBookCount, saveAsBookCount) || other.saveAsBookCount == saveAsBookCount)&&(identical(other.totalCount, totalCount) || other.totalCount == totalCount)&&(identical(other.submitState, submitState) || other.submitState == submitState)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SaveBatchAsBookState&&(identical(other.saveAsBookCount, saveAsBookCount) || other.saveAsBookCount == saveAsBookCount)&&(identical(other.totalCount, totalCount) || other.totalCount == totalCount)&&(identical(other.step, step) || other.step == step)&&(identical(other.stepCurrent, stepCurrent) || other.stepCurrent == stepCurrent)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.bookIndex, bookIndex) || other.bookIndex == bookIndex)&&(identical(other.submitState, submitState) || other.submitState == submitState)); } @override -int get hashCode => Object.hash(runtimeType,saveAsBookCount,totalCount,submitState); +int get hashCode => Object.hash(runtimeType,saveAsBookCount,totalCount,step,stepCurrent,stepTotal,bookIndex,submitState); @override String toString() { - return 'SaveBatchAsBookState(saveAsBookCount: $saveAsBookCount, totalCount: $totalCount, submitState: $submitState)'; + return 'SaveBatchAsBookState(saveAsBookCount: $saveAsBookCount, totalCount: $totalCount, step: $step, stepCurrent: $stepCurrent, stepTotal: $stepTotal, bookIndex: $bookIndex, submitState: $submitState)'; } @@ -526,7 +534,7 @@ abstract mixin class _$SaveBatchAsBookStateCopyWith<$Res> implements $SaveBatchA factory _$SaveBatchAsBookStateCopyWith(_SaveBatchAsBookState value, $Res Function(_SaveBatchAsBookState) _then) = __$SaveBatchAsBookStateCopyWithImpl; @override @useResult $Res call({ - int saveAsBookCount, int totalCount, AsyncValue submitState + int saveAsBookCount, int totalCount, SaveStep step, int stepCurrent, int stepTotal, int bookIndex, AsyncValue submitState }); @@ -543,10 +551,14 @@ class __$SaveBatchAsBookStateCopyWithImpl<$Res> /// Create a copy of SaveBatchAsBookState /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? saveAsBookCount = null,Object? totalCount = null,Object? submitState = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? saveAsBookCount = null,Object? totalCount = null,Object? step = null,Object? stepCurrent = null,Object? stepTotal = null,Object? bookIndex = null,Object? submitState = null,}) { return _then(_SaveBatchAsBookState( saveAsBookCount: null == saveAsBookCount ? _self.saveAsBookCount : saveAsBookCount // ignore: cast_nullable_to_non_nullable as int,totalCount: null == totalCount ? _self.totalCount : totalCount // ignore: cast_nullable_to_non_nullable +as int,step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable +as SaveStep,stepCurrent: null == stepCurrent ? _self.stepCurrent : stepCurrent // ignore: cast_nullable_to_non_nullable +as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable +as int,bookIndex: null == bookIndex ? _self.bookIndex : bookIndex // ignore: cast_nullable_to_non_nullable as int,submitState: null == submitState ? _self.submitState : submitState // ignore: cast_nullable_to_non_nullable as AsyncValue, )); diff --git a/lib/feature/parse/ui/provider/parse_batch_image_folder.g.dart b/lib/feature/parse/ui/provider/parse_batch_image_folder.g.dart index c9970dc..b24d98c 100644 --- a/lib/feature/parse/ui/provider/parse_batch_image_folder.g.dart +++ b/lib/feature/parse/ui/provider/parse_batch_image_folder.g.dart @@ -55,7 +55,7 @@ final class ParseBatchImageFolderProvider } String _$parseBatchImageFolderHash() => - r'e1705d70a73923c87e96ca709fcfeb8903173595'; + r'020c694ab8f1d17f99bf74075a61d1908039bb32'; final class ParseBatchImageFolderFamily extends $Family with diff --git a/lib/feature/parse/ui/provider/parse_batch_pdf_provider.dart b/lib/feature/parse/ui/provider/parse_batch_pdf_provider.dart index a1e7c8b..fd49e68 100644 --- a/lib/feature/parse/ui/provider/parse_batch_pdf_provider.dart +++ b/lib/feature/parse/ui/provider/parse_batch_pdf_provider.dart @@ -45,7 +45,23 @@ abstract class ParseBatchPdfSaveBookProgressState const factory ParseBatchPdfSaveBookProgressState({ @Default(0) int current, @Default(0) int total, + @Default(SaveStep.generateCover) SaveStep step, + @Default(0) int stepCurrent, + @Default(0) int stepTotal, + @Default(0) int bookIndex, }) = _ParseBatchPdfSaveBookProgressState; + + const ParseBatchPdfSaveBookProgressState._(); + + String get stepText { + final bookInfo = total > 0 ? '(${bookIndex + 1}/$total) ' : ''; + return switch (step) { + SaveStep.generateCover => '$bookInfo生成封面图...', + SaveStep.generatePreview => '$bookInfo生成预览图... ($stepCurrent/$stepTotal)', + SaveStep.saveOriginal => '$bookInfo保存原图... ($stepCurrent/$stepTotal)', + SaveStep.saveDatabase => '保存数据...', + }; + } } final parseBatchProgressProvider = StateProvider((ref) { @@ -67,8 +83,7 @@ class ParseBatchPdf extends _$ParseBatchPdf { @override FutureOr build(ParseBatchPdfParam param) async { _param = param; - Future.microtask(_parseBatch); - return const ParseBatchPdfState(parseBatchList: []); + return await _parseBatch(); } Future _requestStoragePermission() async { @@ -81,23 +96,11 @@ class ParseBatchPdf extends _$ParseBatchPdf { return storageStatus.isGranted; } - Future _parseBatch() async { - final current = state.value ?? const ParseBatchPdfState(parseBatchList: []); - ref - .read(parseBatchProgressProvider.notifier) - .state = const ParseBatchPdfProgress( - completeCount: 0, - totalCount: 0, - currentFileName: '', - currentFileProgress: 0, - currentFileTotal: 0, - ); - + Future _parseBatch() async { state = const AsyncValue.loading(); final hasPermission = await _requestStoragePermission(); if (!hasPermission) { - state = AsyncValue.error("需要存储权限才能解析PDF", StackTrace.current); - return; + throw Exception("需要存储权限才能解析PDF"); } final pdfPaths = _param.pdfPaths; @@ -106,16 +109,19 @@ class ParseBatchPdf extends _$ParseBatchPdf { ? await _service.parseBatchPdfsFromPaths( pdfPaths, (total) { + if (!ref.mounted) return; final progress = ref.read(parseBatchProgressProvider); ref.read(parseBatchProgressProvider.notifier).state = progress .copyWith(totalCount: total); }, (count) { + if (!ref.mounted) return; final progress = ref.read(parseBatchProgressProvider); ref.read(parseBatchProgressProvider.notifier).state = progress .copyWith(completeCount: count); }, onCurrentFileChanged: (fileName) { + if (!ref.mounted) return; final progress = ref.read(parseBatchProgressProvider); ref.read(parseBatchProgressProvider.notifier).state = progress .copyWith( @@ -125,6 +131,7 @@ class ParseBatchPdf extends _$ParseBatchPdf { ); }, onCurrentFileProgress: (current, total) { + if (!ref.mounted) return; final progress = ref.read(parseBatchProgressProvider); ref.read(parseBatchProgressProvider.notifier).state = progress .copyWith( @@ -136,16 +143,19 @@ class ParseBatchPdf extends _$ParseBatchPdf { : await _service.parseBatchPdfs( pdfDirPath ?? '', (total) { + if (!ref.mounted) return; final progress = ref.read(parseBatchProgressProvider); ref.read(parseBatchProgressProvider.notifier).state = progress .copyWith(totalCount: total); }, (count) { + if (!ref.mounted) return; final progress = ref.read(parseBatchProgressProvider); ref.read(parseBatchProgressProvider.notifier).state = progress .copyWith(completeCount: count); }, onCurrentFileChanged: (fileName) { + if (!ref.mounted) return; final progress = ref.read(parseBatchProgressProvider); ref.read(parseBatchProgressProvider.notifier).state = progress .copyWith( @@ -155,6 +165,7 @@ class ParseBatchPdf extends _$ParseBatchPdf { ); }, onCurrentFileProgress: (current, total) { + if (!ref.mounted) return; final progress = ref.read(parseBatchProgressProvider); ref.read(parseBatchProgressProvider.notifier).state = progress .copyWith( @@ -164,14 +175,11 @@ class ParseBatchPdf extends _$ParseBatchPdf { }, ); - parseResult.fold( - onSuccess: (data) { - state = AsyncValue.data(current.copyWith(parseBatchList: data)); - }, - onError: (error) { - state = AsyncValue.error(error.message, StackTrace.current); - }, - ); + if (parseResult.isError) { + throw Exception(parseResult.error); + } + + return ParseBatchPdfState(parseBatchList: parseResult.data!); } } @@ -196,6 +204,13 @@ class ParseBatchPdfSaveBook extends _$ParseBatchPdfSaveBook { .map((e) => SaveAsBookDto(title: e.name, paths: e.tempPaths)) .toList(); + ref.read(parseBatchPdfSaveBookProgressProvider.notifier).state = + ParseBatchPdfSaveBookProgressState( + current: 0, + total: parseBatchList.length, + ); + + final result = await _repository.saveBatchAsBooks(dos, (count) { final progress = ref.read(parseBatchPdfSaveBookProgressProvider); ref.read(parseBatchPdfSaveBookProgressProvider.notifier).state = progress diff --git a/lib/feature/parse/ui/provider/parse_batch_pdf_provider.freezed.dart b/lib/feature/parse/ui/provider/parse_batch_pdf_provider.freezed.dart index 4f8374b..bb23c44 100644 --- a/lib/feature/parse/ui/provider/parse_batch_pdf_provider.freezed.dart +++ b/lib/feature/parse/ui/provider/parse_batch_pdf_provider.freezed.dart @@ -814,7 +814,7 @@ as List?, /// @nodoc mixin _$ParseBatchPdfSaveBookProgressState { - int get current; int get total; + int get current; int get total; SaveStep get step; int get stepCurrent; int get stepTotal; int get bookIndex; /// Create a copy of ParseBatchPdfSaveBookProgressState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -825,16 +825,16 @@ $ParseBatchPdfSaveBookProgressStateCopyWith @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseBatchPdfSaveBookProgressState&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseBatchPdfSaveBookProgressState&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)&&(identical(other.step, step) || other.step == step)&&(identical(other.stepCurrent, stepCurrent) || other.stepCurrent == stepCurrent)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.bookIndex, bookIndex) || other.bookIndex == bookIndex)); } @override -int get hashCode => Object.hash(runtimeType,current,total); +int get hashCode => Object.hash(runtimeType,current,total,step,stepCurrent,stepTotal,bookIndex); @override String toString() { - return 'ParseBatchPdfSaveBookProgressState(current: $current, total: $total)'; + return 'ParseBatchPdfSaveBookProgressState(current: $current, total: $total, step: $step, stepCurrent: $stepCurrent, stepTotal: $stepTotal, bookIndex: $bookIndex)'; } @@ -845,7 +845,7 @@ abstract mixin class $ParseBatchPdfSaveBookProgressStateCopyWith<$Res> { factory $ParseBatchPdfSaveBookProgressStateCopyWith(ParseBatchPdfSaveBookProgressState value, $Res Function(ParseBatchPdfSaveBookProgressState) _then) = _$ParseBatchPdfSaveBookProgressStateCopyWithImpl; @useResult $Res call({ - int current, int total + int current, int total, SaveStep step, int stepCurrent, int stepTotal, int bookIndex }); @@ -862,10 +862,14 @@ class _$ParseBatchPdfSaveBookProgressStateCopyWithImpl<$Res> /// Create a copy of ParseBatchPdfSaveBookProgressState /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? current = null,Object? total = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? current = null,Object? total = null,Object? step = null,Object? stepCurrent = null,Object? stepTotal = null,Object? bookIndex = null,}) { return _then(_self.copyWith( current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable +as int,step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable +as SaveStep,stepCurrent: null == stepCurrent ? _self.stepCurrent : stepCurrent // ignore: cast_nullable_to_non_nullable +as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable +as int,bookIndex: null == bookIndex ? _self.bookIndex : bookIndex // ignore: cast_nullable_to_non_nullable as int, )); } @@ -951,10 +955,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( int current, int total)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( int current, int total, SaveStep step, int stepCurrent, int stepTotal, int bookIndex)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _ParseBatchPdfSaveBookProgressState() when $default != null: -return $default(_that.current,_that.total);case _: +return $default(_that.current,_that.total,_that.step,_that.stepCurrent,_that.stepTotal,_that.bookIndex);case _: return orElse(); } @@ -972,10 +976,10 @@ return $default(_that.current,_that.total);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( int current, int total) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( int current, int total, SaveStep step, int stepCurrent, int stepTotal, int bookIndex) $default,) {final _that = this; switch (_that) { case _ParseBatchPdfSaveBookProgressState(): -return $default(_that.current,_that.total);case _: +return $default(_that.current,_that.total,_that.step,_that.stepCurrent,_that.stepTotal,_that.bookIndex);case _: throw StateError('Unexpected subclass'); } @@ -992,10 +996,10 @@ return $default(_that.current,_that.total);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( int current, int total)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( int current, int total, SaveStep step, int stepCurrent, int stepTotal, int bookIndex)? $default,) {final _that = this; switch (_that) { case _ParseBatchPdfSaveBookProgressState() when $default != null: -return $default(_that.current,_that.total);case _: +return $default(_that.current,_that.total,_that.step,_that.stepCurrent,_that.stepTotal,_that.bookIndex);case _: return null; } @@ -1006,12 +1010,16 @@ return $default(_that.current,_that.total);case _: /// @nodoc -class _ParseBatchPdfSaveBookProgressState implements ParseBatchPdfSaveBookProgressState { - const _ParseBatchPdfSaveBookProgressState({this.current = 0, this.total = 0}); +class _ParseBatchPdfSaveBookProgressState extends ParseBatchPdfSaveBookProgressState { + const _ParseBatchPdfSaveBookProgressState({this.current = 0, this.total = 0, this.step = SaveStep.generateCover, this.stepCurrent = 0, this.stepTotal = 0, this.bookIndex = 0}): super._(); @override@JsonKey() final int current; @override@JsonKey() final int total; +@override@JsonKey() final SaveStep step; +@override@JsonKey() final int stepCurrent; +@override@JsonKey() final int stepTotal; +@override@JsonKey() final int bookIndex; /// Create a copy of ParseBatchPdfSaveBookProgressState /// with the given fields replaced by the non-null parameter values. @@ -1023,16 +1031,16 @@ _$ParseBatchPdfSaveBookProgressStateCopyWith<_ParseBatchPdfSaveBookProgressState @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseBatchPdfSaveBookProgressState&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseBatchPdfSaveBookProgressState&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)&&(identical(other.step, step) || other.step == step)&&(identical(other.stepCurrent, stepCurrent) || other.stepCurrent == stepCurrent)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.bookIndex, bookIndex) || other.bookIndex == bookIndex)); } @override -int get hashCode => Object.hash(runtimeType,current,total); +int get hashCode => Object.hash(runtimeType,current,total,step,stepCurrent,stepTotal,bookIndex); @override String toString() { - return 'ParseBatchPdfSaveBookProgressState(current: $current, total: $total)'; + return 'ParseBatchPdfSaveBookProgressState(current: $current, total: $total, step: $step, stepCurrent: $stepCurrent, stepTotal: $stepTotal, bookIndex: $bookIndex)'; } @@ -1043,7 +1051,7 @@ abstract mixin class _$ParseBatchPdfSaveBookProgressStateCopyWith<$Res> implemen factory _$ParseBatchPdfSaveBookProgressStateCopyWith(_ParseBatchPdfSaveBookProgressState value, $Res Function(_ParseBatchPdfSaveBookProgressState) _then) = __$ParseBatchPdfSaveBookProgressStateCopyWithImpl; @override @useResult $Res call({ - int current, int total + int current, int total, SaveStep step, int stepCurrent, int stepTotal, int bookIndex }); @@ -1060,10 +1068,14 @@ class __$ParseBatchPdfSaveBookProgressStateCopyWithImpl<$Res> /// Create a copy of ParseBatchPdfSaveBookProgressState /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? current = null,Object? total = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? current = null,Object? total = null,Object? step = null,Object? stepCurrent = null,Object? stepTotal = null,Object? bookIndex = null,}) { return _then(_ParseBatchPdfSaveBookProgressState( current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable +as int,step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable +as SaveStep,stepCurrent: null == stepCurrent ? _self.stepCurrent : stepCurrent // ignore: cast_nullable_to_non_nullable +as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable +as int,bookIndex: null == bookIndex ? _self.bookIndex : bookIndex // ignore: cast_nullable_to_non_nullable as int, )); } diff --git a/lib/feature/parse/ui/provider/parse_batch_pdf_provider.g.dart b/lib/feature/parse/ui/provider/parse_batch_pdf_provider.g.dart index 858774f..58f2ead 100644 --- a/lib/feature/parse/ui/provider/parse_batch_pdf_provider.g.dart +++ b/lib/feature/parse/ui/provider/parse_batch_pdf_provider.g.dart @@ -50,7 +50,7 @@ final class ParseBatchPdfProvider } } -String _$parseBatchPdfHash() => r'6523aa1d248e8d893e5e3700a7ab2b5b3bf62e62'; +String _$parseBatchPdfHash() => r'4528c3d2c95825540661e8230b492c74f4184a00'; final class ParseBatchPdfFamily extends $Family with diff --git a/lib/feature/parse/ui/provider/parse_form_provider.dart b/lib/feature/parse/ui/provider/parse_form_provider.dart index 72889d6..b8872f8 100644 --- a/lib/feature/parse/ui/provider/parse_form_provider.dart +++ b/lib/feature/parse/ui/provider/parse_form_provider.dart @@ -152,7 +152,10 @@ class ParseForm extends _$ParseForm { final paths = result.paths.whereType().toList(); final text = paths.isEmpty ? '' : '已选择 ${paths.length} 个 ZIP 文件'; batchArchivePathController.text = text; - state = state.copyWith(batchArchivePaths: paths, batchArchivePath: text); + state = state.copyWith( + batchArchivePaths: paths, + batchArchivePath: text, + ); } return; } @@ -162,7 +165,10 @@ class ParseForm extends _$ParseForm { ); if (result != null) { batchArchivePathController.text = result; - state = state.copyWith(batchArchivePaths: const [], batchArchivePath: result); + state = state.copyWith( + batchArchivePaths: const [], + batchArchivePath: result, + ); } } @@ -204,7 +210,10 @@ class ParseForm extends _$ParseForm { final paths = result.paths.whereType().toList(); final text = paths.isEmpty ? '' : '已选择 ${paths.length} 张图片(按所在文件夹分组)'; batchImageFolderPathController.text = text; - state = state.copyWith(batchImagePaths: paths, batchImageFolderPath: text); + state = state.copyWith( + batchImagePaths: paths, + batchImageFolderPath: text, + ); } return; } @@ -214,7 +223,10 @@ class ParseForm extends _$ParseForm { ); if (result != null) { batchImageFolderPathController.text = result; - state = state.copyWith(batchImagePaths: const [], batchImageFolderPath: result); + state = state.copyWith( + batchImagePaths: const [], + batchImageFolderPath: result, + ); } } @@ -259,12 +271,15 @@ class ParseForm extends _$ParseForm { } enum ParseFormType { - web, - archive, - batchArchive, - imageFolder, - batchImageFolder, - pdf, - batchPdf, -} + web("网页"), + archive("压缩包"), + batchArchive("批量压缩包"), + imageFolder("图片文件夹"), + batchImageFolder("批量图片文件夹"), + pdf("PDF"), + batchPdf("批量PDF"); + + final String description; + const ParseFormType(this.description); +} diff --git a/lib/feature/parse/ui/provider/parse_image_folder_provider.dart b/lib/feature/parse/ui/provider/parse_image_folder_provider.dart index ab51493..4df7528 100644 --- a/lib/feature/parse/ui/provider/parse_image_folder_provider.dart +++ b/lib/feature/parse/ui/provider/parse_image_folder_provider.dart @@ -42,15 +42,26 @@ abstract class ParseImageFolderSaveBookParam abstract class ParseImageFolderSaveBookProgress with _$ParseImageFolderSaveBookProgress { const factory ParseImageFolderSaveBookProgress({ + @Default(SaveStep.generateCover) SaveStep step, @Default(0) int current, @Default(0) int total, }) = _ParseImageFolderSaveBookProgress; + + const ParseImageFolderSaveBookProgress._(); + + String get stepText => switch (step) { + SaveStep.generateCover => '生成封面图...', + SaveStep.generatePreview => '生成预览图... ($current/$total)', + SaveStep.saveOriginal => '保存原图... ($current/$total)', + SaveStep.saveDatabase => '保存中...', + }; } final parseImageFolderSaveBookProgressProvider = - StateProvider.family( - (_, __) => const ParseImageFolderSaveBookProgress(), - ); + StateProvider.family< + ParseImageFolderSaveBookProgress, + ParseImageFolderParam + >((_, __) => const ParseImageFolderSaveBookProgress()); @riverpod class ParseImageFolder extends _$ParseImageFolder { @@ -59,9 +70,7 @@ class ParseImageFolder extends _$ParseImageFolder { @override FutureOr build(ParseImageFolderParam param) async { - final folderName = _resolveFolderName(param); - Future.microtask(() => _parseImageFolder(param)); - return ParseImageFolderState(folderName: folderName, imagePaths: const []); + return await _parseImageFolder(param); } String _resolveFolderName(ParseImageFolderParam param) { @@ -90,13 +99,14 @@ class ParseImageFolder extends _$ParseImageFolder { return storageStatus.isGranted; } - Future _parseImageFolder(ParseImageFolderParam param) async { + Future _parseImageFolder( + ParseImageFolderParam param, + ) async { state = const AsyncLoading(); final hasPermission = await _requestStoragePermission(); if (!hasPermission) { - state = AsyncError('需要存储权限才能读取图片文件夹', StackTrace.current); - return; + throw Exception('需要存储权限才能读取图片文件夹'); } final folderName = _resolveFolderName(param); @@ -105,15 +115,13 @@ class ParseImageFolder extends _$ParseImageFolder { ? await _parseArchiveService.parseImagePaths(param.imagePathsInput!) : await _parseArchiveService.parseImageFolder(param.folderPath ?? ''); - result.fold( - onSuccess: (data) { - state = AsyncData( - ParseImageFolderState(folderName: folderName, imagePaths: data), - ); - }, - onError: (error) { - state = AsyncError(error.message, StackTrace.current); - }, + if (result.isError) { + throw Exception(result.error?.message); + } + + return ParseImageFolderState( + folderName: folderName, + imagePaths: result.data!, ); } } @@ -129,17 +137,27 @@ class ParseImageFolderSaveBook extends _$ParseImageFolderSaveBook { if (state.isLoading || submitParam.imagePaths.isEmpty) return; state = const AsyncLoading(); - ref.read(parseImageFolderSaveBookProgressProvider(param).notifier).state = - ParseImageFolderSaveBookProgress( - current: 0, - total: submitParam.imagePaths.length, - ); + ref + .read(parseImageFolderSaveBookProgressProvider(param).notifier) + .state = ParseImageFolderSaveBookProgress( + step: SaveStep.generateCover, + current: 0, + total: submitParam.imagePaths.length, + ); final result = await _bookRepository.saveAsBook( - SaveAsBookDto(title: submitParam.folderName, paths: submitParam.imagePaths), - onProgress: (current, total) { - ref.read(parseImageFolderSaveBookProgressProvider(param).notifier).state = - ParseImageFolderSaveBookProgress(current: current, total: total); + SaveAsBookDto( + title: submitParam.folderName, + paths: submitParam.imagePaths, + ), + onStepProgress: (step, current, total) { + ref + .read(parseImageFolderSaveBookProgressProvider(param).notifier) + .state = ParseImageFolderSaveBookProgress( + step: step, + current: current, + total: total, + ); }, ); @@ -153,4 +171,3 @@ class ParseImageFolderSaveBook extends _$ParseImageFolderSaveBook { ); } } - diff --git a/lib/feature/parse/ui/provider/parse_image_folder_provider.freezed.dart b/lib/feature/parse/ui/provider/parse_image_folder_provider.freezed.dart index 2f42216..f2b0893 100644 --- a/lib/feature/parse/ui/provider/parse_image_folder_provider.freezed.dart +++ b/lib/feature/parse/ui/provider/parse_image_folder_provider.freezed.dart @@ -814,7 +814,7 @@ as List, /// @nodoc mixin _$ParseImageFolderSaveBookProgress { - int get current; int get total; + SaveStep get step; int get current; int get total; /// Create a copy of ParseImageFolderSaveBookProgress /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -825,16 +825,16 @@ $ParseImageFolderSaveBookProgressCopyWith get @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseImageFolderSaveBookProgress&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParseImageFolderSaveBookProgress&&(identical(other.step, step) || other.step == step)&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); } @override -int get hashCode => Object.hash(runtimeType,current,total); +int get hashCode => Object.hash(runtimeType,step,current,total); @override String toString() { - return 'ParseImageFolderSaveBookProgress(current: $current, total: $total)'; + return 'ParseImageFolderSaveBookProgress(step: $step, current: $current, total: $total)'; } @@ -845,7 +845,7 @@ abstract mixin class $ParseImageFolderSaveBookProgressCopyWith<$Res> { factory $ParseImageFolderSaveBookProgressCopyWith(ParseImageFolderSaveBookProgress value, $Res Function(ParseImageFolderSaveBookProgress) _then) = _$ParseImageFolderSaveBookProgressCopyWithImpl; @useResult $Res call({ - int current, int total + SaveStep step, int current, int total }); @@ -862,9 +862,10 @@ class _$ParseImageFolderSaveBookProgressCopyWithImpl<$Res> /// Create a copy of ParseImageFolderSaveBookProgress /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? current = null,Object? total = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? step = null,Object? current = null,Object? total = null,}) { return _then(_self.copyWith( -current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable +step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable +as SaveStep,current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable as int, )); @@ -951,10 +952,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( int current, int total)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( SaveStep step, int current, int total)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _ParseImageFolderSaveBookProgress() when $default != null: -return $default(_that.current,_that.total);case _: +return $default(_that.step,_that.current,_that.total);case _: return orElse(); } @@ -972,10 +973,10 @@ return $default(_that.current,_that.total);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( int current, int total) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( SaveStep step, int current, int total) $default,) {final _that = this; switch (_that) { case _ParseImageFolderSaveBookProgress(): -return $default(_that.current,_that.total);case _: +return $default(_that.step,_that.current,_that.total);case _: throw StateError('Unexpected subclass'); } @@ -992,10 +993,10 @@ return $default(_that.current,_that.total);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( int current, int total)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( SaveStep step, int current, int total)? $default,) {final _that = this; switch (_that) { case _ParseImageFolderSaveBookProgress() when $default != null: -return $default(_that.current,_that.total);case _: +return $default(_that.step,_that.current,_that.total);case _: return null; } @@ -1006,10 +1007,11 @@ return $default(_that.current,_that.total);case _: /// @nodoc -class _ParseImageFolderSaveBookProgress implements ParseImageFolderSaveBookProgress { - const _ParseImageFolderSaveBookProgress({this.current = 0, this.total = 0}); +class _ParseImageFolderSaveBookProgress extends ParseImageFolderSaveBookProgress { + const _ParseImageFolderSaveBookProgress({this.step = SaveStep.generateCover, this.current = 0, this.total = 0}): super._(); +@override@JsonKey() final SaveStep step; @override@JsonKey() final int current; @override@JsonKey() final int total; @@ -1023,16 +1025,16 @@ _$ParseImageFolderSaveBookProgressCopyWith<_ParseImageFolderSaveBookProgress> ge @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseImageFolderSaveBookProgress&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ParseImageFolderSaveBookProgress&&(identical(other.step, step) || other.step == step)&&(identical(other.current, current) || other.current == current)&&(identical(other.total, total) || other.total == total)); } @override -int get hashCode => Object.hash(runtimeType,current,total); +int get hashCode => Object.hash(runtimeType,step,current,total); @override String toString() { - return 'ParseImageFolderSaveBookProgress(current: $current, total: $total)'; + return 'ParseImageFolderSaveBookProgress(step: $step, current: $current, total: $total)'; } @@ -1043,7 +1045,7 @@ abstract mixin class _$ParseImageFolderSaveBookProgressCopyWith<$Res> implements factory _$ParseImageFolderSaveBookProgressCopyWith(_ParseImageFolderSaveBookProgress value, $Res Function(_ParseImageFolderSaveBookProgress) _then) = __$ParseImageFolderSaveBookProgressCopyWithImpl; @override @useResult $Res call({ - int current, int total + SaveStep step, int current, int total }); @@ -1060,9 +1062,10 @@ class __$ParseImageFolderSaveBookProgressCopyWithImpl<$Res> /// Create a copy of ParseImageFolderSaveBookProgress /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? current = null,Object? total = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? step = null,Object? current = null,Object? total = null,}) { return _then(_ParseImageFolderSaveBookProgress( -current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable +step: null == step ? _self.step : step // ignore: cast_nullable_to_non_nullable +as SaveStep,current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable as int,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable as int, )); diff --git a/lib/feature/parse/ui/provider/parse_image_folder_provider.g.dart b/lib/feature/parse/ui/provider/parse_image_folder_provider.g.dart index 8f61810..67b893c 100644 --- a/lib/feature/parse/ui/provider/parse_image_folder_provider.g.dart +++ b/lib/feature/parse/ui/provider/parse_image_folder_provider.g.dart @@ -50,7 +50,7 @@ final class ParseImageFolderProvider } } -String _$parseImageFolderHash() => r'dcffb0f2ea9ae5610fe4b0de8fdb4a6e8c25e1dc'; +String _$parseImageFolderHash() => r'c8d88c1656f7fc63a7b09d86de108d637ef3e6bf'; final class ParseImageFolderFamily extends $Family with @@ -147,7 +147,7 @@ final class ParseImageFolderSaveBookProvider } String _$parseImageFolderSaveBookHash() => - r'c95340ec81291d91a439c61940e0a656dfa47dae'; + r'df6a4025c5d290269667cfd4e0ffb453ec0e4c1a'; final class ParseImageFolderSaveBookFamily extends $Family with diff --git a/lib/feature/parse/ui/provider/parse_pdf_provider.dart b/lib/feature/parse/ui/provider/parse_pdf_provider.dart index 8037075..74cae94 100644 --- a/lib/feature/parse/ui/provider/parse_pdf_provider.dart +++ b/lib/feature/parse/ui/provider/parse_pdf_provider.dart @@ -54,6 +54,7 @@ class ParsePdf extends _$ParsePdf { ref.read(parsePdfProgressProvider(pdfPath).notifier).state = (0, 0); final hasPermission = await _requestStoragePermission(); + if (!ref.mounted) return; if (!hasPermission) { state = AsyncValue.error('需要存储权限才能解析 PDF', StackTrace.current); return; @@ -63,12 +64,14 @@ class ParsePdf extends _$ParsePdf { final result = await _service.parsePdf( pdfPath, onProgress: (current, total) { + if (!ref.mounted) return; ref.read(parsePdfProgressProvider(pdfPath).notifier).state = ( current, total, ); }, ); + if (!ref.mounted) return; result.fold( onSuccess: (data) { state = AsyncValue.data( @@ -83,22 +86,34 @@ class ParsePdf extends _$ParsePdf { } final parsePdfSaveBookProgressProvider = - StateProvider<(int current, int total)>((_) => (0, 0)); + StateProvider<(SaveStep step, int current, int total)>( + (_) => (SaveStep.generateCover, 0, 0), + ); + +String saveBookStepText((SaveStep step, int current, int total) progress) { + final (step, current, total) = progress; + return switch (step) { + SaveStep.generateCover => '生成封面图...', + SaveStep.generatePreview => '生成预览图... ($current/$total)', + SaveStep.saveOriginal => '保存原图... ($current/$total)', + SaveStep.saveDatabase => '保存中...', + }; +} @riverpod class ParsePdfSaveBook extends _$ParsePdfSaveBook { BookRepository get _repository => ref.read(bookRepositoryProvider); @override - FutureOr build() async { - return null; - } + FutureOr build() => null; + Future onSave(List tempPaths, String pdfName) async { DKLog.i('开始保存解析结果为书籍,tempPaths: $tempPaths, pdfName: $pdfName'); state = AsyncValue.loading(); ref.read(parsePdfSaveBookProgressProvider.notifier).state = ( + SaveStep.generateCover, 0, tempPaths.length, ); @@ -106,8 +121,9 @@ class ParsePdfSaveBook extends _$ParsePdfSaveBook { DKLog.i('调用 repository.saveAsBook,开始文件复制和 DB 写入'); final result = await _repository.saveAsBook( SaveAsBookDto(title: pdfName, paths: tempPaths), - onProgress: (current, total) { + onStepProgress: (step, current, total) { ref.read(parsePdfSaveBookProgressProvider.notifier).state = ( + step, current, total, ); diff --git a/lib/feature/parse/ui/provider/parse_pdf_provider.g.dart b/lib/feature/parse/ui/provider/parse_pdf_provider.g.dart index d8c703c..14ffadb 100644 --- a/lib/feature/parse/ui/provider/parse_pdf_provider.g.dart +++ b/lib/feature/parse/ui/provider/parse_pdf_provider.g.dart @@ -50,7 +50,7 @@ final class ParsePdfProvider } } -String _$parsePdfHash() => r'6b20494d7df97ff721a555fc1d5c8dee0b99d85f'; +String _$parsePdfHash() => r'e0b05363d6c097e4b8c3141968eb773d58e94b26'; final class ParsePdfFamily extends $Family with @@ -122,7 +122,7 @@ final class ParsePdfSaveBookProvider ParsePdfSaveBook create() => ParsePdfSaveBook(); } -String _$parsePdfSaveBookHash() => r'7851b1d22cd47f9be7cf7e6ed5d5bbda7f8c8c8c'; +String _$parsePdfSaveBookHash() => r'2bb199c158dc082cd536e8a9bc71eb25994461fc'; abstract class _$ParsePdfSaveBook extends $AsyncNotifier { FutureOr build(); diff --git a/lib/feature/parse/ui/provider/parse_web_provider.dart b/lib/feature/parse/ui/provider/parse_web_provider.dart index 1fba543..dad98eb 100644 --- a/lib/feature/parse/ui/provider/parse_web_provider.dart +++ b/lib/feature/parse/ui/provider/parse_web_provider.dart @@ -23,6 +23,8 @@ class ParseWeb extends _$ParseWeb { DownloadService get _downloadService => ref.read(downloadServiceProvider); + bool get isInit => _webViewController != null; + InAppWebViewController? _webViewController; @override @@ -32,6 +34,7 @@ class ParseWeb extends _$ParseWeb { } Future _initialize(String url) async { + if (!ref.mounted) return; state = state.copyWith(progress: 0); await Future.delayed(Duration.zero); } @@ -58,6 +61,20 @@ class ParseWeb extends _$ParseWeb { state = state.copyWith(urls: urls, progress: progress); } + Future parseWeb() async { + if (_webViewController == null) { + return; + } + + final urls = await _parseWebService.extractImagesFromWebView( + onExtractImages: (js) async { + final result = await _webViewController!.evaluateJavascript(source: js); + return result?.toString(); + }, + ); + state = state.copyWith(urls: urls); + } + Future startDownload() async { final current = state; if (current.urls.isEmpty) return; diff --git a/lib/feature/parse/ui/provider/parse_web_provider.g.dart b/lib/feature/parse/ui/provider/parse_web_provider.g.dart index c54c0bc..373ce14 100644 --- a/lib/feature/parse/ui/provider/parse_web_provider.g.dart +++ b/lib/feature/parse/ui/provider/parse_web_provider.g.dart @@ -58,7 +58,7 @@ final class ParseWebProvider } } -String _$parseWebHash() => r'676e20c1d434b2780a7a4e71d5c43ac9f6929b28'; +String _$parseWebHash() => r'3f70fc70a1b6dbff9de825ecf6dd0f8d271c1a9a'; final class ParseWebFamily extends $Family with diff --git a/lib/feature/parse/ui/view/parse_archive_view.dart b/lib/feature/parse/ui/view/parse_archive_view.dart index 230243a..bcc3298 100644 --- a/lib/feature/parse/ui/view/parse_archive_view.dart +++ b/lib/feature/parse/ui/view/parse_archive_view.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:forui/forui.dart'; import 'package:go_router/go_router.dart'; import 'package:tele_book/common/widget/error_widget.dart'; import 'package:tele_book/core/route/app_route.dart'; @@ -23,14 +24,15 @@ class ParseArchiveView extends ConsumerWidget { final saveProgress = ref.watch(parseArchiveSaveBookProgressProvider); ref.listen(parseArchiveSaveBookProvider, (previous, next) { + if (previous == null) return; if (next.hasError) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('保存失败:${next.error}'))); - } else if (previous?.isLoading == true && next.hasValue) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('保存成功'))); + showFToast( + context: context, + title: Text("保存失败"), + description: Text(next.error.toString()), + ); + } else if (previous.isLoading && next.hasValue) { + showFToast(context: context, title: Text("保存成功")); context.go(AppRoute.main); } }); @@ -69,14 +71,17 @@ class _ParseArchiveContent extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('解析压缩包')), - body: asyncState.when( + return FScaffold( + header: FHeader.nested( + title: const Text('解析压缩包'), + prefixes: [FHeaderAction.back(onPress: () => context.pop())], + ), + child: asyncState.when( loading: () => Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ - const CircularProgressIndicator(), + const FCircularProgress(size: .xl), const SizedBox(height: 12), Text('正在解析 ${parseProgress.$1}/${parseProgress.$2}'), ], @@ -90,32 +95,81 @@ class _ParseArchiveContent extends StatelessWidget { ), data: (state) => state.tempPaths.isEmpty ? const Center(child: Text('未解析到图片')) - : GridView.builder( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - mainAxisSpacing: 4, - crossAxisSpacing: 4, - ), - itemBuilder: (context, index) { - final image = state.tempPaths[index]; - return Image.file(File(image), fit: BoxFit.cover); - }, - itemCount: state.tempPaths.length, - ), - ), - bottomNavigationBar: asyncState.value?.tempPaths.isNotEmpty == true - ? Padding( - padding: EdgeInsets.all(16), - child: FilledButton( - onPressed: saveState.isLoading - ? null - : () => onSave(asyncState.value!), - child: saveState.isLoading - ? Text('保存中 ${saveProgress.current}/${saveProgress.total}') - : const Text('保存到书架'), + : Column( + children: [ + Expanded( + child: GridView.builder( + gridDelegate: + const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 150, + mainAxisExtent: 200, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + ), + itemBuilder: (context, index) { + final image = state.tempPaths[index]; + return ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Stack( + fit: StackFit.expand, + children: [ + Image.file( + File(image), + fit: BoxFit.cover, + cacheWidth: 300, + errorBuilder: (_, __, ___) => Container( + color: Colors.grey[200], + child: Icon( + Icons.broken_image, + color: Colors.grey[400], + ), + ), + ), + // 页码角标 + Positioned( + bottom: 4, + right: 4, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.6), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + '${index + 1}', + style: const TextStyle( + color: Colors.white, + fontSize: 11, + ), + ), + ), + ), + ], + ), + ); + }, + itemCount: state.tempPaths.length, + ), + ), + asyncState.value?.tempPaths.isNotEmpty == true + ? Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: FButton( + onPress: saveState.isLoading + ? null + : () => onSave(asyncState.value!), + child: saveState.isLoading + ? Text(saveProgress.stepText) + : const Text('保存到书架'), + ), + ) + : SizedBox.shrink(), + ], ), - ) - : null, + ), ); } } diff --git a/lib/feature/parse/ui/view/parse_batch_archive_view.dart b/lib/feature/parse/ui/view/parse_batch_archive_view.dart index e613009..709334c 100644 --- a/lib/feature/parse/ui/view/parse_batch_archive_view.dart +++ b/lib/feature/parse/ui/view/parse_batch_archive_view.dart @@ -3,14 +3,16 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:forui/forui.dart'; import 'package:go_router/go_router.dart'; import 'package:tele_book/common/widget/error_widget.dart'; +import 'package:tele_book/common/widget/f_sheet_content.dart'; +import 'package:tele_book/common/widget/f_text.dart'; import 'package:tele_book/common/widget/local_image_widget.dart'; import 'package:tele_book/core/route/app_route.dart'; -import 'package:tele_book/feature/parse/model/parse_batch_archive_vo.dart'; import 'package:tele_book/feature/parse/ui/provider/parse_batch_archive_provider.dart'; -class ParseBatchArchiveView extends ConsumerWidget { +class ParseBatchArchiveView extends ConsumerStatefulWidget { final String? archiveDirPath; final List? archivePaths; @@ -21,26 +23,34 @@ class ParseBatchArchiveView extends ConsumerWidget { }); @override - Widget build(BuildContext context, WidgetRef ref) { + ConsumerState createState() => + _ParseBatchArchiveViewState(); +} + +class _ParseBatchArchiveViewState extends ConsumerState { + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { final provider = parseBatchArchiveProvider( ParseBatchArchiveParam( - archiveDirPath: archiveDirPath, - archivePaths: archivePaths, + archiveDirPath: widget.archiveDirPath, + archivePaths: widget.archivePaths, ), ); final asyncState = ref.watch(provider); final parseProgress = ref.watch(parseBatchArchiveProgressProvider); - final saveState = ref.watch(parseBatchArchiveSaveBookProvider); - final saveProgress = ref.watch(parseBatchArchiveSaveBookProgressProvider); - final saveNotifier = ref.watch(parseBatchArchiveSaveBookProvider.notifier); - ref.listen(parseBatchArchiveSaveBookProvider, (previous, next) { + if (previous == null) return; if (next.hasError) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('保存失败:${next.error}'))); - } else if (previous?.isLoading == true && next.hasValue) { + } else if (previous.isLoading && next.hasValue) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('保存成功'))); @@ -48,48 +58,28 @@ class ParseBatchArchiveView extends ConsumerWidget { } }); - return _ParseBatchArchiveContentView( - asyncState: asyncState, - parseProgress: parseProgress, - saveState: saveState, - saveProgress: saveProgress, - onSave: (data) => saveNotifier.saveBatchAsBook(data), - ); - } -} - -class _ParseBatchArchiveContentView extends StatelessWidget { - final AsyncValue asyncState; - final ParseBatchArchiveProgress parseProgress; - final AsyncValue saveState; - final ParseBatchArchiveSaveBookProgress saveProgress; - final void Function(List data) onSave; - - const _ParseBatchArchiveContentView({ - required this.asyncState, - required this.parseProgress, - required this.saveState, - required this.saveProgress, - required this.onSave, - }); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('批量解析'), elevation: 0), - body: asyncState.when( + return FScaffold( + header: FHeader.nested( + title: const Text('批量解析'), + prefixes: [FHeaderAction.back(onPress: () => context.pop())], + ), + child: asyncState.when( loading: () => Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - const CircularProgressIndicator(), - const SizedBox(height: 8), - Text('正���处理:${parseProgress.completeCount}/${parseProgress.totalCount}'), + FText.title( + context, + '正在处理:${parseProgress.completeCount}/${parseProgress.totalCount}', + ), if (parseProgress.currentFileName.isNotEmpty) - Text('当前文件:${parseProgress.currentFileName}'), + FText.subTitle( + context, + '当前文件:${parseProgress.currentFileName}', + ), if (parseProgress.currentFileProgressText.isNotEmpty) - Text(parseProgress.currentFileProgressText), + FText.subTitle(context, parseProgress.currentFileProgressText), ], ), ), @@ -104,81 +94,63 @@ class _ParseBatchArchiveContentView extends StatelessWidget { if (data.isEmpty) { return const Center(child: Text('暂无可解析内容')); } - return ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: data.length, - itemBuilder: (context, index) { - final archive = data[index]; - return Row( - children: [ - LocalImageWidget(imagePath: archive.tempPaths.first), - Expanded( - child: ListTile( - title: Text(archive.name), - subtitle: Text('图片数: ${archive.tempPaths.length}'), - onTap: () { - _buildArchiveImageList(context, archive.tempPaths); - }, - ), + + return Builder( + builder: (innerContext) => FItemGroup( + children: [ + for (var archive in data) + .item( + title: Text(archive.name), + subtitle: Text('图片数: ${archive.tempPaths.length}'), + prefix: LocalImageWidget( + imagePath: archive.tempPaths.first, ), - ], - ); - }, - ); + suffix: const Icon(FLucideIcons.chevronRight), + onPress: () { + _buildImageList( + innerContext, + archive.name, + archive.tempPaths, + ); + }, + ), + ], + ), + ); }, ), - bottomNavigationBar: - asyncState.value?.parseBatchArchiveList.isNotEmpty == true - ? Padding( - padding: const EdgeInsets.all(16), - child: FilledButton( - onPressed: saveState.isLoading - ? null - : () => onSave(asyncState.value!.parseBatchArchiveList), - child: saveState.isLoading - ? Text('正在保存:${saveProgress.current}/${saveProgress.total}') - : const Text('保存到书架'), - ), - ) - : null, ); } - void _buildArchiveImageList(BuildContext context, List imageList) { - showModalBottomSheet( + void _buildImageList( + BuildContext context, + String title, + List imageList, + ) { + showFSheet( context: context, - isScrollControlled: true, // 允许高度超过半屏 - useSafeArea: true, - builder: (context) { - return DraggableScrollableSheet( - initialChildSize: 0.7, - minChildSize: 0.4, - maxChildSize: 1.0, - expand: false, - builder: (context, scrollController) { - return Column( + side: .btt, + mainAxisMaxRatio: null, + builder: (context) => DraggableScrollableSheet( + expand: false, + maxChildSize: 0.8, + builder: (context, scrollController) => ScrollConfiguration( + behavior: ScrollConfiguration.of( + context, + ).copyWith(dragDevices: {.touch, .mouse, .trackpad}), + child: FSheetContent( + side: .btt, + child: Column( + crossAxisAlignment: .start, + mainAxisSize: .min, children: [ - // 拖动把手 - Container( - margin: const EdgeInsets.symmetric(vertical: 8), - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.grey[400], - borderRadius: BorderRadius.circular(2), - ), - ), - Text( - "包含图片(${imageList.length})", - style: Theme.of(context).textTheme.titleMedium, - ), + FSheetContent.title(context, title), + FSheetContent.subTitle(context, "包含图片(${imageList.length})"), const SizedBox(height: 8), - Expanded( child: MasonryGridView.count( controller: scrollController, crossAxisCount: 3, - // 列数 mainAxisSpacing: 4, crossAxisSpacing: 4, padding: const EdgeInsets.symmetric( @@ -188,19 +160,82 @@ class _ParseBatchArchiveContentView extends StatelessWidget { itemCount: imageList.length, itemBuilder: (context, index) { final path = imageList[index]; - // Image.file 会根据图片真实宽高自适应,形成瀑布流效果 return ClipRRect( - borderRadius: BorderRadius.circular(4), - child: Image.file(File(path), fit: BoxFit.cover), + borderRadius: BorderRadius.circular(8), + child: Stack( + children: [ + Image.file( + File(path), + fit: BoxFit.cover, + cacheWidth: 300, + ), + Positioned( + bottom: 4, + right: 4, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.6), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + '${index + 1}', + style: const TextStyle( + color: Colors.white, + fontSize: 11, + ), + ), + ), + ), + ], + ), ); }, ), ), + Consumer( + builder: (context, ref, _) { + final provider = parseBatchArchiveProvider( + ParseBatchArchiveParam( + archiveDirPath: widget.archiveDirPath, + archivePaths: widget.archivePaths, + ), + ); + final asyncState = ref.watch(provider); + final saveState = ref.watch( + parseBatchArchiveSaveBookProvider, + ); + final saveProgress = ref.watch( + parseBatchArchiveSaveBookProgressProvider, + ); + final saveNotifier = ref.watch( + parseBatchArchiveSaveBookProvider.notifier, + ); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: FButton( + onPress: saveState.isLoading + ? null + : () => saveNotifier.saveBatchAsBook( + asyncState.value!.parseBatchArchiveList, + ), + child: saveState.isLoading + ? Text( + '正在保存:${saveProgress.current}/${saveProgress.total}', + ) + : const Text('保存到书架'), + ), + ); + }, + ), ], - ); - }, - ); - }, + ), + ), + ), + ), ); } } diff --git a/lib/feature/parse/ui/view/parse_batch_image_folder_view.dart b/lib/feature/parse/ui/view/parse_batch_image_folder_view.dart index 2b0d051..5aa1787 100644 --- a/lib/feature/parse/ui/view/parse_batch_image_folder_view.dart +++ b/lib/feature/parse/ui/view/parse_batch_image_folder_view.dart @@ -1,15 +1,18 @@ import 'dart:io'; +import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:forui/forui.dart'; import 'package:go_router/go_router.dart'; import 'package:tele_book/common/widget/error_widget.dart'; +import 'package:tele_book/common/widget/f_sheet_content.dart'; import 'package:tele_book/common/widget/local_image_widget.dart'; import 'package:tele_book/core/route/app_route.dart'; import 'package:tele_book/feature/parse/ui/provider/parse_batch_image_folder.dart'; -class ParseBatchImageFolderView extends ConsumerWidget { +class ParseBatchImageFolderView extends ConsumerStatefulWidget { final String? parentDirPath; final List? imagePaths; @@ -20,10 +23,22 @@ class ParseBatchImageFolderView extends ConsumerWidget { }); @override - Widget build(BuildContext context, WidgetRef ref) { + ConsumerState createState() => + _ParseBatchImageFolderViewState(); +} + +class _ParseBatchImageFolderViewState + extends ConsumerState { + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { final param = ParseBatchImageFolderParam( - parentDirPath: parentDirPath, - imagePaths: imagePaths, + parentDirPath: widget.parentDirPath, + imagePaths: widget.imagePaths, ); final parseProvider = parseBatchImageFolderProvider(param); @@ -37,23 +52,34 @@ class ParseBatchImageFolderView extends ConsumerWidget { previous, next, ) { + if (previous == null) return; if (next.hasError) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text("保存失败:${next.error}"))); - } else if (previous?.isLoading == true && + showFToast( + context: context, + title: Text("保存失败"), + description: Text(e.toString()), + ); + } else if (previous.isLoading && next.isLoading == false && next.error == null) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text("保存成功!"))); + showFToast(context: context, title: Text("保存成功!")); context.go(AppRoute.main); } }); - return Scaffold( - appBar: AppBar(title: const Text('批量解析文件夹'), elevation: 0), - body: parseAsync.when( + return FScaffold( + header: FHeader.nested( + title: const Text('批量解析文件夹'), + prefixes: [FHeaderAction.back(onPress: () => context.pop())], + suffixes: [ + if (parseAsync.value?.isParsing == false) + FHeaderAction( + icon: const Icon(FLucideIcons.refreshCw), + onPress: () => parseNotifier.refresh(), + ), + ], + ), + child: parseAsync.when( error: (error, stack) => Center( child: CustomErrorWidget( errorMessage: error.toString(), @@ -68,7 +94,7 @@ class ParseBatchImageFolderView extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - const CircularProgressIndicator(), + const FCircularProgress(size: .xl), const SizedBox(height: 8), Text("正在处理:${current.completeCount}/${current.totalCount}"), if (current.currentFileName.isNotEmpty) @@ -77,19 +103,19 @@ class ParseBatchImageFolderView extends ConsumerWidget { ), ); } - return const Center(child: CircularProgressIndicator()); + return const Center(child: FCircularProgress(size: .xl)); }, data: (state) { if (state.isParsing) { if (state.totalCount == 0 && state.completeCount == 0) { - return const Center(child: CircularProgressIndicator()); + return const Center(child: FCircularProgress(size: .xl)); } return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - const CircularProgressIndicator(), + const FCircularProgress(size: .xl), const SizedBox(height: 8), Text("正在处理:${state.completeCount}/${state.totalCount}"), if (state.currentFileName.isNotEmpty) @@ -105,85 +131,78 @@ class ParseBatchImageFolderView extends ConsumerWidget { return const Center(child: Text('未解析到可用图片文件夹')); } - return ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: state.parseBatchFolderList.length, - itemBuilder: (context, index) { - final folder = state.parseBatchFolderList[index]; - return Row( - children: [ - LocalImageWidget(imagePath: folder.tempPaths.first), - Expanded( - child: ListTile( - title: Text(folder.name), - subtitle: Text("图片数: ${folder.tempPaths.length}"), - onTap: () { - _buildImageList(context, folder.tempPaths); - }, - ), - ), - ], - ); - }, + return Column( + children: [ + Expanded( + child: FItemGroup( + children: [ + for (var folder in state.parseBatchFolderList) + .item( + title: Text(folder.name), + subtitle: Text("图片数: ${folder.tempPaths.length}"), + prefix: LocalImageWidget( + imagePath: folder.tempPaths.first, + ), + suffix: const Icon(FLucideIcons.chevronRight), + onPress: () { + _buildImageList( + context, + folder.name, + folder.tempPaths, + ); + }, + ), + ], + ), + ), + + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: FButton( + onPress: saveState.submitState.isLoading + ? null + : () => saveNotifier.submit( + parseAsync.value!.parseBatchFolderList, + ), + child: saveState.submitState.isLoading + ? Text( + "正在保存:${saveState.saveAsBookCount}/${saveState.totalCount}", + ) + : const Text("保存到书架"), + ), + ), + ], ); }, ), - bottomNavigationBar: - parseAsync.value?.parseBatchFolderList.isNotEmpty == true - ? Padding( - padding: const EdgeInsets.all(16), - child: FilledButton( - onPressed: saveState.submitState.isLoading - ? null - : () => saveNotifier.submit( - parseAsync.value!.parseBatchFolderList, - ), - child: saveState.submitState.isLoading - ? Text( - "正在保存:${saveState.saveAsBookCount}/${saveState.totalCount}", - ) - : const Text("保存到书架"), - ), - ) - : null, - floatingActionButton: parseAsync.value?.isParsing == false - ? FloatingActionButton( - onPressed: () { - parseNotifier.refresh(); - }, - child: const Icon(Icons.refresh), - ) - : null, ); } - void _buildImageList(BuildContext context, List imageList) { - showModalBottomSheet( + void _buildImageList( + BuildContext context, + String title, + List imageList, + ) { + showFSheet( context: context, - isScrollControlled: true, - useSafeArea: true, - builder: (context) { - return DraggableScrollableSheet( - initialChildSize: 0.7, - minChildSize: 0.4, - maxChildSize: 1.0, - expand: false, - builder: (context, scrollController) { - return Column( + side: .btt, + mainAxisMaxRatio: null, + builder: (context) => DraggableScrollableSheet( + expand: false, + maxChildSize: 0.8, + builder: (context, scrollController) => ScrollConfiguration( + behavior: ScrollConfiguration.of( + context, + ).copyWith(dragDevices: {.touch, .mouse, .trackpad}), + child: FSheetContent( + side: .btt, + child: Column( + mainAxisSize: .min, + crossAxisAlignment: .start, children: [ - Container( - margin: const EdgeInsets.symmetric(vertical: 8), - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.grey[400], - borderRadius: BorderRadius.circular(2), - ), - ), - Text( - "包含图片(${imageList.length})", - style: Theme.of(context).textTheme.titleMedium, - ), + FSheetContent.title(context, title), + FSheetContent.subTitle(context, "包含图片(${imageList.length})"), + const SizedBox(height: 8), Expanded( child: MasonryGridView.count( @@ -199,17 +218,46 @@ class ParseBatchImageFolderView extends ConsumerWidget { itemBuilder: (context, index) { final path = imageList[index]; return ClipRRect( - borderRadius: BorderRadius.circular(4), - child: Image.file(File(path), fit: BoxFit.cover), + borderRadius: BorderRadius.circular(8), + child: Stack( + children: [ + Image.file( + File(path), + fit: BoxFit.cover, + cacheWidth: 300, + ), + Positioned( + bottom: 4, + right: 4, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.6), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + '${index + 1}', + style: const TextStyle( + color: Colors.white, + fontSize: 11, + ), + ), + ), + ), + ], + ), ); }, ), ), ], - ); - }, - ); - }, + ), + ), + ), + ), ); } } diff --git a/lib/feature/parse/ui/view/parse_batch_pdf_view.dart b/lib/feature/parse/ui/view/parse_batch_pdf_view.dart index 77602da..8745607 100644 --- a/lib/feature/parse/ui/view/parse_batch_pdf_view.dart +++ b/lib/feature/parse/ui/view/parse_batch_pdf_view.dart @@ -3,24 +3,46 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:forui/forui.dart'; +import 'package:go_router/go_router.dart'; +import 'package:tele_book/common/widget/f_sheet_content.dart'; import 'package:tele_book/common/widget/local_image_widget.dart'; import 'package:tele_book/feature/parse/model/parse_batch_archive_vo.dart'; import 'package:tele_book/feature/parse/ui/provider/parse_batch_pdf_provider.dart'; -class ParseBatchPdfView extends ConsumerWidget { +import '../../../../core/route/app_route.dart'; + +class ParseBatchPdfView extends ConsumerStatefulWidget { final String? pdfDirPath; final List? pdfPaths; const ParseBatchPdfView({super.key, this.pdfDirPath, this.pdfPaths}); @override - Widget build(BuildContext context, WidgetRef ref) { - final param = ParseBatchPdfParam( - pdfDirPath: pdfDirPath ?? '', - pdfPaths: pdfPaths ?? [], + ConsumerState createState() => _ParseBatchPdfViewState(); +} + +class _ParseBatchPdfViewState extends ConsumerState { + late final ParseBatchPdfParam _param; + + @override + void initState() { + super.initState(); + _param = ParseBatchPdfParam( + pdfDirPath: widget.pdfDirPath ?? '', + pdfPaths: widget.pdfPaths, ); + } + + + @override + void dispose() { + super.dispose(); + } - final asyncState = ref.watch(parseBatchPdfProvider(param)); + @override + Widget build(BuildContext context) { + final asyncState = ref.watch(parseBatchPdfProvider(_param)); final parseProgress = ref.watch(parseBatchProgressProvider); final saveBatchBookState = ref.watch(parseBatchPdfSaveBookProvider); @@ -29,109 +51,161 @@ class ParseBatchPdfView extends ConsumerWidget { ); final saveBookProgress = ref.watch(parseBatchPdfSaveBookProgressProvider); - return Scaffold( - appBar: AppBar(title: const Text('批量解析 PDF'), elevation: 0), - body: asyncState.when( + ref.listen(parseBatchPdfSaveBookProvider, (previous, next) { + if (previous == null) return; + if (next.hasError) { + showFToast( + context: context, + title: Text("保存失败"), + description: Text(next.error.toString()), + ); + } else if (previous.isLoading && + next.isLoading == false && + next.error == null) { + showFToast(context: context, title: Text("保存成功!")); + context.go(AppRoute.main); + } + }); + + return FScaffold( + header: FHeader.nested( + title: const Text('批量解析 PDF'), + prefixes: [FHeaderAction.back(onPress: () => context.pop())], + ), + child: asyncState.when( loading: () => Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const CircularProgressIndicator(), - SizedBox(height: 8), + const FCircularProgress(size: .xl), + const SizedBox(height: 8), Text( '正在处理:${parseProgress.completeCount} / ${parseProgress.totalCount}', ), if (parseProgress.currentFileName.isNotEmpty) Text('当前文件:${parseProgress.currentFileName}'), - Text("当前文件进度:${parseProgress.currentFileProgress}%"), ], ), ), error: (error, stack) => Center(child: Text(error.toString())), - data: (state) => ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: state.parseBatchList.length, - itemBuilder: (context, index) { - final item = state.parseBatchList[index]; - return Row( - children: [ - LocalImageWidget(imagePath: item.tempPaths.first), - Expanded( - child: ListTile( - title: Text(item.name), - subtitle: Text('页数:${item.tempPaths.length}'), - onTap: () => _showPagePreview(context, item), - ), - ), - ], - ); - }, - ), - ), - bottomNavigationBar: asyncState.hasValue - ? Padding( - padding: const EdgeInsets.all(16), - child: FilledButton( - onPressed: saveBatchBookState.isLoading == true - ? null - : () => saveBatchBookNotifier.saveBatchAsBook( - asyncState.value!.parseBatchList, + data: (state) { + if (state.parseBatchList.isEmpty) { + return const Center(child: Text('暂无可解析内容')); + } + + return Column( + children: [ + Expanded( + child: FItemGroup( + children: [ + for (var item in state.parseBatchList) + .item( + title: Text(item.name), + subtitle: Text('页数:${item.tempPaths.length}'), + prefix: LocalImageWidget( + imagePath: item.tempPaths.first, + ), + suffix: const Icon(FLucideIcons.chevronRight), + onPress: () { + _showPagePreview(context, item); + }, ), - child: saveBatchBookState.isLoading == true - ? Text( - '正在保存:${saveBookProgress.current} / ${saveBookProgress.total}', - ) - : const Text('保存到书架'), + ], + ), ), - ) - : null, + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: FButton( + onPress: saveBatchBookState.isLoading == true + ? null + : () => saveBatchBookNotifier.saveBatchAsBook( + asyncState.value!.parseBatchList, + ), + child: saveBatchBookState.isLoading == true + ? Text( + '正在保存:${saveBookProgress.current} / ${saveBookProgress.total}', + ) + : const Text('保存到书架'), + ), + ), + ], + ); + }, + ), ); } void _showPagePreview(BuildContext context, ParseBatchArchiveVo item) { - showModalBottomSheet( + showFSheet( context: context, - isScrollControlled: true, - useSafeArea: true, + side: .btt, + mainAxisMaxRatio: null, builder: (context) => DraggableScrollableSheet( - initialChildSize: 0.7, - minChildSize: 0.4, - maxChildSize: 1.0, expand: false, - builder: (context, scrollController) => Column( - children: [ - Container( - margin: const EdgeInsets.symmetric(vertical: 8), - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.grey[400], - borderRadius: BorderRadius.circular(2), - ), - ), - Text( - '${item.name}(${item.tempPaths.length} 页)', - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 8), - Expanded( - child: MasonryGridView.count( - controller: scrollController, - crossAxisCount: 3, - mainAxisSpacing: 4, - crossAxisSpacing: 4, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - itemCount: item.tempPaths.length, - itemBuilder: (context, index) => ClipRRect( - borderRadius: BorderRadius.circular(4), - child: Image.file( - File(item.tempPaths[index]), - fit: BoxFit.cover, + maxChildSize: 0.8, + builder: (context, scrollController) => ScrollConfiguration( + behavior: ScrollConfiguration.of( + context, + ).copyWith(dragDevices: {.touch, .mouse, .trackpad}), + child: FSheetContent( + side: .btt, + child: Column( + crossAxisAlignment: .start, + children: [ + FSheetContent.title(context, item.name), + FSheetContent.subTitle(context, "${item.tempPaths.length} 页"), + const SizedBox(height: 8), + Expanded( + child: MasonryGridView.count( + controller: scrollController, + crossAxisCount: 3, + mainAxisSpacing: 4, + crossAxisSpacing: 4, + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + itemCount: item.tempPaths.length, + itemBuilder: (context, index) { + return ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Stack( + children: [ + Image.file( + File(item.tempPaths[index]), + fit: BoxFit.cover, + cacheWidth: 300, + ), + Positioned( + bottom: 4, + right: 4, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.6), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + '${index + 1}', + style: const TextStyle( + color: Colors.white, + fontSize: 11, + ), + ), + ), + ), + ], + ), + ); + }, ), ), - ), + ], ), - ], + ), ), ), ); diff --git a/lib/feature/parse/ui/view/parse_form_view.dart b/lib/feature/parse/ui/view/parse_form_view.dart index aba3cd2..d216f7e 100644 --- a/lib/feature/parse/ui/view/parse_form_view.dart +++ b/lib/feature/parse/ui/view/parse_form_view.dart @@ -2,6 +2,8 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:forui/forui.dart'; +import 'package:go_router/go_router.dart'; import 'package:tele_book/feature/parse/ui/provider/parse_form_provider.dart'; class ParseFormView extends ConsumerWidget { @@ -12,83 +14,99 @@ class ParseFormView extends ConsumerWidget { final state = ref.watch(parseFormProvider); final notifier = ref.read(parseFormProvider.notifier); - return Scaffold( - appBar: AppBar(title: const Text("解析表单")), - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - LayoutBuilder( - builder: (context, constraints) { - return DropdownMenu( - width: constraints.maxWidth, - initialSelection: state.type, - decorationBuilder: (context, state) { - return const InputDecoration( - labelText: "选择解析来源", - prefixIcon: Icon(Icons.source), - border: OutlineInputBorder(), - ); - }, - menuStyle: MenuStyle( - padding: WidgetStateProperty.all(EdgeInsets.zero), - ), - dropdownMenuEntries: const [ - DropdownMenuEntry( - value: ParseFormType.web, - label: "网页", - leadingIcon: Icon(Icons.web), - ), - DropdownMenuEntry( - value: ParseFormType.archive, - label: "压缩包", - leadingIcon: Icon(Icons.archive), - ), - DropdownMenuEntry( - value: ParseFormType.batchArchive, - label: "批量压缩包", - leadingIcon: Icon(Icons.batch_prediction), - ), - DropdownMenuEntry( - value: ParseFormType.imageFolder, - label: "文件夹", - leadingIcon: Icon(Icons.photo_library), - ), - DropdownMenuEntry( - value: ParseFormType.batchImageFolder, - label: "批量文件夹", - leadingIcon: Icon(Icons.folder_copy), - ), - DropdownMenuEntry( - value: ParseFormType.pdf, - label: "PDF", - leadingIcon: Icon(Icons.picture_as_pdf), - ), - DropdownMenuEntry( - value: ParseFormType.batchPdf, - label: "批量 PDF", - leadingIcon: Icon(Icons.folder_special), - ), - ], - onSelected: (value) { - notifier.setType(value); - }, - ); + return FScaffold( + header: FHeader.nested( + title: const Text("解析表单"), + prefixes: [ + FHeaderAction.back( + onPress: () { + context.pop(); + }, + ), + ], + ), + child: Column( + mainAxisSize: .min, + crossAxisAlignment: .stretch, + children: [ + Text( + "导入书籍", + style: context.theme.typography.display.xl2.copyWith( + fontWeight: .w600, + color: context.theme.colors.foreground, + height: 1.5, + ), + ), + SizedBox(height: 2), + Text( + "请选择导入方式,然后输入导入网站/文件路径/文件夹路径", + style: context.theme.typography.body.sm.copyWith( + color: context.theme.colors.mutedForeground, + ), + ), + SizedBox(height: 16), + FSelect.rich( + control: FSelectControl.managed( + initial: ParseFormType.web, + onChange: (value) { + notifier.setType(value); }, ), - const SizedBox(height: 16), - _buildSubForm(context, notifier, state.type), - const Spacer(), - FilledButton( - onPressed: () { + hint: "请选择导入方式", + label: Text("导入方式"), + format: (s) => s.description, + children: [ + .item( + value: ParseFormType.web, + title: Text("网页"), + prefix: Icon(Icons.web), + ), + .item( + value: ParseFormType.archive, + title: Text("压缩包"), + prefix: Icon(Icons.archive), + ), + .item( + value: ParseFormType.batchArchive, + title: Text("批量压缩包"), + prefix: Icon(Icons.batch_prediction), + ), + .item( + value: ParseFormType.imageFolder, + title: Text("文件夹"), + prefix: Icon(Icons.photo_library), + ), + .item( + value: ParseFormType.batchImageFolder, + title: Text("批量文件夹"), + prefix: Icon(Icons.folder_copy), + ), + .item( + value: ParseFormType.pdf, + title: Text("PDF"), + prefix: Icon(Icons.picture_as_pdf), + ), + .item( + value: ParseFormType.batchPdf, + title: Text("批量 PDF"), + prefix: Icon(Icons.folder_special), + ), + ], + ), + const SizedBox(height: 16), + _buildSubForm(context, notifier, state.type), + const SizedBox(height: 16), + Spacer(), + Padding( + padding: .symmetric(vertical: 16), + child: FButton( + onPress: () { notifier.onParse(context); }, child: const Text("解析"), ), - const SizedBox(height: 16), - ], - ), + ), + ], ), ); } @@ -117,109 +135,104 @@ class ParseFormView extends ConsumerWidget { } Widget _buildWebForm(BuildContext context, ParseForm notifier) { - return TextField( - controller: notifier.urlController, - onChanged: notifier.onUrlChanged, - decoration: InputDecoration( - labelText: "输入文本", - prefixIcon: const Icon(Icons.web), - suffixIcon: IconButton( - onPressed: notifier.getClipboardUrl, - icon: const Icon(Icons.paste), - ), - border: const OutlineInputBorder(), + return FTextFormField( + label: const Text('URL'), + hint: '请输入URL', + control: FTextFieldControl.managed(controller: notifier.urlController), + suffixBuilder: (context, style, variants) => FButton.icon( + style: style.obscureButtonStyle, + onPress: notifier.getClipboardUrl, + child: const Icon(FLucideIcons.clipboardPaste), ), ); } Widget _buildArchiveForm(BuildContext context, ParseForm notifier) { - return TextField( - controller: notifier.archivePathController, - decoration: InputDecoration( - labelText: "请选择压缩包文件", - prefixIcon: const Icon(Icons.archive), - suffixIcon: IconButton( - onPressed: notifier.pickerArchive, - icon: const Icon(Icons.folder_open), - ), - border: const OutlineInputBorder(), + return FTextFormField( + label: const Text('压缩包文件'), + hint: '请选择压缩包文件', + control: FTextFieldControl.managed( + controller: notifier.archivePathController, + ), + suffixBuilder: (context, style, variants) => FButton.icon( + style: style.obscureButtonStyle, + onPress: notifier.pickerArchive, + child: const Icon(FLucideIcons.folderOpen), ), ); } Widget _buildBatchArchiveForm(BuildContext context, ParseForm notifier) { - return TextField( - controller: notifier.batchArchivePathController, - decoration: InputDecoration( - labelText: Platform.isIOS ? "请选择一个或多个 ZIP 文件" : "请选择压缩包文件夹", - prefixIcon: const Icon(Icons.folder), - suffixIcon: IconButton( - onPressed: notifier.pickerBatchArchive, - icon: const Icon(Icons.folder_open), - ), - border: const OutlineInputBorder(), + return FTextFormField( + label: const Text('批量压缩包'), + hint: Platform.isIOS ? '请选择一个或多个 ZIP 文件' : '请选择压缩包文件夹', + control: FTextFieldControl.managed( + controller: notifier.batchArchivePathController, + ), + suffixBuilder: (context, style, variants) => FButton.icon( + style: style.obscureButtonStyle, + onPress: notifier.pickerBatchArchive, + child: const Icon(FLucideIcons.folderOpen), ), ); } Widget _buildImageFolderForm(BuildContext context, ParseForm notifier) { - return TextField( - controller: notifier.imageFolderPathController, - decoration: InputDecoration( - labelText: Platform.isIOS ? "请选择一个或多个图片文件" : "请选择图片文件夹", - prefixIcon: const Icon(Icons.photo_library), - suffixIcon: IconButton( - onPressed: notifier.pickerImageFolder, - icon: const Icon(Icons.folder_open), - ), - border: const OutlineInputBorder(), + return FTextFormField( + label: const Text('图片路径'), + hint: Platform.isIOS ? '请选择一个或多个图片文件' : '请选择图片文件夹', + control: FTextFieldControl.managed( + controller: notifier.imageFolderPathController, + ), + suffixBuilder: (context, style, variants) => FButton.icon( + style: style.obscureButtonStyle, + onPress: notifier.pickerImageFolder, + child: const Icon(FLucideIcons.folderOpen), ), ); } Widget _buildBatchImageFolderForm(BuildContext context, ParseForm notifier) { - return TextField( - controller: notifier.batchImageFolderPathController, - decoration: InputDecoration( - labelText: Platform.isIOS - ? "请选择多个图片文件(按文件夹分组)" - : "请选择批量图片文件夹父目录", - prefixIcon: const Icon(Icons.folder_copy), - suffixIcon: IconButton( - onPressed: notifier.pickerBatchImageFolder, - icon: const Icon(Icons.folder_open), - ), - border: const OutlineInputBorder(), + return FTextFormField( + label: const Text('批量图片路径'), + hint: Platform.isIOS ? '请选择多个图片文件(按文件夹分组)' : '请选择批量图片文件夹父目录', + control: FTextFieldControl.managed( + controller: notifier.batchImageFolderPathController, + ), + suffixBuilder: (context, style, variants) => FButton.icon( + style: style.obscureButtonStyle, + onPress: notifier.pickerBatchImageFolder, + child: const Icon(FLucideIcons.folderOpen), ), ); } Widget _buildPdfForm(BuildContext context, ParseForm notifier) { - return TextField( - controller: notifier.pdfPathController, - decoration: InputDecoration( - labelText: "请选择 PDF 文件", - prefixIcon: const Icon(Icons.picture_as_pdf), - suffixIcon: IconButton( - onPressed: notifier.pickerPdf, - icon: const Icon(Icons.folder_open), - ), - border: const OutlineInputBorder(), + return FTextFormField( + label: const Text('PDF 文件'), + hint: '请选择 PDF 文件', + control: FTextFieldControl.managed( + controller: notifier.pdfPathController, + ), + suffixBuilder: (context, style, variants) => FButton.icon( + style: style.obscureButtonStyle, + onPress: notifier.pickerPdf, + child: const Icon(FLucideIcons.folderOpen), ), ); } Widget _buildBatchPdfForm(BuildContext context, ParseForm notifier) { - return TextField( - controller: notifier.batchPdfPathController, - decoration: InputDecoration( - labelText: Platform.isIOS ? "请选择一个或多个 PDF 文件" : "请选择包含 PDF 的文件夹", - prefixIcon: const Icon(Icons.folder_special), - suffixIcon: IconButton( - onPressed: notifier.pickerBatchPdf, - icon: const Icon(Icons.folder_open), - ), - border: const OutlineInputBorder(), + return FTextFormField( + label: const Text('批量 PDF'), + hint: Platform.isIOS ? '请选择一个或多个 PDF 文件' : '请选择包含 PDF 的文件夹', + control: FTextFieldControl.managed( + controller: notifier.batchPdfPathController, + ), + suffixBuilder: (context, style, variants) => FButton.icon( + style: style.obscureButtonStyle, + onPress: notifier.pickerBatchPdf, + child: const Icon(FLucideIcons.folderOpen), ), ); } diff --git a/lib/feature/parse/ui/view/parse_image_folder_view.dart b/lib/feature/parse/ui/view/parse_image_folder_view.dart index c6af774..f87f681 100644 --- a/lib/feature/parse/ui/view/parse_image_folder_view.dart +++ b/lib/feature/parse/ui/view/parse_image_folder_view.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:forui/forui.dart'; import 'package:go_router/go_router.dart'; import 'package:tele_book/common/widget/error_widget.dart'; import 'package:tele_book/core/route/app_route.dart'; @@ -11,11 +12,7 @@ class ParseImageFolderView extends ConsumerWidget { final String? folderPath; final List? imagePaths; - const ParseImageFolderView({ - super.key, - this.folderPath, - this.imagePaths, - }); + const ParseImageFolderView({super.key, this.folderPath, this.imagePaths}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -27,18 +24,23 @@ class ParseImageFolderView extends ConsumerWidget { final asyncState = ref.watch(provider); final saveState = ref.watch(parseImageFolderSaveBookProvider(param)); - final saveNotifier = ref.watch(parseImageFolderSaveBookProvider(param).notifier); - final saveProgress = ref.watch(parseImageFolderSaveBookProgressProvider(param)); + final saveNotifier = ref.watch( + parseImageFolderSaveBookProvider(param).notifier, + ); + final saveProgress = ref.watch( + parseImageFolderSaveBookProgressProvider(param), + ); ref.listen(parseImageFolderSaveBookProvider(param), (previous, next) { + if (previous == null) return; if (next.hasError) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('保存失败:${next.error}'))); - } else if (previous?.isLoading == true && next.hasValue) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('保存成功'))); + showFToast( + context: context, + title: Text("保存失败"), + description: Text(next.error.toString()), + ); + } else if (previous.isLoading && next.hasValue) { + showFToast(context: context, title: Text("保存成功")); context.go(AppRoute.main); } }); @@ -74,10 +76,13 @@ class _ParseImageFolderContent extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('解析文件夹')), - body: asyncState.when( - loading: () => const Center(child: CircularProgressIndicator()), + return FScaffold( + header: FHeader.nested( + title: const Text('解析文件夹'), + prefixes: [FHeaderAction.back(onPress: () => context.pop())], + ), + child: asyncState.when( + loading: () => const Center(child: FCircularProgress(size: .xl)), error: (error, stack) => Center( child: CustomErrorWidget( errorMessage: error.toString(), @@ -86,33 +91,80 @@ class _ParseImageFolderContent extends StatelessWidget { ), data: (state) => state.imagePaths.isEmpty ? const Center(child: Text('未解析到图片')) - : GridView.builder( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - mainAxisSpacing: 4, - crossAxisSpacing: 4, - ), - itemBuilder: (context, index) { - final image = state.imagePaths[index]; - return Image.file(File(image), fit: BoxFit.cover); - }, - itemCount: state.imagePaths.length, - ), - ), - bottomNavigationBar: asyncState.value?.imagePaths.isNotEmpty == true - ? Padding( - padding: const EdgeInsets.all(16), - child: FilledButton( - onPressed: saveState.isLoading - ? null - : () => onSave(asyncState.value!), - child: saveState.isLoading - ? Text('保存中 ${saveProgress.current}/${saveProgress.total}') - : const Text("保存到书架"), + : Column( + children: [ + Expanded( + child: GridView.builder( + padding: const EdgeInsets.all(12), + gridDelegate: + const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 150, + mainAxisExtent: 200, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + ), + itemCount: state.imagePaths.length, + itemBuilder: (context, index) { + final image = state.imagePaths[index]; + return ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Stack( + fit: StackFit.expand, + children: [ + Image.file( + File(image), + fit: BoxFit.cover, + cacheWidth: 300, + errorBuilder: (_, __, ___) => Container( + color: Colors.grey[200], + child: Icon( + Icons.broken_image, + color: Colors.grey[400], + ), + ), + ), + // 页码角标 + Positioned( + bottom: 4, + right: 4, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.6), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + '${index + 1}', + style: const TextStyle( + color: Colors.white, + fontSize: 11, + ), + ), + ), + ), + ], + ), + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: FButton( + onPress: saveState.isLoading + ? null + : () => onSave(asyncState.value!), + child: saveState.isLoading + ? Text(saveProgress.stepText) + : const Text("保存到书架"), + ), + ), + ], ), - ) - : null, + ), ); } } - diff --git a/lib/feature/parse/ui/view/parse_pdf_view.dart b/lib/feature/parse/ui/view/parse_pdf_view.dart index 8fd427a..1ac9731 100644 --- a/lib/feature/parse/ui/view/parse_pdf_view.dart +++ b/lib/feature/parse/ui/view/parse_pdf_view.dart @@ -1,8 +1,8 @@ import 'dart:io'; -import 'package:dk_util/dk_util.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:forui/forui.dart'; import 'package:go_router/go_router.dart'; import 'package:tele_book/core/route/app_route.dart'; import 'package:tele_book/feature/parse/ui/provider/parse_pdf_provider.dart'; @@ -21,66 +21,117 @@ class ParsePdfView extends ConsumerWidget { final saveBookState = ref.watch(parsePdfSaveBookProvider); ref.listen(parsePdfSaveBookProvider, (previous, next) { - DKLog.d('保存状态变化:previous=$previous, next=$next'); + if (previous == null) return; if (next.hasError && !next.isLoading) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('保存失败:${next.error}'))); + showFToast( + context: context, + title: Text("保存失败"), + description: Text(next.error.toString()), + ); } - if (previous?.isLoading == true && next.hasValue) { - DKLog.d('保存成功,返回书架'); - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('保存成功'))); + if (previous.isLoading && next.hasValue) { + showFToast(context: context, title: Text('保存成功')); context.go(AppRoute.main); } }); - return Scaffold( - appBar: AppBar(title: Text('解析 PDF:${asyncState.value?.pdfName ?? ''}')), - body: asyncState.when( + return FScaffold( + header: FHeader.nested( + title: Text('解析 PDF:${asyncState.value?.pdfName ?? ''}'), + prefixes: [FHeaderAction.back(onPress: () => context.pop())], + ), + child: asyncState.when( loading: () => Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ - CircularProgressIndicator(), - SizedBox(height: 16), + const FCircularProgress(size: .xl), + const SizedBox(height: 16), Text('正在解析 ${progress.$1}/${progress.$2}'), ], ), ), error: (error, stack) => Center(child: Text(error.toString())), - data: (state) => GridView.builder( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - mainAxisSpacing: 4, - crossAxisSpacing: 4, - ), - itemCount: state.tempPaths.length, - itemBuilder: (context, index) => - Image.file(File(state.tempPaths[index]), fit: BoxFit.cover), - ), - ), - bottomNavigationBar: asyncState.hasValue == true - ? Padding( + data: (state) => Column( + children: [ + Expanded( + child: GridView.builder( + padding: const EdgeInsets.all(12), + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 150, + mainAxisExtent: 200, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + ), + itemCount: state.tempPaths.length, + itemBuilder: (context, index) { + final image = state.tempPaths[index]; + return ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Stack( + fit: StackFit.expand, + children: [ + Image.file( + File(image), + fit: BoxFit.cover, + cacheWidth: 300, + errorBuilder: (_, __, ___) => Container( + color: Colors.grey[200], + child: Icon( + Icons.broken_image, + color: Colors.grey[400], + ), + ), + ), + // 页码角标 + Positioned( + bottom: 4, + right: 4, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.6), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + '${index + 1}', + style: const TextStyle( + color: Colors.white, + fontSize: 11, + ), + ), + ), + ), + ], + ), + ); + }, + ), + ), + Padding( padding: const EdgeInsets.all(16), - child: FilledButton( - onPressed: saveBookState.isLoading + child: FButton( + onPress: saveBookState.isLoading ? null - : () => ref.read(parsePdfSaveBookProvider.notifier).onSave( - asyncState.value!.tempPaths, - asyncState.value!.pdfName, - ), + : () => ref + .read(parsePdfSaveBookProvider.notifier) + .onSave( + asyncState.value!.tempPaths, + asyncState.value!.pdfName, + ), child: saveBookState.isLoading - ? Text( - '保存中... ${saveBookProgress.$1}/${saveBookProgress.$2}', - ) + ? Text(saveBookStepText(saveBookProgress)) : const Text('保存到书架'), ), - ) - : null, + ), + ], + ), + ), ); } } diff --git a/lib/feature/parse/ui/view/parse_web_view.dart b/lib/feature/parse/ui/view/parse_web_view.dart index b8721fe..9424a9b 100644 --- a/lib/feature/parse/ui/view/parse_web_view.dart +++ b/lib/feature/parse/ui/view/parse_web_view.dart @@ -1,51 +1,60 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:forui/forui.dart'; import 'package:go_router/go_router.dart'; import 'package:tele_book/common/widget/network_image_widget.dart'; import 'package:tele_book/core/route/app_route.dart'; import 'package:tele_book/feature/main/provider/main_provider.dart'; import 'package:tele_book/feature/parse/ui/provider/parse_web_provider.dart'; -class ParseWebView extends ConsumerWidget { +class ParseWebView extends ConsumerStatefulWidget { final String url; const ParseWebView({super.key, required this.url}); @override - Widget build(BuildContext context, WidgetRef ref) { - final state = ref.watch(parseWebProvider(url)); - final notifier = ref.read(parseWebProvider(url).notifier); + ConsumerState createState() => _ParseWebViewState(); +} - return Scaffold( - appBar: AppBar(title: Text(state.title)), - floatingActionButton: FloatingActionButton( - child: Badge( - label: Text((state.urls.length).toString()), - child: const Icon(Icons.photo), - ), - onPressed: () { - showModalBottomSheet( - context: context, - builder: (sheetContext) => - _buildBottomSheet(sheetContext: sheetContext, state: state), - ).then((confirmed) { - if (confirmed != true || !context.mounted) return; +class _ParseWebViewState extends ConsumerState { + @override + void dispose() { + super.dispose(); + } - ref.read(mainProvider.notifier).updateCurrentIndex(1); - context.go(AppRoute.main); - unawaited(notifier.startDownload()); - }); - }, + @override + Widget build(BuildContext context) { + final state = ref.watch(parseWebProvider(widget.url)); + final notifier = ref.read(parseWebProvider(widget.url).notifier); + + return FScaffold( + header: FHeader.nested( + title: Text(state.title), + prefixes: [FHeaderAction.back(onPress: () => context.pop())], + suffixes: [ + Builder( + builder: (innerContext) => FHeaderAction( + icon: FBadge(child: Text('${state.urls.length}')), + onPress: () { + _showBottomSheet( + context: innerContext, + url: widget.url, + ); + }, + ), + ), + ], ), - body: Column( + child: Column( children: [ - LinearProgressIndicator(value: (state.progress) / 100), + FDeterminateProgress(value: (state.progress) / 100), Expanded( child: InAppWebView( - initialUrlRequest: URLRequest(url: WebUri(url)), + initialUrlRequest: URLRequest(url: WebUri(widget.url)), onLoadStart: (controller, url) { notifier.onLoadStart(controller); }, @@ -62,66 +71,106 @@ class ParseWebView extends ConsumerWidget { ); } - Widget _buildBottomSheet({ - required BuildContext sheetContext, - required ParseWebState state, - }) { - return Container( - padding: EdgeInsets.all(16), - height: MediaQuery.of(sheetContext).size.height * 0.7, - child: Column( - children: [ - Text("解析到的图片链接", style: Theme.of(sheetContext).textTheme.titleMedium), - SizedBox(height: 16), - Expanded( - child: ListView.builder( - itemCount: state.urls.length, - itemBuilder: (context, index) { - final url = state.urls[index]; - return Row( - children: [ - NetworkImageWidget(imageUrl: url), - Expanded( - child: ListTile( - title: Text( - url, - maxLines: 3, - overflow: TextOverflow.ellipsis, + void _showBottomSheet({required BuildContext context, required String url}) { + showFSheet( + context: context, + side: .btt, + builder: (context) => Consumer( + builder: (context, ref, _) { + final state = ref.watch(parseWebProvider(url)); + final notifier = ref.read(parseWebProvider(url).notifier); + + return Container( + decoration: BoxDecoration( + color: context.theme.colors.background, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(16), + ), + border: .symmetric( + horizontal: BorderSide(color: context.theme.colors.border), + ), + ), + child: Padding( + padding: .all(16), + child: Column( + crossAxisAlignment: .start, + children: [ + Row( + mainAxisAlignment: .spaceBetween, + children: [ + Text( + "解析到的图片链接", + style: context.theme.typography.body.xl.copyWith( + fontWeight: .w600, + color: context.theme.colors.foreground, + height: 1.5, ), - onTap: () { - Navigator.push( - sheetContext, - MaterialPageRoute( - builder: (context) => _buildImagePreview(url), - ), - ); - }, ), + FButton.icon( + variant: .ghost, + onPress: notifier.isInit + ? () { + notifier.parseWeb(); + } + : null, + child: Icon(FLucideIcons.refreshCcw), + ), + ], + ), + SizedBox(height: 16), + Expanded( + child: FItemGroup.builder( + count: state.urls.length, + itemBuilder: (context, index) { + final url = state.urls[index]; + return FItem( + prefix: NetworkImageWidget(imageUrl: url), + title: Text( + url, + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + onPress: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + _buildImagePreview(context, url), + ), + ); + }, + suffix: Icon(FLucideIcons.chevronRight), + ); + }, ), - ], - ); - }, - ), - ), - SizedBox( - width: double.infinity, - child: FilledButton( - onPressed: () { - if (state.urls.isEmpty) return; - Navigator.of(sheetContext).pop(true); - }, - child: Text("下载"), + ), + FButton( + onPress: () { + if (state.urls.isEmpty) return; + ref.read(mainProvider.notifier).updateCurrentIndex(1); + context.go(AppRoute.main); + unawaited(notifier.startDownload()); + }, + child: const Text("下载"), + ), + ], + ), ), - ), - ], + ); + }, ), ); } - Widget _buildImagePreview(String url) { - return Scaffold( - appBar: AppBar(title: Text("图片预览")), - body: Center( + Widget _buildImagePreview(BuildContext parentContext, String url) { + return FScaffold( + header: FHeader.nested( + title: const Text("图片预览"), + prefixes: [ + FHeaderAction.back(onPress: () => Navigator.of(parentContext).pop()), + ], + ), + child: Center( child: Image.network( url, fit: BoxFit.contain, diff --git a/lib/main.dart b/lib/main.dart index d296d89..1950036 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:forui/forui.dart'; import 'package:responsive_framework/responsive_framework.dart'; import 'package:tele_book/common/config/global_config.dart'; import 'package:tele_book/common/theme/app_theme.dart'; @@ -39,22 +40,28 @@ Future _init() async { await InAppWebViewController.setWebContentsDebuggingEnabled(kDebugMode); } + final (lightTheme, darkTheme) = + const { + .android, + .iOS, + .fuchsia, + }.contains(defaultTargetPlatform) + ? (FTheme.neutral.light.touch, FTheme.neutral.dark.touch) + : (FTheme.neutral.light.desktop, FTheme.neutral.dark.desktop); + appProviders = ProviderScope( child: MaterialApp.router( title: 'tele_book', routerConfig: AppRoute.router, debugShowCheckedModeBanner: false, - theme: AppTheme.light, - darkTheme: AppTheme.dark, + supportedLocales: FLocalizations.supportedLocales, + localizationsDelegates: const [...FLocalizations.localizationsDelegates], + theme: lightTheme.toApproximateMaterialTheme(), + darkTheme: darkTheme.toApproximateMaterialTheme(), themeMode: ThemeMode.system, - builder: (context, child) => ResponsiveBreakpoints.builder( - child: child!, - breakpoints: [ - const Breakpoint(start: 0, end: 480, name: MOBILE), - const Breakpoint(start: 481, end: 800, name: TABLET), - const Breakpoint(start: 801, end: 1200, name: DESKTOP), - const Breakpoint(start: 1201, end: double.infinity, name: '4K'), - ], + builder: (context, child) => FTheme( + data: Theme.brightnessOf(context) == .light ? lightTheme : darkTheme, + child: FToaster(child: FTooltipGroup(child: child!)), ), ), ); diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b..4b81f9b 100644 --- a/macos/Flutter/Flutter-Debug.xcconfig +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig index c2efd0b..5caa9d1 100644 --- a/macos/Flutter/Flutter-Release.xcconfig +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 0a7d3b8..e6f081b 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,7 @@ import FlutterMacOS import Foundation import file_picker +import flutter_image_compress_macos import flutter_inappwebview_macos import nsd_macos import package_info_plus @@ -18,6 +19,7 @@ import webview_flutter_wkwebview func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) + FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) NsdMacosPlugin.register(with: registry.registrar(forPlugin: "NsdMacosPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 5bfa030..c2e4704 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -27,7 +27,9 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 4C0D964FFD8E0E1C15A516F5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 21424197D5842335C26ECC6E /* Pods_Runner.framework */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + BDE97472BE39ADEB8998F11C /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E37E4236474FDB01D292621 /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -61,11 +63,14 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 20FC8B8BD9C1C6F8EE0D561D /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 21424197D5842335C26ECC6E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2890361C15CCC1C9FFF925E2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* tele_book.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "tele_book.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* tele_book.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tele_book.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -77,9 +82,14 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 45B361BDE7B819C9E996548B /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 5E37E4236474FDB01D292621 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 60179C70E3F0D97078C749B8 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 68E590BE6A61E5C0AC2C0D57 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 97EAD1D63D707A24CD7F7B8C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -87,6 +97,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BDE97472BE39ADEB8998F11C /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -95,12 +106,27 @@ buildActionMask = 2147483647; files = ( 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + 4C0D964FFD8E0E1C15A516F5 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0194D0BA33C64134F7153C62 /* Pods */ = { + isa = PBXGroup; + children = ( + 97EAD1D63D707A24CD7F7B8C /* Pods-Runner.debug.xcconfig */, + 2890361C15CCC1C9FFF925E2 /* Pods-Runner.release.xcconfig */, + 45B361BDE7B819C9E996548B /* Pods-Runner.profile.xcconfig */, + 20FC8B8BD9C1C6F8EE0D561D /* Pods-RunnerTests.debug.xcconfig */, + 60179C70E3F0D97078C749B8 /* Pods-RunnerTests.release.xcconfig */, + 68E590BE6A61E5C0AC2C0D57 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; 331C80D6294CF71000263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( @@ -128,6 +154,7 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 0194D0BA33C64134F7153C62 /* Pods */, ); sourceTree = ""; }; @@ -179,6 +206,8 @@ D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 21424197D5842335C26ECC6E /* Pods_Runner.framework */, + 5E37E4236474FDB01D292621 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -190,6 +219,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 2F681318101AFA9BE4B572C2 /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -208,11 +238,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 2DF00E3BAFF6F7125C743DBE /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 8F00F77955ECAE6FA17BB14F /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -268,7 +300,7 @@ ); mainGroup = 33CC10E42044A3C60003C045; packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; @@ -301,6 +333,50 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 2DF00E3BAFF6F7125C743DBE /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 2F681318101AFA9BE4B572C2 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -339,6 +415,23 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 8F00F77955ECAE6FA17BB14F /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -390,6 +483,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 20FC8B8BD9C1C6F8EE0D561D /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -404,6 +498,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 60179C70E3F0D97078C749B8 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -418,6 +513,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 68E590BE6A61E5C0AC2C0D57 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -712,7 +808,7 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { isa = XCLocalSwiftPackageReference; relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; }; diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/pubspec.yaml b/pubspec.yaml index 163f94e..6285ad4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,8 @@ dependencies: flutter_riverpod: ^3.3.1 riverpod_annotation: ^4.0.2 freezed_annotation: ^3.1.0 + forui: ^0.24.3 + flutter_image_compress: ^2.4.0 dev_dependencies: build_runner: ^2.15.0 From 96438b05fddb40e06158e3587d7375b7aaf6134c Mon Sep 17 00:00:00 2001 From: warren Date: Mon, 3 Aug 2026 16:33:37 +0800 Subject: [PATCH 15/16] =?UTF-8?q?pref(forui)=20=E6=9B=B4=E6=8D=A2ui?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=EF=BC=8C=E6=94=B9=E4=B8=BA=E6=9B=B4=E4=BC=98?= =?UTF-8?q?=E9=9B=85=E7=9A=84forui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 1c9772c..48559db 100644 --- a/version.properties +++ b/version.properties @@ -1,3 +1,3 @@ -VERSION_NAME=3.1.4 +VERSION_NAME=3.1.6 VERSION_CODE=4 From a9a593bd9eb62626abf56cd87f9c00f5dbf3a08d Mon Sep 17 00:00:00 2001 From: warren Date: Mon, 3 Aug 2026 16:33:44 +0800 Subject: [PATCH 16/16] =?UTF-8?q?pref(forui)=20=E6=9B=B4=E6=8D=A2ui?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=EF=BC=8C=E6=94=B9=E4=B8=BA=E6=9B=B4=E4=BC=98?= =?UTF-8?q?=E9=9B=85=E7=9A=84forui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 48559db..89aa294 100644 --- a/version.properties +++ b/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.1.6 -VERSION_CODE=4 +VERSION_CODE=1