drawer: split albums by type
This commit is contained in:
parent
d407f5b7d5
commit
73bb51895f
4 changed files with 111 additions and 54 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue