fix
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<!-- 针对 Android 11 及以上 -->
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
|
||||
|
||||
|
||||
<application
|
||||
android:label="Cloud Archiver"
|
||||
android:name="${applicationName}"
|
||||
|
||||
+31
-3
@@ -5,6 +5,7 @@ import 'package:cloud_archiver/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:webdav_client/webdav_client.dart' as dav;
|
||||
|
||||
Future<void> main() async {
|
||||
@@ -46,6 +47,33 @@ class MyHomePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
@override
|
||||
void initState() {
|
||||
checkAndRequestStoragePermission();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future<void> 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<MyHomePage> {
|
||||
? 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<MyHomePage> {
|
||||
? 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<MyHomePage> {
|
||||
value: file,
|
||||
onTap: () {
|
||||
if (syncItemConfig != null) {
|
||||
download(syncItemConfig, file);
|
||||
download(context, syncItemConfig, file);
|
||||
}
|
||||
},
|
||||
child: SizedBox(
|
||||
|
||||
+42
-7
@@ -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<void> upload(SyncItemConfig item, bool needCreateDir) async {
|
||||
Future<void> upload(BuildContext context, SyncItemConfig item, bool needCreateDir) async {
|
||||
if (needCreateDir) {
|
||||
await _client.mkdir(item.name);
|
||||
}
|
||||
@@ -122,26 +123,52 @@ Future<void> 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<void> download(SyncItemConfig item, dav.File cloudArchive) async {
|
||||
Future<void> 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<void> 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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
+1
-2
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user