#120 warn user if system file picker is disabled
This commit is contained in:
parent
8aacd5064f
commit
25311c5fcb
9 changed files with 68 additions and 12 deletions
|
@ -1,6 +1,7 @@
|
||||||
package deckers.thibault.aves.channel.calls
|
package deckers.thibault.aves.channel.calls
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
|
@ -18,6 +19,7 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
|
||||||
"getDefaultTimeZone" -> safe(call, result, ::getDefaultTimeZone)
|
"getDefaultTimeZone" -> safe(call, result, ::getDefaultTimeZone)
|
||||||
"getLocales" -> safe(call, result, ::getLocales)
|
"getLocales" -> safe(call, result, ::getLocales)
|
||||||
"getPerformanceClass" -> safe(call, result, ::getPerformanceClass)
|
"getPerformanceClass" -> safe(call, result, ::getPerformanceClass)
|
||||||
|
"isSystemFilePickerEnabled" -> safe(call, result, ::isSystemFilePickerEnabled)
|
||||||
else -> result.notImplemented()
|
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,
|
// but using hybrid composition would make it usable on API 19 too,
|
||||||
// cf https://github.com/flutter/flutter/issues/23728
|
// cf https://github.com/flutter/flutter/issues/23728
|
||||||
"canRenderGoogleMaps" to (sdkInt >= Build.VERSION_CODES.KITKAT_WATCH),
|
"canRenderGoogleMaps" to (sdkInt >= Build.VERSION_CODES.KITKAT_WATCH),
|
||||||
"hasFilePicker" to (sdkInt >= Build.VERSION_CODES.KITKAT),
|
|
||||||
"showPinShortcutFeedback" to (sdkInt >= Build.VERSION_CODES.O),
|
"showPinShortcutFeedback" to (sdkInt >= Build.VERSION_CODES.O),
|
||||||
"supportEdgeToEdgeUIMode" to (sdkInt >= Build.VERSION_CODES.Q),
|
"supportEdgeToEdgeUIMode" to (sdkInt >= Build.VERSION_CODES.Q),
|
||||||
)
|
)
|
||||||
|
@ -82,6 +83,15 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
|
||||||
result.success(Build.VERSION.SDK_INT)
|
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 {
|
companion object {
|
||||||
const val CHANNEL = "deckers.thibault/aves/device"
|
const val CHANNEL = "deckers.thibault/aves/device"
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,7 +170,7 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any?
|
||||||
MainActivity.pendingStorageAccessResultHandlers[MainActivity.OPEN_FILE_REQUEST] = PendingStorageAccessResultHandler(null, ::onGranted, ::onDenied)
|
MainActivity.pendingStorageAccessResultHandlers[MainActivity.OPEN_FILE_REQUEST] = PendingStorageAccessResultHandler(null, ::onGranted, ::onDenied)
|
||||||
activity.startActivityForResult(intent, MainActivity.OPEN_FILE_REQUEST)
|
activity.startActivityForResult(intent, MainActivity.OPEN_FILE_REQUEST)
|
||||||
} else {
|
} else {
|
||||||
MainActivity.notifyError("failed to resolve activity for intent=$intent")
|
MainActivity.notifyError("failed to resolve activity for intent=$intent extras=${intent.extras}")
|
||||||
onDenied()
|
onDenied()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ object PermissionManager {
|
||||||
MainActivity.pendingStorageAccessResultHandlers[MainActivity.DOCUMENT_TREE_ACCESS_REQUEST] = PendingStorageAccessResultHandler(path, onGranted, onDenied)
|
MainActivity.pendingStorageAccessResultHandlers[MainActivity.DOCUMENT_TREE_ACCESS_REQUEST] = PendingStorageAccessResultHandler(path, onGranted, onDenied)
|
||||||
activity.startActivityForResult(intent, MainActivity.DOCUMENT_TREE_ACCESS_REQUEST)
|
activity.startActivityForResult(intent, MainActivity.DOCUMENT_TREE_ACCESS_REQUEST)
|
||||||
} else {
|
} else {
|
||||||
MainActivity.notifyError("failed to resolve activity for intent=$intent")
|
MainActivity.notifyError("failed to resolve activity for intent=$intent extras=${intent.extras}")
|
||||||
onDenied()
|
onDenied()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
"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}.}}",
|
"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}.}}",
|
||||||
|
|
|
@ -6,7 +6,7 @@ final Device device = Device._private();
|
||||||
class Device {
|
class Device {
|
||||||
late final String _userAgent;
|
late final String _userAgent;
|
||||||
late final bool _canGrantDirectoryAccess, _canPinShortcut, _canPrint, _canRenderFlagEmojis, _canRenderGoogleMaps;
|
late final bool _canGrantDirectoryAccess, _canPinShortcut, _canPrint, _canRenderFlagEmojis, _canRenderGoogleMaps;
|
||||||
late final bool _hasFilePicker, _showPinShortcutFeedback, _supportEdgeToEdgeUIMode;
|
late final bool _showPinShortcutFeedback, _supportEdgeToEdgeUIMode;
|
||||||
|
|
||||||
String get userAgent => _userAgent;
|
String get userAgent => _userAgent;
|
||||||
|
|
||||||
|
@ -20,8 +20,6 @@ class Device {
|
||||||
|
|
||||||
bool get canRenderGoogleMaps => _canRenderGoogleMaps;
|
bool get canRenderGoogleMaps => _canRenderGoogleMaps;
|
||||||
|
|
||||||
bool get hasFilePicker => _hasFilePicker;
|
|
||||||
|
|
||||||
bool get showPinShortcutFeedback => _showPinShortcutFeedback;
|
bool get showPinShortcutFeedback => _showPinShortcutFeedback;
|
||||||
|
|
||||||
bool get supportEdgeToEdgeUIMode => _supportEdgeToEdgeUIMode;
|
bool get supportEdgeToEdgeUIMode => _supportEdgeToEdgeUIMode;
|
||||||
|
@ -38,7 +36,6 @@ class Device {
|
||||||
_canPrint = capabilities['canPrint'] ?? false;
|
_canPrint = capabilities['canPrint'] ?? false;
|
||||||
_canRenderFlagEmojis = capabilities['canRenderFlagEmojis'] ?? false;
|
_canRenderFlagEmojis = capabilities['canRenderFlagEmojis'] ?? false;
|
||||||
_canRenderGoogleMaps = capabilities['canRenderGoogleMaps'] ?? false;
|
_canRenderGoogleMaps = capabilities['canRenderGoogleMaps'] ?? false;
|
||||||
_hasFilePicker = capabilities['hasFilePicker'] ?? false;
|
|
||||||
_showPinShortcutFeedback = capabilities['showPinShortcutFeedback'] ?? false;
|
_showPinShortcutFeedback = capabilities['showPinShortcutFeedback'] ?? false;
|
||||||
_supportEdgeToEdgeUIMode = capabilities['supportEdgeToEdgeUIMode'] ?? false;
|
_supportEdgeToEdgeUIMode = capabilities['supportEdgeToEdgeUIMode'] ?? false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ abstract class DeviceService {
|
||||||
Future<List<Locale>> getLocales();
|
Future<List<Locale>> getLocales();
|
||||||
|
|
||||||
Future<int> getPerformanceClass();
|
Future<int> getPerformanceClass();
|
||||||
|
|
||||||
|
Future<bool> isSystemFilePickerEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformDeviceService implements DeviceService {
|
class PlatformDeviceService implements DeviceService {
|
||||||
|
@ -60,7 +62,6 @@ class PlatformDeviceService implements DeviceService {
|
||||||
@override
|
@override
|
||||||
Future<int> getPerformanceClass() async {
|
Future<int> getPerformanceClass() async {
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('getPerformanceClass');
|
|
||||||
final result = await platform.invokeMethod('getPerformanceClass');
|
final result = await platform.invokeMethod('getPerformanceClass');
|
||||||
if (result != null) return result as int;
|
if (result != null) return result as int;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
|
@ -68,4 +69,15 @@ class PlatformDeviceService implements DeviceService {
|
||||||
}
|
}
|
||||||
return 0;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,11 +47,12 @@ mixin PermissionAwareMixin {
|
||||||
final confirmed = await showDialog<bool>(
|
final confirmed = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (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);
|
final volume = dir.getVolumeDescription(context);
|
||||||
return AvesDialog(
|
return AvesDialog(
|
||||||
title: context.l10n.storageAccessDialogTitle,
|
title: l10n.storageAccessDialogTitle,
|
||||||
content: Text(context.l10n.storageAccessDialogMessage(directory, volume)),
|
content: Text(l10n.storageAccessDialogMessage(directory, volume)),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
|
@ -68,6 +69,26 @@ mixin PermissionAwareMixin {
|
||||||
// abort if the user cancels in Flutter
|
// abort if the user cancels in Flutter
|
||||||
if (confirmed == null || !confirmed) return false;
|
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);
|
final granted = await storageService.requestDirectoryAccess(dir.volumePath);
|
||||||
if (!granted) {
|
if (!granted) {
|
||||||
// abort if the user denies access from the native dialog
|
// abort if the user denies access from the native dialog
|
||||||
|
|
|
@ -51,7 +51,7 @@ class _DebugAndroidAppSectionState extends State<DebugAndroidAppSection> with Au
|
||||||
return ValueListenableBuilder<String>(
|
return ValueListenableBuilder<String>(
|
||||||
valueListenable: _queryNotifier,
|
valueListenable: _queryNotifier,
|
||||||
builder: (context, query, child) {
|
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 const SizedBox();
|
||||||
}
|
}
|
||||||
return Text.rich(
|
return Text.rich(
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
"de": [
|
"de": [
|
||||||
"filterRatingUnratedLabel",
|
"filterRatingUnratedLabel",
|
||||||
"filterRatingRejectedLabel",
|
"filterRatingRejectedLabel",
|
||||||
|
"missingSystemFilePickerDialogTitle",
|
||||||
|
"missingSystemFilePickerDialogMessage",
|
||||||
"editEntryDateDialogSourceFieldLabel",
|
"editEntryDateDialogSourceFieldLabel",
|
||||||
"editEntryDateDialogSourceCustomDate",
|
"editEntryDateDialogSourceCustomDate",
|
||||||
"editEntryDateDialogSourceTitle",
|
"editEntryDateDialogSourceTitle",
|
||||||
|
@ -12,9 +14,21 @@
|
||||||
"settingsThumbnailShowRating"
|
"settingsThumbnailShowRating"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"fr": [
|
||||||
|
"missingSystemFilePickerDialogTitle",
|
||||||
|
"missingSystemFilePickerDialogMessage"
|
||||||
|
],
|
||||||
|
|
||||||
|
"ko": [
|
||||||
|
"missingSystemFilePickerDialogTitle",
|
||||||
|
"missingSystemFilePickerDialogMessage"
|
||||||
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
"filterRatingUnratedLabel",
|
"filterRatingUnratedLabel",
|
||||||
"filterRatingRejectedLabel",
|
"filterRatingRejectedLabel",
|
||||||
|
"missingSystemFilePickerDialogTitle",
|
||||||
|
"missingSystemFilePickerDialogMessage",
|
||||||
"editEntryDateDialogSourceFieldLabel",
|
"editEntryDateDialogSourceFieldLabel",
|
||||||
"editEntryDateDialogSourceCustomDate",
|
"editEntryDateDialogSourceCustomDate",
|
||||||
"editEntryDateDialogSourceTitle",
|
"editEntryDateDialogSourceTitle",
|
||||||
|
|
Loading…
Reference in a new issue