diff --git a/.flutter b/.flutter
index ba3931984..300451ada 160000
--- a/.flutter
+++ b/.flutter
@@ -1 +1 @@
-Subproject commit ba393198430278b6595976de84fe170f553cc728
+Subproject commit 300451adae589accbece3490f4396f10bdf15e6e
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3922d2d3e..42707db56 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.10.8] - 2024-04-01
+
+### Added
+
+- Collection: support for Fairphone burst pattern
+- Collection: allow using tags/make/model when bulk renaming
+- Video: A-B repeat
+- Settings: hidden items can be toggled
+
+### Changed
+
+- opening app from launcher always show home page
+- use dates with western arabic numerals for maghreb arabic locales
+- album unique names are case insensitive
+- upgraded Flutter to stable v3.19.5
+
+### Fixed
+
+- crash when decoding large region
+- viewer position drift during scale
+- viewer side gesture precedence (next entry by single tap vs zoom by double tap)
+
## [v1.10.7] - 2024-03-12
### Added
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 85f700135..be1a68f29 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -2,9 +2,10 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id 'com.android.application'
- id 'com.google.devtools.ksp' version "$ksp_version"
+ id 'com.google.devtools.ksp'
id 'kotlin-android'
id 'kotlin-kapt'
+ id 'dev.flutter.flutter-gradle-plugin'
}
def packageName = "deckers.thibault.aves"
@@ -20,11 +21,6 @@ if (localPropertiesFile.exists()) {
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
def flutterVersionName = localProperties.getProperty('flutter.versionName')
-def flutterRoot = localProperties.getProperty('flutter.sdk')
-if (flutterRoot == null) {
- throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
-}
-apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
// Keys
@@ -54,8 +50,8 @@ android {
ndkVersion '25.1.8937393'
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
lint {
@@ -181,12 +177,12 @@ android {
}
tasks.withType(KotlinCompile).configureEach {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
kotlin {
- jvmToolchain(8)
+ jvmToolchain(17)
}
flutter {
@@ -210,7 +206,7 @@ repositories {
}
dependencies {
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0'
implementation "androidx.appcompat:appcompat:1.6.1"
implementation 'androidx.core:core-ktx:1.12.0'
@@ -226,7 +222,7 @@ dependencies {
implementation "com.github.bumptech.glide:glide:$glide_version"
implementation 'com.google.android.material:material:1.11.0'
// SLF4J implementation for `mp4parser`
- implementation 'org.slf4j:slf4j-simple:2.0.11'
+ implementation 'org.slf4j:slf4j-simple:2.0.12'
// forked, built by JitPack:
// - https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
@@ -240,7 +236,7 @@ dependencies {
// huawei flavor only
huaweiImplementation "com.huawei.agconnect:agconnect-core:$huawei_agconnect_version"
- testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.1'
+ testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
kapt 'androidx.annotation:annotation:1.7.1'
ksp "com.github.bumptech.glide:ksp:$glide_version"
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index bc58e6d87..5e2437a2b 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -36,6 +36,7 @@
+
@@ -72,11 +73,11 @@
-
+
@@ -258,6 +259,7 @@
+
{
+ override fun extractIntentData(intent: Intent?): FieldMap {
return hashMapOf(
INTENT_DATA_KEY_ACTION to INTENT_ACTION_WIDGET_SETTINGS,
INTENT_DATA_KEY_WIDGET_ID to appWidgetId,
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt
index 8fecffb9e..c54b6820c 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt
@@ -175,18 +175,6 @@ open class MainActivity : FlutterFragmentActivity() {
}
}
- override fun onResume() {
- super.onResume()
- mediaStoreChangeStreamHandler.onAppResume()
- settingsChangeStreamHandler.onAppResume()
- }
-
- override fun onPause() {
- mediaStoreChangeStreamHandler.onAppPause()
- settingsChangeStreamHandler.onAppPause()
- super.onPause()
- }
-
override fun onStop() {
Log.i(LOG_TAG, "onStop")
super.onStop()
@@ -242,7 +230,7 @@ open class MainActivity : FlutterFragmentActivity() {
private fun onEditResult(resultCode: Int, intent: Intent?) {
val fields: FieldMap? = if (resultCode == RESULT_OK) hashMapOf(
- "uri" to intent?.data.toString(),
+ "uri" to intent?.data?.toString(),
"mimeType" to intent?.type,
) else null
pendingEditIntentHandler?.let { it(fields) }
@@ -279,21 +267,19 @@ open class MainActivity : FlutterFragmentActivity() {
}
}
- open fun extractIntentData(intent: Intent?): MutableMap {
+ open fun extractIntentData(intent: Intent?): FieldMap {
when (val action = intent?.action) {
Intent.ACTION_MAIN -> {
- if (intent.getBooleanExtra(EXTRA_KEY_SAFE_MODE, false)) {
- return hashMapOf(
- INTENT_DATA_KEY_SAFE_MODE to true,
- )
- }
+ val fields = hashMapOf(
+ INTENT_DATA_KEY_LAUNCHER to intent.hasCategory(Intent.CATEGORY_LAUNCHER),
+ INTENT_DATA_KEY_SAFE_MODE to intent.getBooleanExtra(EXTRA_KEY_SAFE_MODE, false),
+ )
intent.getStringExtra(EXTRA_KEY_PAGE)?.let { page ->
val filters = extractFiltersFromIntent(intent)
- return hashMapOf(
- INTENT_DATA_KEY_PAGE to page,
- INTENT_DATA_KEY_FILTERS to filters,
- )
+ fields[INTENT_DATA_KEY_PAGE] = page
+ fields[INTENT_DATA_KEY_FILTERS] = filters
}
+ return fields
}
Intent.ACTION_VIEW,
@@ -496,6 +482,7 @@ open class MainActivity : FlutterFragmentActivity() {
const val INTENT_DATA_KEY_ACTION = "action"
const val INTENT_DATA_KEY_ALLOW_MULTIPLE = "allowMultiple"
const val INTENT_DATA_KEY_FILTERS = "filters"
+ const val INTENT_DATA_KEY_LAUNCHER = "launcher"
const val INTENT_DATA_KEY_MIME_TYPE = "mimeType"
const val INTENT_DATA_KEY_PAGE = "page"
const val INTENT_DATA_KEY_QUERY = "query"
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/ScreenSaverSettingsActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/ScreenSaverSettingsActivity.kt
index dbc2e74f3..2d7e07077 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/ScreenSaverSettingsActivity.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/ScreenSaverSettingsActivity.kt
@@ -1,9 +1,10 @@
package deckers.thibault.aves
import android.content.Intent
+import deckers.thibault.aves.model.FieldMap
class ScreenSaverSettingsActivity : MainActivity() {
- override fun extractIntentData(intent: Intent?): MutableMap {
+ override fun extractIntentData(intent: Intent?): FieldMap {
return hashMapOf(
INTENT_DATA_KEY_ACTION to INTENT_ACTION_SCREEN_SAVER_SETTINGS,
)
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt
index 3c0b0d1f6..c37539172 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt
@@ -14,6 +14,7 @@ import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler
import deckers.thibault.aves.channel.calls.window.WindowHandler
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
import deckers.thibault.aves.channel.streams.MediaCommandStreamHandler
+import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.FlutterUtils
import deckers.thibault.aves.utils.FlutterUtils.enableSoftwareRendering
import deckers.thibault.aves.utils.LogUtils
@@ -25,7 +26,7 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
class WallpaperActivity : FlutterFragmentActivity() {
- private lateinit var intentDataMap: MutableMap
+ private lateinit var intentDataMap: FieldMap
private lateinit var mediaSessionHandler: MediaSessionHandler
override fun onCreate(savedInstanceState: Bundle?) {
@@ -103,7 +104,7 @@ class WallpaperActivity : FlutterFragmentActivity() {
}
}
- private fun extractIntentData(intent: Intent?): MutableMap {
+ private fun extractIntentData(intent: Intent?): FieldMap {
when (intent?.action) {
Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> {
(intent.data ?: intent.getParcelableExtraCompat(Intent.EXTRA_STREAM))?.let { uri ->
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchObjectHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchObjectHandler.kt
index d0d317c2a..941928831 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchObjectHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchObjectHandler.kt
@@ -36,13 +36,13 @@ class MediaFetchObjectHandler(private val context: Context) : MethodCallHandler
val provider = getProvider(context, uri)
if (provider == null) {
- result.error("getEntry-provider", "failed to find provider for uri=$uri", null)
+ result.error("getEntry-provider", "failed to find provider for uri=$uri mimeType=$mimeType", null)
return
}
provider.fetchSingle(context, uri, mimeType, object : ImageOpCallback {
override fun onSuccess(fields: FieldMap) = result.success(fields)
- override fun onFailure(throwable: Throwable) = result.error("getEntry-failure", "failed to get entry for uri=$uri", throwable.message)
+ override fun onFailure(throwable: Throwable) = result.error("getEntry-failure", "failed to get entry for uri=$uri mimeType=$mimeType", throwable.message)
})
}
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 b7e03b319..91170c56b 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
@@ -29,6 +29,7 @@ import com.drew.metadata.webp.WebpDirectory
import com.drew.metadata.xmp.XmpDirectory
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.metadata.ExifGeoTiffTags
+import deckers.thibault.aves.metadata.ExifInterfaceHelper
import deckers.thibault.aves.metadata.ExifInterfaceHelper.describeAll
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDateMillis
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDouble
@@ -110,7 +111,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
when (call.method) {
"getAllMetadata" -> ioScope.launch { safe(call, result, ::getAllMetadata) }
"getCatalogMetadata" -> ioScope.launch { safe(call, result, ::getCatalogMetadata) }
- "getFields" -> ioScope.launch { safe(call, result, ::getFields) }
+ "getOverlayMetadata" -> ioScope.launch { safe(call, result, ::getOverlayMetadata) }
"getGeoTiffInfo" -> ioScope.launch { safe(call, result, ::getGeoTiffInfo) }
"getMultiPageInfo" -> ioScope.launch { safe(call, result, ::getMultiPageInfo) }
"getPanoramaInfo" -> ioScope.launch { safe(call, result, ::getPanoramaInfo) }
@@ -119,6 +120,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
"hasContentResolverProp" -> ioScope.launch { safe(call, result, ::hasContentProp) }
"getContentResolverProp" -> ioScope.launch { safe(call, result, ::getContentPropValue) }
"getDate" -> ioScope.launch { safe(call, result, ::getDate) }
+ "getFields" -> ioScope.launch { safe(call, result, ::getFields) }
else -> result.notImplemented()
}
}
@@ -815,7 +817,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
}
}
- private fun getFields(call: MethodCall, result: MethodChannel.Result) {
+ private fun getOverlayMetadata(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument("mimeType")
val uri = call.argument("uri")?.let { Uri.parse(it) }
val sizeBytes = call.argument("sizeBytes")?.toLong()
@@ -1250,6 +1252,71 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
result.success(dateMillis)
}
+ private fun getFields(call: MethodCall, result: MethodChannel.Result) {
+ val mimeType = call.argument("mimeType")
+ val uri = call.argument("uri")?.let { Uri.parse(it) }
+ val sizeBytes = call.argument("sizeBytes")?.toLong()
+ val fields = call.argument>("fields")
+ if (mimeType == null || uri == null || fields == null) {
+ result.error("getFields-args", "missing arguments", null)
+ return
+ }
+
+ val metadataMap = HashMap()
+ if (fields.isEmpty() || isVideo(mimeType)) {
+ result.success(metadataMap)
+ return
+ }
+
+ var foundExif = false
+ if (canReadWithMetadataExtractor(mimeType)) {
+ try {
+ Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
+ val metadata = Helper.safeRead(input)
+ for (dir in metadata.getDirectoriesOfType(ExifDirectoryBase::class.java)) {
+ foundExif = true
+ val allTags = ExifInterfaceHelper.allTags
+ fields.forEach { tag ->
+ allTags[tag]?.let { mapper ->
+ val tagType = mapper.type
+ dir.getDescription(tagType)?.let { value -> metadataMap[tag] = value }
+ }
+ }
+ }
+ }
+ } catch (e: Exception) {
+ Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
+ } catch (e: NoClassDefFoundError) {
+ Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
+ } catch (e: AssertionError) {
+ Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
+ }
+ }
+
+ if (!foundExif && canReadWithExifInterface(mimeType)) {
+ // fallback to read EXIF via ExifInterface
+ try {
+ Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
+ val exif = ExifInterface(input)
+ fields.forEach { tag ->
+ if (exif.hasAttribute(tag)) {
+ val value = exif.getAttribute(tag)
+ if (value != null) {
+ metadataMap[tag] = value
+ }
+ }
+ }
+ }
+ } catch (e: Exception) {
+ // ExifInterface initialization can fail with a RuntimeException
+ // caused by an internal MediaMetadataRetriever failure
+ Log.w(LOG_TAG, "failed to get metadata by ExifInterface for mimeType=$mimeType uri=$uri", e)
+ }
+ }
+
+ result.success(metadataMap)
+ }
+
companion object {
private val LOG_TAG = LogUtils.createTag()
const val CHANNEL = "deckers.thibault/aves/metadata_fetch"
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/StorageHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/StorageHandler.kt
index a51aac145..762e0e091 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/StorageHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/StorageHandler.kt
@@ -135,7 +135,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
}
val trashDirs = context.getExternalFilesDirs(null).mapNotNull { StorageUtils.trashDirFor(context, it.path) }
- val trashItemPaths = trashDirs.flatMap { dir -> dir.listFiles()?.mapNotNull { file -> file?.path } ?: listOf() }
+ val trashItemPaths = trashDirs.flatMap { dir -> dir.listFiles()?.filterNotNull()?.mapNotNull { file -> file.path } ?: listOf() }
val untrackedPaths = trashItemPaths.filterNot(knownPaths::contains).toList()
result.success(untrackedPaths)
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 2cf30d200..9d319e7db 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
@@ -12,7 +12,9 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
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.MemoryUtils
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.StorageUtils
import io.flutter.plugin.common.MethodChannel
@@ -73,7 +75,7 @@ class RegionFetcher internal constructor(
BitmapRegionDecoderCompat.newInstance(input)
}
if (newDecoder == null) {
- result.error("getRegion-read-null", "failed to open file for mimeType=$mimeType uri=$uri regionRect=$regionRect", null)
+ result.error("fetch-read-null", "failed to open file for mimeType=$mimeType uri=$uri regionRect=$regionRect", null)
return
}
currentDecoderRef = LastDecoderRef(uri, newDecoder)
@@ -96,14 +98,22 @@ class RegionFetcher internal constructor(
regionRect
}
+ // 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() / sampleSize
+ 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 bitmap = decoder.decodeRegion(effectiveRect, options)
if (bitmap != null) {
result.success(bitmap.getBytes(MimeTypes.canHaveAlpha(mimeType), recycle = true))
} else {
- result.error("getRegion-null", "failed to decode region for uri=$uri regionRect=$regionRect", null)
+ result.error("fetch-null", "failed to decode region for uri=$uri regionRect=$regionRect", null)
}
} catch (e: Exception) {
- result.error("getRegion-read-exception", "failed to initialize region decoder for uri=$uri regionRect=$regionRect", e.message)
+ result.error("fetch-read-exception", "failed to initialize region decoder for uri=$uri regionRect=$regionRect", e.message)
}
}
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 882844c6e..2c18d004a 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
@@ -11,6 +11,7 @@ import com.caverock.androidsvg.RenderOptions
import com.caverock.androidsvg.SVG
import com.caverock.androidsvg.SVGParseException
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.MemoryUtils
import deckers.thibault.aves.utils.StorageUtils
@@ -116,8 +117,4 @@ class SvgRegionFetcher internal constructor(
val uri: Uri,
val svg: SVG,
)
-
- companion object {
- const val ARGB_8888_BYTE_SIZE = 4
- }
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ActivityResultStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ActivityResultStreamHandler.kt
index 773ef25f2..9fd0d45a0 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ActivityResultStreamHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ActivityResultStreamHandler.kt
@@ -198,7 +198,7 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
.setDataAndType(AppAdapterHandler.getShareableUri(activity, Uri.parse(uri)), mimeType)
if (intent.resolveActivity(activity.packageManager) == null) {
- error("edit-resolve", "cannot resolve activity for this intent", null)
+ error("edit-resolve", "cannot resolve activity for this intent for uri=$uri mimeType=$mimeType", null)
return
}
@@ -207,7 +207,7 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
endOfStream()
}
if (!safeStartActivityForResult(intent, MainActivity.EDIT_REQUEST)) {
- error("edit-start", "cannot start activity for this intent", null)
+ error("edit-start", "cannot start activity for this intent for uri=$uri mimeType=$mimeType", null)
}
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreChangeStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreChangeStreamHandler.kt
index fd0c6eb5f..e926b5b6a 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreChangeStreamHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreChangeStreamHandler.kt
@@ -30,14 +30,6 @@ class MediaStoreChangeStreamHandler(private val context: Context) : EventChannel
}
init {
- onAppResume()
- }
-
- fun dispose() {
- onAppPause()
- }
-
- fun onAppResume() {
Log.i(LOG_TAG, "start listening to Media Store")
context.contentResolver.apply {
registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, contentObserver)
@@ -45,7 +37,7 @@ class MediaStoreChangeStreamHandler(private val context: Context) : EventChannel
}
}
- fun onAppPause() {
+ fun dispose() {
Log.i(LOG_TAG, "stop listening to Media Store")
context.contentResolver.unregisterContentObserver(contentObserver)
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/SettingsChangeStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/SettingsChangeStreamHandler.kt
index 689cc6752..915f900c5 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/SettingsChangeStreamHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/SettingsChangeStreamHandler.kt
@@ -62,19 +62,11 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
}
init {
- onAppResume()
- }
-
- fun dispose() {
- onAppPause()
- }
-
- fun onAppResume() {
Log.i(LOG_TAG, "start listening to system settings")
context.contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, contentObserver)
}
- fun onAppPause() {
+ fun dispose() {
Log.i(LOG_TAG, "stop listening to system settings")
context.contentResolver.unregisterContentObserver(contentObserver)
}
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 9d51d4008..95999d60e 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
@@ -20,6 +20,8 @@ object BitmapUtils {
private val freeBaos = ArrayList()
private val mutex = Mutex()
+ const val ARGB_8888_BYTE_SIZE = 4
+
suspend fun Bitmap.getBytes(canHaveAlpha: Boolean = false, quality: Int = 100, recycle: Boolean): ByteArray? {
val stream: ByteArrayOutputStream
mutex.withLock {
diff --git a/android/build.gradle b/android/build.gradle
index 3d2b26752..84c86585a 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,8 +1,5 @@
buildscript {
ext {
- kotlin_version = '1.9.21'
- ksp_version = "$kotlin_version-1.0.15"
- agp_version = '8.2.2'
glide_version = '4.16.0'
// AppGallery Connect plugin versions: https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-sdk-changenotes-0000001058732550
huawei_agconnect_version = '1.9.1.300'
@@ -22,9 +19,6 @@ buildscript {
}
dependencies {
- classpath "com.android.tools.build:gradle:$agp_version"
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
-
if (useCrashlytics) {
// GMS & Firebase Crashlytics (used by some flavors only)
classpath 'com.google.gms:google-services:4.4.0'
diff --git a/android/exifinterface/build.gradle b/android/exifinterface/build.gradle
index 48a08ec5c..76ba4c97f 100644
--- a/android/exifinterface/build.gradle
+++ b/android/exifinterface/build.gradle
@@ -20,8 +20,8 @@ android {
}
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
}
diff --git a/android/gradle.properties b/android/gradle.properties
index 318036f25..c795f344f 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -6,13 +6,13 @@
# 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=-Xmx2048m -Dfile.encoding=UTF-8
-# AndroidX package structure to make it clearer which packages are bundled with the
-# Android operating system, and which are packaged with your app"s APK
-# https://developer.android.com/topic/libraries/support-library/androidx-rn
+org.gradle.jvmargs=-Xmx4G -Dfile.encoding=UTF-8
android.useAndroidX=true
+android.enableJetifier=true
+
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
+
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index 7666e22b5..11fce01a1 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
diff --git a/android/settings.gradle b/android/settings.gradle
new file mode 100644
index 000000000..16f0b45bc
--- /dev/null
+++ b/android/settings.gradle
@@ -0,0 +1,33 @@
+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 = '1.9.21'
+ settings.ext.ksp_version = "$kotlin_version-1.0.15"
+ settings.ext.agp_version = '8.3.1'
+
+ 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
deleted file mode 100644
index 75e605d83..000000000
--- a/android/settings.gradle.kts
+++ /dev/null
@@ -1,27 +0,0 @@
-pluginManagement {
- repositories {
- google()
- mavenCentral()
- gradlePluginPortal()
- }
-}
-
-plugins {
- id("org.gradle.toolchains.foojay-resolver-convention") version ("0.4.0")
-}
-
-include(":app")
-
-val localPropertiesFile = File(rootProject.projectDir, "local.properties")
-val properties = java.util.Properties()
-
-assert(localPropertiesFile.exists())
-localPropertiesFile.reader(Charsets.UTF_8).also { reader -> properties.load(reader) }
-
-val flutterSdkPath: String? = properties.getProperty("flutter.sdk")
-assert(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
-
-apply {
- from("$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle")
-}
-include(":exifinterface")
diff --git a/fastlane/metadata/android/en-US/changelogs/105.txt b/fastlane/metadata/android/en-US/changelogs/105.txt
deleted file mode 100644
index bf6045a0b..000000000
--- a/fastlane/metadata/android/en-US/changelogs/105.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.9.4:
-- play your animated AVIF, AV1, and HDR videos
-- filter by rating ranges
-- judge tonal distributions with the viewer histogram
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/10501.txt b/fastlane/metadata/android/en-US/changelogs/10501.txt
deleted file mode 100644
index bf6045a0b..000000000
--- a/fastlane/metadata/android/en-US/changelogs/10501.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.9.4:
-- play your animated AVIF, AV1, and HDR videos
-- filter by rating ranges
-- judge tonal distributions with the viewer histogram
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/106.txt b/fastlane/metadata/android/en-US/changelogs/106.txt
deleted file mode 100644
index 30de5ad44..000000000
--- a/fastlane/metadata/android/en-US/changelogs/106.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.9.5:
-- play your animated AVIF, AV1, and HDR videos
-- filter by rating ranges
-- judge tonal distributions with the viewer histogram
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/10601.txt b/fastlane/metadata/android/en-US/changelogs/10601.txt
deleted file mode 100644
index 30de5ad44..000000000
--- a/fastlane/metadata/android/en-US/changelogs/10601.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.9.5:
-- play your animated AVIF, AV1, and HDR videos
-- filter by rating ranges
-- judge tonal distributions with the viewer histogram
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/107.txt b/fastlane/metadata/android/en-US/changelogs/107.txt
deleted file mode 100644
index f1cd4d847..000000000
--- a/fastlane/metadata/android/en-US/changelogs/107.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.9.6:
-- play your animated AVIF, AV1, and HDR videos
-- filter by rating ranges
-- judge tonal distributions with the viewer histogram
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/10701.txt b/fastlane/metadata/android/en-US/changelogs/10701.txt
deleted file mode 100644
index f1cd4d847..000000000
--- a/fastlane/metadata/android/en-US/changelogs/10701.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.9.6:
-- play your animated AVIF, AV1, and HDR videos
-- filter by rating ranges
-- judge tonal distributions with the viewer histogram
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/108.txt b/fastlane/metadata/android/en-US/changelogs/108.txt
deleted file mode 100644
index 59b146938..000000000
--- a/fastlane/metadata/android/en-US/changelogs/108.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-In v1.9.7:
-- enjoy the app in Slovak & Vietnamese
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/10801.txt b/fastlane/metadata/android/en-US/changelogs/10801.txt
deleted file mode 100644
index 59b146938..000000000
--- a/fastlane/metadata/android/en-US/changelogs/10801.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-In v1.9.7:
-- enjoy the app in Slovak & Vietnamese
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/109.txt b/fastlane/metadata/android/en-US/changelogs/109.txt
deleted file mode 100644
index 3c18fca39..000000000
--- a/fastlane/metadata/android/en-US/changelogs/109.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.0:
-- cast images via DLNA/UPnP
-- enjoy the app in Icelandic
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/10901.txt b/fastlane/metadata/android/en-US/changelogs/10901.txt
deleted file mode 100644
index 3c18fca39..000000000
--- a/fastlane/metadata/android/en-US/changelogs/10901.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.0:
-- cast images via DLNA/UPnP
-- enjoy the app in Icelandic
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/110.txt b/fastlane/metadata/android/en-US/changelogs/110.txt
deleted file mode 100644
index 69b5f63e7..000000000
--- a/fastlane/metadata/android/en-US/changelogs/110.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.1:
-- JPEG MPF support
-- enjoy the app in Arabic & Belarusian
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/11001.txt b/fastlane/metadata/android/en-US/changelogs/11001.txt
deleted file mode 100644
index 69b5f63e7..000000000
--- a/fastlane/metadata/android/en-US/changelogs/11001.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.1:
-- JPEG MPF support
-- enjoy the app in Arabic & Belarusian
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/111.txt b/fastlane/metadata/android/en-US/changelogs/111.txt
deleted file mode 100644
index 3278d2a31..000000000
--- a/fastlane/metadata/android/en-US/changelogs/111.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.2:
-- JPEG MPF support
-- enjoy the app in Arabic & Belarusian
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/11101.txt b/fastlane/metadata/android/en-US/changelogs/11101.txt
deleted file mode 100644
index 3278d2a31..000000000
--- a/fastlane/metadata/android/en-US/changelogs/11101.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.2:
-- JPEG MPF support
-- enjoy the app in Arabic & Belarusian
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/112.txt b/fastlane/metadata/android/en-US/changelogs/112.txt
deleted file mode 100644
index 2060b1138..000000000
--- a/fastlane/metadata/android/en-US/changelogs/112.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.3:
-- customize your home page
-- analyze your images with the histogram (for real this time)
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/11201.txt b/fastlane/metadata/android/en-US/changelogs/11201.txt
deleted file mode 100644
index 2060b1138..000000000
--- a/fastlane/metadata/android/en-US/changelogs/11201.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.3:
-- customize your home page
-- analyze your images with the histogram (for real this time)
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/113.txt b/fastlane/metadata/android/en-US/changelogs/113.txt
deleted file mode 100644
index e08549f81..000000000
--- a/fastlane/metadata/android/en-US/changelogs/113.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.4:
-- customize your home page
-- analyze your images with the histogram (for real this time)
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/11301.txt b/fastlane/metadata/android/en-US/changelogs/11301.txt
deleted file mode 100644
index e08549f81..000000000
--- a/fastlane/metadata/android/en-US/changelogs/11301.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.4:
-- customize your home page
-- analyze your images with the histogram (for real this time)
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/114.txt b/fastlane/metadata/android/en-US/changelogs/114.txt
deleted file mode 100644
index 49ab02a6b..000000000
--- a/fastlane/metadata/android/en-US/changelogs/114.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-In v1.10.5:
-- enjoy the app in Catalan
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/11401.txt b/fastlane/metadata/android/en-US/changelogs/11401.txt
deleted file mode 100644
index 49ab02a6b..000000000
--- a/fastlane/metadata/android/en-US/changelogs/11401.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-In v1.10.5:
-- enjoy the app in Catalan
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/117.txt b/fastlane/metadata/android/en-US/changelogs/117.txt
new file mode 100644
index 000000000..720ed4ce1
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/117.txt
@@ -0,0 +1,4 @@
+In v1.10.8:
+- rename in bulk using tags
+- repeat a section section section of a video
+Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/11701.txt b/fastlane/metadata/android/en-US/changelogs/11701.txt
new file mode 100644
index 000000000..720ed4ce1
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/11701.txt
@@ -0,0 +1,4 @@
+In v1.10.8:
+- rename in bulk using tags
+- repeat a section section section of a video
+Full changelog available on GitHub
\ No newline at end of file
diff --git a/lib/convert/metadata/fields.dart b/lib/convert/metadata/fields.dart
index c19f0f91b..a0509b563 100644
--- a/lib/convert/metadata/fields.dart
+++ b/lib/convert/metadata/fields.dart
@@ -43,6 +43,8 @@ extension ExtraMetadataFieldConvert on MetadataField {
case MetadataField.exifGpsTrackRef:
case MetadataField.exifGpsVersionId:
case MetadataField.exifImageDescription:
+ case MetadataField.exifMake:
+ case MetadataField.exifModel:
case MetadataField.exifUserComment:
return MetadataType.exif;
case MetadataField.mp4GpsCoordinates:
@@ -145,6 +147,10 @@ extension ExtraMetadataFieldConvert on MetadataField {
return 'GPSVersionID';
case MetadataField.exifImageDescription:
return 'ImageDescription';
+ case MetadataField.exifMake:
+ return 'Make';
+ case MetadataField.exifModel:
+ return 'Model';
case MetadataField.exifUserComment:
return 'UserComment';
default:
diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb
index e95890030..472c39041 100644
--- a/lib/l10n/app_ar.arb
+++ b/lib/l10n/app_ar.arb
@@ -1524,5 +1524,13 @@
"collectionActionSetHome": "تعيين كخلفية",
"@collectionActionSetHome": {},
"setHomeCustomCollection": "مجموعة مخصصة",
- "@setHomeCustomCollection": {}
+ "@setHomeCustomCollection": {},
+ "videoActionABRepeat": "تكرار A-B",
+ "@videoActionABRepeat": {},
+ "videoRepeatActionSetEnd": "تعيين نهاية التشغيل",
+ "@videoRepeatActionSetEnd": {},
+ "stopTooltip": "توقف",
+ "@stopTooltip": {},
+ "videoRepeatActionSetStart": "تعيين بداية التشغيل",
+ "@videoRepeatActionSetStart": {}
}
diff --git a/lib/l10n/app_be.arb b/lib/l10n/app_be.arb
index 59c413d5d..6477c7184 100644
--- a/lib/l10n/app_be.arb
+++ b/lib/l10n/app_be.arb
@@ -5,7 +5,7 @@
"@welcomeTermsToggle": {},
"welcomeOptional": "Неабавязковыя",
"@welcomeOptional": {},
- "welcomeMessage": "Сардэчна запрашаем у Aves",
+ "welcomeMessage": "Сардэчна запрашаем ў Aves",
"@welcomeMessage": {},
"itemCount": "{count, plural, =1{1 элемент} other{{count} элементаў}}",
"@itemCount": {
@@ -38,7 +38,7 @@
"@saveTooltip": {},
"doNotAskAgain": "Больш не пытайся",
"@doNotAskAgain": {},
- "chipActionGoToCountryPage": "Паказаць у краінах",
+ "chipActionGoToCountryPage": "Паказаць ў Краінах",
"@chipActionGoToCountryPage": {},
"chipActionFilterOut": "Адфільтраваць",
"@chipActionFilterOut": {},
@@ -56,17 +56,17 @@
"@sourceStateCataloguing": {},
"chipActionDelete": "Выдаліць",
"@chipActionDelete": {},
- "chipActionGoToAlbumPage": "Паказаць у альбомах",
+ "chipActionGoToAlbumPage": "Паказаць ў Альбомах",
"@chipActionGoToAlbumPage": {},
"chipActionHide": "Схаваць",
"@chipActionHide": {},
"chipActionCreateVault": "Стварыце сховішча",
"@chipActionCreateVault": {},
- "chipActionGoToPlacePage": "Паказаць у месцах",
+ "chipActionGoToPlacePage": "Паказаць ў Лакацыях",
"@chipActionGoToPlacePage": {},
"chipActionUnpin": "Адмацаваць зверху",
"@chipActionUnpin": {},
- "chipActionGoToTagPage": "Паказаць у тэгах",
+ "chipActionGoToTagPage": "Паказаць ў Тэгах",
"@chipActionGoToTagPage": {},
"chipActionLock": "Заблакаваць",
"@chipActionLock": {},
@@ -76,7 +76,7 @@
"@chipActionRename": {},
"chipActionConfigureVault": "Наладзіць сховішча",
"@chipActionConfigureVault": {},
- "entryActionCopyToClipboard": "Скапіраваць у буфер абмену",
+ "entryActionCopyToClipboard": "Скапіяваць ў буфер абмену",
"@entryActionCopyToClipboard": {},
"entryActionDelete": "Выдаліць",
"@entryActionDelete": {},
@@ -120,13 +120,13 @@
"@entryActionRotateScreen": {},
"entryActionViewSource": "Паглядзець крыніцу",
"@entryActionViewSource": {},
- "entryActionConvertMotionPhotoToStillImage": "Пераўтварыць у нерухомую выяву",
+ "entryActionConvertMotionPhotoToStillImage": "Пераўтварыць ў нерухомую выяву",
"@entryActionConvertMotionPhotoToStillImage": {},
"entryActionViewMotionPhotoVideo": "Адкрыць відэа",
"@entryActionViewMotionPhotoVideo": {},
"entryActionSetAs": "Ўсталяваць як",
"@entryActionSetAs": {},
- "entryActionAddFavourite": "Дадаць у абранае",
+ "entryActionAddFavourite": "Дадаць ў абранае",
"@entryActionAddFavourite": {},
"videoActionUnmute": "Ўключыць гук",
"@videoActionUnmute": {},
@@ -188,11 +188,11 @@
"@entryActionEdit": {},
"entryActionOpen": "Адкрыць з дапамогай",
"@entryActionOpen": {},
- "entryActionOpenMap": "Паказаць у праграме карты",
+ "entryActionOpenMap": "Паказаць ў праграме карты",
"@entryActionOpenMap": {},
"videoActionMute": "Адключыць гук",
"@videoActionMute": {},
- "slideshowActionShowInCollection": "Паказаць у калекцыі",
+ "slideshowActionShowInCollection": "Паказаць ў Калекцыі",
"@slideshowActionShowInCollection": {},
"entryInfoActionEditDate": "Рэдагаваць дату і час",
"@entryInfoActionEditDate": {},
@@ -363,7 +363,7 @@
"@vaultLockTypePassword": {},
"settingsVideoEnablePip": "Карцінка ў карцінцы",
"@settingsVideoEnablePip": {},
- "videoControlsPlayOutside": "Адкрыць у іншым прайгравальніку",
+ "videoControlsPlayOutside": "Адкрыць ў іншым прайгравальніку",
"@videoControlsPlayOutside": {},
"videoControlsPlay": "Прайграванне",
"@videoControlsPlay": {},
@@ -478,7 +478,7 @@
}
}
},
- "addShortcutDialogLabel": "Ярлык хуткага доступу",
+ "addShortcutDialogLabel": "Назва ярлыка",
"@addShortcutDialogLabel": {},
"addShortcutButtonLabel": "ДАДАЦЬ",
"@addShortcutButtonLabel": {},
@@ -507,7 +507,7 @@
"@newAlbumDialogTitle": {},
"newAlbumDialogNameLabel": "Назва альбома",
"@newAlbumDialogNameLabel": {},
- "newAlbumDialogNameLabelAlreadyExistsHelper": "Каталог ужо існуе",
+ "newAlbumDialogNameLabelAlreadyExistsHelper": "Каталог ўжо існуе",
"@newAlbumDialogNameLabelAlreadyExistsHelper": {},
"newAlbumDialogStorageLabel": "Захоўванне:",
"@newAlbumDialogStorageLabel": {},
@@ -691,13 +691,13 @@
"@aboutBugReportInstruction": {},
"entryActionCast": "Трансляцыя",
"@entryActionCast": {},
- "hideFilterConfirmationDialogMessage": "Адпаведныя фота і відэа будуць схаваны з вашай калекцыі. Вы можаце убачыць іх зноў у наладах «Прыватнасць».\n\nВы ўпэўнены, што хочаце іх схаваць?",
+ "hideFilterConfirmationDialogMessage": "Адпаведныя фота і відэа будуць схаваны з вашай калекцыі. Вы можаце убачыць іх зноў ў наладах «Прыватнасць».\n\nВы ўпэўнены, што хочаце іх схаваць?",
"@hideFilterConfirmationDialogMessage": {},
"renameEntrySetPagePatternFieldLabel": "Шаблон наймення",
"@renameEntrySetPagePatternFieldLabel": {},
"renameAlbumDialogLabel": "Новая назва",
"@renameAlbumDialogLabel": {},
- "renameAlbumDialogLabelAlreadyExistsHelper": "Каталог ужо ёсць",
+ "renameAlbumDialogLabelAlreadyExistsHelper": "Каталог ўжо ёсць",
"@renameAlbumDialogLabelAlreadyExistsHelper": {},
"aboutBugReportButton": "Адправіць справаздачу",
"@aboutBugReportButton": {},
@@ -781,7 +781,7 @@
"@statsPageTitle": {},
"settingsUnitSystemDialogTitle": "Адзінкі вымярэння",
"@settingsUnitSystemDialogTitle": {},
- "editEntryDialogCopyFromItem": "Скапіюваць з іншага элемента",
+ "editEntryDialogCopyFromItem": "Скапіяваць з іншага элемента",
"@editEntryDialogCopyFromItem": {},
"settingsThemeEnableDynamicColor": "Дынамічны колер",
"@settingsThemeEnableDynamicColor": {},
@@ -791,13 +791,13 @@
"@renameEntrySetPageInsertTooltip": {},
"settingsThemeBrightnessTile": "Тэма",
"@settingsThemeBrightnessTile": {},
- "settingsSystemDefault": "Як у сістэме",
+ "settingsSystemDefault": "Як ў сістэме",
"@settingsSystemDefault": {},
"settingsCollectionTile": "Калекцыя",
"@settingsCollectionTile": {},
"settingsThemeBrightnessDialogTitle": "Тэма",
"@settingsThemeBrightnessDialogTitle": {},
- "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Выдаліць гэты альбом і элемент у ім?} few{Выдаліць гэты альбом і {count} элементы у ім?} other{Выдаліць гэты альбом і {count} элементаў у ім?}}",
+ "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Выдаліць гэты альбом і элемент ў ім?} few{Выдаліць гэты альбом і {count} элементы ў ім?} other{Выдаліць гэты альбом і {count} элементаў ў ім?}}",
"@deleteSingleAlbumConfirmationDialogMessage": {
"placeholders": {
"count": {}
@@ -853,7 +853,7 @@
"@tileLayoutMosaic": {},
"collectionDeselectSectionTooltip": "Адмяніць выбар раздзела",
"@collectionDeselectSectionTooltip": {},
- "settingsKeepScreenOnTile": "Трымаць экран уключаным",
+ "settingsKeepScreenOnTile": "Трымаць экран ўключаным",
"@settingsKeepScreenOnTile": {},
"tileLayoutGrid": "Сетка",
"@tileLayoutGrid": {},
@@ -895,7 +895,7 @@
"@searchCountriesSectionTitle": {},
"settingsAskEverytime": "Пытацца кожны раз",
"@settingsAskEverytime": {},
- "editEntryDateDialogCopyField": "Капіяваць з іншай даты",
+ "editEntryDateDialogCopyField": "Скапіяваць з іншай даты",
"@editEntryDateDialogCopyField": {},
"searchTagsSectionTitle": "Тэгі",
"@searchTagsSectionTitle": {},
@@ -953,7 +953,7 @@
"@albumPickPageTitlePick": {},
"menuActionMap": "Карта",
"@menuActionMap": {},
- "collectionActionMove": "Перамясціць у альбом",
+ "collectionActionMove": "Перамясціць ў альбом",
"@collectionActionMove": {},
"searchAlbumsSectionTitle": "Альбомы",
"@searchAlbumsSectionTitle": {},
@@ -1013,9 +1013,9 @@
"@albumPageTitle": {},
"editEntryLocationDialogTitle": "Месцазнаходжанне",
"@editEntryLocationDialogTitle": {},
- "albumPickPageTitleCopy": "Капіюваць у альбом",
+ "albumPickPageTitleCopy": "Скапіяваць ў альбом",
"@albumPickPageTitleCopy": {},
- "collectionActionCopy": "Скапіюваць у альбом",
+ "collectionActionCopy": "Скапіяваць ў альбом",
"@collectionActionCopy": {},
"viewDialogReverseSortOrder": "Адваротны парадак сартавання",
"@viewDialogReverseSortOrder": {},
@@ -1033,7 +1033,7 @@
"@tagEmpty": {},
"collectionActionShowTitleSearch": "Паказаць фільтр загалоўка",
"@collectionActionShowTitleSearch": {},
- "menuActionSelectAll": "Выбраць усё",
+ "menuActionSelectAll": "Выбраць ўсё",
"@menuActionSelectAll": {},
"settingsConfirmationTile": "Дыялогі пацверджання",
"@settingsConfirmationTile": {},
@@ -1059,7 +1059,7 @@
"@drawerCollectionAnimated": {},
"durationDialogHours": "Гадзіны",
"@durationDialogHours": {},
- "settingsKeepScreenOnDialogTitle": "Трымаць экран уключаным",
+ "settingsKeepScreenOnDialogTitle": "Трымаць экран ўключаным",
"@settingsKeepScreenOnDialogTitle": {},
"drawerPlacePage": "Месцы",
"@drawerPlacePage": {},
@@ -1077,7 +1077,7 @@
"@appExportFavourites": {},
"collectionEmptyImages": "Няма выяў",
"@collectionEmptyImages": {},
- "albumPickPageTitleExport": "Экспартаваць у альбом",
+ "albumPickPageTitleExport": "Экспартаваць ў альбом",
"@albumPickPageTitleExport": {},
"settingsActionExportDialogTitle": "Экспарт",
"@settingsActionExportDialogTitle": {},
@@ -1149,7 +1149,7 @@
"@coverDialogTabColor": {},
"genericSuccessFeedback": "Гатова!",
"@genericSuccessFeedback": {},
- "aboutLicensesShowAllButtonLabel": "Паказаць усе ліцэнзіі",
+ "aboutLicensesShowAllButtonLabel": "Паказаць ўсе ліцэнзіі",
"@aboutLicensesShowAllButtonLabel": {},
"sortOrderNewestFirst": "Спачатку самае новае",
"@sortOrderNewestFirst": {},
@@ -1175,7 +1175,7 @@
"@menuActionStats": {},
"appPickDialogTitle": "Выбраць праграму",
"@appPickDialogTitle": {},
- "albumPickPageTitleMove": "Перамясціць у альбом",
+ "albumPickPageTitleMove": "Перамясціць ў альбом",
"@albumPickPageTitleMove": {},
"coverDialogTabCover": "Вокладка",
"@coverDialogTabCover": {},
@@ -1405,7 +1405,7 @@
"@settingsStorageAccessEmpty": {},
"settingsRemoveAnimationsTile": "Выдаліць анімацыі",
"@settingsRemoveAnimationsTile": {},
- "settingsStorageAccessBanner": "Некаторыя каталогі патрабуюць відавочнага дазволу на змяненне файлаў у іх. Тут вы можаце прагледзець каталогі, да якіх вы раней далі доступ.",
+ "settingsStorageAccessBanner": "Некаторыя каталогі патрабуюць відавочнага дазволу на змяненне файлаў ў іх. Тут вы можаце прагледзець каталогі, да якіх вы раней далі доступ.",
"@settingsStorageAccessBanner": {},
"collectionCopySuccessFeedback": "{count, plural, =1{1 элемент скапіяваны} few{{count} элементы скапіявана} other{{count} элементаў скапіявана}}",
"@collectionCopySuccessFeedback": {
@@ -1467,7 +1467,7 @@
"@settingsSubtitleThemeTextPositionTile": {},
"settingsVideoBackgroundModeDialogTitle": "Фонавы рэжым",
"@settingsVideoBackgroundModeDialogTitle": {},
- "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Выдаліць гэтыя альбомы і элемент у іх?} few{Выдаліць гэтыя альбомы і {count} элементы у іх?} other{Выдаліць гэтыя альбомы і {count} элементаў у іх?}}",
+ "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Выдаліць гэтыя альбомы і элемент ў іх?} few{Выдаліць гэтыя альбомы і {count} элементы ў іх?} other{Выдаліць гэтыя альбомы і {count} элементаў ў іх?}}",
"@deleteMultiAlbumConfirmationDialogMessage": {
"placeholders": {
"count": {}
@@ -1524,5 +1524,13 @@
"setHomeCustomCollection": "Ўласная калекцыя",
"@setHomeCustomCollection": {},
"settingsThumbnailShowHdrIcon": "Паказаць значок HDR",
- "@settingsThumbnailShowHdrIcon": {}
+ "@settingsThumbnailShowHdrIcon": {},
+ "videoRepeatActionSetEnd": "Ўсталяваць канец",
+ "@videoRepeatActionSetEnd": {},
+ "stopTooltip": "Спыніць",
+ "@stopTooltip": {},
+ "videoActionABRepeat": "Паўтарыць ад А да Б",
+ "@videoActionABRepeat": {},
+ "videoRepeatActionSetStart": "Ўсталяваць пачатак",
+ "@videoRepeatActionSetStart": {}
}
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 620c607b2..935fe211b 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -63,6 +63,7 @@
"actionRemove": "Remove",
"resetTooltip": "Reset",
"saveTooltip": "Save",
+ "stopTooltip": "Stop",
"pickTooltip": "Pick",
"doubleBackExitMessage": "Tap “back” again to exit.",
@@ -127,6 +128,10 @@
"videoActionSkip10": "Seek forward 10 seconds",
"videoActionSelectStreams": "Select tracks",
"videoActionSetSpeed": "Playback speed",
+ "videoActionABRepeat": "A-B repeat",
+
+ "videoRepeatActionSetStart": "Set start",
+ "videoRepeatActionSetEnd": "Set end",
"viewerActionSettings": "Settings",
"viewerActionLock": "Lock viewer",
diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb
index 16f12483d..d4ac1b778 100644
--- a/lib/l10n/app_es.arb
+++ b/lib/l10n/app_es.arb
@@ -1366,5 +1366,13 @@
"collectionActionSetHome": "Fijar como inicio",
"@collectionActionSetHome": {},
"setHomeCustomCollection": "Colección personalizada",
- "@setHomeCustomCollection": {}
+ "@setHomeCustomCollection": {},
+ "videoRepeatActionSetStart": "Fijar el inicio",
+ "@videoRepeatActionSetStart": {},
+ "stopTooltip": "Parar",
+ "@stopTooltip": {},
+ "videoActionABRepeat": "Repetir de A a B",
+ "@videoActionABRepeat": {},
+ "videoRepeatActionSetEnd": "Fijar el fin",
+ "@videoRepeatActionSetEnd": {}
}
diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb
index 7011c61e7..5132a33c0 100644
--- a/lib/l10n/app_fr.arb
+++ b/lib/l10n/app_fr.arb
@@ -1366,5 +1366,13 @@
"setHomeCustomCollection": "Collection personnalisée",
"@setHomeCustomCollection": {},
"settingsThumbnailShowHdrIcon": "Afficher l’icône HDR",
- "@settingsThumbnailShowHdrIcon": {}
+ "@settingsThumbnailShowHdrIcon": {},
+ "videoRepeatActionSetEnd": "Définir la fin",
+ "@videoRepeatActionSetEnd": {},
+ "stopTooltip": "Arrêter",
+ "@stopTooltip": {},
+ "videoActionABRepeat": "Lecture répétée A-B",
+ "@videoActionABRepeat": {},
+ "videoRepeatActionSetStart": "Définir le début",
+ "@videoRepeatActionSetStart": {}
}
diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb
index 234a24b9a..3512284f5 100644
--- a/lib/l10n/app_id.arb
+++ b/lib/l10n/app_id.arb
@@ -1366,5 +1366,13 @@
"settingsThumbnailShowHdrIcon": "Tampilkan ikon HDR",
"@settingsThumbnailShowHdrIcon": {},
"castDialogTitle": "Siarkan Perangkat",
- "@castDialogTitle": {}
+ "@castDialogTitle": {},
+ "stopTooltip": "Berhenti",
+ "@stopTooltip": {},
+ "videoActionABRepeat": "Ulang A-B",
+ "@videoActionABRepeat": {},
+ "videoRepeatActionSetStart": "Tetapkan awal",
+ "@videoRepeatActionSetStart": {},
+ "videoRepeatActionSetEnd": "Tetapkan akhir",
+ "@videoRepeatActionSetEnd": {}
}
diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb
index ece6ed10b..717587375 100644
--- a/lib/l10n/app_ja.arb
+++ b/lib/l10n/app_ja.arb
@@ -1235,7 +1235,7 @@
"@lengthUnitPercent": {},
"saveCopyButtonLabel": "コピーを保存",
"@saveCopyButtonLabel": {},
- "columnCount": "{count, plural, =1{1 列} other{{count} 列}}",
+ "columnCount": "{count, plural, other{{count} 列}}",
"@columnCount": {
"placeholders": {
"count": {}
@@ -1246,5 +1246,23 @@
"entryActionCast": "キャスト",
"@entryActionCast": {},
"editorTransformRotate": "回転",
- "@editorTransformRotate": {}
+ "@editorTransformRotate": {},
+ "settingsVideoResumptionModeTile": "前回の位置からの再生",
+ "@settingsVideoResumptionModeTile": {},
+ "settingsAskEverytime": "都度選択",
+ "@settingsAskEverytime": {},
+ "maxBrightnessNever": "無効",
+ "@maxBrightnessNever": {},
+ "settingsVideoResumptionModeDialogTitle": "前回の位置からの再生",
+ "@settingsVideoResumptionModeDialogTitle": {},
+ "settingsVideoBackgroundMode": "バックグラウンド再生",
+ "@settingsVideoBackgroundMode": {},
+ "settingsVideoGestureVerticalDragBrightnessVolume": "左右で上下にスワイプして輝度と音量を調節",
+ "@settingsVideoGestureVerticalDragBrightnessVolume": {},
+ "settingsViewerShowHistogram": "ヒストグラムを表示",
+ "@settingsViewerShowHistogram": {},
+ "settingsVideoBackgroundModeDialogTitle": "バックグラウンド再生",
+ "@settingsVideoBackgroundModeDialogTitle": {},
+ "maxBrightnessAlways": "常時有効",
+ "@maxBrightnessAlways": {}
}
diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb
index b706cdbec..669fbb575 100644
--- a/lib/l10n/app_ko.arb
+++ b/lib/l10n/app_ko.arb
@@ -1366,5 +1366,13 @@
"collectionActionSetHome": "홈으로 설정",
"@collectionActionSetHome": {},
"setHomeCustomCollection": "지정 미디어",
- "@setHomeCustomCollection": {}
+ "@setHomeCustomCollection": {},
+ "videoRepeatActionSetStart": "시작 지점 설정",
+ "@videoRepeatActionSetStart": {},
+ "videoRepeatActionSetEnd": "종료 지점 설정",
+ "@videoRepeatActionSetEnd": {},
+ "stopTooltip": "취소",
+ "@stopTooltip": {},
+ "videoActionABRepeat": "A-B 반복",
+ "@videoActionABRepeat": {}
}
diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb
index f5d2c1b3d..7b861ad7e 100644
--- a/lib/l10n/app_pl.arb
+++ b/lib/l10n/app_pl.arb
@@ -1524,5 +1524,13 @@
"setHomeCustomCollection": "Własna kolekcja",
"@setHomeCustomCollection": {},
"collectionActionSetHome": "Ustaw jako stronę główną",
- "@collectionActionSetHome": {}
+ "@collectionActionSetHome": {},
+ "videoRepeatActionSetStart": "Ustaw początek",
+ "@videoRepeatActionSetStart": {},
+ "stopTooltip": "Zatrzymaj",
+ "@stopTooltip": {},
+ "videoActionABRepeat": "Powtarzanie A-B",
+ "@videoActionABRepeat": {},
+ "videoRepeatActionSetEnd": "Ustaw koniec",
+ "@videoRepeatActionSetEnd": {}
}
diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb
index 97c61ec99..2fe296167 100644
--- a/lib/l10n/app_pt.arb
+++ b/lib/l10n/app_pt.arb
@@ -1360,5 +1360,11 @@
"entryActionCast": "Transmitir",
"@entryActionCast": {},
"castDialogTitle": "Dispositivos para Transmitir",
- "@castDialogTitle": {}
+ "@castDialogTitle": {},
+ "settingsThumbnailShowHdrIcon": "Mostrar ícone HDR",
+ "@settingsThumbnailShowHdrIcon": {},
+ "collectionActionSetHome": "Definir como início",
+ "@collectionActionSetHome": {},
+ "setHomeCustomCollection": "Coleção personalizada",
+ "@setHomeCustomCollection": {}
}
diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb
index 5483b73d7..2de63a7f7 100644
--- a/lib/l10n/app_ru.arb
+++ b/lib/l10n/app_ru.arb
@@ -1366,5 +1366,13 @@
"setHomeCustomCollection": "Собственная коллекция",
"@setHomeCustomCollection": {},
"collectionActionSetHome": "Установить как главную",
- "@collectionActionSetHome": {}
+ "@collectionActionSetHome": {},
+ "videoRepeatActionSetStart": "Установить начало",
+ "@videoRepeatActionSetStart": {},
+ "stopTooltip": "Остановить",
+ "@stopTooltip": {},
+ "videoActionABRepeat": "Повторить от А до Б",
+ "@videoActionABRepeat": {},
+ "videoRepeatActionSetEnd": "Установить конец",
+ "@videoRepeatActionSetEnd": {}
}
diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb
index ff6915f18..3864c6776 100644
--- a/lib/l10n/app_uk.arb
+++ b/lib/l10n/app_uk.arb
@@ -1524,5 +1524,13 @@
"setHomeCustomCollection": "Власна колекція",
"@setHomeCustomCollection": {},
"collectionActionSetHome": "Встановити як головну",
- "@collectionActionSetHome": {}
+ "@collectionActionSetHome": {},
+ "videoRepeatActionSetStart": "Змінити початок",
+ "@videoRepeatActionSetStart": {},
+ "videoRepeatActionSetEnd": "Змінити кінець",
+ "@videoRepeatActionSetEnd": {},
+ "stopTooltip": "Зупинити",
+ "@stopTooltip": {},
+ "videoActionABRepeat": "Повторити від А до Б",
+ "@videoActionABRepeat": {}
}
diff --git a/lib/model/app/contributors.dart b/lib/model/app/contributors.dart
index 53354d3ba..9115f3f17 100644
--- a/lib/model/app/contributors.dart
+++ b/lib/model/app/contributors.dart
@@ -79,6 +79,7 @@ class Contributors {
Contributor('luckris25', 'lk1thebestl@gmail.com'),
Contributor('Marc Amorós', 'marquitus99@gmail.com'),
Contributor('elea11', 'p.manuel.warnecke@gmail.com'),
+ Contributor('しいたけ', 'Shiitake@users.noreply.hosted.weblate.org'),
// Contributor('Alvi Khan', 'aveenalvi@gmail.com'), // Bengali
// Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese
// Contributor('Khant', 'khant@users.noreply.hosted.weblate.org'), // Burmese
diff --git a/lib/model/availability.dart b/lib/model/availability.dart
index 2661e2266..20c22d564 100644
--- a/lib/model/availability.dart
+++ b/lib/model/availability.dart
@@ -33,8 +33,8 @@ class LiveAvesAvailability implements AvesAvailability {
return _isConnected!;
}
- void _updateConnectivityFromResult(ConnectivityResult result) {
- final newValue = result != ConnectivityResult.none;
+ void _updateConnectivityFromResult(List result) {
+ final newValue = result.isNotEmpty && !result.contains(ConnectivityResult.none);
if (_isConnected != newValue) {
_isConnected = newValue;
debugPrint('Device is connected=$_isConnected');
diff --git a/lib/model/entry/extensions/metadata_edition.dart b/lib/model/entry/extensions/metadata_edition.dart
index cf92324c9..29d1d447d 100644
--- a/lib/model/entry/extensions/metadata_edition.dart
+++ b/lib/model/entry/extensions/metadata_edition.dart
@@ -7,6 +7,7 @@ import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/catalog.dart';
import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/metadata/date_modifier.dart';
+import 'package:aves/ref/locales.dart';
import 'package:aves/ref/metadata/exif.dart';
import 'package:aves/ref/metadata/iptc.dart';
import 'package:aves/ref/metadata/xmp.dart';
@@ -121,7 +122,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
if (latLng != null && latLng != removalLocation) {
final latitude = latLng.latitude;
final longitude = latLng.longitude;
- const locale = 'en_US';
+ const locale = asciiLocale;
final isoLat = '${latitude >= 0 ? '+' : '-'}${NumberFormat('00.0000', locale).format(latitude.abs())}';
final isoLon = '${longitude >= 0 ? '+' : '-'}${NumberFormat('000.0000', locale).format(longitude.abs())}';
iso6709String = '$isoLat$isoLon/';
diff --git a/lib/model/naming_pattern.dart b/lib/model/naming_pattern.dart
index e5eee02d4..ab988fef5 100644
--- a/lib/model/naming_pattern.dart
+++ b/lib/model/naming_pattern.dart
@@ -1,4 +1,8 @@
+import 'package:aves/convert/metadata/fields.dart';
import 'package:aves/model/entry/entry.dart';
+import 'package:aves/services/common/services.dart';
+import 'package:aves_model/aves_model.dart';
+import 'package:collection/collection.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:intl/intl.dart';
@@ -16,6 +20,7 @@ class NamingPattern {
factory NamingPattern.from({
required String userPattern,
required int entryCount,
+ required String locale,
}) {
final processors = [];
@@ -36,7 +41,13 @@ class NamingPattern {
switch (processorKey) {
case DateNamingProcessor.key:
if (processorOptions != null) {
- processors.add(DateNamingProcessor(processorOptions.trim()));
+ processors.add(DateNamingProcessor(processorOptions.trim(), locale));
+ }
+ case TagsNamingProcessor.key:
+ processors.add(TagsNamingProcessor(processorOptions?.trim() ?? ''));
+ case MetadataFieldNamingProcessor.key:
+ if (processorOptions != null) {
+ processors.add(MetadataFieldNamingProcessor(processorOptions.trim()));
}
case NameNamingProcessor.key:
processors.add(const NameNamingProcessor());
@@ -95,21 +106,33 @@ class NamingPattern {
switch (processorKey) {
case DateNamingProcessor.key:
return '<$processorKey, yyyyMMdd-HHmmss>';
+ case TagsNamingProcessor.key:
+ return '<$processorKey, ->';
case CounterNamingProcessor.key:
case NameNamingProcessor.key:
default:
+ if (processorKey.startsWith(MetadataFieldNamingProcessor.key)) {
+ final field = MetadataFieldNamingProcessor.fieldFromKey(processorKey);
+ return '<${MetadataFieldNamingProcessor.key}, $field>';
+ }
return '<$processorKey>';
}
}
- String apply(AvesEntry entry, int index) => processors.map((v) => v.process(entry, index) ?? '').join().trimLeft();
+ Future apply(AvesEntry entry, int index) async {
+ final fields = processors.expand((v) => v.getRequiredFields()).toSet();
+ final fieldValues = await metadataFetchService.getFields(entry, fields);
+ return processors.map((v) => v.process(entry, index, fieldValues) ?? '').join().trim();
+ }
}
@immutable
abstract class NamingProcessor extends Equatable {
const NamingProcessor();
- String? process(AvesEntry entry, int index);
+ String? process(AvesEntry entry, int index, Map fieldValues);
+
+ Set getRequiredFields() => {};
}
@immutable
@@ -122,7 +145,7 @@ class LiteralNamingProcessor extends NamingProcessor {
const LiteralNamingProcessor(this.text);
@override
- String? process(AvesEntry entry, int index) => text;
+ String? process(AvesEntry entry, int index, Map fieldValues) => text;
}
@immutable
@@ -134,15 +157,63 @@ class DateNamingProcessor extends NamingProcessor {
@override
List