#120 warn user if system file picker is disabled

This commit is contained in:
Thibault Deckers 2021-12-30 14:06:01 +09:00
parent 8aacd5064f
commit 25311c5fcb
9 changed files with 68 additions and 12 deletions

View file

@ -1,6 +1,7 @@
package deckers.thibault.aves.channel.calls
import android.content.Context
import android.content.Intent
import android.content.res.Resources
import android.os.Build
import androidx.core.content.pm.ShortcutManagerCompat
@ -18,6 +19,7 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
"getDefaultTimeZone" -> safe(call, result, ::getDefaultTimeZone)
"getLocales" -> safe(call, result, ::getLocales)
"getPerformanceClass" -> safe(call, result, ::getPerformanceClass)
"isSystemFilePickerEnabled" -> safe(call, result, ::isSystemFilePickerEnabled)
else -> result.notImplemented()
}
}
@ -34,7 +36,6 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
// but using hybrid composition would make it usable on API 19 too,
// cf https://github.com/flutter/flutter/issues/23728
"canRenderGoogleMaps" to (sdkInt >= Build.VERSION_CODES.KITKAT_WATCH),
"hasFilePicker" to (sdkInt >= Build.VERSION_CODES.KITKAT),
"showPinShortcutFeedback" to (sdkInt >= Build.VERSION_CODES.O),
"supportEdgeToEdgeUIMode" to (sdkInt >= Build.VERSION_CODES.Q),
)
@ -82,6 +83,15 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
result.success(Build.VERSION.SDK_INT)
}
private fun isSystemFilePickerEnabled(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
val enabled = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).resolveActivity(context.packageManager) != null
} else {
false
}
result.success(enabled)
}
companion object {
const val CHANNEL = "deckers.thibault/aves/device"
}

View file

@ -170,7 +170,7 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any?
MainActivity.pendingStorageAccessResultHandlers[MainActivity.OPEN_FILE_REQUEST] = PendingStorageAccessResultHandler(null, ::onGranted, ::onDenied)
activity.startActivityForResult(intent, MainActivity.OPEN_FILE_REQUEST)
} else {
MainActivity.notifyError("failed to resolve activity for intent=$intent")
MainActivity.notifyError("failed to resolve activity for intent=$intent extras=${intent.extras}")
onDenied()
}
}

View file

@ -55,7 +55,7 @@ object PermissionManager {
MainActivity.pendingStorageAccessResultHandlers[MainActivity.DOCUMENT_TREE_ACCESS_REQUEST] = PendingStorageAccessResultHandler(path, onGranted, onDenied)
activity.startActivityForResult(intent, MainActivity.DOCUMENT_TREE_ACCESS_REQUEST)
} else {
MainActivity.notifyError("failed to resolve activity for intent=$intent")
MainActivity.notifyError("failed to resolve activity for intent=$intent extras=${intent.extras}")
onDenied()
}
}

View file

@ -204,6 +204,8 @@
}
}
},
"missingSystemFilePickerDialogTitle": "Missing System File Picker",
"missingSystemFilePickerDialogMessage": "The system file picker is missing or disabled. Please enable it and try again.",
"unsupportedTypeDialogTitle": "Unsupported Types",
"unsupportedTypeDialogMessage": "{count, plural, =1{This operation is not supported for items of the following type: {types}.} other{This operation is not supported for items of the following types: {types}.}}",

View file

