drawer: split albums by type

This commit is contained in:
Thibault Deckers 2019-12-29 21:27:47 +09:00
parent d407f5b7d5
commit 73bb51895f
4 changed files with 111 additions and 54 deletions

View file

@ -2,7 +2,6 @@ import 'package:aves/model/settings.dart';
import 'package:aves/utils/android_file_utils.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_drawer.dart';
import 'package:aves/widgets/album/all_collection_page.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_query_data_provider.dart';
import 'package:aves/widgets/common/providers/media_store_collection_provider.dart'; import 'package:aves/widgets/common/providers/media_store_collection_provider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -70,9 +69,8 @@ class _HomePageState extends State<HomePage> {
return; return;
} }
androidFileUtils.init();
// TODO notify when icons are ready for drawer and section header refresh // TODO notify when icons are ready for drawer and section header refresh
unawaited(IconUtils.init()); // 170ms await androidFileUtils.init(); // 170ms
await settings.init(); // <20ms await settings.init(); // <20ms
} }

View file

@ -1,3 +1,4 @@
import 'package:aves/utils/android_app_service.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
final AndroidFileUtils androidFileUtils = AndroidFileUtils._private(); final AndroidFileUtils androidFileUtils = AndroidFileUtils._private();
@ -5,19 +6,51 @@ final AndroidFileUtils androidFileUtils = AndroidFileUtils._private();
class AndroidFileUtils { class AndroidFileUtils {
String externalStorage, dcimPath, downloadPath, picturesPath; String externalStorage, dcimPath, downloadPath, picturesPath;
static Map appNameMap = {};
AndroidFileUtils._private(); AndroidFileUtils._private();
void init() { Future<void> init() async {
// path_provider getExternalStorageDirectory() gives '/storage/emulated/0/Android/data/deckers.thibault.aves/files' // path_provider getExternalStorageDirectory() gives '/storage/emulated/0/Android/data/deckers.thibault.aves/files'
externalStorage = '/storage/emulated/0'; externalStorage = '/storage/emulated/0';
dcimPath = join(externalStorage, 'DCIM'); dcimPath = join(externalStorage, 'DCIM');
downloadPath = join(externalStorage, 'Download'); downloadPath = join(externalStorage, 'Download');
picturesPath = join(externalStorage, 'Pictures'); picturesPath = join(externalStorage, 'Pictures');
appNameMap = await AndroidAppService.getAppNames();
} }
bool isCameraPath(String path) => path != null && path.startsWith(dcimPath) && (path.endsWith('Camera') || path.endsWith('100ANDRO')); 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 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; 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,
} }

View file

