From 6757b1fa351ae2c601f7745b5a43ccd0d7c16270 Mon Sep 17 00:00:00 2001 From: Francesco146 Date: Fri, 16 Aug 2024 11:32:08 +0200 Subject: [PATCH 1/3] fix: Handle selecting files and folders for patch options correctly --- lib/services/manager_api.dart | 9 ++- .../patch_options_fields.dart | 67 +++++++++++++------ pubspec.yaml | 1 + 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/lib/services/manager_api.dart b/lib/services/manager_api.dart index 0e02228ae8..2eb4b3bb73 100644 --- a/lib/services/manager_api.dart +++ b/lib/services/manager_api.dart @@ -38,6 +38,8 @@ class ManagerAPI { bool releaseBuild = false; bool suggestedAppVersionSelected = true; bool isDynamicThemeAvailable = false; + bool isScopedStorageAvailable = false; + int sdkVersion = 0; String storedPatchesFile = '/selected-patches.json'; String keystoreFile = '/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore'; @@ -55,8 +57,11 @@ class ManagerAPI { Future initialize() async { _prefs = await SharedPreferences.getInstance(); isRooted = await _rootAPI.isRooted(); - isDynamicThemeAvailable = - (await getSdkVersion()) >= 31; // ANDROID_12_SDK_VERSION = 31 + if (sdkVersion == 0) { + sdkVersion = await getSdkVersion(); + } + isDynamicThemeAvailable = sdkVersion >= 31; // ANDROID_12_SDK_VERSION = 31 + isScopedStorageAvailable = sdkVersion >= 30; // ANDROID_11_SDK_VERSION = 30 storedPatchesFile = (await getApplicationDocumentsDirectory()).path + storedPatchesFile; if (kReleaseMode) { diff --git a/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart b/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart index f400019db1..7827a42dd6 100644 --- a/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart +++ b/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart @@ -1,8 +1,11 @@ +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_file_dialog/flutter_file_dialog.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/gen/strings.g.dart'; import 'package:revanced_manager/models/patch.dart'; +import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/ui/views/patch_options/patch_options_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_card.dart'; @@ -398,6 +401,7 @@ class TextFieldForPatchOption extends StatefulWidget { } class _TextFieldForPatchOptionState extends State { + final ManagerAPI _managerAPI = locator(); final TextEditingController controller = TextEditingController(); String? selectedKey; String? defaultValue; @@ -524,29 +528,54 @@ class _TextFieldForPatchOptionState extends State { ]; }, onSelected: (String selection) async { - switch (selection) { - case 'file': - final String? result = await FlutterFileDialog.pickFile(); - if (result != null) { - controller.text = result; - widget.onChanged(controller.text); + // manageExternalStorage permission is required for file/folder selection + // otherwise, the app will not complain, but the patches will error out + // the same way as if the user selected an empty file/folder. + // Android 11 and above requires the manageExternalStorage permission + final Map availableActions = { + 'file': () async { + if (_managerAPI.isScopedStorageAvailable) { + final permission = + await Permission.manageExternalStorage.request(); + if (!permission.isGranted) { + return; + } + } + final FilePickerResult? result = + await FilePicker.platform.pickFiles(); + if (result == null) { + return; + } + controller.text = result.files.single.path!; + widget.onChanged(controller.text); + }, + 'folder': () async { + if (_managerAPI.isScopedStorageAvailable) { + final permission = + await Permission.manageExternalStorage.request(); + if (!permission.isGranted) { + return; + } } - break; - case 'folder': - final DirectoryLocation? result = - await FlutterFileDialog.pickDirectory(); - if (result != null) { - controller.text = result.toString(); - widget.onChanged(controller.text); + final String? result = + await FilePicker.platform.getDirectoryPath(); + if (result == null) { + return; } - break; - case 'remove': + controller.text = result; + widget.onChanged(controller.text); + }, + 'remove': () { widget.removeValue!(); - break; - case 'null': + }, + 'null': () { controller.text = ''; widget.onChanged(null); - break; + }, + }; + print('[patch option] Selected: $selection'); + if (availableActions.containsKey(selection)) { + await availableActions[selection]!(); } }, ), diff --git a/pubspec.yaml b/pubspec.yaml index e7c2f4a6fd..13fb3414e7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,7 @@ dependencies: dynamic_color: ^1.7.0 dynamic_themes: ^1.1.0 expandable: ^5.0.1 + file_picker: ^8.0.3 flutter: sdk: flutter flutter_background: From 49caff2e6e77cffc75e98c818f41ffca266e8bd5 Mon Sep 17 00:00:00 2001 From: Francesco146 Date: Fri, 16 Aug 2024 11:38:26 +0200 Subject: [PATCH 2/3] chore: update dependency and remove log statement --- lib/ui/widgets/patchesSelectorView/patch_options_fields.dart | 1 - pubspec.yaml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart b/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart index 7827a42dd6..b767eee7dd 100644 --- a/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart +++ b/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart @@ -573,7 +573,6 @@ class _TextFieldForPatchOptionState extends State { widget.onChanged(null); }, }; - print('[patch option] Selected: $selection'); if (availableActions.containsKey(selection)) { await availableActions[selection]!(); } diff --git a/pubspec.yaml b/pubspec.yaml index 13fb3414e7..8a7d5b7f97 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: dynamic_color: ^1.7.0 dynamic_themes: ^1.1.0 expandable: ^5.0.1 - file_picker: ^8.0.3 + file_picker: ^8.0.5 flutter: sdk: flutter flutter_background: From aa5df523bafe1b8551a813a371be2ab1da56789b Mon Sep 17 00:00:00 2001 From: Francesco146 Date: Sat, 17 Aug 2024 20:57:01 +0200 Subject: [PATCH 3/3] fix: Remove unneeded permission in file selection, refactor using a `switch case` --- .../patch_options_fields.dart | 55 +++++++++---------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart b/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart index b767eee7dd..19213a4538 100644 --- a/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart +++ b/lib/ui/widgets/patchesSelectorView/patch_options_fields.dart @@ -528,19 +528,23 @@ class _TextFieldForPatchOptionState extends State { ]; }, onSelected: (String selection) async { - // manageExternalStorage permission is required for file/folder selection - // otherwise, the app will not complain, but the patches will error out - // the same way as if the user selected an empty file/folder. - // Android 11 and above requires the manageExternalStorage permission - final Map availableActions = { - 'file': () async { - if (_managerAPI.isScopedStorageAvailable) { - final permission = - await Permission.manageExternalStorage.request(); - if (!permission.isGranted) { - return; - } - } + Future gotExternalStoragePermission() async { + // manageExternalStorage permission is required for folder selection + // otherwise, the app will not complain, but the patches will error out + // the same way as if the user selected an empty folder. + // Android 11 and above requires the manageExternalStorage permission + if (_managerAPI.isScopedStorageAvailable) { + final permission = + await Permission.manageExternalStorage.request(); + return permission.isGranted; + } + return true; + } + + switch (selection) { + case 'file': + // here scope storage is not required because file_picker + // will copy the file to the app's cache final FilePickerResult? result = await FilePicker.platform.pickFiles(); if (result == null) { @@ -548,14 +552,10 @@ class _TextFieldForPatchOptionState extends State { } controller.text = result.files.single.path!; widget.onChanged(controller.text); - }, - 'folder': () async { - if (_managerAPI.isScopedStorageAvailable) { - final permission = - await Permission.manageExternalStorage.request(); - if (!permission.isGranted) { - return; - } + break; + case 'folder': + if (!await gotExternalStoragePermission()) { + return; } final String? result = await FilePicker.platform.getDirectoryPath(); @@ -564,17 +564,14 @@ class _TextFieldForPatchOptionState extends State { } controller.text = result; widget.onChanged(controller.text); - }, - 'remove': () { + break; + case 'remove': widget.removeValue!(); - }, - 'null': () { + break; + case 'null': controller.text = ''; widget.onChanged(null); - }, - }; - if (availableActions.containsKey(selection)) { - await availableActions[selection]!(); + break; } }, ),