@ -6,7 +6,7 @@ final Device device = Device._private();
class Device {
late final String _userAgent;
late final bool _canGrantDirectoryAccess, _canPinShortcut, _canPrint, _canRenderFlagEmojis, _canRenderGoogleMaps;
late final bool _hasFilePicker, _showPinShortcutFeedback, _supportEdgeToEdgeUIMode;
late final bool _showPinShortcutFeedback, _supportEdgeToEdgeUIMode;
String get userAgent => _userAgent;
@ -20,8 +20,6 @@ class Device {
bool get canRenderGoogleMaps => _canRenderGoogleMaps;
bool get hasFilePicker => _hasFilePicker;
bool get showPinShortcutFeedback => _showPinShortcutFeedback;
bool get supportEdgeToEdgeUIMode => _supportEdgeToEdgeUIMode;
@ -38,7 +36,6 @@ class Device {
_canPrint = capabilities['canPrint'] ?? false;
_canRenderFlagEmojis = capabilities['canRenderFlagEmojis'] ?? false;
_canRenderGoogleMaps = capabilities['canRenderGoogleMaps'] ?? false;
_hasFilePicker = capabilities['hasFilePicker'] ?? false;
_showPinShortcutFeedback = capabilities['showPinShortcutFeedback'] ?? false;
_supportEdgeToEdgeUIMode = capabilities['supportEdgeToEdgeUIMode'] ?? false;
}

View file

@ -11,6 +11,8 @@ abstract class DeviceService {
Future<List<Locale>> getLocales();
Future<int> getPerformanceClass();
Future<bool> isSystemFilePickerEnabled();
}
class PlatformDeviceService implements DeviceService {
@ -60,7 +62,6 @@ class PlatformDeviceService implements DeviceService {
@override
Future<int> getPerformanceClass() async {
try {
await platform.invokeMethod('getPerformanceClass');
final result = await platform.invokeMethod('getPerformanceClass');
if (result != null) return result as int;
} on PlatformException catch (e, stack) {
@ -68,4 +69,15 @@ class PlatformDeviceService implements DeviceService {
}
return 0;
}
@override
Future<bool> isSystemFilePickerEnabled() async {
try {
final result = await platform.invokeMethod('isSystemFilePickerEnabled');
if (result != null) return result as bool;
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
return false;
}
}

View file

@ -47,11 +47,12 @@ mixin PermissionAwareMixin {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) {
final directory = dir.relativeDir.isEmpty ? context.l10n.rootDirectoryDescription : context.l10n.otherDirectoryDescription(dir.relativeDir);
final l10n = context.l10n;
final directory = dir.relativeDir.isEmpty ? l10n.rootDirectoryDescription : l10n.otherDirectoryDescription(dir.relativeDir);
final volume = dir.getVolumeDescription(context);
return AvesDialog(
title: context.l10n.storageAccessDialogTitle,
content: Text(context.l10n.storageAccessDialogMessage(directory, volume)),
title: l10n.storageAccessDialogTitle,
content: Text(l10n.storageAccessDialogMessage(directory, volume)),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
@ -68,6 +69,26 @@ mixin PermissionAwareMixin {
// abort if the user cancels in Flutter
if (confirmed == null || !confirmed) return false;
if (!await deviceService.isSystemFilePickerEnabled()) {
await showDialog(
context: context,
builder: (context) {
final l10n = context.l10n;
return AvesDialog(
title: l10n.missingSystemFilePickerDialogTitle,
content: Text(l10n.missingSystemFilePickerDialogMessage),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(MaterialLocalizations.of(context).okButtonLabel),
),
],
);
},
);
return false;
}
final granted = await storageService.requestDirectoryAccess(dir.volumePath);
if (!granted) {
// abort if the user denies access from the native dialog

View file

@ -51,7 +51,7 @@ class _DebugAndroidAppSectionState extends State<DebugAndroidAppSection> with Au
return ValueListenableBuilder<String>(
valueListenable: _queryNotifier,
builder: (context, query, child) {
if ({package.packageName, ...package.potentialDirs}.none((v) => v.contains(query))) {
if ({package.packageName, ...package.potentialDirs}.none((v) => v.toLowerCase().contains(query.toLowerCase()))) {
return const SizedBox();
}
return Text.rich(

View file

@ -2,6 +2,8 @@
"de": [
"filterRatingUnratedLabel",
"filterRatingRejectedLabel",
"missingSystemFilePickerDialogTitle",
"missingSystemFilePickerDialogMessage",
"editEntryDateDialogSourceFieldLabel",
"editEntryDateDialogSourceCustomDate",
"editEntryDateDialogSourceTitle",
@ -12,9 +14,21 @@
"settingsThumbnailShowRating"
],
"fr": [
"missingSystemFilePickerDialogTitle",
"missingSystemFilePickerDialogMessage"
],
"ko": [
"missingSystemFilePickerDialogTitle",
"missingSystemFilePickerDialogMessage"
],
"ru": [
"filterRatingUnratedLabel",
"filterRatingRejectedLabel",
"missingSystemFilePickerDialogTitle",
"missingSystemFilePickerDialogMessage",
"editEntryDateDialogSourceFieldLabel",
"editEntryDateDialogSourceCustomDate",
"editEntryDateDialogSourceTitle",