diff --git a/assets/kakaotalk.svg b/assets/kakaotalk.svg
new file mode 100644
index 000000000..e721ec2b4
--- /dev/null
+++ b/assets/kakaotalk.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/main.dart b/lib/main.dart
index 1c7057fa4..b48eba16e 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -3,6 +3,7 @@ import 'package:aves/model/image_entry.dart';
import 'package:aves/model/image_file_service.dart';
import 'package:aves/model/metadata_db.dart';
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/fake_app_bar.dart';
@@ -12,6 +13,7 @@ import 'package:permission_handler/permission_handler.dart';
void main() async {
await settings.init();
+ await androidFileUtils.init();
runApp(AvesApp());
}
diff --git a/lib/model/image_collection.dart b/lib/model/image_collection.dart
index 793b8f9b3..4390e1439 100644
--- a/lib/model/image_collection.dart
+++ b/lib/model/image_collection.dart
@@ -4,6 +4,7 @@ import 'package:aves/model/image_metadata.dart';
import 'package:aves/model/metadata_db.dart';
import "package:collection/collection.dart";
import 'package:flutter/material.dart';
+import 'package:path/path.dart';
class ImageCollection with ChangeNotifier {
final List _rawEntries;
@@ -46,7 +47,7 @@ class ImageCollection with ChangeNotifier {
case SortFactor.date:
switch (groupFactor) {
case GroupFactor.album:
- sections = groupBy(_rawEntries, (entry) => entry.bucketDisplayName);
+ sections = groupBy(_rawEntries, (entry) => entry.directory);
break;
case GroupFactor.date:
sections = groupBy(_rawEntries, (entry) => entry.monthTaken);
@@ -82,7 +83,7 @@ class ImageCollection with ChangeNotifier {
return success;
}
- updateAlbums() => albums = _rawEntries.map((entry) => entry.bucketDisplayName).toSet();
+ updateAlbums() => albums = _rawEntries.map((entry) => entry.directory).toSet();
updateTags() => tags = _rawEntries.expand((entry) => entry.xmpSubjects).toSet();
@@ -153,6 +154,17 @@ class ImageCollection with ChangeNotifier {
sortFactor: sortFactor,
);
}
+
+ String getUniqueAlbumName(String album, List albums) {
+ final otherAlbums = albums.where((item) => item != album);
+ final parts = album.split(separator);
+ int partCount = 0;
+ String testName;
+ do {
+ testName = separator + parts.skip(parts.length - ++partCount).join(separator);
+ } while (otherAlbums.any((item) => item.endsWith(testName)));
+ return parts.skip(parts.length - partCount).join(separator);
+ }
}
enum SortFactor { date, size }
diff --git a/lib/model/image_entry.dart b/lib/model/image_entry.dart
index cbcaaf58d..64116ef30 100644
--- a/lib/model/image_entry.dart
+++ b/lib/model/image_entry.dart
@@ -13,6 +13,7 @@ import 'mime_types.dart';
class ImageEntry with ChangeNotifier {
String uri;
String path;
+ String directory;
int contentId;
final String mimeType;
int width;
@@ -41,7 +42,7 @@ class ImageEntry with ChangeNotifier {
this.sourceDateTakenMillis,
this.bucketDisplayName,
this.durationMillis,
- });
+ }) : directory = path != null ? dirname(path) : null;
factory ImageEntry.fromMap(Map map) {
return ImageEntry(
diff --git a/lib/utils/android_file_utils.dart b/lib/utils/android_file_utils.dart
new file mode 100644
index 000000000..fa464c3bf
--- /dev/null
+++ b/lib/utils/android_file_utils.dart
@@ -0,0 +1,26 @@
+import 'package:path/path.dart';
+
+final AndroidFileUtils androidFileUtils = AndroidFileUtils._private();
+
+typedef void AndroidFileUtilsCallback(String key, dynamic oldValue, dynamic newValue);
+
+class AndroidFileUtils {
+ String dcimPath;
+ String picturesPath;
+
+ AndroidFileUtils._private();
+
+ init() async {
+ // TODO TLAD find storage root
+ // getExternalStorageDirectory() gives '/storage/emulated/0/Android/data/deckers.thibault.aves/files'
+ final ext = '/storage/emulated/0';
+ dcimPath = join(ext, 'DCIM');
+ picturesPath = join(ext, 'Pictures');
+ }
+
+ 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 isKakaoTalkPath(String path) => path != null && path.startsWith(picturesPath) && path.endsWith('KakaoTalk');
+}
diff --git a/lib/widgets/album/all_collection_drawer.dart b/lib/widgets/album/all_collection_drawer.dart
index cf9b33991..fb4816f13 100644
--- a/lib/widgets/album/all_collection_drawer.dart
+++ b/lib/widgets/album/all_collection_drawer.dart
@@ -71,8 +71,8 @@ class AllCollectionDrawer extends StatelessWidget {
...albums.map((album) => _buildFilteredCollectionNavTile(
context: context,
leading: Icon(Icons.photo_album),
- title: album,
- filter: (entry) => entry.bucketDisplayName == album,
+ title: collection.getUniqueAlbumName(album, albums),
+ filter: (entry) => entry.directory == album,
)),
Divider(),
...tags.map((tag) => _buildFilteredCollectionNavTile(
diff --git a/lib/widgets/album/sections.dart b/lib/widgets/album/sections.dart
index ee8a22073..1e18b747c 100644
--- a/lib/widgets/album/sections.dart
+++ b/lib/widgets/album/sections.dart
@@ -21,7 +21,7 @@ class DaySectionHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return SectionHeader(text: text);
+ return SectionHeader(title: text);
}
}
@@ -43,36 +43,46 @@ class MonthSectionHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return SectionHeader(text: text);
+ return SectionHeader(title: text);
}
}
class SectionHeader extends StatelessWidget {
- final String text;
+ final Widget leading;
+ final String title;
- const SectionHeader({Key key, this.text}) : super(key: key);
+ const SectionHeader({Key key, this.leading, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
+ final text = OutlinedText(
+ title,
+ style: TextStyle(
+ color: Colors.grey[200],
+ fontSize: 20,
+ fontFamily: 'Concourse Caps',
+ shadows: [
+ Shadow(
+ offset: Offset(0, 2),
+ blurRadius: 3,
+ color: Colors.grey[900],
+ ),
+ ],
+ ),
+ outlineColor: Colors.black87,
+ outlineWidth: 2,
+ );
return Container(
padding: EdgeInsets.all(16),
- child: OutlinedText(
- text,
- style: TextStyle(
- color: Colors.grey[200],
- fontSize: 20,
- fontFamily: 'Concourse Caps',
- shadows: [
- Shadow(
- offset: Offset(0, 2),
- blurRadius: 3,
- color: Colors.grey[900],
- ),
- ],
- ),
- outlineColor: Colors.black87,
- outlineWidth: 2,
- ),
+ child: leading != null
+ ? Row(
+ children: [
+ leading,
+ SizedBox(width: 8),
+ text,
+ ],
+ )
+ : text,
);
}
}
diff --git a/lib/widgets/album/thumbnail_collection.dart b/lib/widgets/album/thumbnail_collection.dart
index 1808fda3b..5fc3692ad 100644
--- a/lib/widgets/album/thumbnail_collection.dart
+++ b/lib/widgets/album/thumbnail_collection.dart
@@ -1,11 +1,13 @@
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/sections.dart';
import 'package:aves/widgets/album/thumbnail.dart';
import 'package:aves/widgets/common/draggable_scrollbar.dart';
import 'package:aves/widgets/fullscreen/image_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_sticky_header/flutter_sticky_header.dart';
+import 'package:flutter_svg/flutter_svg.dart';
class ThumbnailCollection extends AnimatedWidget {
final ImageCollection collection;
@@ -96,7 +98,10 @@ class SectionSliver extends StatelessWidget {
if (collection.sortFactor == SortFactor.date) {
switch (collection.groupFactor) {
case GroupFactor.album:
- header = SectionHeader(text: sectionKey);
+ header = SectionHeader(
+ leading: getAlbumIcon(context, sectionKey),
+ title: collection.getUniqueAlbumName(sectionKey, sections.keys.toList()),
+ );
break;
case GroupFactor.date:
header = MonthSectionHeader(date: sectionKey);
@@ -130,6 +135,17 @@ class SectionSliver extends StatelessWidget {
);
}
+ Widget getAlbumIcon(BuildContext context, String albumDirectory) {
+ if (androidFileUtils.isCameraPath(albumDirectory)) {
+ return Icon(Icons.photo_camera);
+ } else if (androidFileUtils.isScreenshotsPath(albumDirectory)) {
+ return Icon(Icons.smartphone);
+ } else if (androidFileUtils.isKakaoTalkPath(albumDirectory)) {
+ return SvgPicture.asset('assets/kakaotalk.svg', width: IconTheme.of(context).size);
+ }
+ return null;
+ }
+
Future _showFullscreen(BuildContext context, ImageEntry entry) {
return Navigator.push(
context,
diff --git a/lib/widgets/debug_page.dart b/lib/widgets/debug_page.dart
index 52b030848..96d96c481 100644
--- a/lib/widgets/debug_page.dart
+++ b/lib/widgets/debug_page.dart
@@ -2,6 +2,7 @@ import 'package:aves/model/image_entry.dart';
import 'package:aves/model/image_metadata.dart';
import 'package:aves/model/metadata_db.dart';
import 'package:aves/model/settings.dart';
+import 'package:aves/utils/android_file_utils.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
@@ -40,6 +41,10 @@ class DebugPageState extends State {
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
+ Text('Paths'),
+ Text('DCIM path: ${androidFileUtils.dcimPath}'),
+ Text('pictures path: ${androidFileUtils.picturesPath}'),
+ Divider(),
Text('Settings'),
Text('collectionGroupFactor: ${settings.collectionGroupFactor}'),
Text('collectionSortFactor: ${settings.collectionSortFactor}'),