diff --git a/.flutter b/.flutter
index d8a9f9a52..35c388afb 160000
--- a/.flutter
+++ b/.flutter
@@ -1 +1 @@
-Subproject commit d8a9f9a52e5af486f80d932e838ee93861ffd863
+Subproject commit 35c388afb57ef061d06a39b537336c87e0e3d1b1
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index 1b495dbce..4a15a1dfa 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
+ uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
diff --git a/.github/workflows/quality-check.yml b/.github/workflows/quality-check.yml
index 2e1ecb506..ed26b89ed 100644
--- a/.github/workflows/quality-check.yml
+++ b/.github/workflows/quality-check.yml
@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
+ uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
@@ -52,7 +52,7 @@ jobs:
build-mode: manual
steps:
- name: Harden Runner
- uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
+ uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
@@ -69,7 +69,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
+ uses: github/codeql-action/init@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -83,6 +83,6 @@ jobs:
./flutterw build apk --profile -t lib/main_play.dart --flavor play
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
+ uses: github/codeql-action/analyze@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
with:
category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 00fd61de1..f824629c1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -18,7 +18,7 @@ jobs:
id-token: write
steps:
- name: Harden Runner
- uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
+ uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
@@ -75,19 +75,19 @@ jobs:
AVES_GOOGLE_API_KEY: ${{ secrets.AVES_GOOGLE_API_KEY }}
- name: Generate artifact attestation
- uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0
+ uses: actions/attest-build-provenance@bd77c077858b8d561b7a36cbe48ef4cc642ca39d # v2.2.2
with:
subject-path: 'outputs/*'
- name: Create GitHub release
- uses: ncipollo/release-action@cdcc88a9acf3ca41c16c37bb7d21b9ad48560d87 # v1.15.0
+ uses: ncipollo/release-action@440c8c1cb0ed28b9f43e4d1d670870f059653174 # v1.16.0
with:
artifacts: "outputs/*"
body: "[Changelog](https://github.com/${{ github.repository }}/blob/develop/CHANGELOG.md#${{ github.ref_name }})"
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload app bundle
- uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
+ uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with:
name: appbundle
path: outputs/app-play-release.aab
@@ -98,7 +98,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
+ uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
@@ -106,7 +106,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Get appbundle from artifacts
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
+ uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
with:
name: appbundle
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index de1568951..0220f8dd5 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -31,7 +31,7 @@ jobs:
steps:
- name: Harden Runner
- uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
+ uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
@@ -41,7 +41,7 @@ jobs:
persist-credentials: false
- name: "Run analysis"
- uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
+ uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
with:
results_file: results.sarif
results_format: sarif
@@ -63,7 +63,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
- uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
+ uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with:
name: SARIF file
path: results.sarif
@@ -71,6 +71,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
- uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
+ uses: github/codeql-action/upload-sarif@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
with:
sarif_file: results.sarif
diff --git a/.gitignore b/.gitignore
index 695e072ff..96fc9851f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,9 +5,11 @@
*.swp
.DS_Store
.atom/
+.build/
.buildlog/
.history
.svn/
+.swiftpm/
migrate_working_dir/
# IntelliJ related
@@ -27,7 +29,6 @@ migrate_working_dir/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
-.packages
.pub-cache/
.pub/
/build/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fcdb05caf..f84abeae5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,28 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+## [v1.12.4] - 2025-03-05
+
+### Added
+
+- support for Samsung HEIC motion photos embedding video in sefd box
+- Cataloguing: identify video location from Apple QuickTime metadata, and 3GPP `loci` atom
+- Collection: stack RAW and HEIC with same file names
+- display home tile in side drawer when customized
+- Galician translation (thanks Rubén Castiñeiras Lorenzo)
+
+### Changed
+
+- increased precision of file modified date to milliseconds
+- upgraded Flutter to stable v3.29.0
+
+### Fixed
+
+- opening motion photo embedded video when video track is not the first one
+- some SVG rendering issues
+- decoding of SVG containing references to namespaces in !ATTLIST
+- fallback decoding of images packed in RGBA_1010102 config
+
## [v1.12.3] - 2025-02-06
### Added
diff --git a/README.md b/README.md
index f4dbe040d..b571f651b 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ It scans your media collection to identify **motion photos**, **panoramas** (aka
**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 KitKat to Android 14, including Android TV) with features such as **widgets**, **app shortcuts**, **screen saver** and **global search** handling. It also works as a **media viewer and picker**.
+Aves integrates with Android (including Android TV) with features such as **widgets**, **app shortcuts**, **screen saver** and **global search** handling. It also works as a **media viewer and picker**.
## Screenshots
diff --git a/android/.gitignore b/android/.gitignore
index 6f568019d..be3943c96 100644
--- a/android/.gitignore
+++ b/android/.gitignore
@@ -5,9 +5,10 @@ gradle-wrapper.jar
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
+.cxx/
# Remember to never publicly share your keystore.
-# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks
diff --git a/android/app/build.gradle b/android/app/build.gradle
index e4c756d3b..42711a403 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -35,8 +35,6 @@ kotlin {
android {
namespace 'deckers.thibault.aves'
compileSdk 35
- // cf https://developer.android.com/studio/projects/install-ndk#default-ndk-per-agp
- ndkVersion '28.0.12916984'
defaultConfig {
applicationId packageName
@@ -161,7 +159,6 @@ dependencies {
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
implementation 'androidx.work:work-runtime-ktx:2.10.0'
- implementation 'com.caverock:androidsvg-aar:1.4'
implementation 'com.commonsware.cwac:document:0.5.0'
implementation 'com.drewnoakes:metadata-extractor:2.19.0'
implementation "com.github.bumptech.glide:glide:$glide_version"
@@ -171,9 +168,11 @@ dependencies {
// forked, built by JitPack:
// - https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
+ // - https://jitpack.io/p/deckerst/androidsvg
// - https://jitpack.io/p/deckerst/mp4parser
// - https://jitpack.io/p/deckerst/pixymeta-android
implementation 'com.github.deckerst:Android-TiffBitmapFactory:3ed067f021'
+ implementation 'com.github.deckerst:androidsvg:cc9d59a88f'
implementation 'com.github.deckerst.mp4parser:isoparser:d5caf7a3dd'
implementation 'com.github.deckerst.mp4parser:muxer:d5caf7a3dd'
implementation 'com.github.deckerst:pixymeta-android:71eee77dc4'
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index c63fb87bf..6f60db9f2 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -330,8 +330,8 @@
android:name="flutterEmbedding"
android:value="2" />
ioScope.launch { safeSuspend(call, result, ::getExifThumbnails) }
+ "getExifThumbnails" -> ioScope.launch { safe(call, result, ::getExifThumbnails) }
"extractGoogleDeviceItem" -> ioScope.launch { safe(call, result, ::extractGoogleDeviceItem) }
"extractJpegMpfItem" -> ioScope.launch { safe(call, result, ::extractJpegMpfItem) }
"extractMotionPhotoImage" -> ioScope.launch { safe(call, result, ::extractMotionPhotoImage) }
@@ -58,7 +56,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
}
}
- private suspend fun getExifThumbnails(call: MethodCall, result: MethodChannel.Result) {
+ private fun getExifThumbnails(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument("mimeType")
val uri = call.argument("uri")?.toUri()
val sizeBytes = call.argument("sizeBytes")?.toLong()
@@ -75,7 +73,9 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
exif.thumbnailBitmap?.let { bitmap ->
TransformationUtils.rotateImageExif(BitmapUtils.getBitmapPool(context), bitmap, orientation)?.let {
- it.getBytes(canHaveAlpha = false, recycle = false)?.let { bytes -> thumbnails.add(bytes) }
+ // do not recycle bitmaps fetched from `ExifInterface` as their lifecycle is unknown
+ val recycle = false
+ BitmapUtils.getRawBytes(it, recycle = recycle)?.let { bytes -> thumbnails.add(bytes) }
}
}
}
@@ -186,7 +186,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
return
}
- MultiPage.getMotionPhotoVideoSize(context, uri, mimeType, sizeBytes)?.let { videoSizeBytes ->
+ MultiPage.getTrailerVideoSize(context, uri, mimeType, sizeBytes)?.let { videoSizeBytes ->
val imageSizeBytes = sizeBytes - videoSizeBytes
StorageUtils.openInputStream(context, uri)?.let { input ->
copyEmbeddedBytes(result, mimeType, displayName, input, imageSizeBytes)
@@ -207,11 +207,10 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
return
}
- MultiPage.getMotionPhotoVideoSize(context, uri, mimeType, sizeBytes)?.let { videoSizeBytes ->
- val videoStartOffset = sizeBytes - videoSizeBytes
+ MultiPage.getMotionPhotoVideoSizing(context, uri, mimeType, sizeBytes)?.let { (videoOffset, videoSize) ->
StorageUtils.openInputStream(context, uri)?.let { input ->
- input.skip(videoStartOffset)
- copyEmbeddedBytes(result, MimeTypes.MP4, displayName, input, videoSizeBytes)
+ input.skip(videoOffset)
+ copyEmbeddedBytes(result, MimeTypes.MP4, displayName, input, videoSize)
}
return
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchBytesHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchBytesHandler.kt
index 70daf5c04..9286a80d0 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchBytesHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchBytesHandler.kt
@@ -3,11 +3,12 @@ package deckers.thibault.aves.channel.calls
import android.content.Context
import android.graphics.Rect
import androidx.core.net.toUri
-import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
+import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.channel.calls.fetchers.RegionFetcher
import deckers.thibault.aves.channel.calls.fetchers.SvgRegionFetcher
import deckers.thibault.aves.channel.calls.fetchers.ThumbnailFetcher
import deckers.thibault.aves.channel.calls.fetchers.TiffRegionFetcher
+import deckers.thibault.aves.model.EntryFields
import deckers.thibault.aves.utils.MimeTypes
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
@@ -27,18 +28,18 @@ class MediaFetchBytesHandler(private val context: Context) : MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
- "getThumbnail" -> ioScope.launch { safeSuspend(call, result, ::getThumbnail) }
- "getRegion" -> ioScope.launch { safeSuspend(call, result, ::getRegion) }
+ "getThumbnail" -> ioScope.launch { safe(call, result, ::getThumbnail) }
+ "getRegion" -> ioScope.launch { safe(call, result, ::getRegion) }
else -> result.notImplemented()
}
}
- private suspend fun getThumbnail(call: MethodCall, result: MethodChannel.Result) {
- val uri = call.argument("uri")
- val mimeType = call.argument("mimeType")
- val dateModifiedSecs = call.argument("dateModifiedSecs")?.toLong()
- val rotationDegrees = call.argument("rotationDegrees")
- val isFlipped = call.argument("isFlipped")
+ private fun getThumbnail(call: MethodCall, result: MethodChannel.Result) {
+ val uri = call.argument(EntryFields.URI)
+ val mimeType = call.argument(EntryFields.MIME_TYPE)
+ val dateModifiedMillis = call.argument(EntryFields.DATE_MODIFIED_MILLIS)?.toLong()
+ val rotationDegrees = call.argument(EntryFields.ROTATION_DEGREES)
+ val isFlipped = call.argument(EntryFields.IS_FLIPPED)
val widthDip = call.argument("widthDip")?.toDouble()
val heightDip = call.argument("heightDip")?.toDouble()
val pageId = call.argument("pageId")
@@ -55,7 +56,7 @@ class MediaFetchBytesHandler(private val context: Context) : MethodCallHandler {
context = context,
uri = uri,
mimeType = mimeType,
- dateModifiedSecs = dateModifiedSecs ?: (Date().time / 1000),
+ dateModifiedMillis = dateModifiedMillis ?: (Date().time),
rotationDegrees = rotationDegrees,
isFlipped = isFlipped,
width = (widthDip * density).roundToInt(),
@@ -67,7 +68,7 @@ class MediaFetchBytesHandler(private val context: Context) : MethodCallHandler {
).fetch()
}
- private suspend fun getRegion(call: MethodCall, result: MethodChannel.Result) {
+ private fun getRegion(call: MethodCall, result: MethodChannel.Result) {
val uri = call.argument("uri")?.toUri()
val mimeType = call.argument("mimeType")
val pageId = call.argument("pageId")
@@ -96,6 +97,7 @@ class MediaFetchBytesHandler(private val context: Context) : MethodCallHandler {
imageHeight = imageHeight,
result = result,
)
+
MimeTypes.TIFF -> TiffRegionFetcher(context).fetch(
uri = uri,
page = pageId ?: 0,
@@ -103,6 +105,7 @@ class MediaFetchBytesHandler(private val context: Context) : MethodCallHandler {
regionRect = regionRect,
result = result,
)
+
else -> regionFetcher.fetch(
uri = uri,
mimeType = mimeType,
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt
index 27aed4bab..6997406c9 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt
@@ -6,6 +6,7 @@ import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
import android.util.Log
+import androidx.core.net.toUri
import com.adobe.internal.xmp.XMPException
import com.adobe.internal.xmp.XMPMeta
import com.adobe.internal.xmp.XMPMetaFactory
@@ -22,6 +23,7 @@ import com.drew.metadata.exif.GpsDirectory
import com.drew.metadata.file.FileTypeDirectory
import com.drew.metadata.gif.GifAnimationDirectory
import com.drew.metadata.iptc.IptcDirectory
+import com.drew.metadata.mov.metadata.QuickTimeMetadataDirectory
import com.drew.metadata.mp4.media.Mp4UuidBoxDirectory
import com.drew.metadata.png.PngDirectory
import com.drew.metadata.webp.WebpDirectory
@@ -100,6 +102,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.json.JSONObject
+import org.mp4parser.boxes.threegpp.ts26244.LocationInformationBox
+import org.mp4parser.tools.Path
import java.nio.charset.StandardCharsets
import java.text.DecimalFormat
import java.text.ParseException
@@ -107,7 +111,6 @@ import java.util.Locale
import kotlin.math.roundToInt
import kotlin.math.roundToLong
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
-import androidx.core.net.toUri
class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
@@ -449,9 +452,8 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (isVideo(mimeType)) {
// `metadata-extractor` do not extract custom tags in user data box
- val userDataDir = Mp4ParserHelper.getUserData(context, mimeType, uri)
- if (userDataDir.isNotEmpty()) {
- metadataMap[Metadata.DIR_MP4_USER_DATA] = userDataDir
+ Mp4ParserHelper.getUserDataBox(context, mimeType, uri)?.let { box ->
+ metadataMap[Metadata.DIR_MP4_USER_DATA] = Mp4ParserHelper.extractBoxFields(box)
}
// this is used as fallback when the video metadata cannot be found on the Dart side
@@ -470,6 +472,12 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
// Android's `MediaExtractor` and `MediaPlayer` cannot be used for details
// about embedded images as they do not list them as separate tracks
// and only identify at most one
+ } else if (isHeic(mimeType)) {
+ Mp4ParserHelper.getSamsungSefd(context, uri)?.let { (_, bytes) ->
+ metadataMap[Mp4ParserHelper.SAMSUNG_MAKERNOTE_BOX_TYPE] = hashMapOf(
+ "Size" to bytes.size.toString(),
+ )
+ }
}
if (metadataMap.isNotEmpty()) {
@@ -527,8 +535,29 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
val metadataMap = HashMap()
getCatalogMetadataByMetadataExtractor(mimeType, uri, path, sizeBytes, metadataMap)
+
if (isVideo(mimeType) || isHeic(mimeType)) {
getMultimediaCatalogMetadataByMediaMetadataRetriever(mimeType, uri, metadataMap)
+
+ // fallback to MP4 `loci` box for location
+ if (!metadataMap.contains(KEY_LATITUDE) || !metadataMap.contains(KEY_LONGITUDE)) {
+ Mp4ParserHelper.getUserDataBox(context, mimeType, uri)?.let { userDataBox ->
+ Path.getPath(userDataBox, LocationInformationBox.TYPE)?.let { locationBox ->
+ if (!locationBox.isParsed) {
+ locationBox.parseDetails()
+ }
+ metadataMap[KEY_LATITUDE] = locationBox.latitude
+ metadataMap[KEY_LONGITUDE] = locationBox.longitude
+ }
+ }
+ }
+ }
+
+ if (isHeic(mimeType)) {
+ val flags = (metadataMap[KEY_FLAGS] ?: 0) as Int
+ if ((flags and MASK_IS_MOTION_PHOTO == 0) && MultiPage.isHeicSefdMotionPhoto(context, uri)) {
+ metadataMap[KEY_FLAGS] = flags or MASK_IS_MULTIPAGE or MASK_IS_MOTION_PHOTO
+ }
}
// report success even when empty
@@ -686,6 +715,22 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
}
}
+ if (!metadataMap.containsKey(KEY_LATITUDE) || !metadataMap.containsKey(KEY_LONGITUDE)) {
+ for (dir in metadata.getDirectoriesOfType(QuickTimeMetadataDirectory::class.java)) {
+ dir.getSafeString(QuickTimeMetadataDirectory.TAG_LOCATION_ISO6709) { locationString ->
+ val matcher = Metadata.VIDEO_LOCATION_PATTERN.matcher(locationString)
+ if (matcher.find() && matcher.groupCount() >= 2) {
+ val latitude = matcher.group(1)?.toDoubleOrNull()
+ val longitude = matcher.group(2)?.toDoubleOrNull()
+ if (latitude != null && longitude != null) {
+ metadataMap[KEY_LATITUDE] = latitude
+ metadataMap[KEY_LONGITUDE] = longitude
+ }
+ }
+ }
+ }
+ }
+
when (mimeType) {
MimeTypes.PNG -> {
// date fallback to PNG time chunk
@@ -830,7 +875,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
retriever.getSafeDateMillis(MediaMetadataRetriever.METADATA_KEY_DATE) { metadataMap[KEY_DATE_MILLIS] = it }
}
- if (!metadataMap.containsKey(KEY_LATITUDE)) {
+ if (!metadataMap.containsKey(KEY_LATITUDE) || !metadataMap.containsKey(KEY_LONGITUDE)) {
val locationString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION)
if (locationString != null) {
val matcher = Metadata.VIDEO_LOCATION_PATTERN.matcher(locationString)
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/RegionFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/RegionFetcher.kt
index c6c9d0220..73fad7469 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/RegionFetcher.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/RegionFetcher.kt
@@ -4,15 +4,16 @@ import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.BitmapRegionDecoder
+import android.graphics.ColorSpace
import android.graphics.Rect
import android.net.Uri
+import android.os.Build
import android.util.Log
import com.bumptech.glide.Glide
import deckers.thibault.aves.decoder.AvesAppGlideModule
import deckers.thibault.aves.decoder.MultiPageImage
import deckers.thibault.aves.utils.BitmapRegionDecoderCompat
-import deckers.thibault.aves.utils.BitmapUtils.ARGB_8888_BYTE_SIZE
-import deckers.thibault.aves.utils.BitmapUtils.getBytes
+import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MathUtils
import deckers.thibault.aves.utils.MemoryUtils
@@ -32,7 +33,10 @@ class RegionFetcher internal constructor(
private val exportUris = HashMap, Uri>()
- suspend fun fetch(
+ // return decoded bytes in ARGB_8888, with trailer bytes:
+ // - width (int32)
+ // - height (int32)
+ fun fetch(
uri: Uri,
mimeType: String,
pageId: Int?,
@@ -98,20 +102,38 @@ class RegionFetcher internal constructor(
}
}
- // use `Long` as rect size could be unexpectedly large and go beyond `Int` max
- val targetBitmapSizeBytes: Long = ARGB_8888_BYTE_SIZE.toLong() * effectiveRect.width() * effectiveRect.height() / effectiveSampleSize
+ val options = BitmapFactory.Options().apply {
+ inSampleSize = effectiveSampleSize
+ // Specifying preferred config and color space avoids the need for conversion afterwards,
+ // but may prevent decoding (e.g. from RGBA_1010102 to ARGB_8888 on some devices).
+ inPreferredConfig = PREFERRED_CONFIG
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB)
+ }
+ }
+
+ val pixelCount = effectiveRect.width() * effectiveRect.height() / effectiveSampleSize
+ val targetBitmapSizeBytes = BitmapUtils.getExpectedImageSize(pixelCount.toLong(), options.inPreferredConfig)
if (!MemoryUtils.canAllocate(targetBitmapSizeBytes)) {
// decoding a region that large would yield an OOM when creating the bitmap
result.error("fetch-large-region", "Region too large for uri=$uri regionRect=$regionRect", null)
return
}
- val options = BitmapFactory.Options().apply {
- inSampleSize = effectiveSampleSize
+ var bitmap = decoder.decodeRegion(effectiveRect, options)
+ if (bitmap == null) {
+ // retry without specifying config or color space,
+ // falling back to custom byte conversion afterwards
+ options.inPreferredConfig = null
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && options.inPreferredColorSpace != null) {
+ options.inPreferredColorSpace = null
+ }
+ bitmap = decoder.decodeRegion(effectiveRect, options)
}
- val bitmap = decoder.decodeRegion(effectiveRect, options)
- if (bitmap != null) {
- result.success(bitmap.getBytes(MimeTypes.canHaveAlpha(mimeType), recycle = true))
+
+ val bytes = BitmapUtils.getRawBytes(bitmap, recycle = true)
+ if (bytes != null) {
+ result.success(bytes)
} else {
result.error("fetch-null", "failed to decode region for uri=$uri regionRect=$regionRect", null)
}
@@ -165,5 +187,6 @@ class RegionFetcher internal constructor(
companion object {
private val LOG_TAG = LogUtils.createTag()
+ private val PREFERRED_CONFIG = Bitmap.Config.ARGB_8888
}
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/SvgRegionFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/SvgRegionFetcher.kt
index 6b3d7f9aa..afcae1453 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/SvgRegionFetcher.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/SvgRegionFetcher.kt
@@ -6,14 +6,14 @@ import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.RectF
import android.net.Uri
+import androidx.core.graphics.createBitmap
import com.caverock.androidsvg.PreserveAspectRatio
import com.caverock.androidsvg.RenderOptions
import com.caverock.androidsvg.SVG
import com.caverock.androidsvg.SVGParseException
import deckers.thibault.aves.metadata.SVGParserBufferedInputStream
import deckers.thibault.aves.metadata.SvgHelper.normalizeSize
-import deckers.thibault.aves.utils.BitmapUtils.ARGB_8888_BYTE_SIZE
-import deckers.thibault.aves.utils.BitmapUtils.getBytes
+import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.MemoryUtils
import deckers.thibault.aves.utils.StorageUtils
import io.flutter.plugin.common.MethodChannel
@@ -24,7 +24,7 @@ class SvgRegionFetcher internal constructor(
) {
private var lastSvgRef: LastSvgRef? = null
- suspend fun fetch(
+ fun fetch(
uri: Uri,
sizeBytes: Long?,
scale: Int,
@@ -91,25 +91,25 @@ class SvgRegionFetcher internal constructor(
val targetBitmapWidth = regionRect.width()
val targetBitmapHeight = regionRect.height()
+ val canvasWidth = targetBitmapWidth + bleedX * 2
+ val canvasHeight = targetBitmapHeight + bleedY * 2
- // use `Long` as rect size could be unexpectedly large and go beyond `Int` max
- val targetBitmapSizeBytes: Long = ARGB_8888_BYTE_SIZE.toLong() * targetBitmapWidth * targetBitmapHeight
+ val config = PREFERRED_CONFIG
+ val pixelCount = canvasWidth * canvasHeight
+ val targetBitmapSizeBytes = BitmapUtils.getExpectedImageSize(pixelCount.toLong(), config)
if (!MemoryUtils.canAllocate(targetBitmapSizeBytes)) {
// decoding a region that large would yield an OOM when creating the bitmap
result.error("fetch-read-large-region", "SVG region too large for uri=$uri regionRect=$regionRect", null)
return
}
- var bitmap = Bitmap.createBitmap(
- targetBitmapWidth + bleedX * 2,
- targetBitmapHeight + bleedY * 2,
- Bitmap.Config.ARGB_8888
- )
+ var bitmap = createBitmap(canvasWidth, canvasHeight, config)
val canvas = Canvas(bitmap)
svg.renderToCanvas(canvas, renderOptions)
bitmap = Bitmap.createBitmap(bitmap, bleedX, bleedY, targetBitmapWidth, targetBitmapHeight)
- result.success(bitmap.getBytes(canHaveAlpha = true, recycle = true))
+ val bytes = BitmapUtils.getRawBytes(bitmap, recycle = true)
+ result.success(bytes)
} catch (e: Exception) {
result.error("fetch-read-exception", "failed to initialize region decoder for uri=$uri regionRect=$regionRect", e.message)
}
@@ -119,4 +119,8 @@ class SvgRegionFetcher internal constructor(
val uri: Uri,
val svg: SVG,
)
+
+ companion object {
+ private val PREFERRED_CONFIG = Bitmap.Config.ARGB_8888
+ }
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt
index 5d7c4e6b6..1bfb478de 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt
@@ -7,6 +7,7 @@ import android.os.Build
import android.provider.MediaStore
import android.util.Size
import androidx.annotation.RequiresApi
+import androidx.core.net.toUri
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.DiskCacheStrategy
@@ -14,8 +15,8 @@ import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.signature.ObjectKey
import deckers.thibault.aves.decoder.AvesAppGlideModule
import deckers.thibault.aves.decoder.MultiPageImage
+import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation
-import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.MimeTypes.SVG
import deckers.thibault.aves.utils.MimeTypes.isVideo
@@ -24,13 +25,12 @@ import deckers.thibault.aves.utils.MimeTypes.needRotationAfterGlide
import deckers.thibault.aves.utils.StorageUtils
import deckers.thibault.aves.utils.UriUtils.tryParseId
import io.flutter.plugin.common.MethodChannel
-import androidx.core.net.toUri
class ThumbnailFetcher internal constructor(
private val context: Context,
uri: String,
private val mimeType: String,
- private val dateModifiedSecs: Long,
+ private val dateModifiedMillis: Long,
private val rotationDegrees: Int,
private val isFlipped: Boolean,
width: Int?,
@@ -48,7 +48,7 @@ class ThumbnailFetcher internal constructor(
private val multiPageFetch = pageId != null && MultiPageImage.isSupported(mimeType)
private val customFetch = svgFetch || tiffFetch || multiPageFetch
- suspend fun fetch() {
+ fun fetch() {
var bitmap: Bitmap? = null
var exception: Exception? = null
@@ -77,8 +77,11 @@ class ThumbnailFetcher internal constructor(
}
}
- if (bitmap != null) {
- result.success(bitmap.getBytes(MimeTypes.canHaveAlpha(mimeType), recycle = false, quality = quality))
+ // do not recycle bitmaps fetched from `ContentResolver` or Glide as their lifecycle is unknown
+ val recycle = false
+ val bytes = BitmapUtils.getRawBytes(bitmap, recycle = recycle)
+ if (bytes != null) {
+ result.success(bytes)
} else {
var errorDetails: String? = exception?.message
if (errorDetails?.isNotEmpty() == true) {
@@ -119,7 +122,7 @@ class ThumbnailFetcher internal constructor(
// add signature to ignore cache for images which got modified but kept the same URI
var options = RequestOptions()
.format(if (quality == 100) DecodeFormat.PREFER_ARGB_8888 else DecodeFormat.PREFER_RGB_565)
- .signature(ObjectKey("$dateModifiedSecs-$rotationDegrees-$isFlipped-$width-$pageId"))
+ .signature(ObjectKey("$dateModifiedMillis-$rotationDegrees-$isFlipped-$width-$pageId"))
.override(width, height)
if (isVideo(mimeType)) {
options = options.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/TiffRegionFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/TiffRegionFetcher.kt
index 3f6f8805f..d753e1799 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/TiffRegionFetcher.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/TiffRegionFetcher.kt
@@ -1,9 +1,10 @@
package deckers.thibault.aves.channel.calls.fetchers
import android.content.Context
+import android.graphics.Bitmap
import android.graphics.Rect
import android.net.Uri
-import deckers.thibault.aves.utils.BitmapUtils.getBytes
+import deckers.thibault.aves.utils.BitmapUtils
import io.flutter.plugin.common.MethodChannel
import org.beyka.tiffbitmapfactory.DecodeArea
import org.beyka.tiffbitmapfactory.TiffBitmapFactory
@@ -11,7 +12,7 @@ import org.beyka.tiffbitmapfactory.TiffBitmapFactory
class TiffRegionFetcher internal constructor(
private val context: Context,
) {
- suspend fun fetch(
+ fun fetch(
uri: Uri,
page: Int,
sampleSize: Int,
@@ -31,9 +32,10 @@ class TiffRegionFetcher internal constructor(
inSampleSize = sampleSize
inDecodeArea = DecodeArea(regionRect.left, regionRect.top, regionRect.width(), regionRect.height())
}
- val bitmap = TiffBitmapFactory.decodeFileDescriptor(fd, options)
- if (bitmap != null) {
- result.success(bitmap.getBytes(canHaveAlpha = true, recycle = true))
+ val bitmap: Bitmap? = TiffBitmapFactory.decodeFileDescriptor(fd, options)
+ val bytes = BitmapUtils.getRawBytes(bitmap, recycle = true)
+ if (bytes != null) {
+ result.success(bytes)
} else {
result.error("getRegion-tiff-null", "failed to decode region for uri=$uri page=$page regionRect=$regionRect", null)
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ActivityWindowHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ActivityWindowHandler.kt
index 065d5ba30..f333bab00 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ActivityWindowHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ActivityWindowHandler.kt
@@ -77,19 +77,30 @@ class ActivityWindowHandler(private val activity: Activity) : WindowHandler(acti
)
}
- override fun supportsHdr(call: MethodCall, result: MethodChannel.Result) {
- result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && activity.getDisplayCompat()?.isHdr ?: false)
+ override fun supportsWideGamut(call: MethodCall, result: MethodChannel.Result) {
+ result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && activity.resources.configuration.isScreenWideColorGamut)
}
- override fun setHdrColorMode(call: MethodCall, result: MethodChannel.Result) {
- val on = call.argument("on")
- if (on == null) {
- result.error("setHdrColorMode-args", "missing arguments", null)
+ override fun supportsHdr(call: MethodCall, result: MethodChannel.Result) {
+ result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && activity.resources.configuration.isScreenHdr)
+ }
+
+ override fun setColorMode(call: MethodCall, result: MethodChannel.Result) {
+ val wideColorGamut = call.argument("wideColorGamut")
+ val hdr = call.argument("hdr")
+ if (wideColorGamut == null || hdr == null) {
+ result.error("setColorMode-args", "missing arguments", null)
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- activity.window.colorMode = if (on) ActivityInfo.COLOR_MODE_HDR else ActivityInfo.COLOR_MODE_DEFAULT
+ activity.window.colorMode = if (hdr) {
+ ActivityInfo.COLOR_MODE_HDR
+ } else if (wideColorGamut) {
+ ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT
+ } else {
+ ActivityInfo.COLOR_MODE_DEFAULT
+ }
}
result.success(null)
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ServiceWindowHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ServiceWindowHandler.kt
index 347669239..e0beb2b1e 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ServiceWindowHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ServiceWindowHandler.kt
@@ -29,11 +29,15 @@ class ServiceWindowHandler(service: Service) : WindowHandler(service) {
result.success(HashMap())
}
+ override fun supportsWideGamut(call: MethodCall, result: MethodChannel.Result) {
+ result.success(false)
+ }
+
override fun supportsHdr(call: MethodCall, result: MethodChannel.Result) {
result.success(false)
}
- override fun setHdrColorMode(call: MethodCall, result: MethodChannel.Result) {
+ override fun setColorMode(call: MethodCall, result: MethodChannel.Result) {
result.success(null)
}
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/WindowHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/WindowHandler.kt
index 051451024..2f67a454b 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/WindowHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/WindowHandler.kt
@@ -18,8 +18,9 @@ abstract class WindowHandler(private val contextWrapper: ContextWrapper) : Metho
"requestOrientation" -> Coresult.safe(call, result, ::requestOrientation)
"isCutoutAware" -> Coresult.safe(call, result, ::isCutoutAware)
"getCutoutInsets" -> Coresult.safe(call, result, ::getCutoutInsets)
+ "supportsWideGamut" -> Coresult.safe(call, result, ::supportsWideGamut)
"supportsHdr" -> Coresult.safe(call, result, ::supportsHdr)
- "setHdrColorMode" -> Coresult.safe(call, result, ::setHdrColorMode)
+ "setColorMode" -> Coresult.safe(call, result, ::setColorMode)
else -> result.notImplemented()
}
}
@@ -46,9 +47,11 @@ abstract class WindowHandler(private val contextWrapper: ContextWrapper) : Metho
abstract fun getCutoutInsets(call: MethodCall, result: MethodChannel.Result)
+ abstract fun supportsWideGamut(call: MethodCall, result: MethodChannel.Result)
+
abstract fun supportsHdr(call: MethodCall, result: MethodChannel.Result)
- abstract fun setHdrColorMode(call: MethodCall, result: MethodChannel.Result)
+ abstract fun setColorMode(call: MethodCall, result: MethodChannel.Result)
companion object {
private val LOG_TAG = LogUtils.createTag()
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt
index 33ec694b1..b833ae70d 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt
@@ -8,8 +8,8 @@ import android.util.Log
import androidx.core.net.toUri
import com.bumptech.glide.Glide
import deckers.thibault.aves.decoder.AvesAppGlideModule
+import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation
-import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MemoryUtils
import deckers.thibault.aves.utils.MimeTypes
@@ -24,6 +24,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import java.io.ByteArrayInputStream
import java.io.InputStream
class ImageByteStreamHandler(private val context: Context, private val arguments: Any?) : EventChannel.StreamHandler {
@@ -80,11 +81,13 @@ class ImageByteStreamHandler(private val context: Context, private val arguments
return
}
+ val decoded = arguments["decoded"] as Boolean
val mimeType = arguments["mimeType"] as String?
val uri = (arguments["uri"] as String?)?.toUri()
val sizeBytes = (arguments["sizeBytes"] as Number?)?.toLong()
val rotationDegrees = arguments["rotationDegrees"] as Int
val isFlipped = arguments["isFlipped"] as Boolean
+ val isAnimated = arguments["isAnimated"] as Boolean
val pageId = arguments["pageId"] as Int?
if (mimeType == null || uri == null) {
@@ -93,19 +96,31 @@ class ImageByteStreamHandler(private val context: Context, private val arguments
return
}
- if (isVideo(mimeType)) {
- streamVideoByGlide(uri, mimeType, sizeBytes)
- } else if (!canDecodeWithFlutter(mimeType, pageId, rotationDegrees, isFlipped)) {
- // decode exotic format on platform side, then encode it in portable format for Flutter
- streamImageByGlide(uri, pageId, mimeType, sizeBytes, rotationDegrees, isFlipped)
- } else {
+ if (canDecodeWithFlutter(mimeType, isAnimated) && !decoded) {
// to be decoded by Flutter
- streamImageAsIs(uri, mimeType, sizeBytes)
+ streamOriginalEncodedBytes(uri, mimeType, sizeBytes)
+ } else if (isVideo(mimeType)) {
+ streamVideoByGlide(
+ uri = uri,
+ mimeType = mimeType,
+ sizeBytes = sizeBytes,
+ decoded = decoded,
+ )
+ } else {
+ streamImageByGlide(
+ uri = uri,
+ pageId = pageId,
+ mimeType = mimeType,
+ sizeBytes = sizeBytes,
+ rotationDegrees = rotationDegrees,
+ isFlipped = isFlipped,
+ decoded = decoded,
+ )
}
endOfStream()
}
- private fun streamImageAsIs(uri: Uri, mimeType: String, sizeBytes: Long?) {
+ private fun streamOriginalEncodedBytes(uri: Uri, mimeType: String, sizeBytes: Long?) {
if (!MemoryUtils.canAllocate(sizeBytes)) {
error("streamImage-image-read-large", "original image too large at $sizeBytes bytes, for mimeType=$mimeType uri=$uri", null)
return
@@ -125,6 +140,7 @@ class ImageByteStreamHandler(private val context: Context, private val arguments
sizeBytes: Long?,
rotationDegrees: Int,
isFlipped: Boolean,
+ decoded: Boolean,
) {
val target = Glide.with(context)
.asBitmap()
@@ -137,9 +153,16 @@ class ImageByteStreamHandler(private val context: Context, private val arguments
bitmap = applyExifOrientation(context, bitmap, rotationDegrees, isFlipped)
}
if (bitmap != null) {
- val bytes = bitmap.getBytes(MimeTypes.canHaveAlpha(mimeType), recycle = false)
+ // do not recycle bitmaps fetched from Glide as their lifecycle is unknown
+ val recycle = false
+ val bytes = if (decoded) {
+ BitmapUtils.getRawBytes(bitmap, recycle = recycle)
+ } else {
+ BitmapUtils.getEncodedBytes(bitmap, canHaveAlpha = MimeTypes.canHaveAlpha(mimeType), recycle = recycle)
+ }
+
if (MemoryUtils.canAllocate(sizeBytes)) {
- success(bytes)
+ streamBytes(ByteArrayInputStream(bytes))
} else {
error("streamImage-image-decode-large", "decoded image too large at $sizeBytes bytes, for mimeType=$mimeType uri=$uri", null)
}
@@ -153,7 +176,7 @@ class ImageByteStreamHandler(private val context: Context, private val arguments
}
}
- private suspend fun streamVideoByGlide(uri: Uri, mimeType: String, sizeBytes: Long?) {
+ private suspend fun streamVideoByGlide(uri: Uri, mimeType: String, sizeBytes: Long?, decoded: Boolean) {
val target = Glide.with(context)
.asBitmap()
.apply(AvesAppGlideModule.uncachedFullImageOptions)
@@ -162,9 +185,16 @@ class ImageByteStreamHandler(private val context: Context, private val arguments
try {
val bitmap = withContext(Dispatchers.IO) { target.get() }
if (bitmap != null) {
- val bytes = bitmap.getBytes(canHaveAlpha = false, recycle = false)
+ // do not recycle bitmaps fetched from Glide as their lifecycle is unknown
+ val recycle = false
+ val bytes = if (decoded) {
+ BitmapUtils.getRawBytes(bitmap, recycle = recycle)
+ } else {
+ BitmapUtils.getEncodedBytes(bitmap, canHaveAlpha = false, recycle = recycle)
+ }
+
if (MemoryUtils.canAllocate(sizeBytes)) {
- success(bytes)
+ streamBytes(ByteArrayInputStream(bytes))
} else {
error("streamImage-video-large", "decoded image too large at $sizeBytes bytes, for mimeType=$mimeType uri=$uri", null)
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreStreamHandler.kt
index d87f6076f..23734762e 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreStreamHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreStreamHandler.kt
@@ -19,12 +19,13 @@ class MediaStoreStreamHandler(private val context: Context, arguments: Any?) : E
private lateinit var eventSink: EventSink
private lateinit var handler: Handler
- private var knownEntries: Map? = null
+ // knownEntries: map of contentId -> dateModifiedMillis
+ private var knownEntries: Map? = null
private var directory: String? = null
init {
if (arguments is Map<*, *>) {
- knownEntries = (arguments["knownEntries"] as? Map<*, *>?)?.map { (it.key as Number?)?.toLong() to it.value as Int? }?.toMap()
+ knownEntries = (arguments["knownEntries"] as? Map<*, *>?)?.map { (it.key as Number?)?.toLong() to (it.value as Number?)?.toLong() }?.toMap()
directory = arguments["directory"] as String?
}
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/SvgGlideModule.kt b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/SvgGlideModule.kt
index 0a8cef472..9f452bfe1 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/SvgGlideModule.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/SvgGlideModule.kt
@@ -4,6 +4,7 @@ import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.net.Uri
+import androidx.core.graphics.createBitmap
import com.bumptech.glide.Glide
import com.bumptech.glide.Priority
import com.bumptech.glide.Registry
@@ -68,7 +69,7 @@ internal class SvgFetcher(val model: SvgImage, val width: Int, val height: Int)
bitmapWidth = width
bitmapHeight = ceil(svgHeight * width / svgWidth).toInt()
}
- val bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)
+ val bitmap = createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
svg.renderToCanvas(canvas)
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffGlideModule.kt b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffGlideModule.kt
index 0671237a8..e5d5d1d73 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffGlideModule.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffGlideModule.kt
@@ -3,6 +3,7 @@ package deckers.thibault.aves.decoder
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
+import androidx.core.graphics.scale
import com.bumptech.glide.Glide
import com.bumptech.glide.Priority
import com.bumptech.glide.Registry
@@ -82,7 +83,9 @@ internal class TiffFetcher(val model: TiffImage, val width: Int, val height: Int
inSampleSize = sampleSize
}
try {
- val bitmap = TiffBitmapFactory.decodeFileDescriptor(fd, options)
+ val bitmap: Bitmap? = TiffBitmapFactory.decodeFileDescriptor(fd, options)
+ // calling `TiffBitmapFactory.closeFd(fd)` after decoding yields a segmentation fault
+
if (bitmap == null) {
callback.onLoadFailed(Exception("Decoding full TIFF yielded null bitmap"))
} else if (customSize) {
@@ -96,7 +99,7 @@ internal class TiffFetcher(val model: TiffImage, val width: Int, val height: Int
dstWidth = width
dstHeight = (width / aspectRatio).toInt()
}
- callback.onDataReady(Bitmap.createScaledBitmap(bitmap, dstWidth, dstHeight, true))
+ callback.onDataReady(bitmap.scale(dstWidth, dstHeight))
} else {
callback.onDataReady(bitmap)
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt
index 94a435c8f..6f814bc2b 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt
@@ -2,6 +2,7 @@ package deckers.thibault.aves.decoder
import android.content.Context
import android.graphics.Bitmap
+import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
@@ -20,7 +21,6 @@ import com.bumptech.glide.load.model.MultiModelLoaderFactory
import com.bumptech.glide.module.LibraryGlideModule
import com.bumptech.glide.signature.ObjectKey
import deckers.thibault.aves.utils.BitmapUtils
-import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.MemoryUtils
import deckers.thibault.aves.utils.StorageUtils.openMetadataRetriever
import kotlinx.coroutines.CoroutineScope
@@ -28,45 +28,54 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import java.io.ByteArrayInputStream
-import java.io.InputStream
+import java.io.IOException
import kotlin.math.ceil
import kotlin.math.roundToInt
@GlideModule
class VideoThumbnailGlideModule : LibraryGlideModule() {
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
- registry.append(VideoThumbnail::class.java, InputStream::class.java, VideoThumbnailLoader.Factory())
+ registry.append(VideoThumbnail::class.java, Bitmap::class.java, VideoThumbnailLoader.Factory())
}
}
class VideoThumbnail(val context: Context, val uri: Uri)
-internal class VideoThumbnailLoader : ModelLoader {
- override fun buildLoadData(model: VideoThumbnail, width: Int, height: Int, options: Options): ModelLoader.LoadData {
+internal class VideoThumbnailLoader : ModelLoader {
+ override fun buildLoadData(model: VideoThumbnail, width: Int, height: Int, options: Options): ModelLoader.LoadData {
return ModelLoader.LoadData(ObjectKey(model.uri), VideoThumbnailFetcher(model, width, height))
}
override fun handles(model: VideoThumbnail): Boolean = true
- internal class Factory : ModelLoaderFactory {
- override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader = VideoThumbnailLoader()
+ internal class Factory : ModelLoaderFactory {
+ override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader = VideoThumbnailLoader()
override fun teardown() {}
}
}
-internal class VideoThumbnailFetcher(private val model: VideoThumbnail, val width: Int, val height: Int) : DataFetcher {
+internal class VideoThumbnailFetcher(private val model: VideoThumbnail, val width: Int, val height: Int) : DataFetcher {
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
- override fun loadData(priority: Priority, callback: DataCallback) {
+ override fun loadData(priority: Priority, callback: DataCallback) {
ioScope.launch {
val retriever = openMetadataRetriever(model.context, model.uri)
if (retriever == null) {
callback.onLoadFailed(Exception("failed to initialize MediaMetadataRetriever for uri=${model.uri}"))
} else {
try {
- var bytes = retriever.embeddedPicture
- if (bytes == null) {
+ var bitmap: Bitmap? = null
+
+ retriever.embeddedPicture?.let { bytes ->
+ try {
+ bitmap = BitmapFactory.decodeStream(ByteArrayInputStream(bytes))
+ } catch (e: IOException) {
+ // ignore
+ }
+ }
+
+ if (bitmap == null) {
// there is no consistent strategy across devices to match
// the thumbnails returned by the content resolver / Media Store
// so we derive one in an arbitrary way
@@ -111,8 +120,9 @@ internal class VideoThumbnailFetcher(private val model: VideoThumbnail, val widt
}
// the returned frame is already rotated according to the video metadata
- val frame = if (dstWidth > 0 && dstHeight > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
- val targetBitmapSizeBytes: Long = FORMAT_BYTE_SIZE.toLong() * dstWidth * dstHeight
+ bitmap = if (dstWidth > 0 && dstHeight > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
+ val pixelCount = dstWidth * dstHeight
+ val targetBitmapSizeBytes = BitmapUtils.getExpectedImageSize(pixelCount.toLong(), getPreferredConfig())
if (!MemoryUtils.canAllocate(targetBitmapSizeBytes)) {
throw Exception("not enough memory to allocate $targetBitmapSizeBytes bytes for the scaled frame at $dstWidth x $dstHeight")
}
@@ -122,7 +132,8 @@ internal class VideoThumbnailFetcher(private val model: VideoThumbnail, val widt
retriever.getScaledFrameAtTime(timeMicros, option, dstWidth, dstHeight)
}
} else {
- val targetBitmapSizeBytes: Long = (FORMAT_BYTE_SIZE.toLong() * videoWidth * videoHeight).toLong()
+ val pixelCount = videoWidth * videoHeight
+ val targetBitmapSizeBytes = BitmapUtils.getExpectedImageSize(pixelCount.toLong(), getPreferredConfig())
if (!MemoryUtils.canAllocate(targetBitmapSizeBytes)) {
throw Exception("not enough memory to allocate $targetBitmapSizeBytes bytes for the full frame at $videoWidth x $videoHeight")
}
@@ -132,13 +143,12 @@ internal class VideoThumbnailFetcher(private val model: VideoThumbnail, val widt
retriever.getFrameAtTime(timeMicros, option)
}
}
- bytes = frame?.getBytes(canHaveAlpha = false, recycle = false)
}
- if (bytes != null) {
- callback.onDataReady(ByteArrayInputStream(bytes))
+ if (bitmap == null) {
+ callback.onLoadFailed(Exception("failed to get embedded picture or any frame for uri=${model.uri}"))
} else {
- callback.onLoadFailed(Exception("failed to get embedded picture or any frame"))
+ callback.onDataReady(bitmap)
}
} catch (e: Exception) {
callback.onLoadFailed(e)
@@ -151,8 +161,14 @@ internal class VideoThumbnailFetcher(private val model: VideoThumbnail, val widt
}
@RequiresApi(Build.VERSION_CODES.P)
- private fun getBitmapParams() = MediaMetadataRetriever.BitmapParams().apply {
- preferredConfig = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ private fun getBitmapParams(): MediaMetadataRetriever.BitmapParams {
+ val params = MediaMetadataRetriever.BitmapParams()
+ params.preferredConfig = this.getPreferredConfig()
+ return params
+ }
+
+ private fun getPreferredConfig(): Bitmap.Config {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// improved precision with the same memory cost as `ARGB_8888` (4 bytes per pixel)
// for wide-gamut and HDR content which does not require alpha blending
Bitmap.Config.RGBA_1010102
@@ -167,12 +183,7 @@ internal class VideoThumbnailFetcher(private val model: VideoThumbnail, val widt
// cannot cancel
override fun cancel() {}
- override fun getDataClass(): Class = InputStream::class.java
+ override fun getDataClass(): Class = Bitmap::class.java
override fun getDataSource(): DataSource = DataSource.LOCAL
-
- companion object {
- // same for either `ARGB_8888` or `RGBA_1010102`
- private const val FORMAT_BYTE_SIZE = BitmapUtils.ARGB_8888_BYTE_SIZE
- }
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Mp4ParserHelper.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Mp4ParserHelper.kt
index 2f9801b70..1ba6df6e9 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Mp4ParserHelper.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Mp4ParserHelper.kt
@@ -32,6 +32,7 @@ import org.mp4parser.boxes.iso14496.part12.SegmentIndexBox
import org.mp4parser.boxes.iso14496.part12.TrackHeaderBox
import org.mp4parser.boxes.iso14496.part12.UserDataBox
import org.mp4parser.boxes.threegpp.ts26244.AuthorBox
+import org.mp4parser.boxes.threegpp.ts26244.LocationInformationBox
import org.mp4parser.support.AbstractBox
import org.mp4parser.support.Matrix
import org.mp4parser.tools.Path
@@ -45,6 +46,15 @@ object Mp4ParserHelper {
// arbitrary size to detect boxes that may yield an OOM
private const val BOX_SIZE_DANGER_THRESHOLD = 3 * (1 shl 20) // MB
+ const val SAMSUNG_MAKERNOTE_BOX_TYPE = "sefd"
+ const val SEFD_MOTION_PHOTO_NAME = "MotionPhoto_Data"
+
+ private val largerTypeWhitelist = listOf(
+ // HEIC motion photo may contain Samsung maker notes in `sefd` box,
+ // including a video larger than the danger threshold
+ SAMSUNG_MAKERNOTE_BOX_TYPE,
+ )
+
fun computeEdits(context: Context, uri: Uri, modifier: (isoFile: IsoFile) -> Unit): List> {
// we can skip uninteresting boxes with a seekable data source
val pfd = StorageUtils.openInputFileDescriptor(context, uri) ?: throw Exception("failed to open file descriptor for uri=$uri")
@@ -133,6 +143,35 @@ object Mp4ParserHelper {
return false
}
+ // returns the offset and data of the Samsung maker notes box
+ fun getSamsungSefd(context: Context, uri: Uri): Pair? {
+ try {
+ // we can skip uninteresting boxes with a seekable data source
+ val pfd = StorageUtils.openInputFileDescriptor(context, uri) ?: throw Exception("failed to open file descriptor for uri=$uri")
+ pfd.use {
+ FileInputStream(it.fileDescriptor).use { stream ->
+ stream.channel.use { channel ->
+ IsoFile(channel, metadataBoxParser()).use { isoFile ->
+ var offset = 0L
+ for (box in isoFile.boxes) {
+ if (box is UnknownBox && box.type == SAMSUNG_MAKERNOTE_BOX_TYPE) {
+ if (!box.isParsed) {
+ box.parseDetails()
+ }
+ return Pair(offset + 8, box.data.toByteArray()) // skip 8 bytes for box header
+ }
+ offset += box.size
+ }
+ }
+ }
+ }
+ }
+ } catch (e: Exception) {
+ Log.w(LOG_TAG, "failed to read sefd box", e)
+ }
+ return null
+ }
+
// extensions
fun IsoFile.updateLocation(locationIso6709: String?) {
@@ -272,18 +311,18 @@ object Mp4ParserHelper {
)
setBoxSkipper { type, size ->
if (skippedTypes.contains(type)) return@setBoxSkipper true
- if (size > BOX_SIZE_DANGER_THRESHOLD) throw Exception("box (type=$type size=$size) is too large")
+ if (size > BOX_SIZE_DANGER_THRESHOLD && !largerTypeWhitelist.contains(type)) throw Exception("box (type=$type size=$size) is too large")
false
}
}
- fun getUserData(
+ fun getUserDataBox(
context: Context,
mimeType: String,
uri: Uri,
- ): MutableMap {
- val fields = HashMap()
- if (mimeType != MimeTypes.MP4) return fields
+ ): UserDataBox? {
+ if (mimeType != MimeTypes.MP4) return null
+
try {
// we can skip uninteresting boxes with a seekable data source
val pfd = StorageUtils.openInputFileDescriptor(context, uri) ?: throw Exception("failed to open file descriptor for uri=$uri")
@@ -292,10 +331,7 @@ object Mp4ParserHelper {
stream.channel.use { channel ->
// creating `IsoFile` with a `File` or a `File.inputStream()` yields `No such device`
IsoFile(channel, metadataBoxParser()).use { isoFile ->
- val userDataBox = Path.getPath(isoFile.movieBox, UserDataBox.TYPE)
- if (userDataBox != null) {
- fields.putAll(extractBoxFields(userDataBox))
- }
+ return Path.getPath(isoFile.movieBox, UserDataBox.TYPE)
}
}
}
@@ -305,10 +341,10 @@ object Mp4ParserHelper {
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to get User Data box by MP4 parser for mimeType=$mimeType uri=$uri", e)
}
- return fields
+ return null
}
- private fun extractBoxFields(container: Container): HashMap {
+ fun extractBoxFields(container: Container): HashMap {
val fields = HashMap()
for (box in container.boxes) {
if (box is AbstractBox && !box.isParsed) {
@@ -322,9 +358,20 @@ object Mp4ParserHelper {
is AppleGPSCoordinatesBox -> fields[key] = box.value
is AppleItemListBox -> fields.putAll(extractBoxFields(box))
is AppleVariableSignedIntegerBox -> fields[key] = box.value.toString()
- is Utf8AppleDataBox -> fields[key] = box.value
-
is HandlerBox -> {}
+ is LocationInformationBox -> {
+ hashMapOf(
+ "Language" to box.language,
+ "Name" to box.name,
+ "Role" to box.role.toString(),
+ "Longitude" to box.longitude.toString(),
+ "Latitude" to box.latitude.toString(),
+ "Altitude" to box.altitude.toString(),
+ "Astronomical Body" to box.astronomicalBody,
+ "Additional Notes" to box.additionalNotes,
+ ).forEach { (k, v) -> fields["$key/$k"] = v }
+ }
+
is MetaBox -> {
val handlerBox = Path.getPath(box, HandlerBox.TYPE).apply { parseDetails() }
when (val handlerType = handlerBox?.handlerType ?: MetaBox.TYPE) {
@@ -349,6 +396,8 @@ object Mp4ParserHelper {
}
}
+ is Utf8AppleDataBox -> fields[key] = box.value
+
else -> fields[key] = box.toString()
}
}
@@ -361,6 +410,7 @@ object Mp4ParserHelper {
"catg" -> "Category"
"covr" -> "Cover Art"
"keyw" -> "Keyword"
+ "loci" -> "Location"
"mcvr" -> "Preview Image"
"pcst" -> "Podcast"
"SDLN" -> "Play Mode"
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt
index a0d98a966..9619f8336 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt
@@ -37,6 +37,8 @@ import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
object MultiPage {
private val LOG_TAG = LogUtils.createTag()
+ // TODO TLAD more generic support, (e.g. 0x00000014 + `ftyp` + `qt `)
+ // atom length (variable, e.g. `0x00000018`) + atom type (`ftyp`) + type (variable, e.g. `mp42`, `qt`)
private val heicMotionPhotoVideoStartIndicator = byteArrayOf(0x00, 0x00, 0x00, 0x18) + "ftypmp42".toByteArray()
// page info
@@ -84,6 +86,26 @@ object MultiPage {
return tracks
}
+ fun isHeicSefdMotionPhoto(context: Context, uri: Uri): Boolean {
+ return getHeicSefdMotionPhotoVideoSizing(context, uri) != null
+ }
+
+ private fun getHeicSefdMotionPhotoVideoSizing(context: Context, uri: Uri): Pair? {
+ Mp4ParserHelper.getSamsungSefd(context, uri)?.let { (sefdOffset, sefdBytes) ->
+ // we could properly parse each tag until we find the "embedded video" tag (0x0a30)
+ // but it seems that decoding the SEFT trailer is necessary for this,
+ // so we simply search for the "MotionPhoto_Data" sequence instead
+ val name = Mp4ParserHelper.SEFD_MOTION_PHOTO_NAME
+ val index = sefdBytes.indexOfBytes(name.toByteArray(Charsets.UTF_8))
+ if (index != -1) {
+ val videoOffset = sefdOffset + index + name.length
+ val videoSize = sefdBytes.size - (videoOffset - sefdOffset)
+ return Pair(videoOffset, videoSize)
+ }
+ }
+ return null
+ }
+
private fun getJpegMpfPrimaryRotation(context: Context, uri: Uri, sizeBytes: Long): Int {
val mimeType = MimeTypes.JPEG
var rotationDegrees = 0
@@ -245,40 +267,38 @@ object MultiPage {
fun getMotionPhotoPages(context: Context, uri: Uri, mimeType: String, sizeBytes: Long): ArrayList {
val pages = ArrayList()
- getMotionPhotoVideoSize(context, uri, mimeType, sizeBytes)?.let { videoSizeBytes ->
- getTrailerVideoInfo(context, uri, fileSizeBytes = sizeBytes, videoSizeBytes = videoSizeBytes)?.let { videoInfo ->
- // set the original image as the first and default track
- var pageIndex = 0
- pages.add(
- hashMapOf(
- KEY_PAGE to pageIndex++,
- KEY_MIME_TYPE to mimeType,
- KEY_IS_DEFAULT to true,
- )
+ getMotionPhotoVideoInfo(context, uri, mimeType, sizeBytes)?.let { videoInfo ->
+ // set the original image as the first and default track
+ var pageIndex = 0
+ pages.add(
+ hashMapOf(
+ KEY_PAGE to pageIndex++,
+ KEY_MIME_TYPE to mimeType,
+ KEY_IS_DEFAULT to true,
)
- // add video tracks from the appended video
- videoInfo.getString(MediaFormat.KEY_MIME)?.let { mime ->
- if (MimeTypes.isVideo(mime)) {
- val page: FieldMap = hashMapOf(
- KEY_PAGE to pageIndex++,
- KEY_MIME_TYPE to MimeTypes.MP4,
- KEY_IS_DEFAULT to false,
- )
- videoInfo.getSafeInt(MediaFormat.KEY_WIDTH) { page[KEY_WIDTH] = it }
- videoInfo.getSafeInt(MediaFormat.KEY_HEIGHT) { page[KEY_HEIGHT] = it }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- videoInfo.getSafeInt(MediaFormat.KEY_ROTATION) { page[KEY_ROTATION_DEGREES] = it }
- }
- videoInfo.getSafeLong(MediaFormat.KEY_DURATION) { page[KEY_DURATION] = it / 1000 }
- pages.add(page)
+ )
+ // add video tracks from the appended video
+ videoInfo.getString(MediaFormat.KEY_MIME)?.let { mime ->
+ if (MimeTypes.isVideo(mime)) {
+ val page: FieldMap = hashMapOf(
+ KEY_PAGE to pageIndex++,
+ KEY_MIME_TYPE to MimeTypes.MP4,
+ KEY_IS_DEFAULT to false,
+ )
+ videoInfo.getSafeInt(MediaFormat.KEY_WIDTH) { page[KEY_WIDTH] = it }
+ videoInfo.getSafeInt(MediaFormat.KEY_HEIGHT) { page[KEY_HEIGHT] = it }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ videoInfo.getSafeInt(MediaFormat.KEY_ROTATION) { page[KEY_ROTATION_DEGREES] = it }
}
+ videoInfo.getSafeLong(MediaFormat.KEY_DURATION) { page[KEY_DURATION] = it / 1000 }
+ pages.add(page)
}
}
}
return pages
}
- fun getMotionPhotoVideoSize(context: Context, uri: Uri, mimeType: String, sizeBytes: Long): Long? {
+ fun getTrailerVideoSize(context: Context, uri: Uri, mimeType: String, sizeBytes: Long): Long? {
if (MimeTypes.isHeic(mimeType)) {
// XMP in HEIC motion photos (as taken with a Samsung Camera v12.0.01.50) indicates an `Item:Length` of 68 bytes for the video.
// This item does not contain the video itself, but only some kind of metadata (no doc, no spec),
@@ -325,22 +345,35 @@ object MultiPage {
return offsetFromEnd
}
- fun getTrailerVideoInfo(context: Context, uri: Uri, fileSizeBytes: Long, videoSizeBytes: Long): MediaFormat? {
- var format: MediaFormat? = null
+ private fun getMotionPhotoVideoInfo(context: Context, uri: Uri, mimeType: String, sizeBytes: Long): MediaFormat? {
+ getMotionPhotoVideoSizing(context, uri, mimeType, sizeBytes)?.let { (videoOffset, videoSize) ->
+ return getEmbedVideoInfo(context, uri, videoOffset, videoSize)
+ }
+ return null
+ }
+
+ fun getTrailerVideoInfo(context: Context, uri: Uri, fileSize: Long, videoSize: Long): MediaFormat? {
+ return getEmbedVideoInfo(context, uri, videoOffset = fileSize - videoSize, videoSize = videoSize)
+ }
+
+ private fun getEmbedVideoInfo(context: Context, uri: Uri, videoOffset: Long, videoSize: Long): MediaFormat? {
val extractor = MediaExtractor()
var pfd: ParcelFileDescriptor? = null
try {
- val videoStartOffset = fileSizeBytes - videoSizeBytes
pfd = context.contentResolver.openFileDescriptor(uri, "r")
pfd?.fileDescriptor?.let { fd ->
- extractor.setDataSource(fd, videoStartOffset, videoSizeBytes)
- if (extractor.trackCount > 0) {
- // only consider the first track to represent the appended video
- val trackIndex = 0
+ extractor.setDataSource(fd, videoOffset, videoSize)
+ // video track may be after an audio track
+ for (trackIndex in 0 until extractor.trackCount) {
try {
- format = extractor.getTrackFormat(trackIndex)
+ val format = extractor.getTrackFormat(trackIndex)
+ format.getString(MediaFormat.KEY_MIME)?.let {
+ if (MimeTypes.isVideo(it)) {
+ return format
+ }
+ }
} catch (e: Exception) {
- Log.w(LOG_TAG, "failed to get motion photo track information for uri=$uri, track num=$trackIndex", e)
+ Log.w(LOG_TAG, "failed to get track information for uri=$uri, track num=$trackIndex", e)
}
}
}
@@ -350,7 +383,22 @@ object MultiPage {
extractor.release()
pfd?.close()
}
- return format
+ return null
+ }
+
+ fun getMotionPhotoVideoSizing(context: Context, uri: Uri, mimeType: String, sizeBytes: Long): Pair? {
+ // default to trailer videos
+ getTrailerVideoSize(context, uri, mimeType, sizeBytes)?.let { videoSize ->
+ val videoOffset = sizeBytes - videoSize
+ return Pair(videoOffset, videoSize)
+ }
+
+ if (MimeTypes.isHeic(mimeType)) {
+ // fallback to video within Samsung SEFD box
+ return getHeicSefdMotionPhotoVideoSizing(context, uri)
+ }
+
+ return null
}
fun getTiffPages(context: Context, uri: Uri): ArrayList {
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/EntryFields.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/EntryFields.kt
index 279881445..1dab12be1 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/EntryFields.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/EntryFields.kt
@@ -18,7 +18,7 @@ object EntryFields {
const val IS_FLIPPED = "isFlipped" // boolean
const val DATE_ADDED_SECS = "dateAddedSecs" // long
- const val DATE_MODIFIED_SECS = "dateModifiedSecs" // long
+ const val DATE_MODIFIED_MILLIS = "dateModifiedMillis" // long
const val SOURCE_DATE_TAKEN_MILLIS = "sourceDateTakenMillis" // long
const val DURATION_MILLIS = "durationMillis" // long
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt
index 56d95818f..4a3c83ca8 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt
@@ -5,6 +5,7 @@ import android.content.Context
import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever
import android.net.Uri
+import androidx.core.net.toUri
import com.drew.metadata.avi.AviDirectory
import com.drew.metadata.exif.ExifIFD0Directory
import com.drew.metadata.jpeg.JpegDirectory
@@ -29,7 +30,6 @@ import deckers.thibault.aves.utils.UriUtils.tryParseId
import org.beyka.tiffbitmapfactory.TiffBitmapFactory
import java.io.IOException
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
-import androidx.core.net.toUri
class SourceEntry {
private val origin: Int
@@ -42,7 +42,7 @@ class SourceEntry {
private var sourceRotationDegrees: Int? = null
private var sizeBytes: Long? = null
private var dateAddedSecs: Long? = null
- private var dateModifiedSecs: Long? = null
+ private var dateModifiedMillis: Long? = null
private var sourceDateTakenMillis: Long? = null
private var durationMillis: Long? = null
@@ -65,16 +65,16 @@ class SourceEntry {
sizeBytes = toLong(map[EntryFields.SIZE_BYTES])
title = map[EntryFields.TITLE] as String?
dateAddedSecs = toLong(map[EntryFields.DATE_ADDED_SECS])
- dateModifiedSecs = toLong(map[EntryFields.DATE_MODIFIED_SECS])
+ dateModifiedMillis = toLong(map[EntryFields.DATE_MODIFIED_MILLIS])
sourceDateTakenMillis = toLong(map[EntryFields.SOURCE_DATE_TAKEN_MILLIS])
durationMillis = toLong(map[EntryFields.DURATION_MILLIS])
}
- fun initFromFile(path: String, title: String, sizeBytes: Long, dateModifiedSecs: Long) {
+ fun initFromFile(path: String, title: String, sizeBytes: Long, dateModifiedMillis: Long) {
this.path = path
this.title = title
this.sizeBytes = sizeBytes
- this.dateModifiedSecs = dateModifiedSecs
+ this.dateModifiedMillis = dateModifiedMillis
}
fun toMap(): FieldMap {
@@ -89,7 +89,7 @@ class SourceEntry {
EntryFields.SIZE_BYTES to sizeBytes,
EntryFields.TITLE to title,
EntryFields.DATE_ADDED_SECS to dateAddedSecs,
- EntryFields.DATE_MODIFIED_SECS to dateModifiedSecs,
+ EntryFields.DATE_MODIFIED_MILLIS to dateModifiedMillis,
EntryFields.SOURCE_DATE_TAKEN_MILLIS to sourceDateTakenMillis,
EntryFields.DURATION_MILLIS to durationMillis,
// only for map export
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/FileImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/FileImageProvider.kt
index a002183d2..8ab754e2c 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/FileImageProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/FileImageProvider.kt
@@ -46,7 +46,7 @@ internal class FileImageProvider : ImageProvider() {
path = path,
title = file.name,
sizeBytes = file.length(),
- dateModifiedSecs = file.lastModified() / 1000,
+ dateModifiedMillis = file.lastModified(),
)
}
} catch (e: SecurityException) {
@@ -91,7 +91,7 @@ internal class FileImageProvider : ImageProvider() {
return hashMapOf(
EntryFields.URI to Uri.fromFile(newFile).toString(),
EntryFields.PATH to newFile.path,
- EntryFields.DATE_MODIFIED_SECS to newFile.lastModified() / 1000,
+ EntryFields.DATE_MODIFIED_MILLIS to newFile.lastModified(),
)
}
@@ -99,7 +99,7 @@ internal class FileImageProvider : ImageProvider() {
try {
val file = File(path)
if (file.exists()) {
- newFields[EntryFields.DATE_MODIFIED_SECS] = file.lastModified() / 1000
+ newFields[EntryFields.DATE_MODIFIED_MILLIS] = file.lastModified()
newFields[EntryFields.SIZE_BYTES] = file.length()
}
callback.onSuccess(newFields)
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt
index fc46edd70..39fbf091d 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt
@@ -11,6 +11,7 @@ import android.net.Uri
import android.os.Binder
import android.os.Build
import android.util.Log
+import androidx.core.net.toUri
import com.bumptech.glide.Glide
import com.bumptech.glide.request.FutureTarget
import com.commonsware.cwac.document.DocumentFileCompat
@@ -32,6 +33,7 @@ import deckers.thibault.aves.metadata.PixyMetaHelper.xmpDocString
import deckers.thibault.aves.metadata.metadataextractor.Helper
import deckers.thibault.aves.metadata.xmp.GoogleXMP
import deckers.thibault.aves.model.AvesEntry
+import deckers.thibault.aves.model.EntryFields
import deckers.thibault.aves.model.ExifOrientationOp
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.model.NameConflictResolution
@@ -63,8 +65,6 @@ import java.util.Date
import java.util.TimeZone
import kotlin.math.absoluteValue
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
-import androidx.core.net.toUri
-import deckers.thibault.aves.model.EntryFields
abstract class ImageProvider {
open fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, allowUnsized: Boolean, callback: ImageOpCallback) {
@@ -648,13 +648,13 @@ abstract class ImageProvider {
val originalFileSize = File(path).length()
var trailerVideoBytes: ByteArray? = null
val editableFile = StorageUtils.createTempFile(context).apply {
- val videoSize = MultiPage.getMotionPhotoVideoSize(context, uri, mimeType, originalFileSize)?.let { it + trailerDiff }
- val isTrailerVideoValid = videoSize != null && MultiPage.getTrailerVideoInfo(context, uri, originalFileSize, videoSize) != null
+ val trailerVideoSize = MultiPage.getTrailerVideoSize(context, uri, mimeType, originalFileSize)?.let { it + trailerDiff }
+ val isTrailerVideoValid = trailerVideoSize != null && MultiPage.getTrailerVideoInfo(context, uri, originalFileSize, trailerVideoSize) != null
try {
- if (videoSize != null && isTrailerVideoValid) {
+ if (trailerVideoSize != null && isTrailerVideoValid) {
// handle motion photo and embedded video separately
- val imageSize = (originalFileSize - videoSize).toInt()
- val videoByteSize = videoSize.toInt()
+ val imageSize = (originalFileSize - trailerVideoSize).toInt()
+ val videoByteSize = trailerVideoSize.toInt()
trailerVideoBytes = ByteArray(videoByteSize)
StorageUtils.openInputStream(context, uri)?.let { input ->
@@ -733,13 +733,13 @@ abstract class ImageProvider {
val originalFileSize = File(path).length()
var trailerVideoBytes: ByteArray? = null
val editableFile = StorageUtils.createTempFile(context).apply {
- val videoSize = MultiPage.getMotionPhotoVideoSize(context, uri, mimeType, originalFileSize)?.let { it + trailerDiff }
- val isTrailerVideoValid = videoSize != null && MultiPage.getTrailerVideoInfo(context, uri, originalFileSize, videoSize) != null
+ val trailerVideoSize = MultiPage.getTrailerVideoSize(context, uri, mimeType, originalFileSize)?.let { it + trailerDiff }
+ val isTrailerVideoValid = trailerVideoSize != null && MultiPage.getTrailerVideoInfo(context, uri, originalFileSize, trailerVideoSize) != null
try {
- if (videoSize != null && isTrailerVideoValid) {
+ if (trailerVideoSize != null && isTrailerVideoValid) {
// handle motion photo and embedded video separately
- val imageSize = (originalFileSize - videoSize).toInt()
- val videoByteSize = videoSize.toInt()
+ val imageSize = (originalFileSize - trailerVideoSize).toInt()
+ val videoByteSize = trailerVideoSize.toInt()
trailerVideoBytes = ByteArray(videoByteSize)
StorageUtils.openInputStream(context, uri)?.let { input ->
@@ -899,7 +899,7 @@ abstract class ImageProvider {
}
val originalFileSize = File(path).length()
- val videoSize = MultiPage.getMotionPhotoVideoSize(context, uri, mimeType, originalFileSize)?.let { it.toInt() + trailerDiff }
+ val trailerVideoSize = MultiPage.getTrailerVideoSize(context, uri, mimeType, originalFileSize)?.let { it.toInt() + trailerDiff }
val editableFile = StorageUtils.createTempFile(context).apply {
try {
editXmpWithPixy(
@@ -921,7 +921,7 @@ abstract class ImageProvider {
// copy the edited temporary file back to the original
editableFile.transferTo(outputStream(context, mimeType, uri, path))
- if (autoCorrectTrailerOffset && !checkTrailerOffset(context, path, uri, mimeType, videoSize, editableFile, callback)) {
+ if (autoCorrectTrailerOffset && !checkTrailerOffset(context, path, uri, mimeType, trailerVideoSize, editableFile, callback)) {
return false
}
editableFile.delete()
@@ -1262,15 +1262,15 @@ abstract class ImageProvider {
callback: ImageOpCallback,
) {
val originalFileSize = File(path).length()
- val videoSize = MultiPage.getMotionPhotoVideoSize(context, uri, mimeType, originalFileSize)
- if (videoSize == null) {
+ val trailerVideoSize = MultiPage.getTrailerVideoSize(context, uri, mimeType, originalFileSize)
+ if (trailerVideoSize == null) {
callback.onFailure(Exception("failed to get trailer video size"))
return
}
- val isTrailerVideoValid = MultiPage.getTrailerVideoInfo(context, uri, fileSizeBytes = originalFileSize, videoSizeBytes = videoSize) != null
+ val isTrailerVideoValid = MultiPage.getTrailerVideoInfo(context, uri, fileSize = originalFileSize, videoSize = trailerVideoSize) != null
if (!isTrailerVideoValid) {
- callback.onFailure(Exception("failed to open trailer video with size=$videoSize"))
+ callback.onFailure(Exception("failed to open trailer video with size=$trailerVideoSize"))
return
}
@@ -1278,7 +1278,7 @@ abstract class ImageProvider {
try {
val inputStream = StorageUtils.openInputStream(context, uri)
// partial copy
- transferFrom(inputStream, originalFileSize - videoSize)
+ transferFrom(inputStream, originalFileSize - trailerVideoSize)
} catch (e: Exception) {
Log.d(LOG_TAG, "failed to remove trailer video", e)
callback.onFailure(e)
@@ -1313,8 +1313,8 @@ abstract class ImageProvider {
}
val originalFileSize = File(path).length()
- val videoSize = MultiPage.getMotionPhotoVideoSize(context, uri, mimeType, originalFileSize)
- val isTrailerVideoValid = videoSize != null && MultiPage.getTrailerVideoInfo(context, uri, originalFileSize, videoSize) != null
+ val trailerVideoSize = MultiPage.getTrailerVideoSize(context, uri, mimeType, originalFileSize)
+ val isTrailerVideoValid = trailerVideoSize != null && MultiPage.getTrailerVideoInfo(context, uri, originalFileSize, trailerVideoSize) != null
val editableFile = StorageUtils.createTempFile(context).apply {
try {
outputStream().use { output ->
@@ -1334,7 +1334,7 @@ abstract class ImageProvider {
// copy the edited temporary file back to the original
editableFile.transferTo(outputStream(context, mimeType, uri, path))
- if (!types.contains(TYPE_XMP) && isTrailerVideoValid && !checkTrailerOffset(context, path, uri, mimeType, videoSize, editableFile, callback)) {
+ if (!types.contains(TYPE_XMP) && isTrailerVideoValid && !checkTrailerOffset(context, path, uri, mimeType, trailerVideoSize, editableFile, callback)) {
return
}
editableFile.delete()
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 d635d62fb..7b1b7b190 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
@@ -51,14 +51,14 @@ import kotlin.coroutines.suspendCoroutine
class MediaStoreImageProvider : ImageProvider() {
fun fetchAll(
context: Context,
- knownEntries: Map,
+ knownEntries: Map,
directory: String?,
handleNewEntry: NewEntryHandler,
) {
Log.d(LOG_TAG, "fetching all media store items for ${knownEntries.size} known entries, directory=$directory")
- val isModified = fun(contentId: Long, dateModifiedSecs: Int): Boolean {
+ val isModified = fun(contentId: Long, dateModifiedMillis: Long): Boolean {
val knownDate = knownEntries[contentId]
- return knownDate == null || knownDate < dateModifiedSecs
+ return knownDate == null || knownDate < dateModifiedMillis
}
val handleNew: NewEntryHandler
var selection: String? = null
@@ -96,7 +96,7 @@ class MediaStoreImageProvider : ImageProvider() {
var found = false
val fetched = arrayListOf()
val id = uri.tryParseId()
- val alwaysValid: NewEntryChecker = fun(_: Long, _: Int): Boolean = true
+ val alwaysValid: NewEntryChecker = fun(_: Long, _: Long): Boolean = true
val onSuccess: NewEntryHandler = fun(entry: FieldMap) { fetched.add(entry) }
if (id != null) {
if (sourceMimeType == null || isImage(sourceMimeType)) {
@@ -227,8 +227,8 @@ class MediaStoreImageProvider : ImageProvider() {
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE)
val widthColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.WIDTH)
val heightColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.HEIGHT)
- val dateAddedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_ADDED)
- val dateModifiedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)
+ val dateAddedSecsColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_ADDED)
+ val dateModifiedSecsColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)
val dateTakenColumn = cursor.getColumnIndex(MediaColumns.DATE_TAKEN)
// image & video for API >=29, only for images for API <29
@@ -240,8 +240,8 @@ class MediaStoreImageProvider : ImageProvider() {
while (cursor.moveToNext()) {
val id = cursor.getLong(idColumn)
- val dateModifiedSecs = cursor.getInt(dateModifiedColumn)
- if (isValidEntry(id, dateModifiedSecs)) {
+ val dateModifiedMillis = cursor.getInt(dateModifiedSecsColumn) * 1000L
+ if (isValidEntry(id, dateModifiedMillis)) {
// for multiple items, `contentUri` is the root without ID,
// but for single items, `contentUri` already contains the ID
val itemUri = if (contentUriContainsId) contentUri else ContentUris.withAppendedId(contentUri, id)
@@ -255,17 +255,18 @@ class MediaStoreImageProvider : ImageProvider() {
if (mimeType == null) {
Log.w(LOG_TAG, "failed to make entry from uri=$itemUri because of null MIME type")
} else {
- var entryMap: FieldMap = hashMapOf(
+ val path = cursor.getString(pathColumn)
+ var entryFields: FieldMap = hashMapOf(
EntryFields.ORIGIN to SourceEntry.ORIGIN_MEDIA_STORE_CONTENT,
EntryFields.URI to itemUri.toString(),
- EntryFields.PATH to cursor.getString(pathColumn),
+ EntryFields.PATH to path,
EntryFields.SOURCE_MIME_TYPE to mimeType,
EntryFields.WIDTH to width,
EntryFields.HEIGHT to height,
EntryFields.SOURCE_ROTATION_DEGREES to if (orientationColumn != -1) cursor.getInt(orientationColumn) else 0,
EntryFields.SIZE_BYTES to cursor.getLong(sizeColumn),
- EntryFields.DATE_ADDED_SECS to cursor.getInt(dateAddedColumn),
- EntryFields.DATE_MODIFIED_SECS to dateModifiedSecs,
+ EntryFields.DATE_ADDED_SECS to cursor.getInt(dateAddedSecsColumn),
+ EntryFields.DATE_MODIFIED_MILLIS to dateModifiedMillis,
EntryFields.SOURCE_DATE_TAKEN_MILLIS to if (dateTakenColumn != -1) cursor.getLong(dateTakenColumn) else null,
EntryFields.DURATION_MILLIS to durationMillis,
// only for map export
@@ -285,8 +286,8 @@ class MediaStoreImageProvider : ImageProvider() {
if (outWidth > 0 && outHeight > 0) {
width = outWidth
height = outHeight
- entryMap[EntryFields.WIDTH] = width
- entryMap[EntryFields.HEIGHT] = height
+ entryFields[EntryFields.WIDTH] = width
+ entryFields[EntryFields.HEIGHT] = height
}
}
} catch (e: IOException) {
@@ -302,11 +303,13 @@ class MediaStoreImageProvider : ImageProvider() {
// missing some attributes such as width, height, orientation.
// Also, the reported size of raw images is inconsistent across devices
// and Android versions (sometimes the raw size, sometimes the decoded size).
- val entry = SourceEntry(entryMap).fillPreCatalogMetadata(context)
- entryMap = entry.toMap()
+ val entry = SourceEntry(entryFields).fillPreCatalogMetadata(context)
+ entryFields = entry.toMap()
}
- handleNewEntry(entryMap)
+ getFileModifiedDateMillis(path)?.let { entryFields[EntryFields.DATE_MODIFIED_MILLIS] = it }
+
+ handleNewEntry(entryFields)
found = true
}
}
@@ -823,18 +826,32 @@ class MediaStoreImageProvider : ImageProvider() {
try {
val cursor = context.contentResolver.query(uri, projection, null, null, null)
if (cursor != null && cursor.moveToFirst()) {
- cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED).let { if (it != -1) newFields["dateModifiedSecs"] = cursor.getInt(it) }
- cursor.getColumnIndex(MediaStore.MediaColumns.SIZE).let { if (it != -1) newFields["sizeBytes"] = cursor.getLong(it) }
+ cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED).let { if (it != -1) newFields[EntryFields.DATE_MODIFIED_MILLIS] = cursor.getInt(it) * 1000 }
+ cursor.getColumnIndex(MediaStore.MediaColumns.SIZE).let { if (it != -1) newFields[EntryFields.SIZE_BYTES] = cursor.getLong(it) }
cursor.close()
}
} catch (e: Exception) {
callback.onFailure(e)
return@scanFile
}
+ getFileModifiedDateMillis(path)?.let { newFields[EntryFields.DATE_MODIFIED_MILLIS] = it }
callback.onSuccess(newFields)
}
}
+ // try to fetch the modified date from the file,
+ // as it is more precise than the one from the Media Store
+ private fun getFileModifiedDateMillis(path: String?): Long? {
+ if (path != null) {
+ try {
+ return File(path).lastModified()
+ } catch (securityException: SecurityException) {
+ // ignore
+ }
+ }
+ return null
+ }
+
private fun scanObsoletePath(context: Context, uri: Uri, path: String, mimeType: String) {
val file = File(path)
val delayMillis = 500L
@@ -918,8 +935,9 @@ class MediaStoreImageProvider : ImageProvider() {
EntryFields.PATH to path,
)
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_ADDED).let { if (it != -1) newFields[EntryFields.DATE_ADDED_SECS] = cursor.getInt(it) }
- cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED).let { if (it != -1) newFields[EntryFields.DATE_MODIFIED_SECS] = cursor.getInt(it) }
+ cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED).let { if (it != -1) newFields[EntryFields.DATE_MODIFIED_MILLIS] = cursor.getInt(it) * 1000 }
cursor.close()
+ getFileModifiedDateMillis(path)?.let { newFields[EntryFields.DATE_MODIFIED_MILLIS] = it }
return newFields
}
} catch (e: Exception) {
@@ -1030,4 +1048,4 @@ object MediaColumns {
typealias NewEntryHandler = (entry: FieldMap) -> Unit
-private typealias NewEntryChecker = (contentId: Long, dateModifiedSecs: Int) -> Boolean
\ No newline at end of file
+private typealias NewEntryChecker = (contentId: Long, dateModifiedMillis: Long) -> Boolean
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/BitmapUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/BitmapUtils.kt
index 95999d60e..212398a9b 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/BitmapUtils.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/BitmapUtils.kt
@@ -2,27 +2,121 @@ package deckers.thibault.aves.utils
import android.content.Context
import android.graphics.Bitmap
+import android.graphics.ColorSpace
+import android.os.Build
+import android.util.Half
import android.util.Log
+import androidx.annotation.RequiresApi
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.TransformationUtils
import deckers.thibault.aves.metadata.Metadata.getExifCode
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.io.ByteArrayOutputStream
+import java.nio.ByteBuffer
object BitmapUtils {
private val LOG_TAG = LogUtils.createTag()
private const val INITIAL_BUFFER_SIZE = 2 shl 17 // 256kB
- // arbitrary size to detect buffer that may yield an OOM
- private const val BUFFER_SIZE_DANGER_THRESHOLD = 3 * (1 shl 20) // MB
-
private val freeBaos = ArrayList()
private val mutex = Mutex()
- const val ARGB_8888_BYTE_SIZE = 4
+ private const val INT_BYTE_SIZE = 4
+ private const val MAX_2_BITS_FLOAT = 0x3.toFloat()
+ private const val MAX_8_BITS_FLOAT = 0xff.toFloat()
+ private const val MAX_10_BITS_FLOAT = 0x3ff.toFloat()
+
+ private const val RAW_BYTES_TRAILER_LENGTH = INT_BYTE_SIZE * 2
+
+ // bytes per pixel with different bitmap config
+ private const val BPP_ALPHA_8 = 1
+ private const val BPP_RGB_565 = 2
+ private const val BPP_ARGB_8888 = 4
+ private const val BPP_RGBA_1010102 = 4
+ private const val BPP_RGBA_F16 = 8
+
+ private fun getBytePerPixel(config: Bitmap.Config?): Int {
+ return when (config) {
+ Bitmap.Config.ALPHA_8 -> BPP_ALPHA_8
+ Bitmap.Config.RGB_565 -> BPP_RGB_565
+ Bitmap.Config.ARGB_8888 -> BPP_ARGB_8888
+ else -> {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && config == Bitmap.Config.RGBA_F16) {
+ BPP_RGBA_F16
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && config == Bitmap.Config.RGBA_1010102) {
+ BPP_RGBA_1010102
+ } else {
+ // default
+ BPP_ARGB_8888
+ }
+ }
+ }
+ }
+
+ fun getExpectedImageSize(pixelCount: Long, config: Bitmap.Config?): Long {
+ return pixelCount * getBytePerPixel(config)
+ }
+
+ fun getRawBytes(bitmap: Bitmap?, recycle: Boolean): ByteArray? {
+ bitmap ?: return null
+
+ val byteCount = bitmap.byteCount
+ val width = bitmap.width
+ val height = bitmap.height
+ val config = bitmap.config
+ val colorSpace = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) bitmap.colorSpace else null
+
+ if (!MemoryUtils.canAllocate(byteCount)) {
+ throw Exception("bitmap buffer is $byteCount bytes, which cannot be allocated to a new byte array")
+ }
+
+ try {
+ // `ByteBuffer` initial order is always `BIG_ENDIAN`
+ var bytes = ByteBuffer.allocate(byteCount + RAW_BYTES_TRAILER_LENGTH).apply {
+ bitmap.copyPixelsToBuffer(this)
+ }.array()
+
+ // do not access bitmap after recycling
+ if (recycle) bitmap.recycle()
+
+ // convert pixel format and color space, if necessary
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ colorSpace?.let { srcColorSpace ->
+ val dstColorSpace = ColorSpace.get(ColorSpace.Named.SRGB)
+ val connector = ColorSpace.connect(srcColorSpace, dstColorSpace)
+ if (config == Bitmap.Config.ARGB_8888) {
+ if (srcColorSpace != dstColorSpace) {
+ argb8888ToArgb8888(bytes, connector, end = byteCount)
+ }
+ } else if (config == Bitmap.Config.RGBA_F16) {
+ rgbaf16ToArgb8888(bytes, connector, end = byteCount)
+ val newConfigByteCount = byteCount / (BPP_RGBA_F16 / BPP_ARGB_8888)
+ bytes = bytes.sliceArray(0..= Build.VERSION_CODES.TIRAMISU && config == Bitmap.Config.RGBA_1010102) {
+ rgba1010102ToArgb8888(bytes, connector, end = byteCount)
+ }
+ }
+ }
+
+ // append bitmap size for use by the caller to interpret the raw bytes
+ val trailerOffset = bytes.size - RAW_BYTES_TRAILER_LENGTH
+ bytes = ByteBuffer.wrap(bytes).apply {
+ position(trailerOffset)
+ putInt(width)
+ putInt(height)
+ }.array()
+
+ return bytes
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "failed to get bytes from bitmap", e)
+ }
+ return null
+ }
+
+ suspend fun getEncodedBytes(bitmap: Bitmap?, canHaveAlpha: Boolean = false, quality: Int = 100, recycle: Boolean): ByteArray? {
+ bitmap ?: return null
- suspend fun Bitmap.getBytes(canHaveAlpha: Boolean = false, quality: Int = 100, recycle: Boolean): ByteArray? {
val stream: ByteArrayOutputStream
mutex.withLock {
// this method is called a lot, so we try and reuse output streams
@@ -34,19 +128,17 @@ object BitmapUtils {
}
}
try {
- // the Bitmap raw bytes are not decodable by Flutter
- // we need to format them (compress, or add a BMP header) before sending them
// `Bitmap.CompressFormat.PNG` is slower than `JPEG`, but it allows transparency
// the BMP format allows an alpha channel, but Android decoding seems to ignore it
- if (canHaveAlpha && hasAlpha()) {
- this.compress(Bitmap.CompressFormat.PNG, quality, stream)
+ if (canHaveAlpha && bitmap.hasAlpha()) {
+ bitmap.compress(Bitmap.CompressFormat.PNG, quality, stream)
} else {
- this.compress(Bitmap.CompressFormat.JPEG, quality, stream)
+ bitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream)
}
- if (recycle) this.recycle()
+ if (recycle) bitmap.recycle()
val bufferSize = stream.size()
- if (bufferSize > BUFFER_SIZE_DANGER_THRESHOLD && !MemoryUtils.canAllocate(bufferSize)) {
+ if (!MemoryUtils.canAllocate(bufferSize)) {
throw Exception("bitmap compressed to $bufferSize bytes, which cannot be allocated to a new byte array")
}
@@ -62,6 +154,107 @@ object BitmapUtils {
return null
}
+ // convert bytes, without reallocation:
+ // - from original color space to sRGB.
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun argb8888ToArgb8888(bytes: ByteArray, connector: ColorSpace.Connector, start: Int = 0, end: Int = bytes.size) {
+ // unpacking from ARGB_8888 and packing to ARGB_8888
+ // stored as [3,2,1,0] -> [AAAAAAAA BBBBBBBB GGGGGGGG RRRRRRRR]
+ for (i in start.. [AAAAAAAA AAAAAAAA BBBBBBBB BBBBBBBB GGGGGGGG GGGGGGGG RRRRRRRR RRRRRRRR]
+ val i7 = bytes[i + 7].toInt()
+ val i6 = bytes[i + 6].toInt()
+ val i5 = bytes[i + 5].toInt()
+ val i4 = bytes[i + 4].toInt()
+ val i3 = bytes[i + 3].toInt()
+ val i2 = bytes[i + 2].toInt()
+ val i1 = bytes[i + 1].toInt()
+ val i0 = bytes[i].toInt()
+
+ val hA = Half((((i7 and 0xff) shl 8) or (i6 and 0xff)).toShort())
+ val hB = Half((((i5 and 0xff) shl 8) or (i4 and 0xff)).toShort())
+ val hG = Half((((i3 and 0xff) shl 8) or (i2 and 0xff)).toShort())
+ val hR = Half((((i1 and 0xff) shl 8) or (i0 and 0xff)).toShort())
+
+ // components as floats in sRGB
+ val srgbFloats = connector.transform(hR.toFloat(), hG.toFloat(), hB.toFloat())
+ val srgbR = (srgbFloats[0] * 255.0f + 0.5f).toInt()
+ val srgbG = (srgbFloats[1] * 255.0f + 0.5f).toInt()
+ val srgbB = (srgbFloats[2] * 255.0f + 0.5f).toInt()
+ val alpha = (hA.toFloat() * 255.0f + 0.5f).toInt()
+
+ // packing to ARGB_8888
+ // stored as [3,2,1,0] -> [AAAAAAAA BBBBBBBB GGGGGGGG RRRRRRRR]
+ val dstI = i / indexDivider
+ bytes[dstI + 3] = alpha.toByte()
+ bytes[dstI + 2] = srgbB.toByte()
+ bytes[dstI + 1] = srgbG.toByte()
+ bytes[dstI] = srgbR.toByte()
+ }
+ }
+
+ // convert bytes, without reallocation:
+ // - from config RGBA_1010102 to ARGB_8888,
+ // - from original color space to sRGB.
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun rgba1010102ToArgb8888(bytes: ByteArray, connector: ColorSpace.Connector, start: Int = 0, end: Int = bytes.size) {
+ val alphaFactor = 255.0f / MAX_2_BITS_FLOAT
+
+ for (i in start.. [AABBBBBB BBBBGGGG GGGGGGRR RRRRRRRR]
+ val i3 = bytes[i + 3].toInt()
+ val i2 = bytes[i + 2].toInt()
+ val i1 = bytes[i + 1].toInt()
+ val i0 = bytes[i].toInt()
+
+ val iA = ((i3 and 0xc0) shr 6)
+ val iB = ((i3 and 0x3f) shl 4) or ((i2 and 0xf0) shr 4)
+ val iG = ((i2 and 0x0f) shl 6) or ((i1 and 0xfc) shr 2)
+ val iR = ((i1 and 0x03) shl 8) or ((i0 and 0xff) shr 0)
+
+ // components as floats in sRGB
+ val srgbFloats = connector.transform(iR / MAX_10_BITS_FLOAT, iG / MAX_10_BITS_FLOAT, iB / MAX_10_BITS_FLOAT)
+ val srgbR = (srgbFloats[0] * 255.0f + 0.5f).toInt()
+ val srgbG = (srgbFloats[1] * 255.0f + 0.5f).toInt()
+ val srgbB = (srgbFloats[2] * 255.0f + 0.5f).toInt()
+ val alpha = (iA * alphaFactor + 0.5f).toInt()
+
+ // packing to ARGB_8888
+ // stored as [3,2,1,0] -> [AAAAAAAA BBBBBBBB GGGGGGGG RRRRRRRR]
+ bytes[i + 3] = alpha.toByte()
+ bytes[i + 2] = srgbB.toByte()
+ bytes[i + 1] = srgbG.toByte()
+ bytes[i] = srgbR.toByte()
+ }
+ }
+
fun applyExifOrientation(context: Context, bitmap: Bitmap?, rotationDegrees: Int?, isFlipped: Boolean?): Bitmap? {
if (bitmap == null || rotationDegrees == null || isFlipped == null) return bitmap
if (rotationDegrees == 0 && !isFlipped) return bitmap
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/BmpWriter.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/BmpWriter.kt
index 8e097c77b..bfd9947af 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/BmpWriter.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/BmpWriter.kt
@@ -90,12 +90,7 @@ object BmpWriter {
var column = 0
while (column < biWidth) {
- /*
- alpha: (value shr 24 and 0xFF).toByte()
- red: (value shr 16 and 0xFF).toByte()
- green: (value shr 8 and 0xFF).toByte()
- blue: (value and 0xFF).toByte()
- */
+ // non-premultiplied ARGB values in the sRGB color space
value = pixels[column]
// blue: [0], green: [1], red: [2]
rgb[0] = (value and 0xFF).toByte()
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/ByteUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/ByteUtils.kt
index f45236ba4..a0e1e834f 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/ByteUtils.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/ByteUtils.kt
@@ -8,6 +8,8 @@ fun ByteBuffer.toByteArray(): ByteArray {
return bytes
}
+fun Int.toHex(): String = "0x${byteArrayOf(shr(8).toByte(), toByte()).toHex()}"
+
fun ByteArray.toHex(): String = joinToString(separator = "") { it.toHex() }
fun Byte.toHex(): String = "%02x".format(this)
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/CollectionUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/CollectionUtils.kt
index 55ed6ce6c..a2af9f418 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/CollectionUtils.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/CollectionUtils.kt
@@ -20,6 +20,7 @@ fun MutableList.compatRemoveIf(filter: (t: E) -> Boolean): Boolean {
}
// Boyer-Moore algorithm for pattern searching
+// Returns: an index of the first occurrence of the pattern or -1 if none is found.
fun ByteArray.indexOfBytes(pattern: ByteArray, start: Int = 0): Int {
val n: Int = this.size
val m: Int = pattern.size
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt
index b7f236f33..c0bef41c9 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt
@@ -84,11 +84,11 @@ object MimeTypes {
else -> false
}
- // as of Flutter v3.16.4, with additional custom handling for SVG
- fun canDecodeWithFlutter(mimeType: String, pageId: Int?, rotationDegrees: Int?, isFlipped: Boolean?) = when (mimeType) {
+ // as of Flutter v3.16.4, with additional custom handling for SVG in Dart,
+ // while handling still PNG and JPEG on Android for color space and config conversion
+ fun canDecodeWithFlutter(mimeType: String, isAnimated: Boolean) = when (mimeType) {
GIF, WEBP, BMP, WBMP, ICO, SVG -> true
- JPEG -> (pageId ?: 0) == 0
- PNG -> (rotationDegrees ?: 0) == 0 && !(isFlipped ?: false)
+ JPEG, PNG -> isAnimated
else -> false
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt
index 2aff05cea..b43c2c8f0 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt
@@ -15,6 +15,8 @@ import android.provider.DocumentsContract
import android.provider.MediaStore
import android.text.TextUtils
import android.util.Log
+import androidx.core.net.toUri
+import androidx.core.text.isDigitsOnly
import com.commonsware.cwac.document.DocumentFileCompat
import deckers.thibault.aves.model.provider.ImageProvider
import deckers.thibault.aves.utils.FileUtils.transferFrom
@@ -29,8 +31,6 @@ import java.io.InputStream
import java.io.OutputStream
import java.util.Locale
import java.util.regex.Pattern
-import androidx.core.net.toUri
-import androidx.core.text.isDigitsOnly
object StorageUtils {
private val LOG_TAG = LogUtils.createTag()
diff --git a/android/app/src/main/res/values-ca/strings.xml b/android/app/src/main/res/values-ca/strings.xml
index 6fca66146..1146f3a1b 100644
--- a/android/app/src/main/res/values-ca/strings.xml
+++ b/android/app/src/main/res/values-ca/strings.xml
@@ -8,4 +8,5 @@
Exploració de mitjans
Explorant mitjans
Atura
-
\ No newline at end of file
+ Mapa
+
diff --git a/android/app/src/main/res/values-cs/strings.xml b/android/app/src/main/res/values-cs/strings.xml
index 0c4e432fe..af729a9a3 100644
--- a/android/app/src/main/res/values-cs/strings.xml
+++ b/android/app/src/main/res/values-cs/strings.xml
@@ -8,4 +8,5 @@
Prohledávání médií
Zastavit
Fotorámeček
-
\ No newline at end of file
+ Mapa
+
diff --git a/android/app/src/main/res/values-gl/strings.xml b/android/app/src/main/res/values-gl/strings.xml
index 1049c3728..a90ab7c98 100644
--- a/android/app/src/main/res/values-gl/strings.xml
+++ b/android/app/src/main/res/values-gl/strings.xml
@@ -5,7 +5,8 @@
Fondo da pantalla
Procura
Vídeos
- Escaneo multimedia
+ Escanear medios
Escaneando medios
Pare
-
\ No newline at end of file
+ Mapa
+
diff --git a/android/app/src/main/res/values-is/strings.xml b/android/app/src/main/res/values-is/strings.xml
index 915be23e5..d188a9d06 100644
--- a/android/app/src/main/res/values-is/strings.xml
+++ b/android/app/src/main/res/values-is/strings.xml
@@ -9,4 +9,4 @@
Skönnun myndefnis
Leita
Landakort
-
\ No newline at end of file
+
diff --git a/android/app/src/main/res/values-ja/strings.xml b/android/app/src/main/res/values-ja/strings.xml
index 1e2a114ed..cf16df98b 100644
--- a/android/app/src/main/res/values-ja/strings.xml
+++ b/android/app/src/main/res/values-ja/strings.xml
@@ -9,4 +9,4 @@
メディアをスキャン中
停止
マップ
-
\ No newline at end of file
+
diff --git a/android/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterfaceFork.java b/android/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterfaceFork.java
index 2f0f4566a..7643c4193 100644
--- a/android/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterfaceFork.java
+++ b/android/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterfaceFork.java
@@ -91,7 +91,7 @@ import java.util.regex.Pattern;
import java.util.zip.CRC32;
/*
- * Forked from 'androidx.exifinterface:exifinterface:1.4.0-beta01' on 2025/01/21
+ * Forked from 'androidx.exifinterface:exifinterface:1.4.0'
* Named differently to let ExifInterface be loaded as subdependency.
* cf https://maven.google.com/web/index.html?q=exifinterface#androidx.exifinterface:exifinterface
* cf https://github.com/androidx/androidx/tree/androidx-main/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media
diff --git a/android/gradle.properties b/android/gradle.properties
index 68b10e0d2..b0e38d4be 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,21 +1,10 @@
-# Project-wide Gradle settings.
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx4G -Dfile.encoding=UTF-8
+org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
-android.nonTransitiveRClass=false
-android.nonFinalResIds=false
-
# full mode is too aggressive and removes essential code
# of `metadata-extractor` even when adding `-keep class com.drew.**{ *; }`
android.enableR8.fullMode=false
diff --git a/android/settings.gradle b/android/settings.gradle
deleted file mode 100644
index 6643a6d45..000000000
--- a/android/settings.gradle
+++ /dev/null
@@ -1,33 +0,0 @@
-pluginManagement {
- def flutterSdkPath = {
- def properties = new Properties()
- file("local.properties").withInputStream { properties.load(it) }
- def flutterSdkPath = properties.getProperty("flutter.sdk")
- assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
- return flutterSdkPath
- }
- settings.ext.flutterSdkPath = flutterSdkPath()
-
- settings.ext.kotlin_version = '2.1.10'
- settings.ext.ksp_version = "$kotlin_version-1.0.29"
- settings.ext.agp_version = '8.8.0'
-
- includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
-
- repositories {
- google()
- mavenCentral()
- gradlePluginPortal()
- }
-}
-
-plugins {
- id("dev.flutter.flutter-plugin-loader") version("1.0.0")
- id("com.android.application") version("$agp_version") apply(false)
- id("org.jetbrains.kotlin.android") version("$kotlin_version") apply(false)
- id("com.google.devtools.ksp") version("$ksp_version") apply(false)
- id("org.gradle.toolchains.foojay-resolver-convention") version("0.4.0")
-}
-
-include(":app")
-include(":exifinterface")
diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts
new file mode 100644
index 000000000..89d573443
--- /dev/null
+++ b/android/settings.gradle.kts
@@ -0,0 +1,28 @@
+pluginManagement {
+ val flutterSdkPath = run {
+ val properties = java.util.Properties()
+ file("local.properties").inputStream().use { properties.load(it) }
+ val flutterSdkPath = properties.getProperty("flutter.sdk")
+ require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
+ flutterSdkPath
+ }
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id("dev.flutter.flutter-plugin-loader") version "1.0.0"
+ id("com.android.application") version "8.8.1" apply false
+ id("org.jetbrains.kotlin.android") version "2.1.10" apply false
+ id("com.google.devtools.ksp") version "2.1.10-1.0.29" apply false
+ id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
+}
+
+include(":app")
+include(":exifinterface")
diff --git a/fastlane/metadata/android/ar/images/phoneScreenshots/1.png b/fastlane/metadata/android/ar/images/phoneScreenshots/1.png
index c53c38202..0ea4a68d0 100644
Binary files a/fastlane/metadata/android/ar/images/phoneScreenshots/1.png and b/fastlane/metadata/android/ar/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/ar/images/phoneScreenshots/2.png b/fastlane/metadata/android/ar/images/phoneScreenshots/2.png
index b18c82d1e..7a112f059 100644
Binary files a/fastlane/metadata/android/ar/images/phoneScreenshots/2.png and b/fastlane/metadata/android/ar/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/ar/images/phoneScreenshots/3.png b/fastlane/metadata/android/ar/images/phoneScreenshots/3.png
index 50a8c7a8d..dca23f7b7 100644
Binary files a/fastlane/metadata/android/ar/images/phoneScreenshots/3.png and b/fastlane/metadata/android/ar/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/ar/images/phoneScreenshots/4.png b/fastlane/metadata/android/ar/images/phoneScreenshots/4.png
index 9cb4a4ed2..aa29bf400 100644
Binary files a/fastlane/metadata/android/ar/images/phoneScreenshots/4.png and b/fastlane/metadata/android/ar/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/ar/images/phoneScreenshots/5.png b/fastlane/metadata/android/ar/images/phoneScreenshots/5.png
index bf90202cd..fd8b18112 100644
Binary files a/fastlane/metadata/android/ar/images/phoneScreenshots/5.png and b/fastlane/metadata/android/ar/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/ar/images/phoneScreenshots/6.png b/fastlane/metadata/android/ar/images/phoneScreenshots/6.png
index e46abd530..107964a4b 100644
Binary files a/fastlane/metadata/android/ar/images/phoneScreenshots/6.png and b/fastlane/metadata/android/ar/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/ar/images/phoneScreenshots/7.png b/fastlane/metadata/android/ar/images/phoneScreenshots/7.png
index 07d4603b9..c65cf3a65 100644
Binary files a/fastlane/metadata/android/ar/images/phoneScreenshots/7.png and b/fastlane/metadata/android/ar/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/be/images/phoneScreenshots/1.png b/fastlane/metadata/android/be/images/phoneScreenshots/1.png
index 6b176ae33..fe2638e21 100644
Binary files a/fastlane/metadata/android/be/images/phoneScreenshots/1.png and b/fastlane/metadata/android/be/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/be/images/phoneScreenshots/2.png b/fastlane/metadata/android/be/images/phoneScreenshots/2.png
index d22c0a2c9..ae7a968b3 100644
Binary files a/fastlane/metadata/android/be/images/phoneScreenshots/2.png and b/fastlane/metadata/android/be/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/be/images/phoneScreenshots/3.png b/fastlane/metadata/android/be/images/phoneScreenshots/3.png
index 7ebc7d837..ebccc8673 100644
Binary files a/fastlane/metadata/android/be/images/phoneScreenshots/3.png and b/fastlane/metadata/android/be/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/be/images/phoneScreenshots/4.png b/fastlane/metadata/android/be/images/phoneScreenshots/4.png
index cfa4a9727..1b3bc0fcf 100644
Binary files a/fastlane/metadata/android/be/images/phoneScreenshots/4.png and b/fastlane/metadata/android/be/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/be/images/phoneScreenshots/5.png b/fastlane/metadata/android/be/images/phoneScreenshots/5.png
index fc8b1c9bb..100e20703 100644
Binary files a/fastlane/metadata/android/be/images/phoneScreenshots/5.png and b/fastlane/metadata/android/be/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/be/images/phoneScreenshots/6.png b/fastlane/metadata/android/be/images/phoneScreenshots/6.png
index 0567e896e..6139cf7d9 100644
Binary files a/fastlane/metadata/android/be/images/phoneScreenshots/6.png and b/fastlane/metadata/android/be/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/be/images/phoneScreenshots/7.png b/fastlane/metadata/android/be/images/phoneScreenshots/7.png
index e440673f5..736c76022 100644
Binary files a/fastlane/metadata/android/be/images/phoneScreenshots/7.png and b/fastlane/metadata/android/be/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/bg/images/phoneScreenshots/1.png b/fastlane/metadata/android/bg/images/phoneScreenshots/1.png
index 3ce322072..de2ac385a 100644
Binary files a/fastlane/metadata/android/bg/images/phoneScreenshots/1.png and b/fastlane/metadata/android/bg/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/bg/images/phoneScreenshots/2.png b/fastlane/metadata/android/bg/images/phoneScreenshots/2.png
index 7f94da64b..78ffdd8c9 100644
Binary files a/fastlane/metadata/android/bg/images/phoneScreenshots/2.png and b/fastlane/metadata/android/bg/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/bg/images/phoneScreenshots/3.png b/fastlane/metadata/android/bg/images/phoneScreenshots/3.png
index 551cf3a4b..e33481f9a 100644
Binary files a/fastlane/metadata/android/bg/images/phoneScreenshots/3.png and b/fastlane/metadata/android/bg/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/bg/images/phoneScreenshots/4.png b/fastlane/metadata/android/bg/images/phoneScreenshots/4.png
index 4a1a13500..8ccd61bd9 100644
Binary files a/fastlane/metadata/android/bg/images/phoneScreenshots/4.png and b/fastlane/metadata/android/bg/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/bg/images/phoneScreenshots/5.png b/fastlane/metadata/android/bg/images/phoneScreenshots/5.png
index 1ddde86df..b9040a646 100644
Binary files a/fastlane/metadata/android/bg/images/phoneScreenshots/5.png and b/fastlane/metadata/android/bg/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/bg/images/phoneScreenshots/6.png b/fastlane/metadata/android/bg/images/phoneScreenshots/6.png
index 631ba4ded..65962dfd9 100644
Binary files a/fastlane/metadata/android/bg/images/phoneScreenshots/6.png and b/fastlane/metadata/android/bg/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/bg/images/phoneScreenshots/7.png b/fastlane/metadata/android/bg/images/phoneScreenshots/7.png
index 8f468cd9d..23a2f1677 100644
Binary files a/fastlane/metadata/android/bg/images/phoneScreenshots/7.png and b/fastlane/metadata/android/bg/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/ca/full_description.txt b/fastlane/metadata/android/ca/full_description.txt
index 23a3d6235..4601c5bdf 100644
--- a/fastlane/metadata/android/ca/full_description.txt
+++ b/fastlane/metadata/android/ca/full_description.txt
@@ -1,5 +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.
+Aves pot gestionar tot tipus d'imatges i vídeos, inclosos els típics JPEG i MP4, però també coses més exòtiques com multi-pàgina TIFFs, SVG, AVI antics i més! Escaneja la col·lecció multimèdia per identificar fotos en moviment, panoràmiques(àlies esferes de fotos), vídeos 360., així com fitxers GeoTIFF.
-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.
+Navegació i cerca és una part important de Aves. L'objectiu és que els usuaris puguin fluir fàcilment d'àlbums a fotos, etiquetes a mapes, etc.
-Aves integrates with Android (including Android TV) with features such as widgets, app shortcuts, screen saver and global search handling. It also works as a media viewer and picker.
+Aves s'integra amb Android (incloent Android TV) amb funcions com ginys, dreceres d'aplicació, estalvi de pantallai gestió de la cerca global. També funciona com a visor i selector multimèdia.
diff --git a/fastlane/metadata/android/ca/images/phoneScreenshots/1.png b/fastlane/metadata/android/ca/images/phoneScreenshots/1.png
index ae462818a..6c453d2d9 100644
Binary files a/fastlane/metadata/android/ca/images/phoneScreenshots/1.png and b/fastlane/metadata/android/ca/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/ca/images/phoneScreenshots/2.png b/fastlane/metadata/android/ca/images/phoneScreenshots/2.png
index 78175c595..9afb5d025 100644
Binary files a/fastlane/metadata/android/ca/images/phoneScreenshots/2.png and b/fastlane/metadata/android/ca/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/ca/images/phoneScreenshots/3.png b/fastlane/metadata/android/ca/images/phoneScreenshots/3.png
index 7e38caa67..29ea29055 100644
Binary files a/fastlane/metadata/android/ca/images/phoneScreenshots/3.png and b/fastlane/metadata/android/ca/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/ca/images/phoneScreenshots/4.png b/fastlane/metadata/android/ca/images/phoneScreenshots/4.png
index 494b4c6de..7b5dd1f68 100644
Binary files a/fastlane/metadata/android/ca/images/phoneScreenshots/4.png and b/fastlane/metadata/android/ca/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/ca/images/phoneScreenshots/5.png b/fastlane/metadata/android/ca/images/phoneScreenshots/5.png
index 4e5ca98f5..04dc226de 100644
Binary files a/fastlane/metadata/android/ca/images/phoneScreenshots/5.png and b/fastlane/metadata/android/ca/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/ca/images/phoneScreenshots/6.png b/fastlane/metadata/android/ca/images/phoneScreenshots/6.png
index 95bd8f9e5..f41d9d30f 100644
Binary files a/fastlane/metadata/android/ca/images/phoneScreenshots/6.png and b/fastlane/metadata/android/ca/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/ca/images/phoneScreenshots/7.png b/fastlane/metadata/android/ca/images/phoneScreenshots/7.png
index 60adcc959..264594868 100644
Binary files a/fastlane/metadata/android/ca/images/phoneScreenshots/7.png and b/fastlane/metadata/android/ca/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/ca/short_description.txt b/fastlane/metadata/android/ca/short_description.txt
index 8c9445bd5..88082cbdb 100644
--- a/fastlane/metadata/android/ca/short_description.txt
+++ b/fastlane/metadata/android/ca/short_description.txt
@@ -1 +1 @@
-Gallery and metadata explorer
\ No newline at end of file
+Galeria i explorador de metadades
\ No newline at end of file
diff --git a/fastlane/metadata/android/cs/images/phoneScreenshots/1.png b/fastlane/metadata/android/cs/images/phoneScreenshots/1.png
index 472bcd0b3..dc2ae74a1 100644
Binary files a/fastlane/metadata/android/cs/images/phoneScreenshots/1.png and b/fastlane/metadata/android/cs/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/cs/images/phoneScreenshots/2.png b/fastlane/metadata/android/cs/images/phoneScreenshots/2.png
index b1e1fbb7e..4e3c0056d 100644
Binary files a/fastlane/metadata/android/cs/images/phoneScreenshots/2.png and b/fastlane/metadata/android/cs/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/cs/images/phoneScreenshots/3.png b/fastlane/metadata/android/cs/images/phoneScreenshots/3.png
index 51d5c294b..543729f07 100644
Binary files a/fastlane/metadata/android/cs/images/phoneScreenshots/3.png and b/fastlane/metadata/android/cs/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/cs/images/phoneScreenshots/4.png b/fastlane/metadata/android/cs/images/phoneScreenshots/4.png
index a7ceeb655..2dadb1b55 100644
Binary files a/fastlane/metadata/android/cs/images/phoneScreenshots/4.png and b/fastlane/metadata/android/cs/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/cs/images/phoneScreenshots/5.png b/fastlane/metadata/android/cs/images/phoneScreenshots/5.png
index f65ab3a1a..ed50e956a 100644
Binary files a/fastlane/metadata/android/cs/images/phoneScreenshots/5.png and b/fastlane/metadata/android/cs/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/cs/images/phoneScreenshots/6.png b/fastlane/metadata/android/cs/images/phoneScreenshots/6.png
index 92f708e30..1508e9167 100644
Binary files a/fastlane/metadata/android/cs/images/phoneScreenshots/6.png and b/fastlane/metadata/android/cs/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/cs/images/phoneScreenshots/7.png b/fastlane/metadata/android/cs/images/phoneScreenshots/7.png
index 9844a00a5..f46995997 100644
Binary files a/fastlane/metadata/android/cs/images/phoneScreenshots/7.png and b/fastlane/metadata/android/cs/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/da/images/phoneScreenshots/1.png b/fastlane/metadata/android/da/images/phoneScreenshots/1.png
index a50c34388..dd1b6053a 100644
Binary files a/fastlane/metadata/android/da/images/phoneScreenshots/1.png and b/fastlane/metadata/android/da/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/da/images/phoneScreenshots/2.png b/fastlane/metadata/android/da/images/phoneScreenshots/2.png
index b7aa1ab3f..6b1b56ece 100644
Binary files a/fastlane/metadata/android/da/images/phoneScreenshots/2.png and b/fastlane/metadata/android/da/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/da/images/phoneScreenshots/3.png b/fastlane/metadata/android/da/images/phoneScreenshots/3.png
index 35337ada2..69f825a22 100644
Binary files a/fastlane/metadata/android/da/images/phoneScreenshots/3.png and b/fastlane/metadata/android/da/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/da/images/phoneScreenshots/4.png b/fastlane/metadata/android/da/images/phoneScreenshots/4.png
index 6adfc9ef3..42dde7800 100644
Binary files a/fastlane/metadata/android/da/images/phoneScreenshots/4.png and b/fastlane/metadata/android/da/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/da/images/phoneScreenshots/5.png b/fastlane/metadata/android/da/images/phoneScreenshots/5.png
index 47d254adb..0bf016525 100644
Binary files a/fastlane/metadata/android/da/images/phoneScreenshots/5.png and b/fastlane/metadata/android/da/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/da/images/phoneScreenshots/6.png b/fastlane/metadata/android/da/images/phoneScreenshots/6.png
index 751b6a517..5b9ad96db 100644
Binary files a/fastlane/metadata/android/da/images/phoneScreenshots/6.png and b/fastlane/metadata/android/da/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/da/images/phoneScreenshots/7.png b/fastlane/metadata/android/da/images/phoneScreenshots/7.png
index 209f9608f..e6a9f85b0 100644
Binary files a/fastlane/metadata/android/da/images/phoneScreenshots/7.png and b/fastlane/metadata/android/da/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/1.png b/fastlane/metadata/android/de/images/phoneScreenshots/1.png
index dee328b8b..a62d3ab48 100644
Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/1.png and b/fastlane/metadata/android/de/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/2.png b/fastlane/metadata/android/de/images/phoneScreenshots/2.png
index f40409037..2f13cff57 100644
Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/2.png and b/fastlane/metadata/android/de/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/3.png b/fastlane/metadata/android/de/images/phoneScreenshots/3.png
index 0ea3e4fd4..fd47a587b 100644
Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/3.png and b/fastlane/metadata/android/de/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/4.png b/fastlane/metadata/android/de/images/phoneScreenshots/4.png
index d0351b9fe..9804028ae 100644
Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/4.png and b/fastlane/metadata/android/de/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/5.png b/fastlane/metadata/android/de/images/phoneScreenshots/5.png
index 06b031df0..2e6b2c653 100644
Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/5.png and b/fastlane/metadata/android/de/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/6.png b/fastlane/metadata/android/de/images/phoneScreenshots/6.png
index ca7c88fde..c4c844e50 100644
Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/6.png and b/fastlane/metadata/android/de/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/7.png b/fastlane/metadata/android/de/images/phoneScreenshots/7.png
index ff3a19e7a..3689bbbbe 100644
Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/7.png and b/fastlane/metadata/android/de/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/el/images/phoneScreenshots/1.png b/fastlane/metadata/android/el/images/phoneScreenshots/1.png
index f0722cbab..170e53786 100644
Binary files a/fastlane/metadata/android/el/images/phoneScreenshots/1.png and b/fastlane/metadata/android/el/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/el/images/phoneScreenshots/2.png b/fastlane/metadata/android/el/images/phoneScreenshots/2.png
index 215036deb..ba99a0b53 100644
Binary files a/fastlane/metadata/android/el/images/phoneScreenshots/2.png and b/fastlane/metadata/android/el/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/el/images/phoneScreenshots/3.png b/fastlane/metadata/android/el/images/phoneScreenshots/3.png
index 74d97a7d9..7a7e1472d 100644
Binary files a/fastlane/metadata/android/el/images/phoneScreenshots/3.png and b/fastlane/metadata/android/el/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/el/images/phoneScreenshots/4.png b/fastlane/metadata/android/el/images/phoneScreenshots/4.png
index 21c1e62d2..2278d11dd 100644
Binary files a/fastlane/metadata/android/el/images/phoneScreenshots/4.png and b/fastlane/metadata/android/el/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/el/images/phoneScreenshots/5.png b/fastlane/metadata/android/el/images/phoneScreenshots/5.png
index f767b3be2..f949ffc1c 100644
Binary files a/fastlane/metadata/android/el/images/phoneScreenshots/5.png and b/fastlane/metadata/android/el/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/el/images/phoneScreenshots/6.png b/fastlane/metadata/android/el/images/phoneScreenshots/6.png
index 9bcee393e..0519a0eb6 100644
Binary files a/fastlane/metadata/android/el/images/phoneScreenshots/6.png and b/fastlane/metadata/android/el/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/el/images/phoneScreenshots/7.png b/fastlane/metadata/android/el/images/phoneScreenshots/7.png
index 931e6370d..9922f106e 100644
Binary files a/fastlane/metadata/android/el/images/phoneScreenshots/7.png and b/fastlane/metadata/android/el/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/en-US/changelogs/135.txt b/fastlane/metadata/android/en-US/changelogs/135.txt
deleted file mode 100644
index 3d9256a35..000000000
--- a/fastlane/metadata/android/en-US/changelogs/135.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.11.16:
-- enjoy new map layers
-- share "geo" addresses to Aves and see your collection in that area
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/13501.txt b/fastlane/metadata/android/en-US/changelogs/13501.txt
deleted file mode 100644
index 3d9256a35..000000000
--- a/fastlane/metadata/android/en-US/changelogs/13501.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.11.16:
-- enjoy new map layers
-- share "geo" addresses to Aves and see your collection in that area
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/136.txt b/fastlane/metadata/android/en-US/changelogs/136.txt
deleted file mode 100644
index 10f3ff7c4..000000000
--- a/fastlane/metadata/android/en-US/changelogs/136.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.11.17:
-- peruse your videos frame by frame
-- create map shortcuts to filtered collections
-- enjoy the app in Shavian
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/13601.txt b/fastlane/metadata/android/en-US/changelogs/13601.txt
deleted file mode 100644
index 10f3ff7c4..000000000
--- a/fastlane/metadata/android/en-US/changelogs/13601.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.11.17:
-- peruse your videos frame by frame
-- create map shortcuts to filtered collections
-- enjoy the app in Shavian
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/137.txt b/fastlane/metadata/android/en-US/changelogs/137.txt
deleted file mode 100644
index 444e6cae9..000000000
--- a/fastlane/metadata/android/en-US/changelogs/137.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.11.18:
-- peruse your videos frame by frame
-- create map shortcuts to filtered collections
-- enjoy the app in Shavian
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/13701.txt b/fastlane/metadata/android/en-US/changelogs/13701.txt
deleted file mode 100644
index 444e6cae9..000000000
--- a/fastlane/metadata/android/en-US/changelogs/13701.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.11.18:
-- peruse your videos frame by frame
-- create map shortcuts to filtered collections
-- enjoy the app in Shavian
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/138.txt b/fastlane/metadata/android/en-US/changelogs/138.txt
deleted file mode 100644
index e405b5830..000000000
--- a/fastlane/metadata/android/en-US/changelogs/138.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.11.19:
-- peruse your videos frame by frame
-- create map shortcuts to filtered collections
-- enjoy the app in Shavian
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/13801.txt b/fastlane/metadata/android/en-US/changelogs/13801.txt
deleted file mode 100644
index e405b5830..000000000
--- a/fastlane/metadata/android/en-US/changelogs/13801.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.11.19:
-- peruse your videos frame by frame
-- create map shortcuts to filtered collections
-- enjoy the app in Shavian
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/139.txt b/fastlane/metadata/android/en-US/changelogs/139.txt
deleted file mode 100644
index 00cd62158..000000000
--- a/fastlane/metadata/android/en-US/changelogs/139.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.11.20:
-- save your filtered collection as dynamic albums
-- enjoy the app in Tamil and Bulgarian
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/13901.txt b/fastlane/metadata/android/en-US/changelogs/13901.txt
deleted file mode 100644
index 00cd62158..000000000
--- a/fastlane/metadata/android/en-US/changelogs/13901.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.11.20:
-- save your filtered collection as dynamic albums
-- enjoy the app in Tamil and Bulgarian
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/144.txt b/fastlane/metadata/android/en-US/changelogs/144.txt
new file mode 100644
index 000000000..cbdd279bc
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/144.txt
@@ -0,0 +1,4 @@
+In v1.12.4:
+- play more kinds of motion photos
+- enjoy the app in Galician
+Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/14401.txt b/fastlane/metadata/android/en-US/changelogs/14401.txt
new file mode 100644
index 000000000..cbdd279bc
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/14401.txt
@@ -0,0 +1,4 @@
+In v1.12.4:
+- play more kinds of motion photos
+- enjoy the app in Galician
+Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
index ca7edfc76..b35cbb67d 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png 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
index 37f468cc8..4f0a9af9e 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png 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
index f867bbba5..ac5d0b710 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png 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
index 094a087d7..c429f8ab9 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png 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
index 5709828e3..dd7cb20aa 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png 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
index 0e2e20374..4f1052e73 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png
index 0eb452dc8..2d763d6e6 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/1.png
index 4970dc5fa..60f84b666 100644
Binary files a/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/1.png and b/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/2.png
index 4a1e4b281..6800dfede 100644
Binary files a/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/2.png and b/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/3.png b/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/3.png
index 942667170..1d8b4026c 100644
Binary files a/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/3.png and b/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/4.png b/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/4.png
index d8a82ad50..92b6075ef 100644
Binary files a/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/4.png and b/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/5.png b/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/5.png
index f4c597fca..a86289750 100644
Binary files a/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/5.png and b/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/6.png b/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/6.png
index 7db91fac3..33c147f31 100644
Binary files a/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/6.png and b/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/7.png b/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/7.png
index 636ae49e7..6bbe64f8b 100644
Binary files a/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/7.png and b/fastlane/metadata/android/en-XW-Shaw/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/1.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/1.png
index 72009ab1b..c6f34501f 100644
Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/1.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/2.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/2.png
index 61afd897b..cb2f98bf7 100644
Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/2.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/3.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/3.png
index 69ff37cda..fb5ae75b4 100644
Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/3.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/4.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/4.png
index 6491428e5..39164dc13 100644
Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/4.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/5.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/5.png
index 3d00d4077..9944c20e3 100644
Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/5.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/6.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/6.png
index f45ac6185..08cf9464c 100644
Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/6.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/7.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/7.png
index e8a280a16..b56ebd46c 100644
Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/7.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/et/images/phoneScreenshots/1.png b/fastlane/metadata/android/et/images/phoneScreenshots/1.png
index cdb408094..91e46b8c6 100644
Binary files a/fastlane/metadata/android/et/images/phoneScreenshots/1.png and b/fastlane/metadata/android/et/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/et/images/phoneScreenshots/2.png b/fastlane/metadata/android/et/images/phoneScreenshots/2.png
index e9b0da673..2a9b2fd0b 100644
Binary files a/fastlane/metadata/android/et/images/phoneScreenshots/2.png and b/fastlane/metadata/android/et/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/et/images/phoneScreenshots/3.png b/fastlane/metadata/android/et/images/phoneScreenshots/3.png
index 61009e0a1..71246d2f5 100644
Binary files a/fastlane/metadata/android/et/images/phoneScreenshots/3.png and b/fastlane/metadata/android/et/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/et/images/phoneScreenshots/4.png b/fastlane/metadata/android/et/images/phoneScreenshots/4.png
index a2020e1c5..e72231682 100644
Binary files a/fastlane/metadata/android/et/images/phoneScreenshots/4.png and b/fastlane/metadata/android/et/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/et/images/phoneScreenshots/5.png b/fastlane/metadata/android/et/images/phoneScreenshots/5.png
index 370631de2..35b3c3867 100644
Binary files a/fastlane/metadata/android/et/images/phoneScreenshots/5.png and b/fastlane/metadata/android/et/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/et/images/phoneScreenshots/6.png b/fastlane/metadata/android/et/images/phoneScreenshots/6.png
index 9354af38d..543f42a85 100644
Binary files a/fastlane/metadata/android/et/images/phoneScreenshots/6.png and b/fastlane/metadata/android/et/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/et/images/phoneScreenshots/7.png b/fastlane/metadata/android/et/images/phoneScreenshots/7.png
index 896863d5b..ba4d16e78 100644
Binary files a/fastlane/metadata/android/et/images/phoneScreenshots/7.png and b/fastlane/metadata/android/et/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/eu/images/phoneScreenshots/1.png b/fastlane/metadata/android/eu/images/phoneScreenshots/1.png
index c9b016361..a4a7b430c 100644
Binary files a/fastlane/metadata/android/eu/images/phoneScreenshots/1.png and b/fastlane/metadata/android/eu/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/eu/images/phoneScreenshots/2.png b/fastlane/metadata/android/eu/images/phoneScreenshots/2.png
index 4c701a4c6..3a4016fcc 100644
Binary files a/fastlane/metadata/android/eu/images/phoneScreenshots/2.png and b/fastlane/metadata/android/eu/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/eu/images/phoneScreenshots/3.png b/fastlane/metadata/android/eu/images/phoneScreenshots/3.png
index c7cb9c666..43d9613d8 100644
Binary files a/fastlane/metadata/android/eu/images/phoneScreenshots/3.png and b/fastlane/metadata/android/eu/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/eu/images/phoneScreenshots/4.png b/fastlane/metadata/android/eu/images/phoneScreenshots/4.png
index 847627c87..d9563efe6 100644
Binary files a/fastlane/metadata/android/eu/images/phoneScreenshots/4.png and b/fastlane/metadata/android/eu/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/eu/images/phoneScreenshots/5.png b/fastlane/metadata/android/eu/images/phoneScreenshots/5.png
index 2b8914ed8..b8e06cde0 100644
Binary files a/fastlane/metadata/android/eu/images/phoneScreenshots/5.png and b/fastlane/metadata/android/eu/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/eu/images/phoneScreenshots/6.png b/fastlane/metadata/android/eu/images/phoneScreenshots/6.png
index 1a9ff9000..4ea10db3e 100644
Binary files a/fastlane/metadata/android/eu/images/phoneScreenshots/6.png and b/fastlane/metadata/android/eu/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/eu/images/phoneScreenshots/7.png b/fastlane/metadata/android/eu/images/phoneScreenshots/7.png
index b9b6686cc..4601ced48 100644
Binary files a/fastlane/metadata/android/eu/images/phoneScreenshots/7.png and b/fastlane/metadata/android/eu/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/fa/images/phoneScreenshots/1.png b/fastlane/metadata/android/fa/images/phoneScreenshots/1.png
index 69d9359e8..62c4f52b6 100644
Binary files a/fastlane/metadata/android/fa/images/phoneScreenshots/1.png and b/fastlane/metadata/android/fa/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/fa/images/phoneScreenshots/2.png b/fastlane/metadata/android/fa/images/phoneScreenshots/2.png
index ab6f62ff6..512bdb2de 100644
Binary files a/fastlane/metadata/android/fa/images/phoneScreenshots/2.png and b/fastlane/metadata/android/fa/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/fa/images/phoneScreenshots/3.png b/fastlane/metadata/android/fa/images/phoneScreenshots/3.png
index ab81876aa..7e6bc6c32 100644
Binary files a/fastlane/metadata/android/fa/images/phoneScreenshots/3.png and b/fastlane/metadata/android/fa/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/fa/images/phoneScreenshots/4.png b/fastlane/metadata/android/fa/images/phoneScreenshots/4.png
index 71eb26632..542432d86 100644
Binary files a/fastlane/metadata/android/fa/images/phoneScreenshots/4.png and b/fastlane/metadata/android/fa/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/fa/images/phoneScreenshots/5.png b/fastlane/metadata/android/fa/images/phoneScreenshots/5.png
index 172f0d533..f8feef981 100644
Binary files a/fastlane/metadata/android/fa/images/phoneScreenshots/5.png and b/fastlane/metadata/android/fa/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/fa/images/phoneScreenshots/6.png b/fastlane/metadata/android/fa/images/phoneScreenshots/6.png
index ec1eff3e0..fd4ac3a21 100644
Binary files a/fastlane/metadata/android/fa/images/phoneScreenshots/6.png and b/fastlane/metadata/android/fa/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/fa/images/phoneScreenshots/7.png b/fastlane/metadata/android/fa/images/phoneScreenshots/7.png
index 988e02308..76e7408ed 100644
Binary files a/fastlane/metadata/android/fa/images/phoneScreenshots/7.png and b/fastlane/metadata/android/fa/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/1.png b/fastlane/metadata/android/fr/images/phoneScreenshots/1.png
index 1a10f9221..874d367cf 100644
Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/1.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/2.png b/fastlane/metadata/android/fr/images/phoneScreenshots/2.png
index 21b65b162..2c792847f 100644
Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/2.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/3.png b/fastlane/metadata/android/fr/images/phoneScreenshots/3.png
index cc4f7a584..6ff68b0e2 100644
Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/3.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/4.png b/fastlane/metadata/android/fr/images/phoneScreenshots/4.png
index 0bb6490e1..5c88eb2f9 100644
Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/4.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/5.png b/fastlane/metadata/android/fr/images/phoneScreenshots/5.png
index 1388a5333..6da29682f 100644
Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/5.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/6.png b/fastlane/metadata/android/fr/images/phoneScreenshots/6.png
index e3ba684a5..85b043a32 100644
Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/6.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/7.png b/fastlane/metadata/android/fr/images/phoneScreenshots/7.png
index 2699c480b..02700e131 100644
Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/7.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/gl/images/featureGraphic.png b/fastlane/metadata/android/gl/images/featureGraphic.png
new file mode 100755
index 000000000..be1a1f7eb
Binary files /dev/null and b/fastlane/metadata/android/gl/images/featureGraphic.png differ
diff --git a/fastlane/metadata/android/gl/images/phoneScreenshots/1.png b/fastlane/metadata/android/gl/images/phoneScreenshots/1.png
new file mode 100644
index 000000000..b30e957df
Binary files /dev/null and b/fastlane/metadata/android/gl/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/gl/images/phoneScreenshots/2.png b/fastlane/metadata/android/gl/images/phoneScreenshots/2.png
new file mode 100644
index 000000000..934befe2f
Binary files /dev/null and b/fastlane/metadata/android/gl/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/gl/images/phoneScreenshots/3.png b/fastlane/metadata/android/gl/images/phoneScreenshots/3.png
new file mode 100644
index 000000000..3ab853d5c
Binary files /dev/null and b/fastlane/metadata/android/gl/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/gl/images/phoneScreenshots/4.png b/fastlane/metadata/android/gl/images/phoneScreenshots/4.png
new file mode 100644
index 000000000..823080756
Binary files /dev/null and b/fastlane/metadata/android/gl/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/gl/images/phoneScreenshots/5.png b/fastlane/metadata/android/gl/images/phoneScreenshots/5.png
new file mode 100644
index 000000000..3d67e4cbf
Binary files /dev/null and b/fastlane/metadata/android/gl/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/gl/images/phoneScreenshots/6.png b/fastlane/metadata/android/gl/images/phoneScreenshots/6.png
new file mode 100644
index 000000000..f43d9683d
Binary files /dev/null and b/fastlane/metadata/android/gl/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/gl/images/phoneScreenshots/7.png b/fastlane/metadata/android/gl/images/phoneScreenshots/7.png
new file mode 100644
index 000000000..d29b33163
Binary files /dev/null and b/fastlane/metadata/android/gl/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/hi/full_description.txt b/fastlane/metadata/android/hi/full_description.txt
index 704559101..ab592d054 100644
--- a/fastlane/metadata/android/hi/full_description.txt
+++ b/fastlane/metadata/android/hi/full_description.txt
@@ -1,5 +1,5 @@
Aves आपके ठेठ JPEGs और MP4s सम्मिलित करते हुए, लगभग सभी प्रकार के Photos और Videos को सम्भाल सकता है, साथ के साथ यह multi-page TIFFs, SVGs, old AVIs और भी बहुत कुछ संभालता है ! यह आपके Media संग्रह की जाँच करता है, ताकि यह motion photos, panoramas (aka photo spheres), 360° videos, और 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 (including Android TV) with features such as widgets, app shortcuts, screen saver and global search handling. It also works as a media viewer and picker.
+एव्ज एंड्रॉइड (एंड्रॉइड टीवी सहित) के साथ विजेट्स, ऐप शॉर्टकट, स्क्रीन सेवर और ग्लोबल सर्च हैंडलिंग जैसी सुविधाओं के साथ एकीकृत होता है। यह मीडिया व्यूअर और पिकर के रूप में भी काम करता है।
diff --git a/fastlane/metadata/android/hu/images/phoneScreenshots/1.png b/fastlane/metadata/android/hu/images/phoneScreenshots/1.png
index f50f91a94..5ac53e855 100644
Binary files a/fastlane/metadata/android/hu/images/phoneScreenshots/1.png and b/fastlane/metadata/android/hu/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/hu/images/phoneScreenshots/2.png b/fastlane/metadata/android/hu/images/phoneScreenshots/2.png
index 96a5ccdc8..0994067e8 100644
Binary files a/fastlane/metadata/android/hu/images/phoneScreenshots/2.png and b/fastlane/metadata/android/hu/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/hu/images/phoneScreenshots/3.png b/fastlane/metadata/android/hu/images/phoneScreenshots/3.png
index 18a1922bc..ea8180cc8 100644
Binary files a/fastlane/metadata/android/hu/images/phoneScreenshots/3.png and b/fastlane/metadata/android/hu/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/hu/images/phoneScreenshots/4.png b/fastlane/metadata/android/hu/images/phoneScreenshots/4.png
index 8fa0e498c..1b1734e31 100644
Binary files a/fastlane/metadata/android/hu/images/phoneScreenshots/4.png and b/fastlane/metadata/android/hu/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/hu/images/phoneScreenshots/5.png b/fastlane/metadata/android/hu/images/phoneScreenshots/5.png
index 8d8522b2f..21117ac43 100644
Binary files a/fastlane/metadata/android/hu/images/phoneScreenshots/5.png and b/fastlane/metadata/android/hu/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/hu/images/phoneScreenshots/6.png b/fastlane/metadata/android/hu/images/phoneScreenshots/6.png
index 54e31b8c7..cbe8f4cf6 100644
Binary files a/fastlane/metadata/android/hu/images/phoneScreenshots/6.png and b/fastlane/metadata/android/hu/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/hu/images/phoneScreenshots/7.png b/fastlane/metadata/android/hu/images/phoneScreenshots/7.png
index ad7ee0ff0..dbf9d067a 100644
Binary files a/fastlane/metadata/android/hu/images/phoneScreenshots/7.png and b/fastlane/metadata/android/hu/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/1.png b/fastlane/metadata/android/id/images/phoneScreenshots/1.png
index 6eaf2273b..bc30b5c9b 100644
Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/1.png and b/fastlane/metadata/android/id/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/2.png b/fastlane/metadata/android/id/images/phoneScreenshots/2.png
index dfdd8b26d..6781dd9b9 100644
Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/2.png and b/fastlane/metadata/android/id/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/3.png b/fastlane/metadata/android/id/images/phoneScreenshots/3.png
index fd1bb92dd..9dc2dbabc 100644
Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/3.png and b/fastlane/metadata/android/id/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/4.png b/fastlane/metadata/android/id/images/phoneScreenshots/4.png
index 5e575b60d..af64d78fd 100644
Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/4.png and b/fastlane/metadata/android/id/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/5.png b/fastlane/metadata/android/id/images/phoneScreenshots/5.png
index e8b17d935..1bd4f1625 100644
Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/5.png and b/fastlane/metadata/android/id/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/6.png b/fastlane/metadata/android/id/images/phoneScreenshots/6.png
index 763d3f053..7489260f2 100644
Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/6.png and b/fastlane/metadata/android/id/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/7.png b/fastlane/metadata/android/id/images/phoneScreenshots/7.png
index e64da42a5..7fc59e597 100644
Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/7.png and b/fastlane/metadata/android/id/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/is/images/phoneScreenshots/1.png b/fastlane/metadata/android/is/images/phoneScreenshots/1.png
index 37d8598a2..4de341687 100644
Binary files a/fastlane/metadata/android/is/images/phoneScreenshots/1.png and b/fastlane/metadata/android/is/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/is/images/phoneScreenshots/2.png b/fastlane/metadata/android/is/images/phoneScreenshots/2.png
index 77cbf0153..f9dc74ed8 100644
Binary files a/fastlane/metadata/android/is/images/phoneScreenshots/2.png and b/fastlane/metadata/android/is/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/is/images/phoneScreenshots/3.png b/fastlane/metadata/android/is/images/phoneScreenshots/3.png
index 2330679f7..47e870e3b 100644
Binary files a/fastlane/metadata/android/is/images/phoneScreenshots/3.png and b/fastlane/metadata/android/is/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/is/images/phoneScreenshots/4.png b/fastlane/metadata/android/is/images/phoneScreenshots/4.png
index 80df4a727..198b33a4b 100644
Binary files a/fastlane/metadata/android/is/images/phoneScreenshots/4.png and b/fastlane/metadata/android/is/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/is/images/phoneScreenshots/5.png b/fastlane/metadata/android/is/images/phoneScreenshots/5.png
index 691840c7d..751506e8c 100644
Binary files a/fastlane/metadata/android/is/images/phoneScreenshots/5.png and b/fastlane/metadata/android/is/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/is/images/phoneScreenshots/6.png b/fastlane/metadata/android/is/images/phoneScreenshots/6.png
index 56f4213c5..81ed9cdb2 100644
Binary files a/fastlane/metadata/android/is/images/phoneScreenshots/6.png and b/fastlane/metadata/android/is/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/is/images/phoneScreenshots/7.png b/fastlane/metadata/android/is/images/phoneScreenshots/7.png
index a18ca83b0..b307d2cc5 100644
Binary files a/fastlane/metadata/android/is/images/phoneScreenshots/7.png and b/fastlane/metadata/android/is/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/it/images/phoneScreenshots/1.png b/fastlane/metadata/android/it/images/phoneScreenshots/1.png
index 2639a317b..3c813e0a9 100644
Binary files a/fastlane/metadata/android/it/images/phoneScreenshots/1.png and b/fastlane/metadata/android/it/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/it/images/phoneScreenshots/2.png b/fastlane/metadata/android/it/images/phoneScreenshots/2.png
index ce01662ee..2f09c2998 100644
Binary files a/fastlane/metadata/android/it/images/phoneScreenshots/2.png and b/fastlane/metadata/android/it/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/it/images/phoneScreenshots/3.png b/fastlane/metadata/android/it/images/phoneScreenshots/3.png
index e83f88660..a0465d52c 100644
Binary files a/fastlane/metadata/android/it/images/phoneScreenshots/3.png and b/fastlane/metadata/android/it/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/it/images/phoneScreenshots/4.png b/fastlane/metadata/android/it/images/phoneScreenshots/4.png
index dd89e1ef6..90fbeb7ef 100644
Binary files a/fastlane/metadata/android/it/images/phoneScreenshots/4.png and b/fastlane/metadata/android/it/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/it/images/phoneScreenshots/5.png b/fastlane/metadata/android/it/images/phoneScreenshots/5.png
index ee7841838..54cc52b02 100644
Binary files a/fastlane/metadata/android/it/images/phoneScreenshots/5.png and b/fastlane/metadata/android/it/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/it/images/phoneScreenshots/6.png b/fastlane/metadata/android/it/images/phoneScreenshots/6.png
index 4f405d609..ee26fdbca 100644
Binary files a/fastlane/metadata/android/it/images/phoneScreenshots/6.png and b/fastlane/metadata/android/it/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/it/images/phoneScreenshots/7.png b/fastlane/metadata/android/it/images/phoneScreenshots/7.png
index e5ac97ce6..4a3173d66 100644
Binary files a/fastlane/metadata/android/it/images/phoneScreenshots/7.png and b/fastlane/metadata/android/it/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/1.png b/fastlane/metadata/android/ja/images/phoneScreenshots/1.png
index f0295da57..c12907f9f 100644
Binary files a/fastlane/metadata/android/ja/images/phoneScreenshots/1.png and b/fastlane/metadata/android/ja/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/2.png b/fastlane/metadata/android/ja/images/phoneScreenshots/2.png
index 1a09722c2..5d06cc373 100644
Binary files a/fastlane/metadata/android/ja/images/phoneScreenshots/2.png and b/fastlane/metadata/android/ja/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/3.png b/fastlane/metadata/android/ja/images/phoneScreenshots/3.png
index be1c197fa..b262c6459 100644
Binary files a/fastlane/metadata/android/ja/images/phoneScreenshots/3.png and b/fastlane/metadata/android/ja/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/4.png b/fastlane/metadata/android/ja/images/phoneScreenshots/4.png
index 3c794709f..c88922eca 100644
Binary files a/fastlane/metadata/android/ja/images/phoneScreenshots/4.png and b/fastlane/metadata/android/ja/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/5.png b/fastlane/metadata/android/ja/images/phoneScreenshots/5.png
index 85da14dba..2c9b771a8 100644
Binary files a/fastlane/metadata/android/ja/images/phoneScreenshots/5.png and b/fastlane/metadata/android/ja/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/6.png b/fastlane/metadata/android/ja/images/phoneScreenshots/6.png
index 1fa22534d..5db639831 100644
Binary files a/fastlane/metadata/android/ja/images/phoneScreenshots/6.png and b/fastlane/metadata/android/ja/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/7.png b/fastlane/metadata/android/ja/images/phoneScreenshots/7.png
index 859589ec8..9ada59f89 100644
Binary files a/fastlane/metadata/android/ja/images/phoneScreenshots/7.png and b/fastlane/metadata/android/ja/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/1.png b/fastlane/metadata/android/ko/images/phoneScreenshots/1.png
index 7082f118a..59422c77b 100644
Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/1.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/2.png b/fastlane/metadata/android/ko/images/phoneScreenshots/2.png
index 656e9d065..b98d21d88 100644
Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/2.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/3.png b/fastlane/metadata/android/ko/images/phoneScreenshots/3.png
index 653aafa90..451b0f555 100644
Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/3.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/4.png b/fastlane/metadata/android/ko/images/phoneScreenshots/4.png
index 49b756274..ed6d731c3 100644
Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/4.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/5.png b/fastlane/metadata/android/ko/images/phoneScreenshots/5.png
index 9deb97f29..fa8da7997 100644
Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/5.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/6.png b/fastlane/metadata/android/ko/images/phoneScreenshots/6.png
index d95c27b54..e96f34e62 100644
Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/6.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/7.png b/fastlane/metadata/android/ko/images/phoneScreenshots/7.png
index dd7d136e4..6b5b58bf7 100644
Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/7.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/lt/images/phoneScreenshots/1.png b/fastlane/metadata/android/lt/images/phoneScreenshots/1.png
index 9833ac27c..f57e870e2 100644
Binary files a/fastlane/metadata/android/lt/images/phoneScreenshots/1.png and b/fastlane/metadata/android/lt/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/lt/images/phoneScreenshots/2.png b/fastlane/metadata/android/lt/images/phoneScreenshots/2.png
index 3cc6610f8..3524f7161 100644
Binary files a/fastlane/metadata/android/lt/images/phoneScreenshots/2.png and b/fastlane/metadata/android/lt/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/lt/images/phoneScreenshots/3.png b/fastlane/metadata/android/lt/images/phoneScreenshots/3.png
index cc118f076..64ad02a51 100644
Binary files a/fastlane/metadata/android/lt/images/phoneScreenshots/3.png and b/fastlane/metadata/android/lt/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/lt/images/phoneScreenshots/4.png b/fastlane/metadata/android/lt/images/phoneScreenshots/4.png
index 66aa2f726..bd96232ba 100644
Binary files a/fastlane/metadata/android/lt/images/phoneScreenshots/4.png and b/fastlane/metadata/android/lt/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/lt/images/phoneScreenshots/5.png b/fastlane/metadata/android/lt/images/phoneScreenshots/5.png
index 3561b8d1a..3a29282a8 100644
Binary files a/fastlane/metadata/android/lt/images/phoneScreenshots/5.png and b/fastlane/metadata/android/lt/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/lt/images/phoneScreenshots/6.png b/fastlane/metadata/android/lt/images/phoneScreenshots/6.png
index e2495abf0..3777126fd 100644
Binary files a/fastlane/metadata/android/lt/images/phoneScreenshots/6.png and b/fastlane/metadata/android/lt/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/lt/images/phoneScreenshots/7.png b/fastlane/metadata/android/lt/images/phoneScreenshots/7.png
index 849f2e560..75c0fb634 100644
Binary files a/fastlane/metadata/android/lt/images/phoneScreenshots/7.png and b/fastlane/metadata/android/lt/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/1.png b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/1.png
index 887823e42..b6c1564ce 100644
Binary files a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/1.png and b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/2.png b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/2.png
index 296bd4895..26686ac1f 100644
Binary files a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/2.png and b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/3.png b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/3.png
index 31347e3b2..9baff3203 100644
Binary files a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/3.png and b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/4.png b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/4.png
index 3e9afad93..d0e2fae5c 100644
Binary files a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/4.png and b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/5.png b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/5.png
index f900a93fa..8a2f71a7e 100644
Binary files a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/5.png and b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/6.png b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/6.png
index 05412e6d6..0c8988e38 100644
Binary files a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/6.png and b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/7.png b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/7.png
index 07d6de655..feeee34ff 100644
Binary files a/fastlane/metadata/android/nb-NO/images/phoneScreenshots/7.png and b/fastlane/metadata/android/nb-NO/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/nl/images/phoneScreenshots/1.png b/fastlane/metadata/android/nl/images/phoneScreenshots/1.png
index 5605a8319..542508304 100644
Binary files a/fastlane/metadata/android/nl/images/phoneScreenshots/1.png and b/fastlane/metadata/android/nl/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/nl/images/phoneScreenshots/2.png b/fastlane/metadata/android/nl/images/phoneScreenshots/2.png
index d503bd675..205fc65b9 100644
Binary files a/fastlane/metadata/android/nl/images/phoneScreenshots/2.png and b/fastlane/metadata/android/nl/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/nl/images/phoneScreenshots/3.png b/fastlane/metadata/android/nl/images/phoneScreenshots/3.png
index 0a43eef47..9e0da9e8f 100644
Binary files a/fastlane/metadata/android/nl/images/phoneScreenshots/3.png and b/fastlane/metadata/android/nl/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/nl/images/phoneScreenshots/4.png b/fastlane/metadata/android/nl/images/phoneScreenshots/4.png
index dead88501..27220766a 100644
Binary files a/fastlane/metadata/android/nl/images/phoneScreenshots/4.png and b/fastlane/metadata/android/nl/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/nl/images/phoneScreenshots/5.png b/fastlane/metadata/android/nl/images/phoneScreenshots/5.png
index 857d999a8..1afc7d9da 100644
Binary files a/fastlane/metadata/android/nl/images/phoneScreenshots/5.png and b/fastlane/metadata/android/nl/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/nl/images/phoneScreenshots/6.png b/fastlane/metadata/android/nl/images/phoneScreenshots/6.png
index 833b7e922..fada64402 100644
Binary files a/fastlane/metadata/android/nl/images/phoneScreenshots/6.png and b/fastlane/metadata/android/nl/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/nl/images/phoneScreenshots/7.png b/fastlane/metadata/android/nl/images/phoneScreenshots/7.png
index b837ed5b3..5a32c12fb 100644
Binary files a/fastlane/metadata/android/nl/images/phoneScreenshots/7.png and b/fastlane/metadata/android/nl/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/nn/images/phoneScreenshots/1.png b/fastlane/metadata/android/nn/images/phoneScreenshots/1.png
index 093c6c39a..12d383758 100644
Binary files a/fastlane/metadata/android/nn/images/phoneScreenshots/1.png and b/fastlane/metadata/android/nn/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/nn/images/phoneScreenshots/2.png b/fastlane/metadata/android/nn/images/phoneScreenshots/2.png
index 2ed257108..9b15ba37b 100644
Binary files a/fastlane/metadata/android/nn/images/phoneScreenshots/2.png and b/fastlane/metadata/android/nn/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/nn/images/phoneScreenshots/3.png b/fastlane/metadata/android/nn/images/phoneScreenshots/3.png
index 57c909e7d..3734eeec9 100644
Binary files a/fastlane/metadata/android/nn/images/phoneScreenshots/3.png and b/fastlane/metadata/android/nn/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/nn/images/phoneScreenshots/4.png b/fastlane/metadata/android/nn/images/phoneScreenshots/4.png
index b16da8406..32a15cf43 100644
Binary files a/fastlane/metadata/android/nn/images/phoneScreenshots/4.png and b/fastlane/metadata/android/nn/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/nn/images/phoneScreenshots/5.png b/fastlane/metadata/android/nn/images/phoneScreenshots/5.png
index 77dd8e4f5..7053f5359 100644
Binary files a/fastlane/metadata/android/nn/images/phoneScreenshots/5.png and b/fastlane/metadata/android/nn/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/nn/images/phoneScreenshots/6.png b/fastlane/metadata/android/nn/images/phoneScreenshots/6.png
index 486a92c63..5583cec34 100644
Binary files a/fastlane/metadata/android/nn/images/phoneScreenshots/6.png and b/fastlane/metadata/android/nn/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/nn/images/phoneScreenshots/7.png b/fastlane/metadata/android/nn/images/phoneScreenshots/7.png
index a424a0bcb..2c17b13a3 100644
Binary files a/fastlane/metadata/android/nn/images/phoneScreenshots/7.png and b/fastlane/metadata/android/nn/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/pl/images/phoneScreenshots/1.png b/fastlane/metadata/android/pl/images/phoneScreenshots/1.png
index 8bb698c81..f81eda6a6 100644
Binary files a/fastlane/metadata/android/pl/images/phoneScreenshots/1.png and b/fastlane/metadata/android/pl/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/pl/images/phoneScreenshots/2.png b/fastlane/metadata/android/pl/images/phoneScreenshots/2.png
index e5104fdf0..9326138e5 100644
Binary files a/fastlane/metadata/android/pl/images/phoneScreenshots/2.png and b/fastlane/metadata/android/pl/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/pl/images/phoneScreenshots/3.png b/fastlane/metadata/android/pl/images/phoneScreenshots/3.png
index 79e1c8cdc..7f27ac6e9 100644
Binary files a/fastlane/metadata/android/pl/images/phoneScreenshots/3.png and b/fastlane/metadata/android/pl/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/pl/images/phoneScreenshots/4.png b/fastlane/metadata/android/pl/images/phoneScreenshots/4.png
index 816a2d78e..54b211464 100644
Binary files a/fastlane/metadata/android/pl/images/phoneScreenshots/4.png and b/fastlane/metadata/android/pl/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/pl/images/phoneScreenshots/5.png b/fastlane/metadata/android/pl/images/phoneScreenshots/5.png
index 754858c02..4c1803cc5 100644
Binary files a/fastlane/metadata/android/pl/images/phoneScreenshots/5.png and b/fastlane/metadata/android/pl/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/pl/images/phoneScreenshots/6.png b/fastlane/metadata/android/pl/images/phoneScreenshots/6.png
index d8338d257..c8dd4d4a1 100644
Binary files a/fastlane/metadata/android/pl/images/phoneScreenshots/6.png and b/fastlane/metadata/android/pl/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/pl/images/phoneScreenshots/7.png b/fastlane/metadata/android/pl/images/phoneScreenshots/7.png
index 9cf6f6cae..4ff8caa93 100644
Binary files a/fastlane/metadata/android/pl/images/phoneScreenshots/7.png and b/fastlane/metadata/android/pl/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/1.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/1.png
index b0c3240de..fbca03b05 100644
Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/1.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/2.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/2.png
index fcd83f21b..f3357c47d 100644
Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/2.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/3.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/3.png
index 72e7a5bd4..6fd4e367c 100644
Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/3.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/4.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/4.png
index 9e0f5aa79..2c3c92200 100644
Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/4.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/5.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/5.png
index db56dd16f..dfcb60e67 100644
Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/5.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/6.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/6.png
index 3dae76240..367bd47e3 100644
Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/6.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/7.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/7.png
index 9d7a1bb57..0f97d7101 100644
Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/7.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/ro/images/phoneScreenshots/1.png b/fastlane/metadata/android/ro/images/phoneScreenshots/1.png
index b3bb669f5..ecc6a9828 100644
Binary files a/fastlane/metadata/android/ro/images/phoneScreenshots/1.png and b/fastlane/metadata/android/ro/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/ro/images/phoneScreenshots/2.png b/fastlane/metadata/android/ro/images/phoneScreenshots/2.png
index 71931d19b..763a12328 100644
Binary files a/fastlane/metadata/android/ro/images/phoneScreenshots/2.png and b/fastlane/metadata/android/ro/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/ro/images/phoneScreenshots/3.png b/fastlane/metadata/android/ro/images/phoneScreenshots/3.png
index 79db3e221..6244cb924 100644
Binary files a/fastlane/metadata/android/ro/images/phoneScreenshots/3.png and b/fastlane/metadata/android/ro/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/ro/images/phoneScreenshots/4.png b/fastlane/metadata/android/ro/images/phoneScreenshots/4.png
index a9384fbe6..1d22f0df8 100644
Binary files a/fastlane/metadata/android/ro/images/phoneScreenshots/4.png and b/fastlane/metadata/android/ro/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/ro/images/phoneScreenshots/5.png b/fastlane/metadata/android/ro/images/phoneScreenshots/5.png
index 04927bb34..9de6df649 100644
Binary files a/fastlane/metadata/android/ro/images/phoneScreenshots/5.png and b/fastlane/metadata/android/ro/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/ro/images/phoneScreenshots/6.png b/fastlane/metadata/android/ro/images/phoneScreenshots/6.png
index 444c8d903..91c3b6609 100644
Binary files a/fastlane/metadata/android/ro/images/phoneScreenshots/6.png and b/fastlane/metadata/android/ro/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/ro/images/phoneScreenshots/7.png b/fastlane/metadata/android/ro/images/phoneScreenshots/7.png
index ba3d16585..d52ec9ab8 100644
Binary files a/fastlane/metadata/android/ro/images/phoneScreenshots/7.png and b/fastlane/metadata/android/ro/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/1.png b/fastlane/metadata/android/ru/images/phoneScreenshots/1.png
index f8c5321ef..b47bba5f2 100644
Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/1.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/2.png b/fastlane/metadata/android/ru/images/phoneScreenshots/2.png
index 6bf85ed4f..2b3885517 100644
Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/2.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/3.png b/fastlane/metadata/android/ru/images/phoneScreenshots/3.png
index de4aa071c..a3ffa2695 100644
Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/3.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/4.png b/fastlane/metadata/android/ru/images/phoneScreenshots/4.png
index e5750ff6d..ef9ad9606 100644
Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/4.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/5.png b/fastlane/metadata/android/ru/images/phoneScreenshots/5.png
index d257fb384..1f02f5189 100644
Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/5.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/6.png b/fastlane/metadata/android/ru/images/phoneScreenshots/6.png
index f698f89ce..72a8b2821 100644
Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/6.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/7.png b/fastlane/metadata/android/ru/images/phoneScreenshots/7.png
index fda462a26..5cd09a028 100644
Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/7.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/sk/images/phoneScreenshots/1.png b/fastlane/metadata/android/sk/images/phoneScreenshots/1.png
index 97eb00138..2d80b5f73 100644
Binary files a/fastlane/metadata/android/sk/images/phoneScreenshots/1.png and b/fastlane/metadata/android/sk/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/sk/images/phoneScreenshots/2.png b/fastlane/metadata/android/sk/images/phoneScreenshots/2.png
index 3c4414785..a57f7f969 100644
Binary files a/fastlane/metadata/android/sk/images/phoneScreenshots/2.png and b/fastlane/metadata/android/sk/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/sk/images/phoneScreenshots/3.png b/fastlane/metadata/android/sk/images/phoneScreenshots/3.png
index f5b6511b1..404eceaf5 100644
Binary files a/fastlane/metadata/android/sk/images/phoneScreenshots/3.png and b/fastlane/metadata/android/sk/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/sk/images/phoneScreenshots/4.png b/fastlane/metadata/android/sk/images/phoneScreenshots/4.png
index 92e5a9df1..cdcb1e7d2 100644
Binary files a/fastlane/metadata/android/sk/images/phoneScreenshots/4.png and b/fastlane/metadata/android/sk/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/sk/images/phoneScreenshots/5.png b/fastlane/metadata/android/sk/images/phoneScreenshots/5.png
index b99ed6e33..876d55e57 100644
Binary files a/fastlane/metadata/android/sk/images/phoneScreenshots/5.png and b/fastlane/metadata/android/sk/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/sk/images/phoneScreenshots/6.png b/fastlane/metadata/android/sk/images/phoneScreenshots/6.png
index ccf701513..38fda636f 100644
Binary files a/fastlane/metadata/android/sk/images/phoneScreenshots/6.png and b/fastlane/metadata/android/sk/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/sk/images/phoneScreenshots/7.png b/fastlane/metadata/android/sk/images/phoneScreenshots/7.png
index 5534724ad..524cca83c 100644
Binary files a/fastlane/metadata/android/sk/images/phoneScreenshots/7.png and b/fastlane/metadata/android/sk/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/sv/images/phoneScreenshots/1.png b/fastlane/metadata/android/sv/images/phoneScreenshots/1.png
index f911d8733..bdfadda1b 100644
Binary files a/fastlane/metadata/android/sv/images/phoneScreenshots/1.png and b/fastlane/metadata/android/sv/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/sv/images/phoneScreenshots/2.png b/fastlane/metadata/android/sv/images/phoneScreenshots/2.png
index dd0aceeae..738489d29 100644
Binary files a/fastlane/metadata/android/sv/images/phoneScreenshots/2.png and b/fastlane/metadata/android/sv/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/sv/images/phoneScreenshots/3.png b/fastlane/metadata/android/sv/images/phoneScreenshots/3.png
index fd2fc0cca..4129ac60b 100644
Binary files a/fastlane/metadata/android/sv/images/phoneScreenshots/3.png and b/fastlane/metadata/android/sv/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/sv/images/phoneScreenshots/4.png b/fastlane/metadata/android/sv/images/phoneScreenshots/4.png
index b7fa7fd9b..3f811f0c9 100644
Binary files a/fastlane/metadata/android/sv/images/phoneScreenshots/4.png and b/fastlane/metadata/android/sv/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/sv/images/phoneScreenshots/5.png b/fastlane/metadata/android/sv/images/phoneScreenshots/5.png
index f2763eab3..8fe715287 100644
Binary files a/fastlane/metadata/android/sv/images/phoneScreenshots/5.png and b/fastlane/metadata/android/sv/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/sv/images/phoneScreenshots/6.png b/fastlane/metadata/android/sv/images/phoneScreenshots/6.png
index f92ee16f9..b6aff64aa 100644
Binary files a/fastlane/metadata/android/sv/images/phoneScreenshots/6.png and b/fastlane/metadata/android/sv/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/sv/images/phoneScreenshots/7.png b/fastlane/metadata/android/sv/images/phoneScreenshots/7.png
index f59dbc39c..f17c055dd 100644
Binary files a/fastlane/metadata/android/sv/images/phoneScreenshots/7.png and b/fastlane/metadata/android/sv/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/ta/full_description.txt b/fastlane/metadata/android/ta/full_description.txt
index 5af6fb91b..fff338376 100644
--- a/fastlane/metadata/android/ta/full_description.txt
+++ b/fastlane/metadata/android/ta/full_description.txt
@@ -1,4 +1,4 @@
-ஏவ்ச் உங்கள் வழக்கமான செபிஇசிகள் மற்றும் எம்பி4கள் உட்பட அனைத்து வகையான படங்கள் மற்றும் காணொளிகளைக் கையாள முடியும், ஆனால் பல பக்க டிஐஎப்எப்கள், எச்விசிகள், பழைய அவிச் மற்றும் மேலும் போன்ற கவர்ச்சியான உருப்படிகளையும் கையாள முடியும்! இயக்கபுகைப்படங்கள் , பனோரமாகள் (புகைப்படக் கோளங்கள்), 360 ° காணொளிகள் , அத்துடன் சியோடிஐஎப்எப்கள் கோப்புகள்.
+ஏவ்ச் உங்கள் வழக்கமான செபிஇசிகள் மற்றும் எம்பி4கள் உட்பட அனைத்து வகையான படங்கள் மற்றும் காணொளிகளைக் கையாள முடியும், ஆனால் பல பக்க TIFFs, SVGs, பழைய AVIs மற்றும் மேலும் போன்ற கவர்ச்சியான உருப்படிகளையும் கையாள முடியும்! இயக்கபுகைப்படங்கள் , பனோரமாகள் (புகைப்படக் கோளங்கள்), 360 ° காணொளிகள் , அத்துடன் GeoTIFF கோப்புகள்.
வழிசெலுத்தல் மற்றும் தேடல் ஏவ்ச் இன் ஒரு முக்கிய பகுதியாகும். பயனர்கள் ஆல்பங்களிலிருந்து புகைப்படங்கள்வரை குறிச்சொற்களுக்கு வரைபடங்கள் போன்றவற்றுக்கு எளிதாகப் பாய வேண்டும்.
diff --git a/fastlane/metadata/android/ta/images/phoneScreenshots/1.png b/fastlane/metadata/android/ta/images/phoneScreenshots/1.png
index db5a2625e..96df34527 100644
Binary files a/fastlane/metadata/android/ta/images/phoneScreenshots/1.png and b/fastlane/metadata/android/ta/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/ta/images/phoneScreenshots/2.png b/fastlane/metadata/android/ta/images/phoneScreenshots/2.png
index 643f990d0..2ba90ff1f 100644
Binary files a/fastlane/metadata/android/ta/images/phoneScreenshots/2.png and b/fastlane/metadata/android/ta/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/ta/images/phoneScreenshots/3.png b/fastlane/metadata/android/ta/images/phoneScreenshots/3.png
index 9d5fd1909..70e67a4e3 100644
Binary files a/fastlane/metadata/android/ta/images/phoneScreenshots/3.png and b/fastlane/metadata/android/ta/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/ta/images/phoneScreenshots/4.png b/fastlane/metadata/android/ta/images/phoneScreenshots/4.png
index 5bd3c6052..7a1bedc97 100644
Binary files a/fastlane/metadata/android/ta/images/phoneScreenshots/4.png and b/fastlane/metadata/android/ta/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/ta/images/phoneScreenshots/5.png b/fastlane/metadata/android/ta/images/phoneScreenshots/5.png
index 423e383e3..7d7513584 100644
Binary files a/fastlane/metadata/android/ta/images/phoneScreenshots/5.png and b/fastlane/metadata/android/ta/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/ta/images/phoneScreenshots/6.png b/fastlane/metadata/android/ta/images/phoneScreenshots/6.png
index 82f9de360..5c052f169 100644
Binary files a/fastlane/metadata/android/ta/images/phoneScreenshots/6.png and b/fastlane/metadata/android/ta/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/ta/images/phoneScreenshots/7.png b/fastlane/metadata/android/ta/images/phoneScreenshots/7.png
index 91c6f2dfe..8cecb6387 100644
Binary files a/fastlane/metadata/android/ta/images/phoneScreenshots/7.png and b/fastlane/metadata/android/ta/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/tr/images/phoneScreenshots/1.png b/fastlane/metadata/android/tr/images/phoneScreenshots/1.png
index a8d768ef9..f32b33d50 100644
Binary files a/fastlane/metadata/android/tr/images/phoneScreenshots/1.png and b/fastlane/metadata/android/tr/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/tr/images/phoneScreenshots/2.png b/fastlane/metadata/android/tr/images/phoneScreenshots/2.png
index 8ec0ecbb3..ff9f5b722 100644
Binary files a/fastlane/metadata/android/tr/images/phoneScreenshots/2.png and b/fastlane/metadata/android/tr/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/tr/images/phoneScreenshots/3.png b/fastlane/metadata/android/tr/images/phoneScreenshots/3.png
index a667c121c..f920847d5 100644
Binary files a/fastlane/metadata/android/tr/images/phoneScreenshots/3.png and b/fastlane/metadata/android/tr/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/tr/images/phoneScreenshots/4.png b/fastlane/metadata/android/tr/images/phoneScreenshots/4.png
index 699f1e29f..40a9ec17b 100644
Binary files a/fastlane/metadata/android/tr/images/phoneScreenshots/4.png and b/fastlane/metadata/android/tr/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/tr/images/phoneScreenshots/5.png b/fastlane/metadata/android/tr/images/phoneScreenshots/5.png
index 78acdd853..febf64534 100644
Binary files a/fastlane/metadata/android/tr/images/phoneScreenshots/5.png and b/fastlane/metadata/android/tr/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/tr/images/phoneScreenshots/6.png b/fastlane/metadata/android/tr/images/phoneScreenshots/6.png
index 427eebf23..e7ae22370 100644
Binary files a/fastlane/metadata/android/tr/images/phoneScreenshots/6.png and b/fastlane/metadata/android/tr/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/tr/images/phoneScreenshots/7.png b/fastlane/metadata/android/tr/images/phoneScreenshots/7.png
index 9f297340e..547bbb704 100644
Binary files a/fastlane/metadata/android/tr/images/phoneScreenshots/7.png and b/fastlane/metadata/android/tr/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/1.png b/fastlane/metadata/android/uk/images/phoneScreenshots/1.png
index 3f73a6652..f2e9779c8 100644
Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/1.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/2.png b/fastlane/metadata/android/uk/images/phoneScreenshots/2.png
index dae6783b0..47deec72d 100644
Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/2.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/3.png b/fastlane/metadata/android/uk/images/phoneScreenshots/3.png
index aa8945fa3..51bd1503f 100644
Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/3.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/4.png b/fastlane/metadata/android/uk/images/phoneScreenshots/4.png
index e28441b86..ea7842f20 100644
Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/4.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/5.png b/fastlane/metadata/android/uk/images/phoneScreenshots/5.png
index 4418e6501..54cef8e49 100644
Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/5.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/6.png b/fastlane/metadata/android/uk/images/phoneScreenshots/6.png
index 28cf95b24..414480eb1 100644
Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/6.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/uk/images/phoneScreenshots/7.png b/fastlane/metadata/android/uk/images/phoneScreenshots/7.png
index 7d03e2d4d..db8150a73 100644
Binary files a/fastlane/metadata/android/uk/images/phoneScreenshots/7.png and b/fastlane/metadata/android/uk/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/vi/images/phoneScreenshots/1.png b/fastlane/metadata/android/vi/images/phoneScreenshots/1.png
index b263d13c1..5a10537dc 100644
Binary files a/fastlane/metadata/android/vi/images/phoneScreenshots/1.png and b/fastlane/metadata/android/vi/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/vi/images/phoneScreenshots/2.png b/fastlane/metadata/android/vi/images/phoneScreenshots/2.png
index 078f09ca0..ae6457a44 100644
Binary files a/fastlane/metadata/android/vi/images/phoneScreenshots/2.png and b/fastlane/metadata/android/vi/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/vi/images/phoneScreenshots/3.png b/fastlane/metadata/android/vi/images/phoneScreenshots/3.png
index 39fb4faae..cc1f9f8b3 100644
Binary files a/fastlane/metadata/android/vi/images/phoneScreenshots/3.png and b/fastlane/metadata/android/vi/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/vi/images/phoneScreenshots/4.png b/fastlane/metadata/android/vi/images/phoneScreenshots/4.png
index a905ec4ba..9e0c4c9f4 100644
Binary files a/fastlane/metadata/android/vi/images/phoneScreenshots/4.png and b/fastlane/metadata/android/vi/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/vi/images/phoneScreenshots/5.png b/fastlane/metadata/android/vi/images/phoneScreenshots/5.png
index 5fc9215ec..fa441d4dd 100644
Binary files a/fastlane/metadata/android/vi/images/phoneScreenshots/5.png and b/fastlane/metadata/android/vi/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/vi/images/phoneScreenshots/6.png b/fastlane/metadata/android/vi/images/phoneScreenshots/6.png
index 940c8696b..4647a9c40 100644
Binary files a/fastlane/metadata/android/vi/images/phoneScreenshots/6.png and b/fastlane/metadata/android/vi/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/vi/images/phoneScreenshots/7.png b/fastlane/metadata/android/vi/images/phoneScreenshots/7.png
index 8b377e490..a68b7ecc4 100644
Binary files a/fastlane/metadata/android/vi/images/phoneScreenshots/7.png and b/fastlane/metadata/android/vi/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/1.png b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/1.png
index 2903b3606..d7fe61ffe 100644
Binary files a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/1.png and b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/2.png b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/2.png
index 25a6fb2a1..698015cfa 100644
Binary files a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/2.png and b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/3.png b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/3.png
index c703cf985..0910dfb9e 100644
Binary files a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/3.png and b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/4.png b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/4.png
index a56e416f3..600ecea64 100644
Binary files a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/4.png and b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/5.png b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/5.png
index 3525b251d..7648f859c 100644
Binary files a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/5.png and b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/6.png b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/6.png
index c755cf723..50b62eb19 100644
Binary files a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/6.png and b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/7.png b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/7.png
index 1f7d88739..dea40fe35 100644
Binary files a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/7.png and b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/1.png b/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/1.png
index ccc1c93de..47d76667f 100644
Binary files a/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/1.png and b/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/2.png b/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/2.png
index 0cc2fad55..fa05e9e57 100644
Binary files a/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/2.png and b/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/3.png b/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/3.png
index 67eb4b503..e038cf9f5 100644
Binary files a/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/3.png and b/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/4.png b/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/4.png
index 5d48b69c5..01106a75b 100644
Binary files a/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/4.png and b/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/5.png b/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/5.png
index 14916233f..44047ca73 100644
Binary files a/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/5.png and b/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/6.png b/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/6.png
index 80467b75e..8d8faa386 100644
Binary files a/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/6.png and b/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/7.png b/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/7.png
index 6911abd49..109407bb0 100644
Binary files a/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/7.png and b/fastlane/metadata/android/zh-Hant/images/phoneScreenshots/7.png differ
diff --git a/l10n.yaml b/l10n.yaml
index 1a10e8fda..d004b1d61 100644
--- a/l10n.yaml
+++ b/l10n.yaml
@@ -2,9 +2,10 @@
# http://flutter.dev/go/i18n-user-guide
# https://docs.flutter.dev/development/accessibility-and-localization/internationalization
-# use defaults to:
-# - parse ARB files from `lib/l10n`
-# - generate class `AppLocalizations` in `app_localizations.dart`
-
+arb-dir: lib/l10n
+output-dir: lib/l10ngen
+template-arb-file: app_en.arb
+output-localization-file: app_localizations.dart
preferred-supported-locales:
- en
+synthetic-package: false
diff --git a/lib/image_providers/app_icon_image_provider.dart b/lib/image_providers/app_icon_image_provider.dart
index 545fd1c45..1a0046b6a 100644
--- a/lib/image_providers/app_icon_image_provider.dart
+++ b/lib/image_providers/app_icon_image_provider.dart
@@ -1,10 +1,10 @@
import 'dart:ui' as ui;
import 'package:aves/services/common/services.dart';
+import 'package:aves_report/aves_report.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
-import 'package:transparent_image/transparent_image.dart';
class AppIconImage extends ImageProvider {
const AppIconImage({
@@ -39,12 +39,14 @@ class AppIconImage extends ImageProvider {
Future _loadAsync(AppIconImageKey key, ImageDecoderCallback decode) async {
try {
- final bytes = await appService.getAppIcon(key.packageName, key.size);
- final buffer = await ui.ImmutableBuffer.fromUint8List(bytes.isEmpty ? kTransparentImage : bytes);
- return await decode(buffer);
+ final descriptor = await appService.getAppIcon(key.packageName, key.size);
+ if (descriptor == null) {
+ throw UnreportedStateError('$packageName app icon decoding failed');
+ }
+ return descriptor.instantiateCodec();
} catch (error) {
debugPrint('$runtimeType _loadAsync failed with packageName=$packageName, error=$error');
- throw StateError('$packageName app icon decoding failed');
+ throw UnreportedStateError('$packageName app icon decoding failed');
}
}
}
diff --git a/lib/image_providers/descriptor_provider.dart b/lib/image_providers/descriptor_provider.dart
new file mode 100644
index 000000000..75f6d93cf
--- /dev/null
+++ b/lib/image_providers/descriptor_provider.dart
@@ -0,0 +1,45 @@
+import 'dart:ui' as ui;
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/widgets.dart';
+
+@immutable
+class DescriptorImageProvider extends ImageProvider {
+ const DescriptorImageProvider(this.descriptor, {this.scale = 1.0});
+
+ final ui.ImageDescriptor descriptor;
+ final double scale;
+
+ @override
+ Future obtainKey(ImageConfiguration configuration) {
+ return SynchronousFuture(this);
+ }
+
+ @override
+ ImageStreamCompleter loadImage(DescriptorImageProvider key, ImageDecoderCallback decode) {
+ return MultiFrameImageStreamCompleter(
+ codec: _loadAsync(key, decode: decode),
+ scale: key.scale,
+ debugLabel: 'DescriptorImageProvider(${describeIdentity(key.descriptor)})',
+ );
+ }
+
+ Future _loadAsync(DescriptorImageProvider key, {required ImageDecoderCallback decode}) async {
+ assert(key == this);
+ return descriptor.instantiateCodec();
+ }
+
+ @override
+ bool operator ==(Object other) {
+ if (other.runtimeType != runtimeType) {
+ return false;
+ }
+ return other is DescriptorImageProvider && other.descriptor == descriptor && other.scale == scale;
+ }
+
+ @override
+ int get hashCode => Object.hash(descriptor.hashCode, scale);
+
+ @override
+ String toString() => '${objectRuntimeType(this, 'DescriptorImageProvider')}(${describeIdentity(descriptor)}, scale: ${scale.toStringAsFixed(1)})';
+}
diff --git a/lib/image_providers/region_provider.dart b/lib/image_providers/region_provider.dart
index dc85ccaa1..f52f8593b 100644
--- a/lib/image_providers/region_provider.dart
+++ b/lib/image_providers/region_provider.dart
@@ -3,6 +3,7 @@ import 'dart:math';
import 'dart:ui' as ui;
import 'package:aves/services/common/services.dart';
+import 'package:aves_report/aves_report.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
@@ -33,7 +34,7 @@ class RegionProvider extends ImageProvider {
final mimeType = key.mimeType;
final pageId = key.pageId;
try {
- final bytes = await mediaFetchService.getRegion(
+ final descriptor = await mediaFetchService.getRegion(
uri,
mimeType,
key.rotationDegrees,
@@ -45,15 +46,14 @@ class RegionProvider extends ImageProvider {
sizeBytes: key.sizeBytes,
taskKey: key,
);
- if (bytes.isEmpty) {
- throw StateError('$uri ($mimeType) region loading failed');
+ if (descriptor == null) {
+ throw UnreportedStateError('$uri ($mimeType) region loading failed');
}
- final buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
- return await decode(buffer);
+ return descriptor.instantiateCodec();
} catch (error) {
// loading may fail if the provided MIME type is incorrect (e.g. the Media Store may report a JPEG as a TIFF)
debugPrint('$runtimeType _loadAsync failed with mimeType=$mimeType, uri=$uri, error=$error');
- throw StateError('$mimeType region decoding failed (page $pageId)');
+ throw UnreportedStateError('$mimeType region decoding failed (page $pageId)');
}
}
diff --git a/lib/image_providers/thumbnail_provider.dart b/lib/image_providers/thumbnail_provider.dart
index c9f868296..cee22ab83 100644
--- a/lib/image_providers/thumbnail_provider.dart
+++ b/lib/image_providers/thumbnail_provider.dart
@@ -36,25 +36,24 @@ class ThumbnailProvider extends ImageProvider {
final mimeType = key.mimeType;
final pageId = key.pageId;
try {
- final bytes = await mediaFetchService.getThumbnail(
+ final descriptor = await mediaFetchService.getThumbnail(
uri: uri,
mimeType: mimeType,
pageId: pageId,
rotationDegrees: key.rotationDegrees,
isFlipped: key.isFlipped,
- dateModifiedSecs: key.dateModifiedSecs,
+ dateModifiedMillis: key.dateModifiedMillis,
extent: key.extent,
taskKey: key,
);
- if (bytes.isEmpty) {
- throw UnreportedStateError('$uri ($mimeType) loading failed');
+ if (descriptor == null) {
+ throw UnreportedStateError('$uri ($mimeType) thumbnail loading failed');
}
- final buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
- return await decode(buffer);
+ return descriptor.instantiateCodec();
} catch (error) {
// loading may fail if the provided MIME type is incorrect (e.g. the Media Store may report a JPEG as a TIFF)
debugPrint('$runtimeType _loadAsync failed with mimeType=$mimeType, uri=$uri, error=$error');
- throw UnreportedStateError('$mimeType decoding failed (page $pageId)');
+ throw UnreportedStateError('$mimeType thumbnail decoding failed (page $pageId)');
}
}
@@ -75,11 +74,11 @@ class ThumbnailProviderKey extends Equatable {
final int? pageId;
final int rotationDegrees;
final bool isFlipped;
- final int dateModifiedSecs;
+ final int dateModifiedMillis;
final double extent;
@override
- List