From a89c55b5c4f75a885a62c823fd67892332b1e70d Mon Sep 17 00:00:00 2001 From: Levi Date: Fri, 17 May 2024 18:38:35 +0800 Subject: [PATCH] feat: improve Android download directory choose (#541) --- .../app/controllers/app_controller.dart | 9 +- .../modules/setting/views/setting_view.dart | 1 + .../lib/app/views/directory_selector.dart | 86 ++++++++++++++++--- ui/flutter/pubspec.lock | 8 ++ ui/flutter/pubspec.yaml | 1 + 5 files changed, 86 insertions(+), 19 deletions(-) diff --git a/ui/flutter/lib/app/modules/app/controllers/app_controller.dart b/ui/flutter/lib/app/modules/app/controllers/app_controller.dart index a8f101d2..f898b20a 100644 --- a/ui/flutter/lib/app/modules/app/controllers/app_controller.dart +++ b/ui/flutter/lib/app/modules/app/controllers/app_controller.dart @@ -8,7 +8,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:get/get.dart'; import 'package:launch_at_startup/launch_at_startup.dart'; -import 'package:lecle_downloads_path_provider/lecle_downloads_path_provider.dart'; import 'package:path_provider/path_provider.dart'; import 'package:tray_manager/tray_manager.dart'; import 'package:uri_to_file/uri_to_file.dart'; @@ -436,12 +435,8 @@ class AppController extends GetxController with WindowListener, TrayListener { if (Util.isDesktop()) { config.downloadDir = (await getDownloadsDirectory())?.path ?? "./"; } else if (Util.isAndroid()) { - final downloadDir = (await DownloadsPath.downloadsDirectory())?.path; - if (downloadDir != null) { - config.downloadDir = '$downloadDir/Gopeed'; - } else { - config.downloadDir = (await getApplicationDocumentsDirectory()).path; - } + config.downloadDir = (await getExternalStorageDirectory())?.path ?? + (await getApplicationDocumentsDirectory()).path; } else if (Util.isIOS()) { config.downloadDir = (await getApplicationDocumentsDirectory()).path; } else { diff --git a/ui/flutter/lib/app/modules/setting/views/setting_view.dart b/ui/flutter/lib/app/modules/setting/views/setting_view.dart index ca1e625d..70c69fdc 100644 --- a/ui/flutter/lib/app/modules/setting/views/setting_view.dart +++ b/ui/flutter/lib/app/modules/setting/views/setting_view.dart @@ -79,6 +79,7 @@ class SettingView extends GetView { return DirectorySelector( controller: downloadDirController, showLabel: false, + showAndoirdToggle: true, ); }); final buildMaxRunning = _buildConfigItem( diff --git a/ui/flutter/lib/app/views/directory_selector.dart b/ui/flutter/lib/app/views/directory_selector.dart index b2f27d9f..491697a1 100644 --- a/ui/flutter/lib/app/views/directory_selector.dart +++ b/ui/flutter/lib/app/views/directory_selector.dart @@ -1,16 +1,26 @@ +import 'dart:io'; + import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:lecle_downloads_path_provider/lecle_downloads_path_provider.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:toggle_switch/toggle_switch.dart'; import '../../util/mac_secure_util.dart'; +import '../../util/message.dart'; import '../../util/util.dart'; class DirectorySelector extends StatefulWidget { final TextEditingController controller; final bool showLabel; + final bool showAndoirdToggle; const DirectorySelector( - {Key? key, required this.controller, this.showLabel = true}) + {Key? key, + required this.controller, + this.showLabel = true, + this.showAndoirdToggle = false}) : super(key: key); @override @@ -20,6 +30,68 @@ class DirectorySelector extends StatefulWidget { class _DirectorySelectorState extends State { @override Widget build(BuildContext context) { + Widget? buildSelectWidget() { + if (Util.isDesktop()) { + return IconButton( + icon: const Icon(Icons.folder_open), + onPressed: () async { + var dir = await FilePicker.platform.getDirectoryPath(); + if (dir != null) { + widget.controller.text = dir; + MacSecureUtil.saveBookmark(dir); + } + }); + } + // After Android 11, access to external storage is increasingly restricted, so it no longer supports selecting the download directory. However, if you do not download in external storage, all downloaded files will be deleted after the application is uninstalled. + // Fortunately, so far, most Android devices can still access the system download directory. + // For the sake of user experience, it is decided to only support selecting the application's internal directory and the system download directory. Also, a test for file write permission is performed when selecting the system download directory. If it cannot be written, selection is not allowed. + if (Util.isAndroid() && widget.showAndoirdToggle) { + final isSwitchToDownloadDir = + widget.controller.text.endsWith('/Gopeed'); + + return ToggleSwitch( + initialLabelIndex: isSwitchToDownloadDir ? 1 : 0, + totalSwitches: 2, + icons: const [Icons.home, Icons.download], + customWidths: const [50, 50], + onToggle: (index) async { + if (index == 0) { + widget.controller.text = + (await getExternalStorageDirectory())?.path ?? + (await getApplicationDocumentsDirectory()).path; + } else { + widget.controller.text = + '${(await DownloadsPath.downloadsDirectory())!.path}/Gopeed'; + } + }, + cancelToggle: (index) async { + if (index == 0) { + return false; + } + // Check write permission + final downloadDir = + (await DownloadsPath.downloadsDirectory())?.path; + if (downloadDir == null) { + return true; + } + final fileRandomeName = + "test_${DateTime.now().millisecondsSinceEpoch}.tmp"; + final testFile = File('$downloadDir/Gopeed/$fileRandomeName'); + try { + await testFile.create(recursive: true); + await testFile.writeAsString('test'); + await testFile.delete(); + return false; + } catch (e) { + showErrorMessage(e); + return true; + } + }, + ).marginOnly(left: 10); + } + return null; + } + return Row( children: [ Expanded( @@ -35,17 +107,7 @@ class _DirectorySelectorState extends State { return v!.trim().isNotEmpty ? null : 'downloadDirValid'.tr; }, )), - !Util.isWeb() && !Util.isAndroid() && !Util.isIOS() - ? IconButton( - icon: const Icon(Icons.folder_open), - onPressed: () async { - var dir = await FilePicker.platform.getDirectoryPath(); - if (dir != null) { - widget.controller.text = dir; - MacSecureUtil.saveBookmark(dir); - } - }) - : null + buildSelectWidget() ].where((e) => e != null).map((e) => e!).toList(), ); } diff --git a/ui/flutter/pubspec.lock b/ui/flutter/pubspec.lock index e518826d..3a7b0274 100644 --- a/ui/flutter/pubspec.lock +++ b/ui/flutter/pubspec.lock @@ -1002,6 +1002,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + toggle_switch: + dependency: "direct main" + description: + name: toggle_switch + sha256: dca04512d7c23ed320d6c5ede1211a404f177d54d353bf785b07d15546a86ce5 + url: "https://pub.dev" + source: hosted + version: "2.3.0" tray_manager: dependency: "direct main" description: diff --git a/ui/flutter/pubspec.yaml b/ui/flutter/pubspec.yaml index f30d65d1..4820742b 100644 --- a/ui/flutter/pubspec.yaml +++ b/ui/flutter/pubspec.yaml @@ -66,6 +66,7 @@ dependencies: hive: ^2.2.3 launch_at_startup: ^0.2.2 args: ^2.5.0 + toggle_switch: ^2.3.0 dev_dependencies: flutter_test: sdk: flutter