diff --git a/lib/main.dart b/lib/main.dart index b4d67fb90..7f51c14b0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,7 +2,6 @@ import 'package:aves/model/settings.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/album/all_collection_drawer.dart'; import 'package:aves/widgets/album/all_collection_page.dart'; -import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/providers/media_store_collection_provider.dart'; import 'package:flutter/material.dart'; @@ -70,9 +69,8 @@ class _HomePageState extends State { return; } - androidFileUtils.init(); // TODO notify when icons are ready for drawer and section header refresh - unawaited(IconUtils.init()); // 170ms + await androidFileUtils.init(); // 170ms await settings.init(); // <20ms } diff --git a/lib/utils/android_file_utils.dart b/lib/utils/android_file_utils.dart index 42ae6b896..582a8e4d4 100644 --- a/lib/utils/android_file_utils.dart +++ b/lib/utils/android_file_utils.dart @@ -1,3 +1,4 @@ +import 'package:aves/utils/android_app_service.dart'; import 'package:path/path.dart'; final AndroidFileUtils androidFileUtils = AndroidFileUtils._private(); @@ -5,19 +6,51 @@ final AndroidFileUtils androidFileUtils = AndroidFileUtils._private(); class AndroidFileUtils { String externalStorage, dcimPath, downloadPath, picturesPath; + static Map appNameMap = {}; + AndroidFileUtils._private(); - void init() { + Future init() async { // path_provider getExternalStorageDirectory() gives '/storage/emulated/0/Android/data/deckers.thibault.aves/files' externalStorage = '/storage/emulated/0'; dcimPath = join(externalStorage, 'DCIM'); downloadPath = join(externalStorage, 'Download'); picturesPath = join(externalStorage, 'Pictures'); + appNameMap = await AndroidAppService.getAppNames(); } 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.endsWith('Screenshots'); + bool isScreenRecordingsPath(String path) => path != null && path.startsWith(dcimPath) && path.endsWith('Screen recordings'); + bool isDownloadPath(String path) => path == downloadPath; + + AlbumType getAlbumType(String albumDirectory) { + if (albumDirectory != null) { + if (androidFileUtils.isCameraPath(albumDirectory)) return AlbumType.Camera; + if (androidFileUtils.isDownloadPath(albumDirectory)) return AlbumType.Download; + if (androidFileUtils.isScreenRecordingsPath(albumDirectory)) return AlbumType.ScreenRecordings; + if (androidFileUtils.isScreenshotsPath(albumDirectory)) return AlbumType.Screenshots; + + final parts = albumDirectory.split(separator); + if (albumDirectory.startsWith(androidFileUtils.externalStorage) && appNameMap.keys.contains(parts.last)) return AlbumType.App; + } + return AlbumType.Default; + } + + String getAlbumAppPackageName(String albumDirectory) { + final parts = albumDirectory.split(separator); + return AndroidFileUtils.appNameMap[parts.last]; + } +} + +enum AlbumType { + Default, + App, + Camera, + Download, + ScreenRecordings, + Screenshots, } diff --git a/lib/widgets/album/all_collection_drawer.dart b/lib/widgets/album/all_collection_drawer.dart index 033be21cd..d8920707e 100644 --- a/lib/widgets/album/all_collection_drawer.dart +++ b/lib/widgets/album/all_collection_drawer.dart @@ -2,6 +2,7 @@ import 'dart:ui'; import 'package:aves/model/image_collection.dart'; import 'package:aves/model/image_entry.dart'; +import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/album/filtered_collection_page.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:flutter/material.dart'; @@ -14,8 +15,41 @@ class AllCollectionDrawer extends StatelessWidget { @override Widget build(BuildContext context) { final collection = Provider.of(context); - final albums = collection.sortedAlbums; final tags = collection.sortedTags; + final regularAlbums = [], appAlbums = [], specialAlbums = []; + for (var album in collection.sortedAlbums) { + switch (androidFileUtils.getAlbumType(album)) { + case AlbumType.Default: + regularAlbums.add(album); + break; + case AlbumType.App: + appAlbums.add(album); + break; + default: + specialAlbums.add(album); + break; + } + } + + final videoEntry = _FilteredCollectionNavTile( + collection: collection, + leading: const Icon(Icons.video_library), + title: 'Videos', + filter: (entry) => entry.isVideo, + ); + final buildAlbumEntry = (album) => _FilteredCollectionNavTile( + collection: collection, + leading: IconUtils.getAlbumIcon(context, album) ?? const Icon(Icons.photo_album), + title: collection.getUniqueAlbumName(album, collection.sortedAlbums), + filter: (entry) => entry.directory == album, + ); + final buildTagEntry = (tag) => _FilteredCollectionNavTile( + collection: collection, + leading: const Icon(Icons.label), + title: tag, + filter: (entry) => entry.xmpSubjects.contains(tag), + ); + return Drawer( child: Selector( selector: (c, mq) => mq.viewInsets.bottom, @@ -58,22 +92,22 @@ class AllCollectionDrawer extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row(children: [ - Icon(Icons.photo_library), + const Icon(Icons.photo_library), const SizedBox(width: 4), Text('${collection.imageCount}'), ]), Row(children: [ - Icon(Icons.video_library), + const Icon(Icons.video_library), const SizedBox(width: 4), Text('${collection.videoCount}'), ]), Row(children: [ - Icon(Icons.photo_album), + const Icon(Icons.photo_album), const SizedBox(width: 4), Text('${collection.albumCount}'), ]), Row(children: [ - Icon(Icons.label), + const Icon(Icons.label), const SizedBox(width: 4), Text('${collection.tagCount}'), ]), @@ -83,26 +117,23 @@ class AllCollectionDrawer extends StatelessWidget { ), ), ), - _FilteredCollectionNavTile( - collection: collection, - leading: Icon(Icons.video_library), - title: 'Videos', - filter: (entry) => entry.isVideo, - ), - const Divider(), - ...albums.map((album) => _FilteredCollectionNavTile( - collection: collection, - leading: IconUtils.getAlbumIcon(context, album) ?? Icon(Icons.photo_album), - title: collection.getUniqueAlbumName(album, albums), - filter: (entry) => entry.directory == album, - )), - const Divider(), - ...tags.map((tag) => _FilteredCollectionNavTile( - collection: collection, - leading: Icon(Icons.label), - title: tag, - filter: (entry) => entry.xmpSubjects.contains(tag), - )), + videoEntry, + if (specialAlbums.isNotEmpty) ...[ + const Divider(), + ...specialAlbums.map(buildAlbumEntry), + ], + if (appAlbums.isNotEmpty) ...[ + const Divider(), + ...appAlbums.map(buildAlbumEntry), + ], + if (regularAlbums.isNotEmpty) ...[ + const Divider(), + ...regularAlbums.map(buildAlbumEntry), + ], + if (tags.isNotEmpty) ...[ + const Divider(), + ...tags.map(buildTagEntry), + ], ], ), ), diff --git a/lib/widgets/common/icons.dart b/lib/widgets/common/icons.dart index f764d9bc6..59a11c454 100644 --- a/lib/widgets/common/icons.dart +++ b/lib/widgets/common/icons.dart @@ -1,11 +1,9 @@ import 'dart:ui'; import 'package:aves/model/image_entry.dart'; -import 'package:aves/utils/android_app_service.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/common/app_icon.dart'; import 'package:flutter/material.dart'; -import 'package:path/path.dart'; import 'package:provider/provider.dart'; class VideoIcon extends StatelessWidget { @@ -89,30 +87,27 @@ class OverlayIcon extends StatelessWidget { } class IconUtils { - static Map appNameMap = {}; - - static Future init() async { - appNameMap = await AndroidAppService.getAppNames(); - } - static Widget getAlbumIcon(BuildContext context, String albumDirectory) { - if (albumDirectory == null) return null; - if (androidFileUtils.isCameraPath(albumDirectory)) return Icon(Icons.photo_camera); - if (androidFileUtils.isScreenshotsPath(albumDirectory)) return Icon(Icons.smartphone); - if (androidFileUtils.isDownloadPath(albumDirectory)) return Icon(Icons.file_download); - - final parts = albumDirectory.split(separator); - if (albumDirectory.startsWith(androidFileUtils.externalStorage) && appNameMap.keys.contains(parts.last)) { - final packageName = appNameMap[parts.last]; - return Selector( - selector: (c, mq) => mq.devicePixelRatio, - builder: (c, devicePixelRatio, child) => AppIcon( - packageName: packageName, - size: IconTheme.of(context).size, - devicePixelRatio: devicePixelRatio, - ), - ); + switch (androidFileUtils.getAlbumType(albumDirectory)) { + case AlbumType.Camera: + return Icon(Icons.photo_camera); + case AlbumType.Screenshots: + case AlbumType.ScreenRecordings: + return Icon(Icons.smartphone); + case AlbumType.Download: + return Icon(Icons.file_download); + case AlbumType.App: + return Selector( + selector: (c, mq) => mq.devicePixelRatio, + builder: (c, devicePixelRatio, child) => AppIcon( + packageName: androidFileUtils.getAlbumAppPackageName(albumDirectory), + size: IconTheme.of(context).size, + devicePixelRatio: devicePixelRatio, + ), + ); + case AlbumType.Default: + default: + return null; } - return null; } }