mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
support setting new download path on android
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
<application
|
<application
|
||||||
android:label="venera"
|
android:label="venera"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
@@ -3,8 +3,16 @@ package com.github.wgh136.venera
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
|
import android.Manifest
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
@@ -23,6 +31,9 @@ class MainActivity : FlutterActivity() {
|
|||||||
|
|
||||||
private lateinit var result: MethodChannel.Result
|
private lateinit var result: MethodChannel.Result
|
||||||
|
|
||||||
|
private val storageRequestCode = 0x10
|
||||||
|
private var storagePermissionRequest: ((Boolean) -> Unit)? = null
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
if (requestCode == pickDirectoryCode) {
|
if (requestCode == pickDirectoryCode) {
|
||||||
@@ -43,6 +54,11 @@ class MainActivity : FlutterActivity() {
|
|||||||
result.error("Failed to Copy Files", e.toString(), null)
|
result.error("Failed to Copy Files", e.toString(), null)
|
||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
|
} else if (requestCode == storageRequestCode) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
storagePermissionRequest?.invoke(Environment.isExternalStorageManager())
|
||||||
|
}
|
||||||
|
storagePermissionRequest = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +105,13 @@ class MainActivity : FlutterActivity() {
|
|||||||
listening = false
|
listening = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
val storageChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "venera/storage")
|
||||||
|
storageChannel.setMethodCallHandler { _, res ->
|
||||||
|
requestStoragePermission {result ->
|
||||||
|
res.success(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getProxy(): String {
|
private fun getProxy(): String {
|
||||||
@@ -145,6 +168,61 @@ class MainActivity : FlutterActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun requestStoragePermission(result: (Boolean) -> Unit) {
|
||||||
|
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
|
val readPermission = ContextCompat.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
|
||||||
|
val writePermission = ContextCompat.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
|
||||||
|
if (!readPermission || !writePermission) {
|
||||||
|
storagePermissionRequest = result
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
this,
|
||||||
|
arrayOf(
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
),
|
||||||
|
storageRequestCode
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
result(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!Environment.isExternalStorageManager()) {
|
||||||
|
try {
|
||||||
|
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||||
|
intent.addCategory("android.intent.category.DEFAULT")
|
||||||
|
intent.data = Uri.parse("package:" + context.packageName)
|
||||||
|
startActivityForResult(intent, storageRequestCode)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
result(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<out String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
if(requestCode == storageRequestCode) {
|
||||||
|
storagePermissionRequest?.invoke(grantResults.all {
|
||||||
|
it == PackageManager.PERMISSION_GRANTED
|
||||||
|
})
|
||||||
|
storagePermissionRequest = null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class VolumeListen{
|
class VolumeListen{
|
||||||
|
@@ -5,6 +5,7 @@ import 'package:path_provider/path_provider.dart';
|
|||||||
import 'package:sqlite3/sqlite3.dart';
|
import 'package:sqlite3/sqlite3.dart';
|
||||||
import 'package:venera/foundation/comic_source/comic_source.dart';
|
import 'package:venera/foundation/comic_source/comic_source.dart';
|
||||||
import 'package:venera/foundation/comic_type.dart';
|
import 'package:venera/foundation/comic_type.dart';
|
||||||
|
import 'package:venera/foundation/log.dart';
|
||||||
import 'package:venera/network/download.dart';
|
import 'package:venera/network/download.dart';
|
||||||
import 'package:venera/pages/reader/reader.dart';
|
import 'package:venera/pages/reader/reader.dart';
|
||||||
import 'package:venera/utils/ext.dart';
|
import 'package:venera/utils/ext.dart';
|
||||||
@@ -158,12 +159,13 @@ class LocalManager with ChangeNotifier {
|
|||||||
return "Directory is not empty";
|
return "Directory is not empty";
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await copyDirectory(
|
await copyDirectoryIsolate(
|
||||||
Directory(path),
|
Directory(path),
|
||||||
newDir,
|
newDir,
|
||||||
);
|
);
|
||||||
await File(FilePath.join(App.dataPath, 'local_path')).writeAsString(path);
|
await File(FilePath.join(App.dataPath, 'local_path')).writeAsString(path);
|
||||||
} catch (e) {
|
} catch (e, s) {
|
||||||
|
Log.error("IO", e, s);
|
||||||
return e.toString();
|
return e.toString();
|
||||||
}
|
}
|
||||||
await Directory(path).deleteIgnoreError(recursive:true);
|
await Directory(path).deleteIgnoreError(recursive:true);
|
||||||
|
@@ -32,12 +32,28 @@ class _AppSettingsState extends State<AppSettings> {
|
|||||||
title: "Set New Storage Path".tl,
|
title: "Set New Storage Path".tl,
|
||||||
actionTitle: "Set".tl,
|
actionTitle: "Set".tl,
|
||||||
callback: () async {
|
callback: () async {
|
||||||
var result;
|
String? result;
|
||||||
if (App.isAndroid) {
|
if (App.isAndroid) {
|
||||||
context.showMessage(message: "Not supported".tl);
|
var channel = const MethodChannel("venera/storage");
|
||||||
|
var permission = await channel.invokeMethod('');
|
||||||
|
if(permission != true) {
|
||||||
|
context.showMessage(message: "Permission denied".tl);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (App.isIOS) {
|
var path = await selectDirectory();
|
||||||
|
if(path != null) {
|
||||||
|
// check if the path is writable
|
||||||
|
var testFile = File(FilePath.join(path, "test"));
|
||||||
|
try {
|
||||||
|
await testFile.writeAsBytes([1]);
|
||||||
|
await testFile.delete();
|
||||||
|
} catch (e) {
|
||||||
|
context.showMessage(message: "Permission denied".tl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result = path;
|
||||||
|
}
|
||||||
|
} else if (App.isIOS) {
|
||||||
result = await selectDirectoryIOS();
|
result = await selectDirectoryIOS();
|
||||||
} else {
|
} else {
|
||||||
result = await selectDirectory();
|
result = await selectDirectory();
|
||||||
|
@@ -4,6 +4,7 @@ import 'package:flutter/gestures.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_reorderable_grid_view/widgets/reorderable_builder.dart';
|
import 'package:flutter_reorderable_grid_view/widgets/reorderable_builder.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
import 'package:venera/components/components.dart';
|
import 'package:venera/components/components.dart';
|
||||||
import 'package:venera/foundation/app.dart';
|
import 'package:venera/foundation/app.dart';
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||||
@@ -113,6 +114,12 @@ Future<void> copyDirectory(Directory source, Directory destination) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> copyDirectoryIsolate(Directory source, Directory destination) async {
|
||||||
|
await Isolate.run(() {
|
||||||
|
copyDirectory(source, destination);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
String findValidDirectoryName(String path, String directory) {
|
String findValidDirectoryName(String path, String directory) {
|
||||||
var name = sanitizeFileName(directory);
|
var name = sanitizeFileName(directory);
|
||||||
var dir = Directory("$path/$name");
|
var dir = Directory("$path/$name");
|
||||||
|
48
pubspec.lock
48
pubspec.lock
@@ -593,6 +593,54 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.0"
|
||||||
|
permission_handler:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: permission_handler
|
||||||
|
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "11.3.1"
|
||||||
|
permission_handler_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_android
|
||||||
|
sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "12.0.13"
|
||||||
|
permission_handler_apple:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_apple
|
||||||
|
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.4.5"
|
||||||
|
permission_handler_html:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_html
|
||||||
|
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.3+2"
|
||||||
|
permission_handler_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_platform_interface
|
||||||
|
sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.3"
|
||||||
|
permission_handler_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_windows
|
||||||
|
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.1"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@@ -63,6 +63,7 @@ dependencies:
|
|||||||
git:
|
git:
|
||||||
url: https://github.com/wgh136/webdav_client
|
url: https://github.com/wgh136/webdav_client
|
||||||
ref: 285f87f15bccd2d5d5ff443761348c6ee47b98d1
|
ref: 285f87f15bccd2d5d5ff443761348c6ee47b98d1
|
||||||
|
permission_handler: ^11.3.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@@ -11,6 +11,7 @@
|
|||||||
#include <file_selector_windows/file_selector_windows.h>
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
|
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
|
||||||
#include <flutter_qjs/flutter_qjs_plugin.h>
|
#include <flutter_qjs/flutter_qjs_plugin.h>
|
||||||
|
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||||
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
|
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
|
||||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||||
@@ -28,6 +29,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
|
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
|
||||||
FlutterQjsPluginRegisterWithRegistrar(
|
FlutterQjsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FlutterQjsPlugin"));
|
registry->GetRegistrarForPlugin("FlutterQjsPlugin"));
|
||||||
|
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||||
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(
|
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
|
registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
|
||||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||||
|
@@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
file_selector_windows
|
file_selector_windows
|
||||||
flutter_inappwebview_windows
|
flutter_inappwebview_windows
|
||||||
flutter_qjs
|
flutter_qjs
|
||||||
|
permission_handler_windows
|
||||||
screen_retriever_windows
|
screen_retriever_windows
|
||||||
share_plus
|
share_plus
|
||||||
sqlite3_flutter_libs
|
sqlite3_flutter_libs
|
||||||
|
Reference in New Issue
Block a user