From ca5a5523715952a1a9145c0fefecd124822c0b40 Mon Sep 17 00:00:00 2001 From: shingyu Date: Tue, 22 Jul 2025 21:54:09 +0800 Subject: [PATCH] fix web save --- lib/ui/widgets/book_detail_view.dart | 68 +++++++++++++++---- linux/flutter/generated_plugin_registrant.cc | 4 ++ linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 8 +++ pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 8 files changed, 74 insertions(+), 14 deletions(-) diff --git a/lib/ui/widgets/book_detail_view.dart b/lib/ui/widgets/book_detail_view.dart index ca9bfbb..d50bd51 100644 --- a/lib/ui/widgets/book_detail_view.dart +++ b/lib/ui/widgets/book_detail_view.dart @@ -8,6 +8,7 @@ import 'dart:io' show Platform , File, Directory; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:file_saver/file_saver.dart'; import '../../core/book_downloader.dart'; import '../../models/book.dart'; @@ -58,7 +59,9 @@ class _BookDetailViewState extends ConsumerState { } } } catch (err) { - print("获取下载文件夹路径失败: $err"); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('无法获取下载目录')), + ); } return directory?.path; } @@ -70,7 +73,30 @@ class _BookDetailViewState extends ConsumerState { final bool isIOS = !kIsWeb && Platform.isIOS; try { - if (isAndroid || isIOS) { + if (kIsWeb) { + // --- Web 平台逻辑 --- + if (_selectedFormat == DownloadFormat.chapterTxt) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('不支持分章节下载'), + content: const Text('Web 平台不支持分章节下载,请选择单文件下载。'), + actions: [ + TextButton( + child: const Text('确定'), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + ); + return; + } else { + // 获取一个虚拟的、在内存中的目录路径 + final Directory tempDir = await getApplicationDocumentsDirectory(); + String extension = _selectedFormat == DownloadFormat.singleTxt ? 'txt' : 'epub'; + outputPath = '${tempDir.path}/$fileName.$extension'; + } + } else if (isAndroid || isIOS) { // --- 移动平台 (Android/iOS) 逻辑 --- var hasPermission = true; if (isAndroid) { @@ -154,21 +180,35 @@ class _BookDetailViewState extends ConsumerState { // --- 解决方案: 创建一个局部 final 变量来捕获当前路径的值 --- final String path = _lastDownloadedPath!; + final String fileName = p.basename(path); // 从完整路径中提取文件名 // 显示 SnackBar - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - // 使用局部变量 'path' - content: Text('下载完成: $path'), - action: _selectedFormat != DownloadFormat.chapterTxt - ? SnackBarAction( - label: '打开', - // 回调函数现在捕获的是局部变量 'path',它的值不会被改变 - onPressed: () => OpenFile.open(path), + if (kIsWeb) { + // --- Web 平台: 读取虚拟文件并触发浏览器下载 --- + final File file = File(path); + file.readAsBytes().then((bytes) { + // 使用 file_saver 保存文件 + FileSaver.instance.saveFile( + name: p.basenameWithoutExtension(fileName), // 文件名 (无扩展名) + bytes: bytes, // 文件内容 + fileExtension: p.extension(fileName).replaceFirst('.', ''), // 扩展名 (去掉点) + mimeType: MimeType.text + ); + }); + } else { + // --- 移动/桌面平台: 显示带“打开”按钮的 SnackBar --- + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('下载完成: $path'), + action: _selectedFormat != DownloadFormat.chapterTxt + ? SnackBarAction( + label: '打开', + onPressed: () => OpenFile.open(path), + ) + : null, ) - : null, - ) - ); + ); + } // 现在可以安全地清空成员变量,为下一次下载做准备了 _lastDownloadedPath = null; } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 78e64f2..14732c5 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,11 +6,15 @@ #include "generated_plugin_registrant.h" +#include #include #include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_saver_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin"); + file_saver_plugin_register_with_registrar(file_saver_registrar); g_autoptr(FlPluginRegistrar) open_file_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin"); open_file_linux_plugin_register_with_registrar(open_file_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 0c49085..28c158b 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_saver open_file_linux url_launcher_linux window_size diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 95c7f80..9ef159b 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,7 @@ import FlutterMacOS import Foundation import file_picker +import file_saver import open_file_mac import package_info_plus import path_provider_foundation @@ -14,6 +15,7 @@ import window_size func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) + FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) diff --git a/pubspec.lock b/pubspec.lock index dbe2b56..b644657 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -169,6 +169,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "10.2.0" + file_saver: + dependency: "direct main" + description: + name: file_saver + sha256: "9d93db09bd4da9e43238f9dd485360fc51a5c138eea5ef5f407ec56e58079ac0" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.1" flutter: dependency: "direct main" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 7f7eb00..7c94ae9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,6 +48,7 @@ dependencies: # 文件与权限 path_provider: ^2.1.1 # 获取设备文件系统路径 file_picker: ^10.2.0 # 允许用户选择保存位置 + file_saver: ^0.3.1 # 用于在web端保存文件 permission_handler: ^12.0.1 # 处理存储权限 (尤其在 Android) open_file: ^3.5.10 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 951c360..45eb8f9 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,11 +6,14 @@ #include "generated_plugin_registrant.h" +#include #include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSaverPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSaverPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 4315012..aa770eb 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_saver permission_handler_windows url_launcher_windows window_size