import 'package:aves/model/entry.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; mixin PermissionAwareMixin { Future checkStoragePermission(BuildContext context, Set entries) { return checkStoragePermissionForAlbums(context, entries.map((e) => e.directory).whereNotNull().toSet(), entries: entries); } Future checkStoragePermissionForAlbums(BuildContext context, Set albumPaths, {Set? entries}) async { final restrictedDirs = await storageService.getRestrictedDirectories(); while (true) { final dirs = await storageService.getInaccessibleDirectories(albumPaths); final restrictedInaccessibleDirs = dirs.where(restrictedDirs.contains).toSet(); if (restrictedInaccessibleDirs.isNotEmpty) { if (entries != null && await storageService.canRequestMediaFileAccess()) { // request media file access for items in restricted directories final uris = [], mimeTypes = []; entries.where((entry) { final dir = entry.directory; return dir != null && restrictedInaccessibleDirs.contains(VolumeRelativeDirectory.fromPath(dir)); }).forEach((entry) { uris.add(entry.uri); mimeTypes.add(entry.mimeType); }); final granted = await storageService.requestMediaFileAccess(uris, mimeTypes); if (!granted) return false; } else if (entries == null && await storageService.canInsertMedia(restrictedInaccessibleDirs)) { // insertion in restricted directories } else { // cannot proceed further await showRestrictedDirectoryDialog(context, restrictedInaccessibleDirs.first); return false; } // clear restricted directories dirs.removeAll(restrictedInaccessibleDirs); } if (dirs.isEmpty) return true; final dir = dirs.first; final confirmed = await showDialog( context: context, builder: (context) { final l10n = context.l10n; final directory = dir.relativeDir.isEmpty ? l10n.rootDirectoryDescription : l10n.otherDirectoryDescription(dir.relativeDir); final volume = dir.getVolumeDescription(context); return AvesDialog( title: l10n.storageAccessDialogTitle, content: Text(l10n.storageAccessDialogMessage(directory, volume)), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text(MaterialLocalizations.of(context).cancelButtonLabel), ), TextButton( onPressed: () => Navigator.pop(context, true), child: Text(MaterialLocalizations.of(context).okButtonLabel), ), ], ); }, ); // 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.dirPath); if (!granted) { // abort if the user denies access from the native dialog return false; } } } Future showRestrictedDirectoryDialog(BuildContext context, VolumeRelativeDirectory dir) { return showDialog( context: context, builder: (context) { final directory = dir.relativeDir.isEmpty ? context.l10n.rootDirectoryDescription : context.l10n.otherDirectoryDescription(dir.relativeDir); final volume = dir.getVolumeDescription(context); return AvesDialog( title: context.l10n.restrictedAccessDialogTitle, content: Text(context.l10n.restrictedAccessDialogMessage(directory, volume)), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text(MaterialLocalizations.of(context).okButtonLabel), ), ], ); }, ); } }