Merge branch 'develop'

This commit is contained in:
Thibault Deckers 2024-06-11 23:11:21 +02:00
commit ce11587482
253 changed files with 2237 additions and 1501 deletions

@ -1 +1 @@
Subproject commit 54e66469a933b60ddf175f858f82eaeb97e48c8d
Subproject commit 761747bfc538b5af34aa0d3fac380f1bc331ec49

View file

@ -4,6 +4,27 @@ All notable changes to this project will be documented in this file.
## <a id="unreleased"></a>[Unreleased]
## <a id="v1.11.2"></a>[v1.11.2] - 2024-06-11
### Added
- Albums / Countries / Tags: show selection in Collection
- allow shifting dates by seconds
### Changed
- opening app from launcher shows home page only when exited by back button
- Screen saver: black background, consistent with slideshow
- upgraded Flutter to stable v3.22.2
### Removed
- support for Android KitKat (API 19)
### Fixed
- crash when cataloguing large images
## <a id="v1.11.1"></a>[v1.11.1] - 2024-05-03
### Added

View file

@ -66,9 +66,6 @@ android {
defaultConfig {
applicationId packageName
// minSdk constraints:
// - Flutter & other plugins: 19 (cf `flutter.minSdkVersion`)
// - google_maps_flutter v2.1.1: 20
minSdk flutter.minSdkVersion
targetSdk 34
versionCode flutterVersionCode.toInteger()
@ -197,11 +194,11 @@ repositories {
}
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1'
implementation "androidx.appcompat:appcompat:1.6.1"
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.lifecycle:lifecycle-process:2.7.0'
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.lifecycle:lifecycle-process:2.8.0'
implementation 'androidx.media:media:1.7.0'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
@ -211,9 +208,9 @@ dependencies {
implementation 'com.commonsware.cwac:document:0.5.0'
implementation 'com.drewnoakes:metadata-extractor:2.19.0'
implementation "com.github.bumptech.glide:glide:$glide_version"
implementation 'com.google.android.material:material:1.11.0'
implementation 'com.google.android.material:material:1.12.0'
// SLF4J implementation for `mp4parser`
implementation 'org.slf4j:slf4j-simple:2.0.12'
implementation 'org.slf4j:slf4j-simple:2.0.13'
// forked, built by JitPack:
// - https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
@ -229,7 +226,7 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
kapt 'androidx.annotation:annotation:1.7.1'
kapt 'androidx.annotation:annotation:1.8.0'
ksp "com.github.bumptech.glide:ksp:$glide_version"
compileOnly rootProject.findProject(':streams_channel')

View file

@ -72,12 +72,10 @@
-->
<!--
allow install on API 19, despite the `minSdk` declared in dependencies:
- the Security library is from API 21
allow install on API 21, despite the `minSdk` declared in dependencies:
- FFmpegKit for Flutter is from API 24 (when not LTS)
- Google Maps is from API 20
-->
<uses-sdk tools:overrideLibrary="androidx.security, com.arthenica.ffmpegkit.flutter, io.flutter.plugins.googlemaps" />
<uses-sdk tools:overrideLibrary="com.arthenica.ffmpegkit.flutter" />
<!-- from Android 11, we should define <queries> to make other apps visible to this app -->
<queries>
@ -321,8 +319,8 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<!-- as of Flutter v3.19.0 (stable),
Impeller fails to render videos & platform views, has poor performance -->
<!-- as of Flutter v3.22.0 (stable),
Impeller fails to render videos (via `ffmpegkit`), and has random glitches -->
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" />

View file

@ -161,13 +161,12 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine
applicationContext.getString(R.string.analysis_notification_action_stop),
WorkManager.getInstance(applicationContext).createCancelPendingIntent(id)
).build()
val icon = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) R.drawable.ic_notification else R.mipmap.ic_launcher_round
val contentTitle = title ?: applicationContext.getText(R.string.analysis_notification_default_title)
val notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL)
.setContentTitle(contentTitle)
.setTicker(contentTitle)
.setContentText(message)
.setSmallIcon(icon)
.setSmallIcon(R.drawable.ic_notification)
.setOngoing(true)
.setContentIntent(openAppIntent)
.addAction(stopAction)

View file

@ -4,8 +4,6 @@ import android.appwidget.AppWidgetManager
import android.content.Intent
import android.os.Bundle
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.FlutterUtils
import deckers.thibault.aves.utils.FlutterUtils.enableSoftwareRendering
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
@ -13,9 +11,6 @@ class HomeWidgetSettingsActivity : MainActivity() {
private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
public override fun onCreate(savedInstanceState: Bundle?) {
if (FlutterUtils.isSoftwareRenderingRequired()) {
intent.enableSoftwareRendering()
}
super.onCreate(savedInstanceState)
// cancel if user does not complete widget setup

View file

@ -17,13 +17,37 @@ import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
import deckers.thibault.aves.channel.calls.*
import deckers.thibault.aves.channel.calls.AccessibilityHandler
import deckers.thibault.aves.channel.calls.AnalysisHandler
import deckers.thibault.aves.channel.calls.AppAdapterHandler
import deckers.thibault.aves.channel.calls.DebugHandler
import deckers.thibault.aves.channel.calls.DeviceHandler
import deckers.thibault.aves.channel.calls.EmbeddedDataHandler
import deckers.thibault.aves.channel.calls.GeocodingHandler
import deckers.thibault.aves.channel.calls.GlobalSearchHandler
import deckers.thibault.aves.channel.calls.HomeWidgetHandler
import deckers.thibault.aves.channel.calls.MediaEditHandler
import deckers.thibault.aves.channel.calls.MediaFetchBytesHandler
import deckers.thibault.aves.channel.calls.MediaFetchObjectHandler
import deckers.thibault.aves.channel.calls.MediaSessionHandler
import deckers.thibault.aves.channel.calls.MediaStoreHandler
import deckers.thibault.aves.channel.calls.MetadataEditHandler
import deckers.thibault.aves.channel.calls.MetadataFetchHandler
import deckers.thibault.aves.channel.calls.SecurityHandler
import deckers.thibault.aves.channel.calls.StorageHandler
import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler
import deckers.thibault.aves.channel.calls.window.WindowHandler
import deckers.thibault.aves.channel.streams.*
import deckers.thibault.aves.channel.streams.ActivityResultStreamHandler
import deckers.thibault.aves.channel.streams.AnalysisStreamHandler
import deckers.thibault.aves.channel.streams.ErrorStreamHandler
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
import deckers.thibault.aves.channel.streams.ImageOpStreamHandler
import deckers.thibault.aves.channel.streams.IntentStreamHandler
import deckers.thibault.aves.channel.streams.MediaCommandStreamHandler
import deckers.thibault.aves.channel.streams.MediaStoreChangeStreamHandler
import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler
import deckers.thibault.aves.channel.streams.SettingsChangeStreamHandler
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.FlutterUtils.enableSoftwareRendering
import deckers.thibault.aves.utils.FlutterUtils.isSoftwareRenderingRequired
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.getParcelableExtraCompat
import io.flutter.embedding.android.FlutterFragmentActivity
@ -52,13 +76,6 @@ open class MainActivity : FlutterFragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Log.i(LOG_TAG, "onCreate intent=$intent")
if (isSoftwareRenderingRequired()) {
intent.enableSoftwareRendering()
// running the app from Android Studio automatically adds to the intent the `start-paused` flag
// so the IDE can connect to the app, but launching on KitKat emulators fails because of a timeout
intent.removeExtra("start-paused")
}
intent.extras?.takeUnless { it.isEmpty }?.let {
Log.i(LOG_TAG, "onCreate intent extras=$it")
}
@ -168,12 +185,10 @@ open class MainActivity : FlutterFragmentActivity() {
// as of Flutter v3.0.1, the window `viewInsets` and `viewPadding`
// are incorrect on startup in some environments (e.g. API 29 emulator),
// so we manually request to apply the insets to update the window metrics
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
Handler(Looper.getMainLooper()).postDelayed({
window.decorView.requestApplyInsets()
}, 100)
}
}
override fun onStop() {
Log.i(LOG_TAG, "onStop")
@ -270,10 +285,10 @@ open class MainActivity : FlutterFragmentActivity() {
open fun extractIntentData(intent: Intent?): FieldMap {
when (val action = intent?.action) {
Intent.ACTION_MAIN -> {
val fields = hashMapOf<String, Any?>(
INTENT_DATA_KEY_LAUNCHER to intent.hasCategory(Intent.CATEGORY_LAUNCHER),
INTENT_DATA_KEY_SAFE_MODE to intent.getBooleanExtra(EXTRA_KEY_SAFE_MODE, false),
)
val fields = HashMap<String, Any?>()
if (intent.getBooleanExtra(EXTRA_KEY_SAFE_MODE, false)) {
fields[INTENT_DATA_KEY_SAFE_MODE] = true
}
intent.getStringExtra(EXTRA_KEY_PAGE)?.let { page ->
val filters = extractFiltersFromIntent(intent)
fields[INTENT_DATA_KEY_PAGE] = page
@ -482,7 +497,6 @@ 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"

View file

@ -9,6 +9,7 @@ import deckers.thibault.aves.channel.calls.*
import deckers.thibault.aves.channel.calls.window.ServiceWindowHandler
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.channel.streams.MediaStoreStreamHandler
import deckers.thibault.aves.utils.LogUtils
import io.flutter.FlutterInjector
@ -18,12 +19,14 @@ import io.flutter.embedding.android.FlutterView
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint
import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel
// for FlutterView-level integration, cf https://docs.flutter.dev/development/add-to-app/android/add-flutter-view
class ScreenSaverService : DreamService() {
private var flutterEngine: FlutterEngine? = null
private var flutterView: FlutterView? = null
private lateinit var mediaSessionHandler: MediaSessionHandler
override fun onAttachedToWindow() {
Log.i(LOG_TAG, "onAttachedToWindow")
@ -77,6 +80,7 @@ class ScreenSaverService : DreamService() {
private fun release() {
destroyView()
mediaSessionHandler.dispose()
flutterEngine = null
flutterView = null
}
@ -96,12 +100,19 @@ class ScreenSaverService : DreamService() {
private fun initChannels() {
val messenger = flutterEngine!!.dartExecutor
// notification: platform -> dart
val mediaCommandStreamHandler = MediaCommandStreamHandler().apply {
EventChannel(messenger, MediaCommandStreamHandler.CHANNEL).setStreamHandler(this)
}
// dart -> platform -> dart
// - need Context
mediaSessionHandler = MediaSessionHandler(this, mediaCommandStreamHandler)
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this))
MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this))
MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this))
MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this))
MethodChannel(messenger, MediaSessionHandler.CHANNEL).setMethodCallHandler(mediaSessionHandler)
MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(this))
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this))
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this))

View file

@ -34,7 +34,7 @@ class SearchSuggestionsProvider : ContentProvider() {
val columns = arrayOf(
SearchManager.SUGGEST_COLUMN_INTENT_DATA,
SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) SearchManager.SUGGEST_COLUMN_CONTENT_TYPE else "mimeType",
SearchManager.SUGGEST_COLUMN_CONTENT_TYPE,
SearchManager.SUGGEST_COLUMN_TEXT_1,
SearchManager.SUGGEST_COLUMN_TEXT_2,
SearchManager.SUGGEST_COLUMN_ICON_1,

View file

@ -2,21 +2,26 @@ package deckers.thibault.aves
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
import deckers.thibault.aves.channel.calls.*
import deckers.thibault.aves.channel.calls.AccessibilityHandler
import deckers.thibault.aves.channel.calls.DeviceHandler
import deckers.thibault.aves.channel.calls.EmbeddedDataHandler
import deckers.thibault.aves.channel.calls.MediaFetchBytesHandler
import deckers.thibault.aves.channel.calls.MediaFetchObjectHandler
import deckers.thibault.aves.channel.calls.MediaSessionHandler
import deckers.thibault.aves.channel.calls.MetadataFetchHandler
import deckers.thibault.aves.channel.calls.StorageHandler
import deckers.thibault.aves.channel.calls.WallpaperHandler
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
import deckers.thibault.aves.utils.getParcelableExtraCompat
import io.flutter.embedding.android.FlutterFragmentActivity
@ -30,9 +35,6 @@ class WallpaperActivity : FlutterFragmentActivity() {
private lateinit var mediaSessionHandler: MediaSessionHandler
override fun onCreate(savedInstanceState: Bundle?) {
if (FlutterUtils.isSoftwareRenderingRequired()) {
intent.enableSoftwareRendering()
}
super.onCreate(savedInstanceState)
Log.i(LOG_TAG, "onCreate intent=$intent")
@ -83,12 +85,10 @@ class WallpaperActivity : FlutterFragmentActivity() {
// as of Flutter v3.0.1, the window `viewInsets` and `viewPadding`
// are incorrect on startup in some environments (e.g. API 29 emulator),
// so we manually request to apply the insets to update the window metrics
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
Handler(Looper.getMainLooper()).postDelayed({
window.decorView.requestApplyInsets()
}, 100)
}
}
override fun onDestroy() {
mediaSessionHandler.dispose()

View file

@ -1,7 +1,7 @@
package deckers.thibault.aves.channel.calls
import android.content.Context
import androidx.core.app.ComponentActivity
import androidx.activity.ComponentActivity
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder

View file

@ -79,15 +79,9 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
"obbDir" to context.obbDir,
"externalCacheDir" to context.externalCacheDir,
"externalFilesDir" to context.getExternalFilesDir(null),
).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
putAll(
hashMapOf(
"codeCacheDir" to context.codeCacheDir,
"noBackupFilesDir" to context.noBackupFilesDir,
)
)
}
).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
put("dataDir", context.dataDir)
}
@ -108,8 +102,6 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
}
private fun getCodecs(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
val codecs = ArrayList<FieldMap>()
fun getFields(info: MediaCodecInfo): FieldMap {
val fields: FieldMap = hashMapOf(
"name" to info.name,
@ -126,18 +118,7 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
return fields
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
codecs.addAll(MediaCodecList(MediaCodecList.REGULAR_CODECS).codecInfos.map(::getFields))
} else {
@Suppress("deprecation")
val count = MediaCodecList.getCodecCount()
for (i in 0 until count) {
@Suppress("deprecation")
val info = MediaCodecList.getCodecInfoAt(i)
codecs.add(getFields(info))
}
}
val codecs = MediaCodecList(MediaCodecList.REGULAR_CODECS).codecInfos.map(::getFields).toList()
result.success(codecs)
}
@ -294,7 +275,7 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
metadataMap["mimeType"] = metadata.getDirectoriesOfType(FileTypeDirectory::class.java).joinToString { dir ->
if (dir.containsTag(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE)) {
dir.getString(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE)

View file

@ -69,16 +69,11 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
}
private fun getLocales(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
fun toMap(locale: Locale): FieldMap {
val fields: HashMap<String, Any?> = hashMapOf(
fun toMap(locale: Locale): FieldMap = hashMapOf(
"language" to locale.language,
"country" to locale.country,
"script" to locale.script,
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
fields["script"] = locale.script
}
return fields
}
val locales = ArrayList<FieldMap>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
@ -106,11 +101,7 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
}
private fun isSystemFilePickerEnabled(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
val enabled = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).resolveActivity(context.packageManager) != null
} else {
false
}
val enabled = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).resolveActivity(context.packageManager) != null
result.success(enabled)
}

View file

@ -102,7 +102,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
// data can be large and stored in "Extended XMP",
// which is returned as a second XMP directory
val xmpDirs = metadata.getDirectoriesOfType(XmpDirectory::class.java)
@ -272,7 +272,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
// data can be large and stored in "Extended XMP",
// which is returned as a second XMP directory
val xmpDirs = metadata.getDirectoriesOfType(XmpDirectory::class.java)

View file

