feat(programs): Android 端通过 MediaStore 写入公共 Downloads
Android 10+ 受 scoped storage 限制,getDownloadsDirectory() 返回的是 app-specific 目录 (/storage/emulated/0/Android/data/.../files/Download/), 而非用户可见的 /storage/emulated/0/Download/。 新增 MainActivity 端 MethodChannel com.xiarui.kuaishai2/downloads: - API 29+:MediaStore.Downloads 写入公共 Downloads,无需权限 - API <=28:直接写 /storage/emulated/0/Download/(需 WRITE_EXTERNAL_STORAGE) Dart 端 ExcelTemplateService 改用 MethodChannel,Android 平台返回 Download/<filename> 显示路径;其它平台保留 getDownloadsDirectory 行为。 返回值由 File 改为 String,调用方已同步更新。
This commit is contained in:
@@ -454,9 +454,9 @@ class _ProgramsPageState extends ConsumerState<ProgramsPage> {
|
||||
/// 下载 Excel 模板
|
||||
Future<void> _downloadTemplate(BuildContext context) async {
|
||||
try {
|
||||
final file = await ExcelTemplateService.instance.generateTemplate();
|
||||
final path = await ExcelTemplateService.instance.generateTemplate();
|
||||
if (!context.mounted) return;
|
||||
ToastService.showSuccess(context, '模板已保存: ${file.path}');
|
||||
ToastService.showSuccess(context, '模板已保存: $path');
|
||||
} catch (e) {
|
||||
if (!context.mounted) return;
|
||||
ToastService.showError(context, '生成模板失败: ${e.toString()}');
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:excel/excel.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
/// Excel 模板服务
|
||||
@@ -18,16 +19,40 @@ class ExcelTemplateService {
|
||||
static const String sheetPrograms = 'Programs';
|
||||
static const String sheetSteps = 'Steps';
|
||||
|
||||
/// 生成模板并保存到下载目录,返回文件对象
|
||||
/// 模板文件名
|
||||
static const String templateFilename = 'program_template.xlsx';
|
||||
|
||||
/// Android 端写入公共 Downloads 的 MethodChannel(对应 MainActivity.kt)
|
||||
static const MethodChannel _downloadsChannel =
|
||||
MethodChannel('com.xiarui.kuaishai2/downloads');
|
||||
|
||||
/// 生成模板并保存到下载目录,返回可显示的路径字符串
|
||||
///
|
||||
/// 优先使用系统下载目录;不支持时回退到应用文档目录。
|
||||
Future<File> generateTemplate() async {
|
||||
/// - Android:通过 MediaStore 写入公共 Downloads (`/storage/emulated/0/Download/`)
|
||||
/// - 其它平台:使用 [getDownloadsDirectory],不可用时回退到应用文档目录
|
||||
Future<String> generateTemplate() async {
|
||||
final excel = Excel.createExcel();
|
||||
final bytes = _buildTemplateBytes(excel);
|
||||
final dir = await getDownloadsDirectory() ?? await getApplicationDocumentsDirectory();
|
||||
final file = File('${dir.path}/program_template.xlsx');
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
final saved = await _downloadsChannel.invokeMethod<String>(
|
||||
'saveToDownloads',
|
||||
<String, dynamic>{
|
||||
'filename': templateFilename,
|
||||
'bytes': bytes,
|
||||
},
|
||||
);
|
||||
if (saved == null) {
|
||||
throw StateError('保存到 Downloads 失败:未返回路径');
|
||||
}
|
||||
return saved;
|
||||
}
|
||||
|
||||
final dir =
|
||||
await getDownloadsDirectory() ?? await getApplicationDocumentsDirectory();
|
||||
final file = File('${dir.path}/$templateFilename');
|
||||
await file.writeAsBytes(bytes, flush: true);
|
||||
return file;
|
||||
return file.path;
|
||||
}
|
||||
|
||||
/// 构建模板字节流(可在测试中直接调用)
|
||||
|
||||
Reference in New Issue
Block a user