albums: distinct naming improvements & tests, localized common albums
This commit is contained in:
parent
0464bd8678
commit
0db76a46de
34 changed files with 241 additions and 108 deletions
|
@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
|
|||
## [Unreleased]
|
||||
### Added
|
||||
- Collection / Albums / Countries / Tags: added label when dragging scrollbar thumb
|
||||
- Albums: localized common album names
|
||||
|
||||
### Changed
|
||||
- Upgraded Flutter to beta v2.1.0-12.2.pre
|
||||
|
|
|
@ -6,7 +6,6 @@ import android.net.Uri
|
|||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.os.storage.StorageManager
|
||||
import android.util.Log
|
||||
import androidx.core.os.EnvironmentCompat
|
||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
||||
import deckers.thibault.aves.utils.PermissionManager
|
||||
|
@ -150,7 +149,6 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
|
|||
try {
|
||||
val dir = File(it)
|
||||
if (dir.isDirectory && dir.listFiles()?.isEmpty() == true && dir.delete()) {
|
||||
Log.d("TLAD", "deleted empty directory=$dir")
|
||||
deleted++
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
|
|
|
@ -450,6 +450,15 @@
|
|||
"albumPickPageTitleMove": "Move to Album",
|
||||
"@albumPickPageTitleMove": {},
|
||||
|
||||
"albumCamera": "Camera",
|
||||
"@albumCamera": {},
|
||||
"albumDownload": "Download",
|
||||
"@albumDownload": {},
|
||||
"albumScreenshots": "Screenshots",
|
||||
"@albumScreenshots": {},
|
||||
"albumScreenRecordings": "Screen recordings",
|
||||
"@albumScreenRecordings": {},
|
||||
|
||||
"albumPageTitle": "Albums",
|
||||
"@albumPageTitle": {},
|
||||
"albumEmpty": "No albums",
|
||||
|
|
|
@ -201,6 +201,11 @@
|
|||
"albumPickPageTitleExport": "앨범으로 내보내기",
|
||||
"albumPickPageTitleMove": "앨범으로 이동",
|
||||
|
||||
"albumCamera": "카메라",
|
||||
"albumDownload": "다운로드",
|
||||
"albumScreenshots": "스크린샷",
|
||||
"albumScreenRecordings": "화면 녹화 파일",
|
||||
|
||||
"albumPageTitle": "앨범",
|
||||
"albumEmpty": "앨범이 없습니다",
|
||||
"createAlbumTooltip": "새 앨범 만들기",
|
||||
|
|
|
@ -18,7 +18,6 @@ import 'package:country_code/country_code.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:latlong/latlong.dart';
|
||||
import 'package:path/path.dart' as ppath;
|
||||
|
||||
import '../ref/mime_types.dart';
|
||||
|
||||
|
@ -186,17 +185,17 @@ class AvesEntry {
|
|||
String get path => _path;
|
||||
|
||||
String get directory {
|
||||
_directory ??= path != null ? ppath.dirname(path) : null;
|
||||
_directory ??= path != null ? pContext.dirname(path) : null;
|
||||
return _directory;
|
||||
}
|
||||
|
||||
String get filenameWithoutExtension {
|
||||
_filename ??= path != null ? ppath.basenameWithoutExtension(path) : null;
|
||||
_filename ??= path != null ? pContext.basenameWithoutExtension(path) : null;
|
||||
return _filename;
|
||||
}
|
||||
|
||||
String get extension {
|
||||
_extension ??= path != null ? ppath.extension(path) : null;
|
||||
_extension ??= path != null ? pContext.extension(path) : null;
|
||||
return _extension;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import 'package:aves/image_providers/app_icon_image_provider.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/services/services.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_icons.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:palette_generator/palette_generator.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
class AlbumFilter extends CollectionFilter {
|
||||
static const type = 'album';
|
||||
|
@ -14,9 +14,9 @@ class AlbumFilter extends CollectionFilter {
|
|||
static final Map<String, Color> _appColors = {};
|
||||
|
||||
final String album;
|
||||
final String uniqueName;
|
||||
final String displayName;
|
||||
|
||||
const AlbumFilter(this.album, this.uniqueName);
|
||||
const AlbumFilter(this.album, this.displayName);
|
||||
|
||||
AlbumFilter.fromMap(Map<String, dynamic> json)
|
||||
: this(
|
||||
|
@ -28,14 +28,14 @@ class AlbumFilter extends CollectionFilter {
|
|||
Map<String, dynamic> toMap() => {
|
||||
'type': type,
|
||||
'album': album,
|
||||
'uniqueName': uniqueName,
|
||||
'uniqueName': displayName,
|
||||
};
|
||||
|
||||
@override
|
||||
EntryFilter get test => (entry) => entry.directory == album;
|
||||
|
||||
@override
|
||||
String get universalLabel => uniqueName ?? album.split(separator).last;
|
||||
String get universalLabel => displayName ?? pContext.split(album).last;
|
||||
|
||||
@override
|
||||
String getTooltip(BuildContext context) => album;
|
||||
|
|
|
@ -5,8 +5,8 @@ import 'package:aves/model/entry.dart';
|
|||
import 'package:aves/model/favourites.dart';
|
||||
import 'package:aves/model/metadata.dart';
|
||||
import 'package:aves/model/metadata_db_upgrade.dart';
|
||||
import 'package:aves/services/services.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
abstract class MetadataDb {
|
||||
|
@ -82,7 +82,7 @@ abstract class MetadataDb {
|
|||
class SqfliteMetadataDb implements MetadataDb {
|
||||
Future<Database> _database;
|
||||
|
||||
Future<String> get path async => join(await getDatabasesPath(), 'metadata.db');
|
||||
Future<String> get path async => pContext.join(await getDatabasesPath(), 'metadata.db');
|
||||
|
||||
static const entryTable = 'entry';
|
||||
static const dateTakenTable = 'dateTaken';
|
||||
|
|
|
@ -2,10 +2,11 @@ import 'package:aves/model/entry.dart';
|
|||
import 'package:aves/model/filters/album.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/services/services.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
mixin AlbumMixin on SourceBase {
|
||||
final Set<String> _directories = {};
|
||||
|
@ -13,8 +14,8 @@ mixin AlbumMixin on SourceBase {
|
|||
List<String> get rawAlbums => List.unmodifiable(_directories);
|
||||
|
||||
int compareAlbumsByName(String a, String b) {
|
||||
final ua = getUniqueAlbumName(null, a);
|
||||
final ub = getUniqueAlbumName(null, b);
|
||||
final ua = getAlbumDisplayName(null, a);
|
||||
final ub = getAlbumDisplayName(null, b);
|
||||
final c = compareAsciiUpperCase(ua, ub);
|
||||
if (c != 0) return c;
|
||||
final va = androidFileUtils.getStorageVolume(a)?.path ?? '';
|
||||
|
@ -24,36 +25,50 @@ mixin AlbumMixin on SourceBase {
|
|||
|
||||
void _notifyAlbumChange() => eventBus.fire(AlbumsChangedEvent());
|
||||
|
||||
String getUniqueAlbumName(BuildContext context, String dirPath) {
|
||||
String unique(String dirPath, [bool Function(String) test]) {
|
||||
final otherAlbums = _directories.where(test ?? (_) => true).where((item) => item != dirPath);
|
||||
final parts = dirPath.split(separator);
|
||||
var partCount = 0;
|
||||
String testName;
|
||||
do {
|
||||
testName = separator + parts.skip(parts.length - ++partCount).join(separator);
|
||||
} while (otherAlbums.any((item) => item.endsWith(testName)));
|
||||
final uniqueName = parts.skip(parts.length - partCount).join(separator);
|
||||
return uniqueName;
|
||||
String getAlbumDisplayName(BuildContext context, String dirPath) {
|
||||
assert(!dirPath.endsWith(pContext.separator));
|
||||
|
||||
if (context != null) {
|
||||
final type = androidFileUtils.getAlbumType(dirPath);
|
||||
if (type == AlbumType.camera) return context.l10n.albumCamera;
|
||||
if (type == AlbumType.download) return context.l10n.albumDownload;
|
||||
if (type == AlbumType.screenshots) return context.l10n.albumScreenshots;
|
||||
if (type == AlbumType.screenRecordings) return context.l10n.albumScreenRecordings;
|
||||
}
|
||||
|
||||
final dir = VolumeRelativeDirectory.fromPath(dirPath);
|
||||
if (dir == null) return dirPath;
|
||||
|
||||
final uniqueNameInDevice = unique(dirPath);
|
||||
final relativeDir = dir.relativeDir;
|
||||
if (relativeDir.isEmpty) return uniqueNameInDevice;
|
||||
if (relativeDir.isEmpty) {
|
||||
final volume = androidFileUtils.getStorageVolume(dirPath);
|
||||
return volume.getDescription(context);
|
||||
}
|
||||
|
||||
String unique(String dirPath, Set<String> others) {
|
||||
final parts = pContext.split(dirPath);
|
||||
for (var i = parts.length - 1; i > 0; i--) {
|
||||
final testName = pContext.joinAll(['', ...parts.skip(i)]);
|
||||
if (others.every((item) => !item.endsWith(testName))) return testName;
|
||||
}
|
||||
return dirPath;
|
||||
}
|
||||
|
||||
final otherAlbumsOnDevice = _directories.where((item) => item != dirPath).toSet();
|
||||
final uniqueNameInDevice = unique(dirPath, otherAlbumsOnDevice);
|
||||
if (uniqueNameInDevice.length < relativeDir.length) {
|
||||
return uniqueNameInDevice;
|
||||
}
|
||||
|
||||
final volumePath = dir.volumePath;
|
||||
String trimVolumePath(String path) => path.substring(dir.volumePath.length);
|
||||
final otherAlbumsOnVolume = otherAlbumsOnDevice.where((path) => path.startsWith(volumePath)).map(trimVolumePath).toSet();
|
||||
final uniqueNameInVolume = unique(trimVolumePath(dirPath), otherAlbumsOnVolume);
|
||||
final volume = androidFileUtils.getStorageVolume(dirPath);
|
||||
if (volume.isPrimary) {
|
||||
return uniqueNameInVolume;
|
||||
} else {
|
||||
final uniqueNameInVolume = unique(dirPath, (item) => item.startsWith(dir.volumePath));
|
||||
final volume = androidFileUtils.getStorageVolume(dirPath);
|
||||
if (volume.isPrimary) {
|
||||
return uniqueNameInVolume;
|
||||
} else {
|
||||
return '$uniqueNameInVolume (${volume.getDescription(context)})';
|
||||
}
|
||||
return '$uniqueNameInVolume (${volume.getDescription(context)})';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,25 +3,31 @@ import 'package:aves/model/metadata_db.dart';
|
|||
import 'package:aves/services/image_file_service.dart';
|
||||
import 'package:aves/services/media_store_service.dart';
|
||||
import 'package:aves/services/metadata_service.dart';
|
||||
import 'package:aves/services/storage_service.dart';
|
||||
import 'package:aves/services/time_service.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
final pContext = getIt<p.Context>();
|
||||
final availability = getIt<AvesAvailability>();
|
||||
final metadataDb = getIt<MetadataDb>();
|
||||
|
||||
final imageFileService = getIt<ImageFileService>();
|
||||
final mediaStoreService = getIt<MediaStoreService>();
|
||||
final metadataService = getIt<MetadataService>();
|
||||
final storageService = getIt<StorageService>();
|
||||
final timeService = getIt<TimeService>();
|
||||
|
||||
void initPlatformServices() {
|
||||
getIt.registerLazySingleton<p.Context>(() => p.Context());
|
||||
getIt.registerLazySingleton<AvesAvailability>(() => LiveAvesAvailability());
|
||||
getIt.registerLazySingleton<MetadataDb>(() => SqfliteMetadataDb());
|
||||
|
||||
getIt.registerLazySingleton<ImageFileService>(() => PlatformImageFileService());
|
||||
getIt.registerLazySingleton<MediaStoreService>(() => PlatformMediaStoreService());
|
||||
getIt.registerLazySingleton<MetadataService>(() => PlatformMetadataService());
|
||||
getIt.registerLazySingleton<StorageService>(() => PlatformStorageService());
|
||||
getIt.registerLazySingleton<TimeService>(() => PlatformTimeService());
|
||||
}
|
||||
|
|
|
@ -5,11 +5,35 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:streams_channel/streams_channel.dart';
|
||||
|
||||
class StorageService {
|
||||
abstract class StorageService {
|
||||
Future<Set<StorageVolume>> getStorageVolumes();
|
||||
|
||||
Future<int> getFreeSpace(StorageVolume volume);
|
||||
|
||||
Future<List<String>> getGrantedDirectories();
|
||||
|
||||
Future<void> revokeDirectoryAccess(String path);
|
||||
|
||||
Future<Set<VolumeRelativeDirectory>> getInaccessibleDirectories(Iterable<String> dirPaths);
|
||||
|
||||
Future<Set<VolumeRelativeDirectory>> getRestrictedDirectories();
|
||||
|
||||
// returns whether user granted access to volume root at `volumePath`
|
||||
Future<bool> requestVolumeAccess(String volumePath);
|
||||
|
||||
// returns number of deleted directories
|
||||
Future<int> deleteEmptyDirectories(Iterable<String> dirPaths);
|
||||
|
||||
// returns media URI
|
||||
Future<Uri> scanFile(String path, String mimeType);
|
||||
}
|
||||
|
||||
class PlatformStorageService implements StorageService {
|
||||
static const platform = MethodChannel('deckers.thibault/aves/storage');
|
||||
static final StreamsChannel storageAccessChannel = StreamsChannel('deckers.thibault/aves/storageaccessstream');
|
||||
|
||||
static Future<Set<StorageVolume>> getStorageVolumes() async {
|
||||
@override
|
||||
Future<Set<StorageVolume>> getStorageVolumes() async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getStorageVolumes');
|
||||
return (result as List).cast<Map>().map((map) => StorageVolume.fromMap(map)).toSet();
|
||||
|
@ -19,7 +43,8 @@ class StorageService {
|
|||
return {};
|
||||
}
|
||||
|
||||
static Future<int> getFreeSpace(StorageVolume volume) async {
|
||||
@override
|
||||
Future<int> getFreeSpace(StorageVolume volume) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getFreeSpace', <String, dynamic>{
|
||||
'path': volume.path,
|
||||
|
@ -31,7 +56,8 @@ class StorageService {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static Future<List<String>> getGrantedDirectories() async {
|
||||
@override
|
||||
Future<List<String>> getGrantedDirectories() async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getGrantedDirectories');
|
||||
return (result as List).cast<String>();
|
||||
|
@ -41,7 +67,8 @@ class StorageService {
|
|||
return [];
|
||||
}
|
||||
|
||||
static Future<void> revokeDirectoryAccess(String path) async {
|
||||
@override
|
||||
Future<void> revokeDirectoryAccess(String path) async {
|
||||
try {
|
||||
await platform.invokeMethod('revokeDirectoryAccess', <String, dynamic>{
|
||||
'path': path,
|
||||
|
@ -52,7 +79,8 @@ class StorageService {
|
|||
return;
|
||||
}
|
||||
|
||||
static Future<Set<VolumeRelativeDirectory>> getInaccessibleDirectories(Iterable<String> dirPaths) async {
|
||||
@override
|
||||
Future<Set<VolumeRelativeDirectory>> getInaccessibleDirectories(Iterable<String> dirPaths) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getInaccessibleDirectories', <String, dynamic>{
|
||||
'dirPaths': dirPaths.toList(),
|
||||
|
@ -64,7 +92,8 @@ class StorageService {
|
|||
return null;
|
||||
}
|
||||
|
||||
static Future<Set<VolumeRelativeDirectory>> getRestrictedDirectories() async {
|
||||
@override
|
||||
Future<Set<VolumeRelativeDirectory>> getRestrictedDirectories() async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getRestrictedDirectories');
|
||||
return (result as List).cast<Map>().map((map) => VolumeRelativeDirectory.fromMap(map)).toSet();
|
||||
|
@ -75,7 +104,8 @@ class StorageService {
|
|||
}
|
||||
|
||||
// returns whether user granted access to volume root at `volumePath`
|
||||
static Future<bool> requestVolumeAccess(String volumePath) async {
|
||||
@override
|
||||
Future<bool> requestVolumeAccess(String volumePath) async {
|
||||
try {
|
||||
final completer = Completer<bool>();
|
||||
storageAccessChannel.receiveBroadcastStream(<String, dynamic>{
|
||||
|
@ -96,7 +126,8 @@ class StorageService {
|
|||
}
|
||||
|
||||
// returns number of deleted directories
|
||||
static Future<int> deleteEmptyDirectories(Iterable<String> dirPaths) async {
|
||||
@override
|
||||
Future<int> deleteEmptyDirectories(Iterable<String> dirPaths) async {
|
||||
try {
|
||||
return await platform.invokeMethod('deleteEmptyDirectories', <String, dynamic>{
|
||||
'dirPaths': dirPaths.toList(),
|
||||
|
@ -108,7 +139,8 @@ class StorageService {
|
|||
}
|
||||
|
||||
// returns media URI
|
||||
static Future<Uri> scanFile(String path, String mimeType) async {
|
||||
@override
|
||||
Future<Uri> scanFile(String path, String mimeType) async {
|
||||
debugPrint('scanFile with path=$path, mimeType=$mimeType');
|
||||
try {
|
||||
final uriString = await platform.invokeMethod('scanFile', <String, dynamic>{
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import 'package:aves/services/android_app_service.dart';
|
||||
import 'package:aves/services/storage_service.dart';
|
||||
import 'package:aves/services/services.dart';
|
||||
import 'package:aves/utils/change_notifier.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
final AndroidFileUtils androidFileUtils = AndroidFileUtils._private();
|
||||
|
||||
|
@ -21,13 +20,13 @@ class AndroidFileUtils {
|
|||
AndroidFileUtils._private();
|
||||
|
||||
Future<void> init() async {
|
||||
storageVolumes = await StorageService.getStorageVolumes();
|
||||
storageVolumes = await storageService.getStorageVolumes();
|
||||
// path_provider getExternalStorageDirectory() gives '/storage/emulated/0/Android/data/deckers.thibault.aves/files'
|
||||
primaryStorage = storageVolumes.firstWhere((volume) => volume.isPrimary).path;
|
||||
dcimPath = join(primaryStorage, 'DCIM');
|
||||
downloadPath = join(primaryStorage, 'Download');
|
||||
moviesPath = join(primaryStorage, 'Movies');
|
||||
picturesPath = join(primaryStorage, 'Pictures');
|
||||
dcimPath = pContext.join(primaryStorage, 'DCIM');
|
||||
downloadPath = pContext.join(primaryStorage, 'Download');
|
||||
moviesPath = pContext.join(primaryStorage, 'Movies');
|
||||
picturesPath = pContext.join(primaryStorage, 'Pictures');
|
||||
}
|
||||
|
||||
Future<void> initAppNames() async {
|
||||
|
@ -60,7 +59,7 @@ class AndroidFileUtils {
|
|||
if (isScreenRecordingsPath(albumPath)) return AlbumType.screenRecordings;
|
||||
if (isScreenshotsPath(albumPath)) return AlbumType.screenshots;
|
||||
|
||||
final dir = albumPath.split(separator).last;
|
||||
final dir = pContext.split(albumPath).last;
|
||||
if (albumPath.startsWith(primaryStorage) && _potentialAppDirs.contains(dir)) return AlbumType.app;
|
||||
}
|
||||
return AlbumType.regular;
|
||||
|
@ -68,7 +67,7 @@ class AndroidFileUtils {
|
|||
|
||||
String getAlbumAppPackageName(String albumPath) {
|
||||
if (albumPath == null) return null;
|
||||
final dir = albumPath.split(separator).last;
|
||||
final dir = pContext.split(albumPath).last;
|
||||
final package = _launcherPackages.firstWhere((package) => package.potentialDirs.contains(dir), orElse: () => null);
|
||||
return package?.packageName;
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class CollectionDraggableThumbLabel extends StatelessWidget {
|
|||
case EntryGroupFactor.album:
|
||||
return [
|
||||
DraggableThumbLabel.formatMonthThumbLabel(context, entry.bestDate),
|
||||
if (_hasMultipleSections(context)) context.read<CollectionSource>().getUniqueAlbumName(context, entry.directory),
|
||||
if (_hasMultipleSections(context)) context.read<CollectionSource>().getAlbumDisplayName(context, entry.directory),
|
||||
];
|
||||
case EntryGroupFactor.month:
|
||||
case EntryGroupFactor.none:
|
||||
|
@ -43,7 +43,7 @@ class CollectionDraggableThumbLabel extends StatelessWidget {
|
|||
break;
|
||||
case EntrySortFactor.name:
|
||||
return [
|
||||
if (_hasMultipleSections(context)) context.read<CollectionSource>().getUniqueAlbumName(context, entry.directory),
|
||||
if (_hasMultipleSections(context)) context.read<CollectionSource>().getAlbumDisplayName(context, entry.directory),
|
||||
entry.bestTitle,
|
||||
];
|
||||
case EntrySortFactor.size:
|
||||
|
|
|
@ -9,7 +9,6 @@ import 'package:aves/model/source/collection_source.dart';
|
|||
import 'package:aves/services/android_app_service.dart';
|
||||
import 'package:aves/services/image_op_events.dart';
|
||||
import 'package:aves/services/services.dart';
|
||||
import 'package:aves/services/storage_service.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/permission_aware.dart';
|
||||
|
@ -69,7 +68,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
if (moveType == MoveType.move) {
|
||||
// check whether moving is possible given OS restrictions,
|
||||
// before asking to pick a destination album
|
||||
final restrictedDirs = await StorageService.getRestrictedDirectories();
|
||||
final restrictedDirs = await storageService.getRestrictedDirectories();
|
||||
for (final selectionDir in selectionDirs) {
|
||||
final dir = VolumeRelativeDirectory.fromPath(selectionDir);
|
||||
if (restrictedDirs.contains(dir)) {
|
||||
|
@ -127,7 +126,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
|
||||
// cleanup
|
||||
if (moveType == MoveType.move) {
|
||||
await StorageService.deleteEmptyDirectories(selectionDirs);
|
||||
await storageService.deleteEmptyDirectories(selectionDirs);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -178,7 +177,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
}
|
||||
|
||||
// cleanup
|
||||
await StorageService.deleteEmptyDirectories(selectionDirs);
|
||||
await storageService.deleteEmptyDirectories(selectionDirs);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ class AlbumSectionHeader extends StatelessWidget {
|
|||
return SectionHeader.getPreferredHeight(
|
||||
context: context,
|
||||
maxWidth: maxWidth,
|
||||
title: source.getUniqueAlbumName(context, directory),
|
||||
title: source.getAlbumDisplayName(context, directory),
|
||||
hasLeading: androidFileUtils.getAlbumType(directory) != AlbumType.regular,
|
||||
hasTrailing: androidFileUtils.isOnRemovableStorage(directory),
|
||||
);
|
||||
|
|
|
@ -60,7 +60,7 @@ class CollectionSectionHeader extends StatelessWidget {
|
|||
return AlbumSectionHeader(
|
||||
key: ValueKey(sectionKey),
|
||||
directory: directory,
|
||||
albumName: source.getUniqueAlbumName(context, directory),
|
||||
albumName: source.getAlbumDisplayName(context, directory),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/services/storage_service.dart';
|
||||
import 'package:aves/services/services.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||
|
@ -11,9 +11,9 @@ mixin PermissionAwareMixin {
|
|||
}
|
||||
|
||||
Future<bool> checkStoragePermissionForAlbums(BuildContext context, Set<String> albumPaths) async {
|
||||
final restrictedDirs = await StorageService.getRestrictedDirectories();
|
||||
final restrictedDirs = await storageService.getRestrictedDirectories();
|
||||
while (true) {
|
||||
final dirs = await StorageService.getInaccessibleDirectories(albumPaths);
|
||||
final dirs = await storageService.getInaccessibleDirectories(albumPaths);
|
||||
if (dirs == null) return false;
|
||||
if (dirs.isEmpty) return true;
|
||||
|
||||
|
@ -49,7 +49,7 @@ mixin PermissionAwareMixin {
|
|||
// abort if the user cancels in Flutter
|
||||
if (confirmed == null || !confirmed) return false;
|
||||
|
||||
final granted = await StorageService.requestVolumeAccess(dir.volumePath);
|
||||
final granted = await storageService.requestVolumeAccess(dir.volumePath);
|
||||
if (!granted) {
|
||||
// abort if the user denies access from the native dialog
|
||||
return false;
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:math';
|
|||
|
||||
import 'package:aves/model/actions/move_type.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/services/storage_service.dart';
|
||||
import 'package:aves/services/services.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/utils/file_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
|
@ -20,7 +20,7 @@ mixin SizeAwareMixin {
|
|||
MoveType moveType,
|
||||
) async {
|
||||
final destinationVolume = androidFileUtils.getStorageVolume(destinationAlbum);
|
||||
final free = await StorageService.getFreeSpace(destinationVolume);
|
||||
final free = await storageService.getFreeSpace(destinationVolume);
|
||||
int needed;
|
||||
int sumSize(sum, entry) => sum + entry.sizeBytes;
|
||||
switch (moveType) {
|
||||
|
|
|
@ -17,7 +17,7 @@ class DraggableThumbLabel<T> extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final sll = context.read<SectionedListLayout<T>>();
|
||||
final sectionLayout = sll.getSectionAt(offsetY);
|
||||
if (sectionLayout == null) return null;
|
||||
if (sectionLayout == null) return SizedBox();
|
||||
|
||||
final section = sll.sections[sectionLayout.sectionKey];
|
||||
final dy = offsetY - (sectionLayout.minOffset + sectionLayout.headerExtent);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:aves/services/storage_service.dart';
|
||||
import 'package:aves/services/services.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/utils/file_utils.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||
|
@ -17,7 +17,7 @@ class _DebugStorageSectionState extends State<DebugStorageSection> with Automati
|
|||
void initState() {
|
||||
super.initState();
|
||||
androidFileUtils.storageVolumes.forEach((volume) async {
|
||||
final byteCount = await StorageService.getFreeSpace(volume);
|
||||
final byteCount = await storageService.getFreeSpace(volume);
|
||||
setState(() => _freeSpaceByVolume[volume.path] = byteCount);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:aves/services/services.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
import 'aves_dialog.dart';
|
||||
|
||||
|
@ -143,7 +143,7 @@ class _CreateAlbumDialogState extends State<CreateAlbumDialog> {
|
|||
|
||||
String _buildAlbumPath(String name) {
|
||||
if (name == null || name.isEmpty) return '';
|
||||
return join(_selectedVolume.path, 'Pictures', name);
|
||||
return pContext.join(_selectedVolume.path, 'Pictures', name);
|
||||
}
|
||||
|
||||
Future<void> _validate() async {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:aves/services/services.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import '../dialogs/aves_dialog.dart';
|
||||
|
||||
|
@ -22,7 +22,7 @@ class _RenameAlbumDialogState extends State<RenameAlbumDialog> {
|
|||
|
||||
String get album => widget.album;
|
||||
|
||||
String get initialValue => path.basename(album);
|
||||
String get initialValue => pContext.basename(album);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -75,7 +75,7 @@ class _RenameAlbumDialogState extends State<RenameAlbumDialog> {
|
|||
|
||||
String _buildAlbumPath(String name) {
|
||||
if (name == null || name.isEmpty) return '';
|
||||
return path.join(path.dirname(album), name);
|
||||
return pContext.join(pContext.dirname(album), name);
|
||||
}
|
||||
|
||||
Future<void> _validate() async {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/services/services.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'aves_dialog.dart';
|
||||
|
||||
|
@ -69,7 +69,7 @@ class _RenameEntryDialogState extends State<RenameEntryDialog> {
|
|||
|
||||
String _buildEntryPath(String name) {
|
||||
if (name == null || name.isEmpty) return '';
|
||||
return path.join(entry.directory, name + entry.extension);
|
||||
return pContext.join(entry.directory, name + entry.extension);
|
||||
}
|
||||
|
||||
Future<void> _validate() async {
|
||||
|
|
|
@ -15,13 +15,13 @@ class AlbumTile extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final source = context.read<CollectionSource>();
|
||||
final uniqueName = source.getUniqueAlbumName(context, album);
|
||||
final displayName = source.getAlbumDisplayName(context, album);
|
||||
return CollectionNavTile(
|
||||
leading: IconUtils.getAlbumIcon(
|
||||
context: context,
|
||||
album: album,
|
||||
),
|
||||
title: uniqueName,
|
||||
title: displayName,
|
||||
trailing: androidFileUtils.isOnRemovableStorage(album)
|
||||
? Icon(
|
||||
AIcons.removableStorage,
|
||||
|
@ -29,7 +29,7 @@ class AlbumTile extends StatelessWidget {
|
|||
color: Colors.grey,
|
||||
)
|
||||
: null,
|
||||
filter: AlbumFilter(album, uniqueName),
|
||||
filter: AlbumFilter(album, displayName),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ class _AlbumPickPageState extends State<AlbumPickPage> {
|
|||
applyQuery: (filters, query) {
|
||||
if (query == null || query.isEmpty) return filters;
|
||||
query = query.toUpperCase();
|
||||
return filters.where((item) => item.filter.uniqueName.toUpperCase().contains(query)).toList();
|
||||
return filters.where((item) => item.filter.displayName.toUpperCase().contains(query)).toList();
|
||||
},
|
||||
emptyBuilder: () => EmptyContent(
|
||||
icon: AIcons.album,
|
||||
|
|
|
@ -61,7 +61,7 @@ class AlbumListPage extends StatelessWidget {
|
|||
// common with album selection page to move/copy entries
|
||||
|
||||
static Map<ChipSectionKey, List<FilterGridItem<AlbumFilter>>> getAlbumEntries(BuildContext context, CollectionSource source) {
|
||||
final filters = source.rawAlbums.map((album) => AlbumFilter(album, source.getUniqueAlbumName(context, album))).toSet();
|
||||
final filters = source.rawAlbums.map((album) => AlbumFilter(album, source.getAlbumDisplayName(context, album))).toSet();
|
||||
|
||||
final sorted = FilterNavigationPage.sort(settings.albumSortFactor, source, filters);
|
||||
return _group(context, sorted);
|
||||
|
|
|
@ -11,7 +11,6 @@ import 'package:aves/model/settings/settings.dart';
|
|||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/services/image_op_events.dart';
|
||||
import 'package:aves/services/services.dart';
|
||||
import 'package:aves/services/storage_service.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/permission_aware.dart';
|
||||
|
@ -24,7 +23,6 @@ import 'package:aves/widgets/filter_grids/albums_page.dart';
|
|||
import 'package:aves/widgets/filter_grids/countries_page.dart';
|
||||
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
|
@ -181,7 +179,7 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per
|
|||
}
|
||||
|
||||
// cleanup
|
||||
await StorageService.deleteEmptyDirectories({album});
|
||||
await storageService.deleteEmptyDirectories({album});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -196,7 +194,7 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per
|
|||
|
||||
// check whether renaming is possible given OS restrictions,
|
||||
// before asking to input a new name
|
||||
final restrictedDirs = await StorageService.getRestrictedDirectories();
|
||||
final restrictedDirs = await storageService.getRestrictedDirectories();
|
||||
final dir = VolumeRelativeDirectory.fromPath(album);
|
||||
if (restrictedDirs.contains(dir)) {
|
||||
await showRestrictedDirectoryDialog(context, dir);
|
||||
|
@ -211,8 +209,8 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per
|
|||
|
||||
if (!await checkStoragePermissionForAlbums(context, {album})) return;
|
||||
|
||||
final destinationAlbumParent = path.dirname(album);
|
||||
final destinationAlbum = path.join(destinationAlbumParent, newName);
|
||||
final destinationAlbumParent = pContext.dirname(album);
|
||||
final destinationAlbum = pContext.join(destinationAlbumParent, newName);
|
||||
if (!await checkFreeSpaceForMove(context, todoEntries, destinationAlbum, MoveType.move)) return;
|
||||
|
||||
if (!(await File(destinationAlbum).exists())) {
|
||||
|
@ -239,7 +237,7 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per
|
|||
}
|
||||
|
||||
// cleanup
|
||||
await StorageService.deleteEmptyDirectories({album});
|
||||
await storageService.deleteEmptyDirectories({album});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -117,8 +117,14 @@ class CollectionSearchDelegate {
|
|||
StreamBuilder(
|
||||
stream: source.eventBus.on<AlbumsChangedEvent>(),
|
||||
builder: (context, snapshot) {
|
||||
// filter twice: full path, and then unique name
|
||||
final filters = source.rawAlbums.where(containQuery).map((s) => AlbumFilter(s, source.getUniqueAlbumName(context, s))).where((f) => containQuery(f.uniqueName)).toList()..sort();
|
||||
final filters = source.rawAlbums
|
||||
.map((album) => AlbumFilter(
|
||||
album,
|
||||
source.getAlbumDisplayName(context, album),
|
||||
))
|
||||
.where((filter) => containQuery(filter.album) || containQuery(filter.displayName))
|
||||
.toList()
|
||||
..sort();
|
||||
return _buildFilterRow(
|
||||
context: context,
|
||||
title: context.l10n.searchSectionAlbums,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:aves/services/storage_service.dart';
|
||||
import 'package:aves/services/services.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/empty.dart';
|
||||
|
@ -39,7 +39,7 @@ class _StorageAccessPageState extends State<StorageAccessPage> {
|
|||
_load();
|
||||
}
|
||||
|
||||
void _load() => _pathLoader = StorageService.getGrantedDirectories();
|
||||
void _load() => _pathLoader = storageService.getGrantedDirectories();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -87,7 +87,7 @@ class _StorageAccessPageState extends State<StorageAccessPage> {
|
|||
trailing: IconButton(
|
||||
icon: Icon(AIcons.clear),
|
||||
onPressed: () async {
|
||||
await StorageService.revokeDirectoryAccess(path);
|
||||
await storageService.revokeDirectoryAccess(path);
|
||||
_load();
|
||||
setState(() {});
|
||||
},
|
||||
|
|
|
@ -82,7 +82,7 @@ class BasicSection extends StatelessWidget {
|
|||
if (entry.isImage && entry.is360) TypeFilter.panorama,
|
||||
if (entry.isVideo && entry.is360) TypeFilter.sphericalVideo,
|
||||
if (entry.isVideo && !entry.is360) MimeFilter.video,
|
||||
if (album != null) AlbumFilter(album, collection?.source?.getUniqueAlbumName(context, album)),
|
||||
if (album != null) AlbumFilter(album, collection?.source?.getAlbumDisplayName(context, album)),
|
||||
...tags.map((tag) => TagFilter(tag)),
|
||||
};
|
||||
return AnimatedBuilder(
|
||||
|
|
28
test/fake/storage_service.dart
Normal file
28
test/fake/storage_service.dart
Normal file
|
@ -0,0 +1,28 @@
|
|||
import 'package:aves/services/storage_service.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class FakeStorageService extends Fake implements StorageService {
|
||||
static const primaryRootAlbum = '/storage/emulated/0';
|
||||
static const primaryPath = '$primaryRootAlbum/';
|
||||
static const primaryDescription = 'Internal Storage';
|
||||
static const removablePath = '/storage/1234-5678/';
|
||||
static const removableDescription = 'SD Card';
|
||||
|
||||
@override
|
||||
Future<Set<StorageVolume>> getStorageVolumes() => SynchronousFuture({
|
||||
StorageVolume(
|
||||
path: primaryPath,
|
||||
description: primaryDescription,
|
||||
isPrimary: true,
|
||||
isRemovable: false,
|
||||
),
|
||||
StorageVolume(
|
||||
path: removablePath,
|
||||
description: removableDescription,
|
||||
isPrimary: false,
|
||||
isRemovable: true,
|
||||
),
|
||||
});
|
||||
}
|
|
@ -12,29 +12,36 @@ import 'package:aves/services/image_file_service.dart';
|
|||
import 'package:aves/services/media_store_service.dart';
|
||||
import 'package:aves/services/metadata_service.dart';
|
||||
import 'package:aves/services/services.dart';
|
||||
import 'package:aves/services/storage_service.dart';
|
||||
import 'package:aves/services/time_service.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../fake/availability.dart';
|
||||
import '../fake/image_file_service.dart';
|
||||
import '../fake/media_store_service.dart';
|
||||
import '../fake/metadata_db.dart';
|
||||
import '../fake/metadata_service.dart';
|
||||
import '../fake/storage_service.dart';
|
||||
import '../fake/time_service.dart';
|
||||
|
||||
void main() {
|
||||
const volume = '/storage/emulated/0/';
|
||||
const testAlbum = '${volume}Pictures/test';
|
||||
const sourceAlbum = '${volume}Pictures/source';
|
||||
const destinationAlbum = '${volume}Pictures/destination';
|
||||
const testAlbum = '${FakeStorageService.primaryPath}Pictures/test';
|
||||
const sourceAlbum = '${FakeStorageService.primaryPath}Pictures/source';
|
||||
const destinationAlbum = '${FakeStorageService.primaryPath}Pictures/destination';
|
||||
|
||||
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<AvesAvailability>(() => FakeAvesAvailability());
|
||||
getIt.registerLazySingleton<MetadataDb>(() => FakeMetadataDb());
|
||||
|
||||
getIt.registerLazySingleton<ImageFileService>(() => FakeImageFileService());
|
||||
getIt.registerLazySingleton<MediaStoreService>(() => FakeMediaStoreService());
|
||||
getIt.registerLazySingleton<MetadataService>(() => FakeMetadataService());
|
||||
getIt.registerLazySingleton<StorageService>(() => FakeStorageService());
|
||||
getIt.registerLazySingleton<TimeService>(() => FakeTimeService());
|
||||
|
||||
await settings.init();
|
||||
|
@ -236,4 +243,35 @@ void main() {
|
|||
expect(covers.count, 1);
|
||||
expect(covers.coverContentId(albumFilter), image1.contentId);
|
||||
});
|
||||
|
||||
testWidgets('unique album names', (tester) async {
|
||||
(mediaStoreService as FakeMediaStoreService).entries = {
|
||||
FakeMediaStoreService.newImage('${FakeStorageService.primaryPath}Pictures/Elea/Zeno', '1'),
|
||||
FakeMediaStoreService.newImage('${FakeStorageService.primaryPath}Pictures/Citium/Zeno', '1'),
|
||||
FakeMediaStoreService.newImage('${FakeStorageService.primaryPath}Pictures/Cleanthes', '1'),
|
||||
FakeMediaStoreService.newImage('${FakeStorageService.primaryPath}Pictures/Chrysippus', '1'),
|
||||
FakeMediaStoreService.newImage('${FakeStorageService.removablePath}Pictures/Chrysippus', '1'),
|
||||
FakeMediaStoreService.newImage('${FakeStorageService.primaryPath}', '1'),
|
||||
FakeMediaStoreService.newImage('${FakeStorageService.primaryPath}Pictures/Seneca', '1'),
|
||||
FakeMediaStoreService.newImage('${FakeStorageService.primaryPath}Seneca', '1'),
|
||||
};
|
||||
|
||||
await androidFileUtils.init();
|
||||
final source = await _initSource();
|
||||
await tester.pumpWidget(
|
||||
Builder(
|
||||
builder: (context) {
|
||||
expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Elea/Zeno'), 'Elea/Zeno');
|
||||
expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Citium/Zeno'), 'Citium/Zeno');
|
||||
expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Cleanthes'), 'Cleanthes');
|
||||
expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Chrysippus'), 'Chrysippus');
|
||||
expect(source.getAlbumDisplayName(context, '${FakeStorageService.removablePath}Pictures/Chrysippus'), 'Chrysippus (${FakeStorageService.removableDescription})');
|
||||
expect(source.getAlbumDisplayName(context, FakeStorageService.primaryRootAlbum), FakeStorageService.primaryDescription);
|
||||
expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Seneca'), 'Pictures/Seneca');
|
||||
expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Seneca'), 'Seneca');
|
||||
return Placeholder();
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import 'package:aves/model/settings/enums.dart';
|
|||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/services/storage_service.dart';
|
||||
import 'package:flutter_driver/driver_extension.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import 'constants.dart';
|
||||
|
||||
|
@ -15,7 +15,7 @@ void main() {
|
|||
// scan files copied from test assets
|
||||
// we do it via the app instead of broadcasting via ADB
|
||||
// because `MEDIA_SCANNER_SCAN_FILE` intent got deprecated in API 29
|
||||
StorageService.scanFile(path.join(targetPicturesDir, 'ipse.jpg'), 'image/jpeg');
|
||||
PlatformStorageService().scanFile(p.join(targetPicturesDir, 'ipse.jpg'), 'image/jpeg');
|
||||
|
||||
configureAndLaunch();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
|
@ -141,9 +141,9 @@ void searchAlbum() {
|
|||
await driver.waitUntilNoTransientCallbacks();
|
||||
|
||||
const albumPath = targetPicturesDirEmulated;
|
||||
final albumUniqueName = path.split(albumPath).last;
|
||||
final albumDisplayName = p.split(albumPath).last;
|
||||
await driver.tap(find.byType('TextField'));
|
||||
await driver.enterText(albumUniqueName);
|
||||
await driver.enterText(albumDisplayName);
|
||||
|
||||
final albumChip = find.byValueKey('album-$albumPath');
|
||||
await driver.waitFor(albumChip);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
String get adb {
|
||||
final env = Platform.environment;
|
||||
// e.g. C:\Users\<username>\AppData\Local\Android\Sdk
|
||||
final sdkDir = env['ANDROID_SDK_ROOT'] ?? env['ANDROID_SDK'];
|
||||
return path.join(sdkDir, 'platform-tools', Platform.isWindows ? 'adb.exe' : 'adb');
|
||||
return p.join(sdkDir, 'platform-tools', Platform.isWindows ? 'adb.exe' : 'adb');
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
Loading…
Reference in a new issue