@ -74,7 +74,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
private fun editDate(call: MethodCall, result: MethodChannel.Result) {
val dateMillis = call.argument<Number>("dateMillis")?.toLong()
val shiftMinutes = call.argument<Number>("shiftMinutes")?.toLong()
val shiftSeconds = call.argument<Number>("shiftSeconds")?.toLong()
val fields = call.argument<List<String>>("fields")
val entryMap = call.argument<FieldMap>("entry")
if (entryMap == null || fields == null) {
@ -97,7 +97,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
}
val callback = MetadataOpCallback("editDate", entryMap, result)
provider.editDate(contextWrapper, path, uri, mimeType, dateMillis, shiftMinutes, fields, callback)
provider.editDate(contextWrapper, path, uri, mimeType, dateMillis, shiftSeconds, fields, callback)
}
private fun editMetadata(call: MethodCall, result: MethodChannel.Result) {

View file

@ -229,7 +229,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
foundExif = metadata.directories.any { it is ExifDirectoryBase && it.tagCount > 0 }
foundMp4Uuid = metadata.directories.any { it is Mp4UuidBoxDirectory && it.tagCount > 0 }
@ -296,7 +296,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
byGeoTiff[false]?.map { exifTagMapper(it) }?.let { dirMap.putAll(it) }
}
mimeType == MimeTypes.DNG -> {
mimeType == MimeTypes.DNG || mimeType == MimeTypes.DNG_ADOBE -> {
// split DNG tags in their own directory
val dngDirMap = metadataMap[DIR_DNG] ?: HashMap()
metadataMap[DIR_DNG] = dngDirMap
@ -599,7 +599,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
foundExif = metadata.directories.any { it is ExifDirectoryBase && it.tagCount > 0 }
foundMp4Uuid = metadata.directories.any { it is Mp4UuidBoxDirectory && it.tagCount > 0 }
@ -803,7 +803,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
}
StorageUtils.openInputStream(context, uri)?.let { input ->
input.skip(dataOffset)
val pageMetadata = Helper.safeRead(input)
val pageMetadata = Helper.safeRead(input, sizeBytes)
if (pageMetadata.getDirectoriesOfType(XmpDirectory::class.java).any { it.xmpMeta.hasHdrGainMap() }) {
return true
}
@ -897,7 +897,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
for (dir in metadata.getDirectoriesOfType(ExifSubIFDDirectory::class.java)) {
foundExif = true
if (fields.contains(KEY_APERTURE)) {
@ -1007,7 +1007,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
val fields = HashMap<Int, Any?>()
for (dir in metadata.getDirectoriesOfType(ExifIFD0Directory::class.java)) {
if (dir.containsGeoTiffTags()) {
@ -1084,7 +1084,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType) && !isLargeMp4(mimeType, sizeBytes)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
metadata.getDirectoriesOfType(XmpDirectory::class.java).map { it.xmpMeta }.forEach {
processXmp(it, allowMultiple = true)
}
@ -1166,7 +1166,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType) && !isLargeMp4(mimeType, sizeBytes)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
metadata.getDirectoriesOfType(XmpDirectory::class.java).map { it.xmpMeta }.forEach {
processXmp(it, allowMultiple = true)
}
@ -1244,7 +1244,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
val tag = when (field) {
ExifInterface.TAG_DATETIME -> ExifIFD0Directory.TAG_DATETIME
ExifInterface.TAG_DATETIME_DIGITIZED -> ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED
@ -1345,7 +1345,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
for (dir in metadata.getDirectoriesOfType(ExifDirectoryBase::class.java)) {
foundExif = true
val allTags = ExifInterfaceHelper.allTags

View file

@ -47,9 +47,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
private fun getDataUsage(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
var internalCache = getFolderSize(context.cacheDir)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
internalCache += getFolderSize(context.codeCacheDir)
}
val externalCache = context.externalCacheDirs.map(::getFolderSize).sum()
val dataDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) context.dataDir else File(context.applicationInfo.dataDir)
@ -105,12 +103,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
val volumeFile = File(volumePath)
try {
val isPrimary = volumePath == primaryVolumePath
val isRemovable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Environment.isExternalStorageRemovable(volumeFile)
} else {
// random guess
!isPrimary
}
val isRemovable = Environment.isExternalStorageRemovable(volumeFile)
volumes.add(
hashMapOf(
"path" to volumePath,
@ -202,11 +195,6 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
return
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
result.error("revokeDirectoryAccess-unsupported", "volume access is not allowed before Android Lollipop", null)
return
}
val success = PermissionManager.revokeDirectoryAccess(context, path)
result.success(success)
}

View file

@ -14,10 +14,12 @@ 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.MathUtils
import deckers.thibault.aves.utils.MemoryUtils
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.StorageUtils
import io.flutter.plugin.common.MethodChannel
import kotlin.math.max
import kotlin.math.roundToInt
// As of Android 14 (API 34), `BitmapRegionDecoder` documentation states
@ -60,10 +62,6 @@ class RegionFetcher internal constructor(
return
}
val options = BitmapFactory.Options().apply {
inSampleSize = sampleSize
}
var currentDecoderRef = lastDecoderRef
if (currentDecoderRef != null && currentDecoderRef.uri != uri) {
currentDecoderRef = null
@ -85,27 +83,35 @@ class RegionFetcher internal constructor(
// with raw images, the known image size may not match the decoded image size
// so we scale the requested region accordingly
val effectiveRect = if (imageWidth != decoder.width || imageHeight != decoder.height) {
var effectiveRect = regionRect
var effectiveSampleSize = sampleSize
if (imageWidth != decoder.width || imageHeight != decoder.height) {
val xf = decoder.width.toDouble() / imageWidth
val yf = decoder.height.toDouble() / imageHeight
Rect(
effectiveRect = Rect(
(regionRect.left * xf).roundToInt(),
(regionRect.top * yf).roundToInt(),
(regionRect.right * xf).roundToInt(),
(regionRect.bottom * yf).roundToInt(),
)
} else {
regionRect
val factor = MathUtils.highestPowerOf2((1 / max(xf, yf)).roundToInt())
if (factor > 1) {
effectiveSampleSize = max(1, effectiveSampleSize / factor)
}
}
// 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
val targetBitmapSizeBytes: Long = ARGB_8888_BYTE_SIZE.toLong() * effectiveRect.width() * effectiveRect.height() / effectiveSampleSize
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
}
val bitmap = decoder.decodeRegion(effectiveRect, options)
if (bitmap != null) {
result.success(bitmap.getBytes(MimeTypes.canHaveAlpha(mimeType), recycle = true))

View file

@ -61,11 +61,6 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
return
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
error("requestDirectoryAccess-unsupported", "directory access is not allowed before Android Lollipop", null)
return
}
PermissionManager.requestDirectoryAccess(activity, ensureTrailingSeparator(path), {
success(true)
endOfStream()

View file

@ -4,14 +4,18 @@ import android.util.Log
import androidx.exifinterface.media.ExifInterface
import com.drew.lang.Rational
import com.drew.metadata.Directory
import com.drew.metadata.exif.*
import com.drew.metadata.exif.ExifDirectoryBase
import com.drew.metadata.exif.ExifIFD0Directory
import com.drew.metadata.exif.ExifThumbnailDirectory
import com.drew.metadata.exif.GpsDirectory
import com.drew.metadata.exif.PanasonicRawIFD0Directory
import com.drew.metadata.exif.makernotes.OlympusCameraSettingsMakernoteDirectory
import com.drew.metadata.exif.makernotes.OlympusImageProcessingMakernoteDirectory
import com.drew.metadata.exif.makernotes.OlympusMakernoteDirectory
import deckers.thibault.aves.utils.LogUtils
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
import java.util.Locale
import kotlin.math.abs
import kotlin.math.floor
import kotlin.math.roundToLong
@ -22,7 +26,7 @@ object ExifInterfaceHelper {
val GPS_DATE_FORMAT = SimpleDateFormat("yyyy:MM:dd", Locale.ROOT)
val GPS_TIME_FORMAT = SimpleDateFormat("HH:mm:ss", Locale.ROOT)
private const val precisionErrorTolerance = 1e-10
private const val PRECISION_ERROR_TOLERANCE = 1e-10
// ExifInterface always states it has the following attributes
// and returns "0" instead of "null" when they are actually missing
@ -220,7 +224,7 @@ object ExifInterfaceHelper {
// initialize metadata-extractor directories that we will fill
// by tags converted from the ExifInterface attributes
// so that we can rely on metadata-extractor descriptions
val dirs = DirType.values().associateWith { it.createDirectory() }
val dirs = DirType.entries.associateWith { it.createDirectory() }
// exclude Exif directory when it only includes image size
val isUselessExif = fun(it: Map<String, String>): Boolean {
@ -308,7 +312,7 @@ object ExifInterfaceHelper {
val numerator = 1L
val f = numerator / d
val denominator = f.roundToLong()
if (abs(f - denominator) < precisionErrorTolerance) {
if (abs(f - denominator) < PRECISION_ERROR_TOLERANCE) {
return Rational(numerator, denominator)
}
}

View file

@ -120,7 +120,7 @@ object Metadata {
return date.time + parseSubSecond(subSecond)
}
// Opening large PSD/TIFF files yields an OOM (both with `metadata-extractor` v2.15.0 and `ExifInterface` v1.3.1),
// Opening some large files yields an OOM (both with `metadata-extractor` v2.15.0 and `ExifInterface` v1.3.1),
// so we define an arbitrary threshold to avoid a crash on launch.
// It is not clear whether it is because of the file itself or its metadata.
private const val FILE_SIZE_MAX = 100 * (1 shl 20) // MB
@ -134,14 +134,8 @@ object Metadata {
private val previewFiles = HashMap<Uri, File>()
private fun getSafeUri(context: Context, uri: Uri, mimeType: String, sizeBytes: Long?): Uri {
return when (mimeType) {
// formats known to yield OOM for large files
MimeTypes.HEIC,
MimeTypes.HEIF,
MimeTypes.MP4,
MimeTypes.PSD_VND,
MimeTypes.PSD_X,
MimeTypes.TIFF -> {
return if ((MimeTypes.isImage(mimeType) || mimeType == MimeTypes.MP4)) {
if (isDangerouslyLarge(sizeBytes)) {
// make a preview from the beginning of the file,
// hoping the metadata is accessible in the copied chunk
@ -155,9 +149,9 @@ object Metadata {
// small enough to be safe as it is
uri
}
}
} else {
// *probably* safe
else -> uri
uri
}
}

View file

@ -97,7 +97,7 @@ object MultiPage {
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
foundExif = metadata.directories.any { it is ExifDirectoryBase && it.tagCount > 0 }
for (dir in metadata.getDirectoriesOfType(ExifIFD0Directory::class.java)) {
dir.getSafeInt(ExifDirectoryBase.TAG_ORIENTATION) {
@ -168,7 +168,7 @@ object MultiPage {
val mimeType = MimeTypes.JPEG
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
return metadata.getDirectoriesOfType(MpEntryDirectory::class.java).map { it.entry }
}
} catch (e: Exception) {
@ -332,7 +332,7 @@ object MultiPage {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
foundXmp = metadata.directories.any { it is XmpDirectory && it.tagCount > 0 }
metadata.getDirectoriesOfType(XmpDirectory::class.java).map { it.xmpMeta }.forEach(::processXmp)
}

View file

@ -29,6 +29,7 @@ import deckers.thibault.aves.metadata.GeoTiffKeys
import deckers.thibault.aves.metadata.Metadata
import deckers.thibault.aves.metadata.metadataextractor.mpf.MpfReader
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MemoryUtils
import java.io.BufferedInputStream
import java.io.IOException
import java.io.InputStream
@ -59,19 +60,21 @@ object Helper {
// e.g. "exif [...] 134 [...] 4578696600004949[...]"
private val PNG_RAW_PROFILE_PATTERN = Regex("^\\n(.*?)\\n\\s*(\\d+)\\n(.*)", RegexOption.DOT_MATCHES_ALL)
// providing the stream length is risky, as it may crash if it is incorrect
private const val safeReadStreamLength = -1L
fun readMimeType(input: InputStream): String? {
val bufferedInputStream = if (input is BufferedInputStream) input else BufferedInputStream(input)
return FileTypeDetector.detectFileType(bufferedInputStream).mimeType
}
@Throws(IOException::class, ImageProcessingException::class)
fun safeRead(input: InputStream): com.drew.metadata.Metadata {
fun safeRead(input: InputStream, @Suppress("unused_parameter") sizeBytes: Long?): com.drew.metadata.Metadata {
val inputStream = if (input is BufferedInputStream) input else BufferedInputStream(input)
val fileType = FileTypeDetector.detectFileType(inputStream)
// Providing the stream length is risky, as it may crash if it is incorrect.
// Not providing the stream length is also risky, as it may lead to OOM
// when `RandomAccessStreamReader` reads the entire stream to validate offsets.
val undefinedStreamLength = -1L
val metadata = when (fileType) {
FileType.Jpeg -> safeReadJpeg(inputStream)
FileType.Mp4 -> safeReadMp4(inputStream)
@ -82,9 +85,9 @@ object Helper {
FileType.Cr2,
FileType.Nef,
FileType.Orf,
FileType.Rw2 -> safeReadTiff(inputStream)
FileType.Rw2 -> safeReadTiff(inputStream, undefinedStreamLength)
else -> ImageMetadataReader.readMetadata(inputStream, safeReadStreamLength, fileType)
else -> ImageMetadataReader.readMetadata(inputStream, undefinedStreamLength, fileType)
}
metadata.addDirectory(FileTypeDirectory(fileType))
@ -115,8 +118,8 @@ object Helper {
}
@Throws(IOException::class, TiffProcessingException::class)
fun safeReadTiff(input: InputStream): com.drew.metadata.Metadata {
val reader = RandomAccessStreamReader(input, RandomAccessStreamReader.DEFAULT_CHUNK_LENGTH, safeReadStreamLength)
fun safeReadTiff(input: InputStream, streamLength: Long): com.drew.metadata.Metadata {
val reader = RandomAccessStreamReader(input, RandomAccessStreamReader.DEFAULT_CHUNK_LENGTH, streamLength)
val metadata = com.drew.metadata.Metadata()
val handler = SafeExifTiffHandler(metadata, null, 0)
TiffReader().processTiff(reader, handler, 0)
@ -294,9 +297,7 @@ object Helper {
if (!modelTiePoints && !modelTransformation) return false
val modelPixelScale = this.containsTag(ExifGeoTiffTags.TAG_MODEL_PIXEL_SCALE)
if ((modelTransformation && modelPixelScale) || (modelPixelScale && !modelTiePoints)) return false
return true
return !((modelTransformation && modelPixelScale) || (modelPixelScale && !modelTiePoints))
}
// TODO TLAD use `GeoTiffDirectory` from the Java version of `metadata-extractor` when available

View file

@ -31,6 +31,7 @@ import deckers.thibault.aves.utils.LogUtils
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream
import java.util.Locale
import java.util.zip.InflaterInputStream
import java.util.zip.ZipException
@ -42,7 +43,7 @@ object SafePngMetadataReader {
private val LOG_TAG = LogUtils.createTag<SafePngMetadataReader>()
// arbitrary size to detect chunks that may yield an OOM
private const val chunkSizeDangerThreshold = SafeXmpReader.SEGMENT_TYPE_SIZE_DANGER_THRESHOLD
private const val CHUNK_SIZE_DANGER_THRESHOLD = SafeXmpReader.SEGMENT_TYPE_SIZE_DANGER_THRESHOLD
private val latin1Encoding = Charsets.ISO_8859_1
private val utf8Encoding = Charsets.UTF_8
@ -85,7 +86,7 @@ object SafePngMetadataReader {
val bytes = chunk.bytes
// TLAD insert start
if (bytes.size > chunkSizeDangerThreshold) {
if (bytes.size > CHUNK_SIZE_DANGER_THRESHOLD) {
Log.w(LOG_TAG, "PNG chunk $chunkType is too large, with a size of ${bytes.size} B")
return
}
@ -290,11 +291,12 @@ object SafePngMetadataReader {
val second = reader.uInt8.toInt()
val directory = PngDirectory(PngChunkType.tIME)
if (DateUtil.isValidDate(year, month - 1, day) && DateUtil.isValidTime(hour, minute, second)) {
val dateString = String.format("%04d:%02d:%02d %02d:%02d:%02d", year, month, day, hour, minute, second)
val dateString = String.format(Locale.ROOT, "%04d:%02d:%02d %02d:%02d:%02d", year, month, day, hour, minute, second)
directory.setString(PngDirectory.TAG_LAST_MODIFICATION_TIME, dateString)
} else {
directory.addError(
String.format(
Locale.ROOT,
"PNG tIME data describes an invalid date/time: year=%d month=%d day=%d hour=%d minute=%d second=%d",
year, month, day, hour, minute, second
)

View file

@ -16,6 +16,7 @@ import com.drew.metadata.xmp.XmpDirectory
import com.drew.metadata.xmp.XmpReader
import deckers.thibault.aves.utils.LogUtils
import java.io.IOException
import java.util.Locale
class SafeXmpReader : XmpReader() {
// adapted from `XmpReader` to detect and skip large extended XMP
@ -133,7 +134,7 @@ class SafeXmpReader : XmpReader() {
System.arraycopy(segmentBytes, totalOffset, extendedXMPBuffer, chunkOffset, segmentLength - totalOffset)
} else {
val directory = XmpDirectory()
directory.addError(String.format("Inconsistent length for the Extended XMP buffer: %d instead of %d", fullLength, extendedXMPBuffer.size))
directory.addError(String.format(Locale.ROOT, "Inconsistent length for the Extended XMP buffer: %d instead of %d", fullLength, extendedXMPBuffer.size))
metadata.addDirectory(directory)
}
}

View file

@ -16,8 +16,6 @@ class MpfDirectory : Directory() {
return _tagNameMap
}
fun getNumberOfImages() = getInt(TAG_NUMBER_OF_IMAGES)
companion object {
const val TAG_MPF_VERSION = 0xb000
const val TAG_NUMBER_OF_IMAGES = 0xb001

View file

@ -15,7 +15,7 @@ class AvesEntry(map: FieldMap) {
val trashed = map["trashed"] as Boolean
val trashPath = map["trashPath"] as String?
val isRotated: Boolean
private val isRotated: Boolean
get() = rotationDegrees % 180 == 90
val displayWidth: Int

View file

@ -163,7 +163,7 @@ class SourceEntry {
try {
Metadata.openSafeInputStream(context, uri, sourceMimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
val metadata = Helper.safeRead(input, sizeBytes)
// do not switch on specific MIME types, as the reported MIME type could be wrong
// (e.g. PNG registered as JPG)

View file

@ -1026,7 +1026,7 @@ abstract class ImageProvider {
uri: Uri,
mimeType: String,
dateMillis: Long?,
shiftMinutes: Long?,
shiftSeconds: Long?,
fields: List<String>,
callback: ImageOpCallback,
) {
@ -1057,9 +1057,9 @@ abstract class ImageProvider {
}
}
shiftMinutes != null -> {
shiftSeconds != null -> {
// shift
val shiftMillis = shiftMinutes * 60000
val shiftMillis = shiftSeconds * 1000
listOf(
ExifInterface.TAG_DATETIME,
ExifInterface.TAG_DATETIME_ORIGINAL,

View file

@ -56,13 +56,7 @@ fun Geocoder.getFromLocationCompat(
onError: (errorCode: String, errorMessage: String?, errorDetails: Any?) -> Unit,
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getFromLocation(latitude, longitude, maxResults, object : Geocoder.GeocodeListener {
override fun onGeocode(addresses: List<Address?>) = processAddresses(addresses.filterNotNull())
override fun onError(errorMessage: String?) {
onError("getAddress-asyncerror", "failed to get address", errorMessage)
}
})
Compat33.geocoderGetFromLocation(this, latitude, longitude, maxResults, processAddresses, onError)
} else {
try {
@Suppress("deprecation")

View file

@ -0,0 +1,30 @@
package deckers.thibault.aves.utils
import android.location.Address
import android.location.Geocoder
import android.os.Build
import androidx.annotation.RequiresApi
/**
* Compatibility layer in a separate object to avoid class loading issues on older Android versions.
* e.g. `ClassNotFoundException` for `android.location.Geocoder$GeocodeListener`
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
object Compat33 {
fun geocoderGetFromLocation(
geocoder: Geocoder,
latitude: Double,
longitude: Double,
maxResults: Int,
processAddresses: (addresses: List<Address>) -> Unit,
onError: (errorCode: String, errorMessage: String?, errorDetails: Any?) -> Unit,
) {
geocoder.getFromLocation(latitude, longitude, maxResults, object : Geocoder.GeocodeListener {
override fun onGeocode(addresses: List<Address?>) = processAddresses(addresses.filterNotNull())
override fun onError(errorMessage: String?) {
onError("getAddress-asyncerror", "failed to get address", errorMessage)
}
})
}
}

View file

@ -1,8 +1,6 @@
package deckers.thibault.aves.utils
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
@ -71,29 +69,4 @@ object FlutterUtils {
r.run()
}
}
fun Intent.enableSoftwareRendering() {
putExtra("enable-software-rendering", true)
Log.i(LOG_TAG, "Enable software rendering")
}
fun isSoftwareRenderingRequired() = Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT && isEmulator
private val isEmulator: Boolean
get() = (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")
|| Build.FINGERPRINT.startsWith("generic")
|| Build.FINGERPRINT.startsWith("unknown")
|| Build.HARDWARE.contains("goldfish")
|| Build.HARDWARE.contains("ranchu")
|| Build.MODEL.contains("google_sdk")
|| Build.MODEL.contains("Emulator")
|| Build.MODEL.contains("Android SDK built for x86")
|| Build.MANUFACTURER.contains("Genymotion")
|| Build.PRODUCT.contains("sdk_google")
|| Build.PRODUCT.contains("google_sdk")
|| Build.PRODUCT.contains("sdk")
|| Build.PRODUCT.contains("sdk_x86")
|| Build.PRODUCT.contains("vbox86p")
|| Build.PRODUCT.contains("emulator")
|| Build.PRODUCT.contains("simulator"))
}

View file

@ -0,0 +1,9 @@
package deckers.thibault.aves.utils
import kotlin.math.log2
import kotlin.math.pow
object MathUtils {
fun highestPowerOf2(x: Int): Int = highestPowerOf2(x.toDouble())
fun highestPowerOf2(x: Double): Int = if (x < 1) 0 else 2.toDouble().pow(log2(x).toInt()).toInt()
}

View file

@ -28,7 +28,8 @@ object MimeTypes {
private const val CR2 = "image/x-canon-cr2"
private const val CRW = "image/x-canon-crw"
private const val DCR = "image/x-kodak-dcr"
const val DNG = "image/x-adobe-dng"
const val DNG = "image/dng"
const val DNG_ADOBE = "image/x-adobe-dng"
private const val ERF = "image/x-epson-erf"
private const val K25 = "image/x-kodak-k25"
private const val KDC = "image/x-kodak-kdc"
@ -71,7 +72,7 @@ object MimeTypes {
fun isRaw(mimeType: String): Boolean {
return when (mimeType) {
ARW, CR2, CRW, DCR, DNG, ERF, K25, KDC, MRW, NEF, NRW, ORF, PEF, RAF, RAW, RW2, SR2, SRF, SRW, X3F -> true
ARW, CR2, CRW, DCR, DNG, DNG_ADOBE, ERF, K25, KDC, MRW, NEF, NRW, ORF, PEF, RAF, RAW, RW2, SR2, SRF, SRW, X3F -> true
else -> false
}
}
@ -142,7 +143,7 @@ object MimeTypes {
return if (pageId != null && MultiPageImage.isSupported(mimeType)) {
true
} else when (mimeType) {
DNG, HEIC, HEIF, PNG, WEBP -> true
DNG, DNG_ADOBE, HEIC, HEIF, PNG, WEBP -> true
else -> false
}
}
@ -151,7 +152,7 @@ object MimeTypes {
// according to EXIF orientation when decoding images of known formats
// but we need to rotate the decoded bitmap for the other formats
fun needRotationAfterContentResolverThumbnail(mimeType: String) = when (mimeType) {
DNG, PNG -> true
DNG, DNG_ADOBE, PNG -> true
else -> false
}

View file

@ -28,7 +28,6 @@ object PermissionManager {
Environment.DIRECTORY_PICTURES,
)
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
suspend fun requestDirectoryAccess(activity: Activity, path: String, onGranted: (uri: Uri) -> Unit, onDenied: () -> Unit) {
Log.i(LOG_TAG, "request user to select and grant access permission to path=$path")
@ -151,7 +150,6 @@ object PermissionManager {
}
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun revokeDirectoryAccess(context: Context, path: String): Boolean {
return StorageUtils.convertDirPathToTreeDocumentUri(context, path)?.let {
releaseUriPermission(context, it)
@ -162,12 +160,10 @@ object PermissionManager {
// returns paths matching directory URIs granted by the user
fun getGrantedDirs(context: Context): Set<String> {
val grantedDirs = HashSet<String>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
for (uriPermission in context.contentResolver.persistedUriPermissions) {
val dirPath = StorageUtils.convertTreeDocumentUriToDirPath(context, uriPermission.uri)
dirPath?.let { grantedDirs.add(it) }
}
}
return grantedDirs
}
@ -216,19 +212,6 @@ object PermissionManager {
)
})
}
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT
|| Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT_WATCH
) {
// removable storage requires access permission, at the file level
// without directory access, we consider the whole volume restricted
val primaryVolume = StorageUtils.getPrimaryVolumePath(context)
val nonPrimaryVolumes = StorageUtils.getVolumePaths(context).filter { it != primaryVolume }
dirs.addAll(nonPrimaryVolumes.map {
hashMapOf(
"volumePath" to it,
"relativeDir" to "",
)
})
}
return dirs
}
@ -236,7 +219,6 @@ object PermissionManager {
// As of Android 11, `MediaStore.getDocumentUri` fails if any of the persisted
// URI permissions we hold points to a folder that no longer exists,
// so we should remove these obsolete URIs before proceeding.
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun sanitizePersistedUriPermissions(context: Context) {
try {
for (uriPermission in context.contentResolver.persistedUriPermissions) {
@ -252,7 +234,6 @@ object PermissionManager {
}
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun releaseUriPermission(context: Context, it: Uri) {
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
context.contentResolver.releasePersistableUriPermission(it, flags)

View file

@ -16,7 +16,6 @@ import android.provider.DocumentsContract
import android.provider.MediaStore
import android.text.TextUtils
import android.util.Log
import androidx.annotation.RequiresApi
import com.commonsware.cwac.document.DocumentFileCompat
import deckers.thibault.aves.model.provider.ImageProvider
import deckers.thibault.aves.utils.FileUtils.transferFrom
@ -29,7 +28,7 @@ import java.io.FileInputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.*
import java.util.Locale
import java.util.regex.Pattern
object StorageUtils {
@ -381,7 +380,6 @@ object StorageUtils {
// e.g.
// /storage/emulated/0/ -> content://com.android.externalstorage.documents/tree/primary%3A
// /storage/10F9-3F13/Pictures/ -> content://com.android.externalstorage.documents/tree/10F9-3F13%3APictures
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun convertDirPathToTreeDocumentUri(context: Context, dirPath: String): Uri? {
val uuid = getVolumeUuidForDocumentUri(context, dirPath)
if (uuid != null) {
@ -446,7 +444,7 @@ object StorageUtils {
fun getDocumentFile(context: Context, anyPath: String, mediaUri: Uri): DocumentFileCompat? {
try {
if (requireAccessPermission(context, anyPath) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (requireAccessPermission(context, anyPath)) {
// need a document URI (not a media content URI) to open a `DocumentFile` output stream
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isMediaStoreContentUri(mediaUri)) {
// cleanest API to get it
@ -485,7 +483,7 @@ object StorageUtils {
fun createDirectoryDocIfAbsent(context: Context, dirPath: String): DocumentFileCompat? {
try {
val cleanDirPath = ensureTrailingSeparator(dirPath)
return if (requireAccessPermission(context, cleanDirPath) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return if (requireAccessPermission(context, cleanDirPath)) {
val grantedDir = getGrantedDirForPath(context, cleanDirPath) ?: return null
val rootTreeDocumentUri = convertDirPathToTreeDocumentUri(context, grantedDir) ?: return null
var parentFile: DocumentFileCompat? = DocumentFileCompat.fromTreeUri(context, rootTreeDocumentUri) ?: return null

View file

@ -10,9 +10,9 @@ class ImageProviderTest {
@Test
fun imageProvider_CorrectEmailSimple_ReturnsTrue() {
val date = LocalDate.of(1990, Month.FEBRUARY, 11).toEpochDay()
assertEquals(ImageProvider.getTimeZoneString(TimeZone.getTimeZone("Europe/Paris"), date), "+01:00")
assertEquals(ImageProvider.getTimeZoneString(TimeZone.getTimeZone("UTC"), date), "+00:00")
assertEquals(ImageProvider.getTimeZoneString(TimeZone.getTimeZone("Asia/Kolkata"), date), "+05:30")
assertEquals(ImageProvider.getTimeZoneString(TimeZone.getTimeZone("America/Chicago"), date), "-06:00")
assertEquals("+01:00", ImageProvider.getTimeZoneString(TimeZone.getTimeZone("Europe/Paris"), date))
assertEquals("+00:00", ImageProvider.getTimeZoneString(TimeZone.getTimeZone("UTC"), date))
assertEquals("+05:30", ImageProvider.getTimeZoneString(TimeZone.getTimeZone("Asia/Kolkata"), date))
assertEquals("-06:00", ImageProvider.getTimeZoneString(TimeZone.getTimeZone("America/Chicago"), date))
}
}

View file

@ -0,0 +1,17 @@
package deckers.thibault.aves.utils
import deckers.thibault.aves.utils.MathUtils.highestPowerOf2
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class MathUtilsTest {
@Test
fun mathUtils_highestPowerOf2() {
assertEquals(1024, highestPowerOf2(1024))
assertEquals(32, highestPowerOf2(42))
assertEquals(0, highestPowerOf2(0))
assertEquals(0, highestPowerOf2(-42))
assertEquals(0, highestPowerOf2(.5))
assertEquals(1, highestPowerOf2(1.5))
}
}

View file

@ -1,6 +1,6 @@
buildscript {
ext {
agp_version = '8.4.0-rc02' // same as `settings.ext.agp_version` in `/android/settings.gradle`
agp_version = '8.4.1' // same as `settings.ext.agp_version` in `/android/settings.gradle`
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'
@ -25,7 +25,7 @@ buildscript {
if (useCrashlytics) {
// GMS & Firebase Crashlytics (used by some flavors only)
classpath 'com.google.gms:google-services:4.4.0'
classpath 'com.google.gms:google-services:4.4.1'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
}

View file

@ -26,5 +26,5 @@ android {
}
dependencies {
implementation 'androidx.annotation:annotation:1.7.1'
implementation 'androidx.annotation:annotation:1.8.0'
}

View file

@ -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.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip

View file

@ -8,9 +8,9 @@ pluginManagement {
}
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.4.0-rc02'
settings.ext.kotlin_version = '1.9.24'
settings.ext.ksp_version = "$kotlin_version-1.0.20"
settings.ext.agp_version = '8.4.1'
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")

3
devtools_options.yaml Normal file
View file

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View file

@ -0,0 +1,3 @@
In v1.11.2:
- show selected albums together in Collection
Full changelog available on GitHub

View file

@ -0,0 +1,3 @@
In v1.11.2:
- show selected albums together in Collection
Full changelog available on GitHub

View file

@ -2,4 +2,4 @@
<b>پیمایش و جستجو</b>یک بخش مهم از <i>اِیوْز</i> است. هدف این است که کاربران به راحتی از آلبوم ها به عکس ها به برچسب ها به نقشه ها و غیره دست پیدا کنند.
<i>اِیوْز</i> با اندروید سازگار است (از نسخه KitKat تا اندروید 14, شامل تلوزیون اندرویدی) با قابلیت هایی مانند <b>ابزارک ها</b>, <b>میانبر ها</b>, <b>ذخیره نیرو</b> و <b>جستجو عمومی</b> و همچنین میتوان از آن به عنوان <b>نمایشگر و انتخابگز رسانه</b> استفاده کرد.
<i>اِیوْز</i> با اندروید سازگار است (از نسخه KitKat تا اندروید 14, شامل تلوزیون اندرویدی) با قابلیت هایی مانند <b>ابزارک ها</b>, <b>میانبر ها</b>, <b>ذخیره نیرو</b> و <b>جستجو عمومی</b> و همچنین میتوان از آن به عنوان <b>نمایشگر و انتخابگر رسانه</b> استفاده کرد.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 KiB

After

Width:  |  Height:  |  Size: 495 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 KiB

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 333 KiB

After

Width:  |  Height:  |  Size: 333 KiB

View file

@ -1,4 +1,5 @@
enum AppMode {
initialization,
main,
pickCollectionFiltersExternal,
pickSingleMediaExternal,
@ -31,7 +32,10 @@ extension ExtraAppMode on AppMode {
AppMode.pickMultipleMediaExternal,
}.contains(this);
bool get canSelectFilter => this == AppMode.main;
bool get canSelectFilter => {
AppMode.main,
AppMode.pickCollectionFiltersExternal,
}.contains(this);
bool get canCreateFilter => {
AppMode.main,

View file

@ -887,7 +887,7 @@
"@placePageTitle": {},
"filterOnThisDayLabel": "في هذا اليوم",
"@filterOnThisDayLabel": {},
"columnCount": "{count, plural, =1{1 عمود} other{{count} أعمدة}}{count}",
"columnCount": "{count, plural, =1{{count} عمود} other{{count} أعمدة}}{count}",
"@columnCount": {
"placeholders": {
"count": {}
@ -941,7 +941,7 @@
"@albumMimeTypeMixed": {},
"settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "الأزرار المتاحة",
"@settingsViewerQuickActionEditorAvailableButtonsSectionTitle": {},
"itemCount": "{count, plural, =1{1 عنصر} other{{count} عناصر}}",
"itemCount": "{count, plural, =1{{count} عنصر} other{{count} عناصر}}",
"@itemCount": {
"placeholders": {
"count": {}
@ -1247,7 +1247,7 @@
"@drawerCollectionImages": {},
"sortOrderSmallestFirst": "الأصغر أولاً",
"@sortOrderSmallestFirst": {},
"timeSeconds": "{seconds, plural, =1{1 ثانية} other{{seconds} ثواني}}",
"timeSeconds": "{count, plural, =1{{count} ثانية} other{{count} ثواني}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
@ -1393,7 +1393,7 @@
"@subtitlePositionBottom": {},
"castDialogTitle": "أجهزة البث",
"@castDialogTitle": {},
"timeDays": "{days, plural, =1{1 يوم} other{{days} أيام}}",
"timeDays": "{count, plural, =1{{count} يوم} other{{count} أيام}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -1425,7 +1425,7 @@
"@newVaultDialogTitle": {},
"entryInfoActionEditRating": "تحرير التقييم",
"@entryInfoActionEditRating": {},
"timeMinutes": "{minutes, plural, =1{1 دقيقة} other{{minutes} دقائق}}",
"timeMinutes": "{count, plural, =1{{count} دقيقة} other{{count} دقائق}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
@ -1536,5 +1536,7 @@
"settingsForceWesternArabicNumeralsTile": "فرض الأرقام العربية",
"@settingsForceWesternArabicNumeralsTile": {},
"renameProcessorHash": "تجزئة",
"@renameProcessorHash": {}
"@renameProcessorHash": {},
"chipActionShowCollection": "عرض في المجموعة",
"@chipActionShowCollection": {}
}

View file

@ -7,7 +7,7 @@
"@welcomeOptional": {},
"welcomeMessage": "Сардэчна запрашаем ў Aves",
"@welcomeMessage": {},
"itemCount": "{count, plural, =1{1 элемент} other{{count} элементаў}}",
"itemCount": "{count, plural, =1{{count} элемент} other{{count} элементаў}}",
"@itemCount": {
"placeholders": {
"count": {}
@ -1495,25 +1495,25 @@
"@settingsViewerShowHistogram": {},
"settingsVideoPlaybackTile": "Прайграванне",
"@settingsVideoPlaybackTile": {},
"columnCount": "{count, plural, =1{1 слупок} few{{count} слупкі} other{{count} слупкоў}}",
"columnCount": "{count, plural, =1{{count} слупок} few{{count} слупкі} other{{count} слупкоў}}",
"@columnCount": {
"placeholders": {
"count": {}
}
},
"timeSeconds": "{seconds, plural, =1{1 секунда} few{{seconds} секунды} other{{seconds} секунд}}",
"timeSeconds": "{count, plural, =1{{count} секунда} few{{count} секунды} other{{count} секунд}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
}
},
"timeDays": "{days, plural, =1{1 дзень} few{{days} дні} other{{days} дзён}}",
"timeDays": "{count, plural, =1{{count} дзень} few{{count} дні} other{{count} дзён}}",
"@timeDays": {
"placeholders": {
"days": {}
}
},
"timeMinutes": "{minutes, plural, =1{1 хвіліна} few{{minutes} хвіліны} other{{minutes} хвілін}}",
"timeMinutes": "{count, plural, =1{{count} хвіліна} few{{count} хвіліны} other{{count} хвілін}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
@ -1536,5 +1536,7 @@
"renameProcessorHash": "Хэш",
"@renameProcessorHash": {},
"settingsForceWesternArabicNumeralsTile": "Прымусовыя арабскія лічбы",
"@settingsForceWesternArabicNumeralsTile": {}
"@settingsForceWesternArabicNumeralsTile": {},
"chipActionShowCollection": "Паказаць ў Калекцыі",
"@chipActionShowCollection": {}
}

View file

@ -7,13 +7,13 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "Estic dacord amb els termes i condicions",
"@welcomeTermsToggle": {},
"itemCount": "{count, plural, =1{1 element} other{{count} elements}}",
"itemCount": "{count, plural, =1{{count} element} other{{count} elements}}",
"@itemCount": {
"placeholders": {
"count": {}
}
},
"columnCount": "{count, plural, =1{1 columna} other{{count} columnes}}",
"columnCount": "{count, plural, =1{{count} columna} other{{count} columnes}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -479,19 +479,19 @@
"@policyPageTitle": {},
"collectionPageTitle": "Coŀlecció",
"@collectionPageTitle": {},
"timeSeconds": "{seconds, plural, =1{1 segon} other{{seconds} segons}}",
"timeSeconds": "{count, plural, =1{{count} segon} other{{count} segons}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
}
},
"timeMinutes": "{minutes, plural, =1{1 minut} other{{minutes} minuts}}",
"timeMinutes": "{count, plural, =1{{count} minut} other{{count} minuts}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
}
},
"timeDays": "{days, plural, =1{1 dia} other{{days} dies}}",
"timeDays": "{count, plural, =1{{count} dia} other{{count} dies}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -1524,5 +1524,7 @@
"placeholders": {
"count": {}
}
}
},
"chipActionShowCollection": "Mostrar a Coŀlecció",
"@chipActionShowCollection": {}
}

View file

@ -4,13 +4,13 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "ڕازیم بە مەرج و یاساکانی بەکارهێنان",
"@welcomeTermsToggle": {},
"columnCount": "{count, plural, =1{١ ڕیز} other{ڕیز {count}}}",
"columnCount": "{count, plural, other{ڕیز {count}}}",
"@columnCount": {
"placeholders": {
"count": {}
}
},
"timeSeconds": "{seconds, plural, =1{١ چرکە} other{چرکە {seconds}}}",
"timeSeconds": "{count, plural, other{چرکە {count}}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
@ -92,7 +92,7 @@
"@entryActionInfo": {},
"entryActionRestore": "گێڕانەوە",
"@entryActionRestore": {},
"timeDays": "{days, plural, =1{١ ڕۆژ} other{ڕۆژ {days}}}",
"timeDays": "{count, plural, other{ڕۆژ {count}}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -131,7 +131,7 @@
"@videoActionPause": {},
"videoActionPlay": "لێدان",
"@videoActionPlay": {},
"itemCount": "{count, plural, =1{١ دانە} other{دانە {count}}}",
"itemCount": "{count, plural, other{دانە {count}}}",
"@itemCount": {
"placeholders": {
"count": {}
@ -155,7 +155,7 @@
"@chipActionCreateAlbum": {},
"entryActionRename": "ناوگۆڕین",
"@entryActionRename": {},
"timeMinutes": "{minutes, plural, =1{١ خولەک} other{خولەک {minutes}}}",
"timeMinutes": "{count, plural, other{خولەک {count}}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}

View file

@ -44,19 +44,19 @@
}
}
},
"timeDays": "{days, plural, =1{1 den} few{{days} dny} other{{days} dnů}}",
"timeDays": "{count, plural, =1{{count} den} few{{count} dny} other{{count} dnů}}",
"@timeDays": {
"placeholders": {
"days": {}
}
},
"itemCount": "{count, plural, =1{1 položka} few{{count} položky} other{{count} položek}}",
"itemCount": "{count, plural, =1{{count} položka} few{{count} položky} other{{count} položek}}",
"@itemCount": {
"placeholders": {
"count": {}
}
},
"columnCount": "{count, plural, =1{1 sloupec} few{{count} sloupce} other{{count} sloupců}}",
"columnCount": "{count, plural, =1{{count} sloupec} few{{count} sloupce} other{{count} sloupců}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -1279,13 +1279,13 @@
"@settingsThumbnailShowVideoDuration": {},
"settingsCollectionQuickActionsTile": "Rychlé akce",
"@settingsCollectionQuickActionsTile": {},
"timeMinutes": "{minutes, plural, =1{1 minuta} few{{minutes} minuty} other{{minutes} minut}}",
"timeMinutes": "{count, plural, =1{{count} minuta} few{{count} minuty} other{{count} minut}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
}
},
"timeSeconds": "{seconds, plural, =1{1 sekunda} few{{seconds} sekundy} other{{seconds} sekund}}",
"timeSeconds": "{count, plural, =1{{count} sekunda} few{{count} sekundy} other{{count} sekund}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
@ -1532,5 +1532,7 @@
"videoRepeatActionSetEnd": "Nastavit konec",
"@videoRepeatActionSetEnd": {},
"videoActionABRepeat": "Opakování A-B",
"@videoActionABRepeat": {}
"@videoActionABRepeat": {},
"chipActionShowCollection": "Zobrazit ve sbírce",
"@chipActionShowCollection": {}
}

View file

@ -7,13 +7,13 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "Ich stimme den Bedingungen und Konditionen zu",
"@welcomeTermsToggle": {},
"itemCount": "{count, plural, =1{1 Element} other{{count} Elemente}}",
"itemCount": "{count, plural, =1{{count} Element} other{{count} Elemente}}",
"@itemCount": {},
"timeSeconds": "{seconds, plural, =1{1 Sekunde} other{{seconds} Sekunde}}",
"timeSeconds": "{count, plural, =1{{count} Sekunde} other{{count} Sekunde}}",
"@timeSeconds": {},
"timeMinutes": "{minutes, plural, =1{1 Minute} other{{minutes} Minuten}}",
"timeMinutes": "{count, plural, =1{{count} Minute} other{{count} Minuten}}",
"@timeMinutes": {},
"timeDays": "{days, plural, =1{1 Tag} other{{days} Tage}}",
"timeDays": "{count, plural, =1{{count} Tag} other{{count} Tage}}",
"@timeDays": {},
"focalLength": "{length} mm",
"@focalLength": {},
@ -1193,7 +1193,7 @@
"@settingsAccessibilityShowPinchGestureAlternatives": {},
"settingsDisplayUseTvInterface": "Android-TV Oberfläche",
"@settingsDisplayUseTvInterface": {},
"columnCount": "{count, plural, =1{1 Spalte} other{{count} Spalten}}",
"columnCount": "{count, plural, =1{{count} Spalte} other{{count} Spalten}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -1366,5 +1366,19 @@
"entryActionCast": "Übertragen",
"@entryActionCast": {},
"castDialogTitle": "Geräte zur Übertragung",
"@castDialogTitle": {}
"@castDialogTitle": {},
"renameProcessorHash": "Raute",
"@renameProcessorHash": {},
"settingsForceWesternArabicNumeralsTile": "Arabische Ziffern erzwingen",
"@settingsForceWesternArabicNumeralsTile": {},
"videoActionABRepeat": "A-B-Wiederholung",
"@videoActionABRepeat": {},
"videoRepeatActionSetStart": "Start festlegen",
"@videoRepeatActionSetStart": {},
"stopTooltip": "Stop",
"@stopTooltip": {},
"videoRepeatActionSetEnd": "Ende festlegen",
"@videoRepeatActionSetEnd": {},
"chipActionShowCollection": "In Sammlung anzeigen",
"@chipActionShowCollection": {}
}

View file

@ -7,13 +7,13 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "Συμφωνώ με τους όρους και τις προυποθέσεις",
"@welcomeTermsToggle": {},
"itemCount": "{count, plural, =1{1 στοιχείο} other{{count} στοιχεία}}",
"itemCount": "{count, plural, =1{{count} στοιχείο} other{{count} στοιχεία}}",
"@itemCount": {},
"timeSeconds": "{seconds, plural, =1{1 δευτερόλεπτο} other{{seconds} δευτερόλεπτα}}",
"timeSeconds": "{count, plural, =1{{count} δευτερόλεπτο} other{{count} δευτερόλεπτα}}",
"@timeSeconds": {},
"timeMinutes": "{minutes, plural, =1{1 λεπτό} other{{minutes} λεπτά}}",
"timeMinutes": "{count, plural, =1{{count} λεπτό} other{{count} λεπτά}}",
"@timeMinutes": {},
"timeDays": "{days, plural, =1{1 ημέρα} other{{days} ημέρες}}",
"timeDays": "{count, plural, =1{{count} ημέρα} other{{count} ημέρες}}",
"@timeDays": {},
"focalLength": "{length} mm",
"@focalLength": {},
@ -1169,7 +1169,7 @@
"@settingsSubtitleThemeTextPositionDialogTitle": {},
"entryInfoActionExportMetadata": "Εξαγωγή μεταδεδομένων",
"@entryInfoActionExportMetadata": {},
"columnCount": "{count, plural, =1{1 στήλη} other{{count} στήλες}}",
"columnCount": "{count, plural, =1{{count} στήλη} other{{count} στήλες}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -1338,5 +1338,7 @@
"overlayHistogramRGB": "RGB",
"@overlayHistogramRGB": {},
"overlayHistogramLuminance": "Φωτεινότητα",
"@overlayHistogramLuminance": {}
"@overlayHistogramLuminance": {},
"chipActionShowCollection": "Εμφάνιση στη Συλλογή",
"@chipActionShowCollection": {}
}

View file

@ -3,35 +3,45 @@
"welcomeMessage": "Welcome to Aves",
"welcomeOptional": "Optional",
"welcomeTermsToggle": "I agree to the terms and conditions",
"itemCount": "{count, plural, =1{1 item} other{{count} items}}",
"itemCount": "{count, plural, =1{{count} item} other{{count} items}}",
"@itemCount": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
"columnCount": "{count, plural, =1{1 column} other{{count} columns}}",
"columnCount": "{count, plural, =1{{count} column} other{{count} columns}}",
"@columnCount": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
"timeSeconds": "{seconds, plural, =1{1 second} other{{seconds} seconds}}",
"timeSeconds": "{count, plural, =1{{count} second} other{{count} seconds}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
"count": {
"format": "decimalPattern"
}
}
},
"timeMinutes": "{minutes, plural, =1{1 minute} other{{minutes} minutes}}",
"timeMinutes": "{count, plural, =1{{count} minute} other{{count} minutes}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
"count": {
"format": "decimalPattern"
}
}
},
"timeDays": "{days, plural, =1{1 day} other{{days} days}}",
"timeDays": "{count, plural, =1{{count} day} other{{count} days}}",
"@timeDays": {
"placeholders": {
"days": {}
"count": {
"format": "decimalPattern"
}
}
},
"focalLength": "{length} mm",
@ -75,6 +85,7 @@
"sourceStateLocatingPlaces": "Locating places",
"chipActionDelete": "Delete",
"chipActionShowCollection": "Show in Collection",
"chipActionGoToAlbumPage": "Show in Albums",
"chipActionGoToCountryPage": "Show in Countries",
"chipActionGoToPlacePage": "Show in Places",
@ -376,13 +387,17 @@
"binEntriesConfirmationDialogMessage": "{count, plural, =1{Move this item to the recycle bin?} other{Move these {count} items to the recycle bin?}}",
"@binEntriesConfirmationDialogMessage": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
"deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Delete this item?} other{Delete these {count} items?}}",
"@deleteEntriesConfirmationDialogMessage": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
"moveUndatedConfirmationDialogMessage": "Save item dates before proceeding?",
@ -446,13 +461,17 @@
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Delete this album and the item in it?} other{Delete this album and the {count} items in it?}}",
"@deleteSingleAlbumConfirmationDialogMessage": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Delete these albums and the item in them?} other{Delete these albums and the {count} items in them?}}",
"@deleteMultiAlbumConfirmationDialogMessage": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
@ -597,61 +616,81 @@
"collectionDeleteFailureFeedback": "{count, plural, =1{Failed to delete 1 item} other{Failed to delete {count} items}}",
"@collectionDeleteFailureFeedback": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
"collectionCopyFailureFeedback": "{count, plural, =1{Failed to copy 1 item} other{Failed to copy {count} items}}",
"@collectionCopyFailureFeedback": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
"collectionMoveFailureFeedback": "{count, plural, =1{Failed to move 1 item} other{Failed to move {count} items}}",
"@collectionMoveFailureFeedback": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
"collectionRenameFailureFeedback": "{count, plural, =1{Failed to rename 1 item} other{Failed to rename {count} items}}",
"@collectionRenameFailureFeedback": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
"collectionEditFailureFeedback": "{count, plural, =1{Failed to edit 1 item} other{Failed to edit {count} items}}",
"@collectionEditFailureFeedback": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
"collectionExportFailureFeedback": "{count, plural, =1{Failed to export 1 page} other{Failed to export {count} pages}}",
"@collectionExportFailureFeedback": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
"collectionCopySuccessFeedback": "{count, plural, =1{Copied 1 item} other{Copied {count} items}}",
"@collectionCopySuccessFeedback": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
"collectionMoveSuccessFeedback": "{count, plural, =1{Moved 1 item} other{Moved {count} items}}",
"@collectionMoveSuccessFeedback": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
"collectionRenameSuccessFeedback": "{count, plural, =1{Renamed 1 item} other{Renamed {count} items}}",
"@collectionRenameSuccessFeedback": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
"collectionEditSuccessFeedback": "{count, plural, =1{Edited 1 item} other{Edited {count} items}}",
"@collectionEditSuccessFeedback": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
@ -949,7 +988,9 @@
"statsWithGps": "{count, plural, =1{1 item with location} other{{count} items with location}}",
"@statsWithGps": {
"placeholders": {
"count": {}
"count": {
"format": "decimalPattern"
}
}
},
"statsTopCountriesSectionTitle": "Top Countries",

View file

@ -7,13 +7,13 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "Acepto los términos y condiciones",
"@welcomeTermsToggle": {},
"itemCount": "{count, plural, =1{1 elemento} other{{count} elementos}}",
"itemCount": "{count, plural, =1{{count} elemento} other{{count} elementos}}",
"@itemCount": {},
"timeSeconds": "{seconds, plural, =1{1 segundo} other{{seconds} segundos}}",
"timeSeconds": "{count, plural, =1{{count} segundo} other{{count} segundos}}",
"@timeSeconds": {},
"timeMinutes": "{minutes, plural, =1{1 minuto} other{{minutes} minutos}}",
"timeMinutes": "{count, plural, =1{{count} minuto} other{{count} minutos}}",
"@timeMinutes": {},
"timeDays": "{days, plural, =1{1 día} other{{days} días}}",
"timeDays": "{count, plural, =1{{count} día} other{{count} días}}",
"@timeDays": {},
"focalLength": "{length} mm",
"@focalLength": {},
@ -1187,7 +1187,7 @@
"@keepScreenOnVideoPlayback": {},
"settingsAccessibilityShowPinchGestureAlternatives": "Mostrar alternativas a gestos multitáctiles",
"@settingsAccessibilityShowPinchGestureAlternatives": {},
"columnCount": "{count, plural, =1{1 columna} other{{count} columnas}}",
"columnCount": "{count, plural, =1{{count} columna} other{{count} columnas}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -1378,5 +1378,7 @@
"settingsForceWesternArabicNumeralsTile": "Forzar números arábigos",
"@settingsForceWesternArabicNumeralsTile": {},
"renameProcessorHash": "Hash",
"@renameProcessorHash": {}
"@renameProcessorHash": {},
"chipActionShowCollection": "Mostrar en Colección",
"@chipActionShowCollection": {}
}

View file

@ -1,25 +1,25 @@
{
"saveTooltip": "Gorde",
"@saveTooltip": {},
"columnCount": "{count, plural, =1{zutabe 1} other{{count} zutabe}}",
"columnCount": "{count, plural, =1{zutabe {count}} other{{count} zutabe}}",
"@columnCount": {
"placeholders": {
"count": {}
}
},
"timeSeconds": "{seconds, plural, =1{segundu 1} other{{seconds} segundu}}",
"timeSeconds": "{count, plural, =1{segundu {count}} other{{count} segundu}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
}
},
"timeMinutes": "{minutes, plural, =1{minutu 1} other{{minutes} minutu}}",
"timeMinutes": "{count, plural, =1{minutu {count}} other{{count} minutu}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
}
},
"timeDays": "{days, plural, =1{egun 1} other{{days} egun}}",
"timeDays": "{count, plural, =1{egun {count}} other{{count} egun}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -50,7 +50,7 @@
"@welcomeMessage": {},
"welcomeOptional": "Aukerazkoa",
"@welcomeOptional": {},
"itemCount": "{count, plural, =1{elementu 1} other{{count} elementu}}",
"itemCount": "{count, plural, =1{elementu {count}} other{{count} elementu}}",
"@itemCount": {
"placeholders": {
"count": {}
@ -1536,5 +1536,7 @@
"videoActionABRepeat": "Atik Brako errepikapena",
"@videoActionABRepeat": {},
"videoRepeatActionSetEnd": "Ezarri amaiera",
"@videoRepeatActionSetEnd": {}
"@videoRepeatActionSetEnd": {},
"chipActionShowCollection": "Erakutsi bilduman",
"@chipActionShowCollection": {}
}

View file

@ -65,9 +65,9 @@
"@hideTooltip": {},
"chipActionCreateAlbum": "ایجاد البوم",
"@chipActionCreateAlbum": {},
"filterNoRatingLabel": "بدون رتبه",
"filterNoRatingLabel": "بدون امتیاز",
"@filterNoRatingLabel": {},
"timeSeconds": "{seconds, plural, =1{1 ثانیع} other{{seconds} ثانیه}}",
"timeSeconds": "{count, plural, =1{{count} ثانیع} other{{count} ثانیه}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
@ -75,7 +75,7 @@
},
"deleteButtonLabel": "پاک کردن",
"@deleteButtonLabel": {},
"itemCount": "{count, plural, =1{1 فایل} other{{count} فایل}}",
"itemCount": "{count, plural, =1{{count} فایل} other{{count} فایل}}",
"@itemCount": {
"placeholders": {
"count": {}
@ -99,7 +99,7 @@
"@doNotAskAgain": {},
"entryActionDelete": "پاک‌کردن",
"@entryActionDelete": {},
"timeMinutes": "{minutes, plural, =1{1 دقیقه} other{{minutes} دقیقه}}",
"timeMinutes": "{count, plural, other{{count} دقیقه}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
@ -123,7 +123,7 @@
"@entryActionRotateCW": {},
"entryInfoActionEditLocation": "ویرایش مکان",
"@entryInfoActionEditLocation": {},
"entryActionFlip": "ایینه کردن افقی",
"entryActionFlip": "آینه کردن افقی",
"@entryActionFlip": {},
"entryActionShare": "اشتراک گذاری",
"@entryActionShare": {},
@ -141,7 +141,7 @@
"@filterBinLabel": {},
"filterNoDateLabel": "بدون تاریخ",
"@filterNoDateLabel": {},
"timeDays": "{days, plural, =1{1 روز} other{{days} روز}}",
"timeDays": "{count, plural, other{{count} روز}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -163,7 +163,7 @@
"@videoActionSkip10": {},
"viewerActionSettings": "تنظیمات",
"@viewerActionSettings": {},
"entryInfoActionEditRating": "ویرایش رتبه",
"entryInfoActionEditRating": "ویرایش امتیاز",
"@entryInfoActionEditRating": {},
"entryInfoActionEditTags": "ویرایش برچسب ها",
"@entryInfoActionEditTags": {},
@ -333,7 +333,7 @@
"@videoPlaybackMuted": {},
"storageVolumeDescriptionFallbackPrimary": "حافظه داخلی",
"@storageVolumeDescriptionFallbackPrimary": {},
"columnCount": "{count, plural, =1{1 ستون} other{{count} ستون}}",
"columnCount": "{count, plural, other{{count} ستون}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -363,7 +363,7 @@
"@nameConflictStrategyReplace": {},
"displayRefreshRatePreferHighest": "بیشترین مقدار",
"@displayRefreshRatePreferHighest": {},
"storageAccessDialogMessage": "لطفا شاخهٔ {directory} در «{volume}» را در صفحه بعد انتخاب کنید و اجازه را به برنامه بدهید.",
"storageAccessDialogMessage": "لطفا شاخهٔ {directory} در «{volume}» را در صفحه بعد انتخاب کنید و به برنامه اجازه بدهید.",
"@storageAccessDialogMessage": {
"placeholders": {
"directory": {
@ -571,9 +571,9 @@
"@setCoverDialogCustom": {},
"nameConflictDialogSingleSourceMessage": "برخی از پرونده های موجود در پوشه مقصد به همین نام هستند.",
"@nameConflictDialogSingleSourceMessage": {},
"missingSystemFilePickerDialogMessage": "انتخابگر پرونده سیستم وجود ندارد یا غیرفعال است. لطفا آن را فعال کنید و دوباره امتحان کنید",
"missingSystemFilePickerDialogMessage": "انتخابگر پرونده سامانه وجود ندارد یا غیرفعال است. لطفا آن را فعال کنید و دوباره امتحان کنید.",
"@missingSystemFilePickerDialogMessage": {},
"nameConflictDialogMultipleSourceMessage": "برخی پرونده ها نام های یکسانی دارند",
"nameConflictDialogMultipleSourceMessage": "برخی پرونده ها نام های یکسانی دارند.",
"@nameConflictDialogMultipleSourceMessage": {},
"noMatchingAppDialogMessage": "هیچ کاره ای وجود ندارد که بتواند این موضوع را مدیریت کند.",
"@noMatchingAppDialogMessage": {},
@ -605,7 +605,7 @@
"@drawerAlbumPage": {},
"drawerTagPage": "برچسب ها",
"@drawerTagPage": {},
"sortByRating": "با رتبه‌بندی",
"sortByRating": "با امتیازبندی",
"@sortByRating": {},
"sortByAlbumFileName": "با آلبوم و نام پرونده",
"@sortByAlbumFileName": {},
@ -621,9 +621,9 @@
"@settingsViewerQuickActionEditorAvailableButtonsSectionTitle": {},
"settingsViewerQuickActionEmpty": "بدون دکمه",
"@settingsViewerQuickActionEmpty": {},
"settingsSlideshowVideoPlaybackTile": "باز‌پخش ویدیو",
"settingsSlideshowVideoPlaybackTile": "پخش ویدیو",
"@settingsSlideshowVideoPlaybackTile": {},
"settingsSlideshowVideoPlaybackDialogTitle": "بازپخش ویدیو",
"settingsSlideshowVideoPlaybackDialogTitle": "پخش ویدیو",
"@settingsSlideshowVideoPlaybackDialogTitle": {},
"settingsVideoResumptionModeTile": "ادامه پخش",
"@settingsVideoResumptionModeTile": {},
@ -639,9 +639,9 @@
"@viewerInfoSearchSuggestionDate": {},
"settingsSlideshowShuffle": "در هم",
"@settingsSlideshowShuffle": {},
"settingsVideoPlaybackTile": "بازپخش",
"settingsVideoPlaybackTile": "پخش",
"@settingsVideoPlaybackTile": {},
"settingsVideoPlaybackPageTitle": "بازپخش",
"settingsVideoPlaybackPageTitle": "پخش",
"@settingsVideoPlaybackPageTitle": {},
"viewerInfoLabelResolution": "وضوح",
"@viewerInfoLabelResolution": {},
@ -668,7 +668,7 @@
"@settingsViewerSlideshowPageTitle": {},
"viewerInfoLabelUri": "URI",
"@viewerInfoLabelUri": {},
"moveUndatedConfirmationDialogSetDate": "ذخیره تاریخ ها",
"moveUndatedConfirmationDialogSetDate": "نگه‌داری تاریخ ها",
"@moveUndatedConfirmationDialogSetDate": {},
"editEntryLocationDialogLongitude": "طول جغرافیایی",
"@editEntryLocationDialogLongitude": {},
@ -865,7 +865,7 @@
},
"settingsConfirmationBeforeMoveToBinItems": "پیش از هدایت موارد به سطل زباله بپرسید",
"@settingsConfirmationBeforeMoveToBinItems": {},
"pinDialogEnter": "وارد کردن شماره",
"pinDialogEnter": "وارد کردن PIN",
"@pinDialogEnter": {},
"collectionGroupDay": "با روز",
"@collectionGroupDay": {},
@ -881,7 +881,7 @@
"@filePickerNoItems": {},
"settingsHomeDialogTitle": "خانه",
"@settingsHomeDialogTitle": {},
"settingsThumbnailShowRating": "نمایش رتبه‌بندی",
"settingsThumbnailShowRating": "نمایش امتیازبندی",
"@settingsThumbnailShowRating": {},
"settingsThumbnailShowTagIcon": "نمایش نماد برچسب",
"@settingsThumbnailShowTagIcon": {},
@ -1067,7 +1067,7 @@
"@drawerPlacePage": {},
"sortOrderNewestFirst": "اول جدیدترین",
"@sortOrderNewestFirst": {},
"albumGroupTier": "با رتبه",
"albumGroupTier": "با امتیاز",
"@albumGroupTier": {},
"sortOrderZtoA": "ی تا ا",
"@sortOrderZtoA": {},
@ -1129,7 +1129,7 @@
"@settingsThumbnailShowHdrIcon": {},
"settingsCollectionQuickActionTabBrowsing": "مرور کردن",
"@settingsCollectionQuickActionTabBrowsing": {},
"settingsCollectionQuickActionEditorPageTitle": "کنش سریع",
"settingsCollectionQuickActionEditorPageTitle": "کنش های سریع",
"@settingsCollectionQuickActionEditorPageTitle": {},
"settingsCollectionQuickActionTabSelecting": "انتخاب کردن",
"@settingsCollectionQuickActionTabSelecting": {},
@ -1153,7 +1153,7 @@
"@settingsSlideshowTransitionTile": {},
"settingsSlideshowIntervalTile": "فاصله",
"@settingsSlideshowIntervalTile": {},
"settingsVideoEnableHardwareAcceleration": "شتاب سختافزاری",
"settingsVideoEnableHardwareAcceleration": "شتاب سختافزاری",
"@settingsVideoEnableHardwareAcceleration": {},
"settingsVideoAutoPlay": "پخش خودکار",
"@settingsVideoAutoPlay": {},
@ -1163,7 +1163,7 @@
"@settingsVideoControlsPageTitle": {},
"settingsVideoBackgroundModeDialogTitle": "حالت پس‌زمینه‌",
"@settingsVideoBackgroundModeDialogTitle": {},
"settingsVideoGestureDoubleTapTogglePlay": "برای پخش/ایست دوبار ضربه ردن",
"settingsVideoGestureDoubleTapTogglePlay": "برای پخش/ایست دوبار ضربه زدن",
"@settingsVideoGestureDoubleTapTogglePlay": {},
"settingsVideoGestureSideDoubleTapSeek": "روی لبه های صفحه دوبار ضربه بزنید تا به عقب/جلو بروید",
"@settingsVideoGestureSideDoubleTapSeek": {},
@ -1249,7 +1249,7 @@
"@renameEntrySetPageInsertTooltip": {},
"renameEntrySetPageTitle": "تغییرنام",
"@renameEntrySetPageTitle": {},
"videoSpeedDialogLabel": "سرعت پازپخش",
"videoSpeedDialogLabel": "سرعت پخش",
"@videoSpeedDialogLabel": {},
"collectionActionAddShortcut": "افزودن میانبر",
"@collectionActionAddShortcut": {},
@ -1293,7 +1293,7 @@
"@settingsCollectionBurstPatternsTile": {},
"settingsCollectionBurstPatternsNone": "هیچکدام",
"@settingsCollectionBurstPatternsNone": {},
"settingsCollectionQuickActionsTile": "کنش سریع",
"settingsCollectionQuickActionsTile": "کنش های سریع",
"@settingsCollectionQuickActionsTile": {},
"settingsUnitSystemTile": "واحد ها",
"@settingsUnitSystemTile": {},
@ -1315,9 +1315,9 @@
"@settingsViewerSectionTitle": {},
"settingsViewerShowInformationSubtitle": "نمایش عنوان، تاریخ، مکان، و...",
"@settingsViewerShowInformationSubtitle": {},
"settingsViewerQuickActionsTile": "کنش های سزیع",
"settingsViewerQuickActionsTile": "کنش های سریع",
"@settingsViewerQuickActionsTile": {},
"settingsViewerQuickActionEditorPageTitle": "کنش های سزیع",
"settingsViewerQuickActionEditorPageTitle": "کنش های سریع",
"@settingsViewerQuickActionEditorPageTitle": {},
"settingsViewerQuickActionEditorBanner": "لمس کنید و نگه دارید تا دکمه ها را حرکت دهید و انتخاب کنید که کدام کنش ها در بیننده نمایش داده می شود.",
"@settingsViewerQuickActionEditorBanner": {},
@ -1440,5 +1440,103 @@
"settingsDisplayRefreshRateModeTile": "نمایش نرخ تازه‌سازی",
"@settingsDisplayRefreshRateModeTile": {},
"settingsSubtitleThemeShowOutline": "نمایش نوار حاشیه و سایه",
"@settingsSubtitleThemeShowOutline": {}
"@settingsSubtitleThemeShowOutline": {},
"collectionCopySuccessFeedback": "{count, plural, =1{۱ مورد رونوشت شد} other{{count} مورد رونوشت شد}}",
"@collectionCopySuccessFeedback": {
"placeholders": {
"count": {}
}
},
"statsWithGps": "{count, plural, =1{1 مورد دارای مکان} other{{count} مورد دارای مکان}}",
"@statsWithGps": {
"placeholders": {
"count": {}
}
},
"editEntryDateDialogShift": "جابه‌جایی",
"@editEntryDateDialogShift": {},
"collectionDeleteFailureFeedback": "{count, plural, =1{پاک‌کردن ۱ مورد ناموفق بود} other{پاک‌کردن {count} مورد ناموفق بود}}",
"@collectionDeleteFailureFeedback": {
"placeholders": {
"count": {}
}
},
"settingsDisablingBinWarningDialogMessage": "موارد موجود در سطل زباله برای همیشه نابود خواهند شد.",
"@settingsDisablingBinWarningDialogMessage": {},
"settingsAllowInstalledAppAccessSubtitle": "برای بهبود نمایش آلبوم بهره‌وری میشود",
"@settingsAllowInstalledAppAccessSubtitle": {},
"settingsHiddenPathsBanner": "تصاویر و ویدیوهای موجود در این پوشه‌ها یا هر یک از زیرپوشه‌های آن‌ها در مجموعه شما دیده نمی‌شوند.",
"@settingsHiddenPathsBanner": {},
"settingsStorageAccessBanner": "برخی پوشه ها برای اصلاح پرونپه های موجود در آنها به یک مجوز دسترسی نیاز دارند. در اینجا می توانید پوشه هایی را که قبلاً به آنها دسترسی داشته اید، مرور کنید.",
"@settingsStorageAccessBanner": {},
"collectionMoveFailureFeedback": "{count, plural, =1{هدایت ۱ مورد ناموفق بود} other{هدایت {count} مورد ناموفق بود}}",
"@collectionMoveFailureFeedback": {
"placeholders": {
"count": {}
}
},
"collectionRenameFailureFeedback": "{count, plural, =1{تغییرنام ۱ مورد ناموفق بود} other{تغییرنام {count} مورد ناموفق بود}}",
"@collectionRenameFailureFeedback": {
"placeholders": {
"count": {}
}
},
"aboutCreditsWorldAtlas1": "این برنامه یک پرونده TopoJSON استفاده میکند از",
"@aboutCreditsWorldAtlas1": {},
"collectionCopyFailureFeedback": "{count, plural, =1{رونوشت ۱ مورد ناموفق بود} other{رونوشت {count} مورد ناموفق بود}}",
"@collectionCopyFailureFeedback": {
"placeholders": {
"count": {}
}
},
"collectionMoveSuccessFeedback": "{count, plural, =1{۱ مورد هدایت شد} other{{count} مورد هدایت شد}}",
"@collectionMoveSuccessFeedback": {
"placeholders": {
"count": {}
}
},
"collectionRenameSuccessFeedback": "{count, plural, =1{۱ مورد تغییرنام یافت} other{{count} مورد تغییرنام یافت}}",
"@collectionRenameSuccessFeedback": {
"placeholders": {
"count": {}
}
},
"collectionEditSuccessFeedback": "{count, plural, =1{۱ مورد ویرایش شد} other{{count} مورد ویرایش شد}}",
"@collectionEditSuccessFeedback": {
"placeholders": {
"count": {}
}
},
"settingsViewerUseCutout": "ناحیه برش را بکار بگیر",
"@settingsViewerUseCutout": {},
"settingsViewerShowHistogram": "نمایش نمودار",
"@settingsViewerShowHistogram": {},
"settingsVideoGestureVerticalDragBrightnessVolume": "برای تنظیم نور/صدا به بالا و پایین بکشید",
"@settingsVideoGestureVerticalDragBrightnessVolume": {},
"mapEmptyRegion": "تصویری در این ناحیه وجود ندارد",
"@mapEmptyRegion": {},
"mapAttributionOsmHot": "داده‌های نقشه © [OpenStreetMap](https:www.openstreetmap.orgcopyright) مشارکت‌کنندگان • کاشی ها توسط [HOT](https:www.hotosm.org) • میزبانی شده به دست [OSM France](https:openstreetmap.fr)",
"@mapAttributionOsmHot": {},
"mapAttributionStamen": "داده‌های نقشه © [OpenStreetMap](https:www.openstreetmap.org/copyright) مشارکت‌کنندگان • کاشی ها بدست [Stamen Design](https:stamen.com)، [CC BY 3.0](https:creativecommons.orglicensesby3.0)",
"@mapAttributionStamen": {},
"addShortcutButtonLabel": "افزودن",
"@addShortcutButtonLabel": {},
"aboutBugReportInstruction": "در گیت‌هاب با گزارش ها و ریزگان دستگاه گزارش کنید",
"@aboutBugReportInstruction": {},
"settingsViewerShowShootingDetails": "نمایش ریزگان تصویربرداری",
"@settingsViewerShowShootingDetails": {},
"settingsHiddenFiltersBanner": "تصاویر و ویدیوهایی که با پالایش‌های مخفی سازگار نیستند در مجموعه شما دیده نمیشوند.",
"@settingsHiddenFiltersBanner": {},
"moveUndatedConfirmationDialogMessage": "پیش از ادامه دادن تاریخ موارد نگه‌داری شود؟",
"@moveUndatedConfirmationDialogMessage": {},
"settingsAllowInstalledAppAccess": "اجازه دسترسی به دارایی برنامه",
"@settingsAllowInstalledAppAccess": {},
"collectionEditFailureFeedback": "{count, plural, =1{ویرایش ۱ مورد ناموفق بود} other{ویرایش {count} مورد ناموفق بود}}",
"@collectionEditFailureFeedback": {
"placeholders": {
"count": {}
}
},
"chipActionShowCollection": "نمایش در مجموعه",
"@chipActionShowCollection": {}
}

View file

@ -137,7 +137,7 @@
"@viewerActionSettings": {},
"filterOnThisDayLabel": "Tänä päivänä",
"@filterOnThisDayLabel": {},
"columnCount": "{count, plural, =1{1 sarake} other{{count} saraketta}}",
"columnCount": "{count, plural, =1{{count} sarake} other{{count} saraketta}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -149,7 +149,7 @@
"@maxBrightnessNever": {},
"videoActionReplay10": "kelaa taaksepäin 10 sekuntia",
"@videoActionReplay10": {},
"itemCount": "{count, plural, =1{1 kohde} other{{count} kohdetta}}",
"itemCount": "{count, plural, =1{{count} kohde} other{{count} kohdetta}}",
"@itemCount": {
"placeholders": {
"count": {}
@ -231,7 +231,7 @@
"@keepScreenOnNever": {},
"mapStyleOsmHot": "Humanitaarinen OSM",
"@mapStyleOsmHot": {},
"timeSeconds": "{seconds, plural, =1{1 sekunti} other{{seconds} sekuntia}}",
"timeSeconds": "{count, plural, =1{{count} sekunti} other{{count} sekuntia}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
@ -265,7 +265,7 @@
"@entryActionRotateCW": {},
"entryActionFlip": "Käännä vaakasuorassa",
"@entryActionFlip": {},
"timeDays": "{days, plural, =1{1 Päivä} other{{days} Päivää}}",
"timeDays": "{count, plural, =1{{count} Päivä} other{{count} Päivää}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -279,7 +279,7 @@
"@entryActionEdit": {},
"entryInfoActionEditRating": "Muokkaa luokitusta",
"@entryInfoActionEditRating": {},
"timeMinutes": "{minutes, plural, =1{1 minuutti} other{{minutes} minuuttia}}",
"timeMinutes": "{count, plural, =1{{count} minuutti} other{{count} minuuttia}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
@ -300,5 +300,7 @@
"entryActionShareImageOnly": "Jaa vain kuva",
"@entryActionShareImageOnly": {},
"filterNoDateLabel": "Päiväämätön",
"@filterNoDateLabel": {}
"@filterNoDateLabel": {},
"chipActionShowCollection": "Näytä kokoelmassa",
"@chipActionShowCollection": {}
}

View file

@ -7,13 +7,13 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "Jaccepte les conditions dutilisation",
"@welcomeTermsToggle": {},
"itemCount": "{count, plural, =1{1 élément} other{{count} éléments}}",
"itemCount": "{count, plural, =1{{count} élément} other{{count} éléments}}",
"@itemCount": {},
"timeSeconds": "{seconds, plural, =0{0 seconde} =1{1 seconde} other{{seconds} secondes}}",
"timeSeconds": "{count, plural, =0{{count} seconde} =1{{count} seconde} other{{count} secondes}}",
"@timeSeconds": {},
"timeMinutes": "{minutes, plural, =0{0 minute} =1{1 minute} other{{minutes} minutes}}",
"timeMinutes": "{count, plural, =0{{count} minute} =1{{count} minute} other{{count} minutes}}",
"@timeMinutes": {},
"timeDays": "{days, plural, =0{0 jour} =1{1 jour} other{{days} jours}}",
"timeDays": "{count, plural, =0{{count} jour} =1{{count} jour} other{{count} jours}}",
"@timeDays": {},
"focalLength": "{length} mm",
"@focalLength": {},
@ -1185,7 +1185,7 @@
"@entryInfoActionRemoveLocation": {},
"keepScreenOnVideoPlayback": "Pendant la lecture de vidéos",
"@keepScreenOnVideoPlayback": {},
"columnCount": "{count, plural, =1{1 colonne} other{{count} colonnes}}",
"columnCount": "{count, plural, =1{{count} colonne} other{{count} colonnes}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -1378,5 +1378,7 @@
"renameProcessorHash": "Hash",
"@renameProcessorHash": {},
"settingsForceWesternArabicNumeralsTile": "Toujours utiliser les chiffres arabes",
"@settingsForceWesternArabicNumeralsTile": {}
"@settingsForceWesternArabicNumeralsTile": {},
"chipActionShowCollection": "Afficher dans Collection",
"@chipActionShowCollection": {}
}

View file

@ -177,25 +177,25 @@
"@appName": {},
"welcomeMessage": "Benvido a Aves",
"@welcomeMessage": {},
"itemCount": "{count, plural, =1{1 elementos} other{{count} elementos}}",
"itemCount": "{count, plural, =1{{count} elementos} other{{count} elementos}}",
"@itemCount": {
"placeholders": {
"count": {}
}
},
"timeSeconds": "{seconds, plural, =1{1 segundo} other{{seconds} segundos}}",
"timeSeconds": "{count, plural, =1{{count} segundo} other{{count} segundos}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
}
},
"timeMinutes": "{minutes, plural, =1{1 minuto} other{{minutes} minutos}}",
"timeMinutes": "{count, plural, =1{{count} minuto} other{{count} minutos}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
}
},
"timeDays": "{days, plural, =1{1 día} other{{days} días}}",
"timeDays": "{count, plural, =1{{count} día} other{{count} días}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -296,5 +296,7 @@
"mapStyleHuaweiTerrain": "Petal Maps (Terreo)",
"@mapStyleHuaweiTerrain": {},
"mapStyleOsmHot": "Humanitarian OpenStreetMap Team",
"@mapStyleOsmHot": {}
"@mapStyleOsmHot": {},
"chipActionShowCollection": "Mostrar na colección",
"@chipActionShowCollection": {}
}

View file

@ -3,19 +3,19 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "मैं नियमों और शर्तों पर सहमत हुं",
"@welcomeTermsToggle": {},
"columnCount": "{count, plural, =1{१ कॉलम} other{{count} कॉलम}}",
"columnCount": "{count, plural, other{{count} कॉलम}}",
"@columnCount": {
"placeholders": {
"count": {}
}
},
"timeSeconds": "{seconds, plural, =1{ १ सेकंड} other{{seconds} सेकंडस}}",
"timeSeconds": "{count, plural, =1{{count} सेकंड} other{{count} सेकंडस}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
}
},
"timeDays": "{days, plural, =1{ १ दिन} other{{days} दिन}}",
"timeDays": "{count, plural, other{{count} दिन}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -35,7 +35,7 @@
"@clearTooltip": {},
"actionRemove": "हटाएं",
"@actionRemove": {},
"itemCount": "{count, plural, =1{ चीज} other{{count} चीजे}}",
"itemCount": "{count, plural, =1{{count} चीज} other{{count} चीजे}}",
"@itemCount": {
"placeholders": {
"count": {}
@ -43,7 +43,7 @@
},
"deleteButtonLabel": "डिलीट",
"@deleteButtonLabel": {},
"timeMinutes": "{minutes, plural, =1{ १ मिनट} other{{minutes} मिनट}}",
"timeMinutes": "{count, plural, other{{count} मिनट}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}

View file

@ -811,7 +811,7 @@
"@widgetOpenPageCollection": {},
"renameEntrySetPageInsertTooltip": "Mező beszúrása",
"@renameEntrySetPageInsertTooltip": {},
"timeDays": "{days, plural, other{{days} nap}}",
"timeDays": "{count, plural, other{{count} nap}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -829,13 +829,13 @@
"count": {}
}
},
"timeSeconds": "{seconds, plural, other{{seconds} másodperc}}",
"timeSeconds": "{count, plural, other{{count} másodperc}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
}
},
"timeMinutes": "{minutes, plural, other{{minutes} perc}}",
"timeMinutes": "{count, plural, other{{count} perc}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
@ -1532,5 +1532,7 @@
"videoActionABRepeat": "A-B ismétlés",
"@videoActionABRepeat": {},
"videoRepeatActionSetEnd": "Végpont beállítása",
"@videoRepeatActionSetEnd": {}
"@videoRepeatActionSetEnd": {},
"chipActionShowCollection": "Megjelenítés a gyűjteményekben",
"@chipActionShowCollection": {}
}

View file

@ -9,11 +9,11 @@
"@welcomeTermsToggle": {},
"itemCount": "{count, plural, other{{count} benda}}",
"@itemCount": {},
"timeSeconds": "{seconds, plural, other{{seconds} detik}}",
"timeSeconds": "{count, plural, other{{count} detik}}",
"@timeSeconds": {},
"timeMinutes": "{minutes, plural, other{{minutes} menit}}",
"timeMinutes": "{count, plural, other{{count} menit}}",
"@timeMinutes": {},
"timeDays": "{days, plural, other{{days} hari}}",
"timeDays": "{count, plural, other{{count} hari}}",
"@timeDays": {},
"focalLength": "{length} mm",
"@focalLength": {},
@ -1378,5 +1378,7 @@
"settingsForceWesternArabicNumeralsTile": "Paksa angka Arab",
"@settingsForceWesternArabicNumeralsTile": {},
"renameProcessorHash": "Hash",
"@renameProcessorHash": {}
"@renameProcessorHash": {},
"chipActionShowCollection": "Tampilkan di Koleksi",
"@chipActionShowCollection": {}
}

View file

@ -782,7 +782,7 @@
"@placePageTitle": {},
"filterOnThisDayLabel": "Á þessum degi",
"@filterOnThisDayLabel": {},
"columnCount": "{count, plural, =1{1 dálkur} other{{count} dálkar}}",
"columnCount": "{count, plural, =1{{count} dálkur} other{{count} dálkar}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -840,7 +840,7 @@
"@albumMimeTypeMixed": {},
"settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Tiltækir hnappar",
"@settingsViewerQuickActionEditorAvailableButtonsSectionTitle": {},
"itemCount": "{count, plural, =1{1 atriði} other{{count} atriðum}}",
"itemCount": "{count, plural, =1{{count} atriði} other{{count} atriðum}}",
"@itemCount": {
"placeholders": {
"count": {}
@ -1136,7 +1136,7 @@
"@drawerCollectionImages": {},
"sortOrderSmallestFirst": "Minnsta fyrst",
"@sortOrderSmallestFirst": {},
"timeSeconds": "{seconds, plural, =1{1 sekúnda} other{{seconds} sekúndur}}",
"timeSeconds": "{count, plural, =1{{count} sekúnda} other{{count} sekúndur}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
@ -1292,7 +1292,7 @@
"@subtitlePositionBottom": {},
"castDialogTitle": "Útvörpunartæki",
"@castDialogTitle": {},
"timeDays": "{days, plural, =1{1 dagur} other{{days} dagar}}",
"timeDays": "{count, plural, =1{{count} dagur} other{{count} dagar}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -1322,7 +1322,7 @@
"@entryActionEdit": {},
"entryInfoActionEditRating": "Breyta einkunn",
"@entryInfoActionEditRating": {},
"timeMinutes": "{minutes, plural, =1{1 mínúta} other{{minutes} mínútur}}",
"timeMinutes": "{count, plural, =1{{count} mínúta} other{{count} mínútur}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
@ -1536,5 +1536,7 @@
"videoActionABRepeat": "Endurtekning A-B",
"@videoActionABRepeat": {},
"videoRepeatActionSetEnd": "Stilla endi",
"@videoRepeatActionSetEnd": {}
"@videoRepeatActionSetEnd": {},
"chipActionShowCollection": "Sýna í safni",
"@chipActionShowCollection": {}
}

View file

@ -7,13 +7,13 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "Accetto i termini e le condizioni",
"@welcomeTermsToggle": {},
"itemCount": "{count, plural, =1{1 elemento} other{{count} elementi}}",
"itemCount": "{count, plural, =1{{count} elemento} other{{count} elementi}}",
"@itemCount": {},
"timeSeconds": "{seconds, plural, =1{1 secondo} other{{seconds} secondi}}",
"timeSeconds": "{count, plural, =1{{count} secondo} other{{count} secondi}}",
"@timeSeconds": {},
"timeMinutes": "{minutes, plural, =1{1 minuto} other{{minutes} minuti}}",
"timeMinutes": "{count, plural, =1{{count} minuto} other{{count} minuti}}",
"@timeMinutes": {},
"timeDays": "{days, plural, =1{1 giorno} other{{days} giorni}}",
"timeDays": "{count, plural, =1{{count} giorno} other{{count} giorni}}",
"@timeDays": {},
"focalLength": "{length} mm",
"@focalLength": {},
@ -1171,7 +1171,7 @@
"@settingsWidgetDisplayedItem": {},
"settingsViewerShowRatingTags": "Mostra valutazione & etichette",
"@settingsViewerShowRatingTags": {},
"columnCount": "{count, plural, =1{1 colonna} other{{count} colonne}}",
"columnCount": "{count, plural, =1{{count} colonna} other{{count} colonne}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -1374,5 +1374,11 @@
"collectionActionSetHome": "Imposta come pagina iniziale",
"@collectionActionSetHome": {},
"setHomeCustomCollection": "Collezione personalizzata",
"@setHomeCustomCollection": {}
"@setHomeCustomCollection": {},
"chipActionShowCollection": "Mostra nella Collezione",
"@chipActionShowCollection": {},
"renameProcessorHash": "Hash",
"@renameProcessorHash": {},
"settingsForceWesternArabicNumeralsTile": "Forza numeri arabi",
"@settingsForceWesternArabicNumeralsTile": {}
}

View file

@ -9,11 +9,11 @@
"@welcomeTermsToggle": {},
"itemCount": "{count, plural, other{{count} 件のアイテム}}",
"@itemCount": {},
"timeSeconds": "{seconds, plural, other{{seconds} 秒}}",
"timeSeconds": "{count, plural, other{{count} 秒}}",
"@timeSeconds": {},
"timeMinutes": "{minutes, plural, other{{minutes} 分}}",
"timeMinutes": "{count, plural, other{{count} 分}}",
"@timeMinutes": {},
"timeDays": "{days, plural, other{{days} 日}}",
"timeDays": "{count, plural, other{{count} 日}}",
"@timeDays": {},
"focalLength": "{length} mm",
"@focalLength": {},
@ -1264,5 +1264,117 @@
"settingsVideoBackgroundModeDialogTitle": "バックグラウンド再生",
"@settingsVideoBackgroundModeDialogTitle": {},
"maxBrightnessAlways": "常時有効",
"@maxBrightnessAlways": {}
"@maxBrightnessAlways": {},
"chipActionShowCollection": "コレクションで表示",
"@chipActionShowCollection": {},
"aboutDataUsageData": "データ",
"@aboutDataUsageData": {},
"searchStatesSectionTitle": "地域",
"@searchStatesSectionTitle": {},
"settingsViewerShowDescription": "説明を表示",
"@settingsViewerShowDescription": {},
"chipActionConfigureVault": "保管庫を設定",
"@chipActionConfigureVault": {},
"videoActionABRepeat": "A-B リピート",
"@videoActionABRepeat": {},
"videoRepeatActionSetStart": "開始点を設定",
"@videoRepeatActionSetStart": {},
"videoRepeatActionSetEnd": "終点を設定",
"@videoRepeatActionSetEnd": {},
"cropAspectRatioSquare": "正方形",
"@cropAspectRatioSquare": {},
"filterTaggedLabel": "タグ付き",
"@filterTaggedLabel": {},
"albumTierVaults": "保管庫",
"@albumTierVaults": {},
"overlayHistogramRGB": "RGB",
"@overlayHistogramRGB": {},
"subtitlePositionBottom": "下",
"@subtitlePositionBottom": {},
"statsTopStatesSectionTitle": "トップ地域",
"@statsTopStatesSectionTitle": {},
"applyTooltip": "適用する",
"@applyTooltip": {},
"viewerActionUnlock": "ビューをアンロック",
"@viewerActionUnlock": {},
"editorActionTransform": "変換",
"@editorActionTransform": {},
"settingsVideoPlaybackPageTitle": "再生",
"@settingsVideoPlaybackPageTitle": {},
"editorTransformCrop": "切り取り",
"@editorTransformCrop": {},
"exportEntryDialogWriteMetadata": "メタデータを書き込む",
"@exportEntryDialogWriteMetadata": {},
"videoResumptionModeNever": "無効",
"@videoResumptionModeNever": {},
"aboutDataUsageSectionTitle": "データ使用量",
"@aboutDataUsageSectionTitle": {},
"videoResumptionModeAlways": "常にオン",
"@videoResumptionModeAlways": {},
"settingsCollectionBurstPatternsTile": "バーストパターン",
"@settingsCollectionBurstPatternsTile": {},
"settingsDisplayUseTvInterface": "Android TV インターフェイス",
"@settingsDisplayUseTvInterface": {},
"tagEditorDiscardDialogMessage": "変更を破棄しますか?",
"@tagEditorDiscardDialogMessage": {},
"widgetTapUpdateWidget": "ウィジェットを更新",
"@widgetTapUpdateWidget": {},
"vaultBinUsageDialogMessage": "ゴミ箱を使用している保管庫があります。",
"@vaultBinUsageDialogMessage": {},
"settingsConfirmationVaultDataLoss": "保管庫のデータ損失警告を表示する",
"@settingsConfirmationVaultDataLoss": {},
"settingsWidgetDisplayedItem": "表示アイテム",
"@settingsWidgetDisplayedItem": {},
"renameProcessorHash": "ハッシュ",
"@renameProcessorHash": {},
"exportEntryDialogQuality": "画質",
"@exportEntryDialogQuality": {},
"castDialogTitle": "キャストデバイス",
"@castDialogTitle": {},
"aboutDataUsageCache": "キャッシュ",
"@aboutDataUsageCache": {},
"aboutDataUsageMisc": "その他",
"@aboutDataUsageMisc": {},
"aboutDataUsageInternal": "内部ストレージ",
"@aboutDataUsageInternal": {},
"aboutDataUsageDatabase": "データベース",
"@aboutDataUsageDatabase": {},
"aboutDataUsageExternal": "外部ストレージ",
"@aboutDataUsageExternal": {},
"collectionActionSetHome": "ホームに設定",
"@collectionActionSetHome": {},
"placeEmpty": "場所なし",
"@placeEmpty": {},
"settingsThumbnailShowHdrIcon": "HDRアイコンを表示",
"@settingsThumbnailShowHdrIcon": {},
"settingsCollectionBurstPatternsNone": "なし",
"@settingsCollectionBurstPatternsNone": {},
"settingsVideoPlaybackTile": "再生",
"@settingsVideoPlaybackTile": {},
"settingsDisablingBinWarningDialogMessage": "ごみ箱の中のアイテムは永久に削除されます。",
"@settingsDisablingBinWarningDialogMessage": {},
"settingsForceWesternArabicNumeralsTile": "アラビア数字を強制する",
"@settingsForceWesternArabicNumeralsTile": {},
"overlayHistogramNone": "なし",
"@overlayHistogramNone": {},
"overlayHistogramLuminance": "明るさ",
"@overlayHistogramLuminance": {},
"settingsModificationWarningDialogMessage": "他の設定は変更されます。",
"@settingsModificationWarningDialogMessage": {},
"setHomeCustomCollection": "カスタムコレクション",
"@setHomeCustomCollection": {},
"settingsAccessibilityShowPinchGestureAlternatives": "マルチタッチジェスチャーの選択肢を表示する",
"@settingsAccessibilityShowPinchGestureAlternatives": {},
"chipActionCreateVault": "保管庫を作成",
"@chipActionCreateVault": {},
"aboutDataUsageClearCache": "キャッシュを削除",
"@aboutDataUsageClearCache": {},
"viewerActionLock": "ビューをロック",
"@viewerActionLock": {},
"cropAspectRatioFree": "フリー",
"@cropAspectRatioFree": {},
"cropAspectRatioOriginal": "オリジナル",
"@cropAspectRatioOriginal": {},
"stopTooltip": "停止",
"@stopTooltip": {}
}

View file

@ -9,11 +9,11 @@
"@welcomeTermsToggle": {},
"itemCount": "{count, plural, other{{count}개}}",
"@itemCount": {},
"timeSeconds": "{seconds, plural, other{{seconds}초}}",
"timeSeconds": "{count, plural, other{{count}초}}",
"@timeSeconds": {},
"timeMinutes": "{minutes, plural, other{{minutes}분}}",
"timeMinutes": "{count, plural, other{{count}분}}",
"@timeMinutes": {},
"timeDays": "{days, plural, other{{days}일}}",
"timeDays": "{count, plural, other{{count}일}}",
"@timeDays": {},
"focalLength": "{length} mm",
"@focalLength": {},
@ -1378,5 +1378,7 @@
"renameProcessorHash": "해시",
"@renameProcessorHash": {},
"settingsForceWesternArabicNumeralsTile": "아라비아 숫자 항상 사용",
"@settingsForceWesternArabicNumeralsTile": {}
"@settingsForceWesternArabicNumeralsTile": {},
"chipActionShowCollection": "미디어 페이지에서 보기",
"@chipActionShowCollection": {}
}

View file

@ -13,13 +13,13 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "Sutinku su taisyklėmis ir sąlygomis",
"@welcomeTermsToggle": {},
"timeSeconds": "{seconds, plural, =1{1 sekundė} few{{seconds} sekundės} other{{seconds} sekundžių}}",
"timeSeconds": "{count, plural, =1{{count} sekundė} few{{count} sekundės} other{{count} sekundžių}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
}
},
"timeDays": "{days, plural, =1{1 diena} few{{days} dienos} other{{days} dienų}}",
"timeDays": "{count, plural, =1{{count} diena} few{{count} dienos} other{{count} dienų}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -285,13 +285,13 @@
"@collectionActionEdit": {},
"welcomeMessage": "Sveiki atvykę į Aves",
"@welcomeMessage": {},
"itemCount": "{count, plural, =1{1 elementas} few{{count} elementai} other{{count} elementų}}",
"itemCount": "{count, plural, =1{{count} elementas} few{{count} elementai} other{{count} elementų}}",
"@itemCount": {
"placeholders": {
"count": {}
}
},
"timeMinutes": "{minutes, plural, =1{1 minutė} few{{minutes} minutės} other{{minutes} minučių}}",
"timeMinutes": "{count, plural, =1{{count} minutė} few{{count} minutės} other{{count} minučių}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
@ -1340,5 +1340,7 @@
"entryActionShareImageOnly": "Bendrinti tik paveikslėlį",
"@entryActionShareImageOnly": {},
"entryInfoActionRemoveLocation": "Pašalinti vietą",
"@entryInfoActionRemoveLocation": {}
"@entryInfoActionRemoveLocation": {},
"chipActionShowCollection": "Rodyti kolekcijoje",
"@chipActionShowCollection": {}
}

View file

@ -23,19 +23,19 @@
"count": {}
}
},
"timeSeconds": "{seconds, plural, other{{seconds} စက္ကန့်}}",
"timeSeconds": "{count, plural, other{{count} စက္ကန့်}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
}
},
"timeMinutes": "{minutes, plural, other{{minutes} မိနစ်}}",
"timeMinutes": "{count, plural, other{{count} မိနစ်}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
}
},
"timeDays": "{days, plural, other{{days} ရက်}}",
"timeDays": "{count, plural, other{{count} ရက်}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -1296,5 +1296,7 @@
"settingsViewerShowHistogram": "Histogram ကို ပြရန်",
"@settingsViewerShowHistogram": {},
"settingsVideoPlaybackTile": "ဖွင့်ကြည့်ခြင်း",
"@settingsVideoPlaybackTile": {}
"@settingsVideoPlaybackTile": {},
"chipActionShowCollection": "စုစည်းမှုထဲမှာ ပြရန်",
"@chipActionShowCollection": {}
}

View file

@ -751,25 +751,25 @@
"@settingsConfirmationTile": {},
"settingsThumbnailSectionTitle": "Miniatyrbilder",
"@settingsThumbnailSectionTitle": {},
"timeMinutes": "{minutes, plural, =1{1 minutt} other{{minutes} minutter}}",
"timeMinutes": "{count, plural, =1{{count} minutt} other{{count} minutter}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
}
},
"timeDays": "{days, plural, =1{1 dag} other{{days} dager}}",
"timeDays": "{count, plural, =1{{count} dag} other{{count} dager}}",
"@timeDays": {
"placeholders": {
"days": {}
}
},
"itemCount": "{count, plural, =1{1 element} other{{count} elementer}}",
"itemCount": "{count, plural, =1{{count} element} other{{count} elementer}}",
"@itemCount": {
"placeholders": {
"count": {}
}
},
"timeSeconds": "{seconds, plural, =1{1 sekund} other{{seconds} sekunder}}",
"timeSeconds": "{count, plural, =1{{count} sekund} other{{count} sekunder}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
@ -1343,7 +1343,7 @@
"@settingsDisplayUseTvInterface": {},
"settingsAccessibilityShowPinchGestureAlternatives": "Vis multi-trykkhåndvendingsalternativer",
"@settingsAccessibilityShowPinchGestureAlternatives": {},
"columnCount": "{count, plural, =1{1 kolonne} other{{count} kolonner}}",
"columnCount": "{count, plural, =1{{count} kolonne} other{{count} kolonner}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -1478,5 +1478,7 @@
"searchStatesSectionTitle": "Tilstander",
"@searchStatesSectionTitle": {},
"vaultLockTypePattern": "Mønster",
"@vaultLockTypePattern": {}
"@vaultLockTypePattern": {},
"chipActionShowCollection": "Vis i samling",
"@chipActionShowCollection": {}
}

View file

@ -7,13 +7,13 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "Ik ga akkoord met de voorwaarden",
"@welcomeTermsToggle": {},
"itemCount": "{count, plural, =1{1 item} other{{count} items}}",
"itemCount": "{count, plural, =1{{count} item} other{{count} items}}",
"@itemCount": {},
"timeSeconds": "{seconds, plural, =1{1 seconde} other{{seconds} seconden}}",
"timeSeconds": "{count, plural, =1{{count} seconde} other{{count} seconden}}",
"@timeSeconds": {},
"timeMinutes": "{minutes, plural, =1{1 minuut} other{{minutes} minuten}}",
"timeMinutes": "{count, plural, =1{{count} minuut} other{{count} minuten}}",
"@timeMinutes": {},
"timeDays": "{days, plural, =1{1 dag} other{{days} dagen}}",
"timeDays": "{count, plural, =1{{count} dag} other{{count} dagen}}",
"@timeDays": {},
"focalLength": "{length} mm",
"@focalLength": {},
@ -387,7 +387,7 @@
"@renameProcessorName": {},
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Verwijder dit album en het item binnen dit album?} other{Verwijder dit album en de {count} items binnen dit album?}}",
"@deleteSingleAlbumConfirmationDialogMessage": {},
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Verwijder deze albums en het item binnen deze albums?} other{Verwijder dit album en de {count} items binnen deze albums?}}",
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Verwijder deze albums en het item binnen deze albums?} other{Verwijder deze albums en de {count} items binnen deze albums?}}",
"@deleteMultiAlbumConfirmationDialogMessage": {},
"exportEntryDialogFormat": "Formaat:",
"@exportEntryDialogFormat": {},
@ -1190,5 +1190,91 @@
"applyTooltip": "Toepassen",
"@applyTooltip": {},
"tagPlaceholderState": "Staat",
"@tagPlaceholderState": {}
"@tagPlaceholderState": {},
"stopTooltip": "Stop",
"@stopTooltip": {},
"chipActionLock": "Vergrendel",
"@chipActionLock": {},
"chipActionShowCountryStates": "Status laten xien",
"@chipActionShowCountryStates": {},
"chipActionGoToPlacePage": "Laat zien in plaatsen",
"@chipActionGoToPlacePage": {},
"subtitlePositionTop": "Boven",
"@subtitlePositionTop": {},
"subtitlePositionBottom": "Onder",
"@subtitlePositionBottom": {},
"settingsThumbnailShowHdrIcon": "HDR icoon zichtbaar",
"@settingsThumbnailShowHdrIcon": {},
"editorTransformCrop": "Bijsnijden",
"@editorTransformCrop": {},
"patternDialogConfirm": "Bevestig patroon",
"@patternDialogConfirm": {},
"pinDialogEnter": "Voer PIN in",
"@pinDialogEnter": {},
"settingsAskEverytime": "Vraag elke keer",
"@settingsAskEverytime": {},
"aboutDataUsageExternal": "Extern",
"@aboutDataUsageExternal": {},
"tooManyItemsErrorDialogMessage": "Probeer opnieuw met minder items.",
"@tooManyItemsErrorDialogMessage": {},
"maxBrightnessAlways": "Altijd",
"@maxBrightnessAlways": {},
"patternDialogEnter": "Voer patroon in",
"@patternDialogEnter": {},
"settingsViewerShowDescription": "Laat beschrijving zien",
"@settingsViewerShowDescription": {},
"exportEntryDialogQuality": "Kwaliteit",
"@exportEntryDialogQuality": {},
"aboutDataUsageDatabase": "Database",
"@aboutDataUsageDatabase": {},
"aboutDataUsageMisc": "Overig",
"@aboutDataUsageMisc": {},
"settingsModificationWarningDialogMessage": "Andere instellingen zullen worden aangepast.",
"@settingsModificationWarningDialogMessage": {},
"vaultDialogLockModeWhenScreenOff": "Vergrendel als scherm uitgaat",
"@vaultDialogLockModeWhenScreenOff": {},
"aboutDataUsageData": "Data",
"@aboutDataUsageData": {},
"aboutDataUsageCache": "Cache",
"@aboutDataUsageCache": {},
"aboutDataUsageInternal": "Intern",
"@aboutDataUsageInternal": {},
"overlayHistogramRGB": "RGB",
"@overlayHistogramRGB": {},
"overlayHistogramLuminance": "Helderheid",
"@overlayHistogramLuminance": {},
"videoResumptionModeNever": "Nooit",
"@videoResumptionModeNever": {},
"pinDialogConfirm": "Bevestig PIN",
"@pinDialogConfirm": {},
"passwordDialogEnter": "Voer wachtwoord in",
"@passwordDialogEnter": {},
"passwordDialogConfirm": "Bevestig wachtwoord",
"@passwordDialogConfirm": {},
"settingsViewerShowHistogram": "Laat histogram zien",
"@settingsViewerShowHistogram": {},
"settingsVideoGestureVerticalDragBrightnessVolume": "Veeg omhoog of naar beneden om helderheid/volume aan te passen",
"@settingsVideoGestureVerticalDragBrightnessVolume": {},
"settingsSubtitleThemeTextPositionTile": "Tekstpositie",
"@settingsSubtitleThemeTextPositionTile": {},
"settingsSubtitleThemeTextPositionDialogTitle": "Tekstpositie",
"@settingsSubtitleThemeTextPositionDialogTitle": {},
"vaultLockTypePattern": "Patroon",
"@vaultLockTypePattern": {},
"entryActionShareImageOnly": "Enkel afbeelding delen",
"@entryActionShareImageOnly": {},
"entryActionShareVideoOnly": "Enkel video delen",
"@entryActionShareVideoOnly": {},
"cropAspectRatioOriginal": "Origineel",
"@cropAspectRatioOriginal": {},
"cropAspectRatioSquare": "Vierkant",
"@cropAspectRatioSquare": {},
"maxBrightnessNever": "Nooit",
"@maxBrightnessNever": {},
"videoResumptionModeAlways": "Altijd",
"@videoResumptionModeAlways": {},
"exportEntryDialogWriteMetadata": "Schrijf metadata",
"@exportEntryDialogWriteMetadata": {},
"chipActionShowCollection": "Tonen in Collectie",
"@chipActionShowCollection": {}
}

View file

@ -1,5 +1,5 @@
{
"timeSeconds": "{seconds, plural, other{{seconds} sekund}}",
"timeSeconds": "{count, plural, other{{count} sekund}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
@ -9,19 +9,19 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "Eg samtykkjer til krava og føresetnadane",
"@welcomeTermsToggle": {},
"itemCount": "{count, plural, =1{1 er vald} other{{count} er valde}}",
"itemCount": "{count, plural, =1{{count} er vald} other{{count} er valde}}",
"@itemCount": {
"placeholders": {
"count": {}
}
},
"timeMinutes": "{minutes, plural, other{{minutes} minutt}}",
"timeMinutes": "{count, plural, other{{count} minutt}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
}
},
"timeDays": "{days, plural, =1{1 dag} other{{days} dagar}}",
"timeDays": "{count, plural, =1{{count} dag} other{{count} dagar}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -1421,7 +1421,7 @@
"@mapAttributionOsmHot": {},
"mapAttributionStamen": "Kartdata © [OpenStreetMap](https://www.openstreetmap.org/copyright)-medverkarar • Fliser av [Stamen Design](https://stamen.com), [CC BY 3.0](https://creativecommons.org/licenses/by/3.0)",
"@mapAttributionStamen": {},
"columnCount": "{count, plural, =1{1 kolonne} other{{count} kolonnar}}",
"columnCount": "{count, plural, =1{{count} kolonne} other{{count} kolonnar}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -1444,5 +1444,7 @@
"filterNoLocationLabel": "Ustadsette",
"@filterNoLocationLabel": {},
"settingsNavigationSectionTitle": "Finn fram",
"@settingsNavigationSectionTitle": {}
"@settingsNavigationSectionTitle": {},
"chipActionShowCollection": "Vis i Samling",
"@chipActionShowCollection": {}
}

View file

@ -233,31 +233,31 @@
"@displayRefreshRatePreferLowest": {},
"videoPlaybackMuted": "Odtwarzaj bez dźwięku",
"@videoPlaybackMuted": {},
"itemCount": "{count, plural, =1{1 element} few{{count} elementy} other{{count} elelmentów}}",
"itemCount": "{count, plural, =1{{count} element} few{{count} elementy} other{{count} elelmentów}}",
"@itemCount": {
"placeholders": {
"count": {}
}
},
"columnCount": "{count, plural, =1{1 rząd} few{{count} rzędy} other{{count} rzędów}}",
"columnCount": "{count, plural, =1{{count} rząd} few{{count} rzędy} other{{count} rzędów}}",
"@columnCount": {
"placeholders": {
"count": {}
}
},
"timeSeconds": "{seconds, plural, =1{1 sekunda} few{{seconds} sekundy} other{{seconds} sekund}}",
"timeSeconds": "{count, plural, =1{{count} sekunda} few{{count} sekundy} other{{count} sekund}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
}
},
"timeMinutes": "{minutes, plural, =1{1 minuta} few{{minutes} minuty} other{{minutes} minut}}",
"timeMinutes": "{count, plural, =1{{count} minuta} few{{count} minuty} other{{count} minut}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
}
},
"timeDays": "{days, plural, =1{1 dzień} few{{days} dni} other{{days} dni}}",
"timeDays": "{count, plural, =1{{count} dzień} few{{count} dni} other{{count} dni}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -1536,5 +1536,7 @@
"settingsForceWesternArabicNumeralsTile": "Wymuszaj cyfry arabskie",
"@settingsForceWesternArabicNumeralsTile": {},
"renameProcessorHash": "Skrót",
"@renameProcessorHash": {}
"@renameProcessorHash": {},
"chipActionShowCollection": "Pokaż w Kolekcji",
"@chipActionShowCollection": {}
}

View file

@ -7,13 +7,13 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "Eu concordo com os Termos e Condições",
"@welcomeTermsToggle": {},
"itemCount": "{count, plural, =1{1 item} other{{count} itens}}",
"itemCount": "{count, plural, =1{{count} item} other{{count} itens}}",
"@itemCount": {},
"timeSeconds": "{seconds, plural, =1{1 segundo} other{{seconds} segundos}}",
"timeSeconds": "{count, plural, =1{{count} segundo} other{{count} segundos}}",
"@timeSeconds": {},
"timeMinutes": "{minutes, plural, =1{1 minuto} other{{minutes} minutos}}",
"timeMinutes": "{count, plural, =1{{count} minuto} other{{count} minutos}}",
"@timeMinutes": {},
"timeDays": "{days, plural, =1{1 dia} other{{days} dias}}",
"timeDays": "{count, plural, =1{{count} dia} other{{count} dias}}",
"@timeDays": {},
"focalLength": "{length} mm",
"@focalLength": {},
@ -1215,7 +1215,7 @@
"@pinDialogConfirm": {},
"passwordDialogEnter": "Digite a senha",
"@passwordDialogEnter": {},
"columnCount": "{count, plural, =1{1 coluna} other{{count} colunas}}",
"columnCount": "{count, plural, =1{{count} coluna} other{{count} colunas}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -1374,5 +1374,7 @@
"stopTooltip": "Parar",
"@stopTooltip": {},
"videoRepeatActionSetStart": "Definir início",
"@videoRepeatActionSetStart": {}
"@videoRepeatActionSetStart": {},
"chipActionShowCollection": "Mostrar na Coleção",
"@chipActionShowCollection": {}
}

View file

@ -5,19 +5,19 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "Sunt de acord cu Termenii și condițiile",
"@welcomeTermsToggle": {},
"timeSeconds": "{seconds, plural, =1{1 secundă} other{{seconds} secunde}}",
"timeSeconds": "{count, plural, =1{{count} secundă} other{{count} secunde}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
}
},
"timeMinutes": "{minutes, plural, =1{1 minut} other{{minutes} minute}}",
"timeMinutes": "{count, plural, =1{{count} minut} other{{count} minute}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
}
},
"timeDays": "{days, plural, =1{1 zi} other{{days} zile}}",
"timeDays": "{count, plural, =1{{count} zi} other{{count} zile}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -444,9 +444,9 @@
},
"exportEntryDialogFormat": "Format:",
"@exportEntryDialogFormat": {},
"exportEntryDialogWidth": "Lăţime",
"exportEntryDialogWidth": "Lățime",
"@exportEntryDialogWidth": {},
"exportEntryDialogHeight": "Înălţime",
"exportEntryDialogHeight": "Înălțime",
"@exportEntryDialogHeight": {},
"renameEntryDialogLabel": "Nume nou",
"@renameEntryDialogLabel": {},
@ -648,7 +648,7 @@
},
"appName": "Aves",
"@appName": {},
"itemCount": "{count, plural, =1{1 element} other{{count} elemente}}",
"itemCount": "{count, plural, =1{{count} element} other{{count} elemente}}",
"@itemCount": {
"placeholders": {
"count": {}
@ -1329,7 +1329,7 @@
"@statsTopCountriesSectionTitle": {},
"settingsAccessibilityShowPinchGestureAlternatives": "Afișare alternative de gesturi atingeri-multiple",
"@settingsAccessibilityShowPinchGestureAlternatives": {},
"columnCount": "{count, plural, =1{1 coloană} other{{count} coloane}}",
"columnCount": "{count, plural, =1{{count} coloană} other{{count} coloane}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -1524,5 +1524,19 @@
"maxBrightnessNever": "Niciodată",
"@maxBrightnessNever": {},
"maxBrightnessAlways": "Mereu",
"@maxBrightnessAlways": {}
"@maxBrightnessAlways": {},
"renameProcessorHash": "Hash",
"@renameProcessorHash": {},
"settingsForceWesternArabicNumeralsTile": "Cifre arabe forțat",
"@settingsForceWesternArabicNumeralsTile": {},
"stopTooltip": "Oprește",
"@stopTooltip": {},
"videoActionABRepeat": "Repetă de la A la B",
"@videoActionABRepeat": {},
"videoRepeatActionSetStart": "Setează începutul",
"@videoRepeatActionSetStart": {},
"videoRepeatActionSetEnd": "Setează sfârșitul",
"@videoRepeatActionSetEnd": {},
"chipActionShowCollection": "Afișați în colecție",
"@chipActionShowCollection": {}
}

View file

@ -7,13 +7,13 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "Я согласен с условиями и положениями",
"@welcomeTermsToggle": {},
"itemCount": "{count, plural, =1{1 объект} few{{count} объекта} other{{count} объектов}}",
"itemCount": "{count, plural, =1{{count} объект} few{{count} объекта} other{{count} объектов}}",
"@itemCount": {},
"timeSeconds": "{seconds, plural, =1{1 секунда} few{{seconds} секунды} other{{seconds} секунд}}",
"timeSeconds": "{count, plural, =1{{count} секунда} few{{count} секунды} other{{count} секунд}}",
"@timeSeconds": {},
"timeMinutes": "{minutes, plural, =1{1 минута} few{{minutes} минуты} other{{minutes} минут}}",
"timeMinutes": "{count, plural, =1{{count} минута} few{{count} минуты} other{{count} минут}}",
"@timeMinutes": {},
"timeDays": "{days, plural, =1{1 день} few{{days} дня} other{{days} дней}}",
"timeDays": "{count, plural, =1{{count} день} few{{count} дня} other{{count} дней}}",
"@timeDays": {},
"focalLength": "{length} mm",
"@focalLength": {},
@ -1177,7 +1177,7 @@
"@filterAspectRatioPortraitLabel": {},
"settingsViewerShowRatingTags": "Показать рейтинг и теги",
"@settingsViewerShowRatingTags": {},
"columnCount": "{count, plural, =1{1 столбец} few{{count} столбца} other{{count} столбцов}}",
"columnCount": "{count, plural, =1{{count} столбец} few{{count} столбца} other{{count} столбцов}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -1378,5 +1378,7 @@
"renameProcessorHash": "Хэш",
"@renameProcessorHash": {},
"settingsForceWesternArabicNumeralsTile": "Принудительные арабские цифры",
"@settingsForceWesternArabicNumeralsTile": {}
"@settingsForceWesternArabicNumeralsTile": {},
"chipActionShowCollection": "Показать в Коллекции",
"@chipActionShowCollection": {}
}

View file

@ -27,7 +27,7 @@
"@chipActionDelete": {},
"welcomeTermsToggle": "Súhlasím s pravidlami a podmienkami",
"@welcomeTermsToggle": {},
"timeDays": "{days, plural, other{{days} dni}}",
"timeDays": "{count, plural, other{{count} dni}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -50,7 +50,7 @@
"@doubleBackExitMessage": {},
"welcomeOptional": "Voliteľné",
"@welcomeOptional": {},
"timeMinutes": "{minutes, plural, =1{1 minúta} few{{minutes} minúty} other{{minutes} minút}}",
"timeMinutes": "{count, plural, =1{{count} minúta} few{{count} minúty} other{{count} minút}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
@ -638,19 +638,19 @@
"@filePickerOpenFrom": {},
"filePickerUseThisFolder": "Použiť tento priečinok",
"@filePickerUseThisFolder": {},
"itemCount": "{count, plural, =1{1 položka} other{{count} položiek}}",
"itemCount": "{count, plural, =1{{count} položka} other{{count} položiek}}",
"@itemCount": {
"placeholders": {
"count": {}
}
},
"columnCount": "{count, plural, =1{1 stĺpec} other{{count} stĺpcov}}",
"columnCount": "{count, plural, =1{{count} stĺpec} other{{count} stĺpcov}}",
"@columnCount": {
"placeholders": {
"count": {}
}
},
"timeSeconds": "{seconds, plural, =1{1 sekunda} other{{seconds} sekúnd}}",
"timeSeconds": "{count, plural, =1{{count} sekunda} other{{count} sekúnd}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
@ -1524,5 +1524,7 @@
"setHomeCustomCollection": "Kolekcia na mieru",
"@setHomeCustomCollection": {},
"settingsThumbnailShowHdrIcon": "Zobraziť ikonu HDR",
"@settingsThumbnailShowHdrIcon": {}
"@settingsThumbnailShowHdrIcon": {},
"chipActionShowCollection": "Zobraziť v kolekcií",
"@chipActionShowCollection": {}
}

View file

@ -106,25 +106,25 @@
"count": {}
}
},
"columnCount": "{count, plural, =1{1 kolumn} other{{count} kolumner}}",
"columnCount": "{count, plural, =1{{count} kolumn} other{{count} kolumner}}",
"@columnCount": {
"placeholders": {
"count": {}
}
},
"timeSeconds": "{seconds, plural, =1{1 sekund} other{{seconds} sekunder}}",
"timeSeconds": "{count, plural, =1{{count} sekund} other{{count} sekunder}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
}
},
"timeMinutes": "{minutes, plural, =1{1 minut} other{{minutes} minuter}}",
"timeMinutes": "{count, plural, =1{{count} minut} other{{count} minuter}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
}
},
"timeDays": "{days, plural, =1{1 dag} other{{days} dagar}}",
"timeDays": "{count, plural, =1{{count} dag} other{{count} dagar}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -835,5 +835,7 @@
"filePickerUseThisFolder": "Använd den har mappen",
"@filePickerUseThisFolder": {},
"chipActionUnpin": "Släpp från fästet",
"@chipActionUnpin": {}
"@chipActionUnpin": {},
"chipActionShowCollection": "Visa i samling",
"@chipActionShowCollection": {}
}

View file

@ -649,5 +649,7 @@
"description": "a list of unsupported types"
}
}
}
},
"chipActionShowCollection": "แสดงคอลเลกชัน",
"@chipActionShowCollection": {}
}

View file

@ -7,13 +7,13 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "Hüküm ve koşulları kabul ediyorum",
"@welcomeTermsToggle": {},
"itemCount": "{count, plural, =1{1 öğe} other{{count} öğe}}",
"itemCount": "{count, plural, other{{count} öğe}}",
"@itemCount": {},
"timeSeconds": "{seconds, plural, =1{1 saniye} other{{seconds} saniye}}",
"timeSeconds": "{count, plural, other{{count} saniye}}",
"@timeSeconds": {},
"timeMinutes": "{minutes, plural, =1{1 dakika} other{{minutes} dakika}}",
"timeMinutes": "{count, plural, other{{count} dakika}}",
"@timeMinutes": {},
"timeDays": "{days, plural, =1{1 gün} other{{days} gün}}",
"timeDays": "{count, plural, other{{count} gün}}",
"@timeDays": {},
"focalLength": "{length} mm",
"@focalLength": {},
@ -1378,5 +1378,7 @@
"settingsForceWesternArabicNumeralsTile": "Arap rakamlarını zorla",
"@settingsForceWesternArabicNumeralsTile": {},
"renameProcessorHash": "Sağlama",
"@renameProcessorHash": {}
"@renameProcessorHash": {},
"chipActionShowCollection": "Koleksiyonda göster",
"@chipActionShowCollection": {}
}

View file

@ -7,7 +7,7 @@
"@welcomeOptional": {},
"welcomeTermsToggle": "Я погоджуюсь з умовами та положеннями",
"@welcomeTermsToggle": {},
"timeDays": "{days, plural, =1{1 день} few{{days} дні} other{{days} днів}}",
"timeDays": "{count, plural, =1{{count} день} few{{count} дні} other{{count} днів}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -637,19 +637,19 @@
"@entryActionShareVideoOnly": {},
"continueButtonLabel": "ПРОДОВЖИТИ",
"@continueButtonLabel": {},
"itemCount": "{count, plural, =1{1 елемент} few{{count} елементи} other{{count} елементів}}",
"itemCount": "{count, plural, =1{{count} елемент} few{{count} елементи} other{{count} елементів}}",
"@itemCount": {
"placeholders": {
"count": {}
}
},
"timeSeconds": "{seconds, plural, =1{1 секунда} few{{seconds} секунди} other{{seconds} секунд}}",
"timeSeconds": "{count, plural, =1{{count} секунда} few{{count} секунди} other{{count} секунд}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
}
},
"timeMinutes": "{minutes, plural, =1{1 хвилина} few{{minutes} хвилини} other{{minutes} хвилин}}",
"timeMinutes": "{count, plural, =1{{count} хвилина} few{{count} хвилини} other{{count} хвилин}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
@ -1345,7 +1345,7 @@
"@mapAttributionOsmHot": {},
"settingsAccessibilityShowPinchGestureAlternatives": "Показувати альтернативи мультисенсорним жестам",
"@settingsAccessibilityShowPinchGestureAlternatives": {},
"columnCount": "{count, plural, =1{1 стовпець} few{{count} стовпці} other{{count} стовпців}}",
"columnCount": "{count, plural, =1{{count} стовпець} few{{count} стовпці} other{{count} стовпців}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -1532,5 +1532,11 @@
"stopTooltip": "Зупинити",
"@stopTooltip": {},
"videoActionABRepeat": "Повторити від А до Б",
"@videoActionABRepeat": {}
"@videoActionABRepeat": {},
"renameProcessorHash": "Хеш",
"@renameProcessorHash": {},
"settingsForceWesternArabicNumeralsTile": "Примусові арабські цифри",
"@settingsForceWesternArabicNumeralsTile": {},
"chipActionShowCollection": "Показати у Колекції",
"@chipActionShowCollection": {}
}

View file

@ -951,7 +951,7 @@
"@placePageTitle": {},
"filterOnThisDayLabel": "Vào ngày này",
"@filterOnThisDayLabel": {},
"columnCount": "{count, plural, =1{1 cột} other{{count} cột}}",
"columnCount": "{count, plural, other{{count} cột}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -1007,7 +1007,7 @@
"@albumMimeTypeMixed": {},
"settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Các nút có sẵn",
"@settingsViewerQuickActionEditorAvailableButtonsSectionTitle": {},
"itemCount": "{count, plural, =1{1 mục} other{{count} mục}}",
"itemCount": "{count, plural, other{{count} mục}}",
"@itemCount": {
"placeholders": {
"count": {}
@ -1271,7 +1271,7 @@
"@drawerCollectionImages": {},
"sortOrderSmallestFirst": "Nhỏ trước",
"@sortOrderSmallestFirst": {},
"timeSeconds": "{seconds, plural, =1{1 giây} other{{seconds} giây}}",
"timeSeconds": "{count, plural, other{{count} giây}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
@ -1405,7 +1405,7 @@
"@settingsNavigationDrawerTabAlbums": {},
"subtitlePositionBottom": "Dưới",
"@subtitlePositionBottom": {},
"timeDays": "{days, plural, =1{1 ngày} other{{days} ngày}}",
"timeDays": "{count, plural, other{{count} ngày}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -1429,7 +1429,7 @@
"@settingsViewerQuickActionsTile": {},
"newVaultDialogTitle": "Két sắt mới",
"@newVaultDialogTitle": {},
"timeMinutes": "{minutes, plural, =1{1 phút} other{{minutes} phút}}",
"timeMinutes": "{count, plural, other{{count} phút}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
@ -1536,5 +1536,7 @@
"renameProcessorHash": "Băm",
"@renameProcessorHash": {},
"settingsForceWesternArabicNumeralsTile": "Buộc chữ số Ả Rập",
"@settingsForceWesternArabicNumeralsTile": {}
"@settingsForceWesternArabicNumeralsTile": {},
"chipActionShowCollection": "Hiển thị trong Bộ sưu tập",
"@chipActionShowCollection": {}
}

View file

@ -9,11 +9,11 @@
"@welcomeTermsToggle": {},
"itemCount": "{count, plural, other{{count} 项}}",
"@itemCount": {},
"timeSeconds": "{seconds, plural, other{{seconds} 秒}}",
"timeSeconds": "{count, plural, other{{count} 秒}}",
"@timeSeconds": {},
"timeMinutes": "{minutes, plural, other{{minutes} 分}}",
"timeMinutes": "{count, plural, other{{count} 分}}",
"@timeMinutes": {},
"timeDays": "{days, plural, other{{days} 天}}",
"timeDays": "{count, plural, other{{count} 天}}",
"@timeDays": {},
"focalLength": "{length} mm",
"@focalLength": {},
@ -723,7 +723,7 @@
"@searchMetadataSectionTitle": {},
"settingsPageTitle": "设置",
"@settingsPageTitle": {},
"settingsSystemDefault": "系统",
"settingsSystemDefault": "系统默认",
"@settingsSystemDefault": {},
"settingsDefault": "默认",
"@settingsDefault": {},
@ -1377,6 +1377,8 @@
"@videoRepeatActionSetEnd": {},
"renameProcessorHash": "哈希",
"@renameProcessorHash": {},
"settingsForceWesternArabicNumeralsTile": "强制阿拉伯数数字",
"@settingsForceWesternArabicNumeralsTile": {}
"settingsForceWesternArabicNumeralsTile": "强制使用阿拉伯数字",
"@settingsForceWesternArabicNumeralsTile": {},
"chipActionShowCollection": "在媒体集中显示",
"@chipActionShowCollection": {}
}

View file

@ -13,7 +13,7 @@
"count": {}
}
},
"timeDays": "{days, plural, other{{days} 天}}",
"timeDays": "{count, plural, other{{count} 天}}",
"@timeDays": {
"placeholders": {
"days": {}
@ -919,13 +919,13 @@
"@mapZoomOutTooltip": {},
"mapPointNorthUpTooltip": "北方向上",
"@mapPointNorthUpTooltip": {},
"timeSeconds": "{seconds, plural, other{{seconds} 秒}}",
"timeSeconds": "{count, plural, other{{count} 秒}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
}
},
"timeMinutes": "{minutes, plural, other{{minutes} 分}}",
"timeMinutes": "{count, plural, other{{count} 分}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
@ -1419,7 +1419,7 @@
"@settingsVideoResumptionModeDialogTitle": {},
"tagEditorDiscardDialogMessage": "是否要放棄更改?",
"@tagEditorDiscardDialogMessage": {},
"columnCount": "{count, plural, =1{1 列} other{{count} 列}}",
"columnCount": "{count, plural, other{{count} 列}}",
"@columnCount": {
"placeholders": {
"count": {}
@ -1532,5 +1532,11 @@
"videoRepeatActionSetStart": "設置起點",
"@videoRepeatActionSetStart": {},
"videoRepeatActionSetEnd": "設置終點",
"@videoRepeatActionSetEnd": {}
"@videoRepeatActionSetEnd": {},
"renameProcessorHash": "雜湊",
"@renameProcessorHash": {},
"settingsForceWesternArabicNumeralsTile": "強制使用阿拉伯數字",
"@settingsForceWesternArabicNumeralsTile": {},
"chipActionShowCollection": "在收藏品中顯示",
"@chipActionShowCollection": {}
}

View file

@ -87,6 +87,10 @@ class Contributors {
Contributor('slasb37', 'p84haghi@gmail.com'),
Contributor('mimvahedi', 'vahedi0vahedi@gmail.com'),
Contributor('Alireza Rashidi', 'alirezarashidigoorabi@gmail.com'),
Contributor('何意挽秋風', '94283631+RejectVanity@users.noreply.github.com'),
Contributor('cheese', 'deanlemans5646@gmail.com'),
Contributor('Owen Elderbroek', 'o.elderbroek@gmail.com'),
Contributor('Maxi', 'maxitendo01@proton.me'),
// Contributor('Alvi Khan', 'aveenalvi@gmail.com'), // Bengali
// Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese
// Contributor('Khant', 'khant@users.noreply.hosted.weblate.org'), // Burmese

View file

@ -23,6 +23,9 @@ import 'package:latlong2/latlong.dart';
import 'package:xml/xml.dart';
extension ExtraAvesEntryMetadataEdition on AvesEntry {
static final _iso6709LatitudeFormatter = NumberFormat('00.0000', asciiLocale);
static final _iso6709LongitudeFormatter = NumberFormat('000.0000', asciiLocale);
Future<Set<EntryDataType>> editDate(DateModifier userModifier) async {
final dataTypes = <EntryDataType>{};
@ -59,7 +62,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
final date = DateTime.tryParse(xmpDate);
if (date != null) {
// TODO TLAD [date] DateTime.tryParse converts to UTC time, losing the time zone offset
final shiftedDate = date.add(Duration(minutes: appliedModifier.shiftMinutes!));
final shiftedDate = date.add(Duration(seconds: appliedModifier.shiftSeconds!));
editCreateDateXmp(descriptions, shiftedDate);
} else {
reportService.recordError('failed to parse XMP date=$xmpDate', null);
@ -122,9 +125,8 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
if (latLng != null && latLng != removalLocation) {
final latitude = latLng.latitude;
final longitude = latLng.longitude;
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())}';
final isoLat = '${latitude >= 0 ? '+' : '-'}${_iso6709LatitudeFormatter.format(latitude.abs())}';
final isoLon = '${longitude >= 0 ? '+' : '-'}${_iso6709LongitudeFormatter.format(longitude.abs())}';
iso6709String = '$isoLat$isoLon/';
}
mp4Fields[MetadataField.mp4GpsCoordinates] = iso6709String;

View file

@ -11,6 +11,7 @@ import 'package:aves/services/common/services.dart';
import 'package:aves/theme/text.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/time_utils.dart';
import 'package:intl/intl.dart';
extension ExtraAvesEntryProps on AvesEntry {
bool get isValid => !isMissingAtPath && sizeBytes != 0 && width > 0 && height > 0;
@ -51,9 +52,10 @@ extension ExtraAvesEntryProps on AvesEntry {
// text
String get resolutionText {
final ws = width;
final hs = height;
String getResolutionText(String locale) {
final dimensionFormatter = NumberFormat('0', locale);
final ws = dimensionFormatter.format(width);
final hs = dimensionFormatter.format(height);
return isRotated ? '$hs${AText.resolutionSeparator}$ws' : '$ws${AText.resolutionSeparator}$hs';
}

View file

@ -68,9 +68,7 @@ class AspectRatioFilter extends CollectionFilter {
}
@override
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) {
return Icon(AIcons.aspectRatio, size: size);
}
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.aspectRatio, size: size);
@override
String get category => type;

View file

@ -69,7 +69,7 @@ class CoordinateFilter extends CollectionFilter {
}
@override
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.geoBounds, size: size);
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.geoBounds, size: size);
@override
String get category => type;

View file

@ -100,8 +100,7 @@ class DateFilter extends CollectionFilter {
@override
String getLabel(BuildContext context) {
final l10n = context.l10n;
final locale = l10n.localeName;
final locale = context.locale;
switch (level) {
case DateLevel.y:
return DateFormat.y(locale).format(_effectiveDate);
@ -113,7 +112,7 @@ class DateFilter extends CollectionFilter {
if (date != null) {
return DateFormat.MMMd(locale).format(_effectiveDate);
} else {
return l10n.filterOnThisDayLabel;
return context.l10n.filterOnThisDayLabel;
}
case DateLevel.m:
return DateFormat.MMMM(locale).format(_effectiveDate);
@ -123,9 +122,7 @@ class DateFilter extends CollectionFilter {
}
@override
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) {
return Icon(AIcons.date, size: size);
}
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.date, size: size);
@override
String get category => type;

View file

@ -45,7 +45,7 @@ class FavouriteFilter extends CollectionFilter {
String getLabel(BuildContext context) => context.l10n.filterFavouriteLabel;
@override
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.favourite, size: size);
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.favourite, size: size);
@override
Future<Color> color(BuildContext context) {

Some files were not shown because too many files have changed in this diff Show more