From 86b982d270cf3c1c3a2796a92c7e23d351d09662 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sat, 25 Mar 2023 00:09:13 +0100 Subject: [PATCH] refactor --- .../thibault/aves/utils/StorageUtils.kt | 22 ++- .../app_icon_image_provider.dart | 2 +- .../actions/{chip_actions.dart => chip.dart} | 0 .../{chip_set_actions.dart => chip_set.dart} | 0 .../{entry_actions.dart => entry.dart} | 0 ...{entry_set_actions.dart => entry_set.dart} | 0 .../actions/{map_actions.dart => map.dart} | 0 ..._cluster_actions.dart => map_cluster.dart} | 0 lib/model/actions/move_type.dart | 8 +- .../{settings_actions.dart => settings.dart} | 0 .../{share_actions.dart => share.dart} | 0 ...{slideshow_actions.dart => slideshow.dart} | 0 lib/model/app/support.dart | 96 ++++++++++ lib/model/apps.dart | 78 ++++++++ lib/model/covers.dart | 6 +- lib/model/entry/dirs.dart | 2 +- lib/model/entry/entry.dart | 29 +-- lib/model/entry/extensions/props.dart | 155 ++++++++------- lib/model/favourites.dart | 1 + lib/model/filters/album.dart | 2 +- lib/model/settings/defaults.dart | 4 +- lib/model/settings/settings.dart | 4 +- lib/model/source/album.dart | 2 + lib/model/source/enums/enums.dart | 11 ++ lib/model/source/trash.dart | 1 + lib/model/storage/relative_dir.dart | 49 +++++ lib/model/storage/volume.dart | 41 ++++ lib/ref/mime_types.dart | 69 +------ ...roid_app_service.dart => app_service.dart} | 6 +- lib/services/common/services.dart | 6 +- lib/services/media/media_fetch_service.dart | 50 ++++- .../metadata/metadata_edit_service.dart | 1 + lib/services/storage_service.dart | 3 +- lib/utils/android_file_utils.dart | 180 ++---------------- lib/widgets/aves_app.dart | 6 +- lib/widgets/collection/app_bar.dart | 2 +- .../collection/entry_set_action_delegate.dart | 8 +- .../collection/grid/headers/album.dart | 1 + .../quick_choosers/move_button.dart | 2 +- .../quick_choosers/rate_button.dart | 2 +- .../quick_choosers/share_button.dart | 4 +- .../quick_choosers/share_chooser.dart | 2 +- .../quick_choosers/tag_button.dart | 2 +- .../action_mixins/permission_aware.dart | 3 +- .../common/action_mixins/size_aware.dart | 1 + .../common/identity/aves_filter_chip.dart | 2 +- lib/widgets/common/identity/aves_icons.dart | 2 +- lib/widgets/common/map/buttons/panel.dart | 2 +- .../common/map/map_action_delegate.dart | 2 +- lib/widgets/debug/android_apps.dart | 4 +- lib/widgets/dialogs/convert_entry_dialog.dart | 3 +- .../filter_editors/create_album_dialog.dart | 1 + .../dialogs/pick_dialogs/album_pick_page.dart | 2 +- .../dialogs/pick_dialogs/app_pick_page.dart | 4 +- lib/widgets/filter_grids/albums_page.dart | 3 +- .../common/action_delegates/album_set.dart | 4 +- .../common/action_delegates/chip.dart | 2 +- .../common/action_delegates/chip_set.dart | 2 +- lib/widgets/filter_grids/common/app_bar.dart | 2 +- .../common/covered_filter_chip.dart | 3 +- .../filter_grids/common/section_keys.dart | 2 +- lib/widgets/home_page.dart | 3 +- lib/widgets/map/map_page.dart | 4 +- lib/widgets/navigation/drawer/app_drawer.dart | 1 + .../privacy/file_picker/crumb_line.dart | 2 +- .../privacy/file_picker/file_picker_page.dart | 2 + lib/widgets/settings/settings_page.dart | 2 +- .../collection_actions_editor_page.dart | 2 +- .../viewer/viewer_actions_editor.dart | 2 +- .../viewer/action/entry_action_delegate.dart | 20 +- .../action/entry_info_action_delegate.dart | 2 +- .../viewer/action/video_action_delegate.dart | 4 +- lib/widgets/viewer/controls/intents.dart | 2 +- .../viewer/controls/notifications.dart | 2 +- lib/widgets/viewer/controls/shortcuts.dart | 2 +- lib/widgets/viewer/debug/debug_page.dart | 3 +- lib/widgets/viewer/entry_vertical_pager.dart | 2 +- lib/widgets/viewer/entry_viewer_stack.dart | 2 +- lib/widgets/viewer/info/basic_section.dart | 16 +- .../info/embedded/embedded_data_opener.dart | 4 +- lib/widgets/viewer/info/info_app_bar.dart | 2 +- lib/widgets/viewer/info/info_page.dart | 2 +- .../viewer/overlay/slideshow_buttons.dart | 2 +- .../viewer/overlay/video/controls.dart | 2 +- lib/widgets/viewer/overlay/video/video.dart | 2 +- .../viewer/overlay/viewer_buttons.dart | 2 +- lib/widgets/viewer/slideshow_page.dart | 2 +- .../viewer/visual/entry_page_view.dart | 2 +- lib/widgets/wallpaper_page.dart | 2 +- test/fake/android_app_service.dart | 6 +- test/fake/storage_service.dart | 2 +- test/model/collection_source_test.dart | 4 +- 92 files changed, 573 insertions(+), 435 deletions(-) rename lib/model/actions/{chip_actions.dart => chip.dart} (100%) rename lib/model/actions/{chip_set_actions.dart => chip_set.dart} (100%) rename lib/model/actions/{entry_actions.dart => entry.dart} (100%) rename lib/model/actions/{entry_set_actions.dart => entry_set.dart} (100%) rename lib/model/actions/{map_actions.dart => map.dart} (100%) rename lib/model/actions/{map_cluster_actions.dart => map_cluster.dart} (100%) rename lib/model/actions/{settings_actions.dart => settings.dart} (100%) rename lib/model/actions/{share_actions.dart => share.dart} (100%) rename lib/model/actions/{slideshow_actions.dart => slideshow.dart} (100%) create mode 100644 lib/model/app/support.dart create mode 100644 lib/model/apps.dart create mode 100644 lib/model/storage/relative_dir.dart create mode 100644 lib/model/storage/volume.dart rename lib/services/{android_app_service.dart => app_service.dart} (97%) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt index a02a1a184..3de3ca4c0 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt @@ -33,11 +33,23 @@ import java.util.regex.Pattern object StorageUtils { private val LOG_TAG = LogUtils.createTag() - // from `DocumentsContract` + private const val SCHEME_CONTENT = ContentResolver.SCHEME_CONTENT + + // cf DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY private const val EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents" + + // cf DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID private const val EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID = "primary" - private const val TREE_URI_ROOT = "content://$EXTERNAL_STORAGE_PROVIDER_AUTHORITY/tree/" + private const val TREE_URI_ROOT = "$SCHEME_CONTENT://$EXTERNAL_STORAGE_PROVIDER_AUTHORITY/tree/" + + private val MEDIA_STORE_VOLUME_EXTERNAL = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) MediaStore.VOLUME_EXTERNAL else "external" + + // TODO TLAD get it from `MediaStore.Images.Media.EXTERNAL_CONTENT_URI`? + private val IMAGE_PATH_ROOT = "/$MEDIA_STORE_VOLUME_EXTERNAL/images/" + + // TODO TLAD get it from `MediaStore.Video.Media.EXTERNAL_CONTENT_URI`? + private val VIDEO_PATH_ROOT = "/$MEDIA_STORE_VOLUME_EXTERNAL/video/" private val UUID_PATTERN = Regex("[A-Fa-f\\d-]+") private val TREE_URI_PATH_PATTERN = Pattern.compile("(.*?):(.*)") @@ -545,7 +557,7 @@ object StorageUtils { uri ?: return false // a URI's authority is [userinfo@]host[:port] // but we only want the host when comparing to Media Store's "authority" - return ContentResolver.SCHEME_CONTENT.equals(uri.scheme, ignoreCase = true) && MediaStore.AUTHORITY.equals(uri.host, ignoreCase = true) + return SCHEME_CONTENT.equals(uri.scheme, ignoreCase = true) && MediaStore.AUTHORITY.equals(uri.host, ignoreCase = true) } fun getOriginalUri(context: Context, uri: Uri): Uri { @@ -554,7 +566,7 @@ object StorageUtils { val path = uri.path path ?: return uri // from Android 11, accessing the original URI for a `file` or `downloads` media content yields a `SecurityException` - if (path.startsWith("/external/images/") || path.startsWith("/external/video/")) { + if (path.startsWith(IMAGE_PATH_ROOT) || path.startsWith(VIDEO_PATH_ROOT)) { // "Caller must hold ACCESS_MEDIA_LOCATION permission to access original" if (context.checkSelfPermission(Manifest.permission.ACCESS_MEDIA_LOCATION) == PackageManager.PERMISSION_GRANTED) { return MediaStore.setRequireOriginal(uri) @@ -611,7 +623,7 @@ object StorageUtils { return uri } - // Build a typical `images` or `videos` content URI from the original content ID. + // Build a typical `images` or `video` content URI from the original content ID. // We cannot safely apply this to a `file` content URI, as it may point to a file not indexed // by the Media Store (via `.nomedia`), and therefore has no matching image/video content URI. private fun getMediaUriImageVideoUri(uri: Uri, mimeType: String): Uri? { diff --git a/lib/image_providers/app_icon_image_provider.dart b/lib/image_providers/app_icon_image_provider.dart index 0729c7ce1..a0aaa32b2 100644 --- a/lib/image_providers/app_icon_image_provider.dart +++ b/lib/image_providers/app_icon_image_provider.dart @@ -39,7 +39,7 @@ class AppIconImage extends ImageProvider { Future _loadAsync(AppIconImageKey key, DecoderBufferCallback decode) async { try { - final bytes = await androidAppService.getAppIcon(key.packageName, key.size); + final bytes = await appService.getAppIcon(key.packageName, key.size); final buffer = await ui.ImmutableBuffer.fromUint8List(bytes.isEmpty ? kTransparentImage : bytes); return await decode(buffer); } catch (error) { diff --git a/lib/model/actions/chip_actions.dart b/lib/model/actions/chip.dart similarity index 100% rename from lib/model/actions/chip_actions.dart rename to lib/model/actions/chip.dart diff --git a/lib/model/actions/chip_set_actions.dart b/lib/model/actions/chip_set.dart similarity index 100% rename from lib/model/actions/chip_set_actions.dart rename to lib/model/actions/chip_set.dart diff --git a/lib/model/actions/entry_actions.dart b/lib/model/actions/entry.dart similarity index 100% rename from lib/model/actions/entry_actions.dart rename to lib/model/actions/entry.dart diff --git a/lib/model/actions/entry_set_actions.dart b/lib/model/actions/entry_set.dart similarity index 100% rename from lib/model/actions/entry_set_actions.dart rename to lib/model/actions/entry_set.dart diff --git a/lib/model/actions/map_actions.dart b/lib/model/actions/map.dart similarity index 100% rename from lib/model/actions/map_actions.dart rename to lib/model/actions/map.dart diff --git a/lib/model/actions/map_cluster_actions.dart b/lib/model/actions/map_cluster.dart similarity index 100% rename from lib/model/actions/map_cluster_actions.dart rename to lib/model/actions/map_cluster.dart diff --git a/lib/model/actions/move_type.dart b/lib/model/actions/move_type.dart index cc7ff0c6b..7c362b0cc 100644 --- a/lib/model/actions/move_type.dart +++ b/lib/model/actions/move_type.dart @@ -1 +1,7 @@ -enum MoveType { copy, move, export, toBin, fromBin } +enum MoveType { + copy, + move, + export, + toBin, + fromBin, +} diff --git a/lib/model/actions/settings_actions.dart b/lib/model/actions/settings.dart similarity index 100% rename from lib/model/actions/settings_actions.dart rename to lib/model/actions/settings.dart diff --git a/lib/model/actions/share_actions.dart b/lib/model/actions/share.dart similarity index 100% rename from lib/model/actions/share_actions.dart rename to lib/model/actions/share.dart diff --git a/lib/model/actions/slideshow_actions.dart b/lib/model/actions/slideshow.dart similarity index 100% rename from lib/model/actions/slideshow_actions.dart rename to lib/model/actions/slideshow.dart diff --git a/lib/model/app/support.dart b/lib/model/app/support.dart new file mode 100644 index 000000000..fbf2a29b4 --- /dev/null +++ b/lib/model/app/support.dart @@ -0,0 +1,96 @@ +import 'package:aves/ref/mime_types.dart'; + +class AppSupport { + // TODO TLAD [codec] make it dynamic if it depends on OS/lib versions + static const Set undecodableImages = { + MimeTypes.art, + MimeTypes.cdr, + MimeTypes.crw, + MimeTypes.djvu, + MimeTypes.jpeg2000, + MimeTypes.jxl, + MimeTypes.pat, + MimeTypes.pcx, + MimeTypes.pnm, + MimeTypes.psdVnd, + MimeTypes.psdX, + MimeTypes.octetStream, + MimeTypes.zip, + }; + + static bool canDecode(String mimeType) => !undecodableImages.contains(mimeType); + + // Android's `BitmapRegionDecoder` documentation states that "only the JPEG and PNG formats are supported" + // but in practice (tested on API 25, 27, 29), it successfully decodes the formats listed below, + // and it actually fails to decode GIF, DNG and animated WEBP. Other formats were not tested. + static bool _supportedByBitmapRegionDecoder(String mimeType) => [ + MimeTypes.heic, + MimeTypes.heif, + MimeTypes.jpeg, + MimeTypes.png, + MimeTypes.webp, + MimeTypes.arw, + MimeTypes.cr2, + MimeTypes.nef, + MimeTypes.nrw, + MimeTypes.orf, + MimeTypes.pef, + MimeTypes.raf, + MimeTypes.rw2, + MimeTypes.srw, + ].contains(mimeType); + + static bool canDecodeRegion(String mimeType) => _supportedByBitmapRegionDecoder(mimeType) || mimeType == MimeTypes.tiff; + + // `exifinterface` v1.3.3 declared support for DNG, but it strips non-standard Exif tags when saving attributes, + // and DNG requires DNG-specific tags saved along standard Exif. So it was actually breaking DNG files. + static bool canEditExif(String mimeType) { + switch (mimeType.toLowerCase()) { + // as of androidx.exifinterface:exifinterface:1.3.4 + case MimeTypes.jpeg: + case MimeTypes.png: + case MimeTypes.webp: + return true; + default: + return false; + } + } + + static bool canEditIptc(String mimeType) { + switch (mimeType.toLowerCase()) { + // as of latest PixyMeta + case MimeTypes.jpeg: + case MimeTypes.tiff: + return true; + default: + return false; + } + } + + static bool canEditXmp(String mimeType) { + switch (mimeType.toLowerCase()) { + // as of latest PixyMeta + case MimeTypes.gif: + case MimeTypes.jpeg: + case MimeTypes.png: + case MimeTypes.tiff: + return true; + // using `mp4parser` + case MimeTypes.mp4: + return true; + default: + return false; + } + } + + static bool canRemoveMetadata(String mimeType) { + switch (mimeType.toLowerCase()) { + // as of latest PixyMeta + case MimeTypes.jpeg: + case MimeTypes.tiff: + return true; + default: + return false; + } + } +} diff --git a/lib/model/apps.dart b/lib/model/apps.dart new file mode 100644 index 000000000..b7bc6e944 --- /dev/null +++ b/lib/model/apps.dart @@ -0,0 +1,78 @@ +import 'package:aves/services/common/services.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; + +final AppInventory appInventory = AppInventory._private(); + +class AppInventory { + Set _packages = {}; + List _potentialAppDirs = []; + + ValueNotifier areAppNamesReadyNotifier = ValueNotifier(false); + + Iterable get _launcherPackages => _packages.where((v) => v.categoryLauncher); + + AppInventory._private(); + + Future initAppNames() async { + if (_packages.isEmpty) { + debugPrint('Access installed app inventory'); + _packages = await appService.getPackages(); + _potentialAppDirs = _launcherPackages.expand((v) => v.potentialDirs).toList(); + areAppNamesReadyNotifier.value = true; + } + } + + Future resetAppNames() async { + _packages.clear(); + _potentialAppDirs.clear(); + areAppNamesReadyNotifier.value = false; + } + + bool isPotentialAppDir(String dir) => _potentialAppDirs.contains(dir); + + String? getAlbumAppPackageName(String albumPath) { + final dir = pContext.split(albumPath).last; + final package = _launcherPackages.firstWhereOrNull((v) => v.potentialDirs.contains(dir)); + return package?.packageName; + } + + String? getCurrentAppName(String packageName) { + final package = _packages.firstWhereOrNull((v) => v.packageName == packageName); + return package?.currentLabel; + } +} + +class Package { + final String packageName; + final String? currentLabel, englishLabel; + final bool categoryLauncher, isSystem; + final Set ownedDirs = {}; + + Package({ + required this.packageName, + required this.currentLabel, + required this.englishLabel, + required this.categoryLauncher, + required this.isSystem, + }); + + factory Package.fromMap(Map map) { + return Package( + packageName: map['packageName'] ?? '', + currentLabel: map['currentLabel'], + englishLabel: map['englishLabel'], + categoryLauncher: map['categoryLauncher'] ?? false, + isSystem: map['isSystem'] ?? false, + ); + } + + Set get potentialDirs => [ + currentLabel, + englishLabel, + ...ownedDirs, + ].whereNotNull().toSet(); + + @override + String toString() => '$runtimeType#${shortHash(this)}{packageName=$packageName, categoryLauncher=$categoryLauncher, isSystem=$isSystem, currentLabel=$currentLabel, englishLabel=$englishLabel, ownedDirs=$ownedDirs}'; +} diff --git a/lib/model/covers.dart b/lib/model/covers.dart index 0eb13eb55..962310fa8 100644 --- a/lib/model/covers.dart +++ b/lib/model/covers.dart @@ -1,10 +1,12 @@ import 'dart:async'; +import 'package:aves/model/apps.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; -import 'package:aves/model/vaults/vaults.dart'; import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/model/source/enums/enums.dart'; +import 'package:aves/model/vaults/vaults.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:collection/collection.dart'; @@ -121,7 +123,7 @@ class Covers { String? effectiveAlbumPackage(String albumPath) { final filterPackage = of(AlbumFilter(albumPath, null))?.item2; - return filterPackage ?? androidFileUtils.getAlbumAppPackageName(albumPath); + return filterPackage ?? appInventory.getAlbumAppPackageName(albumPath); } // import/export diff --git a/lib/model/entry/dirs.dart b/lib/model/entry/dirs.dart index c500504a3..a59861c0d 100644 --- a/lib/model/entry/dirs.dart +++ b/lib/model/entry/dirs.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'dart:io'; +import 'package:aves/model/storage/relative_dir.dart'; import 'package:aves/services/common/services.dart'; -import 'package:aves/utils/android_file_utils.dart'; import 'package:collection/collection.dart'; final entryDirRepo = EntryDirRepo._private(); diff --git a/lib/model/entry/entry.dart b/lib/model/entry/entry.dart index 268357e2a..13b1a5b5b 100644 --- a/lib/model/entry/entry.dart +++ b/lib/model/entry/entry.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'dart:ui'; import 'package:aves/model/entry/cache.dart'; @@ -7,13 +6,12 @@ import 'package:aves/model/entry/dirs.dart'; import 'package:aves/model/metadata/address.dart'; import 'package:aves/model/metadata/catalog.dart'; import 'package:aves/model/metadata/trash.dart'; -import 'package:aves/model/source/trash.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/format.dart'; -import 'package:aves_utils/aves_utils.dart'; import 'package:aves/utils/time_utils.dart'; import 'package:aves_model/aves_model.dart'; +import 'package:aves_utils/aves_utils.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; @@ -80,10 +78,6 @@ class AvesEntry with AvesEntryBase { this.durationMillis = durationMillis; } - bool get canDecode => !MimeTypes.undecodableImages.contains(mimeType); - - bool get canHaveAlpha => MimeTypes.alphaImages.contains(mimeType); - AvesEntry copyWith({ int? id, String? uri, @@ -225,15 +219,6 @@ class AvesEntry with AvesEntryBase { return _extension; } - String? get storagePath => trashed ? trashDetails?.path : path; - - String? get storageDirectory => trashed ? pContext.dirname(trashDetails!.path) : directory; - - bool get isMissingAtPath { - final _storagePath = storagePath; - return _storagePath != null && !File(_storagePath).existsSync(); - } - // the MIME type reported by the Media Store is unreliable // so we use the one found during cataloguing if possible String get mimeType => _catalogMetadata?.mimeType ?? sourceMimeType; @@ -323,18 +308,6 @@ class AvesEntry with AvesEntryBase { return _durationText!; } - bool get isExpiredTrash { - final dateMillis = trashDetails?.dateMillis; - if (dateMillis == null) return false; - return DateTime.fromMillisecondsSinceEpoch(dateMillis).add(TrashMixin.binKeepDuration).isBefore(DateTime.now()); - } - - int? get trashDaysLeft { - final dateMillis = trashDetails?.dateMillis; - if (dateMillis == null) return null; - return DateTime.fromMillisecondsSinceEpoch(dateMillis).add(TrashMixin.binKeepDuration).difference(DateTime.now()).inDays; - } - // returns whether this entry has GPS coordinates // (0, 0) coordinates are considered invalid, as it is likely a default value bool get hasGps => (_catalogMetadata?.latitude ?? 0) != 0 || (_catalogMetadata?.longitude ?? 0) != 0; diff --git a/lib/model/entry/extensions/props.dart b/lib/model/entry/extensions/props.dart index 96ce06708..5bd21349b 100644 --- a/lib/model/entry/extensions/props.dart +++ b/lib/model/entry/extensions/props.dart @@ -1,65 +1,111 @@ +import 'dart:io'; import 'dart:ui'; +import 'package:aves/model/app/support.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/settings/settings.dart'; +import 'package:aves/model/source/trash.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/ref/unicode.dart'; +import 'package:aves/services/common/services.dart'; import 'package:aves/theme/text.dart'; import 'package:aves/utils/android_file_utils.dart'; extension ExtraAvesEntryProps on AvesEntry { + // type + String get mimeTypeAnySubtype => mimeType.replaceAll(RegExp('/.*'), '/*'); + bool get canHaveAlpha => MimeTypes.canHaveAlpha(mimeType); + bool get isSvg => mimeType == MimeTypes.svg; - // guess whether this is a photo, according to file type (used as a hint to e.g. display megapixels) - bool get isPhoto => [MimeTypes.heic, MimeTypes.heif, MimeTypes.jpeg, MimeTypes.tiff].contains(mimeType) || isRaw; - - // Android's `BitmapRegionDecoder` documentation states that "only the JPEG and PNG formats are supported" - // but in practice (tested on API 25, 27, 29), it successfully decodes the formats listed below, - // and it actually fails to decode GIF, DNG and animated WEBP. Other formats were not tested. - bool get _supportedByBitmapRegionDecoder => - [ - MimeTypes.heic, - MimeTypes.heif, - MimeTypes.jpeg, - MimeTypes.png, - MimeTypes.webp, - MimeTypes.arw, - MimeTypes.cr2, - MimeTypes.nef, - MimeTypes.nrw, - MimeTypes.orf, - MimeTypes.pef, - MimeTypes.raf, - MimeTypes.rw2, - MimeTypes.srw, - ].contains(mimeType) && - !isAnimated; - - bool get supportTiling => _supportedByBitmapRegionDecoder || mimeType == MimeTypes.tiff; - - bool get useTiles => supportTiling && (width > 4096 || height > 4096); - - bool get isRaw => MimeTypes.rawImages.contains(mimeType); + bool get isRaw => MimeTypes.isRaw(mimeType); bool get isImage => MimeTypes.isImage(mimeType); bool get isVideo => MimeTypes.isVideo(mimeType); + // size + + bool get useTiles => canDecodeRegion && (width > 4096 || height > 4096); + + bool get isSized => width > 0 && height > 0; + + Size videoDisplaySize(double sar) { + final size = displaySize; + if (sar != 1) { + final dar = displayAspectRatio * sar; + final w = size.width; + final h = size.height; + if (w >= h) return Size(w, w / dar); + if (h > w) return Size(h * dar, h); + } + return size; + } + + // text + + String get resolutionText { + final ws = width; + final hs = height; + return isRotated ? '$hs${AText.resolutionSeparator}$ws' : '$ws${AText.resolutionSeparator}$hs'; + } + + String get aspectRatioText { + const separator = UniChars.ratio; + if (width > 0 && height > 0) { + final gcd = width.gcd(height); + final w = width ~/ gcd; + final h = height ~/ gcd; + return isRotated ? '$h$separator$w' : '$w$separator$h'; + } else { + return '?$separator?'; + } + } + + // catalog + bool get isAnimated => catalogMetadata?.isAnimated ?? false; bool get isGeotiff => catalogMetadata?.isGeotiff ?? false; bool get is360 => catalogMetadata?.is360 ?? false; - bool get isMediaStoreContent => uri.startsWith('content://media/'); + // trash - bool get isMediaStoreMediaContent => isMediaStoreContent && {'/external/images/', '/external/video/'}.any(uri.contains); + bool get isExpiredTrash { + final dateMillis = trashDetails?.dateMillis; + if (dateMillis == null) return false; + return DateTime.fromMillisecondsSinceEpoch(dateMillis).add(TrashMixin.binKeepDuration).isBefore(DateTime.now()); + } - bool get isVaultContent => path?.startsWith(androidFileUtils.vaultRoot) ?? false; + int? get trashDaysLeft { + final dateMillis = trashDetails?.dateMillis; + if (dateMillis == null) return null; + return DateTime.fromMillisecondsSinceEpoch(dateMillis).add(TrashMixin.binKeepDuration).difference(DateTime.now()).inDays; + } - bool get canEdit => !settings.isReadOnly && path != null && !trashed && (isMediaStoreContent || isVaultContent); + // storage + + String? get storageDirectory => trashed ? pContext.dirname(trashDetails!.path) : directory; + + bool get isMissingAtPath { + final _storagePath = trashed ? trashDetails?.path : path; + return _storagePath != null && !File(_storagePath).existsSync(); + } + + // providers + + bool get _isVaultContent => path?.startsWith(androidFileUtils.vaultRoot) ?? false; + + bool get _isMediaStoreContent => uri.startsWith(AndroidFileUtils.mediaStoreUriRoot); + + bool get isMediaStoreMediaContent => _isMediaStoreContent && AndroidFileUtils.mediaUriPathRoots.any(uri.contains); + + // edition + + bool get canEdit => !settings.isReadOnly && path != null && !trashed && (_isMediaStoreContent || _isVaultContent); bool get canEditDate => canEdit && (canEditExif || canEditXmp); @@ -75,44 +121,17 @@ extension ExtraAvesEntryProps on AvesEntry { bool get canFlip => canEdit && canEditExif; - bool get canEditExif => MimeTypes.canEditExif(mimeType); + // app support - bool get canEditIptc => MimeTypes.canEditIptc(mimeType); + bool get canDecode => AppSupport.canDecode(mimeType); - bool get canEditXmp => MimeTypes.canEditXmp(mimeType); + bool get canDecodeRegion => AppSupport.canDecodeRegion(mimeType) && !isAnimated; - bool get canRemoveMetadata => MimeTypes.canRemoveMetadata(mimeType); + bool get canEditExif => AppSupport.canEditExif(mimeType); - bool get isSized => width > 0 && height > 0; + bool get canEditIptc => AppSupport.canEditIptc(mimeType); - String get resolutionText { - final ws = width; - final hs = height; - return isRotated ? '$hs${AText.resolutionSeparator}$ws' : '$ws${AText.resolutionSeparator}$hs'; - } + bool get canEditXmp => AppSupport.canEditXmp(mimeType); - String get aspectRatioText { - if (width > 0 && height > 0) { - final gcd = width.gcd(height); - final w = width ~/ gcd; - final h = height ~/ gcd; - return isRotated ? '$h${UniChars.ratio}$w' : '$w${UniChars.ratio}$h'; - } else { - return '?${UniChars.ratio}?'; - } - } - - Size videoDisplaySize(double sar) { - final size = displaySize; - if (sar != 1) { - final dar = displayAspectRatio * sar; - final w = size.width; - final h = size.height; - if (w >= h) return Size(w, w / dar); - if (h > w) return Size(h * dar, h); - } - return size; - } - - int get megaPixels => (width * height / 1000000).round(); + bool get canRemoveMetadata => AppSupport.canRemoveMetadata(mimeType); } diff --git a/lib/model/favourites.dart b/lib/model/favourites.dart index 8c5eeea57..f2e77476d 100644 --- a/lib/model/favourites.dart +++ b/lib/model/favourites.dart @@ -1,5 +1,6 @@ import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/model/storage/volume.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:collection/collection.dart'; diff --git a/lib/model/filters/album.dart b/lib/model/filters/album.dart index 226436bbf..96a8612ad 100644 --- a/lib/model/filters/album.dart +++ b/lib/model/filters/album.dart @@ -1,9 +1,9 @@ import 'package:aves/model/covers.dart'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/colors.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'; diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart index 212e8a424..9edcf1400 100644 --- a/lib/model/settings/defaults.dart +++ b/lib/model/settings/defaults.dart @@ -1,5 +1,5 @@ -import 'package:aves/model/actions/entry_actions.dart'; -import 'package:aves/model/actions/entry_set_actions.dart'; +import 'package:aves/model/actions/entry.dart'; +import 'package:aves/model/actions/entry_set.dart'; import 'package:aves/model/filters/recent.dart'; import 'package:aves/model/naming_pattern.dart'; import 'package:aves/model/settings/enums/enums.dart'; diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index 88061781f..147323ef2 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -3,8 +3,8 @@ import 'dart:convert'; import 'dart:math'; import 'package:aves/app_flavor.dart'; -import 'package:aves/model/actions/entry_actions.dart'; -import 'package:aves/model/actions/entry_set_actions.dart'; +import 'package:aves/model/actions/entry.dart'; +import 'package:aves/model/actions/entry_set.dart'; import 'package:aves/model/device.dart'; import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/filters.dart'; diff --git a/lib/model/source/album.dart b/lib/model/source/album.dart index 7b4e9c48c..a5827a48f 100644 --- a/lib/model/source/album.dart +++ b/lib/model/source/album.dart @@ -2,6 +2,8 @@ import 'package:aves/model/entry/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/model/source/enums/enums.dart'; +import 'package:aves/model/storage/relative_dir.dart'; import 'package:aves/model/vaults/vaults.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/utils/android_file_utils.dart'; diff --git a/lib/model/source/enums/enums.dart b/lib/model/source/enums/enums.dart index de1b1faef..e856f30f7 100644 --- a/lib/model/source/enums/enums.dart +++ b/lib/model/source/enums/enums.dart @@ -9,3 +9,14 @@ enum EntrySortFactor { date, name, rating, size } enum EntryGroupFactor { none, album, month, day } enum TileLayout { mosaic, grid, list } + +enum AlbumType { + regular, + vault, + app, + camera, + download, + screenRecordings, + screenshots, + videoCaptures, +} diff --git a/lib/model/source/trash.dart b/lib/model/source/trash.dart index 51c98dd2a..5aac6de6e 100644 --- a/lib/model/source/trash.dart +++ b/lib/model/source/trash.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/services/common/image_op_events.dart'; import 'package:aves/services/common/services.dart'; diff --git a/lib/model/storage/relative_dir.dart b/lib/model/storage/relative_dir.dart new file mode 100644 index 000000000..d7e552480 --- /dev/null +++ b/lib/model/storage/relative_dir.dart @@ -0,0 +1,49 @@ +import 'package:aves/utils/android_file_utils.dart'; +import 'package:collection/collection.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/widgets.dart'; + +@immutable +class VolumeRelativeDirectory extends Equatable { + final String volumePath, relativeDir; + + @override + List get props => [volumePath, relativeDir]; + + String get dirPath => '$volumePath$relativeDir'; + + const VolumeRelativeDirectory({ + required this.volumePath, + required this.relativeDir, + }); + + static VolumeRelativeDirectory fromMap(Map map) { + return VolumeRelativeDirectory( + volumePath: map['volumePath'] ?? '', + relativeDir: map['relativeDir'] ?? '', + ); + } + + Map toMap() => { + 'volumePath': volumePath, + 'relativeDir': relativeDir, + }; + + // prefer static method over a null returning factory constructor + static VolumeRelativeDirectory? fromPath(String dirPath) { + final volume = androidFileUtils.getStorageVolume(dirPath); + if (volume == null) return null; + + final root = volume.path; + final rootLength = root.length; + return VolumeRelativeDirectory( + volumePath: root, + relativeDir: dirPath.length < rootLength ? '' : dirPath.substring(rootLength), + ); + } + + String getVolumeDescription(BuildContext context) { + final volume = androidFileUtils.storageVolumes.firstWhereOrNull((volume) => volume.path == volumePath); + return volume?.getDescription(context) ?? volumePath; + } +} diff --git a/lib/model/storage/volume.dart b/lib/model/storage/volume.dart new file mode 100644 index 000000000..284529bcf --- /dev/null +++ b/lib/model/storage/volume.dart @@ -0,0 +1,41 @@ +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/widgets.dart'; + +@immutable +class StorageVolume extends Equatable { + final String? _description; + final String path, state; + final bool isPrimary, isRemovable; + + @override + List get props => [_description, path, state, isPrimary, isRemovable]; + + const StorageVolume({ + required String? description, + required this.isPrimary, + required this.isRemovable, + required this.path, + required this.state, + }) : _description = description; + + String getDescription(BuildContext? context) { + if (_description != null) return _description!; + // ideally, the context should always be provided, but in some cases (e.g. album comparison), + // this would require numerous additional methods to have the context as argument + // for such a minor benefit: fallback volume description on Android < N + if (isPrimary) return context?.l10n.storageVolumeDescriptionFallbackPrimary ?? 'Internal Storage'; + return context?.l10n.storageVolumeDescriptionFallbackNonPrimary ?? 'SD card'; + } + + factory StorageVolume.fromMap(Map map) { + final isPrimary = map['isPrimary'] ?? false; + return StorageVolume( + description: map['description'], + isPrimary: isPrimary, + isRemovable: map['isRemovable'] ?? false, + path: map['path'] ?? '', + state: map['state'] ?? '', + ); + } +} diff --git a/lib/ref/mime_types.dart b/lib/ref/mime_types.dart index 4a993fe42..8d2b7ea6c 100644 --- a/lib/ref/mime_types.dart +++ b/lib/ref/mime_types.dart @@ -86,22 +86,9 @@ class MimeTypes { static const Set rawImages = {arw, cr2, crw, dcr, dng, dngX, erf, k25, kdc, mrw, nef, nrw, orf, pef, raf, raw, rw2, sr2, srf, srw, x3f}; - // TODO TLAD [codec] make it dynamic if it depends on OS/lib versions - static const Set undecodableImages = {art, cdr, crw, djvu, jpeg2000, jxl, pat, pcx, pnm, psdVnd, psdX, octetStream, zip}; + static bool canHaveAlpha(String mimeType) => MimeTypes.alphaImages.contains(mimeType); - static const Set _knownOpaqueImages = {jpeg}; - - static const Set _knownVideos = {v3gpp, asf, avi, aviMSVideo, aviVnd, aviXMSVideo, dvd, flv, flvX, mkv, mkvX, mov, movX, mp2p, mp2t, mp2ts, mp4, mpeg, ogv, realVideo, webm, wmv}; - - static final Set knownMediaTypes = { - anyImage, - ..._knownOpaqueImages, - ...alphaImages, - ...rawImages, - ...undecodableImages, - anyVideo, - ..._knownVideos, - }; + static bool isRaw(String mimeType) => MimeTypes.rawImages.contains(mimeType); static bool isImage(String mimeType) => mimeType.startsWith('image'); @@ -147,56 +134,4 @@ class MimeTypes { } return null; } - - // `exifinterface` v1.3.3 declared support for DNG, but it strips non-standard Exif tags when saving attributes, - // and DNG requires DNG-specific tags saved along standard Exif. So it was actually breaking DNG files. - static bool canEditExif(String mimeType) { - switch (mimeType.toLowerCase()) { - // as of androidx.exifinterface:exifinterface:1.3.4 - case jpeg: - case png: - case webp: - return true; - default: - return false; - } - } - - static bool canEditIptc(String mimeType) { - switch (mimeType.toLowerCase()) { - // as of latest PixyMeta - case jpeg: - case tiff: - return true; - default: - return false; - } - } - - static bool canEditXmp(String mimeType) { - switch (mimeType.toLowerCase()) { - // as of latest PixyMeta - case gif: - case jpeg: - case png: - case tiff: - return true; - // using `mp4parser` - case mp4: - return true; - default: - return false; - } - } - - static bool canRemoveMetadata(String mimeType) { - switch (mimeType.toLowerCase()) { - // as of latest PixyMeta - case jpeg: - case tiff: - return true; - default: - return false; - } - } } diff --git a/lib/services/android_app_service.dart b/lib/services/app_service.dart similarity index 97% rename from lib/services/android_app_service.dart rename to lib/services/app_service.dart index cf181a68b..14fc795ba 100644 --- a/lib/services/android_app_service.dart +++ b/lib/services/app_service.dart @@ -1,14 +1,14 @@ +import 'package:aves/model/apps.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/services/common/services.dart'; -import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/math_utils.dart'; import 'package:collection/collection.dart'; import 'package:flutter/services.dart'; import 'package:latlong2/latlong.dart'; -abstract class AndroidAppService { +abstract class AppService { Future> getPackages(); Future getAppIcon(String packageName, double size); @@ -30,7 +30,7 @@ abstract class AndroidAppService { Future pinToHomeScreen(String label, AvesEntry? coverEntry, {Set? filters, String? uri}); } -class PlatformAndroidAppService implements AndroidAppService { +class PlatformAppService implements AppService { static const _platform = MethodChannel('deckers.thibault/aves/app'); static final _knownAppDirs = { diff --git a/lib/services/common/services.dart b/lib/services/common/services.dart index 6fadb62e1..89ce1c47a 100644 --- a/lib/services/common/services.dart +++ b/lib/services/common/services.dart @@ -3,7 +3,7 @@ import 'package:aves/model/db/db_metadata.dart'; import 'package:aves/model/db/db_metadata_sqflite.dart'; import 'package:aves/model/settings/store/store.dart'; import 'package:aves/model/settings/store/store_shared_pref.dart'; -import 'package:aves/services/android_app_service.dart'; +import 'package:aves/services/app_service.dart'; import 'package:aves/services/device_service.dart'; import 'package:aves/services/media/embedded_data_service.dart'; import 'package:aves/services/media/media_edit_service.dart'; @@ -31,7 +31,7 @@ final p.Context pContext = getIt(); final AvesAvailability availability = getIt(); final MetadataDb metadataDb = getIt(); -final AndroidAppService androidAppService = getIt(); +final AppService appService = getIt(); final DeviceService deviceService = getIt(); final EmbeddedDataService embeddedDataService = getIt(); final MediaEditService mediaEditService = getIt(); @@ -51,7 +51,7 @@ void initPlatformServices() { getIt.registerLazySingleton(LiveAvesAvailability.new); getIt.registerLazySingleton(SqfliteMetadataDb.new); - getIt.registerLazySingleton(PlatformAndroidAppService.new); + getIt.registerLazySingleton(PlatformAppService.new); getIt.registerLazySingleton(PlatformDeviceService.new); getIt.registerLazySingleton(PlatformEmbeddedDataService.new); getIt.registerLazySingleton(PlatformMediaEditService.new); diff --git a/lib/services/media/media_fetch_service.dart b/lib/services/media/media_fetch_service.dart index 4d6a9c4cd..e249024d2 100644 --- a/lib/services/media/media_fetch_service.dart +++ b/lib/services/media/media_fetch_service.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:math'; +import 'package:aves/model/app/support.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/common/output_buffer.dart'; @@ -152,7 +153,7 @@ class PlatformMediaFetchService implements MediaFetchService { // `await` here, so that `completeError` will be caught below return await completer.future; } on PlatformException catch (e, stack) { - if (!MimeTypes.knownMediaTypes.contains(mimeType) && MimeTypes.isVisual(mimeType)) { + if (_isUnknownVisual(mimeType)) { await reportService.recordError(e, stack); } } @@ -191,7 +192,7 @@ class PlatformMediaFetchService implements MediaFetchService { }); if (result != null) return result as Uint8List; } on PlatformException catch (e, stack) { - if (!MimeTypes.knownMediaTypes.contains(mimeType) && MimeTypes.isVisual(mimeType)) { + if (_isUnknownVisual(mimeType)) { await reportService.recordError(e, stack); } } @@ -231,7 +232,7 @@ class PlatformMediaFetchService implements MediaFetchService { }); if (result != null) return result as Uint8List; } on PlatformException catch (e, stack) { - if (!MimeTypes.knownMediaTypes.contains(mimeType) && MimeTypes.isVisual(mimeType)) { + if (_isUnknownVisual(mimeType)) { await reportService.recordError(e, stack); } } @@ -259,4 +260,47 @@ class PlatformMediaFetchService implements MediaFetchService { @override Future? resumeLoading(Object taskKey) => servicePolicy.resume(taskKey); + + // convenience methods + + bool _isUnknownVisual(String mimeType) => !_knownMediaTypes.contains(mimeType) && MimeTypes.isVisual(mimeType); + + static const Set _knownOpaqueImages = { + MimeTypes.jpeg, + }; + + static const Set _knownVideos = { + MimeTypes.v3gpp, + MimeTypes.asf, + MimeTypes.avi, + MimeTypes.aviMSVideo, + MimeTypes.aviVnd, + MimeTypes.aviXMSVideo, + MimeTypes.dvd, + MimeTypes.flv, + MimeTypes.flvX, + MimeTypes.mkv, + MimeTypes.mkvX, + MimeTypes.mov, + MimeTypes.movX, + MimeTypes.mp2p, + MimeTypes.mp2t, + MimeTypes.mp2ts, + MimeTypes.mp4, + MimeTypes.mpeg, + MimeTypes.ogv, + MimeTypes.realVideo, + MimeTypes.webm, + MimeTypes.wmv, + }; + + static final Set _knownMediaTypes = { + MimeTypes.anyImage, + ..._knownOpaqueImages, + ...MimeTypes.alphaImages, + ...MimeTypes.rawImages, + ...AppSupport.undecodableImages, + MimeTypes.anyVideo, + ..._knownVideos, + }; } diff --git a/lib/services/metadata/metadata_edit_service.dart b/lib/services/metadata/metadata_edit_service.dart index 53eb204a4..a6b287575 100644 --- a/lib/services/metadata/metadata_edit_service.dart +++ b/lib/services/metadata/metadata_edit_service.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/metadata/date_modifier.dart'; import 'package:aves/model/metadata/enums/enums.dart'; import 'package:aves/model/metadata/enums/metadata_type.dart'; diff --git a/lib/services/storage_service.dart b/lib/services/storage_service.dart index 2718acb32..f6213df05 100644 --- a/lib/services/storage_service.dart +++ b/lib/services/storage_service.dart @@ -1,8 +1,9 @@ import 'dart:async'; +import 'package:aves/model/storage/relative_dir.dart'; +import 'package:aves/model/storage/volume.dart'; import 'package:aves/services/common/output_buffer.dart'; import 'package:aves/services/common/services.dart'; -import 'package:aves/utils/android_file_utils.dart'; import 'package:flutter/services.dart'; import 'package:streams_channel/streams_channel.dart'; diff --git a/lib/utils/android_file_utils.dart b/lib/utils/android_file_utils.dart index d49784fb6..dcb589758 100644 --- a/lib/utils/android_file_utils.dart +++ b/lib/utils/android_file_utils.dart @@ -1,28 +1,34 @@ +import 'package:aves/model/apps.dart'; +import 'package:aves/model/source/enums/enums.dart'; +import 'package:aves/model/storage/volume.dart'; import 'package:aves/model/vaults/vaults.dart'; import 'package:aves/services/common/services.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:collection/collection.dart'; -import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; final AndroidFileUtils androidFileUtils = AndroidFileUtils._private(); class AndroidFileUtils { + // cf https://developer.android.com/reference/android/content/ContentResolver#SCHEME_CONTENT + static const contentScheme = 'content'; + + // cf https://developer.android.com/reference/android/provider/MediaStore#AUTHORITY + static const mediaStoreAuthority = 'media'; + + // cf https://developer.android.com/reference/android/provider/MediaStore#VOLUME_EXTERNAL + static const externalVolume = 'external'; + + static const mediaStoreUriRoot = '$contentScheme://$mediaStoreAuthority/'; + static const mediaUriPathRoots = {'/$externalVolume/images/', '/$externalVolume/video/'}; + static const String trashDirPath = '#trash'; late final String separator, vaultRoot, primaryStorage; late final String dcimPath, downloadPath, moviesPath, picturesPath, avesVideoCapturesPath; late final Set videoCapturesPaths; Set storageVolumes = {}; - Set _packages = {}; - List _potentialAppDirs = []; bool _initialized = false; - ValueNotifier areAppNamesReadyNotifier = ValueNotifier(false); - - Iterable get _launcherPackages => _packages.where((package) => package.categoryLauncher); - AndroidFileUtils._private(); Future init() async { @@ -58,21 +64,6 @@ class AndroidFileUtils { } } - Future initAppNames() async { - if (_packages.isEmpty) { - debugPrint('Access installed app inventory'); - _packages = await androidAppService.getPackages(); - _potentialAppDirs = _launcherPackages.expand((package) => package.potentialDirs).toList(); - areAppNamesReadyNotifier.value = true; - } - } - - Future resetAppNames() async { - _packages.clear(); - _potentialAppDirs.clear(); - areAppNamesReadyNotifier.value = false; - } - 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('${separator}Screenshots'); @@ -103,147 +94,8 @@ class AndroidFileUtils { if (isVideoCapturesPath(dirPath)) return AlbumType.videoCaptures; final dir = pContext.split(dirPath).last; - if (dirPath.startsWith(primaryStorage) && _potentialAppDirs.contains(dir)) return AlbumType.app; + if (dirPath.startsWith(primaryStorage) && appInventory.isPotentialAppDir(dir)) return AlbumType.app; return AlbumType.regular; } - - String? getAlbumAppPackageName(String albumPath) { - final dir = pContext.split(albumPath).last; - final package = _launcherPackages.firstWhereOrNull((package) => package.potentialDirs.contains(dir)); - return package?.packageName; - } - - String? getCurrentAppName(String packageName) { - final package = _packages.firstWhereOrNull((package) => package.packageName == packageName); - return package?.currentLabel; - } -} - -enum AlbumType { - regular, - vault, - app, - camera, - download, - screenRecordings, - screenshots, - videoCaptures, -} - -class Package { - final String packageName; - final String? currentLabel, englishLabel; - final bool categoryLauncher, isSystem; - final Set ownedDirs = {}; - - Package({ - required this.packageName, - required this.currentLabel, - required this.englishLabel, - required this.categoryLauncher, - required this.isSystem, - }); - - factory Package.fromMap(Map map) { - return Package( - packageName: map['packageName'] ?? '', - currentLabel: map['currentLabel'], - englishLabel: map['englishLabel'], - categoryLauncher: map['categoryLauncher'] ?? false, - isSystem: map['isSystem'] ?? false, - ); - } - - Set get potentialDirs => [ - currentLabel, - englishLabel, - ...ownedDirs, - ].whereNotNull().toSet(); - - @override - String toString() => '$runtimeType#${shortHash(this)}{packageName=$packageName, categoryLauncher=$categoryLauncher, isSystem=$isSystem, currentLabel=$currentLabel, englishLabel=$englishLabel, ownedDirs=$ownedDirs}'; -} - -@immutable -class StorageVolume extends Equatable { - final String? _description; - final String path, state; - final bool isPrimary, isRemovable; - - @override - List get props => [_description, path, state, isPrimary, isRemovable]; - - const StorageVolume({ - required String? description, - required this.isPrimary, - required this.isRemovable, - required this.path, - required this.state, - }) : _description = description; - - String getDescription(BuildContext? context) { - if (_description != null) return _description!; - // ideally, the context should always be provided, but in some cases (e.g. album comparison), - // this would require numerous additional methods to have the context as argument - // for such a minor benefit: fallback volume description on Android < N - if (isPrimary) return context?.l10n.storageVolumeDescriptionFallbackPrimary ?? 'Internal Storage'; - return context?.l10n.storageVolumeDescriptionFallbackNonPrimary ?? 'SD card'; - } - - factory StorageVolume.fromMap(Map map) { - final isPrimary = map['isPrimary'] ?? false; - return StorageVolume( - description: map['description'], - isPrimary: isPrimary, - isRemovable: map['isRemovable'] ?? false, - path: map['path'] ?? '', - state: map['state'] ?? '', - ); - } -} - -@immutable -class VolumeRelativeDirectory extends Equatable { - final String volumePath, relativeDir; - - @override - List get props => [volumePath, relativeDir]; - - String get dirPath => '$volumePath$relativeDir'; - - const VolumeRelativeDirectory({ - required this.volumePath, - required this.relativeDir, - }); - - static VolumeRelativeDirectory fromMap(Map map) { - return VolumeRelativeDirectory( - volumePath: map['volumePath'] ?? '', - relativeDir: map['relativeDir'] ?? '', - ); - } - - Map toMap() => { - 'volumePath': volumePath, - 'relativeDir': relativeDir, - }; - - // prefer static method over a null returning factory constructor - static VolumeRelativeDirectory? fromPath(String dirPath) { - final volume = androidFileUtils.getStorageVolume(dirPath); - if (volume == null) return null; - - final root = volume.path; - final rootLength = root.length; - return VolumeRelativeDirectory( - volumePath: root, - relativeDir: dirPath.length < rootLength ? '' : dirPath.substring(rootLength), - ); - } - - String getVolumeDescription(BuildContext context) { - final volume = androidFileUtils.storageVolumes.firstWhereOrNull((volume) => volume.path == volumePath); - return volume?.getDescription(context) ?? volumePath; - } } diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart index 6d275cfd8..0faebd213 100644 --- a/lib/widgets/aves_app.dart +++ b/lib/widgets/aves_app.dart @@ -24,7 +24,7 @@ import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/theme/styles.dart'; import 'package:aves/theme/themes.dart'; -import 'package:aves/utils/android_file_utils.dart'; +import 'package:aves/model/apps.dart'; import 'package:aves/utils/debouncer.dart'; import 'package:aves/widgets/collection/collection_grid.dart'; import 'package:aves/widgets/collection/collection_page.dart'; @@ -493,9 +493,9 @@ class _AvesAppState extends State with WidgetsBindingObserver { void _monitorSettings() { void applyIsInstalledAppAccessAllowed() { if (settings.isInstalledAppAccessAllowed) { - androidFileUtils.initAppNames(); + appInventory.initAppNames(); } else { - androidFileUtils.resetAppNames(); + appInventory.resetAppNames(); } } diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index 56acb0141..e1a3f92db 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/entry_set_actions.dart'; +import 'package:aves/model/actions/entry_set.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/query.dart'; diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index 9a500191e..b50fc99c4 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/entry_set_actions.dart'; +import 'package:aves/model/actions/entry_set.dart'; import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/device.dart'; import 'package:aves/model/entry/entry.dart'; @@ -20,7 +20,7 @@ import 'package:aves/model/source/analysis_controller.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/vaults/vaults.dart'; -import 'package:aves/services/android_app_service.dart'; +import 'package:aves/services/app_service.dart'; import 'package:aves/services/common/image_op_events.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/durations.dart'; @@ -264,7 +264,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware Future _share(BuildContext context) async { final entries = _getTargetItems(context); try { - if (!await androidAppService.shareEntries(entries)) { + if (!await appService.shareEntries(entries)) { await showNoMatchingAppDialog(context); } } on TooManyItemsException catch (_) { @@ -741,7 +741,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware final name = result.item2; if (name.isEmpty) return; - await androidAppService.pinToHomeScreen(name, coverEntry, filters: filters); + await appService.pinToHomeScreen(name, coverEntry, filters: filters); if (!device.showPinShortcutFeedback) { showFeedback(context, context.l10n.genericSuccessFeedback); } diff --git a/lib/widgets/collection/grid/headers/album.dart b/lib/widgets/collection/grid/headers/album.dart index dc15e9bfc..c8e120f9e 100644 --- a/lib/widgets/collection/grid/headers/album.dart +++ b/lib/widgets/collection/grid/headers/album.dart @@ -1,6 +1,7 @@ import 'package:aves/model/covers.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/model/source/section_keys.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/android_file_utils.dart'; diff --git a/lib/widgets/common/action_controls/quick_choosers/move_button.dart b/lib/widgets/common/action_controls/quick_choosers/move_button.dart index c6ea6163d..503c65d6c 100644 --- a/lib/widgets/common/action_controls/quick_choosers/move_button.dart +++ b/lib/widgets/common/action_controls/quick_choosers/move_button.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; diff --git a/lib/widgets/common/action_controls/quick_choosers/rate_button.dart b/lib/widgets/common/action_controls/quick_choosers/rate_button.dart index e14e1cb8e..f5d6a8025 100644 --- a/lib/widgets/common/action_controls/quick_choosers/rate_button.dart +++ b/lib/widgets/common/action_controls/quick_choosers/rate_button.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/widgets/common/action_controls/quick_choosers/common/button.dart'; import 'package:aves/widgets/common/action_controls/quick_choosers/rate_chooser.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/common/action_controls/quick_choosers/share_button.dart b/lib/widgets/common/action_controls/quick_choosers/share_button.dart index 7e0164400..94482b1b8 100644 --- a/lib/widgets/common/action_controls/quick_choosers/share_button.dart +++ b/lib/widgets/common/action_controls/quick_choosers/share_button.dart @@ -1,5 +1,5 @@ -import 'package:aves/model/actions/entry_actions.dart'; -import 'package:aves/model/actions/share_actions.dart'; +import 'package:aves/model/actions/entry.dart'; +import 'package:aves/model/actions/share.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/widgets/common/action_controls/quick_choosers/common/button.dart'; diff --git a/lib/widgets/common/action_controls/quick_choosers/share_chooser.dart b/lib/widgets/common/action_controls/quick_choosers/share_chooser.dart index d9d8e6176..21a4f7589 100644 --- a/lib/widgets/common/action_controls/quick_choosers/share_chooser.dart +++ b/lib/widgets/common/action_controls/quick_choosers/share_chooser.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:aves/model/actions/share_actions.dart'; +import 'package:aves/model/actions/share.dart'; import 'package:aves/widgets/common/action_controls/quick_choosers/common/menu.dart'; import 'package:aves/widgets/common/basic/popup/menu_row.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/common/action_controls/quick_choosers/tag_button.dart b/lib/widgets/common/action_controls/quick_choosers/tag_button.dart index 6735d644a..08e9576b0 100644 --- a/lib/widgets/common/action_controls/quick_choosers/tag_button.dart +++ b/lib/widgets/common/action_controls/quick_choosers/tag_button.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/settings/settings.dart'; diff --git a/lib/widgets/common/action_mixins/permission_aware.dart b/lib/widgets/common/action_mixins/permission_aware.dart index 5d8fa13f9..79caaf0b8 100644 --- a/lib/widgets/common/action_mixins/permission_aware.dart +++ b/lib/widgets/common/action_mixins/permission_aware.dart @@ -1,6 +1,7 @@ import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/entry/extensions/props.dart'; +import 'package:aves/model/storage/relative_dir.dart'; import 'package:aves/services/common/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'; import 'package:collection/collection.dart'; diff --git a/lib/widgets/common/action_mixins/size_aware.dart b/lib/widgets/common/action_mixins/size_aware.dart index d1a93321b..e79dd8a85 100644 --- a/lib/widgets/common/action_mixins/size_aware.dart +++ b/lib/widgets/common/action_mixins/size_aware.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/storage/volume.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/collection_utils.dart'; diff --git a/lib/widgets/common/identity/aves_filter_chip.dart b/lib/widgets/common/identity/aves_filter_chip.dart index 00151a3c4..8400692bf 100644 --- a/lib/widgets/common/identity/aves_filter_chip.dart +++ b/lib/widgets/common/identity/aves_filter_chip.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:math'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/chip_actions.dart'; +import 'package:aves/model/actions/chip.dart'; import 'package:aves/model/covers.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; diff --git a/lib/widgets/common/identity/aves_icons.dart b/lib/widgets/common/identity/aves_icons.dart index 0428f0ca0..d5ac8f60d 100644 --- a/lib/widgets/common/identity/aves_icons.dart +++ b/lib/widgets/common/identity/aves_icons.dart @@ -3,9 +3,9 @@ import 'package:aves/model/covers.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/model/entry/extensions/props.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/model/vaults/vaults.dart'; import 'package:aves/theme/icons.dart'; -import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/grid/theme.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/common/map/buttons/panel.dart b/lib/widgets/common/map/buttons/panel.dart index 9d72ae6e7..1212ea9f1 100644 --- a/lib/widgets/common/map/buttons/panel.dart +++ b/lib/widgets/common/map/buttons/panel.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/actions/map_actions.dart'; +import 'package:aves/model/actions/map.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/widgets/common/map/map_action_delegate.dart b/lib/widgets/common/map/map_action_delegate.dart index 1b323d2d9..0826135ba 100644 --- a/lib/widgets/common/map/map_action_delegate.dart +++ b/lib/widgets/common/map/map_action_delegate.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/actions/map_actions.dart'; +import 'package:aves/model/actions/map.dart'; import 'package:aves/model/settings/enums/l10n.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/common/services.dart'; diff --git a/lib/widgets/debug/android_apps.dart b/lib/widgets/debug/android_apps.dart index af96119e2..b7f42041c 100644 --- a/lib/widgets/debug/android_apps.dart +++ b/lib/widgets/debug/android_apps.dart @@ -1,6 +1,6 @@ import 'package:aves/image_providers/app_icon_image_provider.dart'; +import 'package:aves/model/apps.dart'; import 'package:aves/services/common/services.dart'; -import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/common/basic/query_bar.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; import 'package:aves/widgets/viewer/info/common.dart'; @@ -23,7 +23,7 @@ class _DebugAndroidAppSectionState extends State with Au @override void initState() { super.initState(); - _loader = androidAppService.getPackages(); + _loader = appService.getPackages(); } @override diff --git a/lib/widgets/dialogs/convert_entry_dialog.dart b/lib/widgets/dialogs/convert_entry_dialog.dart index 9d2d23a1f..68f0dd3e9 100644 --- a/lib/widgets/dialogs/convert_entry_dialog.dart +++ b/lib/widgets/dialogs/convert_entry_dialog.dart @@ -1,3 +1,4 @@ +import 'package:aves/model/app/support.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/metadata/enums/enums.dart'; import 'package:aves/model/metadata/enums/length_unit.dart'; @@ -205,7 +206,7 @@ class _ConvertEntryDialogState extends State { valueListenable: _mimeTypeNotifier, builder: (context, mimeType, child) { Widget child; - if (MimeTypes.canEditExif(mimeType) || MimeTypes.canEditIptc(mimeType) || MimeTypes.canEditXmp(mimeType)) { + if (AppSupport.canEditExif(mimeType) || AppSupport.canEditIptc(mimeType) || AppSupport.canEditXmp(mimeType)) { child = SwitchListTile( value: _writeMetadata, onChanged: (v) => setState(() => _writeMetadata = v), diff --git a/lib/widgets/dialogs/filter_editors/create_album_dialog.dart b/lib/widgets/dialogs/filter_editors/create_album_dialog.dart index def2ecef2..b721a5ae8 100644 --- a/lib/widgets/dialogs/filter_editors/create_album_dialog.dart +++ b/lib/widgets/dialogs/filter_editors/create_album_dialog.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:aves/model/storage/volume.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/utils/android_file_utils.dart'; diff --git a/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart b/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart index d951da8bc..ce392e31c 100644 --- a/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart +++ b/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart @@ -1,5 +1,5 @@ import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/chip_set_actions.dart'; +import 'package:aves/model/actions/chip_set.dart'; import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; diff --git a/lib/widgets/dialogs/pick_dialogs/app_pick_page.dart b/lib/widgets/dialogs/pick_dialogs/app_pick_page.dart index 2b9799064..997388968 100644 --- a/lib/widgets/dialogs/pick_dialogs/app_pick_page.dart +++ b/lib/widgets/dialogs/pick_dialogs/app_pick_page.dart @@ -1,7 +1,7 @@ import 'package:aves/image_providers/app_icon_image_provider.dart'; +import 'package:aves/model/apps.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/common/services.dart'; -import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/common/basic/list_tiles/reselectable_radio.dart'; import 'package:aves/widgets/common/basic/query_bar.dart'; import 'package:aves/widgets/common/basic/scaffold.dart'; @@ -34,7 +34,7 @@ class _AppPickPageState extends State { void initState() { super.initState(); _selectedValue = widget.initialValue; - _loader = androidAppService.getPackages(); + _loader = appService.getPackages(); } @override diff --git a/lib/widgets/filter_grids/albums_page.dart b/lib/widgets/filter_grids/albums_page.dart index 5cd758ffb..e651867d8 100644 --- a/lib/widgets/filter_grids/albums_page.dart +++ b/lib/widgets/filter_grids/albums_page.dart @@ -7,6 +7,7 @@ import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/theme/icons.dart'; +import 'package:aves/model/apps.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/empty.dart'; @@ -35,7 +36,7 @@ class AlbumListPage extends StatelessWidget { }, builder: (context, s, child) { return ValueListenableBuilder( - valueListenable: androidFileUtils.areAppNamesReadyNotifier, + valueListenable: appInventory.areAppNamesReadyNotifier, builder: (context, areAppNamesReady, child) { return StreamBuilder( stream: source.eventBus.on(), diff --git a/lib/widgets/filter_grids/common/action_delegates/album_set.dart b/lib/widgets/filter_grids/common/action_delegates/album_set.dart index b90da750e..49b8204ac 100644 --- a/lib/widgets/filter_grids/common/action_delegates/album_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/album_set.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/chip_set_actions.dart'; +import 'package:aves/model/actions/chip_set.dart'; import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/device.dart'; import 'package:aves/model/entry/entry.dart'; @@ -14,13 +14,13 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/model/source/enums/view.dart'; +import 'package:aves/model/storage/relative_dir.dart'; import 'package:aves/model/vaults/details.dart'; import 'package:aves/model/vaults/vaults.dart'; import 'package:aves/services/common/image_op_events.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/services/media/enums.dart'; import 'package:aves/theme/durations.dart'; -import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/common/action_mixins/entry_storage.dart'; import 'package:aves/widgets/common/action_mixins/vault_aware.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; diff --git a/lib/widgets/filter_grids/common/action_delegates/chip.dart b/lib/widgets/filter_grids/common/action_delegates/chip.dart index 223d0aa38..978dc6bc4 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/actions/chip_actions.dart'; +import 'package:aves/model/actions/chip.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/highlight.dart'; diff --git a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart index a9b9c20a2..85f6049db 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart @@ -1,5 +1,5 @@ import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/chip_set_actions.dart'; +import 'package:aves/model/actions/chip_set.dart'; import 'package:aves/model/covers.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/album.dart'; diff --git a/lib/widgets/filter_grids/common/app_bar.dart b/lib/widgets/filter_grids/common/app_bar.dart index c4061299b..2b6d64dce 100644 --- a/lib/widgets/filter_grids/common/app_bar.dart +++ b/lib/widgets/filter_grids/common/app_bar.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/chip_set_actions.dart'; +import 'package:aves/model/actions/chip_set.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/query.dart'; import 'package:aves/model/selection.dart'; diff --git a/lib/widgets/filter_grids/common/covered_filter_chip.dart b/lib/widgets/filter_grids/common/covered_filter_chip.dart index 610fc5d02..a2e066edf 100644 --- a/lib/widgets/filter_grids/common/covered_filter_chip.dart +++ b/lib/widgets/filter_grids/common/covered_filter_chip.dart @@ -13,6 +13,7 @@ import 'package:aves/model/vaults/vaults.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/theme/text.dart'; +import 'package:aves/model/apps.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; @@ -108,7 +109,7 @@ class CoveredFilterChip extends StatelessWidget { if (_filter is AlbumFilter) { // when we asynchronously fetch installed app names, // album filters themselves do not change, but decoration derived from it does - chipKey = ValueKey(androidFileUtils.areAppNamesReadyNotifier.value); + chipKey = ValueKey(appInventory.areAppNamesReadyNotifier.value); } return AvesFilterChip( key: chipKey, diff --git a/lib/widgets/filter_grids/common/section_keys.dart b/lib/widgets/filter_grids/common/section_keys.dart index a93185c39..b6b3f236e 100644 --- a/lib/widgets/filter_grids/common/section_keys.dart +++ b/lib/widgets/filter_grids/common/section_keys.dart @@ -1,6 +1,6 @@ import 'package:aves/model/source/section_keys.dart'; +import 'package:aves/model/storage/volume.dart'; import 'package:aves/theme/icons.dart'; -import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/filter_grids/common/enums.dart'; import 'package:equatable/equatable.dart'; diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index a7e8d4b18..cca2ed58e 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -17,6 +17,7 @@ import 'package:aves/services/common/services.dart'; import 'package:aves/services/global_search.dart'; import 'package:aves/services/intent_service.dart'; import 'package:aves/services/widget_service.dart'; +import 'package:aves/model/apps.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/basic/scaffold.dart'; @@ -107,7 +108,7 @@ class _HomePageState extends State { await androidFileUtils.init(); if (!{actionScreenSaver, actionSetWallpaper}.contains(intentAction) && settings.isInstalledAppAccessAllowed) { - unawaited(androidFileUtils.initAppNames()); + unawaited(appInventory.initAppNames()); } if (intentData.isNotEmpty) { diff --git a/lib/widgets/map/map_page.dart b/lib/widgets/map/map_page.dart index a74075970..d720d1ba1 100644 --- a/lib/widgets/map/map_page.dart +++ b/lib/widgets/map/map_page.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/map_actions.dart'; -import 'package:aves/model/actions/map_cluster_actions.dart'; +import 'package:aves/model/actions/map.dart'; +import 'package:aves/model/actions/map_cluster.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/filters/coordinate.dart'; diff --git a/lib/widgets/navigation/drawer/app_drawer.dart b/lib/widgets/navigation/drawer/app_drawer.dart index 3e22c1b43..12131803a 100644 --- a/lib/widgets/navigation/drawer/app_drawer.dart +++ b/lib/widgets/navigation/drawer/app_drawer.dart @@ -6,6 +6,7 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/model/source/location/country.dart'; import 'package:aves/model/source/location/place.dart'; import 'package:aves/model/source/tag.dart'; diff --git a/lib/widgets/settings/privacy/file_picker/crumb_line.dart b/lib/widgets/settings/privacy/file_picker/crumb_line.dart index a6b408101..13eecba73 100644 --- a/lib/widgets/settings/privacy/file_picker/crumb_line.dart +++ b/lib/widgets/settings/privacy/file_picker/crumb_line.dart @@ -1,6 +1,6 @@ +import 'package:aves/model/storage/relative_dir.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/icons.dart'; -import 'package:aves/utils/android_file_utils.dart'; import 'package:flutter/material.dart'; class CrumbLine extends StatefulWidget { diff --git a/lib/widgets/settings/privacy/file_picker/file_picker_page.dart b/lib/widgets/settings/privacy/file_picker/file_picker_page.dart index 680ab336a..c42741cf8 100644 --- a/lib/widgets/settings/privacy/file_picker/file_picker_page.dart +++ b/lib/widgets/settings/privacy/file_picker/file_picker_page.dart @@ -1,6 +1,8 @@ import 'dart:io'; import 'package:aves/model/settings/settings.dart'; +import 'package:aves/model/storage/relative_dir.dart'; +import 'package:aves/model/storage/volume.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; diff --git a/lib/widgets/settings/settings_page.dart b/lib/widgets/settings/settings_page.dart index 4b9802d5d..84f1dba40 100644 --- a/lib/widgets/settings/settings_page.dart +++ b/lib/widgets/settings/settings_page.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; -import 'package:aves/model/actions/settings_actions.dart'; +import 'package:aves/model/actions/settings.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/ref/mime_types.dart'; diff --git a/lib/widgets/settings/thumbnails/collection_actions_editor_page.dart b/lib/widgets/settings/thumbnails/collection_actions_editor_page.dart index 82e7495b2..15d896039 100644 --- a/lib/widgets/settings/thumbnails/collection_actions_editor_page.dart +++ b/lib/widgets/settings/thumbnails/collection_actions_editor_page.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/actions/entry_set_actions.dart'; +import 'package:aves/model/actions/entry_set.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; diff --git a/lib/widgets/settings/viewer/viewer_actions_editor.dart b/lib/widgets/settings/viewer/viewer_actions_editor.dart index 98e58fa27..c184fa598 100644 --- a/lib/widgets/settings/viewer/viewer_actions_editor.dart +++ b/lib/widgets/settings/viewer/viewer_actions_editor.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/settings/common/quick_actions/editor_page.dart'; diff --git a/lib/widgets/viewer/action/entry_action_delegate.dart b/lib/widgets/viewer/action/entry_action_delegate.dart index ee5ed6e72..b00d6e856 100644 --- a/lib/widgets/viewer/action/entry_action_delegate.dart +++ b/lib/widgets/viewer/action/entry_action_delegate.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'dart:convert'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/model/actions/move_type.dart'; -import 'package:aves/model/actions/share_actions.dart'; +import 'package:aves/model/actions/share.dart'; import 'package:aves/model/device.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/favourites.dart'; @@ -188,7 +188,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix _addShortcut(context, targetEntry); break; case EntryAction.copyToClipboard: - androidAppService.copyToClipboard(targetEntry.uri, targetEntry.bestTitle).then((success) { + appService.copyToClipboard(targetEntry.uri, targetEntry.bestTitle).then((success) { showFeedback(context, success ? context.l10n.genericSuccessFeedback : context.l10n.genericFailureFeedback); }); break; @@ -214,7 +214,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix _move(context, targetEntry, moveType: MoveType.move); break; case EntryAction.share: - androidAppService.shareEntries({targetEntry}).then((success) { + appService.shareEntries({targetEntry}).then((success) { if (!success) showNoMatchingAppDialog(context); }); break; @@ -255,22 +255,22 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix } break; case EntryAction.edit: - androidAppService.edit(targetEntry.uri, targetEntry.mimeType).then((success) { + appService.edit(targetEntry.uri, targetEntry.mimeType).then((success) { if (!success) showNoMatchingAppDialog(context); }); break; case EntryAction.open: - androidAppService.open(targetEntry.uri, targetEntry.mimeTypeAnySubtype, forceChooser: true).then((success) { + appService.open(targetEntry.uri, targetEntry.mimeTypeAnySubtype, forceChooser: true).then((success) { if (!success) showNoMatchingAppDialog(context); }); break; case EntryAction.openMap: - androidAppService.openMap(targetEntry.latLng!).then((success) { + appService.openMap(targetEntry.latLng!).then((success) { if (!success) showNoMatchingAppDialog(context); }); break; case EntryAction.setAs: - androidAppService.setAs(targetEntry.uri, targetEntry.mimeType).then((success) { + appService.setAs(targetEntry.uri, targetEntry.mimeType).then((success) { if (!success) showNoMatchingAppDialog(context); }); break; @@ -334,7 +334,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix final uri = fields['uri'] as String?; final mimeType = fields['mimeType'] as String?; if (uri != null && mimeType != null) { - await androidAppService.shareSingle(uri, mimeType).then((success) { + await appService.shareSingle(uri, mimeType).then((success) { if (!success) showNoMatchingAppDialog(context); }); } @@ -363,7 +363,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix final name = result.item2; if (name.isEmpty) return; - await androidAppService.pinToHomeScreen(name, targetEntry, uri: targetEntry.uri); + await appService.pinToHomeScreen(name, targetEntry, uri: targetEntry.uri); if (!device.showPinShortcutFeedback) { showFeedback(context, context.l10n.genericSuccessFeedback); } diff --git a/lib/widgets/viewer/action/entry_info_action_delegate.dart b/lib/widgets/viewer/action/entry_info_action_delegate.dart index 62eeffbd0..baa3760d8 100644 --- a/lib/widgets/viewer/action/entry_info_action_delegate.dart +++ b/lib/widgets/viewer/action/entry_info_action_delegate.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/model/actions/events.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/info.dart'; diff --git a/lib/widgets/viewer/action/video_action_delegate.dart b/lib/widgets/viewer/action/video_action_delegate.dart index 784aec424..ff3287bc1 100644 --- a/lib/widgets/viewer/action/video_action_delegate.dart +++ b/lib/widgets/viewer/action/video_action_delegate.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/entry/extensions/props.dart'; @@ -68,7 +68,7 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix await controller.seekTo(controller.currentPosition + 10000); break; case EntryAction.openVideo: - await androidAppService.open(entry.uri, entry.mimeTypeAnySubtype, forceChooser: false).then((success) { + await appService.open(entry.uri, entry.mimeTypeAnySubtype, forceChooser: false).then((success) { if (!success) showNoMatchingAppDialog(context); }); break; diff --git a/lib/widgets/viewer/controls/intents.dart b/lib/widgets/viewer/controls/intents.dart index f59918194..8715aa7c5 100644 --- a/lib/widgets/viewer/controls/intents.dart +++ b/lib/widgets/viewer/controls/intents.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:flutter/widgets.dart'; class ShowPreviousIntent extends Intent { diff --git a/lib/widgets/viewer/controls/notifications.dart b/lib/widgets/viewer/controls/notifications.dart index 5cb76faa7..8ed5068ca 100644 --- a/lib/widgets/viewer/controls/notifications.dart +++ b/lib/widgets/viewer/controls/notifications.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/filters.dart'; diff --git a/lib/widgets/viewer/controls/shortcuts.dart b/lib/widgets/viewer/controls/shortcuts.dart index af2c9b9b8..3f7b85b20 100644 --- a/lib/widgets/viewer/controls/shortcuts.dart +++ b/lib/widgets/viewer/controls/shortcuts.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/widgets/viewer/controls/intents.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; diff --git a/lib/widgets/viewer/debug/debug_page.dart b/lib/widgets/viewer/debug/debug_page.dart index a45b213ed..a987e9a6b 100644 --- a/lib/widgets/viewer/debug/debug_page.dart +++ b/lib/widgets/viewer/debug/debug_page.dart @@ -1,8 +1,8 @@ import 'package:aves/app_mode.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/favourites.dart'; -import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/entry/extensions/images.dart'; +import 'package:aves/model/entry/extensions/location.dart'; import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/theme/icons.dart'; @@ -130,7 +130,6 @@ class ViewerDebugPage extends StatelessWidget { 'sizeBytes': '${entry.sizeBytes}', 'isFavourite': '${entry.isFavourite}', 'isSvg': '${entry.isSvg}', - 'isPhoto': '${entry.isPhoto}', 'isVideo': '${entry.isVideo}', 'isCatalogued': '${entry.isCatalogued}', 'isAnimated': '${entry.isAnimated}', diff --git a/lib/widgets/viewer/entry_vertical_pager.dart b/lib/widgets/viewer/entry_vertical_pager.dart index 3446ca8b4..bb5af6976 100644 --- a/lib/widgets/viewer/entry_vertical_pager.dart +++ b/lib/widgets/viewer/entry_vertical_pager.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'dart:ui'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/catalog.dart'; import 'package:aves/model/entry/extensions/location.dart'; diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart index 0092943cb..076908d50 100644 --- a/lib/widgets/viewer/entry_viewer_stack.dart +++ b/lib/widgets/viewer/entry_viewer_stack.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:math'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/model/actions/move_type.dart'; import 'package:aves/model/device.dart'; import 'package:aves/model/entry/entry.dart'; diff --git a/lib/widgets/viewer/info/basic_section.dart b/lib/widgets/viewer/info/basic_section.dart index b1da03c60..44deff165 100644 --- a/lib/widgets/viewer/info/basic_section.dart +++ b/lib/widgets/viewer/info/basic_section.dart @@ -1,6 +1,7 @@ import 'package:aves/app_mode.dart'; import 'package:aves/image_providers/app_icon_image_provider.dart'; -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; +import 'package:aves/model/apps.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/favourites.dart'; import 'package:aves/model/entry/extensions/multipage.dart'; @@ -14,10 +15,10 @@ import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/filters/type.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; +import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/format.dart'; -import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/file_utils.dart'; import 'package:aves/widgets/common/action_controls/quick_choosers/rate_button.dart'; import 'package:aves/widgets/common/action_controls/quick_choosers/tag_button.dart'; @@ -273,9 +274,12 @@ class _BasicInfoState extends State<_BasicInfo> { AvesEntry get entry => widget.entry; - int get megaPixels => entry.megaPixels; + int get megaPixels => (entry.width * entry.height / 1000000).round(); - bool get showMegaPixels => entry.isPhoto && megaPixels > 0; + // guess whether this is a photo, according to file type + bool get isPhoto => [MimeTypes.heic, MimeTypes.heif, MimeTypes.jpeg, MimeTypes.tiff].contains(entry.mimeType) || entry.isRaw; + + bool get showMegaPixels => isPhoto && megaPixels > 0; String get rasterResolutionText => '${entry.resolutionText}${showMegaPixels ? ' • $megaPixels MP' : ''}'; @@ -291,7 +295,7 @@ class _BasicInfoState extends State<_BasicInfo> { }); final isViewerMode = context.read>().value == AppMode.view; if (isViewerMode && settings.isInstalledAppAccessAllowed) { - _appNameLoader = androidFileUtils.initAppNames(); + _appNameLoader = appInventory.initAppNames(); } } } @@ -349,7 +353,7 @@ class _BasicInfoState extends State<_BasicInfo> { InfoValueSpanBuilder _ownerHandler(String? ownerPackage) { if (ownerPackage == null) return (context, key, value) => []; - final appName = androidFileUtils.getCurrentAppName(ownerPackage) ?? ownerPackage; + final appName = appInventory.getCurrentAppName(ownerPackage) ?? ownerPackage; return (context, key, value) => [ WidgetSpan( alignment: PlaceholderAlignment.middle, diff --git a/lib/widgets/viewer/info/embedded/embedded_data_opener.dart b/lib/widgets/viewer/info/embedded/embedded_data_opener.dart index ff96ebdcd..287a712fb 100644 --- a/lib/widgets/viewer/info/embedded/embedded_data_opener.dart +++ b/lib/widgets/viewer/info/embedded/embedded_data_opener.dart @@ -62,10 +62,10 @@ class EmbeddedDataOpener extends StatelessWidget with FeedbackMixin { final uri = fields['uri']!; if (!MimeTypes.isImage(mimeType) && !MimeTypes.isVideo(mimeType)) { // open with another app - unawaited(androidAppService.open(uri, mimeType, forceChooser: true).then((success) { + unawaited(appService.open(uri, mimeType, forceChooser: true).then((success) { if (!success) { // fallback to sharing, so that the file can be saved somewhere - androidAppService.shareSingle(uri, mimeType).then((success) { + appService.shareSingle(uri, mimeType).then((success) { if (!success) showNoMatchingAppDialog(context); }); } diff --git a/lib/widgets/viewer/info/info_app_bar.dart b/lib/widgets/viewer/info/info_app_bar.dart index a54eded5e..a7d6f5723 100644 --- a/lib/widgets/viewer/info/info_app_bar.dart +++ b/lib/widgets/viewer/info/info_app_bar.dart @@ -1,5 +1,5 @@ import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/selection.dart'; diff --git a/lib/widgets/viewer/info/info_page.dart b/lib/widgets/viewer/info/info_page.dart index 3820af71c..002f516b0 100644 --- a/lib/widgets/viewer/info/info_page.dart +++ b/lib/widgets/viewer/info/info_page.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/model/actions/events.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/multipage.dart'; diff --git a/lib/widgets/viewer/overlay/slideshow_buttons.dart b/lib/widgets/viewer/overlay/slideshow_buttons.dart index cbfee2a3b..d957e62fb 100644 --- a/lib/widgets/viewer/overlay/slideshow_buttons.dart +++ b/lib/widgets/viewer/overlay/slideshow_buttons.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/actions/slideshow_actions.dart'; +import 'package:aves/model/actions/slideshow.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/identity/buttons/captioned_button.dart'; import 'package:aves/widgets/common/identity/buttons/overlay_button.dart'; diff --git a/lib/widgets/viewer/overlay/video/controls.dart b/lib/widgets/viewer/overlay/video/controls.dart index d2522896f..b19546417 100644 --- a/lib/widgets/viewer/overlay/video/controls.dart +++ b/lib/widgets/viewer/overlay/video/controls.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; diff --git a/lib/widgets/viewer/overlay/video/video.dart b/lib/widgets/viewer/overlay/video/video.dart index 409ea7bd2..c12efaace 100644 --- a/lib/widgets/viewer/overlay/video/video.dart +++ b/lib/widgets/viewer/overlay/video/video.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/widgets/common/identity/buttons/overlay_button.dart'; import 'package:aves/widgets/viewer/overlay/video/controls.dart'; diff --git a/lib/widgets/viewer/overlay/viewer_buttons.dart b/lib/widgets/viewer/overlay/viewer_buttons.dart index bd575afd7..12917c8ba 100644 --- a/lib/widgets/viewer/overlay/viewer_buttons.dart +++ b/lib/widgets/viewer/overlay/viewer_buttons.dart @@ -1,7 +1,7 @@ import 'dart:math'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/model/entry/extensions/props.dart'; diff --git a/lib/widgets/viewer/slideshow_page.dart b/lib/widgets/viewer/slideshow_page.dart index 48bcd0e2e..ee0a7e3ab 100644 --- a/lib/widgets/viewer/slideshow_page.dart +++ b/lib/widgets/viewer/slideshow_page.dart @@ -1,5 +1,5 @@ import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/slideshow_actions.dart'; +import 'package:aves/model/actions/slideshow.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/mime.dart'; diff --git a/lib/widgets/viewer/visual/entry_page_view.dart b/lib/widgets/viewer/visual/entry_page_view.dart index fff2d55d1..decda0c66 100644 --- a/lib/widgets/viewer/visual/entry_page_view.dart +++ b/lib/widgets/viewer/visual/entry_page_view.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; diff --git a/lib/widgets/wallpaper_page.dart b/lib/widgets/wallpaper_page.dart index 82642670f..8b40e8eca 100644 --- a/lib/widgets/wallpaper_page.dart +++ b/lib/widgets/wallpaper_page.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/actions/entry.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/model/entry/extensions/props.dart'; diff --git a/test/fake/android_app_service.dart b/test/fake/android_app_service.dart index 436d767e9..0cd007280 100644 --- a/test/fake/android_app_service.dart +++ b/test/fake/android_app_service.dart @@ -1,9 +1,9 @@ -import 'package:aves/services/android_app_service.dart'; -import 'package:aves/utils/android_file_utils.dart'; +import 'package:aves/model/apps.dart'; +import 'package:aves/services/app_service.dart'; import 'package:flutter/foundation.dart'; import 'package:test/fake.dart'; -class FakeAndroidAppService extends Fake implements AndroidAppService { +class FakeAppService extends Fake implements AppService { @override Future> getPackages() => SynchronousFuture({}); } diff --git a/test/fake/storage_service.dart b/test/fake/storage_service.dart index e74eacdcd..428caa5be 100644 --- a/test/fake/storage_service.dart +++ b/test/fake/storage_service.dart @@ -1,5 +1,5 @@ +import 'package:aves/model/storage/volume.dart'; import 'package:aves/services/storage_service.dart'; -import 'package:aves/utils/android_file_utils.dart'; import 'package:flutter/foundation.dart'; import 'package:test/fake.dart'; diff --git a/test/model/collection_source_test.dart b/test/model/collection_source_test.dart index ecc7127af..2c0c7e382 100644 --- a/test/model/collection_source_test.dart +++ b/test/model/collection_source_test.dart @@ -13,7 +13,7 @@ import 'package:aves/model/metadata/catalog.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/media_store_source.dart'; -import 'package:aves/services/android_app_service.dart'; +import 'package:aves/services/app_service.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/services/device_service.dart'; import 'package:aves/services/media/media_fetch_service.dart'; @@ -59,7 +59,7 @@ void main() { getIt.registerLazySingleton(FakeAvesAvailability.new); getIt.registerLazySingleton(FakeMetadataDb.new); - getIt.registerLazySingleton(FakeAndroidAppService.new); + getIt.registerLazySingleton(FakeAppService.new); getIt.registerLazySingleton(FakeDeviceService.new); getIt.registerLazySingleton(FakeMediaFetchService.new); getIt.registerLazySingleton(FakeMediaStoreService.new);