diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9dbfbb6d8..b63d076f8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+## [v1.5.6] - 2021-11-12
+
+### Added
+
+- Viewer: action to add shortcut to media item
+
+### Changed
+
+- Albums / Countries / Tags: use a 3 column layout by default
+
+### Fixed
+
+- video playback was not using hardware-accelerated codecs on recent devices
+- partial fix to deleting/moving file in a clean way on some devices
+
## [v1.5.5] - 2021-11-08
### Added
diff --git a/README.md b/README.md
index e402b934d..e86f34117 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,25 @@
+
+
+

+
+## Aves
+
![Version badge][Version badge]
![Build badge][Build badge]
-
-

+Aves is a gallery and metadata explorer app. It is built for Android, with Flutter.
[

](https://play.google.com/store/apps/details?id=deckers.thibault.aves&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1)
+[

](https://apt.izzysoft.de/fdroid/index/apk/deckers.thibault.aves)
[

](https://github.com/deckerst/aves/releases/latest)
-Aves is a gallery and metadata explorer app. It is built for Android, with Flutter.
-
-



+
## Features
@@ -25,7 +31,9 @@ It scans your media collection to identify **motion photos**, **panoramas** (aka
Aves integrates with Android (from **API 20 to 31**, i.e. from Lollipop to S) with features such as **app shortcuts** and **global search** handling. It also works as a **media viewer and picker**.
-



+## Screenshots
+
+






## Permissions
@@ -51,7 +59,14 @@ If you want to translate this app in your language and share the result, feel fr
### Donations
-Some users have expressed the wish to financially support the project. I haven't set up any sponsorship system, but you can send contributions [here](https://paypal.me/ThibaultDeckers). Thanks! ❤️
+Some users have expressed the wish to financially support the project. Thanks! ❤️
+
+[

](https://paypal.me/ThibaultDeckers)
+[

](https://liberapay.com/deckerst/donate)
## Project Setup
diff --git a/android/app/build.gradle b/android/app/build.gradle
index e46719768..bccdc8a54 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -140,7 +140,7 @@ repositories {
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
- implementation 'androidx.core:core-ktx:1.6.0'
+ implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.exifinterface:exifinterface:1.3.3'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'com.caverock:androidsvg-aar:1.4'
@@ -152,7 +152,7 @@ dependencies {
implementation 'com.github.deckerst:pixymeta-android:0bea51ead2'
implementation 'com.github.bumptech.glide:glide:4.12.0'
- kapt 'androidx.annotation:annotation:1.2.0'
+ kapt 'androidx.annotation:annotation:1.3.0'
kapt 'com.github.bumptech.glide:compiler:4.12.0'
compileOnly rootProject.findProject(':streams_channel')
diff --git a/android/app/libs/fijkplayer-full-release.aar b/android/app/libs/fijkplayer-full-release.aar
index 53c1adfdf..10490ce31 100644
Binary files a/android/app/libs/fijkplayer-full-release.aar and b/android/app/libs/fijkplayer-full-release.aar differ
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt
index d818180d3..2d8dfe057 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt
@@ -329,7 +329,8 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
val label = call.argument
("label")
val iconBytes = call.argument("iconBytes")
val filters = call.argument>("filters")
- if (label == null || filters == null) {
+ val uri = call.argument("uri")?.let { Uri.parse(it) }
+ if (label == null || (filters == null && uri == null)) {
result.error("pin-args", "failed because of missing arguments", null)
return
}
@@ -356,12 +357,19 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
icon = IconCompat.createWithResource(context, if (supportAdaptiveIcon) R.mipmap.ic_shortcut_collection else R.drawable.ic_shortcut_collection)
}
- val intent = Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
- .putExtra("page", "/collection")
- .putExtra("filters", filters.toTypedArray())
- // on API 25, `String[]` or `ArrayList` extras are null when using the shortcut
- // so we use a joined `String` as fallback
- .putExtra("filtersString", filters.joinToString(MainActivity.EXTRA_STRING_ARRAY_SEPARATOR))
+ val intent = when {
+ uri != null -> Intent(Intent.ACTION_VIEW, uri, context, MainActivity::class.java)
+ filters != null -> Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
+ .putExtra("page", "/collection")
+ .putExtra("filters", filters.toTypedArray())
+ // on API 25, `String[]` or `ArrayList` extras are null when using the shortcut
+ // so we use a joined `String` as fallback
+ .putExtra("filtersString", filters.joinToString(MainActivity.EXTRA_STRING_ARRAY_SEPARATOR))
+ else -> {
+ result.error("pin-intent", "failed to build intent", null)
+ return
+ }
+ }
// multiple shortcuts sharing the same ID cannot be created with different labels or icons
// so we provide a unique ID for each one, and let the user manage duplicates (i.e. same filter set), if any
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt
index 29cca22e4..4aebabf55 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt
@@ -4,6 +4,8 @@ import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.graphics.BitmapFactory
+import android.media.MediaCodecInfo
+import android.media.MediaCodecList
import android.net.Uri
import android.os.Build
import android.os.Handler
@@ -47,6 +49,7 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
"safeExceptionInCoroutine" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result) { _, _ -> throw TestException() } }
"getContextDirs" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getContextDirs) }
+ "getCodecs" -> safe(call, result, ::getCodecs)
"getEnv" -> safe(call, result, ::getEnv)
"getBitmapFactoryInfo" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getBitmapFactoryInfo) }
@@ -83,6 +86,40 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
result.success(dirs)
}
+ private fun getCodecs(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
+ val codecs = ArrayList()
+
+ fun getFields(info: MediaCodecInfo): FieldMap {
+ val fields: FieldMap = hashMapOf(
+ "name" to info.name,
+ "isEncoder" to info.isEncoder,
+ "supportedTypes" to info.supportedTypes.joinToString(", "),
+ )
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ if (info.canonicalName != info.name) fields["canonicalName"] = info.canonicalName
+ if (info.isAlias) fields["isAlias"] to info.isAlias
+ if (info.isHardwareAccelerated) fields["isHardwareAccelerated"] to info.isHardwareAccelerated
+ if (info.isSoftwareOnly) fields["isSoftwareOnly"] to info.isSoftwareOnly
+ if (info.isVendor) fields["isVendor"] to info.isVendor
+ }
+ return fields
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ codecs.addAll(MediaCodecList(MediaCodecList.REGULAR_CODECS).codecInfos.map(::getFields))
+ } else {
+ @Suppress("deprecation")
+ val count = MediaCodecList.getCodecCount()
+ for (i in 0 until count) {
+ @Suppress("deprecation")
+ val info = MediaCodecList.getCodecInfoAt(i)
+ codecs.add(getFields(info))
+ }
+ }
+
+ result.success(codecs)
+ }
+
private fun getEnv(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
result.success(System.getenv())
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt
index 2d3e62f9e..aa7d090e9 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt
@@ -225,18 +225,53 @@ class MediaStoreImageProvider : ImageProvider() {
return found
}
+ private fun hasEntry(context: Context, contentUri: Uri): Boolean {
+ var found = false
+ val projection = arrayOf(MediaStore.MediaColumns._ID)
+ try {
+ val cursor = context.contentResolver.query(contentUri, projection, null, null, null)
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ found = true
+ }
+ cursor.close()
+ }
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "failed to get entry at contentUri=$contentUri", e)
+ }
+ return found
+ }
+
private fun needSize(mimeType: String) = MimeTypes.SVG != mimeType
// `uri` is a media URI, not a document URI
override suspend fun delete(activity: Activity, uri: Uri, path: String?, mimeType: String) {
path ?: throw Exception("failed to delete file because path is null")
+ // the following situations are possible:
+ // - there is an entry in the Media Store and there is a file on storage
+ // - there is an entry in the Media Store but there is no longer a file on storage
+ // - there is no entry in the Media Store but there is a file on storage
val file = File(path)
- if (file.exists()) {
+ val fileExists = file.exists()
+
+ if (fileExists) {
if (StorageUtils.canEditByFile(activity, path)) {
- Log.d(LOG_TAG, "delete file at uri=$uri path=$path")
- if (file.delete()) {
- scanObsoletePath(activity, path, mimeType)
+ if (hasEntry(activity, uri)) {
+ Log.d(LOG_TAG, "delete content at uri=$uri path=$path")
+ activity.contentResolver.delete(uri, null, null)
+ }
+ // in theory, deleting via content resolver should remove the file on storage
+ // in practice, the file may still be there afterwards
+ if (file.exists()) {
+ Log.d(LOG_TAG, "delete file at uri=$uri path=$path")
+ if (file.delete()) {
+ // in theory, scanning an obsolete path should remove the entry from the Media Store
+ // in practice, the entry may still be there afterwards
+ scanObsoletePath(activity, path, mimeType)
+ return
+ }
+ } else {
return
}
} else if (!isMediaUriPermissionGranted(activity, uri, mimeType)
@@ -245,7 +280,7 @@ class MediaStoreImageProvider : ImageProvider() {
// if the file is on SD card, calling the content resolver `delete()`
// removes the entry from the Media Store but it doesn't delete the file,
// even when the app has the permission, so we manually delete the document file
- Log.d(LOG_TAG, "delete document at uri=$uri path=$path")
+ Log.d(LOG_TAG, "delete document (fileExists=$fileExists) at uri=$uri path=$path")
val df = StorageUtils.getDocumentFile(activity, path, uri)
@Suppress("BlockingMethodInNonBlockingContext")
@@ -258,9 +293,12 @@ class MediaStoreImageProvider : ImageProvider() {
}
try {
- Log.d(LOG_TAG, "delete content at uri=$uri path=$path")
+ Log.d(LOG_TAG, "delete content (fileExists=$fileExists) at uri=$uri path=$path")
if (activity.contentResolver.delete(uri, null, null) > 0) return
- throw Exception("failed to delete row from content provider")
+
+ if (hasEntry(activity, uri) || file.exists()) {
+ throw Exception("failed to delete row from content provider")
+ }
} catch (securityException: SecurityException) {
// even if the app has access permission granted on the containing directory,
// the delete request may yield a `RecoverableSecurityException` on Android 10+
diff --git a/android/build.gradle b/android/build.gradle
index 7d6fe6924..a66954e85 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -10,7 +10,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// GMS & Firebase Crashlytics are not actually used by all flavors
classpath 'com.google.gms:google-services:4.3.10'
- classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1'
+ classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.0'
}
}
diff --git a/fastlane/metadata/android/de/full_description.txt b/fastlane/metadata/android/de/full_description.txt
new file mode 100644
index 000000000..15d681520
--- /dev/null
+++ b/fastlane/metadata/android/de/full_description.txt
@@ -0,0 +1,5 @@
+Aves kann alle Arten von Bildern und Videos verarbeiten, einschließlich Ihrer typischen JPEGs und MP4s, aber auch exotischere Dinge wie mehrseitige TIFFs, SVGs, alte AVIs und mehr! Es scannt Ihre Mediensammlung, um Bewegungsfotos, Panoramen (auch bekannt als Panoramaaufnahmen), 360°-Videos sowie GeoTIFF-Dateien zu identifizieren.
+
+Navigation und Suche ist ein wichtiger Bestandteil von Aves. Das Ziel besteht darin, dass Benutzer problemlos von Alben zu Fotos zu Tags zu Karten usw. wechseln können.
+
+Aves lässt sich mit Android (von API 20 bis 31, d. h. von Lollipop bis S) mit Funktionen wie App-Verknüpfungen und globaler Suche integrieren. Es funktioniert auch als Medienbetrachter und -auswahl.
\ No newline at end of file
diff --git a/fastlane/metadata/android/de/short_description.txt b/fastlane/metadata/android/de/short_description.txt
new file mode 100644
index 000000000..9f8c85a29
--- /dev/null
+++ b/fastlane/metadata/android/de/short_description.txt
@@ -0,0 +1 @@
+Galerie und Metadata Explorer
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1060.txt b/fastlane/metadata/android/en-US/changelogs/1060.txt
new file mode 100644
index 000000000..257fd5ad9
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1060.txt
@@ -0,0 +1,10 @@
+In v1.5.6:
+- fixed video playback ignoring hardware-accelerated codecs on recent devices
+- partially fixed deleted files leaving ghost items on some devices
+- you can now create shortcuts to a specific media item, not only collections
+In v1.5.5:
+- modify items in bulk (rotation, date, metadata removal)
+- filter items by title
+- enjoy the app in Russian
+Note: the video thumbnails are modified. Clearing the app cache may be necessary.
+Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt
new file mode 100644
index 000000000..c7ccffdc9
--- /dev/null
+++ b/fastlane/metadata/android/en-US/full_description.txt
@@ -0,0 +1,5 @@
+Aves can handle all sorts of images and videos, including your typical JPEGs and MP4s, but also more exotic things like multi-page TIFFs, SVGs, old AVIs and more! It scans your media collection to identify motion photos, panoramas (aka photo spheres), 360° videos, as well as GeoTIFF files.
+
+Navigation and search is an important part of Aves. The goal is for users to easily flow from albums to photos to tags to maps, etc.
+
+Aves integrates with Android (from API 20 to 31, i.e. from Lollipop to S) with features such as app shortcuts and global search handling. It also works as a media viewer and picker.
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/images/featureGraphic.png b/fastlane/metadata/android/en-US/images/featureGraphic.png
new file mode 100644
index 000000000..c3eeb06dc
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/featureGraphic.png differ
diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png
new file mode 100644
index 000000000..73c4a32c9
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/icon.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
new file mode 100644
index 000000000..1b9e0262c
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png
new file mode 100644
index 000000000..b2e45ff00
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png
new file mode 100644
index 000000000..f8e7cee24
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
new file mode 100644
index 000000000..dc95c05ab
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png
new file mode 100644
index 000000000..64a8a9588
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png
new file mode 100644
index 000000000..336e6e21d
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt
new file mode 100644
index 000000000..8c9445bd5
--- /dev/null
+++ b/fastlane/metadata/android/en-US/short_description.txt
@@ -0,0 +1 @@
+Gallery and metadata explorer
\ No newline at end of file
diff --git a/lib/model/actions/entry_actions.dart b/lib/model/actions/entry_actions.dart
index 5d4d580e6..4a8c98248 100644
--- a/lib/model/actions/entry_actions.dart
+++ b/lib/model/actions/entry_actions.dart
@@ -4,6 +4,8 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/widgets.dart';
enum EntryAction {
+ addShortcut,
+ copyToClipboard,
delete,
export,
info,
@@ -17,10 +19,7 @@ enum EntryAction {
flip,
// vector
viewSource,
- // motion photo,
- viewMotionPhotoVideo,
// external
- copyToClipboard,
edit,
open,
openMap,
@@ -39,10 +38,10 @@ class EntryActions {
EntryAction.delete,
EntryAction.rename,
EntryAction.export,
+ EntryAction.addShortcut,
EntryAction.copyToClipboard,
EntryAction.print,
EntryAction.viewSource,
- EntryAction.viewMotionPhotoVideo,
EntryAction.rotateScreen,
];
@@ -63,9 +62,8 @@ class EntryActions {
extension ExtraEntryAction on EntryAction {
String getText(BuildContext context) {
switch (this) {
- case EntryAction.toggleFavourite:
- // different data depending on toggle state
- return context.l10n.entryActionAddFavourite;
+ case EntryAction.addShortcut:
+ return context.l10n.collectionActionAddShortcut;
case EntryAction.copyToClipboard:
return context.l10n.entryActionCopyToClipboard;
case EntryAction.delete:
@@ -74,12 +72,15 @@ extension ExtraEntryAction on EntryAction {
return context.l10n.entryActionExport;
case EntryAction.info:
return context.l10n.entryActionInfo;
- case EntryAction.rename:
- return context.l10n.entryActionRename;
case EntryAction.print:
return context.l10n.entryActionPrint;
+ case EntryAction.rename:
+ return context.l10n.entryActionRename;
case EntryAction.share:
return context.l10n.entryActionShare;
+ case EntryAction.toggleFavourite:
+ // different data depending on toggle state
+ return context.l10n.entryActionAddFavourite;
// raster
case EntryAction.rotateCCW:
return context.l10n.entryActionRotateCCW;
@@ -90,18 +91,15 @@ extension ExtraEntryAction on EntryAction {
// vector
case EntryAction.viewSource:
return context.l10n.entryActionViewSource;
- // motion photo
- case EntryAction.viewMotionPhotoVideo:
- return context.l10n.entryActionViewMotionPhotoVideo;
// external
case EntryAction.edit:
return context.l10n.entryActionEdit;
case EntryAction.open:
return context.l10n.entryActionOpen;
- case EntryAction.setAs:
- return context.l10n.entryActionSetAs;
case EntryAction.openMap:
return context.l10n.entryActionOpenMap;
+ case EntryAction.setAs:
+ return context.l10n.entryActionSetAs;
// platform
case EntryAction.rotateScreen:
return context.l10n.entryActionRotateScreen;
@@ -129,9 +127,8 @@ extension ExtraEntryAction on EntryAction {
IconData? getIconData() {
switch (this) {
- case EntryAction.toggleFavourite:
- // different data depending on toggle state
- return AIcons.favourite;
+ case EntryAction.addShortcut:
+ return AIcons.addShortcut;
case EntryAction.copyToClipboard:
return AIcons.clipboard;
case EntryAction.delete:
@@ -140,12 +137,15 @@ extension ExtraEntryAction on EntryAction {
return AIcons.saveAs;
case EntryAction.info:
return AIcons.info;
- case EntryAction.rename:
- return AIcons.rename;
case EntryAction.print:
return AIcons.print;
+ case EntryAction.rename:
+ return AIcons.rename;
case EntryAction.share:
return AIcons.share;
+ case EntryAction.toggleFavourite:
+ // different data depending on toggle state
+ return AIcons.favourite;
// raster
case EntryAction.rotateCCW:
return AIcons.rotateLeft;
@@ -156,14 +156,11 @@ extension ExtraEntryAction on EntryAction {
// vector
case EntryAction.viewSource:
return AIcons.vector;
- // motion photo
- case EntryAction.viewMotionPhotoVideo:
- return AIcons.motionPhoto;
// external
case EntryAction.edit:
case EntryAction.open:
- case EntryAction.setAs:
case EntryAction.openMap:
+ case EntryAction.setAs:
return null;
// platform
case EntryAction.rotateScreen:
diff --git a/lib/model/actions/entry_info_actions.dart b/lib/model/actions/entry_info_actions.dart
index 52ddd4394..f0c6c7805 100644
--- a/lib/model/actions/entry_info_actions.dart
+++ b/lib/model/actions/entry_info_actions.dart
@@ -1,4 +1,51 @@
+import 'package:aves/theme/icons.dart';
+import 'package:aves/widgets/common/extensions/build_context.dart';
+import 'package:flutter/widgets.dart';
+
enum EntryInfoAction {
+ // general
editDate,
removeMetadata,
+ // motion photo
+ viewMotionPhotoVideo,
+}
+
+class EntryInfoActions {
+ static const all = [
+ EntryInfoAction.editDate,
+ EntryInfoAction.removeMetadata,
+ EntryInfoAction.viewMotionPhotoVideo,
+ ];
+}
+
+extension ExtraEntryInfoAction on EntryInfoAction {
+ String getText(BuildContext context) {
+ switch (this) {
+ // general
+ case EntryInfoAction.editDate:
+ return context.l10n.entryInfoActionEditDate;
+ case EntryInfoAction.removeMetadata:
+ return context.l10n.entryInfoActionRemoveMetadata;
+ // motion photo
+ case EntryInfoAction.viewMotionPhotoVideo:
+ return context.l10n.entryActionViewMotionPhotoVideo;
+ }
+ }
+
+ Widget getIcon() {
+ return Icon(_getIconData());
+ }
+
+ IconData _getIconData() {
+ switch (this) {
+ // general
+ case EntryInfoAction.editDate:
+ return AIcons.date;
+ case EntryInfoAction.removeMetadata:
+ return AIcons.clear;
+ // motion photo
+ case EntryInfoAction.viewMotionPhotoVideo:
+ return AIcons.motionPhoto;
+ }
+ }
}
diff --git a/lib/services/android_app_service.dart b/lib/services/android_app_service.dart
index ef7defa72..a4685b1de 100644
--- a/lib/services/android_app_service.dart
+++ b/lib/services/android_app_service.dart
@@ -31,7 +31,7 @@ abstract class AndroidAppService {
Future canPinToHomeScreen();
- Future pinToHomeScreen(String label, AvesEntry? entry, Set filters);
+ Future pinToHomeScreen(String label, AvesEntry? coverEntry, {Set? filters, String? uri});
}
class PlatformAndroidAppService implements AndroidAppService {
@@ -194,17 +194,17 @@ class PlatformAndroidAppService implements AndroidAppService {
}
@override
- Future pinToHomeScreen(String label, AvesEntry? entry, Set filters) async {
+ Future pinToHomeScreen(String label, AvesEntry? coverEntry, {Set? filters, String? uri}) async {
Uint8List? iconBytes;
- if (entry != null) {
- final size = entry.isVideo ? 0.0 : 256.0;
+ if (coverEntry != null) {
+ final size = coverEntry.isVideo ? 0.0 : 256.0;
iconBytes = await mediaFileService.getThumbnail(
- uri: entry.uri,
- mimeType: entry.mimeType,
- pageId: entry.pageId,
- rotationDegrees: entry.rotationDegrees,
- isFlipped: entry.isFlipped,
- dateModifiedSecs: entry.dateModifiedSecs,
+ uri: coverEntry.uri,
+ mimeType: coverEntry.mimeType,
+ pageId: coverEntry.pageId,
+ rotationDegrees: coverEntry.rotationDegrees,
+ isFlipped: coverEntry.isFlipped,
+ dateModifiedSecs: coverEntry.dateModifiedSecs,
extent: size,
);
}
@@ -212,7 +212,8 @@ class PlatformAndroidAppService implements AndroidAppService {
await platform.invokeMethod('pin', {
'label': label,
'iconBytes': iconBytes,
- 'filters': filters.map((filter) => filter.toJson()).toList(),
+ 'filters': filters?.map((filter) => filter.toJson()).toList(),
+ 'uri': uri,
});
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
diff --git a/lib/services/android_debug_service.dart b/lib/services/android_debug_service.dart
index 15f63b70d..ee522c45c 100644
--- a/lib/services/android_debug_service.dart
+++ b/lib/services/android_debug_service.dart
@@ -56,6 +56,16 @@ class AndroidDebugService {
return {};
}
+ static Future> getCodecs() async {
+ try {
+ final result = await platform.invokeMethod('getCodecs');
+ if (result != null) return (result as List).cast