fixed auto album identification and naming
This commit is contained in:
parent
63c06c09fc
commit
685e3fe9b6
4 changed files with 44 additions and 10 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
27
test/utils/android_file_utils.dart
Normal file
27
test/utils/android_file_utils.dart
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue