import 'package:aves/model/image_entry.dart'; import 'package:aves/services/android_file_service.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:flutter/material.dart'; import 'package:tuple/tuple.dart'; mixin PermissionAwareMixin { Future checkStoragePermission(BuildContext context, Iterable entries) { return checkStoragePermissionForPaths(context, entries.where((e) => e.path != null).map((e) => e.path)); } Future checkStoragePermissionForPaths(BuildContext context, Iterable paths) async { final volumes = paths.map(androidFileUtils.getStorageVolume).toSet(); final ungrantedVolumes = (await Future.wait>( volumes.map( (volume) => AndroidFileService.requireVolumeAccessDialog(volume.path).then( (granted) => Tuple2(volume, granted), ), ), )) .where((t) => t.item2) .map((t) => t.item1) .toList(); while (ungrantedVolumes.isNotEmpty) { final volume = ungrantedVolumes.first; final confirmed = await showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Storage Volume Access'), content: Text('Please select the root directory of “${volume.description}” in the next screen, so that this app can access it and complete your request.'), actions: [ FlatButton( onPressed: () => Navigator.pop(context), child: Text('Cancel'.toUpperCase()), ), FlatButton( onPressed: () => Navigator.pop(context, true), child: Text('OK'.toUpperCase()), ), ], ); }, ); // abort if the user cancels in Flutter if (confirmed == null || !confirmed) return false; final granted = await AndroidFileService.requestVolumeAccess(volume.path); debugPrint('$runtimeType _checkStoragePermission with volume=${volume.path} got granted=$granted'); if (granted) { ungrantedVolumes.remove(volume); } else { // abort if the user denies access from the native dialog return false; } } return true; } }