Мне нужно открыть файл, который является вложением в моем приложении, на самом деле это может быть любой тип файла: изображения, видео, PDF-файлы, файлы Excel... Android 13 имеет разрешения для READ_MEDIA_IMAGES, READ_MEDIA_VIDEO и READ_MEDIA_AUDIO и может запрашивать их с помощью Permission_handler:
[Permission.photos, Permission.videos, Permission.audio].request()
После этого я смогу открыть загруженный файл из уведомления с помощью пакета open_file:
OpenFile.open(path)
Но когда я пытаюсь открыть PDF-файл, он просто не работает. Это дает мне ошибку: Разрешение отклонено: android.permission.MANAGE_EXTERNAL_STORAGE флаттер
Это разрешение навсегда запрещено в настройках, и пользователю не интуитивно понятно отправлять его в приложение всякий раз, когда мне нужно открыть файл из уведомления. Также я прочитал здесь, что не могу поместить его в манифест, потому что он будет отклонен Play Store.
Если вы можете мне помочь, вот код ниже:
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:open_file/open_file.dart';
import 'package:pagedesk/data/model/ticket/attachemnt_item.dart';
import 'package:pagedesk/data/network/firebase_api.dart';
import 'package:pagedesk/utils/Utils.dart';
import 'package:pagedesk/view_model/ticket_attachemnts_view_model.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
class Notifications {
int maxProgress = 5;
bool isCompleted = false;
Future getDownloadNotification(
AttachemntItem item,
String downloadingText,
String downloadCompletedText,
String downoadFaildMessage,
String cantOpenFailMessage,
TicketAttachemntsViewModel viewModel) async {
final AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
'name',
'name',
channelDescription: 'progress channel description',
channelShowBadge: false,
importance: Importance.max,
priority: Priority.high,
onlyAlertOnce: true,
showProgress: false,
);
final NotificationDetails notificationDetails =
NotificationDetails(android: androidNotificationDetails);
String newPath = "";
Directory directory;
try {
if (Platform.isAndroid) {
if (await requestStoragePermissions()) {
directory = (await getExternalStorageDirectory())!;
print(directory);
List<String> paths = directory.path.split("/");
for (int x = 1; x < paths.length; x++) {
String folder = paths[x];
if (folder != "Android") {
newPath += "/$folder";
} else {
break;
}
}
newPath = "$newPath/Download";
directory = Directory(newPath);
} else {
return;
}
} else {
if (await _requestPermission(Permission.storage)) {
directory = await getApplicationDocumentsDirectory();
} else {
return;
}
}
if (!await directory.exists()) {
await directory.create(recursive: true);
}
String filename = item.name;
String path = directory.path;
print('FILENAME: $filename');
print('OATH: $path');
File file = File('$path/$filename');
if (await file.exists()) {
int suffix = 1;
String newFileName;
while (await file.exists()) {
newFileName =
'${filename.replaceAll(RegExp(r'\..+'), '')}($suffix)${filename.substring(filename.lastIndexOf('.'))}';
file = File('$path/$newFileName');
suffix++;
}
} else {
print("File doesn't exist");
}
FirebaseApi.localNotifications.show(
item.id,
item.name,
isCompleted ? downloadCompletedText : downloadingText,
notificationDetails,
payload: newPath);
viewModel.getAttachment(item).then((value) => {
if (value != null)
{
file.writeAsBytes(value),
isCompleted = true,
FirebaseApi.localNotifications.cancel(item.id),
FirebaseApi.localNotifications.show(item.id, item.name,
downloadCompletedText, notificationDetails,
payload: file.path)
}
else
{
isCompleted = false,
FirebaseApi.localNotifications.cancel(item.id),
FirebaseApi.localNotifications.show(item.id, item.name,
downoadFaildMessage, notificationDetails,
payload: null)
}
});
} catch (e) {
print('ERROR');
}
}
}
void openFile(String path, String cantOpenFileMessage) async {
try {
final result = await OpenFile.open(path);
if (result.type == ResultType.done) {
print('File opened successfully');
} else if (result.type == ResultType.noAppToOpen) {
Utils.toastMessage(cantOpenFileMessage);
} else {
Utils.toastMessage("Can't open file throgh this app.");
}
} catch (e) {
print('Error opening file: $e');
}
}
Future<bool> _requestPermission(Permission permission) async {
if (await permission.isGranted) {
return true;
} else if (await permission.isPermanentlyDenied) {
openAppSettings();
return false;
} else {
var result = await permission.request();
if (result == PermissionStatus.granted) {
return true;
}
}
return false;
}
Future<bool> requestStoragePermissions() async {
List<Permission> permissions = [];
final deviceInfo = await DeviceInfoPlugin().androidInfo;
if (deviceInfo.version.sdkInt > 32) {
permissions = [Permission.photos, Permission.videos, Permission.audio];
} else {
permissions = [Permission.storage];
}
Map<Permission, PermissionStatus> statuses = await permissions.request();
return !statuses.containsKey(false);
}
Манифест разрешений:
<uses-permission android:name = "android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name = "android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name = "android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name = "android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name = "android.permission. READ_MEDIA_AUDIO" />
Теперь, если вы сказали, что ваше приложение само загружает данные и само создает для них файл, то вам не нужно никакого разрешения для этого на устройствах Android 13+. И уж точно не управление внешним хранилищем.
И как только ваше приложение создаст и запишет файл, ему автоматически будет разрешено читать файл, используя тот же путь.
Я почти уверен (не на 100%), что, поскольку это новое разрешение, которое по умолчанию запрещено, вам нужно заставить пользователя изменить его вручную через настройки, даже если это не интуитивно понятно для пользователей, как вы сказали.
Я не знаю, видели ли вы, что можно программно открыть страницу настроек, чтобы пользователю было проще.
К сожалению, я не думаю, что есть лучшее решение, чем это.