@ -2,6 +2,7 @@ import 'dart:ui';
import 'package:aves/model/image_collection.dart'; import 'package:aves/model/image_collection.dart';
import 'package:aves/model/image_entry.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/album/filtered_collection_page.dart';
import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/icons.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -14,8 +15,41 @@ class AllCollectionDrawer extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final collection = Provider.of<ImageCollection>(context); final collection = Provider.of<ImageCollection>(context);
final albums = collection.sortedAlbums;
final tags = collection.sortedTags; 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( return Drawer(
child: Selector<MediaQueryData, double>( child: Selector<MediaQueryData, double>(
selector: (c, mq) => mq.viewInsets.bottom, selector: (c, mq) => mq.viewInsets.bottom,
@ -58,22 +92,22 @@ class AllCollectionDrawer extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row(children: [ Row(children: [
Icon(Icons.photo_library), const Icon(Icons.photo_library),
const SizedBox(width: 4), const SizedBox(width: 4),
Text('${collection.imageCount}'), Text('${collection.imageCount}'),
]), ]),
Row(children: [ Row(children: [
Icon(Icons.video_library), const Icon(Icons.video_library),
const SizedBox(width: 4), const SizedBox(width: 4),
Text('${collection.videoCount}'), Text('${collection.videoCount}'),
]), ]),
Row(children: [ Row(children: [
Icon(Icons.photo_album), const Icon(Icons.photo_album),
const SizedBox(width: 4), const SizedBox(width: 4),
Text('${collection.albumCount}'), Text('${collection.albumCount}'),
]), ]),
Row(children: [ Row(children: [
Icon(Icons.label), const Icon(Icons.label),
const SizedBox(width: 4), const SizedBox(width: 4),
Text('${collection.tagCount}'), Text('${collection.tagCount}'),
]), ]),
@ -83,26 +117,23 @@ class AllCollectionDrawer extends StatelessWidget {
), ),
), ),
), ),
_FilteredCollectionNavTile( videoEntry,
collection: collection, if (specialAlbums.isNotEmpty) ...[
leading: Icon(Icons.video_library),
title: 'Videos',
filter: (entry) => entry.isVideo,
),
const Divider(), const Divider(),
...albums.map((album) => _FilteredCollectionNavTile( ...specialAlbums.map(buildAlbumEntry),
collection: collection, ],
leading: IconUtils.getAlbumIcon(context, album) ?? Icon(Icons.photo_album), if (appAlbums.isNotEmpty) ...[
title: collection.getUniqueAlbumName(album, albums),
filter: (entry) => entry.directory == album,
)),
const Divider(), const Divider(),
...tags.map((tag) => _FilteredCollectionNavTile( ...appAlbums.map(buildAlbumEntry),
collection: collection, ],
leading: Icon(Icons.label), if (regularAlbums.isNotEmpty) ...[
title: tag, const Divider(),
filter: (entry) => entry.xmpSubjects.contains(tag), ...regularAlbums.map(buildAlbumEntry),
)), ],
if (tags.isNotEmpty) ...[
const Divider(),
...tags.map(buildTagEntry),
],
], ],
), ),
), ),

View file

@ -1,11 +1,9 @@
import 'dart:ui'; import 'dart:ui';
import 'package:aves/model/image_entry.dart'; 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/utils/android_file_utils.dart';
import 'package:aves/widgets/common/app_icon.dart'; import 'package:aves/widgets/common/app_icon.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:path/path.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class VideoIcon extends StatelessWidget { class VideoIcon extends StatelessWidget {
@ -89,30 +87,27 @@ class OverlayIcon extends StatelessWidget {
} }
class IconUtils { class IconUtils {
static Map appNameMap = {};
static Future<void> init() async {
appNameMap = await AndroidAppService.getAppNames();
}
static Widget getAlbumIcon(BuildContext context, String albumDirectory) { static Widget getAlbumIcon(BuildContext context, String albumDirectory) {
if (albumDirectory == null) return null; switch (androidFileUtils.getAlbumType(albumDirectory)) {
if (androidFileUtils.isCameraPath(albumDirectory)) return Icon(Icons.photo_camera); case AlbumType.Camera:
if (androidFileUtils.isScreenshotsPath(albumDirectory)) return Icon(Icons.smartphone); return Icon(Icons.photo_camera);
if (androidFileUtils.isDownloadPath(albumDirectory)) return Icon(Icons.file_download); case AlbumType.Screenshots:
case AlbumType.ScreenRecordings:
final parts = albumDirectory.split(separator); return Icon(Icons.smartphone);
if (albumDirectory.startsWith(androidFileUtils.externalStorage) && appNameMap.keys.contains(parts.last)) { case AlbumType.Download:
final packageName = appNameMap[parts.last]; return Icon(Icons.file_download);
case AlbumType.App:
return Selector<MediaQueryData, double>( return Selector<MediaQueryData, double>(
selector: (c, mq) => mq.devicePixelRatio, selector: (c, mq) => mq.devicePixelRatio,
builder: (c, devicePixelRatio, child) => AppIcon( builder: (c, devicePixelRatio, child) => AppIcon(
packageName: packageName, packageName: androidFileUtils.getAlbumAppPackageName(albumDirectory),
size: IconTheme.of(context).size, size: IconTheme.of(context).size,
devicePixelRatio: devicePixelRatio, devicePixelRatio: devicePixelRatio,
), ),
); );
} case AlbumType.Default:
default:
return null; return null;
} }
} }
}