import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/android_file_service.dart'; import 'package:aves/utils/change_notifier.dart'; import 'package:flutter/foundation.dart'; import 'package:path/path.dart'; final AndroidFileUtils androidFileUtils = AndroidFileUtils._private(); class AndroidFileUtils { String primaryStorage, dcimPath, downloadPath, moviesPath, picturesPath; Set storageVolumes = {}; Set _packages = {}; List _potentialAppDirs = []; AChangeNotifier appNameChangeNotifier = AChangeNotifier(); Iterable get _launcherPackages => _packages.where((package) => package.categoryLauncher); AndroidFileUtils._private(); Future init() async { storageVolumes = await AndroidFileService.getStorageVolumes(); // path_provider getExternalStorageDirectory() gives '/storage/emulated/0/Android/data/deckers.thibault.aves/files' primaryStorage = storageVolumes.firstWhere((volume) => volume.isPrimary).path; dcimPath = join(primaryStorage, 'DCIM'); downloadPath = join(primaryStorage, 'Download'); moviesPath = join(primaryStorage, 'Movies'); picturesPath = join(primaryStorage, 'Pictures'); } Future initAppNames() async { _packages = await AndroidAppService.getPackages(); _potentialAppDirs = _launcherPackages.expand((package) => package.potentialDirs).toList(); appNameChangeNotifier.notifyListeners(); } bool isCameraPath(String path) => path != null && path.startsWith(dcimPath) && (path.endsWith('Camera') || path.endsWith('100ANDRO')); bool isScreenshotsPath(String path) => path != null && (path.startsWith(dcimPath) || path.startsWith(picturesPath)) && path.endsWith('Screenshots'); bool isScreenRecordingsPath(String path) => path != null && (path.startsWith(dcimPath) || path.startsWith(moviesPath)) && (path.endsWith('Screen recordings') || path.endsWith('ScreenRecords')); bool isDownloadPath(String path) => path == downloadPath; StorageVolume getStorageVolume(String path) => storageVolumes.firstWhere((v) => path.startsWith(v.path), orElse: () => null); bool isOnRemovableStorage(String path) => getStorageVolume(path)?.isRemovable ?? false; AlbumType getAlbumType(String albumPath) { if (albumPath != null) { if (isCameraPath(albumPath)) return AlbumType.camera; if (isDownloadPath(albumPath)) return AlbumType.download; if (isScreenRecordingsPath(albumPath)) return AlbumType.screenRecordings; if (isScreenshotsPath(albumPath)) return AlbumType.screenshots; final dir = albumPath.split(separator).last; if (albumPath.startsWith(primaryStorage) && _potentialAppDirs.contains(dir)) return AlbumType.app; } return AlbumType.regular; } String getAlbumAppPackageName(String albumPath) { if (albumPath == null) return null; final dir = albumPath.split(separator).last; final package = _launcherPackages.firstWhere((package) => package.potentialDirs.contains(dir), orElse: () => null); return package?.packageName; } String getCurrentAppName(String packageName) { final package = _packages.firstWhere((package) => package.packageName == packageName, orElse: () => null); return package?.currentLabel; } } enum AlbumType { regular, app, camera, download, screenRecordings, screenshots } class Package { final String packageName, currentLabel, englishLabel; final bool categoryLauncher, isSystem; final Set ownedDirs = {}; Package({ this.packageName, this.currentLabel, this.englishLabel, this.categoryLauncher, this.isSystem, }); factory Package.fromMap(Map map) { return Package( packageName: map['packageName'], currentLabel: map['currentLabel'], englishLabel: map['englishLabel'], categoryLauncher: map['categoryLauncher'], isSystem: map['isSystem'], ); } Set get potentialDirs => [ currentLabel, englishLabel, ...ownedDirs, ].where((dir) => dir != null).toSet(); @override String toString() => '$runtimeType#${shortHash(this)}{packageName=$packageName, categoryLauncher=$categoryLauncher, isSystem=$isSystem, currentLabel=$currentLabel, englishLabel=$englishLabel, ownedDirs=$ownedDirs}'; } class StorageVolume { final String description, path, state; final bool isEmulated, isPrimary, isRemovable; const StorageVolume({ this.description, this.isEmulated, this.isPrimary, this.isRemovable, this.path, this.state, }); factory StorageVolume.fromMap(Map map) { return StorageVolume( description: map['description'] ?? '', isEmulated: map['isEmulated'] ?? false, isPrimary: map['isPrimary'] ?? false, isRemovable: map['isRemovable'] ?? false, path: map['path'] ?? '', state: map['state'] ?? '', ); } }