refactor
This commit is contained in:
parent
ad3edf4458
commit
86b982d270
92 changed files with 573 additions and 435 deletions
|
@ -33,11 +33,23 @@ import java.util.regex.Pattern
|
||||||
object StorageUtils {
|
object StorageUtils {
|
||||||
private val LOG_TAG = LogUtils.createTag<StorageUtils>()
|
private val LOG_TAG = LogUtils.createTag<StorageUtils>()
|
||||||
|
|
||||||
// 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"
|
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 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 UUID_PATTERN = Regex("[A-Fa-f\\d-]+")
|
||||||
private val TREE_URI_PATH_PATTERN = Pattern.compile("(.*?):(.*)")
|
private val TREE_URI_PATH_PATTERN = Pattern.compile("(.*?):(.*)")
|
||||||
|
@ -545,7 +557,7 @@ object StorageUtils {
|
||||||
uri ?: return false
|
uri ?: return false
|
||||||
// a URI's authority is [userinfo@]host[:port]
|
// a URI's authority is [userinfo@]host[:port]
|
||||||
// but we only want the host when comparing to Media Store's "authority"
|
// 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 {
|
fun getOriginalUri(context: Context, uri: Uri): Uri {
|
||||||
|
@ -554,7 +566,7 @@ object StorageUtils {
|
||||||
val path = uri.path
|
val path = uri.path
|
||||||
path ?: return uri
|
path ?: return uri
|
||||||
// from Android 11, accessing the original URI for a `file` or `downloads` media content yields a `SecurityException`
|
// 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"
|
// "Caller must hold ACCESS_MEDIA_LOCATION permission to access original"
|
||||||
if (context.checkSelfPermission(Manifest.permission.ACCESS_MEDIA_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
if (context.checkSelfPermission(Manifest.permission.ACCESS_MEDIA_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||||
return MediaStore.setRequireOriginal(uri)
|
return MediaStore.setRequireOriginal(uri)
|
||||||
|
@ -611,7 +623,7 @@ object StorageUtils {
|
||||||
return uri
|
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
|
// 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.
|
// by the Media Store (via `.nomedia`), and therefore has no matching image/video content URI.
|
||||||
private fun getMediaUriImageVideoUri(uri: Uri, mimeType: String): Uri? {
|
private fun getMediaUriImageVideoUri(uri: Uri, mimeType: String): Uri? {
|
||||||
|
|
|
@ -39,7 +39,7 @@ class AppIconImage extends ImageProvider<AppIconImageKey> {
|
||||||
|
|
||||||
Future<ui.Codec> _loadAsync(AppIconImageKey key, DecoderBufferCallback decode) async {
|
Future<ui.Codec> _loadAsync(AppIconImageKey key, DecoderBufferCallback decode) async {
|
||||||
try {
|
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);
|
final buffer = await ui.ImmutableBuffer.fromUint8List(bytes.isEmpty ? kTransparentImage : bytes);
|
||||||
return await decode(buffer);
|
return await decode(buffer);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -1 +1,7 @@
|
||||||
enum MoveType { copy, move, export, toBin, fromBin }
|
enum MoveType {
|
||||||
|
copy,
|
||||||
|
move,
|
||||||
|
export,
|
||||||
|
toBin,
|
||||||
|
fromBin,
|
||||||
|
}
|
||||||
|
|
96
lib/model/app/support.dart
Normal file
96
lib/model/app/support.dart
Normal file
|
@ -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<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
lib/model/apps.dart
Normal file
78
lib/model/apps.dart
Normal file
|
@ -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<Package> _packages = {};
|
||||||
|
List<String> _potentialAppDirs = [];
|
||||||
|
|
||||||
|
ValueNotifier<bool> areAppNamesReadyNotifier = ValueNotifier(false);
|
||||||
|
|
||||||
|
Iterable<Package> get _launcherPackages => _packages.where((v) => v.categoryLauncher);
|
||||||
|
|
||||||
|
AppInventory._private();
|
||||||
|
|
||||||
|
Future<void> 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<void> 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<String> 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<String> 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}';
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:aves/model/apps.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/filters/album.dart';
|
import 'package:aves/model/filters/album.dart';
|
||||||
import 'package:aves/model/filters/filters.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/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/services/common/services.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
@ -121,7 +123,7 @@ class Covers {
|
||||||
|
|
||||||
String? effectiveAlbumPackage(String albumPath) {
|
String? effectiveAlbumPackage(String albumPath) {
|
||||||
final filterPackage = of(AlbumFilter(albumPath, null))?.item2;
|
final filterPackage = of(AlbumFilter(albumPath, null))?.item2;
|
||||||
return filterPackage ?? androidFileUtils.getAlbumAppPackageName(albumPath);
|
return filterPackage ?? appInventory.getAlbumAppPackageName(albumPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// import/export
|
// import/export
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:aves/model/storage/relative_dir.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
final entryDirRepo = EntryDirRepo._private();
|
final entryDirRepo = EntryDirRepo._private();
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:aves/model/entry/cache.dart';
|
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/address.dart';
|
||||||
import 'package:aves/model/metadata/catalog.dart';
|
import 'package:aves/model/metadata/catalog.dart';
|
||||||
import 'package:aves/model/metadata/trash.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/ref/mime_types.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/theme/format.dart';
|
import 'package:aves/theme/format.dart';
|
||||||
import 'package:aves_utils/aves_utils.dart';
|
|
||||||
import 'package:aves/utils/time_utils.dart';
|
import 'package:aves/utils/time_utils.dart';
|
||||||
import 'package:aves_model/aves_model.dart';
|
import 'package:aves_model/aves_model.dart';
|
||||||
|
import 'package:aves_utils/aves_utils.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
@ -80,10 +78,6 @@ class AvesEntry with AvesEntryBase {
|
||||||
this.durationMillis = durationMillis;
|
this.durationMillis = durationMillis;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get canDecode => !MimeTypes.undecodableImages.contains(mimeType);
|
|
||||||
|
|
||||||
bool get canHaveAlpha => MimeTypes.alphaImages.contains(mimeType);
|
|
||||||
|
|
||||||
AvesEntry copyWith({
|
AvesEntry copyWith({
|
||||||
int? id,
|
int? id,
|
||||||
String? uri,
|
String? uri,
|
||||||
|
@ -225,15 +219,6 @@ class AvesEntry with AvesEntryBase {
|
||||||
return _extension;
|
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
|
// the MIME type reported by the Media Store is unreliable
|
||||||
// so we use the one found during cataloguing if possible
|
// so we use the one found during cataloguing if possible
|
||||||
String get mimeType => _catalogMetadata?.mimeType ?? sourceMimeType;
|
String get mimeType => _catalogMetadata?.mimeType ?? sourceMimeType;
|
||||||
|
@ -323,18 +308,6 @@ class AvesEntry with AvesEntryBase {
|
||||||
return _durationText!;
|
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
|
// returns whether this entry has GPS coordinates
|
||||||
// (0, 0) coordinates are considered invalid, as it is likely a default value
|
// (0, 0) coordinates are considered invalid, as it is likely a default value
|
||||||
bool get hasGps => (_catalogMetadata?.latitude ?? 0) != 0 || (_catalogMetadata?.longitude ?? 0) != 0;
|
bool get hasGps => (_catalogMetadata?.latitude ?? 0) != 0 || (_catalogMetadata?.longitude ?? 0) != 0;
|
||||||
|
|
|
@ -1,65 +1,111 @@
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:aves/model/app/support.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/settings/settings.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/mime_types.dart';
|
||||||
import 'package:aves/ref/unicode.dart';
|
import 'package:aves/ref/unicode.dart';
|
||||||
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/theme/text.dart';
|
import 'package:aves/theme/text.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
|
|
||||||
extension ExtraAvesEntryProps on AvesEntry {
|
extension ExtraAvesEntryProps on AvesEntry {
|
||||||
|
// type
|
||||||
|
|
||||||
String get mimeTypeAnySubtype => mimeType.replaceAll(RegExp('/.*'), '/*');
|
String get mimeTypeAnySubtype => mimeType.replaceAll(RegExp('/.*'), '/*');
|
||||||
|
|
||||||
|
bool get canHaveAlpha => MimeTypes.canHaveAlpha(mimeType);
|
||||||
|
|
||||||
bool get isSvg => mimeType == MimeTypes.svg;
|
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 isRaw => MimeTypes.isRaw(mimeType);
|
||||||
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 isImage => MimeTypes.isImage(mimeType);
|
bool get isImage => MimeTypes.isImage(mimeType);
|
||||||
|
|
||||||
bool get isVideo => MimeTypes.isVideo(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 isAnimated => catalogMetadata?.isAnimated ?? false;
|
||||||
|
|
||||||
bool get isGeotiff => catalogMetadata?.isGeotiff ?? false;
|
bool get isGeotiff => catalogMetadata?.isGeotiff ?? false;
|
||||||
|
|
||||||
bool get is360 => catalogMetadata?.is360 ?? 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);
|
bool get canEditDate => canEdit && (canEditExif || canEditXmp);
|
||||||
|
|
||||||
|
@ -75,44 +121,17 @@ extension ExtraAvesEntryProps on AvesEntry {
|
||||||
|
|
||||||
bool get canFlip => canEdit && canEditExif;
|
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 {
|
bool get canEditXmp => AppSupport.canEditXmp(mimeType);
|
||||||
final ws = width;
|
|
||||||
final hs = height;
|
|
||||||
return isRotated ? '$hs${AText.resolutionSeparator}$ws' : '$ws${AText.resolutionSeparator}$hs';
|
|
||||||
}
|
|
||||||
|
|
||||||
String get aspectRatioText {
|
bool get canRemoveMetadata => AppSupport.canRemoveMetadata(mimeType);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/source/collection_source.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/services/common/services.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:aves/model/covers.dart';
|
import 'package:aves/model/covers.dart';
|
||||||
import 'package:aves/model/filters/filters.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/services/common/services.dart';
|
||||||
import 'package:aves/theme/colors.dart';
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/theme/icons.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:aves/widgets/common/identity/aves_icons.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:aves/model/actions/entry_actions.dart';
|
import 'package:aves/model/actions/entry.dart';
|
||||||
import 'package:aves/model/actions/entry_set_actions.dart';
|
import 'package:aves/model/actions/entry_set.dart';
|
||||||
import 'package:aves/model/filters/recent.dart';
|
import 'package:aves/model/filters/recent.dart';
|
||||||
import 'package:aves/model/naming_pattern.dart';
|
import 'package:aves/model/naming_pattern.dart';
|
||||||
import 'package:aves/model/settings/enums/enums.dart';
|
import 'package:aves/model/settings/enums/enums.dart';
|
||||||
|
|
|
@ -3,8 +3,8 @@ import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/app_flavor.dart';
|
import 'package:aves/app_flavor.dart';
|
||||||
import 'package:aves/model/actions/entry_actions.dart';
|
import 'package:aves/model/actions/entry.dart';
|
||||||
import 'package:aves/model/actions/entry_set_actions.dart';
|
import 'package:aves/model/actions/entry_set.dart';
|
||||||
import 'package:aves/model/device.dart';
|
import 'package:aves/model/device.dart';
|
||||||
import 'package:aves/model/filters/favourite.dart';
|
import 'package:aves/model/filters/favourite.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
|
|
@ -2,6 +2,8 @@ import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/filters/album.dart';
|
import 'package:aves/model/filters/album.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_source.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/model/vaults/vaults.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
|
|
|
@ -9,3 +9,14 @@ enum EntrySortFactor { date, name, rating, size }
|
||||||
enum EntryGroupFactor { none, album, month, day }
|
enum EntryGroupFactor { none, album, month, day }
|
||||||
|
|
||||||
enum TileLayout { mosaic, grid, list }
|
enum TileLayout { mosaic, grid, list }
|
||||||
|
|
||||||
|
enum AlbumType {
|
||||||
|
regular,
|
||||||
|
vault,
|
||||||
|
app,
|
||||||
|
camera,
|
||||||
|
download,
|
||||||
|
screenRecordings,
|
||||||
|
screenshots,
|
||||||
|
videoCaptures,
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:aves/model/entry/extensions/props.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/services/common/image_op_events.dart';
|
import 'package:aves/services/common/image_op_events.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
|
|
49
lib/model/storage/relative_dir.dart
Normal file
49
lib/model/storage/relative_dir.dart
Normal file
|
@ -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<Object?> 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<String, dynamic> 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;
|
||||||
|
}
|
||||||
|
}
|
41
lib/model/storage/volume.dart
Normal file
41
lib/model/storage/volume.dart
Normal file
|
@ -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<Object?> 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'] ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,22 +86,9 @@ class MimeTypes {
|
||||||
|
|
||||||
static const Set<String> rawImages = {arw, cr2, crw, dcr, dng, dngX, erf, k25, kdc, mrw, nef, nrw, orf, pef, raf, raw, rw2, sr2, srf, srw, x3f};
|
static const Set<String> 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 bool canHaveAlpha(String mimeType) => MimeTypes.alphaImages.contains(mimeType);
|
||||||
static const Set<String> undecodableImages = {art, cdr, crw, djvu, jpeg2000, jxl, pat, pcx, pnm, psdVnd, psdX, octetStream, zip};
|
|
||||||
|
|
||||||
static const Set<String> _knownOpaqueImages = {jpeg};
|
static bool isRaw(String mimeType) => MimeTypes.rawImages.contains(mimeType);
|
||||||
|
|
||||||
static const Set<String> _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<String> knownMediaTypes = {
|
|
||||||
anyImage,
|
|
||||||
..._knownOpaqueImages,
|
|
||||||
...alphaImages,
|
|
||||||
...rawImages,
|
|
||||||
...undecodableImages,
|
|
||||||
anyVideo,
|
|
||||||
..._knownVideos,
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool isImage(String mimeType) => mimeType.startsWith('image');
|
static bool isImage(String mimeType) => mimeType.startsWith('image');
|
||||||
|
|
||||||
|
@ -147,56 +134,4 @@ class MimeTypes {
|
||||||
}
|
}
|
||||||
return null;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
|
import 'package:aves/model/apps.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/props.dart';
|
import 'package:aves/model/entry/extensions/props.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/services/common/services.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:aves/utils/math_utils.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
|
||||||
abstract class AndroidAppService {
|
abstract class AppService {
|
||||||
Future<Set<Package>> getPackages();
|
Future<Set<Package>> getPackages();
|
||||||
|
|
||||||
Future<Uint8List> getAppIcon(String packageName, double size);
|
Future<Uint8List> getAppIcon(String packageName, double size);
|
||||||
|
@ -30,7 +30,7 @@ abstract class AndroidAppService {
|
||||||
Future<void> pinToHomeScreen(String label, AvesEntry? coverEntry, {Set<CollectionFilter>? filters, String? uri});
|
Future<void> pinToHomeScreen(String label, AvesEntry? coverEntry, {Set<CollectionFilter>? filters, String? uri});
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformAndroidAppService implements AndroidAppService {
|
class PlatformAppService implements AppService {
|
||||||
static const _platform = MethodChannel('deckers.thibault/aves/app');
|
static const _platform = MethodChannel('deckers.thibault/aves/app');
|
||||||
|
|
||||||
static final _knownAppDirs = {
|
static final _knownAppDirs = {
|
|
@ -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/db/db_metadata_sqflite.dart';
|
||||||
import 'package:aves/model/settings/store/store.dart';
|
import 'package:aves/model/settings/store/store.dart';
|
||||||
import 'package:aves/model/settings/store/store_shared_pref.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/device_service.dart';
|
||||||
import 'package:aves/services/media/embedded_data_service.dart';
|
import 'package:aves/services/media/embedded_data_service.dart';
|
||||||
import 'package:aves/services/media/media_edit_service.dart';
|
import 'package:aves/services/media/media_edit_service.dart';
|
||||||
|
@ -31,7 +31,7 @@ final p.Context pContext = getIt<p.Context>();
|
||||||
final AvesAvailability availability = getIt<AvesAvailability>();
|
final AvesAvailability availability = getIt<AvesAvailability>();
|
||||||
final MetadataDb metadataDb = getIt<MetadataDb>();
|
final MetadataDb metadataDb = getIt<MetadataDb>();
|
||||||
|
|
||||||
final AndroidAppService androidAppService = getIt<AndroidAppService>();
|
final AppService appService = getIt<AppService>();
|
||||||
final DeviceService deviceService = getIt<DeviceService>();
|
final DeviceService deviceService = getIt<DeviceService>();
|
||||||
final EmbeddedDataService embeddedDataService = getIt<EmbeddedDataService>();
|
final EmbeddedDataService embeddedDataService = getIt<EmbeddedDataService>();
|
||||||
final MediaEditService mediaEditService = getIt<MediaEditService>();
|
final MediaEditService mediaEditService = getIt<MediaEditService>();
|
||||||
|
@ -51,7 +51,7 @@ void initPlatformServices() {
|
||||||
getIt.registerLazySingleton<AvesAvailability>(LiveAvesAvailability.new);
|
getIt.registerLazySingleton<AvesAvailability>(LiveAvesAvailability.new);
|
||||||
getIt.registerLazySingleton<MetadataDb>(SqfliteMetadataDb.new);
|
getIt.registerLazySingleton<MetadataDb>(SqfliteMetadataDb.new);
|
||||||
|
|
||||||
getIt.registerLazySingleton<AndroidAppService>(PlatformAndroidAppService.new);
|
getIt.registerLazySingleton<AppService>(PlatformAppService.new);
|
||||||
getIt.registerLazySingleton<DeviceService>(PlatformDeviceService.new);
|
getIt.registerLazySingleton<DeviceService>(PlatformDeviceService.new);
|
||||||
getIt.registerLazySingleton<EmbeddedDataService>(PlatformEmbeddedDataService.new);
|
getIt.registerLazySingleton<EmbeddedDataService>(PlatformEmbeddedDataService.new);
|
||||||
getIt.registerLazySingleton<MediaEditService>(PlatformMediaEditService.new);
|
getIt.registerLazySingleton<MediaEditService>(PlatformMediaEditService.new);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:aves/model/app/support.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
import 'package:aves/services/common/output_buffer.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
|
// `await` here, so that `completeError` will be caught below
|
||||||
return await completer.future;
|
return await completer.future;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
if (!MimeTypes.knownMediaTypes.contains(mimeType) && MimeTypes.isVisual(mimeType)) {
|
if (_isUnknownVisual(mimeType)) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,7 +192,7 @@ class PlatformMediaFetchService implements MediaFetchService {
|
||||||
});
|
});
|
||||||
if (result != null) return result as Uint8List;
|
if (result != null) return result as Uint8List;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
if (!MimeTypes.knownMediaTypes.contains(mimeType) && MimeTypes.isVisual(mimeType)) {
|
if (_isUnknownVisual(mimeType)) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,7 +232,7 @@ class PlatformMediaFetchService implements MediaFetchService {
|
||||||
});
|
});
|
||||||
if (result != null) return result as Uint8List;
|
if (result != null) return result as Uint8List;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
if (!MimeTypes.knownMediaTypes.contains(mimeType) && MimeTypes.isVisual(mimeType)) {
|
if (_isUnknownVisual(mimeType)) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,4 +260,47 @@ class PlatformMediaFetchService implements MediaFetchService {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<T>? resumeLoading<T>(Object taskKey) => servicePolicy.resume<T>(taskKey);
|
Future<T>? resumeLoading<T>(Object taskKey) => servicePolicy.resume<T>(taskKey);
|
||||||
|
|
||||||
|
// convenience methods
|
||||||
|
|
||||||
|
bool _isUnknownVisual(String mimeType) => !_knownMediaTypes.contains(mimeType) && MimeTypes.isVisual(mimeType);
|
||||||
|
|
||||||
|
static const Set<String> _knownOpaqueImages = {
|
||||||
|
MimeTypes.jpeg,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const Set<String> _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<String> _knownMediaTypes = {
|
||||||
|
MimeTypes.anyImage,
|
||||||
|
..._knownOpaqueImages,
|
||||||
|
...MimeTypes.alphaImages,
|
||||||
|
...MimeTypes.rawImages,
|
||||||
|
...AppSupport.undecodableImages,
|
||||||
|
MimeTypes.anyVideo,
|
||||||
|
..._knownVideos,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:aves/model/entry/entry.dart';
|
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/date_modifier.dart';
|
||||||
import 'package:aves/model/metadata/enums/enums.dart';
|
import 'package:aves/model/metadata/enums/enums.dart';
|
||||||
import 'package:aves/model/metadata/enums/metadata_type.dart';
|
import 'package:aves/model/metadata/enums/metadata_type.dart';
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import 'dart:async';
|
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/output_buffer.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:streams_channel/streams_channel.dart';
|
import 'package:streams_channel/streams_channel.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/model/vaults/vaults.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
|
|
||||||
final AndroidFileUtils androidFileUtils = AndroidFileUtils._private();
|
final AndroidFileUtils androidFileUtils = AndroidFileUtils._private();
|
||||||
|
|
||||||
class AndroidFileUtils {
|
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';
|
static const String trashDirPath = '#trash';
|
||||||
|
|
||||||
late final String separator, vaultRoot, primaryStorage;
|
late final String separator, vaultRoot, primaryStorage;
|
||||||
late final String dcimPath, downloadPath, moviesPath, picturesPath, avesVideoCapturesPath;
|
late final String dcimPath, downloadPath, moviesPath, picturesPath, avesVideoCapturesPath;
|
||||||
late final Set<String> videoCapturesPaths;
|
late final Set<String> videoCapturesPaths;
|
||||||
Set<StorageVolume> storageVolumes = {};
|
Set<StorageVolume> storageVolumes = {};
|
||||||
Set<Package> _packages = {};
|
|
||||||
List<String> _potentialAppDirs = [];
|
|
||||||
bool _initialized = false;
|
bool _initialized = false;
|
||||||
|
|
||||||
ValueNotifier<bool> areAppNamesReadyNotifier = ValueNotifier(false);
|
|
||||||
|
|
||||||
Iterable<Package> get _launcherPackages => _packages.where((package) => package.categoryLauncher);
|
|
||||||
|
|
||||||
AndroidFileUtils._private();
|
AndroidFileUtils._private();
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
|
@ -58,21 +64,6 @@ class AndroidFileUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> 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<void> 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 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');
|
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;
|
if (isVideoCapturesPath(dirPath)) return AlbumType.videoCaptures;
|
||||||
|
|
||||||
final dir = pContext.split(dirPath).last;
|
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;
|
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<String> 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<String> 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<Object?> 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<Object?> 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<String, dynamic> 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/theme/styles.dart';
|
import 'package:aves/theme/styles.dart';
|
||||||
import 'package:aves/theme/themes.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/utils/debouncer.dart';
|
||||||
import 'package:aves/widgets/collection/collection_grid.dart';
|
import 'package:aves/widgets/collection/collection_grid.dart';
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
|
@ -493,9 +493,9 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
void _monitorSettings() {
|
void _monitorSettings() {
|
||||||
void applyIsInstalledAppAccessAllowed() {
|
void applyIsInstalledAppAccessAllowed() {
|
||||||
if (settings.isInstalledAppAccessAllowed) {
|
if (settings.isInstalledAppAccessAllowed) {
|
||||||
androidFileUtils.initAppNames();
|
appInventory.initAppNames();
|
||||||
} else {
|
} else {
|
||||||
androidFileUtils.resetAppNames();
|
appInventory.resetAppNames();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:aves/app_mode.dart';
|
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/entry/entry.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/filters/query.dart';
|
import 'package:aves/model/filters/query.dart';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:aves/app_mode.dart';
|
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/actions/move_type.dart';
|
||||||
import 'package:aves/model/device.dart';
|
import 'package:aves/model/device.dart';
|
||||||
import 'package:aves/model/entry/entry.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_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/vaults/vaults.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/image_op_events.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
|
@ -264,7 +264,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
Future<void> _share(BuildContext context) async {
|
Future<void> _share(BuildContext context) async {
|
||||||
final entries = _getTargetItems(context);
|
final entries = _getTargetItems(context);
|
||||||
try {
|
try {
|
||||||
if (!await androidAppService.shareEntries(entries)) {
|
if (!await appService.shareEntries(entries)) {
|
||||||
await showNoMatchingAppDialog(context);
|
await showNoMatchingAppDialog(context);
|
||||||
}
|
}
|
||||||
} on TooManyItemsException catch (_) {
|
} on TooManyItemsException catch (_) {
|
||||||
|
@ -741,7 +741,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
final name = result.item2;
|
final name = result.item2;
|
||||||
if (name.isEmpty) return;
|
if (name.isEmpty) return;
|
||||||
|
|
||||||
await androidAppService.pinToHomeScreen(name, coverEntry, filters: filters);
|
await appService.pinToHomeScreen(name, coverEntry, filters: filters);
|
||||||
if (!device.showPinShortcutFeedback) {
|
if (!device.showPinShortcutFeedback) {
|
||||||
showFeedback(context, context.l10n.genericSuccessFeedback);
|
showFeedback(context, context.l10n.genericSuccessFeedback);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:aves/model/covers.dart';
|
import 'package:aves/model/covers.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/source/collection_source.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/model/source/section_keys.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.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/album.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.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/common/button.dart';
|
||||||
import 'package:aves/widgets/common/action_controls/quick_choosers/rate_chooser.dart';
|
import 'package:aves/widgets/common/action_controls/quick_choosers/rate_chooser.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:aves/model/actions/entry_actions.dart';
|
import 'package:aves/model/actions/entry.dart';
|
||||||
import 'package:aves/model/actions/share_actions.dart';
|
import 'package:aves/model/actions/share.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||||
import 'package:aves/widgets/common/action_controls/quick_choosers/common/button.dart';
|
import 'package:aves/widgets/common/action_controls/quick_choosers/common/button.dart';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:async';
|
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/action_controls/quick_choosers/common/menu.dart';
|
||||||
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
|
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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/filters.dart';
|
||||||
import 'package:aves/model/filters/tag.dart';
|
import 'package:aves/model/filters/tag.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:aves/model/entry/entry.dart';
|
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/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/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/actions/move_type.dart';
|
import 'package:aves/model/actions/move_type.dart';
|
||||||
import 'package:aves/model/entry/entry.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/services/common/services.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:aves/utils/collection_utils.dart';
|
import 'package:aves/utils/collection_utils.dart';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/app_mode.dart';
|
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/covers.dart';
|
||||||
import 'package:aves/model/filters/album.dart';
|
import 'package:aves/model/filters/album.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
|
|
@ -3,9 +3,9 @@ import 'package:aves/model/covers.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||||
import 'package:aves/model/entry/extensions/props.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/model/vaults/vaults.dart';
|
||||||
import 'package:aves/theme/icons.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/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/grid/theme.dart';
|
import 'package:aves/widgets/common/grid/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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/model/settings/settings.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.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/enums/l10n.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:aves/image_providers/app_icon_image_provider.dart';
|
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/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/basic/query_bar.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||||
import 'package:aves/widgets/viewer/info/common.dart';
|
import 'package:aves/widgets/viewer/info/common.dart';
|
||||||
|
@ -23,7 +23,7 @@ class _DebugAndroidAppSectionState extends State<DebugAndroidAppSection> with Au
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_loader = androidAppService.getPackages();
|
_loader = appService.getPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:aves/model/app/support.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/metadata/enums/enums.dart';
|
import 'package:aves/model/metadata/enums/enums.dart';
|
||||||
import 'package:aves/model/metadata/enums/length_unit.dart';
|
import 'package:aves/model/metadata/enums/length_unit.dart';
|
||||||
|
@ -205,7 +206,7 @@ class _ConvertEntryDialogState extends State<ConvertEntryDialog> {
|
||||||
valueListenable: _mimeTypeNotifier,
|
valueListenable: _mimeTypeNotifier,
|
||||||
builder: (context, mimeType, child) {
|
builder: (context, mimeType, child) {
|
||||||
Widget 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(
|
child = SwitchListTile(
|
||||||
value: _writeMetadata,
|
value: _writeMetadata,
|
||||||
onChanged: (v) => setState(() => _writeMetadata = v),
|
onChanged: (v) => setState(() => _writeMetadata = v),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:aves/model/storage/volume.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:aves/app_mode.dart';
|
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/actions/move_type.dart';
|
||||||
import 'package:aves/model/filters/album.dart';
|
import 'package:aves/model/filters/album.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:aves/image_providers/app_icon_image_provider.dart';
|
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/model/settings/settings.dart';
|
||||||
import 'package:aves/services/common/services.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/list_tiles/reselectable_radio.dart';
|
||||||
import 'package:aves/widgets/common/basic/query_bar.dart';
|
import 'package:aves/widgets/common/basic/query_bar.dart';
|
||||||
import 'package:aves/widgets/common/basic/scaffold.dart';
|
import 'package:aves/widgets/common/basic/scaffold.dart';
|
||||||
|
@ -34,7 +34,7 @@ class _AppPickPageState extends State<AppPickPage> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_selectedValue = widget.initialValue;
|
_selectedValue = widget.initialValue;
|
||||||
_loader = androidAppService.getPackages();
|
_loader = appService.getPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -7,6 +7,7 @@ import 'package:aves/model/source/album.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
|
import 'package:aves/model/apps.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/empty.dart';
|
import 'package:aves/widgets/common/identity/empty.dart';
|
||||||
|
@ -35,7 +36,7 @@ class AlbumListPage extends StatelessWidget {
|
||||||
},
|
},
|
||||||
builder: (context, s, child) {
|
builder: (context, s, child) {
|
||||||
return ValueListenableBuilder<bool>(
|
return ValueListenableBuilder<bool>(
|
||||||
valueListenable: androidFileUtils.areAppNamesReadyNotifier,
|
valueListenable: appInventory.areAppNamesReadyNotifier,
|
||||||
builder: (context, areAppNamesReady, child) {
|
builder: (context, areAppNamesReady, child) {
|
||||||
return StreamBuilder(
|
return StreamBuilder(
|
||||||
stream: source.eventBus.on<AlbumsChangedEvent>(),
|
stream: source.eventBus.on<AlbumsChangedEvent>(),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:aves/app_mode.dart';
|
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/actions/move_type.dart';
|
||||||
import 'package:aves/model/device.dart';
|
import 'package:aves/model/device.dart';
|
||||||
import 'package:aves/model/entry/entry.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/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/model/source/enums/view.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/details.dart';
|
||||||
import 'package:aves/model/vaults/vaults.dart';
|
import 'package:aves/model/vaults/vaults.dart';
|
||||||
import 'package:aves/services/common/image_op_events.dart';
|
import 'package:aves/services/common/image_op_events.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/services/media/enums.dart';
|
import 'package:aves/services/media/enums.dart';
|
||||||
import 'package:aves/theme/durations.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/entry_storage.dart';
|
||||||
import 'package:aves/widgets/common/action_mixins/vault_aware.dart';
|
import 'package:aves/widgets/common/action_mixins/vault_aware.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.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/album.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/highlight.dart';
|
import 'package:aves/model/highlight.dart';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:aves/app_mode.dart';
|
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/covers.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/filters/album.dart';
|
import 'package:aves/model/filters/album.dart';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:aves/app_mode.dart';
|
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/filters/filters.dart';
|
||||||
import 'package:aves/model/query.dart';
|
import 'package:aves/model/query.dart';
|
||||||
import 'package:aves/model/selection.dart';
|
import 'package:aves/model/selection.dart';
|
||||||
|
|
|
@ -13,6 +13,7 @@ import 'package:aves/model/vaults/vaults.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/theme/text.dart';
|
import 'package:aves/theme/text.dart';
|
||||||
|
import 'package:aves/model/apps.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||||
|
@ -108,7 +109,7 @@ class CoveredFilterChip<T extends CollectionFilter> extends StatelessWidget {
|
||||||
if (_filter is AlbumFilter) {
|
if (_filter is AlbumFilter) {
|
||||||
// when we asynchronously fetch installed app names,
|
// when we asynchronously fetch installed app names,
|
||||||
// album filters themselves do not change, but decoration derived from it does
|
// album filters themselves do not change, but decoration derived from it does
|
||||||
chipKey = ValueKey(androidFileUtils.areAppNamesReadyNotifier.value);
|
chipKey = ValueKey(appInventory.areAppNamesReadyNotifier.value);
|
||||||
}
|
}
|
||||||
return AvesFilterChip(
|
return AvesFilterChip(
|
||||||
key: chipKey,
|
key: chipKey,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:aves/model/source/section_keys.dart';
|
import 'package:aves/model/source/section_keys.dart';
|
||||||
|
import 'package:aves/model/storage/volume.dart';
|
||||||
import 'package:aves/theme/icons.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/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/filter_grids/common/enums.dart';
|
import 'package:aves/widgets/filter_grids/common/enums.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
|
@ -17,6 +17,7 @@ import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/services/global_search.dart';
|
import 'package:aves/services/global_search.dart';
|
||||||
import 'package:aves/services/intent_service.dart';
|
import 'package:aves/services/intent_service.dart';
|
||||||
import 'package:aves/services/widget_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/utils/android_file_utils.dart';
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
import 'package:aves/widgets/common/basic/scaffold.dart';
|
import 'package:aves/widgets/common/basic/scaffold.dart';
|
||||||
|
@ -107,7 +108,7 @@ class _HomePageState extends State<HomePage> {
|
||||||
|
|
||||||
await androidFileUtils.init();
|
await androidFileUtils.init();
|
||||||
if (!{actionScreenSaver, actionSetWallpaper}.contains(intentAction) && settings.isInstalledAppAccessAllowed) {
|
if (!{actionScreenSaver, actionSetWallpaper}.contains(intentAction) && settings.isInstalledAppAccessAllowed) {
|
||||||
unawaited(androidFileUtils.initAppNames());
|
unawaited(appInventory.initAppNames());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intentData.isNotEmpty) {
|
if (intentData.isNotEmpty) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:aves/app_mode.dart';
|
import 'package:aves/app_mode.dart';
|
||||||
import 'package:aves/model/actions/map_actions.dart';
|
import 'package:aves/model/actions/map.dart';
|
||||||
import 'package:aves/model/actions/map_cluster_actions.dart';
|
import 'package:aves/model/actions/map_cluster.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/location.dart';
|
import 'package:aves/model/entry/extensions/location.dart';
|
||||||
import 'package:aves/model/filters/coordinate.dart';
|
import 'package:aves/model/filters/coordinate.dart';
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/album.dart';
|
import 'package:aves/model/source/album.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.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/country.dart';
|
||||||
import 'package:aves/model/source/location/place.dart';
|
import 'package:aves/model/source/location/place.dart';
|
||||||
import 'package:aves/model/source/tag.dart';
|
import 'package:aves/model/source/tag.dart';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import 'package:aves/model/storage/relative_dir.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CrumbLine extends StatefulWidget {
|
class CrumbLine extends StatefulWidget {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:aves/model/settings/settings.dart';
|
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/services/common/services.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:typed_data';
|
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/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.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/model/settings/settings.dart';
|
||||||
import 'package:aves/widgets/common/basic/scaffold.dart';
|
import 'package:aves/widgets/common/basic/scaffold.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.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/model/settings/settings.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/settings/common/quick_actions/editor_page.dart';
|
import 'package:aves/widgets/settings/common/quick_actions/editor_page.dart';
|
||||||
|
|
|
@ -2,9 +2,9 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:aves/app_mode.dart';
|
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/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/device.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/favourites.dart';
|
import 'package:aves/model/entry/extensions/favourites.dart';
|
||||||
|
@ -188,7 +188,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
_addShortcut(context, targetEntry);
|
_addShortcut(context, targetEntry);
|
||||||
break;
|
break;
|
||||||
case EntryAction.copyToClipboard:
|
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);
|
showFeedback(context, success ? context.l10n.genericSuccessFeedback : context.l10n.genericFailureFeedback);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
@ -214,7 +214,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
_move(context, targetEntry, moveType: MoveType.move);
|
_move(context, targetEntry, moveType: MoveType.move);
|
||||||
break;
|
break;
|
||||||
case EntryAction.share:
|
case EntryAction.share:
|
||||||
androidAppService.shareEntries({targetEntry}).then((success) {
|
appService.shareEntries({targetEntry}).then((success) {
|
||||||
if (!success) showNoMatchingAppDialog(context);
|
if (!success) showNoMatchingAppDialog(context);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
@ -255,22 +255,22 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EntryAction.edit:
|
case EntryAction.edit:
|
||||||
androidAppService.edit(targetEntry.uri, targetEntry.mimeType).then((success) {
|
appService.edit(targetEntry.uri, targetEntry.mimeType).then((success) {
|
||||||
if (!success) showNoMatchingAppDialog(context);
|
if (!success) showNoMatchingAppDialog(context);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case EntryAction.open:
|
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);
|
if (!success) showNoMatchingAppDialog(context);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case EntryAction.openMap:
|
case EntryAction.openMap:
|
||||||
androidAppService.openMap(targetEntry.latLng!).then((success) {
|
appService.openMap(targetEntry.latLng!).then((success) {
|
||||||
if (!success) showNoMatchingAppDialog(context);
|
if (!success) showNoMatchingAppDialog(context);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case EntryAction.setAs:
|
case EntryAction.setAs:
|
||||||
androidAppService.setAs(targetEntry.uri, targetEntry.mimeType).then((success) {
|
appService.setAs(targetEntry.uri, targetEntry.mimeType).then((success) {
|
||||||
if (!success) showNoMatchingAppDialog(context);
|
if (!success) showNoMatchingAppDialog(context);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
@ -334,7 +334,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
final uri = fields['uri'] as String?;
|
final uri = fields['uri'] as String?;
|
||||||
final mimeType = fields['mimeType'] as String?;
|
final mimeType = fields['mimeType'] as String?;
|
||||||
if (uri != null && mimeType != null) {
|
if (uri != null && mimeType != null) {
|
||||||
await androidAppService.shareSingle(uri, mimeType).then((success) {
|
await appService.shareSingle(uri, mimeType).then((success) {
|
||||||
if (!success) showNoMatchingAppDialog(context);
|
if (!success) showNoMatchingAppDialog(context);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -363,7 +363,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
final name = result.item2;
|
final name = result.item2;
|
||||||
if (name.isEmpty) return;
|
if (name.isEmpty) return;
|
||||||
|
|
||||||
await androidAppService.pinToHomeScreen(name, targetEntry, uri: targetEntry.uri);
|
await appService.pinToHomeScreen(name, targetEntry, uri: targetEntry.uri);
|
||||||
if (!device.showPinShortcutFeedback) {
|
if (!device.showPinShortcutFeedback) {
|
||||||
showFeedback(context, context.l10n.genericSuccessFeedback);
|
showFeedback(context, context.l10n.genericSuccessFeedback);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:aves/app_mode.dart';
|
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/actions/events.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/info.dart';
|
import 'package:aves/model/entry/extensions/info.dart';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:async';
|
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/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/location.dart';
|
import 'package:aves/model/entry/extensions/location.dart';
|
||||||
import 'package:aves/model/entry/extensions/props.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);
|
await controller.seekTo(controller.currentPosition + 10000);
|
||||||
break;
|
break;
|
||||||
case EntryAction.openVideo:
|
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);
|
if (!success) showNoMatchingAppDialog(context);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:aves/model/actions/entry_actions.dart';
|
import 'package:aves/model/actions/entry.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
class ShowPreviousIntent extends Intent {
|
class ShowPreviousIntent extends Intent {
|
||||||
|
|
|
@ -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/actions/move_type.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.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:aves/widgets/viewer/controls/intents.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:aves/app_mode.dart';
|
import 'package:aves/app_mode.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/favourites.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/images.dart';
|
||||||
|
import 'package:aves/model/entry/extensions/location.dart';
|
||||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||||
import 'package:aves/model/entry/extensions/props.dart';
|
import 'package:aves/model/entry/extensions/props.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
|
@ -130,7 +130,6 @@ class ViewerDebugPage extends StatelessWidget {
|
||||||
'sizeBytes': '${entry.sizeBytes}',
|
'sizeBytes': '${entry.sizeBytes}',
|
||||||
'isFavourite': '${entry.isFavourite}',
|
'isFavourite': '${entry.isFavourite}',
|
||||||
'isSvg': '${entry.isSvg}',
|
'isSvg': '${entry.isSvg}',
|
||||||
'isPhoto': '${entry.isPhoto}',
|
|
||||||
'isVideo': '${entry.isVideo}',
|
'isVideo': '${entry.isVideo}',
|
||||||
'isCatalogued': '${entry.isCatalogued}',
|
'isCatalogued': '${entry.isCatalogued}',
|
||||||
'isAnimated': '${entry.isAnimated}',
|
'isAnimated': '${entry.isAnimated}',
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:math';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:aves/app_mode.dart';
|
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/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/catalog.dart';
|
import 'package:aves/model/entry/extensions/catalog.dart';
|
||||||
import 'package:aves/model/entry/extensions/location.dart';
|
import 'package:aves/model/entry/extensions/location.dart';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/app_mode.dart';
|
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/move_type.dart';
|
||||||
import 'package:aves/model/device.dart';
|
import 'package:aves/model/device.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:aves/app_mode.dart';
|
import 'package:aves/app_mode.dart';
|
||||||
import 'package:aves/image_providers/app_icon_image_provider.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/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/favourites.dart';
|
import 'package:aves/model/entry/extensions/favourites.dart';
|
||||||
import 'package:aves/model/entry/extensions/multipage.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/filters/type.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.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/services/common/services.dart';
|
||||||
import 'package:aves/theme/colors.dart';
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/theme/format.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/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/rate_button.dart';
|
||||||
import 'package:aves/widgets/common/action_controls/quick_choosers/tag_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;
|
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' : ''}';
|
String get rasterResolutionText => '${entry.resolutionText}${showMegaPixels ? ' • $megaPixels MP' : ''}';
|
||||||
|
|
||||||
|
@ -291,7 +295,7 @@ class _BasicInfoState extends State<_BasicInfo> {
|
||||||
});
|
});
|
||||||
final isViewerMode = context.read<ValueNotifier<AppMode>>().value == AppMode.view;
|
final isViewerMode = context.read<ValueNotifier<AppMode>>().value == AppMode.view;
|
||||||
if (isViewerMode && settings.isInstalledAppAccessAllowed) {
|
if (isViewerMode && settings.isInstalledAppAccessAllowed) {
|
||||||
_appNameLoader = androidFileUtils.initAppNames();
|
_appNameLoader = appInventory.initAppNames();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -349,7 +353,7 @@ class _BasicInfoState extends State<_BasicInfo> {
|
||||||
InfoValueSpanBuilder _ownerHandler(String? ownerPackage) {
|
InfoValueSpanBuilder _ownerHandler(String? ownerPackage) {
|
||||||
if (ownerPackage == null) return (context, key, value) => [];
|
if (ownerPackage == null) return (context, key, value) => [];
|
||||||
|
|
||||||
final appName = androidFileUtils.getCurrentAppName(ownerPackage) ?? ownerPackage;
|
final appName = appInventory.getCurrentAppName(ownerPackage) ?? ownerPackage;
|
||||||
return (context, key, value) => [
|
return (context, key, value) => [
|
||||||
WidgetSpan(
|
WidgetSpan(
|
||||||
alignment: PlaceholderAlignment.middle,
|
alignment: PlaceholderAlignment.middle,
|
||||||
|
|
|
@ -62,10 +62,10 @@ class EmbeddedDataOpener extends StatelessWidget with FeedbackMixin {
|
||||||
final uri = fields['uri']!;
|
final uri = fields['uri']!;
|
||||||
if (!MimeTypes.isImage(mimeType) && !MimeTypes.isVideo(mimeType)) {
|
if (!MimeTypes.isImage(mimeType) && !MimeTypes.isVideo(mimeType)) {
|
||||||
// open with another app
|
// open with another app
|
||||||
unawaited(androidAppService.open(uri, mimeType, forceChooser: true).then((success) {
|
unawaited(appService.open(uri, mimeType, forceChooser: true).then((success) {
|
||||||
if (!success) {
|
if (!success) {
|
||||||
// fallback to sharing, so that the file can be saved somewhere
|
// 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);
|
if (!success) showNoMatchingAppDialog(context);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:aves/app_mode.dart';
|
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/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/props.dart';
|
import 'package:aves/model/entry/extensions/props.dart';
|
||||||
import 'package:aves/model/selection.dart';
|
import 'package:aves/model/selection.dart';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:async';
|
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/actions/events.dart';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
import 'package:aves/model/entry/extensions/multipage.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/model/settings/settings.dart';
|
||||||
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
|
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
|
||||||
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
|
import 'package:aves/widgets/common/identity/buttons/overlay_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/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/settings/enums/enums.dart';
|
import 'package:aves/model/settings/enums/enums.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:async';
|
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/entry.dart';
|
||||||
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
|
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
|
||||||
import 'package:aves/widgets/viewer/overlay/video/controls.dart';
|
import 'package:aves/widgets/viewer/overlay/video/controls.dart';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/app_mode.dart';
|
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/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||||
import 'package:aves/model/entry/extensions/props.dart';
|
import 'package:aves/model/entry/extensions/props.dart';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:aves/app_mode.dart';
|
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/entry/entry.dart';
|
||||||
import 'package:aves/model/filters/album.dart';
|
import 'package:aves/model/filters/album.dart';
|
||||||
import 'package:aves/model/filters/mime.dart';
|
import 'package:aves/model/filters/mime.dart';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:aves/app_mode.dart';
|
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/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/props.dart';
|
import 'package:aves/model/entry/extensions/props.dart';
|
||||||
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
import 'package:aves/model/settings/enums/accessibility_animations.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/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||||
import 'package:aves/model/entry/extensions/props.dart';
|
import 'package:aves/model/entry/extensions/props.dart';
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:aves/services/android_app_service.dart';
|
import 'package:aves/model/apps.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/services/app_service.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:test/fake.dart';
|
import 'package:test/fake.dart';
|
||||||
|
|
||||||
class FakeAndroidAppService extends Fake implements AndroidAppService {
|
class FakeAppService extends Fake implements AppService {
|
||||||
@override
|
@override
|
||||||
Future<Set<Package>> getPackages() => SynchronousFuture({});
|
Future<Set<Package>> getPackages() => SynchronousFuture({});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import 'package:aves/model/storage/volume.dart';
|
||||||
import 'package:aves/services/storage_service.dart';
|
import 'package:aves/services/storage_service.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:test/fake.dart';
|
import 'package:test/fake.dart';
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import 'package:aves/model/metadata/catalog.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/media_store_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/common/services.dart';
|
||||||
import 'package:aves/services/device_service.dart';
|
import 'package:aves/services/device_service.dart';
|
||||||
import 'package:aves/services/media/media_fetch_service.dart';
|
import 'package:aves/services/media/media_fetch_service.dart';
|
||||||
|
@ -59,7 +59,7 @@ void main() {
|
||||||
getIt.registerLazySingleton<AvesAvailability>(FakeAvesAvailability.new);
|
getIt.registerLazySingleton<AvesAvailability>(FakeAvesAvailability.new);
|
||||||
getIt.registerLazySingleton<MetadataDb>(FakeMetadataDb.new);
|
getIt.registerLazySingleton<MetadataDb>(FakeMetadataDb.new);
|
||||||
|
|
||||||
getIt.registerLazySingleton<AndroidAppService>(FakeAndroidAppService.new);
|
getIt.registerLazySingleton<AppService>(FakeAppService.new);
|
||||||
getIt.registerLazySingleton<DeviceService>(FakeDeviceService.new);
|
getIt.registerLazySingleton<DeviceService>(FakeDeviceService.new);
|
||||||
getIt.registerLazySingleton<MediaFetchService>(FakeMediaFetchService.new);
|
getIt.registerLazySingleton<MediaFetchService>(FakeMediaFetchService.new);
|
||||||
getIt.registerLazySingleton<MediaStoreService>(FakeMediaStoreService.new);
|
getIt.registerLazySingleton<MediaStoreService>(FakeMediaStoreService.new);
|
||||||
|
|
Loading…
Reference in a new issue