fixed auto album identification and naming

This commit is contained in:
Thibault Deckers 2021-07-30 10:39:54 +09:00
parent 63c06c09fc
commit 685e3fe9b6
4 changed files with 44 additions and 10 deletions

View file

@ -29,7 +29,8 @@ mixin AlbumMixin on SourceBase {
void _notifyAlbumChange() => eventBus.fire(AlbumsChangedEvent()); void _notifyAlbumChange() => eventBus.fire(AlbumsChangedEvent());
String getAlbumDisplayName(BuildContext? context, String dirPath) { String getAlbumDisplayName(BuildContext? context, String dirPath) {
assert(!dirPath.endsWith(pContext.separator)); final separator = pContext.separator;
assert(!dirPath.endsWith(separator));
if (context != null) { if (context != null) {
final type = androidFileUtils.getAlbumType(dirPath); final type = androidFileUtils.getAlbumType(dirPath);
@ -52,8 +53,9 @@ mixin AlbumMixin on SourceBase {
String unique(String dirPath, Set<String?> others) { String unique(String dirPath, Set<String?> others) {
final parts = pContext.split(dirPath); final parts = pContext.split(dirPath);
for (var i = parts.length - 1; i > 0; i--) { for (var i = parts.length - 1; i > 0; i--) {
final testName = pContext.joinAll(['', ...parts.skip(i)]); final name = pContext.joinAll(['', ...parts.skip(i)]);
if (others.every((item) => !item!.endsWith(testName))) return testName; final testName = '$separator$name';
if (others.every((item) => !item!.endsWith(testName))) return name;
} }
return dirPath; return dirPath;
} }

View file

@ -10,7 +10,7 @@ import 'package:flutter/widgets.dart';
final AndroidFileUtils androidFileUtils = AndroidFileUtils._private(); final AndroidFileUtils androidFileUtils = AndroidFileUtils._private();
class AndroidFileUtils { class AndroidFileUtils {
late String primaryStorage, dcimPath, downloadPath, moviesPath, picturesPath, videoCapturesPath; late final String separator, primaryStorage, dcimPath, downloadPath, moviesPath, picturesPath, videoCapturesPath;
Set<StorageVolume> storageVolumes = {}; Set<StorageVolume> storageVolumes = {};
Set<Package> _packages = {}; Set<Package> _packages = {};
List<String> _potentialAppDirs = []; List<String> _potentialAppDirs = [];
@ -22,9 +22,10 @@ class AndroidFileUtils {
AndroidFileUtils._private(); AndroidFileUtils._private();
Future<void> init() async { Future<void> init() async {
separator = pContext.separator;
storageVolumes = await storageService.getStorageVolumes(); storageVolumes = await storageService.getStorageVolumes();
// path_provider getExternalStorageDirectory() gives '/storage/emulated/0/Android/data/deckers.thibault.aves/files' primaryStorage = storageVolumes.firstWhereOrNull((volume) => volume.isPrimary)?.path ?? separator;
primaryStorage = storageVolumes.firstWhereOrNull((volume) => volume.isPrimary)?.path ?? '/'; // standard
dcimPath = pContext.join(primaryStorage, 'DCIM'); dcimPath = pContext.join(primaryStorage, 'DCIM');
downloadPath = pContext.join(primaryStorage, 'Download'); downloadPath = pContext.join(primaryStorage, 'Download');
moviesPath = pContext.join(primaryStorage, 'Movies'); moviesPath = pContext.join(primaryStorage, 'Movies');
@ -39,11 +40,11 @@ class AndroidFileUtils {
appNameChangeNotifier.notifyListeners(); appNameChangeNotifier.notifyListeners();
} }
bool isCameraPath(String path) => path.startsWith(dcimPath) && (path.endsWith('Camera') || path.endsWith('100ANDRO')); bool isCameraPath(String path) => path.startsWith(dcimPath) && (path.endsWith('${separator}Camera') || path.endsWith('${separator}100ANDRO'));
bool isScreenshotsPath(String path) => (path.startsWith(dcimPath) || path.startsWith(picturesPath)) && path.endsWith('Screenshots'); bool isScreenshotsPath(String path) => (path.startsWith(dcimPath) || path.startsWith(picturesPath)) && path.endsWith('${separator}Screenshots');
bool isScreenRecordingsPath(String path) => (path.startsWith(dcimPath) || path.startsWith(moviesPath)) && (path.endsWith('Screen recordings') || path.endsWith('ScreenRecords')); bool isScreenRecordingsPath(String path) => (path.startsWith(dcimPath) || path.startsWith(moviesPath)) && (path.endsWith('${separator}Screen recordings') || path.endsWith('${separator}ScreenRecords'));
bool isVideoCapturesPath(String path) => path == videoCapturesPath; bool isVideoCapturesPath(String path) => path == videoCapturesPath;
@ -54,7 +55,7 @@ class AndroidFileUtils {
final volume = storageVolumes.firstWhereOrNull((v) => path.startsWith(v.path)); final volume = storageVolumes.firstWhereOrNull((v) => path.startsWith(v.path));
// storage volume path includes trailing '/', but argument path may or may not, // storage volume path includes trailing '/', but argument path may or may not,
// which is an issue when the path is at the root // which is an issue when the path is at the root
return volume != null || path.endsWith('/') ? volume : getStorageVolume('$path/'); return volume != null || path.endsWith(separator) ? volume : getStorageVolume('$path$separator');
} }
bool isOnRemovableStorage(String path) => getStorageVolume(path)?.isRemovable ?? false; bool isOnRemovableStorage(String path) => getStorageVolume(path)?.isRemovable ?? false;

View file

@ -259,6 +259,8 @@ void main() {
FakeMediaStoreService.newImage('${FakeStorageService.primaryPath}Seneca', '1'), FakeMediaStoreService.newImage('${FakeStorageService.primaryPath}Seneca', '1'),
FakeMediaStoreService.newImage('${FakeStorageService.removablePath}Pictures/Cicero', '1'), FakeMediaStoreService.newImage('${FakeStorageService.removablePath}Pictures/Cicero', '1'),
FakeMediaStoreService.newImage('${FakeStorageService.removablePath}Marcus Aurelius', '1'), FakeMediaStoreService.newImage('${FakeStorageService.removablePath}Marcus Aurelius', '1'),
FakeMediaStoreService.newImage('${FakeStorageService.primaryPath}Pictures/Hannah Arendt', '1'),
FakeMediaStoreService.newImage('${FakeStorageService.primaryPath}Pictures/Arendt', '1'),
}; };
await androidFileUtils.init(); await androidFileUtils.init();
@ -276,6 +278,8 @@ void main() {
expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Seneca'), 'Seneca'); expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Seneca'), 'Seneca');
expect(source.getAlbumDisplayName(context, '${FakeStorageService.removablePath}Pictures/Cicero'), 'Cicero'); expect(source.getAlbumDisplayName(context, '${FakeStorageService.removablePath}Pictures/Cicero'), 'Cicero');
expect(source.getAlbumDisplayName(context, '${FakeStorageService.removablePath}Marcus Aurelius'), 'Marcus Aurelius'); expect(source.getAlbumDisplayName(context, '${FakeStorageService.removablePath}Marcus Aurelius'), 'Marcus Aurelius');
expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Hannah Arendt'), 'Hannah Arendt');
expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Arendt'), 'Arendt');
return const Placeholder(); return const Placeholder();
}, },
), ),

View file

@ -0,0 +1,27 @@
import 'package:aves/services/services.dart';
import 'package:aves/services/storage_service.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import '../fake/storage_service.dart';
void main() {
setUp(() async {
// specify Posix style path context for consistent behaviour when running tests on Windows
getIt.registerLazySingleton<p.Context>(() => p.Context(style: p.Style.posix));
getIt.registerLazySingleton<StorageService>(() => FakeStorageService());
await androidFileUtils.init();
});
tearDown(() async {
await getIt.reset();
});
test('camera album identification', () {
expect(androidFileUtils.isCameraPath('${FakeStorageService.primaryPath}DCIM/Camera'), true);
expect(androidFileUtils.isCameraPath('${FakeStorageService.primaryPath}DCIM/YoloCamera'), false);
});
}