diff --git a/client/android/app/src/main/AndroidManifest.xml b/client/android/app/src/main/AndroidManifest.xml index 81bd19d..610a755 100644 --- a/client/android/app/src/main/AndroidManifest.xml +++ b/client/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,13 @@ - + + + + + + + main() async { @@ -46,6 +47,33 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { + @override + void initState() { + checkAndRequestStoragePermission(); + super.initState(); + } + + Future checkAndRequestStoragePermission() async { + // 1. 检查是否已经拥有“所有文件访问权限” + var status = await Permission.manageExternalStorage.status; + + if (!status.isGranted) { + print("未获得管理外部存储权限,尝试申请..."); + + // 2. 首次申请,或者已经被拒绝过 + // 在 Android 11+ 上,这行代码会自动尝试跳转到系统的“所有文件访问权限”设置列表 + var requestStatus = await Permission.manageExternalStorage.request(); + + // 3. 如果系统未能自动跳转(某些国产ROM限制),则强制通过插件打开系统设置 + if (!requestStatus.isGranted) { + print("自动跳转失败,提示用户手动去设置开启"); + await openAppSettings(); + } + } else { + print("已获得所有文件访问权限,可以放心执行 listSync()"); + } + } + @override Widget build(BuildContext context) { var width = MediaQuery.sizeOf(context).width; @@ -171,7 +199,7 @@ class _MyHomePageState extends State { ? null : () { if (syncItemConfig != null) { - upload(syncItemConfig, cloudModifiedTimeTs == null); + upload(context, syncItemConfig, cloudModifiedTimeTs == null); } }, child: Icon(Icons.upload_rounded, size: 22), @@ -184,7 +212,7 @@ class _MyHomePageState extends State { ? null : () { if (syncItemConfig != null) { - download(syncItemConfig, cloudArchives.sorted.first); + download(context, syncItemConfig, cloudArchives.sorted.first); } }, onLongPress: cloudModifiedTimeTs == null @@ -204,7 +232,7 @@ class _MyHomePageState extends State { value: file, onTap: () { if (syncItemConfig != null) { - download(syncItemConfig, file); + download(context, syncItemConfig, file); } }, child: SizedBox( diff --git a/client/lib/utils.dart b/client/lib/utils.dart index 4f1f9e8..c5dc7ee 100644 --- a/client/lib/utils.dart +++ b/client/lib/utils.dart @@ -4,6 +4,7 @@ import 'package:archive/archive_io.dart'; import 'package:cloud_archiver/config.dart'; import 'package:cloud_archiver/state.dart'; import 'package:filesize/filesize.dart'; +import 'package:flutter/material.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:webdav_client/webdav_client.dart' as dav; @@ -109,7 +110,7 @@ void loadCloudArchives() async { davStatus.value = DavStatus.connected; } -Future upload(SyncItemConfig item, bool needCreateDir) async { +Future upload(BuildContext context, SyncItemConfig item, bool needCreateDir) async { if (needCreateDir) { await _client.mkdir(item.name); } @@ -122,26 +123,52 @@ Future upload(SyncItemConfig item, bool needCreateDir) async { var zipTempFilePath = join(cacheDir.path, zipFileName); zipFileEncoder.create(zipTempFilePath); if (file.statSync().type == FileSystemEntityType.directory) { - await zipFileEncoder.addDirectory(Directory(item.path)); + var directory = Directory(item.path); + for (var file in directory.listSync()) { + print('Add file: ${file.path}'); + } + await zipFileEncoder.addDirectory( + directory, + onProgress: (progress) { + print('Zip progress: $progress'); + }, + ); } else { await zipFileEncoder.addFile(file); } await zipFileEncoder.close(); - await _client.writeFromFile(zipTempFilePath, join(item.name, zipFileName)); File(zipTempFilePath).delete(); - + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('上传完成'), + ), + ); + } loadCloudArchives(); } -Future download(SyncItemConfig item, dav.File cloudArchive) async { +Future download(BuildContext context, SyncItemConfig item, dav.File cloudArchive) async { print('Start download: ${cloudArchive.path}'); var cacheDir = await getApplicationCacheDirectory(); var zipPath = join(cacheDir.path, '${item.name}_${cloudArchive.name}'); - await _client.read2File(cloudArchive.path!, zipPath); - await extractFileToDisk(zipPath, cacheDir.path); + await _client.read2File( + cloudArchive.path!, + zipPath, + onProgress: (count, total) { + print('Downloaded: $count/$total'); + }, + ); + await extractFileToDisk( + zipPath, + cacheDir.path, + callback: (entry) { + print('Unzip: ${entry.name}'); + }, + ); File(zipPath).delete(); var temp = File(join(cacheDir.path, basename(item.path))); @@ -165,6 +192,14 @@ Future download(SyncItemConfig item, dav.File cloudArchive) async { default: print('Failed: $fileSystemEntityType'); } + + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('下载完成'), + ), + ); + } } extension DateTimeExt on DateTime { diff --git a/client/linux/runner/my_application.cc b/client/linux/runner/my_application.cc index 1157397..88919f8 100644 --- a/client/linux/runner/my_application.cc +++ b/client/linux/runner/my_application.cc @@ -45,11 +45,11 @@ static void my_application_activate(GApplication* application) { if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, "cloud_archiver"); + gtk_header_bar_set_title(header_bar, "Cloud Archiver"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { - gtk_window_set_title(window, "cloud_archiver"); + gtk_window_set_title(window, "Cloud Archiver"); } gtk_window_set_default_size(window, 1280, 720); diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 1823aaa..eb46fb3 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -33,8 +33,6 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.8 - crypto: ^3.0.7 path_provider: ^2.1.6 path: ^1.9.1 webdav_client: ^1.2.2 @@ -43,6 +41,7 @@ dependencies: uuid: ^4.5.3 collection: ^1.19.1 archive: ^4.0.9 + permission_handler: ^12.0.3 dev_dependencies: flutter_test: