diff --git a/CHANGELOG.md b/CHANGELOG.md
index 91bdb7498..8de3150aa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,32 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+## [v1.7.9] - 2023-01-15
+
+### Added
+
+- Viewer: optionally show description on overlay
+- Collection: unlocated/untagged overlay icons
+- Video: stop when losing audio focus
+- Video: stop when becoming noisy
+- Info: Google camera portrait mode item extraction
+- TV: handle overscan
+- TV: improved support for Viewer, Info, Map, Stats
+- TV: option to use TV layout on any device
+- Czech translation (thanks vesp)
+- Polish translation (thanks Piotr K, rehork)
+
+### Changed
+
+- editing description writes XMP `dc:description`, and clears Exif `ImageDescription` / `UserComment`
+- in the tag editor, tapping on applied tag applies it to all items instead of removing it
+- pin app bar when selecting items
+
+### Fixed
+
+- transition between collection and viewer when cutout area is not used
+- saving video playback state when leaving viewer
+
## [v1.7.8] - 2022-12-20
### Added
diff --git a/README.md b/README.md
index 9b7a7b0d4..98f3f5833 100644
--- a/README.md
+++ b/README.md
@@ -15,9 +15,6 @@ Aves is a gallery and metadata explorer app. It is built for Android, with Flutt
[ ](https://appgallery.huawei.com/app/C106014023)
-[ ](https://galaxy.store/aves)
[ ](https://www.amazon.com/dp/B09XQHQQ72)
@@ -44,7 +41,7 @@ It scans your media collection to identify **motion photos**, **panoramas** (aka
**Navigation and search** is an important part of Aves. The goal is for users to easily flow from albums to photos to tags to maps, etc.
-Aves integrates with Android (from **API 19 to 33**, i.e. from KitKat to Android 13) with features such as **widgets**, **app shortcuts**, **screen saver** and **global search** handling. It also works as a **media viewer and picker**.
+Aves integrates with Android (from KitKat to Android 13, including Android TV) with features such as **widgets**, **app shortcuts**, **screen saver** and **global search** handling. It also works as a **media viewer and picker**.
## Screenshots
diff --git a/android/app/build.gradle b/android/app/build.gradle
index abcc4d0ef..051231057 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -191,7 +191,7 @@ dependencies {
implementation 'com.drewnoakes:metadata-extractor:2.18.0'
implementation 'com.github.bumptech.glide:glide:4.14.2'
// SLF4J implementation for `mp4parser`
- implementation 'org.slf4j:slf4j-simple:2.0.3'
+ implementation 'org.slf4j:slf4j-simple:2.0.6'
// forked, built by JitPack:
// - https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 9988d4941..64f920264 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -205,6 +205,14 @@ This change eventually prevents building the app with Flutter v3.3.3.
android:resource="@xml/app_widget_info" />
+
+
+
+
+
+
private lateinit var analysisHandler: AnalysisHandler
+ private lateinit var mediaSessionHandler: MediaSessionHandler
override fun onCreate(savedInstanceState: Bundle?) {
Log.i(LOG_TAG, "onCreate intent=$intent")
@@ -70,9 +71,21 @@ open class MainActivity : FlutterActivity() {
val messenger = flutterEngine!!.dartExecutor
+ // notification: platform -> dart
+ analysisStreamHandler = AnalysisStreamHandler().apply {
+ EventChannel(messenger, AnalysisStreamHandler.CHANNEL).setStreamHandler(this)
+ }
+ errorStreamHandler = ErrorStreamHandler().apply {
+ EventChannel(messenger, ErrorStreamHandler.CHANNEL).setStreamHandler(this)
+ }
+ val mediaCommandStreamHandler = MediaCommandStreamHandler().apply {
+ EventChannel(messenger, MediaCommandStreamHandler.CHANNEL).setStreamHandler(this)
+ }
+
// dart -> platform -> dart
// - need Context
analysisHandler = AnalysisHandler(this, ::onAnalysisCompleted)
+ mediaSessionHandler = MediaSessionHandler(this, mediaCommandStreamHandler)
MethodChannel(messenger, AnalysisHandler.CHANNEL).setMethodCallHandler(analysisHandler)
MethodChannel(messenger, AppAdapterHandler.CHANNEL).setMethodCallHandler(AppAdapterHandler(this))
MethodChannel(messenger, DebugHandler.CHANNEL).setMethodCallHandler(DebugHandler(this))
@@ -83,7 +96,7 @@ open class MainActivity : FlutterActivity() {
MethodChannel(messenger, HomeWidgetHandler.CHANNEL).setMethodCallHandler(HomeWidgetHandler(this))
MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this))
MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this))
- MethodChannel(messenger, MediaSessionHandler.CHANNEL).setMethodCallHandler(MediaSessionHandler(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))
@@ -128,16 +141,6 @@ open class MainActivity : FlutterActivity() {
}
}
- // notification: platform -> dart
- analysisStreamHandler = AnalysisStreamHandler().apply {
- EventChannel(messenger, AnalysisStreamHandler.CHANNEL).setStreamHandler(this)
- }
-
- // notification: platform -> dart
- errorStreamHandler = ErrorStreamHandler().apply {
- EventChannel(messenger, ErrorStreamHandler.CHANNEL).setStreamHandler(this)
- }
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
setupShortcuts()
}
@@ -166,6 +169,7 @@ open class MainActivity : FlutterActivity() {
override fun onDestroy() {
Log.i(LOG_TAG, "onDestroy")
+ mediaSessionHandler.dispose()
mediaStoreChangeStreamHandler.dispose()
settingsChangeStreamHandler.dispose()
super.onDestroy()
@@ -431,7 +435,7 @@ open class MainActivity : FlutterActivity() {
}
}
- var errorStreamHandler: ErrorStreamHandler? = null
+ private var errorStreamHandler: ErrorStreamHandler? = null
suspend fun notifyError(error: String) {
Log.e(LOG_TAG, "notifyError error=$error")
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt
index bf68b220b..c044f710f 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt
@@ -10,6 +10,7 @@ import android.net.Uri
import android.os.Build
import android.os.Handler
import android.os.Looper
+import android.os.TransactionTooLargeException
import android.util.Log
import androidx.core.content.FileProvider
import androidx.core.content.pm.ShortcutInfoCompat
@@ -280,7 +281,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
val title = call.argument("title")
val urisByMimeType = call.argument>>("urisByMimeType")
if (urisByMimeType == null) {
- result.error("setAs-args", "missing arguments", null)
+ result.error("share-args", "missing arguments", null)
return
}
@@ -288,15 +289,14 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
val mimeTypes = urisByMimeType.keys.toTypedArray()
// simplify share intent for a single item, as some apps can handle one item but not more
- val started = if (uriList.size == 1) {
+ val intent = if (uriList.size == 1) {
val uri = uriList.first()
val mimeType = mimeTypes.first()
- val intent = Intent(Intent.ACTION_SEND)
+ Intent(Intent.ACTION_SEND)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.setType(mimeType)
.putExtra(Intent.EXTRA_STREAM, getShareableUri(context, uri))
- safeStartActivityChooser(title, intent)
} else {
var mimeType = "*/*"
if (mimeTypes.size == 1) {
@@ -311,14 +311,21 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
}
}
- val intent = Intent(Intent.ACTION_SEND_MULTIPLE)
+ Intent(Intent.ACTION_SEND_MULTIPLE)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList)
.setType(mimeType)
- safeStartActivityChooser(title, intent)
}
-
- result.success(started)
+ try {
+ val started = safeStartActivityChooser(title, intent)
+ result.success(started)
+ } catch (e: Exception) {
+ if (e is TransactionTooLargeException || e.cause is TransactionTooLargeException) {
+ result.error("share-large", "transaction too large with ${uriList.size} URIs", e)
+ } else {
+ result.error("share-exception", "failed to share ${uriList.size} URIs", e)
+ }
+ }
}
private fun safeStartActivity(intent: Intent): Boolean {
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt
index 62f13accd..715b155b5 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt
@@ -11,25 +11,21 @@ import com.bumptech.glide.load.resource.bitmap.TransformationUtils
import com.drew.metadata.xmp.XmpDirectory
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
-import deckers.thibault.aves.metadata.Metadata
-import deckers.thibault.aves.metadata.MultiPage
+import deckers.thibault.aves.metadata.*
+import deckers.thibault.aves.metadata.XMP.doesPropExist
import deckers.thibault.aves.metadata.XMP.getSafeStructField
-import deckers.thibault.aves.metadata.XMPPropName
import deckers.thibault.aves.metadata.metadataextractor.Helper
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.model.provider.ContentImageProvider
import deckers.thibault.aves.model.provider.ImageProvider
-import deckers.thibault.aves.utils.BitmapUtils
+import deckers.thibault.aves.utils.*
import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.FileUtils.transferFrom
-import deckers.thibault.aves.utils.LogUtils
-import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.MimeTypes.canReadWithExifInterface
import deckers.thibault.aves.utils.MimeTypes.canReadWithMetadataExtractor
import deckers.thibault.aves.utils.MimeTypes.extensionFor
import deckers.thibault.aves.utils.MimeTypes.isImage
import deckers.thibault.aves.utils.MimeTypes.isVideo
-import deckers.thibault.aves.utils.StorageUtils
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
@@ -46,6 +42,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getExifThumbnails" -> ioScope.launch { safeSuspend(call, result, ::getExifThumbnails) }
+ "extractGoogleDeviceItem" -> ioScope.launch { safe(call, result, ::extractGoogleDeviceItem) }
"extractMotionPhotoImage" -> ioScope.launch { safe(call, result, ::extractMotionPhotoImage) }
"extractMotionPhotoVideo" -> ioScope.launch { safe(call, result, ::extractMotionPhotoVideo) }
"extractVideoEmbeddedPicture" -> ioScope.launch { safe(call, result, ::extractVideoEmbeddedPicture) }
@@ -84,6 +81,68 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
result.success(thumbnails)
}
+ private fun extractGoogleDeviceItem(call: MethodCall, result: MethodChannel.Result) {
+ val mimeType = call.argument("mimeType")
+ val uri = call.argument("uri")?.let { Uri.parse(it) }
+ val sizeBytes = call.argument("sizeBytes")?.toLong()
+ val displayName = call.argument("displayName")
+ val dataUri = call.argument("dataUri")
+ if (mimeType == null || uri == null || sizeBytes == null || dataUri == null) {
+ result.error("extractGoogleDeviceItem-args", "missing arguments", null)
+ return
+ }
+
+ var container: GoogleDeviceContainer? = null
+
+ if (canReadWithMetadataExtractor(mimeType)) {
+ try {
+ Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
+ val metadata = Helper.safeRead(input)
+ // data can be large and stored in "Extended XMP",
+ // which is returned as a second XMP directory
+ val xmpDirs = metadata.getDirectoriesOfType(XmpDirectory::class.java)
+ try {
+ container = xmpDirs.firstNotNullOfOrNull {
+ val xmpMeta = it.xmpMeta
+ if (xmpMeta.doesPropExist(XMP.GDEVICE_DIRECTORY_PROP_NAME)) {
+ GoogleDeviceContainer().apply { findItems(xmpMeta) }
+ } else {
+ null
+ }
+ }
+ } catch (e: XMPException) {
+ result.error("extractGoogleDeviceItem-xmp", "failed to read XMP directory for uri=$uri dataUri=$dataUri", e.message)
+ return
+ }
+ }
+ } catch (e: Exception) {
+ Log.w(LOG_TAG, "failed to extract file from XMP", e)
+ } catch (e: NoClassDefFoundError) {
+ Log.w(LOG_TAG, "failed to extract file from XMP", e)
+ } catch (e: AssertionError) {
+ Log.w(LOG_TAG, "failed to extract file from XMP", e)
+ }
+ }
+
+ container?.let {
+ it.findOffsets(context, uri, mimeType, sizeBytes)
+
+ val index = it.itemIndex(dataUri)
+ val itemStartOffset = it.itemStartOffset(index)
+ val itemLength = it.itemLength(index)
+ val itemMimeType = it.itemMimeType(index)
+ if (itemStartOffset != null && itemLength != null && itemMimeType != null) {
+ StorageUtils.openInputStream(context, uri)?.let { input ->
+ input.skip(itemStartOffset)
+ copyEmbeddedBytes(result, itemMimeType, displayName, input, itemLength)
+ return
+ }
+ }
+ }
+
+ result.error("extractGoogleDeviceItem-empty", "failed to extract item from Google Device XMP at uri=$uri dataUri=$dataUri", null)
+ }
+
private fun extractMotionPhotoImage(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument("mimeType")
val uri = call.argument("uri")?.let { Uri.parse(it) }
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaSessionHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaSessionHandler.kt
index f2713b79c..e3373bcce 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaSessionHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaSessionHandler.kt
@@ -1,16 +1,17 @@
package deckers.thibault.aves.channel.calls
-import android.content.Context
+import android.content.*
+import android.media.AudioManager
import android.media.session.PlaybackState
import android.net.Uri
import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
-import android.util.Log
+import androidx.media.session.MediaButtonReceiver
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
+import deckers.thibault.aves.channel.streams.MediaCommandStreamHandler
import deckers.thibault.aves.utils.FlutterUtils
-import deckers.thibault.aves.utils.LogUtils
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
@@ -19,20 +20,36 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
-class MediaSessionHandler(private val context: Context) : MethodCallHandler {
+class MediaSessionHandler(private val context: Context, private val mediaCommandHandler: MediaCommandStreamHandler) : MethodCallHandler {
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
- private val sessions = HashMap()
+ private var session: MediaSessionCompat? = null
+ private var wasPlaying = false
+ private var isNoisyAudioReceiverRegistered = false
+ private val noisyAudioReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action == AudioManager.ACTION_AUDIO_BECOMING_NOISY) {
+ mediaCommandHandler.onStop()
+ }
+ }
+ }
+
+ fun dispose() {
+ if (isNoisyAudioReceiverRegistered) {
+ context.unregisterReceiver(noisyAudioReceiver)
+ isNoisyAudioReceiverRegistered = false
+ }
+ }
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
- "update" -> ioScope.launch { safeSuspend(call, result, ::update) }
- "release" -> ioScope.launch { safe(call, result, ::release) }
+ "update" -> ioScope.launch { safeSuspend(call, result, ::updateSession) }
+ "release" -> ioScope.launch { safe(call, result, ::releaseSession) }
else -> result.notImplemented()
}
}
- private suspend fun update(call: MethodCall, result: MethodChannel.Result) {
+ private suspend fun updateSession(call: MethodCall, result: MethodChannel.Result) {
val uri = call.argument("uri")?.let { Uri.parse(it) }
val title = call.argument("title")
val durationMillis = call.argument("durationMillis")?.toLong()
@@ -72,69 +89,51 @@ class MediaSessionHandler(private val context: Context) : MethodCallHandler {
.setActions(actions)
.build()
- var session = sessions[uri]
- if (session == null) {
- session = MediaSessionCompat(context, "aves-$uri")
- sessions[uri] = session
-
- val metadata = MediaMetadataCompat.Builder()
- .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
- .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, title)
- .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, durationMillis)
- .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI, uri.toString())
- .build()
- session.setMetadata(metadata)
-
- val callback: MediaSessionCompat.Callback = object : MediaSessionCompat.Callback() {
- override fun onPlay() {
- super.onPlay()
- Log.d(LOG_TAG, "TLAD onPlay uri=$uri")
- }
-
- override fun onPause() {
- super.onPause()
- Log.d(LOG_TAG, "TLAD onPause uri=$uri")
- }
-
- override fun onStop() {
- super.onStop()
- Log.d(LOG_TAG, "TLAD onStop uri=$uri")
- }
-
- override fun onSeekTo(pos: Long) {
- super.onSeekTo(pos)
- Log.d(LOG_TAG, "TLAD onSeekTo uri=$uri pos=$pos")
+ FlutterUtils.runOnUiThread {
+ if (session == null) {
+ val mbrIntent = MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_PLAY_PAUSE)
+ val mbrName = ComponentName(context, MediaButtonReceiver::class.java)
+ session = MediaSessionCompat(context, "aves", mbrName, mbrIntent).apply {
+ setCallback(mediaCommandHandler)
}
}
- FlutterUtils.runOnUiThread {
- session.setCallback(callback)
+ session!!.apply {
+ val metadata = MediaMetadataCompat.Builder()
+ .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
+ .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, title)
+ .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, durationMillis)
+ .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI, uri.toString())
+ .build()
+ setMetadata(metadata)
+ setPlaybackState(playbackState)
+ if (!isActive) {
+ isActive = true
+ }
}
- }
- session.setPlaybackState(playbackState)
-
- if (!session.isActive) {
- session.isActive = true
+ val isPlaying = state == PlaybackStateCompat.STATE_PLAYING
+ if (!wasPlaying && isPlaying) {
+ context.registerReceiver(noisyAudioReceiver, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY))
+ isNoisyAudioReceiverRegistered = true
+ } else if (wasPlaying && !isPlaying) {
+ context.unregisterReceiver(noisyAudioReceiver)
+ isNoisyAudioReceiverRegistered = false
+ }
+ wasPlaying = isPlaying
}
result.success(null)
}
- private fun release(call: MethodCall, result: MethodChannel.Result) {
- val uri = call.argument("uri")?.let { Uri.parse(it) }
-
- if (uri == null) {
- result.error("release-args", "missing arguments", null)
- return
+ private fun releaseSession(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
+ session?.let {
+ it.release()
+ session = null
}
-
- sessions[uri]?.release()
-
result.success(null)
}
companion object {
- private val LOG_TAG = LogUtils.createTag()
const val CHANNEL = "deckers.thibault/aves/media_session"
const val STATE_STOPPED = "stopped"
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataEditHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataEditHandler.kt
index df2c1c86e..bb349855a 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataEditHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataEditHandler.kt
@@ -3,6 +3,7 @@ package deckers.thibault.aves.channel.calls
import android.content.ContextWrapper
import android.net.Uri
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
+import deckers.thibault.aves.metadata.Mp4TooLargeException
import deckers.thibault.aves.model.ExifOrientationOp
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.model.provider.ImageProvider.ImageOpCallback
@@ -66,10 +67,8 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
return
}
- provider.editOrientation(contextWrapper, path, uri, mimeType, op, object : ImageOpCallback {
- override fun onSuccess(fields: FieldMap) = result.success(fields)
- override fun onFailure(throwable: Throwable) = result.error("editOrientation-failure", "failed to change orientation for mimeType=$mimeType uri=$uri", throwable)
- })
+ val callback = MetadataOpCallback("editOrientation", entryMap, result)
+ provider.editOrientation(contextWrapper, path, uri, mimeType, op, callback)
}
private fun editDate(call: MethodCall, result: MethodChannel.Result) {
@@ -96,10 +95,8 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
return
}
- provider.editDate(contextWrapper, path, uri, mimeType, dateMillis, shiftMinutes, fields, object : ImageOpCallback {
- override fun onSuccess(fields: FieldMap) = result.success(fields)
- override fun onFailure(throwable: Throwable) = result.error("editDate-failure", "failed to edit date for mimeType=$mimeType uri=$uri", throwable)
- })
+ val callback = MetadataOpCallback("editDate", entryMap, result)
+ provider.editDate(contextWrapper, path, uri, mimeType, dateMillis, shiftMinutes, fields, callback)
}
private fun editMetadata(call: MethodCall, result: MethodChannel.Result) {
@@ -125,10 +122,8 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
return
}
- provider.editMetadata(contextWrapper, path, uri, mimeType, metadata, autoCorrectTrailerOffset, callback = object : ImageOpCallback {
- override fun onSuccess(fields: FieldMap) = result.success(fields)
- override fun onFailure(throwable: Throwable) = result.error("editMetadata-failure", "failed to edit metadata for mimeType=$mimeType uri=$uri", throwable)
- })
+ val callback = MetadataOpCallback("editMetadata", entryMap, result)
+ provider.editMetadata(contextWrapper, path, uri, mimeType, metadata, autoCorrectTrailerOffset, callback)
}
private fun removeTrailerVideo(call: MethodCall, result: MethodChannel.Result) {
@@ -152,10 +147,8 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
return
}
- provider.removeTrailerVideo(contextWrapper, path, uri, mimeType, object : ImageOpCallback {
- override fun onSuccess(fields: FieldMap) = result.success(fields)
- override fun onFailure(throwable: Throwable) = result.error("removeTrailerVideo-failure", "failed to remove trailer video for mimeType=$mimeType uri=$uri", throwable)
- })
+ val callback = MetadataOpCallback("removeTrailerVideo", entryMap, result)
+ provider.removeTrailerVideo(contextWrapper, path, uri, mimeType, callback)
}
private fun removeTypes(call: MethodCall, result: MethodChannel.Result) {
@@ -180,13 +173,31 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
return
}
- provider.removeMetadataTypes(contextWrapper, path, uri, mimeType, types.toSet(), object : ImageOpCallback {
- override fun onSuccess(fields: FieldMap) = result.success(fields)
- override fun onFailure(throwable: Throwable) = result.error("removeTypes-failure", "failed to remove metadata for mimeType=$mimeType uri=$uri", throwable)
- })
+ val callback = MetadataOpCallback("removeTypes", entryMap, result)
+ provider.removeMetadataTypes(contextWrapper, path, uri, mimeType, types.toSet(), callback)
}
companion object {
const val CHANNEL = "deckers.thibault/aves/metadata_edit"
}
-}
\ No newline at end of file
+}
+
+private class MetadataOpCallback(
+ private val errorCodeBase: String,
+ private val entryMap: FieldMap,
+ private val result: MethodChannel.Result,
+) : ImageOpCallback {
+ override fun onSuccess(fields: FieldMap) = result.success(fields)
+ override fun onFailure(throwable: Throwable) {
+ val errorCode = if (throwable is Mp4TooLargeException) {
+ if (throwable.type == "moov") {
+ "$errorCodeBase-mp4largemoov"
+ } else {
+ "$errorCodeBase-mp4largeother"
+ }
+ } else {
+ "$errorCodeBase-failure"
+ }
+ result.error(errorCode, "failed for entry=$entryMap", throwable)
+ }
+}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt
index c23c7d85a..291de3940 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt
@@ -134,7 +134,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (prop is XMPPropertyInfo) {
val path = prop.path
if (path?.isNotEmpty() == true) {
- val value = if (XMP.isDataPath(path)) "[skipped]" else prop.value
+ val value = if (XMP.isDataPath(path)) VALUE_SKIPPED_DATA else prop.value
if (value?.isNotEmpty() == true) {
dirMap[path] = value
}
@@ -615,7 +615,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
if (!metadataMap.containsKey(KEY_XMP_TITLE) || !metadataMap.containsKey(KEY_XMP_SUBJECTS)) {
for (dir in metadata.getDirectoriesOfType(IptcDirectory::class.java)) {
if (!metadataMap.containsKey(KEY_XMP_TITLE)) {
- dir.getSafeString(IptcDirectory.TAG_OBJECT_NAME) { metadataMap[KEY_XMP_TITLE] = it }
+ dir.getSafeString(IptcDirectory.TAG_OBJECT_NAME, acceptBlank = false) { metadataMap[KEY_XMP_TITLE] = it }
}
if (!metadataMap.containsKey(KEY_XMP_SUBJECTS)) {
dir.keywords?.let { metadataMap[KEY_XMP_SUBJECTS] = it.joinToString(XMP_SUBJECTS_SEPARATOR) }
@@ -1151,6 +1151,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
// return description from these fields (by precedence):
// - XMP / dc:description
// - IPTC / caption-abstract
+ // - Exif / UserComment
// - Exif / ImageDescription
private fun getDescription(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument("mimeType")
@@ -1171,7 +1172,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
val xmpMeta = dir.xmpMeta
try {
if (xmpMeta.doesPropExist(XMP.DC_DESCRIPTION_PROP_NAME)) {
- xmpMeta.getSafeLocalizedText(XMP.DC_DESCRIPTION_PROP_NAME) { description = it }
+ xmpMeta.getSafeLocalizedText(XMP.DC_DESCRIPTION_PROP_NAME, acceptBlank = false) { description = it }
}
} catch (e: XMPException) {
Log.w(LOG_TAG, "failed to read XMP directory for uri=$uri", e)
@@ -1179,12 +1180,23 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
}
if (description == null) {
for (dir in metadata.getDirectoriesOfType(IptcDirectory::class.java)) {
- dir.getSafeString(IptcDirectory.TAG_CAPTION) { description = it }
+ dir.getSafeString(IptcDirectory.TAG_CAPTION, acceptBlank = false) { description = it }
+ }
+ }
+ if (description == null) {
+ for (dir in metadata.getDirectoriesOfType(ExifSubIFDDirectory::class.java)) {
+ // user comment field specifies encoding, unlike other string fields
+ if (dir.containsTag(ExifSubIFDDirectory.TAG_USER_COMMENT)) {
+ val string = dir.getDescription(ExifSubIFDDirectory.TAG_USER_COMMENT)
+ if (string.isNotBlank()) {
+ description = string
+ }
+ }
}
}
if (description == null) {
for (dir in metadata.getDirectoriesOfType(ExifIFD0Directory::class.java)) {
- dir.getSafeString(ExifIFD0Directory.TAG_IMAGE_DESCRIPTION) { description = it }
+ dir.getSafeString(ExifIFD0Directory.TAG_IMAGE_DESCRIPTION, acceptBlank = false) { description = it }
}
}
}
@@ -1269,5 +1281,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
// additional media key
private const val KEY_HAS_EMBEDDED_PICTURE = "Has Embedded Picture"
+
+ private const val VALUE_SKIPPED_DATA = "[skipped]"
}
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ActivityWindowHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ActivityWindowHandler.kt
index 789f57f41..258682b00 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ActivityWindowHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ActivityWindowHandler.kt
@@ -3,6 +3,7 @@ package deckers.thibault.aves.channel.calls.window
import android.app.Activity
import android.os.Build
import android.view.WindowManager
+import deckers.thibault.aves.utils.getDisplayCompat
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
@@ -42,25 +43,30 @@ class ActivityWindowHandler(private val activity: Activity) : WindowHandler(acti
result.success(true)
}
- override fun canSetCutoutMode(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
+ override fun isCutoutAware(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
}
- override fun setCutoutMode(call: MethodCall, result: MethodChannel.Result) {
- val use = call.argument("use")
- if (use == null) {
- result.error("setCutoutMode-args", "missing arguments", null)
+ override fun getCutoutInsets(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+ result.error("getCutoutInsets-sdk", "unsupported SDK version=${Build.VERSION.SDK_INT}", null)
return
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- val mode = if (use) {
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
- } else {
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
- }
- activity.window.attributes.layoutInDisplayCutoutMode = mode
+ val cutout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ activity.getDisplayCompat()?.cutout
+ } else {
+ activity.window.decorView.rootWindowInsets.displayCutout
}
- result.success(true)
+
+ val density = activity.resources.displayMetrics.density
+ result.success(
+ hashMapOf(
+ "left" to (cutout?.safeInsetLeft ?: 0) / density,
+ "top" to (cutout?.safeInsetTop ?: 0) / density,
+ "right" to (cutout?.safeInsetRight ?: 0) / density,
+ "bottom" to (cutout?.safeInsetBottom ?: 0) / density,
+ )
+ )
}
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ServiceWindowHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ServiceWindowHandler.kt
index 55794ade4..46d1e43b8 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ServiceWindowHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ServiceWindowHandler.kt
@@ -17,11 +17,11 @@ class ServiceWindowHandler(service: Service) : WindowHandler(service) {
result.success(false)
}
- override fun canSetCutoutMode(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
+ override fun isCutoutAware(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
result.success(false)
}
- override fun setCutoutMode(call: MethodCall, result: MethodChannel.Result) {
- result.success(false)
+ override fun getCutoutInsets(call: MethodCall, result: MethodChannel.Result) {
+ result.success(HashMap())
}
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/WindowHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/WindowHandler.kt
index 184d2398d..0a6f41249 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/WindowHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/WindowHandler.kt
@@ -15,8 +15,8 @@ abstract class WindowHandler(private val contextWrapper: ContextWrapper) : Metho
"keepScreenOn" -> Coresult.safe(call, result, ::keepScreenOn)
"isRotationLocked" -> Coresult.safe(call, result, ::isRotationLocked)
"requestOrientation" -> Coresult.safe(call, result, ::requestOrientation)
- "canSetCutoutMode" -> Coresult.safe(call, result, ::canSetCutoutMode)
- "setCutoutMode" -> Coresult.safe(call, result, ::setCutoutMode)
+ "isCutoutAware" -> Coresult.safe(call, result, ::isCutoutAware)
+ "getCutoutInsets" -> Coresult.safe(call, result, ::getCutoutInsets)
else -> result.notImplemented()
}
}
@@ -37,9 +37,9 @@ abstract class WindowHandler(private val contextWrapper: ContextWrapper) : Metho
abstract fun requestOrientation(call: MethodCall, result: MethodChannel.Result)
- abstract fun canSetCutoutMode(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result)
+ abstract fun isCutoutAware(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result)
- abstract fun setCutoutMode(call: MethodCall, result: MethodChannel.Result)
+ abstract fun getCutoutInsets(call: MethodCall, result: MethodChannel.Result)
companion object {
private val LOG_TAG = LogUtils.createTag()
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ActivityResultStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ActivityResultStreamHandler.kt
index cd30bf1a7..c2e9d364d 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ActivityResultStreamHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ActivityResultStreamHandler.kt
@@ -199,7 +199,9 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
activity.startActivityForResult(intent, MainActivity.PICK_COLLECTION_FILTERS_REQUEST)
}
- override fun onCancel(arguments: Any?) {}
+ override fun onCancel(arguments: Any?) {
+ Log.i(LOG_TAG, "onCancel arguments=$arguments")
+ }
private fun success(result: Any?) {
handler.post {
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/AnalysisStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/AnalysisStreamHandler.kt
index 2e199a30d..3cbaad3e2 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/AnalysisStreamHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/AnalysisStreamHandler.kt
@@ -1,5 +1,7 @@
package deckers.thibault.aves.channel.streams
+import android.util.Log
+import deckers.thibault.aves.utils.LogUtils
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.EventChannel.EventSink
@@ -13,13 +15,16 @@ class AnalysisStreamHandler : EventChannel.StreamHandler {
this.eventSink = eventSink
}
- override fun onCancel(arguments: Any?) {}
+ override fun onCancel(arguments: Any?) {
+ Log.i(LOG_TAG, "onCancel arguments=$arguments")
+ }
fun notifyCompletion() {
eventSink?.success(true)
}
companion object {
+ private val LOG_TAG = LogUtils.createTag()
const val CHANNEL = "deckers.thibault/aves/analysis_events"
}
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ErrorStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ErrorStreamHandler.kt
index 1896fd0ef..a71e16b88 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ErrorStreamHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ErrorStreamHandler.kt
@@ -1,6 +1,8 @@
package deckers.thibault.aves.channel.streams
+import android.util.Log
import deckers.thibault.aves.utils.FlutterUtils
+import deckers.thibault.aves.utils.LogUtils
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.EventChannel.EventSink
@@ -14,7 +16,9 @@ class ErrorStreamHandler : EventChannel.StreamHandler {
this.eventSink = eventSink
}
- override fun onCancel(arguments: Any?) {}
+ override fun onCancel(arguments: Any?) {
+ Log.i(LOG_TAG, "onCancel arguments=$arguments")
+ }
suspend fun notifyError(error: String) {
FlutterUtils.runOnUiThread {
@@ -23,6 +27,7 @@ class ErrorStreamHandler : EventChannel.StreamHandler {
}
companion object {
+ private val LOG_TAG = LogUtils.createTag()
const val CHANNEL = "deckers.thibault/aves/error"
}
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/IntentStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/IntentStreamHandler.kt
index e1734338c..a5626196b 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/IntentStreamHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/IntentStreamHandler.kt
@@ -1,5 +1,7 @@
package deckers.thibault.aves.channel.streams
+import android.util.Log
+import deckers.thibault.aves.utils.LogUtils
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.EventChannel.EventSink
@@ -13,13 +15,16 @@ class IntentStreamHandler : EventChannel.StreamHandler {
this.eventSink = eventSink
}
- override fun onCancel(arguments: Any?) {}
+ override fun onCancel(arguments: Any?) {
+ Log.i(LOG_TAG, "onCancel arguments=$arguments")
+ }
fun notifyNewIntent(intentData: MutableMap?) {
eventSink?.success(intentData)
}
companion object {
+ private val LOG_TAG = LogUtils.createTag()
const val CHANNEL = "deckers.thibault/aves/new_intent_stream"
}
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaCommandStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaCommandStreamHandler.kt
new file mode 100644
index 000000000..b81815622
--- /dev/null
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaCommandStreamHandler.kt
@@ -0,0 +1,76 @@
+package deckers.thibault.aves.channel.streams
+
+import android.os.Handler
+import android.os.Looper
+import android.support.v4.media.session.MediaSessionCompat
+import android.util.Log
+import deckers.thibault.aves.model.FieldMap
+import deckers.thibault.aves.utils.LogUtils
+import io.flutter.plugin.common.EventChannel
+import io.flutter.plugin.common.EventChannel.EventSink
+
+class MediaCommandStreamHandler : EventChannel.StreamHandler, MediaSessionCompat.Callback() {
+ // cannot use `lateinit` because we cannot guarantee
+ // its initialization in `onListen` at the right time
+ private var eventSink: EventSink? = null
+ private var handler: Handler? = null
+
+ override fun onListen(arguments: Any?, eventSink: EventSink) {
+ this.eventSink = eventSink
+ handler = Handler(Looper.getMainLooper())
+ }
+
+ override fun onCancel(arguments: Any?) {
+ Log.i(LOG_TAG, "onCancel arguments=$arguments")
+ }
+
+ private fun success(fields: FieldMap) {
+ handler?.post {
+ try {
+ eventSink?.success(fields)
+ } catch (e: Exception) {
+ Log.w(LOG_TAG, "failed to use event sink", e)
+ }
+ }
+ }
+
+ // media session callback
+
+ override fun onPlay() {
+ super.onPlay()
+ success(hashMapOf(KEY_COMMAND to COMMAND_PLAY))
+ }
+
+ override fun onPause() {
+ super.onPause()
+ success(hashMapOf(KEY_COMMAND to COMMAND_PAUSE))
+ }
+
+ override fun onStop() {
+ super.onStop()
+ success(hashMapOf(KEY_COMMAND to COMMAND_STOP))
+ }
+
+ override fun onSeekTo(pos: Long) {
+ super.onSeekTo(pos)
+ success(
+ hashMapOf(
+ KEY_COMMAND to COMMAND_SEEK,
+ KEY_POSITION to pos,
+ )
+ )
+ }
+
+ companion object {
+ private val LOG_TAG = LogUtils.createTag()
+ const val CHANNEL = "deckers.thibault/aves/media_command"
+
+ const val KEY_COMMAND = "command"
+ const val KEY_POSITION = "position"
+
+ const val COMMAND_PLAY = "play"
+ const val COMMAND_PAUSE = "pause"
+ const val COMMAND_STOP = "stop"
+ const val COMMAND_SEEK = "seek"
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreChangeStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreChangeStreamHandler.kt
index 7fdcddffc..05ede13b6 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreChangeStreamHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreChangeStreamHandler.kt
@@ -41,7 +41,9 @@ class MediaStoreChangeStreamHandler(private val context: Context) : EventChannel
handler = Handler(Looper.getMainLooper())
}
- override fun onCancel(arguments: Any?) {}
+ override fun onCancel(arguments: Any?) {
+ Log.i(LOG_TAG, "onCancel arguments=$arguments")
+ }
fun dispose() {
context.contentResolver.unregisterContentObserver(contentObserver)
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/SettingsChangeStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/SettingsChangeStreamHandler.kt
index c61c18ad7..77a753bfc 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/SettingsChangeStreamHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/SettingsChangeStreamHandler.kt
@@ -79,7 +79,9 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
handler = Handler(Looper.getMainLooper())
}
- override fun onCancel(arguments: Any?) {}
+ override fun onCancel(arguments: Any?) {
+ Log.i(LOG_TAG, "onCancel arguments=$arguments")
+ }
fun dispose() {
context.contentResolver.unregisterContentObserver(contentObserver)
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/GoogleDeviceContainer.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/GoogleDeviceContainer.kt
new file mode 100644
index 000000000..590b1f65d
--- /dev/null
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/GoogleDeviceContainer.kt
@@ -0,0 +1,83 @@
+package deckers.thibault.aves.metadata
+
+import android.content.Context
+import android.net.Uri
+import com.adobe.internal.xmp.XMPMeta
+import deckers.thibault.aves.metadata.XMP.countPropArrayItems
+import deckers.thibault.aves.metadata.XMP.getSafeStructField
+import deckers.thibault.aves.utils.indexOfBytes
+import java.io.DataInputStream
+
+class GoogleDeviceContainer {
+ private val jfifSignature = byteArrayOf(0xFF.toByte(), 0xD8.toByte(), 0xFF.toByte(), 0xE0.toByte(), 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01)
+
+ private val items: MutableList = ArrayList()
+ private val offsets: MutableList = ArrayList()
+
+ fun findItems(xmpMeta: XMPMeta) {
+ val count = xmpMeta.countPropArrayItems(XMP.GDEVICE_DIRECTORY_PROP_NAME)
+ for (i in 1 until count + 1) {
+ val mimeType = xmpMeta.getSafeStructField(listOf(XMP.GDEVICE_DIRECTORY_PROP_NAME, i, XMP.GDEVICE_CONTAINER_ITEM_MIME_PROP_NAME))?.value
+ val length = xmpMeta.getSafeStructField(listOf(XMP.GDEVICE_DIRECTORY_PROP_NAME, i, XMP.GDEVICE_CONTAINER_ITEM_LENGTH_PROP_NAME))?.value?.toLongOrNull()
+ val dataUri = xmpMeta.getSafeStructField(listOf(XMP.GDEVICE_DIRECTORY_PROP_NAME, i, XMP.GDEVICE_CONTAINER_ITEM_DATA_URI_PROP_NAME))?.value
+ if (mimeType != null && length != null && dataUri != null) {
+ items.add(
+ GoogleDeviceContainerItem(
+ mimeType = mimeType,
+ length = length,
+ dataUri = dataUri,
+ )
+ )
+ } else throw Exception("failed to extract Google device container item at index=$i with mimeType=$mimeType, length=$length, dataUri=$dataUri")
+ }
+ }
+
+ fun findOffsets(context: Context, uri: Uri, mimeType: String, sizeBytes: Long) {
+ Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
+ val bytes = ByteArray(sizeBytes.toInt())
+ DataInputStream(input).use {
+ it.readFully(bytes)
+ }
+
+ var start = 0
+ while (start < sizeBytes) {
+ val offset = bytes.indexOfBytes(jfifSignature, start)
+ if (offset != -1 && offset >= start) {
+ start = offset + jfifSignature.size
+ offsets.add(offset)
+ } else {
+ start = sizeBytes.toInt()
+ }
+ }
+ }
+
+ // fix first offset as it may refer to included thumbnail instead of primary image
+ while (offsets.size < items.size) {
+ offsets.add(0, 0)
+ }
+ offsets[0] = 0
+ }
+
+ fun itemIndex(dataUri: String) = items.indexOfFirst { it.dataUri == dataUri }
+
+ private fun item(index: Int): GoogleDeviceContainerItem? {
+ return if (0 <= index && index < items.size) {
+ items[index]
+ } else null
+ }
+
+ fun itemStartOffset(index: Int): Long? {
+ return if (0 <= index && index < offsets.size) {
+ offsets[index].toLong()
+ } else null
+ }
+
+ fun itemLength(index: Int): Long? {
+ val lengthByMeta = item(index)?.length ?: return null
+ return if (lengthByMeta != 0L) lengthByMeta else itemStartOffset(index + 1)
+ }
+
+ fun itemMimeType(index: Int) = item(index)?.mimeType
+}
+
+class GoogleDeviceContainerItem(val mimeType: String, val length: Long, val dataUri: String) {}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Mp4ParserHelper.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Mp4ParserHelper.kt
index 5f86a11a6..8edb548df 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Mp4ParserHelper.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Mp4ParserHelper.kt
@@ -33,7 +33,7 @@ object Mp4ParserHelper {
)
setBoxSkipper { type, size ->
if (skippedTypes.contains(type)) return@setBoxSkipper true
- if (size > BOX_SIZE_DANGER_THRESHOLD) throw Exception("box (type=$type size=$size) is too large")
+ if (size > BOX_SIZE_DANGER_THRESHOLD) throw Mp4TooLargeException(type, "box (type=$type size=$size) is too large")
false
}
}
@@ -232,3 +232,5 @@ object Mp4ParserHelper {
return stream.toByteArray()
}
}
+
+class Mp4TooLargeException(val type: String, message: String) : RuntimeException(message)
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt
index 711d62cad..1693c4bab 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt
@@ -175,14 +175,14 @@ object MultiPage {
if (xmpMeta.doesPropExist(XMP.GCAMERA_VIDEO_OFFSET_PROP_NAME)) {
// `GCamera` motion photo
xmpMeta.getSafeLong(XMP.GCAMERA_VIDEO_OFFSET_PROP_NAME) { offsetFromEnd = it }
- } else if (xmpMeta.doesPropExist(XMP.CONTAINER_DIRECTORY_PROP_NAME)) {
+ } else if (xmpMeta.doesPropExist(XMP.GCONTAINER_DIRECTORY_PROP_NAME)) {
// `Container` motion photo
- val count = xmpMeta.countPropArrayItems(XMP.CONTAINER_DIRECTORY_PROP_NAME)
+ val count = xmpMeta.countPropArrayItems(XMP.GCONTAINER_DIRECTORY_PROP_NAME)
if (count == 2) {
// expect the video to be the second item
val i = 2
- val mime = xmpMeta.getSafeStructField(listOf(XMP.CONTAINER_DIRECTORY_PROP_NAME, i, XMP.CONTAINER_ITEM_PROP_NAME, XMP.CONTAINER_ITEM_MIME_PROP_NAME))?.value
- val length = xmpMeta.getSafeStructField(listOf(XMP.CONTAINER_DIRECTORY_PROP_NAME, i, XMP.CONTAINER_ITEM_PROP_NAME, XMP.CONTAINER_ITEM_LENGTH_PROP_NAME))?.value
+ val mime = xmpMeta.getSafeStructField(listOf(XMP.GCONTAINER_DIRECTORY_PROP_NAME, i, XMP.GCONTAINER_ITEM_PROP_NAME, XMP.GCONTAINER_ITEM_MIME_PROP_NAME))?.value
+ val length = xmpMeta.getSafeStructField(listOf(XMP.GCONTAINER_DIRECTORY_PROP_NAME, i, XMP.GCONTAINER_ITEM_PROP_NAME, XMP.GCONTAINER_ITEM_LENGTH_PROP_NAME))?.value
if (MimeTypes.isVideo(mime) && length != null) {
offsetFromEnd = length.toLong()
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/SphericalVideo.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/SphericalVideo.kt
index e2a1c45e4..783e4b2a1 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/SphericalVideo.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/SphericalVideo.kt
@@ -42,17 +42,17 @@ class GSpherical(xmlBytes: ByteArray) {
"StitchingSoftware" -> stitchingSoftware = readTag(parser, tag)
"ProjectionType" -> projectionType = readTag(parser, tag)
"StereoMode" -> stereoMode = readTag(parser, tag)
- "SourceCount" -> sourceCount = Integer.parseInt(readTag(parser, tag))
- "InitialViewHeadingDegrees" -> initialViewHeadingDegrees = Integer.parseInt(readTag(parser, tag))
- "InitialViewPitchDegrees" -> initialViewPitchDegrees = Integer.parseInt(readTag(parser, tag))
- "InitialViewRollDegrees" -> initialViewRollDegrees = Integer.parseInt(readTag(parser, tag))
- "Timestamp" -> timestamp = Integer.parseInt(readTag(parser, tag))
- "FullPanoWidthPixels" -> fullPanoWidthPixels = Integer.parseInt(readTag(parser, tag))
- "FullPanoHeightPixels" -> fullPanoHeightPixels = Integer.parseInt(readTag(parser, tag))
- "CroppedAreaImageWidthPixels" -> croppedAreaImageWidthPixels = Integer.parseInt(readTag(parser, tag))
- "CroppedAreaImageHeightPixels" -> croppedAreaImageHeightPixels = Integer.parseInt(readTag(parser, tag))
- "CroppedAreaLeftPixels" -> croppedAreaLeftPixels = Integer.parseInt(readTag(parser, tag))
- "CroppedAreaTopPixels" -> croppedAreaTopPixels = Integer.parseInt(readTag(parser, tag))
+ "SourceCount" -> sourceCount = readTag(parser, tag).toInt()
+ "InitialViewHeadingDegrees" -> initialViewHeadingDegrees = readTag(parser, tag).toInt()
+ "InitialViewPitchDegrees" -> initialViewPitchDegrees = readTag(parser, tag).toInt()
+ "InitialViewRollDegrees" -> initialViewRollDegrees = readTag(parser, tag).toInt()
+ "Timestamp" -> timestamp = readTag(parser, tag).toInt()
+ "FullPanoWidthPixels" -> fullPanoWidthPixels = readTag(parser, tag).toInt()
+ "FullPanoHeightPixels" -> fullPanoHeightPixels = readTag(parser, tag).toInt()
+ "CroppedAreaImageWidthPixels" -> croppedAreaImageWidthPixels = readTag(parser, tag).toInt()
+ "CroppedAreaImageHeightPixels" -> croppedAreaImageHeightPixels = readTag(parser, tag).toInt()
+ "CroppedAreaLeftPixels" -> croppedAreaLeftPixels = readTag(parser, tag).toInt()
+ "CroppedAreaTopPixels" -> croppedAreaTopPixels = readTag(parser, tag).toInt()
}
}
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt
index a1f0055a7..737c862da 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt
@@ -43,11 +43,13 @@ object XMP {
private const val XMP_NS_URI = "http://ns.adobe.com/xap/1.0/"
// other namespaces
- private const val CONTAINER_NS_URI = "http://ns.google.com/photos/1.0/container/"
- private const val CONTAINER_ITEM_NS_URI = "http://ns.google.com/photos/1.0/container/item/"
private const val GAUDIO_NS_URI = "http://ns.google.com/photos/1.0/audio/"
private const val GCAMERA_NS_URI = "http://ns.google.com/photos/1.0/camera/"
+ private const val GCONTAINER_NS_URI = "http://ns.google.com/photos/1.0/container/"
+ private const val GCONTAINER_ITEM_NS_URI = "http://ns.google.com/photos/1.0/container/item/"
private const val GDEPTH_NS_URI = "http://ns.google.com/photos/1.0/depthmap/"
+ private const val GDEVICE_NS_URI = "http://ns.google.com/photos/dd/1.0/device/"
+ private const val GDEVICE_ITEM_NS_URI = "http://ns.google.com/photos/dd/1.0/item/"
private const val GIMAGE_NS_URI = "http://ns.google.com/photos/1.0/image/"
private const val GPANO_NS_URI = "http://ns.google.com/photos/1.0/panorama/"
private const val PMTM_NS_URI = "http://www.hdrsoft.com/photomatix_settings01"
@@ -75,13 +77,20 @@ object XMP {
fun isDataPath(path: String) = knownDataProps.map { it.toString() }.any { path == it }
+ // google portrait
+
+ val GDEVICE_DIRECTORY_PROP_NAME = XMPPropName(GDEVICE_NS_URI, "Container/Container:Directory")
+ val GDEVICE_CONTAINER_ITEM_DATA_URI_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "DataURI")
+ val GDEVICE_CONTAINER_ITEM_LENGTH_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "Length")
+ val GDEVICE_CONTAINER_ITEM_MIME_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "Mime")
+
// motion photo
val GCAMERA_VIDEO_OFFSET_PROP_NAME = XMPPropName(GCAMERA_NS_URI, "MicroVideoOffset")
- val CONTAINER_DIRECTORY_PROP_NAME = XMPPropName(CONTAINER_NS_URI, "Directory")
- val CONTAINER_ITEM_PROP_NAME = XMPPropName(CONTAINER_NS_URI, "Item")
- val CONTAINER_ITEM_LENGTH_PROP_NAME = XMPPropName(CONTAINER_ITEM_NS_URI, "Length")
- val CONTAINER_ITEM_MIME_PROP_NAME = XMPPropName(CONTAINER_ITEM_NS_URI, "Mime")
+ val GCONTAINER_DIRECTORY_PROP_NAME = XMPPropName(GCONTAINER_NS_URI, "Directory")
+ val GCONTAINER_ITEM_PROP_NAME = XMPPropName(GCONTAINER_NS_URI, "Item")
+ val GCONTAINER_ITEM_LENGTH_PROP_NAME = XMPPropName(GCONTAINER_ITEM_NS_URI, "Length")
+ val GCONTAINER_ITEM_MIME_PROP_NAME = XMPPropName(GCONTAINER_ITEM_NS_URI, "Mime")
// panorama
// cf https://developers.google.com/streetview/spherical-metadata
@@ -189,14 +198,14 @@ object XMP {
if (doesPropExist(GCAMERA_VIDEO_OFFSET_PROP_NAME)) return true
// Container motion photo
- if (doesPropExist(CONTAINER_DIRECTORY_PROP_NAME)) {
- val count = countPropArrayItems(CONTAINER_DIRECTORY_PROP_NAME)
+ if (doesPropExist(GCONTAINER_DIRECTORY_PROP_NAME)) {
+ val count = countPropArrayItems(GCONTAINER_DIRECTORY_PROP_NAME)
if (count == 2) {
var hasImage = false
var hasVideo = false
for (i in 1 until count + 1) {
- val mime = getSafeStructField(listOf(CONTAINER_DIRECTORY_PROP_NAME, i, CONTAINER_ITEM_PROP_NAME, CONTAINER_ITEM_MIME_PROP_NAME))?.value
- val length = getSafeStructField(listOf(CONTAINER_DIRECTORY_PROP_NAME, i, CONTAINER_ITEM_PROP_NAME, CONTAINER_ITEM_LENGTH_PROP_NAME))?.value
+ val mime = getSafeStructField(listOf(GCONTAINER_DIRECTORY_PROP_NAME, i, GCONTAINER_ITEM_PROP_NAME, GCONTAINER_ITEM_MIME_PROP_NAME))?.value
+ val length = getSafeStructField(listOf(GCONTAINER_DIRECTORY_PROP_NAME, i, GCONTAINER_ITEM_PROP_NAME, GCONTAINER_ITEM_LENGTH_PROP_NAME))?.value
hasImage = hasImage || MimeTypes.isImage(mime) && length != null
hasVideo = hasVideo || MimeTypes.isVideo(mime) && length != null
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/Helper.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/Helper.kt
index 8f6672ce9..ed247f4cf 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/Helper.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/Helper.kt
@@ -117,8 +117,13 @@ object Helper {
// extensions
- fun Directory.getSafeString(tag: Int, save: (value: String) -> Unit) {
- if (this.containsTag(tag)) save(this.getString(tag))
+ fun Directory.getSafeString(tag: Int, acceptBlank: Boolean = true, save: (value: String) -> Unit) {
+ if (this.containsTag(tag)) {
+ val string = this.getString(tag)
+ if (acceptBlank || string.isNotBlank()) {
+ save(string)
+ }
+ }
}
fun Directory.getSafeBoolean(tag: Int, save: (value: Boolean) -> Unit) {
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt
index 6d5bb2315..7ff773d21 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt
@@ -55,7 +55,7 @@ internal class ContentImageProvider : ImageProvider() {
if (cursor != null && cursor.moveToFirst()) {
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME).let { if (it != -1) fields["title"] = cursor.getString(it) }
cursor.getColumnIndex(OpenableColumns.SIZE).let { if (it != -1) fields["sizeBytes"] = cursor.getLong(it) }
- cursor.getColumnIndex(PATH).let { if (it != -1) fields["path"] = cursor.getString(it) }
+ cursor.getColumnIndex(MediaStore.MediaColumns.DATA).let { if (it != -1) fields["path"] = cursor.getString(it) }
cursor.close()
}
} catch (e: Exception) {
@@ -73,8 +73,5 @@ internal class ContentImageProvider : ImageProvider() {
companion object {
private val LOG_TAG = LogUtils.createTag()
-
- @Suppress("deprecation")
- const val PATH = MediaStore.MediaColumns.DATA
}
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt
index dd06a2e0c..9e2e9c9eb 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt
@@ -761,8 +761,8 @@ abstract class ImageProvider {
"${XMP.GCAMERA_VIDEO_OFFSET_PROP_NAME}=\"$newTrailerOffset\"",
).replace(
// Container motion photo
- "${XMP.CONTAINER_ITEM_LENGTH_PROP_NAME}=\"$trailerOffset\"",
- "${XMP.CONTAINER_ITEM_LENGTH_PROP_NAME}=\"$newTrailerOffset\"",
+ "${XMP.GCONTAINER_ITEM_LENGTH_PROP_NAME}=\"$trailerOffset\"",
+ "${XMP.GCONTAINER_ITEM_LENGTH_PROP_NAME}=\"$newTrailerOffset\"",
)
})
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt
index 1e92c2e86..3b29bcf55 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt
@@ -55,10 +55,10 @@ class MediaStoreImageProvider : ImageProvider() {
val relativePathDirectory = ensureTrailingSeparator(directory)
val relativePath = PathSegments(context, relativePathDirectory).relativeDir
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && relativePath != null) {
- selection = "${MediaStore.MediaColumns.RELATIVE_PATH} = ? AND ${MediaColumns.PATH} LIKE ?"
+ selection = "${MediaStore.MediaColumns.RELATIVE_PATH} = ? AND ${MediaStore.MediaColumns.DATA} LIKE ?"
selectionArgs = arrayOf(relativePath, "$relativePathDirectory%")
} else {
- selection = "${MediaColumns.PATH} LIKE ?"
+ selection = "${MediaStore.MediaColumns.DATA} LIKE ?"
selectionArgs = arrayOf("$relativePathDirectory%")
}
@@ -139,12 +139,12 @@ class MediaStoreImageProvider : ImageProvider() {
fun checkObsoletePaths(context: Context, knownPathById: Map): List {
val obsoleteIds = ArrayList()
fun check(context: Context, contentUri: Uri) {
- val projection = arrayOf(MediaStore.MediaColumns._ID, MediaColumns.PATH)
+ val projection = arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA)
try {
val cursor = context.contentResolver.query(contentUri, projection, null, null, null)
if (cursor != null) {
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
- val pathColumn = cursor.getColumnIndexOrThrow(MediaColumns.PATH)
+ val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
while (cursor.moveToNext()) {
val id = cursor.getInt(idColumn)
val path = cursor.getString(pathColumn)
@@ -185,7 +185,7 @@ class MediaStoreImageProvider : ImageProvider() {
// image & video
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
- val pathColumn = cursor.getColumnIndexOrThrow(MediaColumns.PATH)
+ val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
val mimeTypeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE)
val widthColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.WIDTH)
@@ -863,7 +863,7 @@ class MediaStoreImageProvider : ImageProvider() {
fun getContentUriForPath(context: Context, path: String): Uri? {
val projection = arrayOf(MediaStore.MediaColumns._ID)
- val selection = "${MediaColumns.PATH} = ?"
+ val selection = "${MediaStore.MediaColumns.DATA} = ?"
val selectionArgs = arrayOf(path)
fun check(context: Context, contentUri: Uri): Uri? {
@@ -892,7 +892,7 @@ class MediaStoreImageProvider : ImageProvider() {
private val BASE_PROJECTION = arrayOf(
MediaStore.MediaColumns._ID,
- MediaColumns.PATH,
+ MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.MIME_TYPE,
MediaStore.MediaColumns.SIZE,
MediaStore.MediaColumns.WIDTH,
@@ -931,9 +931,6 @@ object MediaColumns {
@SuppressLint("InlinedApi")
const val DURATION = MediaStore.MediaColumns.DURATION
-
- @Suppress("deprecation")
- const val PATH = MediaStore.MediaColumns.DATA
}
typealias NewEntryHandler = (entry: FieldMap) -> Unit
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/CollectionUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/CollectionUtils.kt
index 9fc81e5c7..55ed6ce6c 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/CollectionUtils.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/CollectionUtils.kt
@@ -20,7 +20,7 @@ fun MutableList.compatRemoveIf(filter: (t: E) -> Boolean): Boolean {
}
// Boyer-Moore algorithm for pattern searching
-fun ByteArray.indexOfBytes(pattern: ByteArray): Int {
+fun ByteArray.indexOfBytes(pattern: ByteArray, start: Int = 0): Int {
val n: Int = this.size
val m: Int = pattern.size
val badChar = Array(256) { 0 }
@@ -30,7 +30,7 @@ fun ByteArray.indexOfBytes(pattern: ByteArray): Int {
i += 1
}
var j: Int = m - 1
- var s = 0
+ var s = start
while (s <= (n - m)) {
while (j >= 0 && pattern[j] == this[s + j]) {
j -= 1
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/Compat.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/Compat.kt
index 9be2fbe67..4aa362a1b 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/Compat.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/Compat.kt
@@ -1,11 +1,13 @@
package deckers.thibault.aves.utils
+import android.app.Activity
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.os.Build
import android.os.Parcelable
+import android.view.Display
inline fun Intent.getParcelableExtraCompat(name: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
@@ -16,6 +18,14 @@ inline fun Intent.getParcelableExtraCompat(name: String): T? {
}
}
+fun Activity.getDisplayCompat(): Display? {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ display
+ } else {
+ @Suppress("deprecation")
+ windowManager.defaultDisplay
+ }
+}
fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int): ApplicationInfo {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt
index af0e471c2..df4ea5f75 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt
@@ -90,7 +90,8 @@ object MimeTypes {
// as of `metadata-extractor` v2.14.0
fun canReadWithMetadataExtractor(mimeType: String) = when (mimeType) {
- DJVU, WBMP, MKV, MP2T, MP2TS, OGV, WEBM -> false
+ DJVU, WBMP -> false
+ MKV, MP2T, MP2TS, OGV, WEBM -> false
else -> true
}
diff --git a/android/app/src/main/res/values-cs/strings.xml b/android/app/src/main/res/values-cs/strings.xml
new file mode 100644
index 000000000..171ff70fa
--- /dev/null
+++ b/android/app/src/main/res/values-cs/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Aves
+ Tapeta
+ Hledat
+ Videa
+ Prohledat média
+ Prohledat obrázky a videa
+ Prohledávání médií
+ Zastavit
+ Fotorámeček
+
\ No newline at end of file
diff --git a/android/app/src/main/res/xml/app_widget_info.xml b/android/app/src/main/res/xml/app_widget_info.xml
index af3ae7c12..eb1d68c0d 100644
--- a/android/app/src/main/res/xml/app_widget_info.xml
+++ b/android/app/src/main/res/xml/app_widget_info.xml
@@ -1,5 +1,6 @@
\ No newline at end of file
+ android:widgetFeatures="reconfigurable"
+ tools:targetApi="s" />
\ No newline at end of file
diff --git a/fastlane/metadata/android/cs/full_description.txt b/fastlane/metadata/android/cs/full_description.txt
new file mode 100644
index 000000000..2ca7137cc
--- /dev/null
+++ b/fastlane/metadata/android/cs/full_description.txt
@@ -0,0 +1,5 @@
+Aves může pracovat se všemi typy obrázků a videí, jako jsou běžné formáty JPEG a MP4, ale také více exotické jako vícestránkový TIFF, SVG, starý AVI a mnohem více ! Prohledává vaši sbírku médií kvůli rozpoznání pohyblivých fotografií , panoramatických snímků (čili fotosféry), 360° videí , nebo souborů GeoTIFF .
+
+Navigace a vyhledávání jsou důležitou součástí aplikace Aves . Cílem je, aby uživatelé jednoduše přecházeli z alb k fotografiím, albům, mapám, atd.
+
+Aves podporuje Android (od verze KitKat po Android 13, včetně Android TV) s funkcemi jako jsou widgety , zkratky aplikací , spořič displeje a globální vyhledávání . Rovněž jej lze použít pro prohlížení a výběr médií .
\ No newline at end of file
diff --git a/fastlane/metadata/android/cs/images/featureGraphic.png b/fastlane/metadata/android/cs/images/featureGraphic.png
new file mode 100644
index 000000000..a0b3a3e77
Binary files /dev/null and b/fastlane/metadata/android/cs/images/featureGraphic.png differ
diff --git a/fastlane/metadata/android/cs/images/phoneScreenshots/1.png b/fastlane/metadata/android/cs/images/phoneScreenshots/1.png
new file mode 100644
index 000000000..ea8be8f13
Binary files /dev/null and b/fastlane/metadata/android/cs/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/cs/images/phoneScreenshots/2.png b/fastlane/metadata/android/cs/images/phoneScreenshots/2.png
new file mode 100644
index 000000000..f684259cf
Binary files /dev/null and b/fastlane/metadata/android/cs/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/cs/images/phoneScreenshots/3.png b/fastlane/metadata/android/cs/images/phoneScreenshots/3.png
new file mode 100644
index 000000000..7940af98c
Binary files /dev/null and b/fastlane/metadata/android/cs/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/cs/images/phoneScreenshots/4.png b/fastlane/metadata/android/cs/images/phoneScreenshots/4.png
new file mode 100644
index 000000000..3ea278277
Binary files /dev/null and b/fastlane/metadata/android/cs/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/cs/images/phoneScreenshots/5.png b/fastlane/metadata/android/cs/images/phoneScreenshots/5.png
new file mode 100644
index 000000000..6fbd3a037
Binary files /dev/null and b/fastlane/metadata/android/cs/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/cs/images/phoneScreenshots/6.png b/fastlane/metadata/android/cs/images/phoneScreenshots/6.png
new file mode 100644
index 000000000..e63823d65
Binary files /dev/null and b/fastlane/metadata/android/cs/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/cs/images/phoneScreenshots/7.png b/fastlane/metadata/android/cs/images/phoneScreenshots/7.png
new file mode 100644
index 000000000..0c8d9c0e7
Binary files /dev/null and b/fastlane/metadata/android/cs/images/phoneScreenshots/7.png differ
diff --git a/fastlane/metadata/android/cs/short_description.txt b/fastlane/metadata/android/cs/short_description.txt
new file mode 100644
index 000000000..48cbd5539
--- /dev/null
+++ b/fastlane/metadata/android/cs/short_description.txt
@@ -0,0 +1 @@
+Galerie a prohlížeč metadat
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1064.txt b/fastlane/metadata/android/en-US/changelogs/1064.txt
deleted file mode 100644
index 3ce411869..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1064.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.5.10:
-- show, search and edit ratings
-- add many items to favourites at once
-- enjoy the app in Spanish
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1065.txt b/fastlane/metadata/android/en-US/changelogs/1065.txt
deleted file mode 100644
index c75dc2346..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1065.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.5.11:
-- edit locations of images
-- export SVGs to convert and resize them
-- enjoy the app in Portuguese
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1066.txt b/fastlane/metadata/android/en-US/changelogs/1066.txt
deleted file mode 100644
index f1cc67dc3..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1066.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.6.0:
-- recycle bin
-- view small and large images at their actual size
-- enjoy the app in Indonesian
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1067.txt b/fastlane/metadata/android/en-US/changelogs/1067.txt
deleted file mode 100644
index 9a00bb1be..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1067.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.6.1:
-- recycle bin
-- view small and large images at their actual size
-- enjoy the app in Indonesian
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1068.txt b/fastlane/metadata/android/en-US/changelogs/1068.txt
deleted file mode 100644
index decd4dce4..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1068.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.6.2:
-- revisited viewer: new layout, thumbnail previews, video gestures
-- storage related fixes for Android 10 and older
-- enjoy the app in Japanese
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1069.txt b/fastlane/metadata/android/en-US/changelogs/1069.txt
deleted file mode 100644
index 93bf3bf8b..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1069.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.6.3:
-- enjoy the light theme
-- rename items in bulk
-Full changelog available on GitHub
diff --git a/fastlane/metadata/android/en-US/changelogs/1070.txt b/fastlane/metadata/android/en-US/changelogs/1070.txt
deleted file mode 100644
index 64a893886..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1070.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.6.4:
-- customize album cover app & color
-- explore improved GeoTIFF metadata
-- enjoy the app in Italian & Chinese (Simplified)
-Full changelog available on GitHub
diff --git a/fastlane/metadata/android/en-US/changelogs/1071.txt b/fastlane/metadata/android/en-US/changelogs/1071.txt
deleted file mode 100644
index 38d163888..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1071.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.6.5:
-- bottom navigation bar
-- fast scroll with breadcrumbs
-- settings search
-Full changelog available on GitHub
diff --git a/fastlane/metadata/android/en-US/changelogs/1072.txt b/fastlane/metadata/android/en-US/changelogs/1072.txt
deleted file mode 100644
index b8aa57b1e..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1072.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.6.6:
-- bottom navigation bar
-- fast scroll with breadcrumbs
-- settings search
-Full changelog available on GitHub
diff --git a/fastlane/metadata/android/en-US/changelogs/1073.txt b/fastlane/metadata/android/en-US/changelogs/1073.txt
deleted file mode 100644
index 75aa6e973..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1073.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.6.7:
-- bottom navigation bar
-- fast scroll with breadcrumbs
-- settings search
-Full changelog available on GitHub
diff --git a/fastlane/metadata/android/en-US/changelogs/1074.txt b/fastlane/metadata/android/en-US/changelogs/1074.txt
deleted file mode 100644
index 8b9f35460..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1074.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.6.8:
-- bottom navigation bar
-- fast scroll with breadcrumbs
-- settings search
-Full changelog available on GitHub
diff --git a/fastlane/metadata/android/en-US/changelogs/1075.txt b/fastlane/metadata/android/en-US/changelogs/1075.txt
deleted file mode 100644
index c852da8ed..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1075.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.6.9:
-- start slideshows
-- change your wallpaper
-- enjoy the app in Turkish
-Full changelog available on GitHub
diff --git a/fastlane/metadata/android/en-US/changelogs/1076.txt b/fastlane/metadata/android/en-US/changelogs/1076.txt
deleted file mode 100644
index cce1f4f7d..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1076.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.6.10:
-- add the photo frame widget to your home
-- use your photos as screen saver
-- search photos taken "on this day"
-Full changelog available on GitHub
diff --git a/fastlane/metadata/android/en-US/changelogs/1077.txt b/fastlane/metadata/android/en-US/changelogs/1077.txt
deleted file mode 100644
index 546d1be97..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1077.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.6.11:
-- add the photo frame widget to your home
-- use your photos as screen saver
-- search photos taken "on this day"
-Full changelog available on GitHub
diff --git a/fastlane/metadata/android/en-US/changelogs/1078.txt b/fastlane/metadata/android/en-US/changelogs/1078.txt
deleted file mode 100644
index 756c775df..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1078.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.6.12:
-- play your HEIC motion photos
-- find recently downloaded images with the `recently added` filter
-- enjoy the app in Dutch
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1079.txt b/fastlane/metadata/android/en-US/changelogs/1079.txt
deleted file mode 100644
index 30000cd00..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1079.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.6.13:
-- play your HEIC motion photos
-- find recently downloaded images with the `recently added` filter
-- enjoy the app in Dutch
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1080.txt b/fastlane/metadata/android/en-US/changelogs/1080.txt
deleted file mode 100644
index af4200674..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1080.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.7.0:
-- change the sort order
-- edit image titles
-- enjoy the app in Greek
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1081.txt b/fastlane/metadata/android/en-US/changelogs/1081.txt
deleted file mode 100644
index e5b6ac839..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1081.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.7.1:
-- view your photos with the mosaic layout
-- reverse filters to filter out/in
-- set wallpapers with scroll effect
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1082.txt b/fastlane/metadata/android/en-US/changelogs/1082.txt
deleted file mode 100644
index 5905035c9..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1082.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.7.2:
-- tag your MP4, rate your MP4, date your MP4, locate your MP4, rotate your MP4
-- give media management access (on Android 12+) to skip some confirmation dialogs
-- enjoy higher quality thumbnails
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1083.txt b/fastlane/metadata/android/en-US/changelogs/1083.txt
deleted file mode 100644
index b9b547a16..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1083.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.7.3:
-- tag your MP4, rate your MP4, date your MP4, locate your MP4, rotate your MP4
-- give media management access (on Android 12+) to skip some confirmation dialogs
-- enjoy higher quality thumbnails
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1084.txt b/fastlane/metadata/android/en-US/changelogs/1084.txt
deleted file mode 100644
index b434e87d9..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1084.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.7.4:
-- tag your MP4, rate your MP4, date your MP4, locate your MP4, rotate your MP4
-- give media management access (on Android 12+) to skip some confirmation dialogs
-- enjoy higher quality thumbnails
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1085.txt b/fastlane/metadata/android/en-US/changelogs/1085.txt
deleted file mode 100644
index 3ae743b04..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1085.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.7.5:
-- use viewer quick actions to rate, tag, locate
-- set a default editor
-- export metadata to a text file
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1086.txt b/fastlane/metadata/android/en-US/changelogs/1086.txt
deleted file mode 100644
index 55493c9f4..000000000
--- a/fastlane/metadata/android/en-US/changelogs/1086.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-In v1.7.6:
-- use viewer quick actions to rate, tag, locate
-- set a default editor
-- export metadata to a text file
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/89.txt b/fastlane/metadata/android/en-US/changelogs/89.txt
new file mode 100644
index 000000000..ec10c2286
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/89.txt
@@ -0,0 +1,5 @@
+In v1.7.9:
+- Android TV support (cont'd)
+- interact with videos via media session controls
+- enjoy the app in Czech & Polish
+Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/8901.txt b/fastlane/metadata/android/en-US/changelogs/8901.txt
new file mode 100644
index 000000000..ec10c2286
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/8901.txt
@@ -0,0 +1,5 @@
+In v1.7.9:
+- Android TV support (cont'd)
+- interact with videos via media session controls
+- enjoy the app in Czech & Polish
+Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/id/full_description.txt b/fastlane/metadata/android/id/full_description.txt
index 8eda6d0ba..3c18c062f 100644
--- a/fastlane/metadata/android/id/full_description.txt
+++ b/fastlane/metadata/android/id/full_description.txt
@@ -2,4 +2,4 @@
Navigasi dan pencarian merupakan bagian penting dari Aves . Tujuannya adalah agar pengguna dengan mudah mengalir dari album ke foto ke tag ke peta, dll.
-Aves terintegrasi dengan Android (dari API 19 ke 33 , yaitu dari KitKat ke Android 13) dengan fitur-fitur seperti pintasan aplikasi dan pencarian global penanganan. Ini juga berfungsi sebagai penampil dan pemilih media .
\ No newline at end of file
+Aves mengintegrasi dengan Android (dari Kitkat ke Android 13) dengan fitur-fitur seperti pintasan aplikasi , jalan pintas aplikasi , screen saver dan pencarian global penanganan. Ini juga berfungsi sebagai penampil dan pemilih media .
\ No newline at end of file
diff --git a/fastlane/metadata/android/nn/full_description.txt b/fastlane/metadata/android/nn/full_description.txt
index 6c92748f8..ed514a8df 100644
--- a/fastlane/metadata/android/nn/full_description.txt
+++ b/fastlane/metadata/android/nn/full_description.txt
@@ -1,5 +1,5 @@
-Aves can handle all sorts of images and videos, including your typical JPEGs and MP4s, but also more exotic things like multi-page TIFFs, SVGs, old AVIs and more ! It scans your media collection to identify motion photos , panoramas (aka photo spheres), 360° videos , as well as GeoTIFF files.
+Aves kan handsama alle slags bileter og videoar, medteken JPEG og MP4, men au meir uvane ting som fleirsida TIFF-ar, SVG-ar, gamle AVI-ar med meir ! Aves ser igjennom mediasamlinga di for å gjenkjenne rørslebilete , panorama (bilete med vidt oversyn), 360° videoar , og au GeoTIFF -filer.
-Navigation and search is an important part of Aves . The goal is for users to easily flow from albums to photos to tags to maps, etc.
+Navigering og søk har mykje å sei i Aves . Målet er at ein skal lett kunne gå ifrå album, til bilete, til merkelappar, til kart, osv.
Aves integrates with Android (from KitKat to Android 13, including Android TV) with features such as widgets , app shortcuts , screen saver and global search handling. It also works as a media viewer and picker .
\ No newline at end of file
diff --git a/fastlane/metadata/android/pl/images/featureGraphic.png b/fastlane/metadata/android/pl/images/featureGraphic.png
new file mode 100644
index 000000000..ad29dfe39
Binary files /dev/null and b/fastlane/metadata/android/pl/images/featureGraphic.png differ
diff --git a/fastlane/metadata/android/pl/images/phoneScreenshots/1.png b/fastlane/metadata/android/pl/images/phoneScreenshots/1.png
new file mode 100644
index 000000000..01e38dc7f
Binary files /dev/null and b/fastlane/metadata/android/pl/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/pl/images/phoneScreenshots/2.png b/fastlane/metadata/android/pl/images/phoneScreenshots/2.png
new file mode 100644
index 000000000..a1665b69e
Binary files /dev/null and b/fastlane/metadata/android/pl/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/pl/images/phoneScreenshots/3.png b/fastlane/metadata/android/pl/images/phoneScreenshots/3.png
new file mode 100644
index 000000000..b3e2d334c
Binary files /dev/null and b/fastlane/metadata/android/pl/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/pl/images/phoneScreenshots/4.png b/fastlane/metadata/android/pl/images/phoneScreenshots/4.png
new file mode 100644
index 000000000..d66d85bbf
Binary files /dev/null and b/fastlane/metadata/android/pl/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/pl/images/phoneScreenshots/5.png b/fastlane/metadata/android/pl/images/phoneScreenshots/5.png
new file mode 100644
index 000000000..d3f82ec46
Binary files /dev/null and b/fastlane/metadata/android/pl/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/pl/images/phoneScreenshots/6.png b/fastlane/metadata/android/pl/images/phoneScreenshots/6.png
new file mode 100644
index 000000000..cf3ff0987
Binary files /dev/null and b/fastlane/metadata/android/pl/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/pl/images/phoneScreenshots/7.png b/fastlane/metadata/android/pl/images/phoneScreenshots/7.png
new file mode 100644
index 000000000..ed1aa3e5c
Binary files /dev/null and b/fastlane/metadata/android/pl/images/phoneScreenshots/7.png differ
diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb
index 1453cb0df..557e12b6a 100644
--- a/lib/l10n/app_ar.arb
+++ b/lib/l10n/app_ar.arb
@@ -16,5 +16,57 @@
"filePickerShowHiddenFiles": "إظهار الملفات المخفية",
"@filePickerShowHiddenFiles": {},
"panoramaEnableSensorControl": "تمكين التحكم في المستشعر",
- "@panoramaEnableSensorControl": {}
+ "@panoramaEnableSensorControl": {},
+ "saveTooltip": "حفظ",
+ "@saveTooltip": {},
+ "continueButtonLabel": "إستمرار",
+ "@continueButtonLabel": {},
+ "resetTooltip": "إعادة",
+ "@resetTooltip": {},
+ "doNotAskAgain": "عدم السؤال مرة أخرى",
+ "@doNotAskAgain": {},
+ "welcomeTermsToggle": "أوافق على الشروط",
+ "@welcomeTermsToggle": {},
+ "doubleBackExitMessage": "اضغط على \"رجوع\" مرة أخرى للخروج.",
+ "@doubleBackExitMessage": {},
+ "hideButtonLabel": "إخفاء",
+ "@hideButtonLabel": {},
+ "showTooltip": "إظهار",
+ "@showTooltip": {},
+ "clearTooltip": "تنظيف",
+ "@clearTooltip": {},
+ "changeTooltip": "تغيير",
+ "@changeTooltip": {},
+ "actionRemove": "إزالة",
+ "@actionRemove": {},
+ "appName": "Aves",
+ "@appName": {},
+ "welcomeOptional": "اختياري",
+ "@welcomeOptional": {},
+ "deleteButtonLabel": "حذف",
+ "@deleteButtonLabel": {},
+ "nextTooltip": "التالي",
+ "@nextTooltip": {},
+ "cancelTooltip": "إلغاء",
+ "@cancelTooltip": {},
+ "previousTooltip": "السابق",
+ "@previousTooltip": {},
+ "welcomeMessage": "مرحبا بكم في Aves",
+ "@welcomeMessage": {},
+ "applyButtonLabel": "تطبيق",
+ "@applyButtonLabel": {},
+ "nextButtonLabel": "التالي",
+ "@nextButtonLabel": {},
+ "showButtonLabel": "إظهار",
+ "@showButtonLabel": {},
+ "tagEditorSectionRecent": "الأخيرة",
+ "@tagEditorSectionRecent": {},
+ "tagEditorSectionPlaceholders": "العناصر النائبة",
+ "@tagEditorSectionPlaceholders": {},
+ "filePickerUseThisFolder": "إستخدام هذا المجلد",
+ "@filePickerUseThisFolder": {},
+ "hideTooltip": "إخفاء",
+ "@hideTooltip": {},
+ "tagEditorPageAddTagTooltip": "إضافة علامة",
+ "@tagEditorPageAddTagTooltip": {}
}
diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb
new file mode 100644
index 000000000..b487cf3b0
--- /dev/null
+++ b/lib/l10n/app_cs.arb
@@ -0,0 +1,1358 @@
+{
+ "welcomeOptional": "Volitelné",
+ "@welcomeOptional": {},
+ "welcomeTermsToggle": "Souhlasím s podmínkami použivání",
+ "@welcomeTermsToggle": {},
+ "appName": "Aves",
+ "@appName": {},
+ "welcomeMessage": "Vítá tě Aves",
+ "@welcomeMessage": {},
+ "applyButtonLabel": "POUŽÍT",
+ "@applyButtonLabel": {},
+ "deleteButtonLabel": "SMAZAT",
+ "@deleteButtonLabel": {},
+ "nextButtonLabel": "DALŠÍ",
+ "@nextButtonLabel": {},
+ "showButtonLabel": "ZOBRAZIT",
+ "@showButtonLabel": {},
+ "continueButtonLabel": "POKRAČOVAT",
+ "@continueButtonLabel": {},
+ "cancelTooltip": "Zrušit",
+ "@cancelTooltip": {},
+ "changeTooltip": "Upravit",
+ "@changeTooltip": {},
+ "clearTooltip": "Vyčistit",
+ "@clearTooltip": {},
+ "previousTooltip": "Předchozí",
+ "@previousTooltip": {},
+ "nextTooltip": "Další",
+ "@nextTooltip": {},
+ "showTooltip": "Zobrazit",
+ "@showTooltip": {},
+ "actionRemove": "Odstranit",
+ "@actionRemove": {},
+ "resetTooltip": "Resetovat",
+ "@resetTooltip": {},
+ "saveTooltip": "Uložit",
+ "@saveTooltip": {},
+ "focalLength": "{length} mm",
+ "@focalLength": {
+ "placeholders": {
+ "length": {
+ "type": "String",
+ "example": "5.4"
+ }
+ }
+ },
+ "timeDays": "{days, plural, =1{1 den} =2..4{{days} dny} other{{days} dnů}}",
+ "@timeDays": {
+ "placeholders": {
+ "days": {}
+ }
+ },
+ "itemCount": "{count, plural, =1{1 položka} =2..4{{count} položky} other{{count} položek}}",
+ "@itemCount": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "columnCount": "{count, plural, =1{1 sloupec} =2..4{{count} sloupce} other{{count} sloupců}}",
+ "@columnCount": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "hideButtonLabel": "SKRÝT",
+ "@hideButtonLabel": {},
+ "hideTooltip": "Skrýt",
+ "@hideTooltip": {},
+ "doNotAskAgain": "Znovu nezobrazovat",
+ "@doNotAskAgain": {},
+ "sourceStateLoading": "Nahrávání",
+ "@sourceStateLoading": {},
+ "sourceStateCataloguing": "Katalogizace",
+ "@sourceStateCataloguing": {},
+ "sourceStateLocatingCountries": "Vyhledání zemí",
+ "@sourceStateLocatingCountries": {},
+ "sourceStateLocatingPlaces": "Vyhledávání míst",
+ "@sourceStateLocatingPlaces": {},
+ "chipActionDelete": "Smazat",
+ "@chipActionDelete": {},
+ "chipActionGoToAlbumPage": "Zobrazit v albech",
+ "@chipActionGoToAlbumPage": {},
+ "chipActionGoToCountryPage": "Zobrazit v zemích",
+ "@chipActionGoToCountryPage": {},
+ "chipActionGoToTagPage": "Zobrazit ve štítcích",
+ "@chipActionGoToTagPage": {},
+ "chipActionFilterOut": "Odfiltrovat",
+ "@chipActionFilterOut": {},
+ "chipActionFilterIn": "Filtrovat",
+ "@chipActionFilterIn": {},
+ "chipActionPin": "Připnout nahoru",
+ "@chipActionPin": {},
+ "chipActionUnpin": "Odepnout seshora",
+ "@chipActionUnpin": {},
+ "chipActionHide": "Skrýt",
+ "@chipActionHide": {},
+ "chipActionRename": "Přejmenovat",
+ "@chipActionRename": {},
+ "chipActionCreateAlbum": "Vytvořit album",
+ "@chipActionCreateAlbum": {},
+ "entryActionCopyToClipboard": "Kopírovat do paměti",
+ "@entryActionCopyToClipboard": {},
+ "entryActionDelete": "Smazat",
+ "@entryActionDelete": {},
+ "entryActionConvert": "Konvertovat",
+ "@entryActionConvert": {},
+ "entryActionExport": "Exportovat",
+ "@entryActionExport": {},
+ "entryActionInfo": "Informace",
+ "@entryActionInfo": {},
+ "entryActionRename": "Přejmenovat",
+ "@entryActionRename": {},
+ "entryActionRestore": "Obnovit",
+ "@entryActionRestore": {},
+ "entryActionRotateCCW": "Otočit doleva",
+ "@entryActionRotateCCW": {},
+ "entryActionRotateCW": "Otočit doprava",
+ "@entryActionRotateCW": {},
+ "entryActionFlip": "Převrátit vodorovně",
+ "@entryActionFlip": {},
+ "entryActionPrint": "Tisknout",
+ "@entryActionPrint": {},
+ "entryActionShare": "Sdílet",
+ "@entryActionShare": {},
+ "entryActionShareImageOnly": "Sdílet pouze obrázek",
+ "@entryActionShareImageOnly": {},
+ "entryActionShareVideoOnly": "Sdílet pouze video",
+ "@entryActionShareVideoOnly": {},
+ "entryActionViewSource": "Zobrazit zdroj",
+ "@entryActionViewSource": {},
+ "entryActionShowGeoTiffOnMap": "Zobrazit jako překryv mapy",
+ "@entryActionShowGeoTiffOnMap": {},
+ "entryActionConvertMotionPhotoToStillImage": "Konvertovat na statický obrázek",
+ "@entryActionConvertMotionPhotoToStillImage": {},
+ "entryActionViewMotionPhotoVideo": "Otevřít video",
+ "@entryActionViewMotionPhotoVideo": {},
+ "entryActionEdit": "Upravit",
+ "@entryActionEdit": {},
+ "entryActionOpen": "Otevřít s",
+ "@entryActionOpen": {},
+ "entryActionSetAs": "Nastavit jako",
+ "@entryActionSetAs": {},
+ "entryActionOpenMap": "Zobrazit na mapě",
+ "@entryActionOpenMap": {},
+ "entryActionRotateScreen": "Otočit obrazovku",
+ "@entryActionRotateScreen": {},
+ "entryActionAddFavourite": "Přidat do oblíbených",
+ "@entryActionAddFavourite": {},
+ "entryActionRemoveFavourite": "Odebrat z oblíbených",
+ "@entryActionRemoveFavourite": {},
+ "videoActionCaptureFrame": "Zachytit rámeček",
+ "@videoActionCaptureFrame": {},
+ "videoActionMute": "Ztlumit",
+ "@videoActionMute": {},
+ "videoActionUnmute": "Zrušit ztlumení",
+ "@videoActionUnmute": {},
+ "chipActionSetCover": "Nastavit přebal",
+ "@chipActionSetCover": {},
+ "videoActionPlay": "Spustit",
+ "@videoActionPlay": {},
+ "videoActionPause": "Pozastavit",
+ "@videoActionPause": {},
+ "videoActionReplay10": "Přetočit zpět o 10 sekund",
+ "@videoActionReplay10": {},
+ "videoActionSkip10": "Posunout vpřed o 10 sekund",
+ "@videoActionSkip10": {},
+ "videoActionSelectStreams": "Vybrat stopy",
+ "@videoActionSelectStreams": {},
+ "videoActionSetSpeed": "Rychlost přehrávání",
+ "@videoActionSetSpeed": {},
+ "videoActionSettings": "Nastavení",
+ "@videoActionSettings": {},
+ "slideshowActionResume": "Pokračovat",
+ "@slideshowActionResume": {},
+ "slideshowActionShowInCollection": "Zobrazit ve sbírce",
+ "@slideshowActionShowInCollection": {},
+ "entryInfoActionEditDate": "Upravit datum a čas",
+ "@entryInfoActionEditDate": {},
+ "entryInfoActionEditLocation": "Upravit polohu",
+ "@entryInfoActionEditLocation": {},
+ "entryInfoActionEditTitleDescription": "Upravit název a popis",
+ "@entryInfoActionEditTitleDescription": {},
+ "entryInfoActionEditRating": "Upravit hodnocení",
+ "@entryInfoActionEditRating": {},
+ "entryInfoActionEditTags": "Upravit štítky",
+ "@entryInfoActionEditTags": {},
+ "entryInfoActionRemoveMetadata": "Odstranit metadata",
+ "@entryInfoActionRemoveMetadata": {},
+ "entryInfoActionExportMetadata": "Exportovat metadata",
+ "@entryInfoActionExportMetadata": {},
+ "entryInfoActionRemoveLocation": "Odstranit polohu",
+ "@entryInfoActionRemoveLocation": {},
+ "filterAspectRatioPortraitLabel": "Na výšku",
+ "@filterAspectRatioPortraitLabel": {},
+ "filterAspectRatioLandscapeLabel": "Na šířku",
+ "@filterAspectRatioLandscapeLabel": {},
+ "filterBinLabel": "Koš",
+ "@filterBinLabel": {},
+ "filterFavouriteLabel": "Oblíbené",
+ "@filterFavouriteLabel": {},
+ "filterNoDateLabel": "Bez data",
+ "@filterNoDateLabel": {},
+ "filterNoAddressLabel": "Bez adresy",
+ "@filterNoAddressLabel": {},
+ "filterNoLocationLabel": "Bez polohy",
+ "@filterNoLocationLabel": {},
+ "filterNoRatingLabel": "Nehodnocený",
+ "@filterNoRatingLabel": {},
+ "filterNoTagLabel": "Bez štítků",
+ "@filterNoTagLabel": {},
+ "filterNoTitleLabel": "Bez názvu",
+ "@filterNoTitleLabel": {},
+ "filterOnThisDayLabel": "V tento den",
+ "@filterOnThisDayLabel": {},
+ "filterRecentlyAddedLabel": "Naposled přidané",
+ "@filterRecentlyAddedLabel": {},
+ "filterRatingRejectedLabel": "Zamítnutý",
+ "@filterRatingRejectedLabel": {},
+ "filterTypeAnimatedLabel": "Animovaný",
+ "@filterTypeAnimatedLabel": {},
+ "filterTypeMotionPhotoLabel": "Pohyblivá fotografie",
+ "@filterTypeMotionPhotoLabel": {},
+ "filterTypePanoramaLabel": "Panoráma",
+ "@filterTypePanoramaLabel": {},
+ "filterTypeRawLabel": "Raw",
+ "@filterTypeRawLabel": {},
+ "filterTypeSphericalVideoLabel": "360° video",
+ "@filterTypeSphericalVideoLabel": {},
+ "filterTypeGeotiffLabel": "GeoTIFF",
+ "@filterTypeGeotiffLabel": {},
+ "filterMimeImageLabel": "Obrázek",
+ "@filterMimeImageLabel": {},
+ "filterMimeVideoLabel": "Video",
+ "@filterMimeVideoLabel": {},
+ "coordinateDmsNorth": "S",
+ "@coordinateDmsNorth": {},
+ "coordinateDmsSouth": "J",
+ "@coordinateDmsSouth": {},
+ "coordinateDmsEast": "V",
+ "@coordinateDmsEast": {},
+ "coordinateDmsWest": "Z",
+ "@coordinateDmsWest": {},
+ "unitSystemMetric": "Metrická soustava",
+ "@unitSystemMetric": {},
+ "unitSystemImperial": "Imperiální jednotky",
+ "@unitSystemImperial": {},
+ "videoLoopModeNever": "Nikdy",
+ "@videoLoopModeNever": {},
+ "videoLoopModeShortOnly": "Pouze krátká videa",
+ "@videoLoopModeShortOnly": {},
+ "videoLoopModeAlways": "Vždy",
+ "@videoLoopModeAlways": {},
+ "videoControlsPlay": "Přehrát",
+ "@videoControlsPlay": {},
+ "videoControlsPlaySeek": "Přehrávat a vyhledávat vzad/vpřed",
+ "@videoControlsPlaySeek": {},
+ "videoControlsPlayOutside": "Otevřít jiným přehrávačem",
+ "@videoControlsPlayOutside": {},
+ "videoControlsNone": "Žádný",
+ "@videoControlsNone": {},
+ "coordinateFormatDms": "Stupně, minuty, vteřiny",
+ "@coordinateFormatDms": {},
+ "coordinateFormatDecimal": "Stupně s desetinnými místy",
+ "@coordinateFormatDecimal": {},
+ "coordinateDms": "{coordinate} {direction}",
+ "@coordinateDms": {
+ "placeholders": {
+ "coordinate": {
+ "type": "String",
+ "example": "38° 41′ 47.72″"
+ },
+ "direction": {
+ "type": "String",
+ "example": "S"
+ }
+ }
+ },
+ "mapStyleGoogleTerrain": "Mapy Google (terén)",
+ "@mapStyleGoogleTerrain": {},
+ "mapStyleHuaweiNormal": "Mapy Petal",
+ "@mapStyleHuaweiNormal": {},
+ "mapStyleHuaweiTerrain": "Mapy Petal (terénní)",
+ "@mapStyleHuaweiTerrain": {},
+ "mapStyleOsmHot": "Humanitární OSM",
+ "@mapStyleOsmHot": {},
+ "mapStyleStamenToner": "Stamen Toner (černobílé)",
+ "@mapStyleStamenToner": {},
+ "mapStyleStamenWatercolor": "Stamen Watercolor (vodové barvy)",
+ "@mapStyleStamenWatercolor": {},
+ "nameConflictStrategyRename": "Přejmenovat",
+ "@nameConflictStrategyRename": {},
+ "nameConflictStrategyReplace": "Nahradit",
+ "@nameConflictStrategyReplace": {},
+ "keepScreenOnVideoPlayback": "Při přehrávání videa",
+ "@keepScreenOnVideoPlayback": {},
+ "keepScreenOnAlways": "Vždy",
+ "@keepScreenOnAlways": {},
+ "accessibilityAnimationsRemove": "Zakázat vizuální efekty",
+ "@accessibilityAnimationsRemove": {},
+ "accessibilityAnimationsKeep": "Povolit vizuální efekty",
+ "@accessibilityAnimationsKeep": {},
+ "displayRefreshRatePreferHighest": "Nejvyšší",
+ "@displayRefreshRatePreferHighest": {},
+ "subtitlePositionTop": "Nahoře",
+ "@subtitlePositionTop": {},
+ "keepScreenOnViewerOnly": "Pouze v zobrazení prohlížeče",
+ "@keepScreenOnViewerOnly": {},
+ "subtitlePositionBottom": "Dole",
+ "@subtitlePositionBottom": {},
+ "videoPlaybackSkip": "Přeskočit",
+ "@videoPlaybackSkip": {},
+ "videoPlaybackMuted": "Přehrát ztlumené",
+ "@videoPlaybackMuted": {},
+ "videoPlaybackWithSound": "Přehrát se zvukem",
+ "@videoPlaybackWithSound": {},
+ "themeBrightnessLight": "Svetlé",
+ "@themeBrightnessLight": {},
+ "themeBrightnessDark": "Tmavé",
+ "@themeBrightnessDark": {},
+ "themeBrightnessBlack": "Černé",
+ "@themeBrightnessBlack": {},
+ "viewerTransitionSlide": "Posun",
+ "@viewerTransitionSlide": {},
+ "viewerTransitionParallax": "Parallax",
+ "@viewerTransitionParallax": {},
+ "viewerTransitionFade": "Zeslábnutí",
+ "@viewerTransitionFade": {},
+ "viewerTransitionZoomIn": "Přiblížení",
+ "@viewerTransitionZoomIn": {},
+ "viewerTransitionNone": "Žádný",
+ "@viewerTransitionNone": {},
+ "wallpaperTargetHome": "Domovská obrazovka",
+ "@wallpaperTargetHome": {},
+ "wallpaperTargetLock": "Zamykací obrazovka",
+ "@wallpaperTargetLock": {},
+ "wallpaperTargetHomeLock": "Domovská i zamykací obrazovka",
+ "@wallpaperTargetHomeLock": {},
+ "widgetDisplayedItemRandom": "Náhodně",
+ "@widgetDisplayedItemRandom": {},
+ "widgetDisplayedItemMostRecent": "Nejnovější",
+ "@widgetDisplayedItemMostRecent": {},
+ "widgetOpenPageHome": "Otevřít domovskou stránku",
+ "@widgetOpenPageHome": {},
+ "widgetOpenPageCollection": "Otevřít sbírku",
+ "@widgetOpenPageCollection": {},
+ "widgetOpenPageViewer": "Otevřít prohlížeč",
+ "@widgetOpenPageViewer": {},
+ "albumTierNew": "Nové",
+ "@albumTierNew": {},
+ "albumTierPinned": "Připnuté",
+ "@albumTierPinned": {},
+ "albumTierSpecial": "Společné",
+ "@albumTierSpecial": {},
+ "albumTierApps": "Aplikace",
+ "@albumTierApps": {},
+ "albumTierRegular": "Ostatní",
+ "@albumTierRegular": {},
+ "storageVolumeDescriptionFallbackPrimary": "Interní úložiště",
+ "@storageVolumeDescriptionFallbackPrimary": {},
+ "storageVolumeDescriptionFallbackNonPrimary": "SD karta",
+ "@storageVolumeDescriptionFallbackNonPrimary": {},
+ "rootDirectoryDescription": "kořenový adresář",
+ "@rootDirectoryDescription": {},
+ "otherDirectoryDescription": "„{name}“ adresář",
+ "@otherDirectoryDescription": {
+ "placeholders": {
+ "name": {
+ "type": "String",
+ "example": "Pictures",
+ "description": "the name of a specific directory"
+ }
+ }
+ },
+ "storageAccessDialogMessage": "Prosím zvolte adresář {directory} v „{volume}“ na další obrazovce, abyste k němu povolili aplikaci přístup.",
+ "@storageAccessDialogMessage": {
+ "placeholders": {
+ "directory": {
+ "type": "String",
+ "description": "the name of a directory, using the output of `rootDirectoryDescription` or `otherDirectoryDescription`"
+ },
+ "volume": {
+ "type": "String",
+ "example": "SD card",
+ "description": "the name of a storage volume"
+ }
+ }
+ },
+ "restrictedAccessDialogMessage": "Tato aplikace nemá povolené úpravy souborů v adresáři {directory} v „{volume}“.\n\nPro přesun položek do jiného adresáře prosím použijte předinstalovaného správce souborů nebo galerii.",
+ "@restrictedAccessDialogMessage": {
+ "placeholders": {
+ "directory": {
+ "type": "String",
+ "description": "the name of a directory, using the output of `rootDirectoryDescription` or `otherDirectoryDescription`"
+ },
+ "volume": {
+ "type": "String",
+ "example": "SD card",
+ "description": "the name of a storage volume"
+ }
+ }
+ },
+ "notEnoughSpaceDialogMessage": "Pro dokončení této operace je vyžadováno {neededSize} volného místa na „{volume}“, ale k dispozici je pouze {freeSize}.",
+ "@notEnoughSpaceDialogMessage": {
+ "placeholders": {
+ "neededSize": {
+ "type": "String",
+ "example": "314 MB"
+ },
+ "freeSize": {
+ "type": "String",
+ "example": "123 MB"
+ },
+ "volume": {
+ "type": "String",
+ "example": "SD card",
+ "description": "the name of a storage volume"
+ }
+ }
+ },
+ "unsupportedTypeDialogMessage": "{count, plural, =1{Tato operace není dostupná pro položky tohoto typu: {types}.} other{Tato operace není dostupná pro položky těchto typů: {types}.}}",
+ "@unsupportedTypeDialogMessage": {
+ "placeholders": {
+ "count": {},
+ "types": {
+ "type": "String",
+ "example": "GIF, TIFF, MP4",
+ "description": "a list of unsupported types"
+ }
+ }
+ },
+ "nameConflictDialogSingleSourceMessage": "Některé soubory v cílovém umístění mají stejný název.",
+ "@nameConflictDialogSingleSourceMessage": {},
+ "nameConflictDialogMultipleSourceMessage": "Některé soubory mají stejný název.",
+ "@nameConflictDialogMultipleSourceMessage": {},
+ "addShortcutButtonLabel": "PŘIDAT",
+ "@addShortcutButtonLabel": {},
+ "noMatchingAppDialogMessage": "Pro tuto operaci není k dispozici žádná aplikace.",
+ "@noMatchingAppDialogMessage": {},
+ "deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Smazat tuto položku?} =2..4{Smazat tyto {count} položky?} other{Smazat těchto {count} položek?}}",
+ "@deleteEntriesConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "moveUndatedConfirmationDialogMessage": "Uložit data položek, než budete pokračovat?",
+ "@moveUndatedConfirmationDialogMessage": {},
+ "missingSystemFilePickerDialogMessage": "Systémový nástroj pro výběr souborů chybí nebo je zakázaný. Prosím povolte jej a zkuste to znovu.",
+ "@missingSystemFilePickerDialogMessage": {},
+ "addShortcutDialogLabel": "Název zástupce",
+ "@addShortcutDialogLabel": {},
+ "moveUndatedConfirmationDialogSetDate": "Uložit tada",
+ "@moveUndatedConfirmationDialogSetDate": {},
+ "videoResumeDialogMessage": "Chcete obnovit přehrávání v čase {time}?",
+ "@videoResumeDialogMessage": {
+ "placeholders": {
+ "time": {
+ "type": "String",
+ "example": "13:37"
+ }
+ }
+ },
+ "videoStartOverButtonLabel": "SPUSTIT ZNOVU",
+ "@videoStartOverButtonLabel": {},
+ "videoResumeButtonLabel": "POKRAČOVAT",
+ "@videoResumeButtonLabel": {},
+ "setCoverDialogLatest": "Poslední položka",
+ "@setCoverDialogLatest": {},
+ "newAlbumDialogNameLabel": "Název alba",
+ "@newAlbumDialogNameLabel": {},
+ "newAlbumDialogNameLabelAlreadyExistsHelper": "Adresář již existuje",
+ "@newAlbumDialogNameLabelAlreadyExistsHelper": {},
+ "newAlbumDialogStorageLabel": "Úložiště:",
+ "@newAlbumDialogStorageLabel": {},
+ "renameAlbumDialogLabel": "Nový název",
+ "@renameAlbumDialogLabel": {},
+ "renameAlbumDialogLabelAlreadyExistsHelper": "Adresář již existuje",
+ "@renameAlbumDialogLabelAlreadyExistsHelper": {},
+ "renameEntrySetPageTitle": "Přejmenovat",
+ "@renameEntrySetPageTitle": {},
+ "renameEntrySetPagePatternFieldLabel": "Vzor názvu",
+ "@renameEntrySetPagePatternFieldLabel": {},
+ "renameEntrySetPageInsertTooltip": "Vložit pole",
+ "@renameEntrySetPageInsertTooltip": {},
+ "renameEntrySetPagePreviewSectionTitle": "Náhled",
+ "@renameEntrySetPagePreviewSectionTitle": {},
+ "renameProcessorName": "Název",
+ "@renameProcessorName": {},
+ "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Smazat toto album a tuto položku?} =2..4{Smazat toto album a tyto {count} položky?} other{Smazat toto album a těchto {count} položek?}}",
+ "@deleteSingleAlbumConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Smazat tato alba a jejich položku?} =2..4{Smazat tato alba a jejich {count} položky?} other{Smazat tato alba a jejich {count} položek?}}",
+ "@deleteMultiAlbumConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "exportEntryDialogFormat": "Formát:",
+ "@exportEntryDialogFormat": {},
+ "exportEntryDialogWidth": "Šířka",
+ "@exportEntryDialogWidth": {},
+ "exportEntryDialogHeight": "Výška",
+ "@exportEntryDialogHeight": {},
+ "renameEntryDialogLabel": "Nový název",
+ "@renameEntryDialogLabel": {},
+ "editEntryDialogCopyFromItem": "Kopírovat z jiné položky",
+ "@editEntryDialogCopyFromItem": {},
+ "editEntryDialogTargetFieldsHeader": "Pole k úpravě",
+ "@editEntryDialogTargetFieldsHeader": {},
+ "editEntryDateDialogCopyField": "Kopírovat z jiného data",
+ "@editEntryDateDialogCopyField": {},
+ "editEntryDateDialogExtractFromTitle": "Odvodit z názvu",
+ "@editEntryDateDialogExtractFromTitle": {},
+ "editEntryDateDialogShift": "Posun",
+ "@editEntryDateDialogShift": {},
+ "editEntryDateDialogSourceFileModifiedDate": "Datum úpravy souboru",
+ "@editEntryDateDialogSourceFileModifiedDate": {},
+ "durationDialogHours": "Hodiny",
+ "@durationDialogHours": {},
+ "durationDialogMinutes": "Minuty",
+ "@durationDialogMinutes": {},
+ "durationDialogSeconds": "Sekundy",
+ "@durationDialogSeconds": {},
+ "renameProcessorCounter": "Počítadlo",
+ "@renameProcessorCounter": {},
+ "editEntryLocationDialogTitle": "Poloha",
+ "@editEntryLocationDialogTitle": {},
+ "editEntryLocationDialogSetCustom": "Nastavit vlastní polohu",
+ "@editEntryLocationDialogSetCustom": {},
+ "editEntryLocationDialogChooseOnMap": "Vybrat na mapě",
+ "@editEntryLocationDialogChooseOnMap": {},
+ "editEntryLocationDialogLatitude": "Zeměpisná šířka",
+ "@editEntryLocationDialogLatitude": {},
+ "editEntryLocationDialogLongitude": "Zeměpisná délka",
+ "@editEntryLocationDialogLongitude": {},
+ "locationPickerUseThisLocationButton": "Použít tuto polohu",
+ "@locationPickerUseThisLocationButton": {},
+ "editEntryRatingDialogTitle": "Hodnocení",
+ "@editEntryRatingDialogTitle": {},
+ "removeEntryMetadataDialogTitle": "Odstranění metadat",
+ "@removeEntryMetadataDialogTitle": {},
+ "removeEntryMetadataDialogMore": "Zobrazit více",
+ "@removeEntryMetadataDialogMore": {},
+ "removeEntryMetadataMotionPhotoXmpWarningDialogMessage": "Pro přehrávání videa v pohyblivé fotografii je vyžadováno XMP.\n\nOpravdu jej chcete odstranit?",
+ "@removeEntryMetadataMotionPhotoXmpWarningDialogMessage": {},
+ "videoSpeedDialogLabel": "Rychlost přehrávání",
+ "@videoSpeedDialogLabel": {},
+ "videoStreamSelectionDialogVideo": "Video",
+ "@videoStreamSelectionDialogVideo": {},
+ "videoStreamSelectionDialogAudio": "Audio",
+ "@videoStreamSelectionDialogAudio": {},
+ "videoStreamSelectionDialogText": "Titulky",
+ "@videoStreamSelectionDialogText": {},
+ "videoStreamSelectionDialogOff": "Vypnuto",
+ "@videoStreamSelectionDialogOff": {},
+ "videoStreamSelectionDialogTrack": "Stopa",
+ "@videoStreamSelectionDialogTrack": {},
+ "videoStreamSelectionDialogNoSelection": "Nejsou k dispozici žádné další stopy.",
+ "@videoStreamSelectionDialogNoSelection": {},
+ "genericSuccessFeedback": "Hotovo!",
+ "@genericSuccessFeedback": {},
+ "genericFailureFeedback": "Selhání",
+ "@genericFailureFeedback": {},
+ "genericDangerWarningDialogMessage": "Jste si jisti?",
+ "@genericDangerWarningDialogMessage": {},
+ "menuActionConfigureView": "Zobrazení",
+ "@menuActionConfigureView": {},
+ "menuActionSelect": "Výběr",
+ "@menuActionSelect": {},
+ "menuActionSelectAll": "Vybrat vše",
+ "@menuActionSelectAll": {},
+ "menuActionSelectNone": "Zrušit výběr",
+ "@menuActionSelectNone": {},
+ "menuActionMap": "Mapa",
+ "@menuActionMap": {},
+ "menuActionSlideshow": "Slideshow",
+ "@menuActionSlideshow": {},
+ "menuActionStats": "Statistiky",
+ "@menuActionStats": {},
+ "viewDialogGroupSectionTitle": "Seskupení",
+ "@viewDialogGroupSectionTitle": {},
+ "viewDialogLayoutSectionTitle": "Rozložení",
+ "@viewDialogLayoutSectionTitle": {},
+ "viewDialogReverseSortOrder": "Obrátit řazení",
+ "@viewDialogReverseSortOrder": {},
+ "tileLayoutGrid": "Mřížka",
+ "@tileLayoutGrid": {},
+ "tileLayoutList": "Seznam",
+ "@tileLayoutList": {},
+ "drawerCollectionVideos": "Videa",
+ "@drawerCollectionVideos": {},
+ "drawerCollectionAnimated": "Animované",
+ "@drawerCollectionAnimated": {},
+ "drawerCollectionMotionPhotos": "Pohyblivé fotografie",
+ "@drawerCollectionMotionPhotos": {},
+ "sortByAlbumFileName": "Podle alba a názvu souboru",
+ "@sortByAlbumFileName": {},
+ "sortByRating": "Podle hodnocení",
+ "@sortByRating": {},
+ "settingsPageTitle": "Nastavení",
+ "@settingsPageTitle": {},
+ "settingsSystemDefault": "Výchozí nastavení systému",
+ "@settingsSystemDefault": {},
+ "settingsDefault": "Výchozí",
+ "@settingsDefault": {},
+ "settingsDisabled": "Zakázáno",
+ "@settingsDisabled": {},
+ "settingsSearchFieldLabel": "Prohledat nastavení",
+ "@settingsSearchFieldLabel": {},
+ "settingsSearchEmpty": "Žádné odpovídající položky",
+ "@settingsSearchEmpty": {},
+ "settingsActionExport": "Export",
+ "@settingsActionExport": {},
+ "settingsActionExportDialogTitle": "Export",
+ "@settingsActionExportDialogTitle": {},
+ "settingsActionImport": "Import",
+ "@settingsActionImport": {},
+ "settingsActionImportDialogTitle": "Import",
+ "@settingsActionImportDialogTitle": {},
+ "appExportCovers": "Přebaly",
+ "@appExportCovers": {},
+ "appExportFavourites": "Oblíbené",
+ "@appExportFavourites": {},
+ "appExportSettings": "Nastavení",
+ "@appExportSettings": {},
+ "settingsHomeTile": "Domů",
+ "@settingsHomeTile": {},
+ "settingsNavigationSectionTitle": "Navigace",
+ "@settingsNavigationSectionTitle": {},
+ "settingsHomeDialogTitle": "Domů",
+ "@settingsHomeDialogTitle": {},
+ "settingsShowBottomNavigationBar": "Zobrazit spodní navigační lištu",
+ "@settingsShowBottomNavigationBar": {},
+ "settingsKeepScreenOnTile": "Ponechat obrazovku zapnutou",
+ "@settingsKeepScreenOnTile": {},
+ "settingsKeepScreenOnDialogTitle": "Ponechat obrazovku zapnutou",
+ "@settingsKeepScreenOnDialogTitle": {},
+ "albumGroupTier": "Podle úrovně",
+ "@albumGroupTier": {},
+ "albumMimeTypeMixed": "Smíšené",
+ "@albumMimeTypeMixed": {},
+ "settingsNavigationDrawerTabTypes": "Typy",
+ "@settingsNavigationDrawerTabTypes": {},
+ "settingsNavigationDrawerTabAlbums": "Alba",
+ "@settingsNavigationDrawerTabAlbums": {},
+ "settingsNavigationDrawerTabPages": "Stránky",
+ "@settingsNavigationDrawerTabPages": {},
+ "settingsNavigationDrawerAddAlbum": "Přidat album",
+ "@settingsNavigationDrawerAddAlbum": {},
+ "settingsThumbnailSectionTitle": "Náhledy",
+ "@settingsThumbnailSectionTitle": {},
+ "settingsThumbnailShowFavouriteIcon": "Zobrazit ikonu oblíbených",
+ "@settingsThumbnailShowFavouriteIcon": {},
+ "settingsThumbnailShowTagIcon": "Zobrazit ikonu štítků",
+ "@settingsThumbnailShowTagIcon": {},
+ "settingsThumbnailShowLocationIcon": "Zobrazit ikonu polohy",
+ "@settingsThumbnailShowLocationIcon": {},
+ "settingsCollectionQuickActionTabSelecting": "Výběr",
+ "@settingsCollectionQuickActionTabSelecting": {},
+ "settingsCollectionBrowsingQuickActionEditorBanner": "Stiskněte a podržte pro přesun tlačítek a vyberte, které akce budou zobrazeny při procházení položek.",
+ "@settingsCollectionBrowsingQuickActionEditorBanner": {},
+ "settingsCollectionSelectionQuickActionEditorBanner": "Stiskněte a podržte pro přesun tlačítek a vyberte, které akce budou zobrazeny při výběru položek.",
+ "@settingsCollectionSelectionQuickActionEditorBanner": {},
+ "settingsViewerSectionTitle": "Prohlížeč",
+ "@settingsViewerSectionTitle": {},
+ "settingsViewerGestureSideTapNext": "Stisknout v rozích obrazovky pro zobrazení předcházející/následující položky",
+ "@settingsViewerGestureSideTapNext": {},
+ "settingsViewerUseCutout": "Použít oblast výřezu",
+ "@settingsViewerUseCutout": {},
+ "settingsViewerMaximumBrightness": "Nejvyšší jas",
+ "@settingsViewerMaximumBrightness": {},
+ "settingsMotionPhotoAutoPlay": "Automatické přehrávání pohyblivých fotografií",
+ "@settingsMotionPhotoAutoPlay": {},
+ "settingsImageBackground": "Pozadí obrázku",
+ "@settingsImageBackground": {},
+ "settingsViewerQuickActionsTile": "Rychlé akce",
+ "@settingsViewerQuickActionsTile": {},
+ "settingsViewerQuickActionEditorPageTitle": "Rychlé akce",
+ "@settingsViewerQuickActionEditorPageTitle": {},
+ "settingsViewerQuickActionEditorBanner": "Stiskněte a podržte pro přesun tlačítek a vyberte, které akce budou zobrazeny při prohlížení položek.",
+ "@settingsViewerQuickActionEditorBanner": {},
+ "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Zobrazená tlačítka",
+ "@settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": {},
+ "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Dostupná tlačítka",
+ "@settingsViewerQuickActionEditorAvailableButtonsSectionTitle": {},
+ "settingsViewerQuickActionEmpty": "Žádná tlačítka",
+ "@settingsViewerQuickActionEmpty": {},
+ "settingsViewerOverlayTile": "Souhrn",
+ "@settingsViewerOverlayTile": {},
+ "settingsViewerOverlayPageTitle": "Souhrn",
+ "@settingsViewerOverlayPageTitle": {},
+ "settingsThumbnailOverlayTile": "Zobrazené detaily",
+ "@settingsThumbnailOverlayTile": {},
+ "settingsThumbnailOverlayPageTitle": "Zobrazené detaily",
+ "@settingsThumbnailOverlayPageTitle": {},
+ "settingsViewerShowMinimap": "Zobrazit minimapu",
+ "@settingsViewerShowMinimap": {},
+ "settingsViewerShowInformationSubtitle": "Zobrazit název, datum, polohu apod.",
+ "@settingsViewerShowInformationSubtitle": {},
+ "settingsViewerShowShootingDetails": "Zobrazit podrobnosti o pořízené fogotrafii",
+ "@settingsViewerShowShootingDetails": {},
+ "settingsViewerShowOverlayThumbnails": "Zobrazit náhledy",
+ "@settingsViewerShowOverlayThumbnails": {},
+ "settingsViewerEnableOverlayBlurEffect": "Efekt rozostření",
+ "@settingsViewerEnableOverlayBlurEffect": {},
+ "settingsViewerSlideshowTile": "Prezentace",
+ "@settingsViewerSlideshowTile": {},
+ "settingsViewerSlideshowPageTitle": "Prezentace",
+ "@settingsViewerSlideshowPageTitle": {},
+ "settingsSlideshowTransitionTile": "Přechod",
+ "@settingsSlideshowTransitionTile": {},
+ "settingsSlideshowVideoPlaybackTile": "Přehrávání videa",
+ "@settingsSlideshowVideoPlaybackTile": {},
+ "settingsSlideshowVideoPlaybackDialogTitle": "Přehrávání videa",
+ "@settingsSlideshowVideoPlaybackDialogTitle": {},
+ "settingsVideoPageTitle": "Nastavení videa",
+ "@settingsVideoPageTitle": {},
+ "settingsVideoSectionTitle": "Video",
+ "@settingsVideoSectionTitle": {},
+ "settingsVideoShowVideos": "Zobrazovat videa",
+ "@settingsVideoShowVideos": {},
+ "settingsVideoEnableHardwareAcceleration": "Hardwarová akcelerace",
+ "@settingsVideoEnableHardwareAcceleration": {},
+ "settingsVideoAutoPlay": "Automatické přehrávání",
+ "@settingsVideoAutoPlay": {},
+ "settingsVideoLoopModeTile": "Režim smyčky",
+ "@settingsVideoLoopModeTile": {},
+ "settingsVideoLoopModeDialogTitle": "Režim smyčky",
+ "@settingsVideoLoopModeDialogTitle": {},
+ "settingsSubtitleThemeTile": "Titulky",
+ "@settingsSubtitleThemeTile": {},
+ "settingsSubtitleThemeTextAlignmentTile": "Zarovnání textu",
+ "@settingsSubtitleThemeTextAlignmentTile": {},
+ "settingsSubtitleThemeTextAlignmentDialogTitle": "Zarovnání textu",
+ "@settingsSubtitleThemeTextAlignmentDialogTitle": {},
+ "settingsSubtitleThemeShowOutline": "Zobrazovat obrys a stín",
+ "@settingsSubtitleThemeShowOutline": {},
+ "settingsSubtitleThemeTextColor": "Barva textu",
+ "@settingsSubtitleThemeTextColor": {},
+ "settingsSubtitleThemeTextOpacity": "Průhlednost textu",
+ "@settingsSubtitleThemeTextOpacity": {},
+ "settingsSubtitleThemeBackgroundColor": "Barva pozadí",
+ "@settingsSubtitleThemeBackgroundColor": {},
+ "settingsSubtitleThemeBackgroundOpacity": "Průhlednost pozadí",
+ "@settingsSubtitleThemeBackgroundOpacity": {},
+ "settingsSubtitleThemeTextAlignmentLeft": "Doleva",
+ "@settingsSubtitleThemeTextAlignmentLeft": {},
+ "settingsSubtitleThemeTextAlignmentCenter": "Na střed",
+ "@settingsSubtitleThemeTextAlignmentCenter": {},
+ "settingsSubtitleThemeTextAlignmentRight": "Doprava",
+ "@settingsSubtitleThemeTextAlignmentRight": {},
+ "settingsVideoControlsTile": "Ovládání",
+ "@settingsVideoControlsTile": {},
+ "settingsVideoGestureDoubleTapTogglePlay": "Dvojité stisknutí pro spuštění / pauzu",
+ "@settingsVideoGestureDoubleTapTogglePlay": {},
+ "settingsVideoGestureSideDoubleTapSeek": "Dvojití stisknutí v rozích obrazovky pro posun vzad / vpřed",
+ "@settingsVideoGestureSideDoubleTapSeek": {},
+ "settingsAllowInstalledAppAccess": "Povolit přístup do aplikačního inventáře",
+ "@settingsAllowInstalledAppAccess": {},
+ "settingsAllowInstalledAppAccessSubtitle": "Používá se pro zlepšení zobrazení alb",
+ "@settingsAllowInstalledAppAccessSubtitle": {},
+ "settingsAllowErrorReporting": "Povolit anonymní chybová hlášení",
+ "@settingsAllowErrorReporting": {},
+ "settingsSaveSearchHistory": "Uložit historii vyhledávání",
+ "@settingsSaveSearchHistory": {},
+ "settingsEnableBin": "Používat koš",
+ "@settingsEnableBin": {},
+ "settingsEnableBinSubtitle": "Ponechat v koši smazané položky po dobu 30 dní",
+ "@settingsEnableBinSubtitle": {},
+ "settingsAllowMediaManagement": "Povolit správu médií",
+ "@settingsAllowMediaManagement": {},
+ "settingsHiddenItemsTile": "Skryté položky",
+ "@settingsHiddenItemsTile": {},
+ "settingsHiddenItemsPageTitle": "Skryté položky",
+ "@settingsHiddenItemsPageTitle": {},
+ "settingsHiddenFiltersBanner": "Fotografie a videa odpovídající filtrům skrytých položek nebudou zobrazeny ve vaši sbírce.",
+ "@settingsHiddenFiltersBanner": {},
+ "settingsHiddenItemsTabFilters": "Filtry položek",
+ "@settingsHiddenItemsTabFilters": {},
+ "settingsHiddenFiltersEmpty": "Žádné filtry skrytých položek",
+ "@settingsHiddenFiltersEmpty": {},
+ "settingsHiddenItemsTabPaths": "Filtry umístění",
+ "@settingsHiddenItemsTabPaths": {},
+ "settingsHiddenPathsBanner": "Fotografie a videa v těchto adresářích a jejich podsložkách nebudou zobrazeny ve vaši sbírce.",
+ "@settingsHiddenPathsBanner": {},
+ "addPathTooltip": "Přidat umístění",
+ "@addPathTooltip": {},
+ "settingsStorageAccessTile": "Přístup k úložišti",
+ "@settingsStorageAccessTile": {},
+ "settingsStorageAccessPageTitle": "Přístup k úložišti",
+ "@settingsStorageAccessPageTitle": {},
+ "settingsStorageAccessEmpty": "Žádné povolené přístupy",
+ "@settingsStorageAccessEmpty": {},
+ "settingsStorageAccessRevokeTooltip": "Odvolat",
+ "@settingsStorageAccessRevokeTooltip": {},
+ "settingsAccessibilitySectionTitle": "Přístupnost",
+ "@settingsAccessibilitySectionTitle": {},
+ "settingsRemoveAnimationsTile": "Zakázat animace",
+ "@settingsRemoveAnimationsTile": {},
+ "settingsRemoveAnimationsDialogTitle": "Zakázat animace",
+ "@settingsRemoveAnimationsDialogTitle": {},
+ "settingsTimeToTakeActionTile": "Čas k provedení akce",
+ "@settingsTimeToTakeActionTile": {},
+ "settingsAccessibilityShowPinchGestureAlternatives": "Zobrazit alternativy vícedotykových gest",
+ "@settingsAccessibilityShowPinchGestureAlternatives": {},
+ "settingsDisplaySectionTitle": "Zobrazení",
+ "@settingsDisplaySectionTitle": {},
+ "settingsThemeBrightnessTile": "Vzhled",
+ "@settingsThemeBrightnessTile": {},
+ "settingsThemeBrightnessDialogTitle": "Vzhled",
+ "@settingsThemeBrightnessDialogTitle": {},
+ "settingsThemeColorHighlights": "Zvýraznění barev",
+ "@settingsThemeColorHighlights": {},
+ "settingsThemeEnableDynamicColor": "Dynamické barvy",
+ "@settingsThemeEnableDynamicColor": {},
+ "settingsDisplayRefreshRateModeTile": "Obnovovací frekvence displeje",
+ "@settingsDisplayRefreshRateModeTile": {},
+ "settingsDisplayRefreshRateModeDialogTitle": "Obnovovací frekvence",
+ "@settingsDisplayRefreshRateModeDialogTitle": {},
+ "settingsLanguageSectionTitle": "Jazyk a formáty",
+ "@settingsLanguageSectionTitle": {},
+ "settingsLanguageTile": "Jazyk",
+ "@settingsLanguageTile": {},
+ "settingsLanguagePageTitle": "Jazyk",
+ "@settingsLanguagePageTitle": {},
+ "settingsCoordinateFormatTile": "Formát souřadnic",
+ "@settingsCoordinateFormatTile": {},
+ "settingsCoordinateFormatDialogTitle": "Formát souřadnic",
+ "@settingsCoordinateFormatDialogTitle": {},
+ "settingsUnitSystemDialogTitle": "Jednotky",
+ "@settingsUnitSystemDialogTitle": {},
+ "settingsScreenSaverPageTitle": "Spořič displeje",
+ "@settingsScreenSaverPageTitle": {},
+ "settingsWidgetPageTitle": "Rámeček fotografie",
+ "@settingsWidgetPageTitle": {},
+ "settingsWidgetShowOutline": "Obrys",
+ "@settingsWidgetShowOutline": {},
+ "settingsWidgetOpenPage": "Při stisknutí widgetu",
+ "@settingsWidgetOpenPage": {},
+ "settingsWidgetDisplayedItem": "Zobrazená položka",
+ "@settingsWidgetDisplayedItem": {},
+ "settingsCollectionTile": "Sbírka",
+ "@settingsCollectionTile": {},
+ "statsPageTitle": "Statistiky",
+ "@statsPageTitle": {},
+ "statsTopCountriesSectionTitle": "Nejčastější země",
+ "@statsTopCountriesSectionTitle": {},
+ "statsTopPlacesSectionTitle": "Nejčastější místa",
+ "@statsTopPlacesSectionTitle": {},
+ "statsTopTagsSectionTitle": "Nejčastější štítky",
+ "@statsTopTagsSectionTitle": {},
+ "statsTopAlbumsSectionTitle": "Nejčastější alba",
+ "@statsTopAlbumsSectionTitle": {},
+ "viewerOpenPanoramaButtonLabel": "OTEVŘÍT PANORAMA",
+ "@viewerOpenPanoramaButtonLabel": {},
+ "viewerSetWallpaperButtonLabel": "NASTAVIT POZADÍ",
+ "@viewerSetWallpaperButtonLabel": {},
+ "viewerErrorUnknown": "Jejda!",
+ "@viewerErrorUnknown": {},
+ "viewerErrorDoesNotExist": "Soubor již neexistuje.",
+ "@viewerErrorDoesNotExist": {},
+ "viewerInfoPageTitle": "Detaily",
+ "@viewerInfoPageTitle": {},
+ "viewerInfoBackToViewerTooltip": "Zpět k prohlížení",
+ "@viewerInfoBackToViewerTooltip": {},
+ "viewerInfoUnknown": "neznámý",
+ "@viewerInfoUnknown": {},
+ "viewerInfoLabelDescription": "Popis",
+ "@viewerInfoLabelDescription": {},
+ "viewerInfoLabelTitle": "Název",
+ "@viewerInfoLabelTitle": {},
+ "viewerInfoLabelResolution": "Rozlišení",
+ "@viewerInfoLabelResolution": {},
+ "viewerInfoLabelSize": "Velikost",
+ "@viewerInfoLabelSize": {},
+ "viewerInfoLabelUri": "URI",
+ "@viewerInfoLabelUri": {},
+ "viewerInfoLabelPath": "Umístění",
+ "@viewerInfoLabelPath": {},
+ "viewerInfoLabelDuration": "Délka",
+ "@viewerInfoLabelDuration": {},
+ "viewerInfoLabelCoordinates": "Souřadnice",
+ "@viewerInfoLabelCoordinates": {},
+ "viewerInfoLabelAddress": "Adresa",
+ "@viewerInfoLabelAddress": {},
+ "mapStyleDialogTitle": "Styl mapy",
+ "@mapStyleDialogTitle": {},
+ "mapStyleTooltip": "Vyberte styl mapy",
+ "@mapStyleTooltip": {},
+ "mapZoomInTooltip": "Přiblížit",
+ "@mapZoomInTooltip": {},
+ "mapZoomOutTooltip": "Oddálit",
+ "@mapZoomOutTooltip": {},
+ "mapPointNorthUpTooltip": "Sever nahoře",
+ "@mapPointNorthUpTooltip": {},
+ "openMapPageTooltip": "Zobrazit na mapě",
+ "@openMapPageTooltip": {},
+ "mapEmptyRegion": "Žádné obrázky v této oblasti",
+ "@mapEmptyRegion": {},
+ "viewerInfoOpenEmbeddedFailureFeedback": "Nepodařilo se extrahovat vložená data",
+ "@viewerInfoOpenEmbeddedFailureFeedback": {},
+ "viewerInfoOpenLinkText": "Otevřít",
+ "@viewerInfoOpenLinkText": {},
+ "viewerInfoSearchFieldLabel": "Prohledat metadata",
+ "@viewerInfoSearchFieldLabel": {},
+ "viewerInfoSearchEmpty": "Žádné odpovídající klíče",
+ "@viewerInfoSearchEmpty": {},
+ "viewerInfoSearchSuggestionDate": "Datum a čas",
+ "@viewerInfoSearchSuggestionDate": {},
+ "viewerInfoSearchSuggestionDescription": "Popis",
+ "@viewerInfoSearchSuggestionDescription": {},
+ "viewerInfoSearchSuggestionResolution": "Rozlišení",
+ "@viewerInfoSearchSuggestionResolution": {},
+ "wallpaperUseScrollEffect": "Použít rolovací efekt na domovské obrazovce",
+ "@wallpaperUseScrollEffect": {},
+ "tagEditorPageTitle": "Upravit štítky",
+ "@tagEditorPageTitle": {},
+ "mapAttributionOsmHot": "Mapová data © [OpenStreetMap](https://www.openstreetmap.org/copyright) přispěvatelé • Dlaždice z [HOT](https://www.hotosm.org/) • Hostováno na [OSM France](https://openstreetmap.fr/)",
+ "@mapAttributionOsmHot": {},
+ "viewerInfoSearchSuggestionRights": "Práva",
+ "@viewerInfoSearchSuggestionRights": {},
+ "tagEditorSectionPlaceholders": "Umístění",
+ "@tagEditorSectionPlaceholders": {},
+ "tagPlaceholderCountry": "Země",
+ "@tagPlaceholderCountry": {},
+ "tagPlaceholderPlace": "Místo",
+ "@tagPlaceholderPlace": {},
+ "panoramaEnableSensorControl": "Povolit ovládání senzorem",
+ "@panoramaEnableSensorControl": {},
+ "sourceViewerPageTitle": "Zdroj",
+ "@sourceViewerPageTitle": {},
+ "filePickerShowHiddenFiles": "Zobrazit skryté soubory",
+ "@filePickerShowHiddenFiles": {},
+ "filePickerDoNotShowHiddenFiles": "Nezobrazovat skryté soubory",
+ "@filePickerDoNotShowHiddenFiles": {},
+ "filePickerOpenFrom": "Otevřít z",
+ "@filePickerOpenFrom": {},
+ "filePickerNoItems": "Žádné položky",
+ "@filePickerNoItems": {},
+ "filePickerUseThisFolder": "Použít tuto složku",
+ "@filePickerUseThisFolder": {},
+ "pickTooltip": "Vybrat",
+ "@pickTooltip": {},
+ "doubleBackExitMessage": "Pro ukončení klepněte znovu na „zpět“.",
+ "@doubleBackExitMessage": {},
+ "mapStyleGoogleNormal": "Mapy Google",
+ "@mapStyleGoogleNormal": {},
+ "mapStyleGoogleHybrid": "Mapy Google (satelitní)",
+ "@mapStyleGoogleHybrid": {},
+ "displayRefreshRatePreferLowest": "Nejnižší",
+ "@displayRefreshRatePreferLowest": {},
+ "nameConflictStrategySkip": "Přeskočit",
+ "@nameConflictStrategySkip": {},
+ "keepScreenOnNever": "Nikdy",
+ "@keepScreenOnNever": {},
+ "binEntriesConfirmationDialogMessage": "{count, plural, =1{Přesunout tuto položku do koše?} =2..4{Přesunout tyto {count} položky do koše?} other{Přesunout těchto {count} položek do koše?}}",
+ "@binEntriesConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "setCoverDialogCustom": "Vlastní",
+ "@setCoverDialogCustom": {},
+ "appPickDialogTitle": "Vybrat aplikaci",
+ "@appPickDialogTitle": {},
+ "setCoverDialogAuto": "Automaticky",
+ "@setCoverDialogAuto": {},
+ "hideFilterConfirmationDialogMessage": "Odpovídající fotografie a videa budou ve vaší sbírce schovány. Můžete je znovu zobrazit v nastavení \"Soukromí\".\n\nOpravdu je chcete skrýt?",
+ "@hideFilterConfirmationDialogMessage": {},
+ "newAlbumDialogTitle": "Nové album",
+ "@newAlbumDialogTitle": {},
+ "editEntryDateDialogTitle": "Datum a čas",
+ "@editEntryDateDialogTitle": {},
+ "editEntryDateDialogSetCustom": "Nastavit vlastní datum",
+ "@editEntryDateDialogSetCustom": {},
+ "aboutBugCopyInfoButton": "Kopírovat",
+ "@aboutBugCopyInfoButton": {},
+ "viewDialogSortSectionTitle": "Řazení",
+ "@viewDialogSortSectionTitle": {},
+ "coverDialogTabColor": "Barva",
+ "@coverDialogTabColor": {},
+ "tileLayoutMosaic": "Mozaika",
+ "@tileLayoutMosaic": {},
+ "coverDialogTabCover": "Přebal",
+ "@coverDialogTabCover": {},
+ "coverDialogTabApp": "Aplikace",
+ "@coverDialogTabApp": {},
+ "aboutPageTitle": "O aplikaci",
+ "@aboutPageTitle": {},
+ "aboutBugSectionTitle": "Hlášení chyb",
+ "@aboutBugSectionTitle": {},
+ "aboutLinkLicense": "Licence",
+ "@aboutLinkLicense": {},
+ "appPickDialogNone": "Žádný",
+ "@appPickDialogNone": {},
+ "aboutLinkPolicy": "Zásady ochrany osobních údajů",
+ "@aboutLinkPolicy": {},
+ "aboutBugSaveLogInstruction": "Uložte aplikační logy do souboru",
+ "@aboutBugSaveLogInstruction": {},
+ "aboutBugReportInstruction": "Nahlásit na GitHub s logy a informacemi o systému",
+ "@aboutBugReportInstruction": {},
+ "aboutBugReportButton": "Nahlásit",
+ "@aboutBugReportButton": {},
+ "aboutCreditsWorldAtlas2": "pod ISC licencí.",
+ "@aboutCreditsWorldAtlas2": {},
+ "collectionEmptyFavourites": "Žádné oblíbené položky",
+ "@collectionEmptyFavourites": {},
+ "albumScreenRecordings": "Nahrávky obrazovky",
+ "@albumScreenRecordings": {},
+ "aboutCreditsSectionTitle": "Zásluhy",
+ "@aboutCreditsSectionTitle": {},
+ "aboutBugCopyInfoInstruction": "Zkopírujte informace o systému",
+ "@aboutBugCopyInfoInstruction": {},
+ "collectionMoveFailureFeedback": "{count, plural, =1{Chyba při přesouvání 1 položky} other{Chyba při přesouvání {count} položek}}",
+ "@collectionMoveFailureFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "collectionRenameFailureFeedback": "{count, plural, =1{Chyba přejmenování 1 položky} other{Chyba přejmenování {count} položek}}",
+ "@collectionRenameFailureFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "collectionExportFailureFeedback": "{count, plural, =1{Chyba při exportu 1 stránky} other{Chyba při exportu {count} stránek}}",
+ "@collectionExportFailureFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "drawerCollectionRaws": "Fotografie Raw",
+ "@drawerCollectionRaws": {},
+ "drawerTagPage": "Štítky",
+ "@drawerTagPage": {},
+ "aboutCreditsWorldAtlas1": "Tato aplikace využívá soubor TopoJSON od",
+ "@aboutCreditsWorldAtlas1": {},
+ "drawerCollectionSphericalVideos": "360° videa",
+ "@drawerCollectionSphericalVideos": {},
+ "aboutLicensesBanner": "Tato aplikace využívá tyto open-source baličky a knihovny.",
+ "@aboutLicensesBanner": {},
+ "collectionGroupNone": "Neseskupovat",
+ "@collectionGroupNone": {},
+ "aboutLicensesSectionTitle": "Licence open-source",
+ "@aboutLicensesSectionTitle": {},
+ "collectionActionHideTitleSearch": "Skrýt filtr dle názvu",
+ "@collectionActionHideTitleSearch": {},
+ "aboutTranslatorsSectionTitle": "Překladatalé",
+ "@aboutTranslatorsSectionTitle": {},
+ "aboutLicensesAndroidLibrariesSectionTitle": "Knihovny Androidu",
+ "@aboutLicensesAndroidLibrariesSectionTitle": {},
+ "aboutLicensesFlutterPackagesSectionTitle": "Baličky Flutteru",
+ "@aboutLicensesFlutterPackagesSectionTitle": {},
+ "policyPageTitle": "Zásady ochrany osobních údajů",
+ "@policyPageTitle": {},
+ "collectionPageTitle": "Sbírky",
+ "@collectionPageTitle": {},
+ "collectionPickPageTitle": "Výběr",
+ "@collectionPickPageTitle": {},
+ "collectionActionShowTitleSearch": "Filtrovat dle názvu",
+ "@collectionActionShowTitleSearch": {},
+ "collectionSelectPageTitle": "Vybrat položky",
+ "@collectionSelectPageTitle": {},
+ "aboutLicensesFlutterPluginsSectionTitle": "Pluginy Flutteru",
+ "@aboutLicensesFlutterPluginsSectionTitle": {},
+ "aboutLicensesDartPackagesSectionTitle": "Balíčky Dartu",
+ "@aboutLicensesDartPackagesSectionTitle": {},
+ "aboutLicensesShowAllButtonLabel": "Zobrazit všechny licence",
+ "@aboutLicensesShowAllButtonLabel": {},
+ "collectionActionEmptyBin": "Vysypat koš",
+ "@collectionActionEmptyBin": {},
+ "collectionActionCopy": "Kopírovat do alba",
+ "@collectionActionCopy": {},
+ "collectionActionMove": "Přesunout do alba",
+ "@collectionActionMove": {},
+ "collectionActionEdit": "Upravit",
+ "@collectionActionEdit": {},
+ "collectionCopyFailureFeedback": "{count, plural, =1{Chyba při kopírování 1 položky} other{Chyba při kopírování {count} položek}}",
+ "@collectionCopyFailureFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "collectionActionAddShortcut": "Vytvořit zástupce",
+ "@collectionActionAddShortcut": {},
+ "collectionGroupDay": "Podle dne",
+ "@collectionGroupDay": {},
+ "collectionSelectSectionTooltip": "Vybrat sekci",
+ "@collectionSelectSectionTooltip": {},
+ "drawerAboutButton": "O aplikaci",
+ "@drawerAboutButton": {},
+ "collectionActionRescan": "Znovu prohledat",
+ "@collectionActionRescan": {},
+ "collectionSearchTitlesHintText": "Hledat názvy",
+ "@collectionSearchTitlesHintText": {},
+ "collectionGroupAlbum": "Podle alba",
+ "@collectionGroupAlbum": {},
+ "collectionGroupMonth": "Podle měsíce",
+ "@collectionGroupMonth": {},
+ "sectionUnknown": "Neznámý",
+ "@sectionUnknown": {},
+ "dateToday": "Dnes",
+ "@dateToday": {},
+ "dateYesterday": "Včera",
+ "@dateYesterday": {},
+ "dateThisMonth": "Tento měsíc",
+ "@dateThisMonth": {},
+ "collectionDeleteFailureFeedback": "{count, plural, =1{Chyba při mazání 1 položky} other{Chyba při mazání {count} položek}}",
+ "@collectionDeleteFailureFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "sortOrderLowestFirst": "Od nejnižšího",
+ "@sortOrderLowestFirst": {},
+ "collectionEditFailureFeedback": "{count, plural, =1{Chyba při úpravě 1 položky} other{Chyba při úpravě {count} položek}}",
+ "@collectionEditFailureFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "collectionCopySuccessFeedback": "{count, plural, =1{Zkopírována 1 položka} =2..4{Zkopírovány {count} položky} other{Zkopírováno {count} položek}}",
+ "@collectionCopySuccessFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "collectionMoveSuccessFeedback": "{count, plural, =1{Přesunuta 1 položka} =2..4{Přesunuty {count} položky} other{Přesunuto {count} položek}}",
+ "@collectionMoveSuccessFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "collectionRenameSuccessFeedback": "{count, plural, =1{Přejmenována 1 položka} =2..4{Přejmenovány {count} položky} other{Přejmenováno {count} položek}}",
+ "@collectionRenameSuccessFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "collectionEditSuccessFeedback": "{count, plural, =1{Upravena 1 položka} =2..4{Upraveny {count} položky} other{Upraveno {count} položek}}",
+ "@collectionEditSuccessFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "collectionEmptyVideos": "Žádná videa",
+ "@collectionEmptyVideos": {},
+ "collectionDeselectSectionTooltip": "Zrušit výběr sekce",
+ "@collectionDeselectSectionTooltip": {},
+ "drawerSettingsButton": "Nastavení",
+ "@drawerSettingsButton": {},
+ "drawerCollectionFavourites": "Oblíbené",
+ "@drawerCollectionFavourites": {},
+ "drawerCollectionPanoramas": "Panoramata",
+ "@drawerCollectionPanoramas": {},
+ "drawerCountryPage": "Země",
+ "@drawerCountryPage": {},
+ "sortByDate": "Podle data",
+ "@sortByDate": {},
+ "sortByName": "Abecedně",
+ "@sortByName": {},
+ "sortByItemCount": "Podle počtu položek",
+ "@sortByItemCount": {},
+ "sortBySize": "Podle velikosti",
+ "@sortBySize": {},
+ "albumGroupVolume": "Podle úložiště",
+ "@albumGroupVolume": {},
+ "drawerCollectionAll": "Celá sbírka",
+ "@drawerCollectionAll": {},
+ "collectionEmptyImages": "Žádné obrázky",
+ "@collectionEmptyImages": {},
+ "collectionEmptyGrantAccessButtonLabel": "Povolit přístup",
+ "@collectionEmptyGrantAccessButtonLabel": {},
+ "drawerCollectionImages": "Obrázky",
+ "@drawerCollectionImages": {},
+ "drawerAlbumPage": "Alba",
+ "@drawerAlbumPage": {},
+ "sortOrderZtoA": "Od Z do A",
+ "@sortOrderZtoA": {},
+ "sortOrderHighestFirst": "Od nejvyššího",
+ "@sortOrderHighestFirst": {},
+ "albumGroupType": "Podle typu",
+ "@albumGroupType": {},
+ "sortOrderNewestFirst": "Od nejnovějšího",
+ "@sortOrderNewestFirst": {},
+ "sortOrderAtoZ": "Od A do Z",
+ "@sortOrderAtoZ": {},
+ "sortOrderOldestFirst": "Od nejstaršího",
+ "@sortOrderOldestFirst": {},
+ "albumPickPageTitleExport": "Exportovat do alba",
+ "@albumPickPageTitleExport": {},
+ "albumCamera": "Fotoaparát",
+ "@albumCamera": {},
+ "albumPageTitle": "Alba",
+ "@albumPageTitle": {},
+ "albumEmpty": "Žádná alba",
+ "@albumEmpty": {},
+ "createAlbumButtonLabel": "VYTVOŘIT",
+ "@createAlbumButtonLabel": {},
+ "searchPlacesSectionTitle": "Místa",
+ "@searchPlacesSectionTitle": {},
+ "sortOrderLargestFirst": "Od nejširšího",
+ "@sortOrderLargestFirst": {},
+ "sortOrderSmallestFirst": "Od nejužšího",
+ "@sortOrderSmallestFirst": {},
+ "albumGroupNone": "Neseskupovat",
+ "@albumGroupNone": {},
+ "albumVideoCaptures": "Snímky videa",
+ "@albumVideoCaptures": {},
+ "createAlbumTooltip": "Vytvořit album",
+ "@createAlbumTooltip": {},
+ "countryPageTitle": "Země",
+ "@countryPageTitle": {},
+ "searchCollectionFieldHint": "Prohledat sbírky",
+ "@searchCollectionFieldHint": {},
+ "albumPickPageTitleCopy": "Kopírovat do alba",
+ "@albumPickPageTitleCopy": {},
+ "tagPageTitle": "Štítky",
+ "@tagPageTitle": {},
+ "albumPickPageTitleMove": "Přesunout do alba",
+ "@albumPickPageTitleMove": {},
+ "albumPickPageTitlePick": "Vybrat album",
+ "@albumPickPageTitlePick": {},
+ "albumDownload": "Stažené",
+ "@albumDownload": {},
+ "albumScreenshots": "Snímky obrazovky",
+ "@albumScreenshots": {},
+ "newFilterBanner": "nový",
+ "@newFilterBanner": {},
+ "countryEmpty": "Žádné země",
+ "@countryEmpty": {},
+ "tagEmpty": "Žádné štítky",
+ "@tagEmpty": {},
+ "binPageTitle": "Koš",
+ "@binPageTitle": {},
+ "searchMetadataSectionTitle": "Metadata",
+ "@searchMetadataSectionTitle": {},
+ "searchRatingSectionTitle": "Hodnocení",
+ "@searchRatingSectionTitle": {},
+ "settingsDoubleBackExit": "Klepnout dvakrát na „zpět“ pro ukončení",
+ "@settingsDoubleBackExit": {},
+ "settingsConfirmationTile": "Potvrzovací dialogy",
+ "@settingsConfirmationTile": {},
+ "settingsConfirmationBeforeDeleteItems": "Zeptat se před smazáním položek navždy",
+ "@settingsConfirmationBeforeDeleteItems": {},
+ "settingsConfirmationAfterMoveToBinItems": "Zobrazit zprávu po přesunu položek do koše",
+ "@settingsConfirmationAfterMoveToBinItems": {},
+ "searchRecentSectionTitle": "Nedávné",
+ "@searchRecentSectionTitle": {},
+ "searchDateSectionTitle": "Datum",
+ "@searchDateSectionTitle": {},
+ "searchAlbumsSectionTitle": "Alba",
+ "@searchAlbumsSectionTitle": {},
+ "searchCountriesSectionTitle": "Země",
+ "@searchCountriesSectionTitle": {},
+ "searchTagsSectionTitle": "Štítky",
+ "@searchTagsSectionTitle": {},
+ "settingsNavigationDrawerTile": "Navigační menu",
+ "@settingsNavigationDrawerTile": {},
+ "settingsNavigationDrawerEditorPageTitle": "Navigační menu",
+ "@settingsNavigationDrawerEditorPageTitle": {},
+ "settingsCollectionQuickActionEditorPageTitle": "Rychlé akce",
+ "@settingsCollectionQuickActionEditorPageTitle": {},
+ "settingsConfirmationDialogTitle": "Potvrzovací dialogy",
+ "@settingsConfirmationDialogTitle": {},
+ "settingsConfirmationBeforeMoveUndatedItems": "Zeptat se před přesunem položek bez data",
+ "@settingsConfirmationBeforeMoveUndatedItems": {},
+ "settingsConfirmationBeforeMoveToBinItems": "Zeptat se před přesunem položek do koše",
+ "@settingsConfirmationBeforeMoveToBinItems": {},
+ "settingsNavigationDrawerBanner": "Stiskněte a podržte pro přesun položek a změnu jejich pořadí.",
+ "@settingsNavigationDrawerBanner": {},
+ "settingsCollectionQuickActionTabBrowsing": "Procházení",
+ "@settingsCollectionQuickActionTabBrowsing": {},
+ "settingsThumbnailShowMotionPhotoIcon": "Zobrazit ikonu pohyblivých fotografií",
+ "@settingsThumbnailShowMotionPhotoIcon": {},
+ "settingsThumbnailShowRating": "Zobrazit hodnocení",
+ "@settingsThumbnailShowRating": {},
+ "settingsThumbnailShowRawIcon": "Zobrazit ikonu RAW",
+ "@settingsThumbnailShowRawIcon": {},
+ "settingsThumbnailShowVideoDuration": "Zobrazit délku videa",
+ "@settingsThumbnailShowVideoDuration": {},
+ "settingsCollectionQuickActionsTile": "Rychlé akce",
+ "@settingsCollectionQuickActionsTile": {},
+ "timeMinutes": "{minutes, plural, =1{1 minuta} =2..4{{minutes} minuty} other{{minutes} minut}}",
+ "@timeMinutes": {
+ "placeholders": {
+ "minutes": {}
+ }
+ },
+ "timeSeconds": "{seconds, plural, =1{1 sekunda} =2..4{{seconds} sekundy} other{{seconds} sekund}}",
+ "@timeSeconds": {
+ "placeholders": {
+ "seconds": {}
+ }
+ },
+ "settingsViewerShowOverlayOnOpening": "Zobrazit při otevření",
+ "@settingsViewerShowOverlayOnOpening": {},
+ "settingsViewerShowInformation": "Zobrazit informace",
+ "@settingsViewerShowInformation": {},
+ "settingsViewerShowRatingTags": "Zobrazit hodnocení a štítky",
+ "@settingsViewerShowRatingTags": {},
+ "settingsSlideshowRepeat": "Opakovat",
+ "@settingsSlideshowRepeat": {},
+ "settingsSubtitleThemeTextPositionTile": "Pozice textu",
+ "@settingsSubtitleThemeTextPositionTile": {},
+ "settingsSlideshowFillScreen": "Vyplnit obrazovku",
+ "@settingsSlideshowFillScreen": {},
+ "settingsSlideshowShuffle": "Náhodně",
+ "@settingsSlideshowShuffle": {},
+ "settingsSlideshowAnimatedZoomEffect": "Animovaný efekt přiblížení",
+ "@settingsSlideshowAnimatedZoomEffect": {},
+ "settingsSlideshowIntervalTile": "Interval",
+ "@settingsSlideshowIntervalTile": {},
+ "settingsSubtitleThemeSample": "Toto je příklad.",
+ "@settingsSubtitleThemeSample": {},
+ "settingsSubtitleThemeTextSize": "Velikost textu",
+ "@settingsSubtitleThemeTextSize": {},
+ "settingsSubtitleThemePageTitle": "Titulky",
+ "@settingsSubtitleThemePageTitle": {},
+ "settingsSubtitleThemeTextPositionDialogTitle": "Pozice textu",
+ "@settingsSubtitleThemeTextPositionDialogTitle": {},
+ "settingsVideoButtonsTile": "Tlačítka",
+ "@settingsVideoButtonsTile": {},
+ "settingsVideoControlsPageTitle": "Ovládání",
+ "@settingsVideoControlsPageTitle": {},
+ "settingsPrivacySectionTitle": "Soukromí",
+ "@settingsPrivacySectionTitle": {},
+ "settingsStorageAccessBanner": "Některé adresáře vyžadují explicitní udělení přístupu k úpravě jejich souborů. Zde si můžete prohlédnout adresáře, ke kterým jste dříve udělili přístup.",
+ "@settingsStorageAccessBanner": {},
+ "settingsUnitSystemTile": "Jednotky",
+ "@settingsUnitSystemTile": {},
+ "statsWithGps": "{count, plural, =1{1 položka s polohou} =2..4{{count} položky s polohou} other{{count} položek s polohou}}",
+ "@statsWithGps": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "viewerInfoLabelDate": "Datum",
+ "@viewerInfoLabelDate": {},
+ "viewerInfoLabelOwner": "Vlastník",
+ "@viewerInfoLabelOwner": {},
+ "tagEditorPageAddTagTooltip": "Přidat štítek",
+ "@tagEditorPageAddTagTooltip": {},
+ "viewerInfoViewXmlLinkText": "Zobrazit XML",
+ "@viewerInfoViewXmlLinkText": {},
+ "viewerInfoSearchSuggestionDimensions": "Rozměry",
+ "@viewerInfoSearchSuggestionDimensions": {},
+ "tagEditorPageNewTagFieldLabel": "Nový štítek",
+ "@tagEditorPageNewTagFieldLabel": {},
+ "tagEditorSectionRecent": "Nedávné",
+ "@tagEditorSectionRecent": {},
+ "mapAttributionStamen": "Mapová data © [OpenStreetMap](https://www.openstreetmap.org/copyright) přispěvatelé • Dlaždice z [Stamen Design](https://stamen.com), [CC BY 3.0](https://creativecommons.org/licenses/by/3.0)",
+ "@mapAttributionStamen": {},
+ "panoramaDisableSensorControl": "Zakázat ovládání senzorem",
+ "@panoramaDisableSensorControl": {}
+}
diff --git a/lib/l10n/app_el.arb b/lib/l10n/app_el.arb
index 32692a0df..6214117be 100644
--- a/lib/l10n/app_el.arb
+++ b/lib/l10n/app_el.arb
@@ -1196,5 +1196,15 @@
"filterNoAddressLabel": "Χωρίς διεύθυνση",
"@filterNoAddressLabel": {},
"settingsViewerShowRatingTags": "Εμφάνιση βαθμολογίας & ετικετών",
- "@settingsViewerShowRatingTags": {}
+ "@settingsViewerShowRatingTags": {},
+ "filterLocatedLabel": "Με τοποθεσία",
+ "@filterLocatedLabel": {},
+ "filterTaggedLabel": "Με ετικέτα",
+ "@filterTaggedLabel": {},
+ "settingsModificationWarningDialogMessage": "Άλλες ρυθμίσεις θα τροποποιηθούν.",
+ "@settingsModificationWarningDialogMessage": {},
+ "settingsDisplayUseTvInterface": "Χρήση του Android TV περιβάλλον",
+ "@settingsDisplayUseTvInterface": {},
+ "settingsViewerShowDescription": "Εμφάνιση περιγραφής",
+ "@settingsViewerShowDescription": {}
}
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 8f1c08b35..52c9ab346 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -139,8 +139,10 @@
"filterFavouriteLabel": "Favorite",
"filterNoDateLabel": "Undated",
"filterNoAddressLabel": "No address",
+ "filterLocatedLabel": "Located",
"filterNoLocationLabel": "Unlocated",
"filterNoRatingLabel": "Unrated",
+ "filterTaggedLabel": "Tagged",
"filterNoTagLabel": "Untagged",
"filterNoTitleLabel": "Untitled",
"filterOnThisDayLabel": "On this day",
@@ -375,13 +377,13 @@
"renameProcessorCounter": "Counter",
"renameProcessorName": "Name",
- "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Delete this album and its item?} other{Delete this album and its {count} items?}}",
+ "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": {}
}
},
- "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Delete these albums and their item?} other{Delete these albums and their {count} items?}}",
+ "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": {}
@@ -436,6 +438,8 @@
"genericFailureFeedback": "Failed",
"genericDangerWarningDialogMessage": "Are you sure?",
+ "tooManyItemsErrorDialogMessage": "Try again with fewer items.",
+
"menuActionConfigureView": "View",
"menuActionSelect": "Select",
"menuActionSelectAll": "Select all",
@@ -656,6 +660,7 @@
"settingsSystemDefault": "System default",
"settingsDefault": "Default",
"settingsDisabled": "Disabled",
+ "settingsModificationWarningDialogMessage": "Other settings will be modified.",
"settingsSearchFieldLabel": "Search settings",
"settingsSearchEmpty": "No matching setting",
@@ -731,6 +736,7 @@
"settingsViewerShowInformationSubtitle": "Show title, date, location, etc.",
"settingsViewerShowRatingTags": "Show rating & tags",
"settingsViewerShowShootingDetails": "Show shooting details",
+ "settingsViewerShowDescription": "Show description",
"settingsViewerShowOverlayThumbnails": "Show thumbnails",
"settingsViewerEnableOverlayBlurEffect": "Blur effect",
@@ -815,6 +821,7 @@
"settingsThemeEnableDynamicColor": "Dynamic color",
"settingsDisplayRefreshRateModeTile": "Display refresh rate",
"settingsDisplayRefreshRateModeDialogTitle": "Refresh Rate",
+ "settingsDisplayUseTvInterface": "Android TV interface",
"settingsLanguageSectionTitle": "Language & Formats",
"settingsLanguageTile": "Language",
diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb
index 95fc9ebc5..3cf489059 100644
--- a/lib/l10n/app_es.arb
+++ b/lib/l10n/app_es.arb
@@ -371,9 +371,9 @@
"@renameProcessorCounter": {},
"renameProcessorName": "Nombre",
"@renameProcessorName": {},
- "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{¿Está seguro de que desea borrar este álbum y un elemento?} other{¿Está seguro de que desea borrar este álbum y sus {count} elementos?}}",
+ "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{¿Eliminar este álbum y el elemento que contiene?} other{¿Eliminar este álbum y los {count} elementos que contiene?}}",
"@deleteSingleAlbumConfirmationDialogMessage": {},
- "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{¿Está seguro de que desea borrar estos álbumes y un elemento?} other{¿Está seguro de que desea borrar estos álbumes y sus {count} elementos?}}",
+ "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{¿Eliminar estos álbumes y el elemento que contienen?} other{¿Eliminar estos álbumes y los {count} elementos que contienen?}}",
"@deleteMultiAlbumConfirmationDialogMessage": {},
"exportEntryDialogFormat": "Formato:",
"@exportEntryDialogFormat": {},
@@ -1196,5 +1196,15 @@
"placeholders": {
"count": {}
}
- }
+ },
+ "settingsViewerShowDescription": "Mostrar la descripción",
+ "@settingsViewerShowDescription": {},
+ "settingsModificationWarningDialogMessage": "Otras configuraciones serán modificadas.",
+ "@settingsModificationWarningDialogMessage": {},
+ "settingsDisplayUseTvInterface": "Interfaz de Android TV",
+ "@settingsDisplayUseTvInterface": {},
+ "filterLocatedLabel": "Localizado",
+ "@filterLocatedLabel": {},
+ "filterTaggedLabel": "Etiquetado",
+ "@filterTaggedLabel": {}
}
diff --git a/lib/l10n/app_fa.arb b/lib/l10n/app_fa.arb
index 242e48083..9c1c2e9a3 100644
--- a/lib/l10n/app_fa.arb
+++ b/lib/l10n/app_fa.arb
@@ -297,5 +297,86 @@
"policyPageTitle": "سیاست حفظ حریم خصوصی",
"@policyPageTitle": {},
"collectionPickPageTitle": "انتخاب",
- "@collectionPickPageTitle": {}
+ "@collectionPickPageTitle": {},
+ "videoResumeDialogMessage": "ادامه پخش از زمان {time}؟",
+ "@videoResumeDialogMessage": {
+ "placeholders": {
+ "time": {
+ "type": "String",
+ "example": "13:37"
+ }
+ }
+ },
+ "storageVolumeDescriptionFallbackNonPrimary": "کارت حافظه",
+ "@storageVolumeDescriptionFallbackNonPrimary": {},
+ "videoPlaybackWithSound": "پخش با صدا",
+ "@videoPlaybackWithSound": {},
+ "entryActionCopyToClipboard": "کپی به کلیپ بورد",
+ "@entryActionCopyToClipboard": {},
+ "entryActionShowGeoTiffOnMap": "نمایش بر روی نقشه",
+ "@entryActionShowGeoTiffOnMap": {},
+ "filterOnThisDayLabel": "در امروز",
+ "@filterOnThisDayLabel": {},
+ "mapStyleGoogleNormal": "گوگل مپ",
+ "@mapStyleGoogleNormal": {},
+ "mapStyleGoogleTerrain": "گوگل مپ (نمایش زمین)",
+ "@mapStyleGoogleTerrain": {},
+ "mapStyleGoogleHybrid": "گوگل مپ (نمایش هیبریدی)",
+ "@mapStyleGoogleHybrid": {},
+ "subtitlePositionTop": "بالا",
+ "@subtitlePositionTop": {},
+ "mapStyleStamenWatercolor": "استامن (نمایش نقشه کشیده شده)",
+ "@mapStyleStamenWatercolor": {},
+ "displayRefreshRatePreferLowest": "کمترین مقدار",
+ "@displayRefreshRatePreferLowest": {},
+ "videoPlaybackMuted": "پخش بی صدا",
+ "@videoPlaybackMuted": {},
+ "storageVolumeDescriptionFallbackPrimary": "حافظه داخلی",
+ "@storageVolumeDescriptionFallbackPrimary": {},
+ "columnCount": "{count, plural, =1{1 ستون} other{{count} ستون}}",
+ "@columnCount": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "mapStyleHuaweiNormal": "پتال مپس",
+ "@mapStyleHuaweiNormal": {},
+ "mapStyleHuaweiTerrain": "پتال مپس (نمایش زمین)",
+ "@mapStyleHuaweiTerrain": {},
+ "mapStyleOsmHot": "اوپناستریتمپ",
+ "@mapStyleOsmHot": {},
+ "subtitlePositionBottom": "پایین",
+ "@subtitlePositionBottom": {},
+ "themeBrightnessLight": "روشن",
+ "@themeBrightnessLight": {},
+ "themeBrightnessDark": "تاریک",
+ "@themeBrightnessDark": {},
+ "themeBrightnessBlack": "سیاه",
+ "@themeBrightnessBlack": {},
+ "videoStartOverButtonLabel": "شروع از اول",
+ "@videoStartOverButtonLabel": {},
+ "albumTierApps": "برنامه ها",
+ "@albumTierApps": {},
+ "nameConflictStrategyRename": "تغییر نام",
+ "@nameConflictStrategyRename": {},
+ "nameConflictStrategyReplace": "جایگزین کردن",
+ "@nameConflictStrategyReplace": {},
+ "displayRefreshRatePreferHighest": "بیشترین مقدار",
+ "@displayRefreshRatePreferHighest": {},
+ "storageAccessDialogMessage": "لطفا فولدر {directory} در {volume} را در صفحه بعد انتخاب کنید و اجازه را به برنامه بدهید.",
+ "@storageAccessDialogMessage": {
+ "placeholders": {
+ "directory": {
+ "type": "String",
+ "description": "the name of a directory, using the output of `rootDirectoryDescription` or `otherDirectoryDescription`"
+ },
+ "volume": {
+ "type": "String",
+ "example": "SD card",
+ "description": "the name of a storage volume"
+ }
+ }
+ },
+ "mapStyleStamenToner": "استامن (نمایش رود ها)",
+ "@mapStyleStamenToner": {}
}
diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb
index 1a08c095a..90c5e9903 100644
--- a/lib/l10n/app_fr.arb
+++ b/lib/l10n/app_fr.arb
@@ -69,7 +69,7 @@
"@chipActionGoToAlbumPage": {},
"chipActionGoToCountryPage": "Afficher dans Pays",
"@chipActionGoToCountryPage": {},
- "chipActionGoToTagPage": "Afficher dans Libellés",
+ "chipActionGoToTagPage": "Afficher dans Étiquettes",
"@chipActionGoToTagPage": {},
"chipActionFilterOut": "Exclure",
"@chipActionFilterOut": {},
@@ -165,7 +165,7 @@
"@entryInfoActionEditTitleDescription": {},
"entryInfoActionEditRating": "Modifier la notation",
"@entryInfoActionEditRating": {},
- "entryInfoActionEditTags": "Modifier les libellés",
+ "entryInfoActionEditTags": "Modifier les étiquettes",
"@entryInfoActionEditTags": {},
"entryInfoActionRemoveMetadata": "Retirer les métadonnées",
"@entryInfoActionRemoveMetadata": {},
@@ -179,7 +179,7 @@
"@filterNoLocationLabel": {},
"filterNoRatingLabel": "Sans notation",
"@filterNoRatingLabel": {},
- "filterNoTagLabel": "Sans libellé",
+ "filterNoTagLabel": "Sans étiquette",
"@filterNoTagLabel": {},
"filterNoTitleLabel": "Sans titre",
"@filterNoTitleLabel": {},
@@ -391,9 +391,9 @@
"@renameProcessorCounter": {},
"renameProcessorName": "Nom",
"@renameProcessorName": {},
- "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Supprimer cet album et son élément ?} other{Supprimer cet album et ses {count} éléments ?}}",
+ "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Supprimer cet album et l’élément dedans ?} other{Supprimer cet album et les {count} éléments dedans ?}}",
"@deleteSingleAlbumConfirmationDialogMessage": {},
- "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Supprimer ces albums et leur élément ?} other{Supprimer ces albums et leurs {count} éléments ?}}",
+ "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Supprimer ces albums et l’élément dedans ?} other{Supprimer ces albums et les {count} éléments dedans ?}}",
"@deleteMultiAlbumConfirmationDialogMessage": {},
"exportEntryDialogFormat": "Format :",
"@exportEntryDialogFormat": {},
@@ -641,7 +641,7 @@
"@drawerAlbumPage": {},
"drawerCountryPage": "Pays",
"@drawerCountryPage": {},
- "drawerTagPage": "Libellés",
+ "drawerTagPage": "Étiquettes",
"@drawerTagPage": {},
"sortByDate": "par date",
"@sortByDate": {},
@@ -713,9 +713,9 @@
"@countryPageTitle": {},
"countryEmpty": "Aucun pays",
"@countryEmpty": {},
- "tagPageTitle": "Libellés",
+ "tagPageTitle": "Étiquettes",
"@tagPageTitle": {},
- "tagEmpty": "Aucun libellé",
+ "tagEmpty": "Aucune étiquette",
"@tagEmpty": {},
"binPageTitle": "Corbeille",
"@binPageTitle": {},
@@ -731,7 +731,7 @@
"@searchCountriesSectionTitle": {},
"searchPlacesSectionTitle": "Lieux",
"@searchPlacesSectionTitle": {},
- "searchTagsSectionTitle": "Libellés",
+ "searchTagsSectionTitle": "Étiquettes",
"@searchTagsSectionTitle": {},
"searchRatingSectionTitle": "Notations",
"@searchRatingSectionTitle": {},
@@ -811,7 +811,7 @@
"@settingsThumbnailOverlayPageTitle": {},
"settingsThumbnailShowFavouriteIcon": "Afficher l’icône de favori",
"@settingsThumbnailShowFavouriteIcon": {},
- "settingsThumbnailShowTagIcon": "Afficher l’icône de libellé",
+ "settingsThumbnailShowTagIcon": "Afficher l’icône d’étiquette",
"@settingsThumbnailShowTagIcon": {},
"settingsThumbnailShowLocationIcon": "Afficher l’icône de lieu",
"@settingsThumbnailShowLocationIcon": {},
@@ -1043,7 +1043,7 @@
"@statsTopCountriesSectionTitle": {},
"statsTopPlacesSectionTitle": "Top lieux",
"@statsTopPlacesSectionTitle": {},
- "statsTopTagsSectionTitle": "Top libellés",
+ "statsTopTagsSectionTitle": "Top étiquettes",
"@statsTopTagsSectionTitle": {},
"statsTopAlbumsSectionTitle": "Top albums",
"@statsTopAlbumsSectionTitle": {},
@@ -1123,11 +1123,11 @@
"@viewerInfoSearchSuggestionRights": {},
"wallpaperUseScrollEffect": "Utiliser l’effet de défilement sur l’écran d’accueil",
"@wallpaperUseScrollEffect": {},
- "tagEditorPageTitle": "Modifier les libellés",
+ "tagEditorPageTitle": "Modifier les étiquettes",
"@tagEditorPageTitle": {},
- "tagEditorPageNewTagFieldLabel": "Nouveau libellé",
+ "tagEditorPageNewTagFieldLabel": "Nouvelle étiquette",
"@tagEditorPageNewTagFieldLabel": {},
- "tagEditorPageAddTagTooltip": "Ajouter le libellé",
+ "tagEditorPageAddTagTooltip": "Ajouter l’étiquette",
"@tagEditorPageAddTagTooltip": {},
"tagEditorSectionRecent": "Ajouts récents",
"@tagEditorSectionRecent": {},
@@ -1149,7 +1149,7 @@
"@filePickerUseThisFolder": {},
"editEntryLocationDialogSetCustom": "Définir un lieu personnalisé",
"@editEntryLocationDialogSetCustom": {},
- "tagEditorSectionPlaceholders": "Libellés de substitution",
+ "tagEditorSectionPlaceholders": "Étiquettes de substitution",
"@tagEditorSectionPlaceholders": {},
"tagPlaceholderPlace": "Lieu",
"@tagPlaceholderPlace": {},
@@ -1179,7 +1179,7 @@
"@filterAspectRatioPortraitLabel": {},
"filterAspectRatioLandscapeLabel": "Paysage",
"@filterAspectRatioLandscapeLabel": {},
- "settingsViewerShowRatingTags": "Afficher la notation et les libellés",
+ "settingsViewerShowRatingTags": "Afficher la notation et les étiquettes",
"@settingsViewerShowRatingTags": {},
"entryActionShareImageOnly": "Partager l’image seulement",
"@entryActionShareImageOnly": {},
@@ -1196,5 +1196,17 @@
}
},
"settingsAccessibilityShowPinchGestureAlternatives": "Afficher des alternatives aux interactions multitactiles",
- "@settingsAccessibilityShowPinchGestureAlternatives": {}
+ "@settingsAccessibilityShowPinchGestureAlternatives": {},
+ "settingsViewerShowDescription": "Afficher la description",
+ "@settingsViewerShowDescription": {},
+ "settingsModificationWarningDialogMessage": "D’autres réglages seront modifiés.",
+ "@settingsModificationWarningDialogMessage": {},
+ "settingsDisplayUseTvInterface": "Interface Android TV",
+ "@settingsDisplayUseTvInterface": {},
+ "filterTaggedLabel": "Étiqueté",
+ "@filterTaggedLabel": {},
+ "filterLocatedLabel": "Localisé",
+ "@filterLocatedLabel": {},
+ "tooManyItemsErrorDialogMessage": "Réessayez avec moins d’éléments.",
+ "@tooManyItemsErrorDialogMessage": {}
}
diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb
index da533b164..9b1209d1e 100644
--- a/lib/l10n/app_id.arb
+++ b/lib/l10n/app_id.arb
@@ -337,7 +337,7 @@
"@binEntriesConfirmationDialogMessage": {},
"deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Anda yakin ingin menghapus benda ini?} other{Apakah Anda yakin ingin menghapus {count} benda?}}",
"@deleteEntriesConfirmationDialogMessage": {},
- "moveUndatedConfirmationDialogMessage": "Beberapa benda tidak mempunyai tanggal metadata. Tanggal mereka sekarang akan diatur ulang dengan operasi ini kecuali ada tanggal metadata yang ditetapkan.",
+ "moveUndatedConfirmationDialogMessage": "Simpan tanggal benda sebelum melanjutkan?",
"@moveUndatedConfirmationDialogMessage": {},
"moveUndatedConfirmationDialogSetDate": "Atur tanggal",
"@moveUndatedConfirmationDialogSetDate": {},
@@ -379,9 +379,9 @@
"@renameProcessorCounter": {},
"renameProcessorName": "Nama",
"@renameProcessorName": {},
- "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Apakah Anda yakin ingin menghapus album ini dan bendanya?} other{Apakah Anda yakin ingin menghapus album ini dan {count} bendanya?}}",
+ "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Hapus album ini dan item yang ada di dalam?} other{Hapus album ini dan {count} item yang ada di dalam?}}",
"@deleteSingleAlbumConfirmationDialogMessage": {},
- "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Apakah Anda yakin ingin menghapus album ini dan bendanya?} other{Apakah Anda yakin ingin menghapus album ini dan {count} bendanya?}}",
+ "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Hapus album ini dan item yang ada di dalam?} other{Hapus album ini dan {count} item yang ada di dalam?}}",
"@deleteMultiAlbumConfirmationDialogMessage": {},
"exportEntryDialogFormat": "Format:",
"@exportEntryDialogFormat": {},
@@ -867,7 +867,7 @@
"@settingsSlideshowFillScreen": {},
"settingsSlideshowTransitionTile": "Transisi",
"@settingsSlideshowTransitionTile": {},
- "settingsSlideshowIntervalTile": "Interval",
+ "settingsSlideshowIntervalTile": "Jarak waktu",
"@settingsSlideshowIntervalTile": {},
"settingsSlideshowVideoPlaybackTile": "Putaran ulang video",
"@settingsSlideshowVideoPlaybackTile": {},
@@ -1157,7 +1157,7 @@
"@tagPlaceholderPlace": {},
"editEntryLocationDialogSetCustom": "Terapkan lokasi kustom",
"@editEntryLocationDialogSetCustom": {},
- "subtitlePositionTop": "Atas",
+ "subtitlePositionTop": "Teratas",
"@subtitlePositionTop": {},
"subtitlePositionBottom": "Bawah",
"@subtitlePositionBottom": {},
@@ -1196,5 +1196,17 @@
}
},
"settingsAccessibilityShowPinchGestureAlternatives": "Tampilkan alternatif gestur multisentuh",
- "@settingsAccessibilityShowPinchGestureAlternatives": {}
+ "@settingsAccessibilityShowPinchGestureAlternatives": {},
+ "settingsViewerShowDescription": "Tampilkan deskripsi",
+ "@settingsViewerShowDescription": {},
+ "settingsModificationWarningDialogMessage": "Pengaturan lain akan diubah.",
+ "@settingsModificationWarningDialogMessage": {},
+ "settingsDisplayUseTvInterface": "Antarmuka Android TV",
+ "@settingsDisplayUseTvInterface": {},
+ "filterLocatedLabel": "Terletak",
+ "@filterLocatedLabel": {},
+ "filterTaggedLabel": "Dilabel",
+ "@filterTaggedLabel": {},
+ "tooManyItemsErrorDialogMessage": "Coba lagi dengan item yang lebih sedikit.",
+ "@tooManyItemsErrorDialogMessage": {}
}
diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb
index eb05a73f9..a9577f7bc 100644
--- a/lib/l10n/app_it.arb
+++ b/lib/l10n/app_it.arb
@@ -389,9 +389,9 @@
"@renameProcessorCounter": {},
"renameProcessorName": "Nome",
"@renameProcessorName": {},
- "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Cancellare questo album e i suoi elementi?} other{Cancellare questo album e i suoi {count} elementi?}}",
+ "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Cancellare questo album e l’elemento in esso?} other{Cancellare questo album e i {count} elementi in esso?}}",
"@deleteSingleAlbumConfirmationDialogMessage": {},
- "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Cancellare questi album e i loro elementi?} other{Cancellare questi album e i loro {count} elementi?}}",
+ "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Cancellare questi album e l’elemento in essi?} other{Cancellare questi album e i {count} elementi in essi?}}",
"@deleteMultiAlbumConfirmationDialogMessage": {},
"exportEntryDialogFormat": "Formato:",
"@exportEntryDialogFormat": {},
@@ -1196,5 +1196,17 @@
"entryActionShareVideoOnly": "Condividi solo video",
"@entryActionShareVideoOnly": {},
"filterNoAddressLabel": "Senza indirizzo",
- "@filterNoAddressLabel": {}
+ "@filterNoAddressLabel": {},
+ "filterLocatedLabel": "Posizionato",
+ "@filterLocatedLabel": {},
+ "filterTaggedLabel": "Etichettato",
+ "@filterTaggedLabel": {},
+ "settingsModificationWarningDialogMessage": "Le altre impostazioni saranno modificate.",
+ "@settingsModificationWarningDialogMessage": {},
+ "settingsDisplayUseTvInterface": "Interfaccia Android TV",
+ "@settingsDisplayUseTvInterface": {},
+ "settingsViewerShowDescription": "Mostra la descrizione",
+ "@settingsViewerShowDescription": {},
+ "tooManyItemsErrorDialogMessage": "Riprova con meno elementi.",
+ "@tooManyItemsErrorDialogMessage": {}
}
diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb
index 708959f76..5a62c46eb 100644
--- a/lib/l10n/app_ko.arb
+++ b/lib/l10n/app_ko.arb
@@ -175,7 +175,7 @@
"@filterFavouriteLabel": {},
"filterNoDateLabel": "날짜 없음",
"@filterNoDateLabel": {},
- "filterNoLocationLabel": "장소 없음",
+ "filterNoLocationLabel": "위치 없음",
"@filterNoLocationLabel": {},
"filterNoRatingLabel": "별점 없음",
"@filterNoRatingLabel": {},
@@ -1196,5 +1196,17 @@
"placeholders": {
"count": {}
}
- }
+ },
+ "settingsViewerShowDescription": "설명 표시",
+ "@settingsViewerShowDescription": {},
+ "settingsModificationWarningDialogMessage": "다른 설정도 변경될 것입니다.",
+ "@settingsModificationWarningDialogMessage": {},
+ "settingsDisplayUseTvInterface": "안드로이드 TV 인터페이스 사용하기",
+ "@settingsDisplayUseTvInterface": {},
+ "filterTaggedLabel": "태그 있음",
+ "@filterTaggedLabel": {},
+ "filterLocatedLabel": "위치 있음",
+ "@filterLocatedLabel": {},
+ "tooManyItemsErrorDialogMessage": "항목 수를 줄이고 다시 시도하세요.",
+ "@tooManyItemsErrorDialogMessage": {}
}
diff --git a/lib/l10n/app_nn.arb b/lib/l10n/app_nn.arb
index b838058b6..d4bd4a9a3 100644
--- a/lib/l10n/app_nn.arb
+++ b/lib/l10n/app_nn.arb
@@ -264,7 +264,7 @@
"@keepScreenOnViewerOnly": {},
"keepScreenOnAlways": "Heile tida",
"@keepScreenOnAlways": {},
- "accessibilityAnimationsRemove": "Hindra rørsle",
+ "accessibilityAnimationsRemove": "Hindra skjermrørsle",
"@accessibilityAnimationsRemove": {},
"subtitlePositionTop": "På toppen",
"@subtitlePositionTop": {},
@@ -352,5 +352,403 @@
"description": "the name of a specific directory"
}
}
- }
+ },
+ "keepScreenOnVideoPlayback": "Under videoavspeling",
+ "@keepScreenOnVideoPlayback": {},
+ "newAlbumDialogNameLabel": "Albumsnamn",
+ "@newAlbumDialogNameLabel": {},
+ "durationDialogMinutes": "Minutt",
+ "@durationDialogMinutes": {},
+ "settingsThemeColorHighlights": "Farga framhevjingar",
+ "@settingsThemeColorHighlights": {},
+ "viewerInfoBackToViewerTooltip": "Attende til vising",
+ "@viewerInfoBackToViewerTooltip": {},
+ "mapStyleDialogTitle": "Kartstil",
+ "@mapStyleDialogTitle": {},
+ "notEnoughSpaceDialogMessage": "Denne gjerda tarv {neededSize} unytta rom på «{volume}» for å verta fullgjord, men det er berre {freeSize} att.",
+ "@notEnoughSpaceDialogMessage": {
+ "placeholders": {
+ "neededSize": {
+ "type": "String",
+ "example": "314 MB"
+ },
+ "freeSize": {
+ "type": "String",
+ "example": "123 MB"
+ },
+ "volume": {
+ "type": "String",
+ "example": "SD card",
+ "description": "the name of a storage volume"
+ }
+ }
+ },
+ "missingSystemFilePickerDialogMessage": "Systemfilveljaren er borte eller avslegen. Slå han på og røyn om att.",
+ "@missingSystemFilePickerDialogMessage": {},
+ "videoResumeDialogMessage": "Hald fram avspeling ifrå {time}?",
+ "@videoResumeDialogMessage": {
+ "placeholders": {
+ "time": {
+ "type": "String",
+ "example": "13:37"
+ }
+ }
+ },
+ "videoStartOverButtonLabel": "BYRJA OM ATT",
+ "@videoStartOverButtonLabel": {},
+ "hideFilterConfirmationDialogMessage": "Samsvarande bilete og videoar vil verte skjult ifrå samlinga di. Du kan visa dei att ifrå «Personvern»-innstillingane.\n\nEr du sikker på at du vil skjule dei?",
+ "@hideFilterConfirmationDialogMessage": {},
+ "renameEntrySetPageInsertTooltip": "Innskrivingsområde",
+ "@renameEntrySetPageInsertTooltip": {},
+ "renameEntrySetPagePatternFieldLabel": "Namngjevingsmønster",
+ "@renameEntrySetPagePatternFieldLabel": {},
+ "renameEntrySetPagePreviewSectionTitle": "Førehandsvis",
+ "@renameEntrySetPagePreviewSectionTitle": {},
+ "renameEntryDialogLabel": "Nytt namn",
+ "@renameEntryDialogLabel": {},
+ "editEntryDialogCopyFromItem": "Kopier ifrå anna element",
+ "@editEntryDialogCopyFromItem": {},
+ "editEntryDateDialogSourceFileModifiedDate": "Filbrigdedato",
+ "@editEntryDateDialogSourceFileModifiedDate": {},
+ "durationDialogHours": "Timar",
+ "@durationDialogHours": {},
+ "editEntryLocationDialogChooseOnMap": "Vel på kartet",
+ "@editEntryLocationDialogChooseOnMap": {},
+ "settingsLanguageTile": "Mål",
+ "@settingsLanguageTile": {},
+ "settingsUnitSystemTile": "Einingar",
+ "@settingsUnitSystemTile": {},
+ "settingsCoordinateFormatDialogTitle": "Koordinatformat",
+ "@settingsCoordinateFormatDialogTitle": {},
+ "settingsWidgetDisplayedItem": "Vist element",
+ "@settingsWidgetDisplayedItem": {},
+ "statsTopAlbumsSectionTitle": "Topp-album",
+ "@statsTopAlbumsSectionTitle": {},
+ "statsTopTagsSectionTitle": "Toppmerkelappar",
+ "@statsTopTagsSectionTitle": {},
+ "viewerInfoUnknown": "ukjend",
+ "@viewerInfoUnknown": {},
+ "viewerInfoLabelResolution": "Oppløysing",
+ "@viewerInfoLabelResolution": {},
+ "viewerInfoLabelUri": "URI",
+ "@viewerInfoLabelUri": {},
+ "viewerInfoLabelOwner": "Eigar",
+ "@viewerInfoLabelOwner": {},
+ "viewerInfoLabelCoordinates": "Koordinatar",
+ "@viewerInfoLabelCoordinates": {},
+ "tagEditorPageAddTagTooltip": "Legg til merkelapp",
+ "@tagEditorPageAddTagTooltip": {},
+ "filePickerDoNotShowHiddenFiles": "Ikkje vis skjulte filer",
+ "@filePickerDoNotShowHiddenFiles": {},
+ "panoramaEnableSensorControl": "Slå på sensorstyring",
+ "@panoramaEnableSensorControl": {},
+ "panoramaDisableSensorControl": "Slå av sensorstyring",
+ "@panoramaDisableSensorControl": {},
+ "filePickerOpenFrom": "Opne ifrå",
+ "@filePickerOpenFrom": {},
+ "filePickerNoItems": "Ingen element",
+ "@filePickerNoItems": {},
+ "nameConflictDialogSingleSourceMessage": "Somme filer i målmappa har same namn.",
+ "@nameConflictDialogSingleSourceMessage": {},
+ "nameConflictDialogMultipleSourceMessage": "Somme filer har same namn.",
+ "@nameConflictDialogMultipleSourceMessage": {},
+ "binEntriesConfirmationDialogMessage": "{count, plural, =1{Flytt dette elementet til papirkorga?} other{Flytt desse {count} elementa til papirkorga?}}",
+ "@binEntriesConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Slett dette elementet?} other{Slett desse {count} elementa?}}",
+ "@deleteEntriesConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "videoResumeButtonLabel": "HALD FRAM",
+ "@videoResumeButtonLabel": {},
+ "entryActionShareVideoOnly": "Del berre video",
+ "@entryActionShareVideoOnly": {},
+ "entryActionShareImageOnly": "Del berre bilete",
+ "@entryActionShareImageOnly": {},
+ "unsupportedTypeDialogMessage": "{count, plural, other{Denne gjerda er ustødd for element av fylgjande slag: {types}.}}",
+ "@unsupportedTypeDialogMessage": {
+ "placeholders": {
+ "count": {},
+ "types": {
+ "type": "String",
+ "example": "GIF, TIFF, MP4",
+ "description": "a list of unsupported types"
+ }
+ }
+ },
+ "addShortcutDialogLabel": "Snarvegsmerkelapp",
+ "@addShortcutDialogLabel": {},
+ "addShortcutButtonLabel": "LEGG TIL",
+ "@addShortcutButtonLabel": {},
+ "noMatchingAppDialogMessage": "Ingen appar kan handsame dette.",
+ "@noMatchingAppDialogMessage": {},
+ "moveUndatedConfirmationDialogMessage": "Gøym elementdatoar før framhald?",
+ "@moveUndatedConfirmationDialogMessage": {},
+ "moveUndatedConfirmationDialogSetDate": "Gøym datoar",
+ "@moveUndatedConfirmationDialogSetDate": {},
+ "setCoverDialogLatest": "Nyaste element",
+ "@setCoverDialogLatest": {},
+ "setCoverDialogAuto": "Auto",
+ "@setCoverDialogAuto": {},
+ "newAlbumDialogTitle": "Nytt Album",
+ "@newAlbumDialogTitle": {},
+ "newAlbumDialogNameLabelAlreadyExistsHelper": "Mappa finst alt",
+ "@newAlbumDialogNameLabelAlreadyExistsHelper": {},
+ "newAlbumDialogStorageLabel": "Gøyme:",
+ "@newAlbumDialogStorageLabel": {},
+ "renameAlbumDialogLabel": "Nytt namn",
+ "@renameAlbumDialogLabel": {},
+ "renameAlbumDialogLabelAlreadyExistsHelper": "Mappa finst alt",
+ "@renameAlbumDialogLabelAlreadyExistsHelper": {},
+ "renameEntrySetPageTitle": "Døyp om",
+ "@renameEntrySetPageTitle": {},
+ "exportEntryDialogWidth": "Breidd",
+ "@exportEntryDialogWidth": {},
+ "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Slett desse albuma og deira element?} other{Slett desse albuma og deira {count} element?}}",
+ "@deleteMultiAlbumConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "exportEntryDialogHeight": "Høgd",
+ "@exportEntryDialogHeight": {},
+ "editEntryDateDialogExtractFromTitle": "Tak ut ifrå namn",
+ "@editEntryDateDialogExtractFromTitle": {},
+ "editEntryDateDialogCopyField": "Kopier ifrå annan dato",
+ "@editEntryDateDialogCopyField": {},
+ "editEntryDateDialogShift": "Byt",
+ "@editEntryDateDialogShift": {},
+ "durationDialogSeconds": "Sekund",
+ "@durationDialogSeconds": {},
+ "editEntryRatingDialogTitle": "Omdøme",
+ "@editEntryRatingDialogTitle": {},
+ "removeEntryMetadataDialogTitle": "Metadataborttaking",
+ "@removeEntryMetadataDialogTitle": {},
+ "removeEntryMetadataDialogMore": "Meir",
+ "@removeEntryMetadataDialogMore": {},
+ "statsTopPlacesSectionTitle": "Toppstadar",
+ "@statsTopPlacesSectionTitle": {},
+ "settingsCollectionTile": "Samling",
+ "@settingsCollectionTile": {},
+ "statsPageTitle": "Samandrag",
+ "@statsPageTitle": {},
+ "statsTopCountriesSectionTitle": "Toppland",
+ "@statsTopCountriesSectionTitle": {},
+ "viewerOpenPanoramaButtonLabel": "OPNE PANORAMA",
+ "@viewerOpenPanoramaButtonLabel": {},
+ "viewerInfoLabelSize": "Storleik",
+ "@viewerInfoLabelSize": {},
+ "viewerInfoLabelDate": "Dato",
+ "@viewerInfoLabelDate": {},
+ "viewerInfoLabelDuration": "Lengd",
+ "@viewerInfoLabelDuration": {},
+ "viewerInfoLabelPath": "Sti",
+ "@viewerInfoLabelPath": {},
+ "mapZoomOutTooltip": "Mink",
+ "@mapZoomOutTooltip": {},
+ "mapZoomInTooltip": "Auk",
+ "@mapZoomInTooltip": {},
+ "openMapPageTooltip": "Vis på kartsida",
+ "@openMapPageTooltip": {},
+ "viewerInfoViewXmlLinkText": "Vis XML",
+ "@viewerInfoViewXmlLinkText": {},
+ "viewerInfoSearchFieldLabel": "Søk metadata",
+ "@viewerInfoSearchFieldLabel": {},
+ "viewerInfoSearchEmpty": "Ingen samsvarande lyklar",
+ "@viewerInfoSearchEmpty": {},
+ "viewerInfoSearchSuggestionDate": "Dato og tid",
+ "@viewerInfoSearchSuggestionDate": {},
+ "tagEditorPageTitle": "Brigd merkelappar",
+ "@tagEditorPageTitle": {},
+ "tagEditorPageNewTagFieldLabel": "Ny merkelapp",
+ "@tagEditorPageNewTagFieldLabel": {},
+ "filePickerShowHiddenFiles": "Vis skjulte filer",
+ "@filePickerShowHiddenFiles": {},
+ "sourceViewerPageTitle": "Kjelde",
+ "@sourceViewerPageTitle": {},
+ "renameProcessorCounter": "Teljar",
+ "@renameProcessorCounter": {},
+ "renameProcessorName": "Namn",
+ "@renameProcessorName": {},
+ "editEntryDateDialogTitle": "Dato og tid",
+ "@editEntryDateDialogTitle": {},
+ "editEntryLocationDialogLatitude": "Breiddegrad",
+ "@editEntryLocationDialogLatitude": {},
+ "editEntryLocationDialogLongitude": "Lengdegrad",
+ "@editEntryLocationDialogLongitude": {},
+ "sourceStateLoading": "Hentar inn",
+ "@sourceStateLoading": {},
+ "filePickerUseThisFolder": "Bruk denne mappa",
+ "@filePickerUseThisFolder": {},
+ "viewerErrorDoesNotExist": "Fila finst ikkje meir.",
+ "@viewerErrorDoesNotExist": {},
+ "filterBinLabel": "Papirkorg",
+ "@filterBinLabel": {},
+ "filterTypeAnimatedLabel": "Animert",
+ "@filterTypeAnimatedLabel": {},
+ "filterTypeMotionPhotoLabel": "Rørslebilete",
+ "@filterTypeMotionPhotoLabel": {},
+ "filterTypePanoramaLabel": "Panorama",
+ "@filterTypePanoramaLabel": {},
+ "mapStyleOsmHot": "Humanitært OSM",
+ "@mapStyleOsmHot": {},
+ "mapStyleStamenToner": "Stamen Toner (svart-kvitt)",
+ "@mapStyleStamenToner": {},
+ "themeBrightnessLight": "Ljos",
+ "@themeBrightnessLight": {},
+ "themeBrightnessDark": "Mørk",
+ "@themeBrightnessDark": {},
+ "themeBrightnessBlack": "Svart",
+ "@themeBrightnessBlack": {},
+ "viewerTransitionSlide": "Skridande",
+ "@viewerTransitionSlide": {},
+ "viewerTransitionParallax": "Parallakse",
+ "@viewerTransitionParallax": {},
+ "viewerTransitionFade": "Ton ut",
+ "@viewerTransitionFade": {},
+ "widgetDisplayedItemRandom": "Tilfeldig",
+ "@widgetDisplayedItemRandom": {},
+ "widgetOpenPageHome": "Opne heimside",
+ "@widgetOpenPageHome": {},
+ "restrictedAccessDialogMessage": "Denne appen har ikkje lov til å brigde filer i «{directory}»-mappa i «{volume}».\n\nBruk ein førehandsinnlagd filhandsamar eller galleriapp til å flytta elementa til ei anna mappe.",
+ "@restrictedAccessDialogMessage": {
+ "placeholders": {
+ "directory": {
+ "type": "String",
+ "description": "the name of a directory, using the output of `rootDirectoryDescription` or `otherDirectoryDescription`"
+ },
+ "volume": {
+ "type": "String",
+ "example": "SD card",
+ "description": "the name of a storage volume"
+ }
+ }
+ },
+ "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Slett dette albumet og elementet i det?} other{Slett dette albumet og dei {count} elementa i det?}}",
+ "@deleteSingleAlbumConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "exportEntryDialogFormat": "Format:",
+ "@exportEntryDialogFormat": {},
+ "tagPlaceholderCountry": "Land",
+ "@tagPlaceholderCountry": {},
+ "tagEditorSectionRecent": "Nyleg",
+ "@tagEditorSectionRecent": {},
+ "tagPlaceholderPlace": "Stad",
+ "@tagPlaceholderPlace": {},
+ "viewerInfoSearchSuggestionRights": "Rettar",
+ "@viewerInfoSearchSuggestionRights": {},
+ "viewerInfoSearchSuggestionResolution": "Oppløysing",
+ "@viewerInfoSearchSuggestionResolution": {},
+ "viewerInfoOpenLinkText": "Opne",
+ "@viewerInfoOpenLinkText": {},
+ "mapStyleTooltip": "Vel kartstil",
+ "@mapStyleTooltip": {},
+ "viewerInfoLabelAddress": "Adresse",
+ "@viewerInfoLabelAddress": {},
+ "viewerInfoLabelTitle": "Namn",
+ "@viewerInfoLabelTitle": {},
+ "viewerErrorUnknown": "Oida.",
+ "@viewerErrorUnknown": {},
+ "viewerSetWallpaperButtonLabel": "SET SOM BAKGRUNNSBILETE",
+ "@viewerSetWallpaperButtonLabel": {},
+ "settingsWidgetShowOutline": "Omrit",
+ "@settingsWidgetShowOutline": {},
+ "settingsWidgetPageTitle": "Bileteramme",
+ "@settingsWidgetPageTitle": {},
+ "settingsScreenSaverPageTitle": "Skjermsparar",
+ "@settingsScreenSaverPageTitle": {},
+ "settingsUnitSystemDialogTitle": "Einingar",
+ "@settingsUnitSystemDialogTitle": {},
+ "settingsCoordinateFormatTile": "Koordinatformat",
+ "@settingsCoordinateFormatTile": {},
+ "settingsLanguagePageTitle": "Mål",
+ "@settingsLanguagePageTitle": {},
+ "settingsLanguageSectionTitle": "Mål og format",
+ "@settingsLanguageSectionTitle": {},
+ "settingsDisplaySectionTitle": "Vising",
+ "@settingsDisplaySectionTitle": {},
+ "videoStreamSelectionDialogTrack": "Spor",
+ "@videoStreamSelectionDialogTrack": {},
+ "genericDangerWarningDialogMessage": "Er du viss?",
+ "@genericDangerWarningDialogMessage": {},
+ "menuActionStats": "Samandrag",
+ "@menuActionStats": {},
+ "viewDialogGroupSectionTitle": "Hop",
+ "@viewDialogGroupSectionTitle": {},
+ "viewDialogLayoutSectionTitle": "Oppsett",
+ "@viewDialogLayoutSectionTitle": {},
+ "tileLayoutMosaic": "Mosaikk",
+ "@tileLayoutMosaic": {},
+ "aboutBugCopyInfoButton": "Kopier",
+ "@aboutBugCopyInfoButton": {},
+ "aboutCreditsWorldAtlas1": "Denne appen nyttar ei TopoJSON-fil ifrå",
+ "@aboutCreditsWorldAtlas1": {},
+ "viewerInfoSearchSuggestionDescription": "Utgreiing",
+ "@viewerInfoSearchSuggestionDescription": {},
+ "aboutCreditsWorldAtlas2": "under ISC-løyve.",
+ "@aboutCreditsWorldAtlas2": {},
+ "videoSpeedDialogLabel": "Avspelingssnøggleik",
+ "@videoSpeedDialogLabel": {},
+ "videoStreamSelectionDialogVideo": "Video",
+ "@videoStreamSelectionDialogVideo": {},
+ "videoStreamSelectionDialogAudio": "Ljod",
+ "@videoStreamSelectionDialogAudio": {},
+ "videoStreamSelectionDialogText": "Undertekster",
+ "@videoStreamSelectionDialogText": {},
+ "videoStreamSelectionDialogOff": "Av",
+ "@videoStreamSelectionDialogOff": {},
+ "videoStreamSelectionDialogNoSelection": "Det er ingen andre spor.",
+ "@videoStreamSelectionDialogNoSelection": {},
+ "genericSuccessFeedback": "Fullgjort",
+ "@genericSuccessFeedback": {},
+ "genericFailureFeedback": "Mislykka",
+ "@genericFailureFeedback": {},
+ "menuActionSelectAll": "Vel alle",
+ "@menuActionSelectAll": {},
+ "menuActionSelectNone": "Tak bort val",
+ "@menuActionSelectNone": {},
+ "menuActionMap": "Kart",
+ "@menuActionMap": {},
+ "menuActionSlideshow": "Ljosbiletevising",
+ "@menuActionSlideshow": {},
+ "menuActionConfigureView": "Vis",
+ "@menuActionConfigureView": {},
+ "menuActionSelect": "Vel",
+ "@menuActionSelect": {},
+ "aboutBugCopyInfoInstruction": "Kopier systemopplysingar",
+ "@aboutBugCopyInfoInstruction": {},
+ "tagEditorSectionPlaceholders": "Førebels",
+ "@tagEditorSectionPlaceholders": {},
+ "tileLayoutGrid": "Rutenett",
+ "@tileLayoutGrid": {},
+ "tileLayoutList": "Liste",
+ "@tileLayoutList": {},
+ "coverDialogTabCover": "Omslag",
+ "@coverDialogTabCover": {},
+ "coverDialogTabApp": "App",
+ "@coverDialogTabApp": {},
+ "coverDialogTabColor": "Let",
+ "@coverDialogTabColor": {},
+ "appPickDialogTitle": "Vel app",
+ "@appPickDialogTitle": {},
+ "aboutPageTitle": "Om",
+ "@aboutPageTitle": {},
+ "aboutLinkLicense": "Løyve",
+ "@aboutLinkLicense": {},
+ "appPickDialogNone": "Ingen",
+ "@appPickDialogNone": {},
+ "aboutBugSectionTitle": "Mistakrapport",
+ "@aboutBugSectionTitle": {},
+ "aboutTranslatorsSectionTitle": "Omsetjarar",
+ "@aboutTranslatorsSectionTitle": {},
+ "viewerInfoOpenEmbeddedFailureFeedback": "Kunne ikkje ta ut innbygde opplysingar",
+ "@viewerInfoOpenEmbeddedFailureFeedback": {}
}
diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb
index b8d2e39c1..d8bc6fbe4 100644
--- a/lib/l10n/app_pl.arb
+++ b/lib/l10n/app_pl.arb
@@ -13,7 +13,7 @@
"@resetTooltip": {},
"pickTooltip": "Wybierz",
"@pickTooltip": {},
- "doubleBackExitMessage": "Tapnij ponownie \"wstecz\" aby wyjść.",
+ "doubleBackExitMessage": "Tapnij ponownie „wstecz” aby wyjść.",
"@doubleBackExitMessage": {},
"saveTooltip": "Zapisz",
"@saveTooltip": {},
@@ -184,5 +184,1185 @@
"entryActionInfo": "Informacje",
"@entryActionInfo": {},
"entryActionRestore": "Przywróć",
- "@entryActionRestore": {}
+ "@entryActionRestore": {},
+ "subtitlePositionTop": "Na górze",
+ "@subtitlePositionTop": {},
+ "wallpaperTargetHome": "Ekran główny",
+ "@wallpaperTargetHome": {},
+ "nameConflictStrategySkip": "Pomiń",
+ "@nameConflictStrategySkip": {},
+ "videoLoopModeAlways": "Zawsze",
+ "@videoLoopModeAlways": {},
+ "filterLocatedLabel": "Usytuowany",
+ "@filterLocatedLabel": {},
+ "filterTaggedLabel": "Oznaczony",
+ "@filterTaggedLabel": {},
+ "nameConflictStrategyReplace": "Zastąp",
+ "@nameConflictStrategyReplace": {},
+ "subtitlePositionBottom": "Na dole",
+ "@subtitlePositionBottom": {},
+ "wallpaperTargetLock": "Ekran blokady",
+ "@wallpaperTargetLock": {},
+ "videoPlaybackSkip": "Pomiń",
+ "@videoPlaybackSkip": {},
+ "viewerTransitionNone": "Brak",
+ "@viewerTransitionNone": {},
+ "filterAspectRatioPortraitLabel": "Portret",
+ "@filterAspectRatioPortraitLabel": {},
+ "filterNoAddressLabel": "Brak adresu",
+ "@filterNoAddressLabel": {},
+ "videoControlsPlaySeek": "Odtwórz i szukaj do przodu/do tyłu",
+ "@videoControlsPlaySeek": {},
+ "videoControlsPlayOutside": "Otwórz w innym odtwarzaczu",
+ "@videoControlsPlayOutside": {},
+ "mapStyleGoogleNormal": "Mapy Google",
+ "@mapStyleGoogleNormal": {},
+ "videoLoopModeShortOnly": "Tylko krótkie wideo",
+ "@videoLoopModeShortOnly": {},
+ "mapStyleHuaweiTerrain": "Mapy Petal (teren)",
+ "@mapStyleHuaweiTerrain": {},
+ "mapStyleStamenToner": "Stamen Toner (czarno-białe)",
+ "@mapStyleStamenToner": {},
+ "mapStyleStamenWatercolor": "Stamen Watercolor (kolory wody)",
+ "@mapStyleStamenWatercolor": {},
+ "nameConflictStrategyRename": "Zmień nazwę",
+ "@nameConflictStrategyRename": {},
+ "mapStyleOsmHot": "Humanitarny OSM",
+ "@mapStyleOsmHot": {},
+ "keepScreenOnVideoPlayback": "Podczas odtwarzania wideo",
+ "@keepScreenOnVideoPlayback": {},
+ "displayRefreshRatePreferLowest": "Najniższa",
+ "@displayRefreshRatePreferLowest": {},
+ "videoPlaybackMuted": "Odtwarzaj bez dźwięku",
+ "@videoPlaybackMuted": {},
+ "itemCount": "{count, plural, =1{1 element} =2..4{{count} elementy} other{{count} elelmentów}}",
+ "@itemCount": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "columnCount": "{count, plural, =1{1 rząd} =2..4{{count} rzędy} other{{count} rzędów}}",
+ "@columnCount": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "timeSeconds": "{seconds, plural, =1{1 sekunda} =2..4{{seconds} sekundy} other{{seconds} sekund}}",
+ "@timeSeconds": {
+ "placeholders": {
+ "seconds": {}
+ }
+ },
+ "timeMinutes": "{minutes, plural, =1{1 minuta} =2..4{{minutes} minuty} other{{minutes} minut}}",
+ "@timeMinutes": {
+ "placeholders": {
+ "minutes": {}
+ }
+ },
+ "timeDays": "{days, plural, =1{1 dzień} =2..4{{days} dni} other{{days} dni}}",
+ "@timeDays": {
+ "placeholders": {
+ "days": {}
+ }
+ },
+ "focalLength": "{length} mm",
+ "@focalLength": {
+ "placeholders": {
+ "length": {
+ "type": "String",
+ "example": "5.4"
+ }
+ }
+ },
+ "filterTypeRawLabel": "Raw",
+ "@filterTypeRawLabel": {},
+ "filterTypeSphericalVideoLabel": "Wideo 360°",
+ "@filterTypeSphericalVideoLabel": {},
+ "coordinateFormatDecimal": "Stopnie z miejscami dziesiętnymi",
+ "@coordinateFormatDecimal": {},
+ "coordinateDmsSouth": "Pd",
+ "@coordinateDmsSouth": {},
+ "coordinateDms": "{coordinate} {direction}",
+ "@coordinateDms": {
+ "placeholders": {
+ "coordinate": {
+ "type": "String",
+ "example": "38° 41′ 47.72″"
+ },
+ "direction": {
+ "type": "String",
+ "example": "S"
+ }
+ }
+ },
+ "coordinateDmsNorth": "Pn",
+ "@coordinateDmsNorth": {},
+ "mapStyleGoogleHybrid": "Mapy Google (hybrydowe)",
+ "@mapStyleGoogleHybrid": {},
+ "mapStyleGoogleTerrain": "Mapy Google (teren)",
+ "@mapStyleGoogleTerrain": {},
+ "mapStyleHuaweiNormal": "Mapy Petal",
+ "@mapStyleHuaweiNormal": {},
+ "keepScreenOnAlways": "Zawsze",
+ "@keepScreenOnAlways": {},
+ "themeBrightnessLight": "Jasny",
+ "@themeBrightnessLight": {},
+ "themeBrightnessDark": "Ciemny",
+ "@themeBrightnessDark": {},
+ "themeBrightnessBlack": "Czarny",
+ "@themeBrightnessBlack": {},
+ "viewerTransitionSlide": "Slajd",
+ "@viewerTransitionSlide": {},
+ "viewerTransitionParallax": "Paralaksa",
+ "@viewerTransitionParallax": {},
+ "entryActionShareImageOnly": "Udostępnij tylko zdjęcia",
+ "@entryActionShareImageOnly": {},
+ "entryInfoActionRemoveLocation": "Usuń lokalizację",
+ "@entryInfoActionRemoveLocation": {},
+ "entryActionShareVideoOnly": "Udostępnij tylko wideo",
+ "@entryActionShareVideoOnly": {},
+ "filterAspectRatioLandscapeLabel": "Pejzaż",
+ "@filterAspectRatioLandscapeLabel": {},
+ "filterTypeGeotiffLabel": "GeoTIFF",
+ "@filterTypeGeotiffLabel": {},
+ "filterMimeImageLabel": "Obraz",
+ "@filterMimeImageLabel": {},
+ "unitSystemImperial": "Imperialny",
+ "@unitSystemImperial": {},
+ "videoLoopModeNever": "Nigdy",
+ "@videoLoopModeNever": {},
+ "videoControlsNone": "Nic",
+ "@videoControlsNone": {},
+ "accessibilityAnimationsRemove": "Zapobiegaj efektom ekranu",
+ "@accessibilityAnimationsRemove": {},
+ "accessibilityAnimationsKeep": "Zachowaj efekty ekranu",
+ "@accessibilityAnimationsKeep": {},
+ "displayRefreshRatePreferHighest": "Najwyższa",
+ "@displayRefreshRatePreferHighest": {},
+ "keepScreenOnNever": "Nigdy",
+ "@keepScreenOnNever": {},
+ "keepScreenOnViewerOnly": "Tylko na stronie przeglądarki",
+ "@keepScreenOnViewerOnly": {},
+ "videoPlaybackWithSound": "Odtwarzaj z dźwiękiem",
+ "@videoPlaybackWithSound": {},
+ "viewerTransitionFade": "Zanikanie",
+ "@viewerTransitionFade": {},
+ "viewerTransitionZoomIn": "Powiększanie",
+ "@viewerTransitionZoomIn": {},
+ "entryInfoActionExportMetadata": "Wyeksportuj metadane",
+ "@entryInfoActionExportMetadata": {},
+ "filterMimeVideoLabel": "Wideo",
+ "@filterMimeVideoLabel": {},
+ "coordinateFormatDms": "Stopnie, Minuty, Sekundy",
+ "@coordinateFormatDms": {},
+ "coordinateDmsEast": "Ws",
+ "@coordinateDmsEast": {},
+ "coordinateDmsWest": "Z",
+ "@coordinateDmsWest": {},
+ "unitSystemMetric": "Metryczny",
+ "@unitSystemMetric": {},
+ "videoControlsPlay": "Odtwórz",
+ "@videoControlsPlay": {},
+ "albumTierPinned": "Przypięty",
+ "@albumTierPinned": {},
+ "videoResumeButtonLabel": "WZNÓW",
+ "@videoResumeButtonLabel": {},
+ "widgetDisplayedItemRandom": "Losowo",
+ "@widgetDisplayedItemRandom": {},
+ "widgetOpenPageHome": "Otwórz stronę główną",
+ "@widgetOpenPageHome": {},
+ "widgetOpenPageViewer": "Otwórz przeglądarkę",
+ "@widgetOpenPageViewer": {},
+ "albumTierNew": "Nowy",
+ "@albumTierNew": {},
+ "albumTierSpecial": "Wspólny",
+ "@albumTierSpecial": {},
+ "albumTierApps": "Aplikacje",
+ "@albumTierApps": {},
+ "storageVolumeDescriptionFallbackNonPrimary": "Karta SD",
+ "@storageVolumeDescriptionFallbackNonPrimary": {},
+ "rootDirectoryDescription": "katalog główny",
+ "@rootDirectoryDescription": {},
+ "storageAccessDialogMessage": "Wybierz {directory} lub „{volume}” na następnym ekranie, aby dać tej aplikacji dostęp do niego.",
+ "@storageAccessDialogMessage": {
+ "placeholders": {
+ "directory": {
+ "type": "String",
+ "description": "the name of a directory, using the output of `rootDirectoryDescription` or `otherDirectoryDescription`"
+ },
+ "volume": {
+ "type": "String",
+ "example": "SD card",
+ "description": "the name of a storage volume"
+ }
+ }
+ },
+ "unsupportedTypeDialogMessage": "{count, plural, =1{Ta operacja nie jest obsługiwana dla elementów następującego typu: {types}.} other{Ta operacja nie jest obsługiwana dla elementów następujących typów: {types}.}}",
+ "@unsupportedTypeDialogMessage": {
+ "placeholders": {
+ "count": {},
+ "types": {
+ "type": "String",
+ "example": "GIF, TIFF, MP4",
+ "description": "a list of unsupported types"
+ }
+ }
+ },
+ "hideFilterConfirmationDialogMessage": "Pasujące zdjęcia i filmy zostaną ukryte w kolekcji. Możesz je ponownie wyświetlić w ustawieniach, w sekcji „Prywatność”.\n\nCzy na pewno chcesz je ukryć?",
+ "@hideFilterConfirmationDialogMessage": {},
+ "wallpaperTargetHomeLock": "Ekran główny i blokady",
+ "@wallpaperTargetHomeLock": {},
+ "widgetOpenPageCollection": "Otwórz kolekcję",
+ "@widgetOpenPageCollection": {},
+ "albumTierRegular": "Inne",
+ "@albumTierRegular": {},
+ "setCoverDialogLatest": "Ostatni element",
+ "@setCoverDialogLatest": {},
+ "newAlbumDialogNameLabel": "Nazwa albumu",
+ "@newAlbumDialogNameLabel": {},
+ "nameConflictDialogMultipleSourceMessage": "Niektóre pliki mają tę samą nazwę.",
+ "@nameConflictDialogMultipleSourceMessage": {},
+ "videoResumeDialogMessage": "Czy chcesz wznowić odtwarzanie od {time}?",
+ "@videoResumeDialogMessage": {
+ "placeholders": {
+ "time": {
+ "type": "String",
+ "example": "13:37"
+ }
+ }
+ },
+ "nameConflictDialogSingleSourceMessage": "Niektóre pliki w katalogu docelowym mają taką samą nazwę.",
+ "@nameConflictDialogSingleSourceMessage": {},
+ "otherDirectoryDescription": "katalog „{name}”",
+ "@otherDirectoryDescription": {
+ "placeholders": {
+ "name": {
+ "type": "String",
+ "example": "Pictures",
+ "description": "the name of a specific directory"
+ }
+ }
+ },
+ "addShortcutButtonLabel": "DODAJ",
+ "@addShortcutButtonLabel": {},
+ "widgetDisplayedItemMostRecent": "Najnowszy",
+ "@widgetDisplayedItemMostRecent": {},
+ "restrictedAccessDialogMessage": "Ta aplikacja nie może modyfikować plików w {directory} „{volume}”.\n\nUżyj wstępnie zainstalowanego menedżera plików lub aplikacji galerii, aby przenieść elementy do innego katalogu.",
+ "@restrictedAccessDialogMessage": {
+ "placeholders": {
+ "directory": {
+ "type": "String",
+ "description": "the name of a directory, using the output of `rootDirectoryDescription` or `otherDirectoryDescription`"
+ },
+ "volume": {
+ "type": "String",
+ "example": "SD card",
+ "description": "the name of a storage volume"
+ }
+ }
+ },
+ "storageVolumeDescriptionFallbackPrimary": "Pamięć wewnętrzna",
+ "@storageVolumeDescriptionFallbackPrimary": {},
+ "notEnoughSpaceDialogMessage": "Ta operacja wymaga {neededSize} wolnego miejsca na „{volume}”, aby ją ukończyć, ale pozostało tylko {freeSize}.",
+ "@notEnoughSpaceDialogMessage": {
+ "placeholders": {
+ "neededSize": {
+ "type": "String",
+ "example": "314 MB"
+ },
+ "freeSize": {
+ "type": "String",
+ "example": "123 MB"
+ },
+ "volume": {
+ "type": "String",
+ "example": "SD card",
+ "description": "the name of a storage volume"
+ }
+ }
+ },
+ "noMatchingAppDialogMessage": "Nie ma aplikacji, które mogłyby sobie z tym poradzić.",
+ "@noMatchingAppDialogMessage": {},
+ "addShortcutDialogLabel": "Etykieta skrótu",
+ "@addShortcutDialogLabel": {},
+ "binEntriesConfirmationDialogMessage": "{count, plural, =1{Przenieść ten element do Kosza?} other{Przenieść te elementy {count} do Kosza?}}",
+ "@binEntriesConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Usunąć ten element?} other{Usunąć te elementy {count}?}}",
+ "@deleteEntriesConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "moveUndatedConfirmationDialogMessage": "Zapisać daty elementów przed kontynuacją?",
+ "@moveUndatedConfirmationDialogMessage": {},
+ "newAlbumDialogTitle": "Nowy album",
+ "@newAlbumDialogTitle": {},
+ "newAlbumDialogNameLabelAlreadyExistsHelper": "Katalog już istnieje",
+ "@newAlbumDialogNameLabelAlreadyExistsHelper": {},
+ "missingSystemFilePickerDialogMessage": "Brak selektora plików systemowych lub jest on wyłączony. Włącz go i spróbuj ponownie.",
+ "@missingSystemFilePickerDialogMessage": {},
+ "setCoverDialogAuto": "Automatycznie",
+ "@setCoverDialogAuto": {},
+ "moveUndatedConfirmationDialogSetDate": "Zapisz daty",
+ "@moveUndatedConfirmationDialogSetDate": {},
+ "videoStartOverButtonLabel": "ZACZNIJ OD NOWA",
+ "@videoStartOverButtonLabel": {},
+ "setCoverDialogCustom": "Własny",
+ "@setCoverDialogCustom": {},
+ "collectionActionCopy": "Kopiuj do albumu",
+ "@collectionActionCopy": {},
+ "albumGroupNone": "Nie grupuj",
+ "@albumGroupNone": {},
+ "sortOrderOldestFirst": "Najpierw najstarsze",
+ "@sortOrderOldestFirst": {},
+ "searchDateSectionTitle": "Data",
+ "@searchDateSectionTitle": {},
+ "albumPageTitle": "Albumy",
+ "@albumPageTitle": {},
+ "countryPageTitle": "Kraje",
+ "@countryPageTitle": {},
+ "sortOrderZtoA": "Od Z do A",
+ "@sortOrderZtoA": {},
+ "dateYesterday": "Wczoraj",
+ "@dateYesterday": {},
+ "videoStreamSelectionDialogAudio": "Audio",
+ "@videoStreamSelectionDialogAudio": {},
+ "dateThisMonth": "W tym miesiącu",
+ "@dateThisMonth": {},
+ "videoStreamSelectionDialogTrack": "Ścieżka",
+ "@videoStreamSelectionDialogTrack": {},
+ "appPickDialogTitle": "Wybierz aplikację",
+ "@appPickDialogTitle": {},
+ "newAlbumDialogStorageLabel": "Pamięć:",
+ "@newAlbumDialogStorageLabel": {},
+ "renameAlbumDialogLabel": "Nowa nazwa",
+ "@renameAlbumDialogLabel": {},
+ "renameEntrySetPagePatternFieldLabel": "Wzorzec nazewnictwa",
+ "@renameEntrySetPagePatternFieldLabel": {},
+ "renameProcessorCounter": "Licznik",
+ "@renameProcessorCounter": {},
+ "renameProcessorName": "Nazwa",
+ "@renameProcessorName": {},
+ "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Usunąć ten album i jego element?} =2..4{Usunąć ten album i jego {count} elementy?} other{Usunąć ten album i jego {count} elementów?}}",
+ "@deleteSingleAlbumConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "editEntryDateDialogShift": "Zmiana",
+ "@editEntryDateDialogShift": {},
+ "editEntryRatingDialogTitle": "Ocena",
+ "@editEntryRatingDialogTitle": {},
+ "videoStreamSelectionDialogOff": "Wyłącz",
+ "@videoStreamSelectionDialogOff": {},
+ "videoStreamSelectionDialogNoSelection": "Nie ma innych ścieżek.",
+ "@videoStreamSelectionDialogNoSelection": {},
+ "genericSuccessFeedback": "Gotowe!",
+ "@genericSuccessFeedback": {},
+ "genericFailureFeedback": "Niepowodzenie",
+ "@genericFailureFeedback": {},
+ "genericDangerWarningDialogMessage": "Czy na pewno?",
+ "@genericDangerWarningDialogMessage": {},
+ "tileLayoutMosaic": "Mozaika",
+ "@tileLayoutMosaic": {},
+ "appPickDialogNone": "Brak",
+ "@appPickDialogNone": {},
+ "aboutBugReportInstruction": "Zgłoś w usłudze GitHub z dziennikami i informacją o systemie",
+ "@aboutBugReportInstruction": {},
+ "aboutCreditsSectionTitle": "Zasługi",
+ "@aboutCreditsSectionTitle": {},
+ "aboutLicensesFlutterPackagesSectionTitle": "Pakiety Fluttera",
+ "@aboutLicensesFlutterPackagesSectionTitle": {},
+ "aboutLicensesDartPackagesSectionTitle": "Pakiety Dart",
+ "@aboutLicensesDartPackagesSectionTitle": {},
+ "collectionPageTitle": "Kolekcja",
+ "@collectionPageTitle": {},
+ "collectionSelectPageTitle": "Wybierz elementy",
+ "@collectionSelectPageTitle": {},
+ "collectionActionShowTitleSearch": "Pokaż filtr tytułu",
+ "@collectionActionShowTitleSearch": {},
+ "collectionActionHideTitleSearch": "Ukryj filtr tytułu",
+ "@collectionActionHideTitleSearch": {},
+ "collectionActionMove": "Przenieś do albumu",
+ "@collectionActionMove": {},
+ "collectionActionRescan": "Przeskanuj",
+ "@collectionActionRescan": {},
+ "collectionActionEdit": "Edytuj",
+ "@collectionActionEdit": {},
+ "collectionSearchTitlesHintText": "Szukaj tytułów",
+ "@collectionSearchTitlesHintText": {},
+ "collectionGroupMonth": "Według miesiąca",
+ "@collectionGroupMonth": {},
+ "collectionGroupNone": "Nie grupuj",
+ "@collectionGroupNone": {},
+ "sectionUnknown": "Nieznany",
+ "@sectionUnknown": {},
+ "dateToday": "Dzisiaj",
+ "@dateToday": {},
+ "collectionCopySuccessFeedback": "{count, plural, =1{Skopiowano 1 element} =2..4{Skopiowano {count} elementy} other{Skopiowano {count} elementów}}",
+ "@collectionCopySuccessFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "collectionEditSuccessFeedback": "{count, plural, =1{Wyedytowano 1 element} =2..4{Wyedytowano {count} elementy} other{Wyedytowano {count} elementów}}",
+ "@collectionEditSuccessFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "collectionEmptyFavourites": "Brak ulubionych",
+ "@collectionEmptyFavourites": {},
+ "collectionEmptyVideos": "Brak filmów",
+ "@collectionEmptyVideos": {},
+ "sortByDate": "Według daty",
+ "@sortByDate": {},
+ "sortByRating": "Według oceny",
+ "@sortByRating": {},
+ "sortOrderNewestFirst": "Najpierw najnowsze",
+ "@sortOrderNewestFirst": {},
+ "sortOrderHighestFirst": "Najpierw najwyższe",
+ "@sortOrderHighestFirst": {},
+ "searchRatingSectionTitle": "Oceny",
+ "@searchRatingSectionTitle": {},
+ "sortOrderLowestFirst": "Najpierw najniższe",
+ "@sortOrderLowestFirst": {},
+ "sortOrderLargestFirst": "Najpierw największe",
+ "@sortOrderLargestFirst": {},
+ "albumGroupVolume": "Według pojemności magazynu",
+ "@albumGroupVolume": {},
+ "createAlbumButtonLabel": "UTWÓRZ",
+ "@createAlbumButtonLabel": {},
+ "newFilterBanner": "nowy",
+ "@newFilterBanner": {},
+ "countryEmpty": "Brak krajów",
+ "@countryEmpty": {},
+ "tagPageTitle": "Znaczniki",
+ "@tagPageTitle": {},
+ "searchMetadataSectionTitle": "Metadane",
+ "@searchMetadataSectionTitle": {},
+ "settingsPageTitle": "Ustawienia",
+ "@settingsPageTitle": {},
+ "exportEntryDialogHeight": "Wysokość",
+ "@exportEntryDialogHeight": {},
+ "renameEntryDialogLabel": "Nowa nazwa",
+ "@renameEntryDialogLabel": {},
+ "durationDialogMinutes": "Minuty",
+ "@durationDialogMinutes": {},
+ "editEntryLocationDialogLongitude": "Długość geograficzna",
+ "@editEntryLocationDialogLongitude": {},
+ "locationPickerUseThisLocationButton": "Użyj tej pozycji",
+ "@locationPickerUseThisLocationButton": {},
+ "removeEntryMetadataDialogTitle": "Usuwanie metadanych",
+ "@removeEntryMetadataDialogTitle": {},
+ "menuActionMap": "Mapa",
+ "@menuActionMap": {},
+ "menuActionStats": "Statystyki",
+ "@menuActionStats": {},
+ "viewDialogGroupSectionTitle": "Grupuj",
+ "@viewDialogGroupSectionTitle": {},
+ "viewDialogLayoutSectionTitle": "Układ",
+ "@viewDialogLayoutSectionTitle": {},
+ "viewDialogReverseSortOrder": "Odwróć porządek sortowania",
+ "@viewDialogReverseSortOrder": {},
+ "coverDialogTabColor": "Kolor",
+ "@coverDialogTabColor": {},
+ "aboutBugCopyInfoInstruction": "Skopiuj informację o systemie",
+ "@aboutBugCopyInfoInstruction": {},
+ "aboutBugReportButton": "Zgłoś",
+ "@aboutBugReportButton": {},
+ "aboutCreditsWorldAtlas2": "na licencji ISC.",
+ "@aboutCreditsWorldAtlas2": {},
+ "aboutTranslatorsSectionTitle": "Tłumacze",
+ "@aboutTranslatorsSectionTitle": {},
+ "aboutLicensesSectionTitle": "Licencje o otwartym kodzie",
+ "@aboutLicensesSectionTitle": {},
+ "aboutLicensesBanner": "Ta aplikacja używa następujących pakietów i bibliotek z otwartym kodem.",
+ "@aboutLicensesBanner": {},
+ "aboutLicensesAndroidLibrariesSectionTitle": "Biblioteki systemu Android",
+ "@aboutLicensesAndroidLibrariesSectionTitle": {},
+ "aboutLicensesFlutterPluginsSectionTitle": "Wtyczki Fluttera",
+ "@aboutLicensesFlutterPluginsSectionTitle": {},
+ "collectionActionAddShortcut": "Dodaj skrót",
+ "@collectionActionAddShortcut": {},
+ "collectionActionEmptyBin": "Opróżnij kosz",
+ "@collectionActionEmptyBin": {},
+ "collectionRenameSuccessFeedback": "{count, plural, =1{Zmieniono nazwę 1 elementowi} other{Zmieniono nazwę {count} elementom}}",
+ "@collectionRenameSuccessFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "collectionEmptyImages": "Brak obrazów",
+ "@collectionEmptyImages": {},
+ "collectionEmptyGrantAccessButtonLabel": "Przyznaj dostęp",
+ "@collectionEmptyGrantAccessButtonLabel": {},
+ "sortByItemCount": "Według liczby elementów",
+ "@sortByItemCount": {},
+ "sortBySize": "Według rozmiaru",
+ "@sortBySize": {},
+ "createAlbumTooltip": "Utwórz album",
+ "@createAlbumTooltip": {},
+ "albumEmpty": "Brak albumów",
+ "@albumEmpty": {},
+ "renameEntrySetPageTitle": "Zmień nazwę",
+ "@renameEntrySetPageTitle": {},
+ "durationDialogHours": "Godziny",
+ "@durationDialogHours": {},
+ "editEntryLocationDialogLatitude": "Szerokość geograficzna",
+ "@editEntryLocationDialogLatitude": {},
+ "removeEntryMetadataDialogMore": "Więcej",
+ "@removeEntryMetadataDialogMore": {},
+ "viewDialogSortSectionTitle": "Sortuj",
+ "@viewDialogSortSectionTitle": {},
+ "aboutCreditsWorldAtlas1": "Ta aplikacja używa pliku TopoJSON z",
+ "@aboutCreditsWorldAtlas1": {},
+ "policyPageTitle": "Polityka prywatności",
+ "@policyPageTitle": {},
+ "drawerCollectionImages": "Obrazy",
+ "@drawerCollectionImages": {},
+ "drawerCollectionVideos": "Wideo",
+ "@drawerCollectionVideos": {},
+ "drawerCountryPage": "Kraje",
+ "@drawerCountryPage": {},
+ "drawerTagPage": "Znaczniki",
+ "@drawerTagPage": {},
+ "albumGroupTier": "Według poziomu",
+ "@albumGroupTier": {},
+ "sortOrderSmallestFirst": "Najpierw najmniejsze",
+ "@sortOrderSmallestFirst": {},
+ "albumPickPageTitleMove": "Przenieś do albumu",
+ "@albumPickPageTitleMove": {},
+ "albumPickPageTitlePick": "Wybierz album",
+ "@albumPickPageTitlePick": {},
+ "albumScreenshots": "Zrzuty ekranu",
+ "@albumScreenshots": {},
+ "albumCamera": "Kamera",
+ "@albumCamera": {},
+ "albumDownload": "Pobrane",
+ "@albumDownload": {},
+ "settingsDisabled": "Wyłączono",
+ "@settingsDisabled": {},
+ "editEntryLocationDialogTitle": "Pozycja",
+ "@editEntryLocationDialogTitle": {},
+ "menuActionSlideshow": "Pokaz slajdów",
+ "@menuActionSlideshow": {},
+ "collectionEditFailureFeedback": "{count, plural, =1{Nie udało się wyedytować 1 elementu} other{Nie udało się wyedytować {count} elementów}}",
+ "@collectionEditFailureFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "drawerCollectionAnimated": "Animacje",
+ "@drawerCollectionAnimated": {},
+ "drawerCollectionMotionPhotos": "Animowane zdjęcia",
+ "@drawerCollectionMotionPhotos": {},
+ "collectionSelectSectionTooltip": "Zaznacz sekcję",
+ "@collectionSelectSectionTooltip": {},
+ "drawerCollectionPanoramas": "Zdjęcia panoramiczne",
+ "@drawerCollectionPanoramas": {},
+ "drawerCollectionRaws": "Nieprzetworzone zdjęcia",
+ "@drawerCollectionRaws": {},
+ "sortByAlbumFileName": "Według albumu i nazwy pliku",
+ "@sortByAlbumFileName": {},
+ "albumMimeTypeMixed": "Mieszane",
+ "@albumMimeTypeMixed": {},
+ "albumPickPageTitleCopy": "Skopiuj do albumu",
+ "@albumPickPageTitleCopy": {},
+ "albumPickPageTitleExport": "Wyeksportuj do albumu",
+ "@albumPickPageTitleExport": {},
+ "tagEmpty": "Bez znaczników",
+ "@tagEmpty": {},
+ "searchCountriesSectionTitle": "Kraje",
+ "@searchCountriesSectionTitle": {},
+ "searchPlacesSectionTitle": "Miejsca",
+ "@searchPlacesSectionTitle": {},
+ "searchTagsSectionTitle": "Znaczniki",
+ "@searchTagsSectionTitle": {},
+ "exportEntryDialogWidth": "Szerokość",
+ "@exportEntryDialogWidth": {},
+ "aboutBugSectionTitle": "Raport o błędach",
+ "@aboutBugSectionTitle": {},
+ "aboutBugSaveLogInstruction": "Zapisz dzienniki aplikacji w pliku",
+ "@aboutBugSaveLogInstruction": {},
+ "albumVideoCaptures": "Przechwycone wideo",
+ "@albumVideoCaptures": {},
+ "searchCollectionFieldHint": "Wyszukaj kolekcję",
+ "@searchCollectionFieldHint": {},
+ "binPageTitle": "Kosz",
+ "@binPageTitle": {},
+ "editEntryDialogCopyFromItem": "Kopiuj z innego elementu",
+ "@editEntryDialogCopyFromItem": {},
+ "editEntryDialogTargetFieldsHeader": "Pola do modyfikacji",
+ "@editEntryDialogTargetFieldsHeader": {},
+ "editEntryDateDialogTitle": "Data i godzina",
+ "@editEntryDateDialogTitle": {},
+ "editEntryDateDialogExtractFromTitle": "Wydobądź z tytułu",
+ "@editEntryDateDialogExtractFromTitle": {},
+ "menuActionSelectAll": "Zaznacz wszystkie",
+ "@menuActionSelectAll": {},
+ "removeEntryMetadataMotionPhotoXmpWarningDialogMessage": "XMP jest wymagane do odtworzenia wideo wewnątrz zdjęcia.\n\nCzy na pewno chcesz go usunąć?",
+ "@removeEntryMetadataMotionPhotoXmpWarningDialogMessage": {},
+ "tileLayoutGrid": "Siatka",
+ "@tileLayoutGrid": {},
+ "aboutBugCopyInfoButton": "Kopiuj",
+ "@aboutBugCopyInfoButton": {},
+ "albumGroupType": "Według typu",
+ "@albumGroupType": {},
+ "renameEntrySetPagePreviewSectionTitle": "Podgląd",
+ "@renameEntrySetPagePreviewSectionTitle": {},
+ "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Usunąć te albumy i ich element?} =2..4{Usunąć te albumy i ich {count} elementy?} other{Usunąć te albumy i ich {count} elementów?}}",
+ "@deleteMultiAlbumConfirmationDialogMessage": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "renameAlbumDialogLabelAlreadyExistsHelper": "Katalog już istnieje",
+ "@renameAlbumDialogLabelAlreadyExistsHelper": {},
+ "renameEntrySetPageInsertTooltip": "Wstaw pole",
+ "@renameEntrySetPageInsertTooltip": {},
+ "exportEntryDialogFormat": "Format:",
+ "@exportEntryDialogFormat": {},
+ "editEntryDateDialogSetCustom": "Ustaw własną datę",
+ "@editEntryDateDialogSetCustom": {},
+ "editEntryDateDialogCopyField": "Skopiuj z innej daty",
+ "@editEntryDateDialogCopyField": {},
+ "durationDialogSeconds": "Sekundy",
+ "@durationDialogSeconds": {},
+ "editEntryLocationDialogChooseOnMap": "Wybierz z mapy",
+ "@editEntryLocationDialogChooseOnMap": {},
+ "collectionGroupDay": "Według dnia",
+ "@collectionGroupDay": {},
+ "editEntryDateDialogSourceFileModifiedDate": "Data modyfikacji pliku",
+ "@editEntryDateDialogSourceFileModifiedDate": {},
+ "editEntryLocationDialogSetCustom": "Ustaw własną pozycję",
+ "@editEntryLocationDialogSetCustom": {},
+ "videoStreamSelectionDialogVideo": "Wideo",
+ "@videoStreamSelectionDialogVideo": {},
+ "videoStreamSelectionDialogText": "Napisy",
+ "@videoStreamSelectionDialogText": {},
+ "menuActionSelect": "Zaznacz",
+ "@menuActionSelect": {},
+ "videoSpeedDialogLabel": "Szybkość odtwarzania",
+ "@videoSpeedDialogLabel": {},
+ "aboutPageTitle": "O aplikacji",
+ "@aboutPageTitle": {},
+ "menuActionConfigureView": "Widok",
+ "@menuActionConfigureView": {},
+ "menuActionSelectNone": "Odznacz",
+ "@menuActionSelectNone": {},
+ "tileLayoutList": "Lista",
+ "@tileLayoutList": {},
+ "coverDialogTabCover": "Okładka",
+ "@coverDialogTabCover": {},
+ "coverDialogTabApp": "Aplikacja",
+ "@coverDialogTabApp": {},
+ "aboutLinkLicense": "Licencje",
+ "@aboutLinkLicense": {},
+ "aboutLinkPolicy": "Polityka prywatności",
+ "@aboutLinkPolicy": {},
+ "aboutLicensesShowAllButtonLabel": "Pokaż wszystkie licencje",
+ "@aboutLicensesShowAllButtonLabel": {},
+ "collectionPickPageTitle": "Wybierz",
+ "@collectionPickPageTitle": {},
+ "collectionGroupAlbum": "Według albumu",
+ "@collectionGroupAlbum": {},
+ "collectionDeleteFailureFeedback": "{count, plural, =1{Nie udało się usunąć 1 elementu} other{Nie udało się usunąć {count} elementów}}",
+ "@collectionDeleteFailureFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "collectionCopyFailureFeedback": "{count, plural, =1{Nie udało się skopiować 1 elementu} other{Nie udało się skopiować {count} elementów}}",
+ "@collectionCopyFailureFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "collectionMoveFailureFeedback": "{count, plural, =1{Nie udało się przenieść 1 elementu} other{Nie udało się przenieść {count} elementów}}",
+ "@collectionMoveFailureFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "collectionRenameFailureFeedback": "{count, plural, =1{Nie udało się zmienić nazwy 1 elementowi} other{Nie udało się zmienić nazwy {count} elementom}}",
+ "@collectionRenameFailureFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "albumScreenRecordings": "Nagrania ekranu",
+ "@albumScreenRecordings": {},
+ "collectionExportFailureFeedback": "{count, plural, =1{Nie udało się wyeksportować 1 strony} other{Nie udało się wyeksportować{count} stron}}",
+ "@collectionExportFailureFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "collectionMoveSuccessFeedback": "{count, plural, =1{Przeniesiono 1 element} =2..4{Przeniesiono {count} elementy} other{Przeniesiono {count} elementów}}",
+ "@collectionMoveSuccessFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "drawerCollectionAll": "Wszystkie kolekcje",
+ "@drawerCollectionAll": {},
+ "drawerSettingsButton": "Ustawienia",
+ "@drawerSettingsButton": {},
+ "drawerCollectionFavourites": "Ulubione",
+ "@drawerCollectionFavourites": {},
+ "sortByName": "Według nazwy",
+ "@sortByName": {},
+ "searchAlbumsSectionTitle": "Albumy",
+ "@searchAlbumsSectionTitle": {},
+ "settingsSystemDefault": "Ustawienie systemu",
+ "@settingsSystemDefault": {},
+ "settingsModificationWarningDialogMessage": "Pozostałe ustawienia zostaną zmodyfikowane.",
+ "@settingsModificationWarningDialogMessage": {},
+ "settingsDefault": "Domyślne",
+ "@settingsDefault": {},
+ "collectionDeselectSectionTooltip": "Odznacz sekcję",
+ "@collectionDeselectSectionTooltip": {},
+ "drawerAboutButton": "O programie",
+ "@drawerAboutButton": {},
+ "drawerCollectionSphericalVideos": "Wideo 360°",
+ "@drawerCollectionSphericalVideos": {},
+ "drawerAlbumPage": "Albumy",
+ "@drawerAlbumPage": {},
+ "sortOrderAtoZ": "Od A do Z",
+ "@sortOrderAtoZ": {},
+ "searchRecentSectionTitle": "Ostatni",
+ "@searchRecentSectionTitle": {},
+ "settingsSlideshowShuffle": "Mieszaj",
+ "@settingsSlideshowShuffle": {},
+ "settingsWidgetDisplayedItem": "Wyświetlany element",
+ "@settingsWidgetDisplayedItem": {},
+ "statsPageTitle": "Statystyki",
+ "@statsPageTitle": {},
+ "settingsViewerShowOverlayThumbnails": "Pokaż miniaturki",
+ "@settingsViewerShowOverlayThumbnails": {},
+ "settingsSearchFieldLabel": "Wyszukaj ustawienia",
+ "@settingsSearchFieldLabel": {},
+ "settingsActionImport": "Zaimportuj",
+ "@settingsActionImport": {},
+ "appExportFavourites": "Ulubione",
+ "@appExportFavourites": {},
+ "appExportSettings": "Ustawienia",
+ "@appExportSettings": {},
+ "settingsHomeDialogTitle": "Strona główna",
+ "@settingsHomeDialogTitle": {},
+ "settingsShowBottomNavigationBar": "Pokaż dolny pasek nawigacyjny",
+ "@settingsShowBottomNavigationBar": {},
+ "settingsDoubleBackExit": "Dotknij dwa razy „wstecz”, aby wyjść",
+ "@settingsDoubleBackExit": {},
+ "settingsNavigationDrawerTabTypes": "Kategorie",
+ "@settingsNavigationDrawerTabTypes": {},
+ "settingsNavigationDrawerAddAlbum": "Dodaj album",
+ "@settingsNavigationDrawerAddAlbum": {},
+ "settingsConfirmationTile": "Działania do potwierdzenia",
+ "@settingsConfirmationTile": {},
+ "settingsThumbnailShowTagIcon": "Pokaż ikonę znacznika",
+ "@settingsThumbnailShowTagIcon": {},
+ "settingsThumbnailShowLocationIcon": "Pokaż ikonę położenia",
+ "@settingsThumbnailShowLocationIcon": {},
+ "settingsThumbnailShowRawIcon": "Pokaż ikonę RAW",
+ "@settingsThumbnailShowRawIcon": {},
+ "settingsCollectionSelectionQuickActionEditorBanner": "Dotknij i przytrzymaj, aby przesuwać przyciski i wybrać akcje, które mają być wyświetlane podczas wybierania elementów.",
+ "@settingsCollectionSelectionQuickActionEditorBanner": {},
+ "settingsViewerGestureSideTapNext": "Dotknij krawędzi ekranu, aby wyświetlić poprzedni / następny element",
+ "@settingsViewerGestureSideTapNext": {},
+ "settingsViewerQuickActionEditorPageTitle": "Szybkie działania",
+ "@settingsViewerQuickActionEditorPageTitle": {},
+ "settingsSlideshowVideoPlaybackDialogTitle": "Odtwarzanie wideo",
+ "@settingsSlideshowVideoPlaybackDialogTitle": {},
+ "settingsVideoPageTitle": "Ustawienia wideo",
+ "@settingsVideoPageTitle": {},
+ "settingsVideoLoopModeTile": "Tryb pętli",
+ "@settingsVideoLoopModeTile": {},
+ "settingsVideoLoopModeDialogTitle": "Tryb pętli",
+ "@settingsVideoLoopModeDialogTitle": {},
+ "settingsSubtitleThemeTextAlignmentTile": "Dopasowanie tekstu",
+ "@settingsSubtitleThemeTextAlignmentTile": {},
+ "settingsSubtitleThemeTextPositionDialogTitle": "Pozycja tekstu",
+ "@settingsSubtitleThemeTextPositionDialogTitle": {},
+ "settingsSubtitleThemeTextSize": "Rozmiar tekstu",
+ "@settingsSubtitleThemeTextSize": {},
+ "settingsSubtitleThemeBackgroundColor": "Kolor tła",
+ "@settingsSubtitleThemeBackgroundColor": {},
+ "settingsSubtitleThemeTextAlignmentLeft": "Lewo",
+ "@settingsSubtitleThemeTextAlignmentLeft": {},
+ "settingsVideoControlsTile": "Sterowanie",
+ "@settingsVideoControlsTile": {},
+ "settingsVideoGestureDoubleTapTogglePlay": "Dotknij dwukrotnie, aby odtworzyć/wstrzymać",
+ "@settingsVideoGestureDoubleTapTogglePlay": {},
+ "settingsPrivacySectionTitle": "Prywatność",
+ "@settingsPrivacySectionTitle": {},
+ "settingsAllowInstalledAppAccess": "Zezwól na dostęp do spisu aplikacji",
+ "@settingsAllowInstalledAppAccess": {},
+ "settingsAllowErrorReporting": "Pozwól na anonimowe zgłaszanie błędów",
+ "@settingsAllowErrorReporting": {},
+ "settingsSaveSearchHistory": "Zapisz historię wyszukiwania",
+ "@settingsSaveSearchHistory": {},
+ "settingsHiddenItemsTabPaths": "Ukryte ścieżki",
+ "@settingsHiddenItemsTabPaths": {},
+ "settingsStorageAccessTile": "Dostęp do pamięci masowej",
+ "@settingsStorageAccessTile": {},
+ "settingsRemoveAnimationsTile": "Usuń animacje",
+ "@settingsRemoveAnimationsTile": {},
+ "settingsDisplayUseTvInterface": "Interfejs Android TV",
+ "@settingsDisplayUseTvInterface": {},
+ "settingsLanguagePageTitle": "Język",
+ "@settingsLanguagePageTitle": {},
+ "settingsScreenSaverPageTitle": "Wygaszacz ekranu",
+ "@settingsScreenSaverPageTitle": {},
+ "settingsWidgetPageTitle": "Ramka zdjęcia",
+ "@settingsWidgetPageTitle": {},
+ "settingsWidgetOpenPage": "Po dotknięciu widżetu",
+ "@settingsWidgetOpenPage": {},
+ "statsTopAlbumsSectionTitle": "Najlepsze albumy",
+ "@statsTopAlbumsSectionTitle": {},
+ "viewerOpenPanoramaButtonLabel": "OTWÓRZ PANORAMĘ",
+ "@viewerOpenPanoramaButtonLabel": {},
+ "viewerErrorUnknown": "Ups!",
+ "@viewerErrorUnknown": {},
+ "viewerErrorDoesNotExist": "Plik już nie istnieje.",
+ "@viewerErrorDoesNotExist": {},
+ "viewerInfoLabelResolution": "Rozdzielczość",
+ "@viewerInfoLabelResolution": {},
+ "viewerInfoLabelSize": "Rozmiar",
+ "@viewerInfoLabelSize": {},
+ "viewerInfoLabelPath": "Ścieżka",
+ "@viewerInfoLabelPath": {},
+ "mapZoomInTooltip": "Powiększ",
+ "@mapZoomInTooltip": {},
+ "mapAttributionStamen": "Dane mapy © [OpenStreetMap](https://www.openstreetmap.org/copyright) współtwórcy • Kafelki od [Stamen Design](https://stamen.com), [CC BY 3.0](https://creativecommons.org/licenses/by/3.0)",
+ "@mapAttributionStamen": {},
+ "filePickerUseThisFolder": "Użyj tego katalogu",
+ "@filePickerUseThisFolder": {},
+ "mapEmptyRegion": "Brak obrazów w tym regionie",
+ "@mapEmptyRegion": {},
+ "settingsKeepScreenOnTile": "Pozostaw ekran załączony",
+ "@settingsKeepScreenOnTile": {},
+ "filePickerOpenFrom": "Otwórz z",
+ "@filePickerOpenFrom": {},
+ "filePickerNoItems": "Brak elementów",
+ "@filePickerNoItems": {},
+ "viewerInfoSearchSuggestionRights": "Prawa",
+ "@viewerInfoSearchSuggestionRights": {},
+ "filePickerDoNotShowHiddenFiles": "Nie pokazuj ukrytych plików",
+ "@filePickerDoNotShowHiddenFiles": {},
+ "settingsActionImportDialogTitle": "Zaimportuj",
+ "@settingsActionImportDialogTitle": {},
+ "settingsKeepScreenOnDialogTitle": "Pozostaw ekran załączony",
+ "@settingsKeepScreenOnDialogTitle": {},
+ "settingsNavigationDrawerTile": "Menu nawigacyjne",
+ "@settingsNavigationDrawerTile": {},
+ "settingsThumbnailSectionTitle": "Miniatury",
+ "@settingsThumbnailSectionTitle": {},
+ "settingsCollectionQuickActionTabSelecting": "Wybieranie",
+ "@settingsCollectionQuickActionTabSelecting": {},
+ "settingsThumbnailShowVideoDuration": "Pokaż czas trwania wideo",
+ "@settingsThumbnailShowVideoDuration": {},
+ "settingsViewerUseCutout": "Użyj obszaru wycięcia",
+ "@settingsViewerUseCutout": {},
+ "settingsViewerMaximumBrightness": "Jasność maksymalna",
+ "@settingsViewerMaximumBrightness": {},
+ "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Wyświetlane przyciski",
+ "@settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": {},
+ "settingsViewerSlideshowTile": "Pokaz slajdów",
+ "@settingsViewerSlideshowTile": {},
+ "settingsSlideshowFillScreen": "Wypełnij ekran",
+ "@settingsSlideshowFillScreen": {},
+ "settingsSlideshowIntervalTile": "Interwał",
+ "@settingsSlideshowIntervalTile": {},
+ "settingsVideoEnableHardwareAcceleration": "Przyspieszenie sprzętowe",
+ "@settingsVideoEnableHardwareAcceleration": {},
+ "settingsVideoAutoPlay": "Odtwarzaj automatycznie",
+ "@settingsVideoAutoPlay": {},
+ "settingsSubtitleThemeSample": "To jest próbka.",
+ "@settingsSubtitleThemeSample": {},
+ "settingsSubtitleThemeTextAlignmentDialogTitle": "Dopasowanie tekstu",
+ "@settingsSubtitleThemeTextAlignmentDialogTitle": {},
+ "settingsSubtitleThemeTextPositionTile": "Pozycja tekstu",
+ "@settingsSubtitleThemeTextPositionTile": {},
+ "settingsSubtitleThemeBackgroundOpacity": "Krycie tła",
+ "@settingsSubtitleThemeBackgroundOpacity": {},
+ "settingsSubtitleThemeTextAlignmentCenter": "Środek",
+ "@settingsSubtitleThemeTextAlignmentCenter": {},
+ "settingsSubtitleThemeTextAlignmentRight": "Prawo",
+ "@settingsSubtitleThemeTextAlignmentRight": {},
+ "settingsEnableBinSubtitle": "Zachowaj usunięte elementy przez 30 dni",
+ "@settingsEnableBinSubtitle": {},
+ "settingsAllowMediaManagement": "Zezwalaj na zarządzanie multimediami",
+ "@settingsAllowMediaManagement": {},
+ "settingsHiddenFiltersBanner": "Zdjęcia i wideo pasujące do ukrytych filtrów nie pojawią się w twojej kolekcji.",
+ "@settingsHiddenFiltersBanner": {},
+ "settingsHiddenFiltersEmpty": "Brak ukrytych filtrów",
+ "@settingsHiddenFiltersEmpty": {},
+ "settingsStorageAccessEmpty": "Brak przyznanych uprawnień",
+ "@settingsStorageAccessEmpty": {},
+ "settingsAccessibilityShowPinchGestureAlternatives": "Pokaż alternatywy gestów wielodotykowych",
+ "@settingsAccessibilityShowPinchGestureAlternatives": {},
+ "settingsThemeBrightnessDialogTitle": "Motyw",
+ "@settingsThemeBrightnessDialogTitle": {},
+ "settingsThemeColorHighlights": "Podkreślenia kolorów",
+ "@settingsThemeColorHighlights": {},
+ "settingsThemeEnableDynamicColor": "Kolor dynamiczny",
+ "@settingsThemeEnableDynamicColor": {},
+ "settingsDisplayRefreshRateModeTile": "Częstotliwość odświeżania ekranu",
+ "@settingsDisplayRefreshRateModeTile": {},
+ "settingsDisplayRefreshRateModeDialogTitle": "Częstotliwość odświeżania",
+ "@settingsDisplayRefreshRateModeDialogTitle": {},
+ "settingsLanguageSectionTitle": "Język i formaty",
+ "@settingsLanguageSectionTitle": {},
+ "settingsCoordinateFormatTile": "Format współrzędnych",
+ "@settingsCoordinateFormatTile": {},
+ "settingsUnitSystemDialogTitle": "Jednostki",
+ "@settingsUnitSystemDialogTitle": {},
+ "viewerSetWallpaperButtonLabel": "USTAW TAPETĘ",
+ "@viewerSetWallpaperButtonLabel": {},
+ "viewerInfoLabelAddress": "Adres",
+ "@viewerInfoLabelAddress": {},
+ "mapStyleTooltip": "Wybierz styl mapy",
+ "@mapStyleTooltip": {},
+ "mapStyleDialogTitle": "Styl mapy",
+ "@mapStyleDialogTitle": {},
+ "wallpaperUseScrollEffect": "Użyj efektu przewijania na ekranie głównym",
+ "@wallpaperUseScrollEffect": {},
+ "tagEditorSectionRecent": "Ostatnie",
+ "@tagEditorSectionRecent": {},
+ "sourceViewerPageTitle": "Źródło",
+ "@sourceViewerPageTitle": {},
+ "viewerInfoSearchEmpty": "Brak pasujących kluczy",
+ "@viewerInfoSearchEmpty": {},
+ "settingsStorageAccessRevokeTooltip": "Odwołaj",
+ "@settingsStorageAccessRevokeTooltip": {},
+ "settingsAccessibilitySectionTitle": "Dostępność",
+ "@settingsAccessibilitySectionTitle": {},
+ "viewerInfoOpenLinkText": "Otwórz",
+ "@viewerInfoOpenLinkText": {},
+ "tagEditorSectionPlaceholders": "Symbole zastępcze",
+ "@tagEditorSectionPlaceholders": {},
+ "viewerInfoPageTitle": "Informacje",
+ "@viewerInfoPageTitle": {},
+ "viewerInfoViewXmlLinkText": "Zobacz XML",
+ "@viewerInfoViewXmlLinkText": {},
+ "viewerInfoOpenEmbeddedFailureFeedback": "Nie udało się wyodrębnić osadzonych danych",
+ "@viewerInfoOpenEmbeddedFailureFeedback": {},
+ "settingsConfirmationBeforeMoveToBinItems": "Zapytaj, zanim przeniesiesz elementy do kosza",
+ "@settingsConfirmationBeforeMoveToBinItems": {},
+ "settingsConfirmationBeforeMoveUndatedItems": "Zapytaj, zanim przeniesiesz niedatowane elementy",
+ "@settingsConfirmationBeforeMoveUndatedItems": {},
+ "settingsConfirmationAfterMoveToBinItems": "Pokaż komunikat po przeniesieniu elementów do Kosza",
+ "@settingsConfirmationAfterMoveToBinItems": {},
+ "settingsNavigationDrawerEditorPageTitle": "Menu nawigacyjne",
+ "@settingsNavigationDrawerEditorPageTitle": {},
+ "settingsNavigationDrawerBanner": "Dotknij i przytrzymaj, aby przenieść i zmienić kolejność pozycji menu.",
+ "@settingsNavigationDrawerBanner": {},
+ "settingsThumbnailShowFavouriteIcon": "Pokaż ikonę ulubionych",
+ "@settingsThumbnailShowFavouriteIcon": {},
+ "settingsThumbnailShowMotionPhotoIcon": "Pokaż ikonę zdjęcia ruchomego",
+ "@settingsThumbnailShowMotionPhotoIcon": {},
+ "settingsThumbnailOverlayTile": "Nakładka",
+ "@settingsThumbnailOverlayTile": {},
+ "settingsThumbnailShowRating": "Pokaż ocenę",
+ "@settingsThumbnailShowRating": {},
+ "settingsCollectionQuickActionsTile": "Szybkie działania",
+ "@settingsCollectionQuickActionsTile": {},
+ "settingsCollectionBrowsingQuickActionEditorBanner": "Dotknij i przytrzymaj, aby przenieść przyciski i wybrać akcje wyświetlane podczas przeglądania elementów.",
+ "@settingsCollectionBrowsingQuickActionEditorBanner": {},
+ "appExportCovers": "Okładki",
+ "@appExportCovers": {},
+ "settingsHomeTile": "Strona główna",
+ "@settingsHomeTile": {},
+ "settingsThumbnailOverlayPageTitle": "Nakładka",
+ "@settingsThumbnailOverlayPageTitle": {},
+ "settingsLanguageTile": "Język",
+ "@settingsLanguageTile": {},
+ "settingsCollectionTile": "Kolekcja",
+ "@settingsCollectionTile": {},
+ "statsTopPlacesSectionTitle": "Najlepsze miejsca",
+ "@statsTopPlacesSectionTitle": {},
+ "statsTopTagsSectionTitle": "Najlepsze znaczniki",
+ "@statsTopTagsSectionTitle": {},
+ "viewerInfoBackToViewerTooltip": "Wróć do przeglądarki",
+ "@viewerInfoBackToViewerTooltip": {},
+ "mapZoomOutTooltip": "Pomniejsz",
+ "@mapZoomOutTooltip": {},
+ "viewerInfoSearchSuggestionDescription": "Opis",
+ "@viewerInfoSearchSuggestionDescription": {},
+ "viewerInfoSearchSuggestionDimensions": "Wymiary",
+ "@viewerInfoSearchSuggestionDimensions": {},
+ "viewerInfoSearchSuggestionDate": "Data i godzina",
+ "@viewerInfoSearchSuggestionDate": {},
+ "viewerInfoSearchSuggestionResolution": "Rozdzielczość",
+ "@viewerInfoSearchSuggestionResolution": {},
+ "tagEditorPageTitle": "Edytuj znaczniki",
+ "@tagEditorPageTitle": {},
+ "tagEditorPageNewTagFieldLabel": "Nowy znacznik",
+ "@tagEditorPageNewTagFieldLabel": {},
+ "tagEditorPageAddTagTooltip": "Dodaj znacznik",
+ "@tagEditorPageAddTagTooltip": {},
+ "settingsNavigationSectionTitle": "Nawigowanie",
+ "@settingsNavigationSectionTitle": {},
+ "viewerInfoLabelDate": "Data",
+ "@viewerInfoLabelDate": {},
+ "settingsTimeToTakeActionTile": "Czas na podjęcie działania",
+ "@settingsTimeToTakeActionTile": {},
+ "settingsDisplaySectionTitle": "Wyświetlanie",
+ "@settingsDisplaySectionTitle": {},
+ "settingsNavigationDrawerTabAlbums": "Albumy",
+ "@settingsNavigationDrawerTabAlbums": {},
+ "settingsNavigationDrawerTabPages": "Strony",
+ "@settingsNavigationDrawerTabPages": {},
+ "settingsVideoSectionTitle": "Wideo",
+ "@settingsVideoSectionTitle": {},
+ "settingsSubtitleThemeShowOutline": "Pokaż kontur i cień",
+ "@settingsSubtitleThemeShowOutline": {},
+ "settingsSubtitleThemeTextColor": "Kolor tekstu",
+ "@settingsSubtitleThemeTextColor": {},
+ "settingsSubtitleThemeTextOpacity": "Krycie tekstu",
+ "@settingsSubtitleThemeTextOpacity": {},
+ "settingsUnitSystemTile": "Jednostki",
+ "@settingsUnitSystemTile": {},
+ "addPathTooltip": "Dodaj ścieżkę",
+ "@addPathTooltip": {},
+ "settingsHiddenPathsBanner": "Zdjęcia i wideo w tych folderach ani w żadnym z ich podfolderów nie pojawią się w kolekcji.",
+ "@settingsHiddenPathsBanner": {},
+ "viewerInfoLabelOwner": "Właściciel",
+ "@viewerInfoLabelOwner": {},
+ "viewerInfoSearchFieldLabel": "Wyszukaj metadane",
+ "@viewerInfoSearchFieldLabel": {},
+ "panoramaEnableSensorControl": "Włącz sterowanie czujnikiem",
+ "@panoramaEnableSensorControl": {},
+ "settingsSearchEmpty": "Brak pasującego ustawienia",
+ "@settingsSearchEmpty": {},
+ "settingsActionExport": "Wyeksportuj",
+ "@settingsActionExport": {},
+ "settingsActionExportDialogTitle": "Wyeksportuj",
+ "@settingsActionExportDialogTitle": {},
+ "settingsCollectionQuickActionEditorPageTitle": "Szybkie działania",
+ "@settingsCollectionQuickActionEditorPageTitle": {},
+ "settingsViewerSectionTitle": "Przeglądarka",
+ "@settingsViewerSectionTitle": {},
+ "settingsImageBackground": "Tło obrazu",
+ "@settingsImageBackground": {},
+ "settingsViewerShowRatingTags": "Pokaż ocenę i znaczniki",
+ "@settingsViewerShowRatingTags": {},
+ "settingsViewerShowShootingDetails": "Pokaż szczegóły fotografowania",
+ "@settingsViewerShowShootingDetails": {},
+ "settingsViewerShowInformation": "Pokaż informacje",
+ "@settingsViewerShowInformation": {},
+ "settingsViewerEnableOverlayBlurEffect": "Efekt rozmycia",
+ "@settingsViewerEnableOverlayBlurEffect": {},
+ "settingsSubtitleThemePageTitle": "Napisy",
+ "@settingsSubtitleThemePageTitle": {},
+ "settingsVideoShowVideos": "Pokaż wideo",
+ "@settingsVideoShowVideos": {},
+ "settingsCoordinateFormatDialogTitle": "Format współrzędnych",
+ "@settingsCoordinateFormatDialogTitle": {},
+ "statsTopCountriesSectionTitle": "Najlepsze kraje",
+ "@statsTopCountriesSectionTitle": {},
+ "viewerInfoLabelUri": "URI",
+ "@viewerInfoLabelUri": {},
+ "panoramaDisableSensorControl": "Wyłącz sterowanie czujnikiem",
+ "@panoramaDisableSensorControl": {},
+ "tagPlaceholderCountry": "Kraj",
+ "@tagPlaceholderCountry": {},
+ "settingsConfirmationBeforeDeleteItems": "Zapytaj, zanim usuniesz elementy na zawsze",
+ "@settingsConfirmationBeforeDeleteItems": {},
+ "settingsConfirmationDialogTitle": "Działania do potwierdzenia",
+ "@settingsConfirmationDialogTitle": {},
+ "settingsMotionPhotoAutoPlay": "Automatyczne odtwarzanie ruchomych zdjęć",
+ "@settingsMotionPhotoAutoPlay": {},
+ "settingsViewerQuickActionsTile": "Szybkie działania",
+ "@settingsViewerQuickActionsTile": {},
+ "settingsViewerQuickActionEditorBanner": "Dotknij i przytrzymaj, aby przesunąć przyciski i wybrać czynności, które mają być wyświetlane w przeglądarce.",
+ "@settingsViewerQuickActionEditorBanner": {},
+ "settingsViewerQuickActionEmpty": "Brak przycisków",
+ "@settingsViewerQuickActionEmpty": {},
+ "settingsSlideshowAnimatedZoomEffect": "Animowany efekt powiększenia",
+ "@settingsSlideshowAnimatedZoomEffect": {},
+ "settingsViewerShowInformationSubtitle": "Pokaż tytuł, datę, położenie itp.",
+ "@settingsViewerShowInformationSubtitle": {},
+ "filePickerShowHiddenFiles": "Pokaż ukryte pliki",
+ "@filePickerShowHiddenFiles": {},
+ "settingsHiddenItemsTabFilters": "Ukryte filtry",
+ "@settingsHiddenItemsTabFilters": {},
+ "settingsThemeBrightnessTile": "Motyw",
+ "@settingsThemeBrightnessTile": {},
+ "tagPlaceholderPlace": "Miejsce",
+ "@tagPlaceholderPlace": {},
+ "settingsHiddenItemsPageTitle": "Ukryte elementy",
+ "@settingsHiddenItemsPageTitle": {},
+ "settingsRemoveAnimationsDialogTitle": "Usuń animacje",
+ "@settingsRemoveAnimationsDialogTitle": {},
+ "settingsVideoControlsPageTitle": "Sterowanie",
+ "@settingsVideoControlsPageTitle": {},
+ "settingsVideoButtonsTile": "Przyciski",
+ "@settingsVideoButtonsTile": {},
+ "settingsAllowInstalledAppAccessSubtitle": "Używane do poprawy wyświetlania albumu",
+ "@settingsAllowInstalledAppAccessSubtitle": {},
+ "settingsEnableBin": "Użyj kosza",
+ "@settingsEnableBin": {},
+ "settingsWidgetShowOutline": "Zarys",
+ "@settingsWidgetShowOutline": {},
+ "settingsHiddenItemsTile": "Ukryte elementy",
+ "@settingsHiddenItemsTile": {},
+ "viewerInfoLabelDuration": "Czas trwania",
+ "@viewerInfoLabelDuration": {},
+ "settingsStorageAccessPageTitle": "Dostęp do pamięci masowej",
+ "@settingsStorageAccessPageTitle": {},
+ "settingsStorageAccessBanner": "Niektóre katalogi wymagają jawnego udzielenia dostępu, aby modyfikować znajdujące się w nich pliki. Możesz przejrzeć tutaj katalogi, do których wcześniej udzielono dostępu.",
+ "@settingsStorageAccessBanner": {},
+ "statsWithGps": "{count, plural, =1{1 element z położeniem} =2..4{{count} elementy z położeniem} other{{count} elementów z położeniem}}",
+ "@statsWithGps": {
+ "placeholders": {
+ "count": {}
+ }
+ },
+ "viewerInfoLabelDescription": "Opis",
+ "@viewerInfoLabelDescription": {},
+ "viewerInfoLabelTitle": "Tytuł",
+ "@viewerInfoLabelTitle": {},
+ "viewerInfoLabelCoordinates": "Współrzędne",
+ "@viewerInfoLabelCoordinates": {},
+ "viewerInfoUnknown": "Nieznany",
+ "@viewerInfoUnknown": {},
+ "mapAttributionOsmHot": "Dane mapy © [OpenStreetMap](https://www.openstreetmap.org/copyright) współtwórcy • Kafelki od [HOT](https://www.hotosm.org/) • Obsługiwany przez: [OSM France](https://openstreetmap.fr/)",
+ "@mapAttributionOsmHot": {},
+ "mapPointNorthUpTooltip": "Północ u góry",
+ "@mapPointNorthUpTooltip": {},
+ "settingsCollectionQuickActionTabBrowsing": "Przeglądanie",
+ "@settingsCollectionQuickActionTabBrowsing": {},
+ "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Dostępne przyciski",
+ "@settingsViewerQuickActionEditorAvailableButtonsSectionTitle": {},
+ "settingsViewerOverlayTile": "Nakładka",
+ "@settingsViewerOverlayTile": {},
+ "settingsViewerOverlayPageTitle": "Nakładka",
+ "@settingsViewerOverlayPageTitle": {},
+ "settingsViewerShowOverlayOnOpening": "Pokaz w momencie otwarcia",
+ "@settingsViewerShowOverlayOnOpening": {},
+ "settingsViewerShowMinimap": "Pokaż minimapę",
+ "@settingsViewerShowMinimap": {},
+ "settingsVideoGestureSideDoubleTapSeek": "Dotknij dwukrotnie krawędzi ekranu, aby przeszukać do tyłu / do przodu",
+ "@settingsVideoGestureSideDoubleTapSeek": {},
+ "settingsViewerShowDescription": "Pokaż opis",
+ "@settingsViewerShowDescription": {},
+ "settingsViewerSlideshowPageTitle": "Pokaz slajdów",
+ "@settingsViewerSlideshowPageTitle": {},
+ "settingsSlideshowRepeat": "Powtarzaj",
+ "@settingsSlideshowRepeat": {},
+ "settingsSlideshowTransitionTile": "Przejście",
+ "@settingsSlideshowTransitionTile": {},
+ "settingsSlideshowVideoPlaybackTile": "Odtwarzanie wideo",
+ "@settingsSlideshowVideoPlaybackTile": {},
+ "settingsSubtitleThemeTile": "Napisy",
+ "@settingsSubtitleThemeTile": {},
+ "openMapPageTooltip": "Wyświetl na mapie",
+ "@openMapPageTooltip": {}
}
diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb
index 3ef91434c..075bb96f4 100644
--- a/lib/l10n/app_pt.arb
+++ b/lib/l10n/app_pt.arb
@@ -1156,5 +1156,49 @@
"tagPlaceholderPlace": "Lugar",
"@tagPlaceholderPlace": {},
"editEntryLocationDialogSetCustom": "Definir local personalizado",
- "@editEntryLocationDialogSetCustom": {}
+ "@editEntryLocationDialogSetCustom": {},
+ "subtitlePositionBottom": "Fundo",
+ "@subtitlePositionBottom": {},
+ "subtitlePositionTop": "Topo",
+ "@subtitlePositionTop": {},
+ "widgetDisplayedItemRandom": "Aleatório",
+ "@widgetDisplayedItemRandom": {},
+ "settingsSubtitleThemeTextPositionTile": "Posição do texto",
+ "@settingsSubtitleThemeTextPositionTile": {},
+ "settingsSubtitleThemeTextPositionDialogTitle": "Posição do Texto",
+ "@settingsSubtitleThemeTextPositionDialogTitle": {},
+ "settingsWidgetDisplayedItem": "Item exibido",
+ "@settingsWidgetDisplayedItem": {},
+ "entryInfoActionRemoveLocation": "Remover localização",
+ "@entryInfoActionRemoveLocation": {},
+ "filterNoAddressLabel": "Sem endereço",
+ "@filterNoAddressLabel": {},
+ "keepScreenOnVideoPlayback": "Durante a reprodução do video",
+ "@keepScreenOnVideoPlayback": {},
+ "settingsViewerShowDescription": "Mostrar descrição",
+ "@settingsViewerShowDescription": {},
+ "entryActionShareImageOnly": "Compartilhar apenas imagem",
+ "@entryActionShareImageOnly": {},
+ "entryActionShareVideoOnly": "Compartilhar apenas video",
+ "@entryActionShareVideoOnly": {},
+ "filterAspectRatioPortraitLabel": "Retrato",
+ "@filterAspectRatioPortraitLabel": {},
+ "filterAspectRatioLandscapeLabel": "Paisagem",
+ "@filterAspectRatioLandscapeLabel": {},
+ "entryInfoActionExportMetadata": "Exportar metadados",
+ "@entryInfoActionExportMetadata": {},
+ "widgetDisplayedItemMostRecent": "Mais recente",
+ "@widgetDisplayedItemMostRecent": {},
+ "filterTaggedLabel": "Marcado",
+ "@filterTaggedLabel": {},
+ "filterLocatedLabel": "Localizado",
+ "@filterLocatedLabel": {},
+ "settingsAccessibilityShowPinchGestureAlternatives": "Mostrar alternativas de gesto multitoque",
+ "@settingsAccessibilityShowPinchGestureAlternatives": {},
+ "settingsModificationWarningDialogMessage": "Outras configurações serão modificadas.",
+ "@settingsModificationWarningDialogMessage": {},
+ "settingsDisplayUseTvInterface": "Interface de TV Android",
+ "@settingsDisplayUseTvInterface": {},
+ "settingsViewerShowRatingTags": "Mostrar avaliações e tags",
+ "@settingsViewerShowRatingTags": {}
}
diff --git a/lib/l10n/app_ro.arb b/lib/l10n/app_ro.arb
index 7bd5ebd81..d0ffb46ed 100644
--- a/lib/l10n/app_ro.arb
+++ b/lib/l10n/app_ro.arb
@@ -1354,5 +1354,15 @@
"filterNoAddressLabel": "Nicio adresă",
"@filterNoAddressLabel": {},
"entryInfoActionRemoveLocation": "Eliminare locație",
- "@entryInfoActionRemoveLocation": {}
+ "@entryInfoActionRemoveLocation": {},
+ "settingsViewerShowDescription": "Afișare descriere",
+ "@settingsViewerShowDescription": {},
+ "filterLocatedLabel": "Locație",
+ "@filterLocatedLabel": {},
+ "filterTaggedLabel": "Etichetat",
+ "@filterTaggedLabel": {},
+ "settingsModificationWarningDialogMessage": "Alte setări vor fi modificate.",
+ "@settingsModificationWarningDialogMessage": {},
+ "settingsDisplayUseTvInterface": "Interfață Android TV",
+ "@settingsDisplayUseTvInterface": {}
}
diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb
index 8f4d75410..3cd48cd05 100644
--- a/lib/l10n/app_ru.arb
+++ b/lib/l10n/app_ru.arb
@@ -1196,5 +1196,7 @@
"entryActionShareImageOnly": "Поделиться только изображением",
"@entryActionShareImageOnly": {},
"entryActionShareVideoOnly": "Поделиться только видео",
- "@entryActionShareVideoOnly": {}
+ "@entryActionShareVideoOnly": {},
+ "settingsViewerShowDescription": "Показать описание",
+ "@settingsViewerShowDescription": {}
}
diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb
index 6637d66e6..84018f06c 100644
--- a/lib/l10n/app_tr.arb
+++ b/lib/l10n/app_tr.arb
@@ -345,9 +345,9 @@
"@renameProcessorCounter": {},
"renameProcessorName": "Ad",
"@renameProcessorName": {},
- "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Bu albüm ve öğesi silinsin mi?} other{Bu albüm ve {count} öğesi silinsin mi?}}",
+ "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Bu albüm ve içindeki öge silinsin mi?} other{Bu albüm ve içindeki {count} öge silinsin mi?}}",
"@deleteSingleAlbumConfirmationDialogMessage": {},
- "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Bu albümler ve öğeleri silinsin mi?} other{Bu albümler ve {count} öğesi silinsin mi?}}",
+ "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Bu albümler ve içindeki öge silinsin mi?} other{Bu albümler ve içindeki {count} ögesi silinsin mi?}}",
"@deleteMultiAlbumConfirmationDialogMessage": {},
"exportEntryDialogFormat": "Biçim:",
"@exportEntryDialogFormat": {},
@@ -1196,5 +1196,17 @@
}
},
"settingsAccessibilityShowPinchGestureAlternatives": "Çoklu dokunma hareketi alternatiflerini göster",
- "@settingsAccessibilityShowPinchGestureAlternatives": {}
+ "@settingsAccessibilityShowPinchGestureAlternatives": {},
+ "settingsViewerShowDescription": "Açıklamayı göster",
+ "@settingsViewerShowDescription": {},
+ "settingsModificationWarningDialogMessage": "Diğer ayarlar değiştirilecektir.",
+ "@settingsModificationWarningDialogMessage": {},
+ "settingsDisplayUseTvInterface": "Android TV arayüzü",
+ "@settingsDisplayUseTvInterface": {},
+ "filterLocatedLabel": "Konumlu",
+ "@filterLocatedLabel": {},
+ "filterTaggedLabel": "Etiketli",
+ "@filterTaggedLabel": {},
+ "tooManyItemsErrorDialogMessage": "Daha az ögeyle tekrar deneyin.",
+ "@tooManyItemsErrorDialogMessage": {}
}
diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb
index e106e51cf..37a0c5579 100644
--- a/lib/l10n/app_uk.arb
+++ b/lib/l10n/app_uk.arb
@@ -203,7 +203,7 @@
"@coordinateDmsWest": {},
"unitSystemMetric": "Метричні",
"@unitSystemMetric": {},
- "unitSystemImperial": "Імперські",
+ "unitSystemImperial": "Англійські",
"@unitSystemImperial": {},
"videoLoopModeNever": "Ніколи",
"@videoLoopModeNever": {},
@@ -267,9 +267,9 @@
"@wallpaperTargetLock": {},
"viewerTransitionNone": "Нічого",
"@viewerTransitionNone": {},
- "widgetDisplayedItemRandom": "Випадкові",
+ "widgetDisplayedItemRandom": "Випадковий",
"@widgetDisplayedItemRandom": {},
- "widgetDisplayedItemMostRecent": "Нещодавні",
+ "widgetDisplayedItemMostRecent": "Нещодавний",
"@widgetDisplayedItemMostRecent": {},
"widgetOpenPageHome": "Відкрити головну сторінку",
"@widgetOpenPageHome": {},
@@ -459,7 +459,7 @@
"@coverDialogTabApp": {},
"coverDialogTabColor": "Колір",
"@coverDialogTabColor": {},
- "appPickDialogTitle": "Вибрати Додаток",
+ "appPickDialogTitle": "Вибрати додаток",
"@appPickDialogTitle": {},
"appPickDialogNone": "Нічого",
"@appPickDialogNone": {},
@@ -657,7 +657,7 @@
"minutes": {}
}
},
- "doubleBackExitMessage": "Натисніть «назад» ще раз, щоб вийти.",
+ "doubleBackExitMessage": "Натисніть “назад” ще раз, щоб вийти.",
"@doubleBackExitMessage": {},
"actionRemove": "Видалити",
"@actionRemove": {},
@@ -729,9 +729,9 @@
"@widgetOpenPageCollection": {},
"accessibilityAnimationsKeep": "Зберегти екранні ефекти",
"@accessibilityAnimationsKeep": {},
- "displayRefreshRatePreferHighest": "Найвищий рейтинг",
+ "displayRefreshRatePreferHighest": "Найвища частота",
"@displayRefreshRatePreferHighest": {},
- "displayRefreshRatePreferLowest": "Найнижчий рейтинг",
+ "displayRefreshRatePreferLowest": "Найнижча частота",
"@displayRefreshRatePreferLowest": {},
"viewerTransitionSlide": "Ковзання",
"@viewerTransitionSlide": {},
@@ -781,9 +781,9 @@
},
"videoStartOverButtonLabel": "ВІДТВОРИТИ СПОЧАТКУ",
"@videoStartOverButtonLabel": {},
- "newAlbumDialogTitle": "Новий Альбом",
+ "newAlbumDialogTitle": "Новий альбом",
"@newAlbumDialogTitle": {},
- "newAlbumDialogNameLabel": "Назва Альбому",
+ "newAlbumDialogNameLabel": "Назва альбому",
"@newAlbumDialogNameLabel": {},
"hideFilterConfirmationDialogMessage": "Відповідні фотографії та відео будуть приховані з вашої колекції. Ви можете показати їх знову в налаштуваннях у розділі \"Конфіденційність\".\n\nВи впевнені, що хочете їх приховати?",
"@hideFilterConfirmationDialogMessage": {},
@@ -795,7 +795,7 @@
"count": {}
}
},
- "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Видалити цей альбом і його елемент?} few{Видалити цей альбом і {count} елементи?} other{Видалити цей альбом і {count} елементів?}}",
+ "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Видалити цей альбом і елемент у ньому?} few{Видалити цей альбом і {count} елементи у ньому?} other{Видалити цей альбом і {count} елементів у ньому?}}",
"@deleteSingleAlbumConfirmationDialogMessage": {
"placeholders": {
"count": {}
@@ -803,7 +803,7 @@
},
"nameConflictDialogSingleSourceMessage": "Деякі файли в папці призначення мають одну й ту саму назву.",
"@nameConflictDialogSingleSourceMessage": {},
- "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Видалити ці альбоми та їх елементи?} few{Видалити ці альбоми та їх {count} елементи?} other{Видалити ці альбоми та їх {count} елементів?}}",
+ "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Видалити ці альбоми та елемент в них?} few{Видалити ці альбоми та {count} елементи в них?} other{Видалити ці альбоми та {count} елементів в них?}}",
"@deleteMultiAlbumConfirmationDialogMessage": {
"placeholders": {
"count": {}
@@ -923,11 +923,11 @@
"@settingsShowBottomNavigationBar": {},
"settingsConfirmationTile": "Діалоги підтвердження",
"@settingsConfirmationTile": {},
- "settingsConfirmationDialogTitle": "Діалоги Підтвердження",
+ "settingsConfirmationDialogTitle": "Діалоги підтвердження",
"@settingsConfirmationDialogTitle": {},
- "settingsConfirmationBeforeDeleteItems": "Запитати, перш ніж видаляти предмети назавжди",
+ "settingsConfirmationBeforeDeleteItems": "Запитати, перш ніж видаляти елементи назавжди",
"@settingsConfirmationBeforeDeleteItems": {},
- "settingsConfirmationBeforeMoveToBinItems": "Запитати перед тим, як переносити предмети до кошика",
+ "settingsConfirmationBeforeMoveToBinItems": "Запитати перед тим, як переносити елементи до кошика",
"@settingsConfirmationBeforeMoveToBinItems": {},
"settingsNavigationDrawerTabPages": "Сторінки",
"@settingsNavigationDrawerTabPages": {},
@@ -945,7 +945,7 @@
"@settingsThumbnailShowRawIcon": {},
"settingsThumbnailShowVideoDuration": "Показати тривалість відео",
"@settingsThumbnailShowVideoDuration": {},
- "settingsCollectionQuickActionEditorPageTitle": "Швидкі Дії",
+ "settingsCollectionQuickActionEditorPageTitle": "Швидкі дії",
"@settingsCollectionQuickActionEditorPageTitle": {},
"settingsCollectionQuickActionTabBrowsing": "Перегляд",
"@settingsCollectionQuickActionTabBrowsing": {},
@@ -965,7 +965,7 @@
"@settingsImageBackground": {},
"settingsViewerQuickActionsTile": "Швидкі дії",
"@settingsViewerQuickActionsTile": {},
- "settingsViewerQuickActionEditorPageTitle": "Швидкі Дії",
+ "settingsViewerQuickActionEditorPageTitle": "Швидкі дії",
"@settingsViewerQuickActionEditorPageTitle": {},
"settingsViewerQuickActionEditorBanner": "Торкніться і утримуйте для переміщення кнопок і вибору дій, які відображатимуться у переглядачі.",
"@settingsViewerQuickActionEditorBanner": {},
@@ -1005,7 +1005,7 @@
"@settingsSlideshowIntervalTile": {},
"settingsSlideshowVideoPlaybackTile": "Відтворення відео",
"@settingsSlideshowVideoPlaybackTile": {},
- "settingsSlideshowVideoPlaybackDialogTitle": "Відтворення Відео",
+ "settingsSlideshowVideoPlaybackDialogTitle": "Відтворення відео",
"@settingsSlideshowVideoPlaybackDialogTitle": {},
"settingsVideoPageTitle": "Налаштування Відео",
"@settingsVideoPageTitle": {},
@@ -1015,13 +1015,13 @@
"@settingsVideoEnableHardwareAcceleration": {},
"settingsVideoAutoPlay": "Автоматичне відтворення",
"@settingsVideoAutoPlay": {},
- "settingsVideoLoopModeDialogTitle": "Циклічний Режим",
+ "settingsVideoLoopModeDialogTitle": "Циклічний режим",
"@settingsVideoLoopModeDialogTitle": {},
"settingsSubtitleThemeTile": "Субтитри",
"@settingsSubtitleThemeTile": {},
"settingsSubtitleThemePageTitle": "Субтитри",
"@settingsSubtitleThemePageTitle": {},
- "settingsSubtitleThemeTextAlignmentDialogTitle": "Вирівнювання Тексту",
+ "settingsSubtitleThemeTextAlignmentDialogTitle": "Вирівнювання тексту",
"@settingsSubtitleThemeTextAlignmentDialogTitle": {},
"settingsSubtitleThemeTextPositionTile": "Положення тексту",
"@settingsSubtitleThemeTextPositionTile": {},
@@ -1047,7 +1047,7 @@
"@settingsVideoGestureDoubleTapTogglePlay": {},
"settingsVideoGestureSideDoubleTapSeek": "Подвійне натискання на краї екрану для переходу назад/вперед",
"@settingsVideoGestureSideDoubleTapSeek": {},
- "settingsAllowErrorReporting": "Дозволити анонімну відправку повідомлення про помилки",
+ "settingsAllowErrorReporting": "Дозволити анонімну відправку повідомлень про помилки",
"@settingsAllowErrorReporting": {},
"settingsSaveSearchHistory": "Зберігати історію пошуку",
"@settingsSaveSearchHistory": {},
@@ -1057,21 +1057,21 @@
"@settingsAllowMediaManagement": {},
"settingsHiddenItemsTile": "Приховані елементи",
"@settingsHiddenItemsTile": {},
- "settingsHiddenItemsPageTitle": "Приховані Елементи",
+ "settingsHiddenItemsPageTitle": "Приховані елементи",
"@settingsHiddenItemsPageTitle": {},
- "settingsHiddenItemsTabFilters": "Приховані Фільтри",
+ "settingsHiddenItemsTabFilters": "Приховані фільтри",
"@settingsHiddenItemsTabFilters": {},
"settingsHiddenFiltersBanner": "Фотографії та відео, що відповідають прихованим фільтрам, не з'являться у вашій колекції.",
"@settingsHiddenFiltersBanner": {},
"settingsHiddenFiltersEmpty": "Немає прихованих фільтрів",
"@settingsHiddenFiltersEmpty": {},
- "settingsHiddenItemsTabPaths": "Приховані Шляхи",
+ "settingsHiddenItemsTabPaths": "Приховані шляхи",
"@settingsHiddenItemsTabPaths": {},
"addPathTooltip": "Додати шлях",
"@addPathTooltip": {},
"settingsStorageAccessTile": "Доступ до сховища",
"@settingsStorageAccessTile": {},
- "settingsStorageAccessPageTitle": "Доступ до Сховища",
+ "settingsStorageAccessPageTitle": "Доступ до сховища",
"@settingsStorageAccessPageTitle": {},
"settingsStorageAccessBanner": "Деякі каталоги вимагають явного надання доступу для зміни файлів в них. Ви можете переглянути тут каталоги, до яких ви раніше надавали доступ.",
"@settingsStorageAccessBanner": {},
@@ -1083,7 +1083,7 @@
"@settingsAccessibilitySectionTitle": {},
"settingsRemoveAnimationsTile": "Видалити анімації",
"@settingsRemoveAnimationsTile": {},
- "settingsRemoveAnimationsDialogTitle": "Видалити Анімації",
+ "settingsRemoveAnimationsDialogTitle": "Видалити анімації",
"@settingsRemoveAnimationsDialogTitle": {},
"settingsTimeToTakeActionTile": "Час на виконання",
"@settingsTimeToTakeActionTile": {},
@@ -1097,7 +1097,7 @@
"@settingsThemeEnableDynamicColor": {},
"settingsDisplayRefreshRateModeTile": "Частота оновлення дисплея",
"@settingsDisplayRefreshRateModeTile": {},
- "settingsLanguageSectionTitle": "Мова та Формати",
+ "settingsLanguageSectionTitle": "Мова та формати",
"@settingsLanguageSectionTitle": {},
"settingsLanguageTile": "Мова",
"@settingsLanguageTile": {},
@@ -1105,7 +1105,7 @@
"@settingsCoordinateFormatTile": {},
"settingsUnitSystemTile": "Одиниці виміру",
"@settingsUnitSystemTile": {},
- "settingsUnitSystemDialogTitle": "Одиниці Виміру",
+ "settingsUnitSystemDialogTitle": "Одиниці виміру",
"@settingsUnitSystemDialogTitle": {},
"settingsWidgetPageTitle": "Фоторамка",
"@settingsWidgetPageTitle": {},
@@ -1159,7 +1159,7 @@
"@viewerInfoLabelCoordinates": {},
"viewerInfoLabelAddress": "Адреса",
"@viewerInfoLabelAddress": {},
- "mapStyleDialogTitle": "Стиль Карти",
+ "mapStyleDialogTitle": "Стиль карти",
"@mapStyleDialogTitle": {},
"mapStyleTooltip": "Виберіть стиль карти",
"@mapStyleTooltip": {},
@@ -1197,7 +1197,7 @@
"@viewerInfoSearchSuggestionRights": {},
"wallpaperUseScrollEffect": "Використовувати ефект прокрутки на головному екрані",
"@wallpaperUseScrollEffect": {},
- "tagEditorPageTitle": "Редагування Тегів",
+ "tagEditorPageTitle": "Редагування тегів",
"@tagEditorPageTitle": {},
"tagEditorPageNewTagFieldLabel": "Новий тег",
"@tagEditorPageNewTagFieldLabel": {},
@@ -1245,7 +1245,7 @@
"@settingsActionImportDialogTitle": {},
"appExportFavourites": "Обране",
"@appExportFavourites": {},
- "settingsKeepScreenOnDialogTitle": "Тримати Екран Увімкненим",
+ "settingsKeepScreenOnDialogTitle": "Тримати екран увімкненим",
"@settingsKeepScreenOnDialogTitle": {},
"settingsActionExportDialogTitle": "Експорт",
"@settingsActionExportDialogTitle": {},
@@ -1279,15 +1279,15 @@
"@settingsNavigationDrawerTabTypes": {},
"settingsThumbnailOverlayPageTitle": "Накладення",
"@settingsThumbnailOverlayPageTitle": {},
- "settingsNavigationDrawerEditorPageTitle": "Навігаційне Меню",
+ "settingsNavigationDrawerEditorPageTitle": "Навігаційне меню",
"@settingsNavigationDrawerEditorPageTitle": {},
"settingsCollectionSelectionQuickActionEditorBanner": "Торкніться і утримуйте для переміщення кнопок і вибору дій, які будуть відображатися при виборі елементів.",
"@settingsCollectionSelectionQuickActionEditorBanner": {},
"settingsThumbnailSectionTitle": "Мініатюри",
"@settingsThumbnailSectionTitle": {},
- "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Відображувані Кнопки",
+ "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Відображувані кнопки",
"@settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": {},
- "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Доступні Кнопки",
+ "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Доступні кнопки",
"@settingsViewerQuickActionEditorAvailableButtonsSectionTitle": {},
"settingsSubtitleThemeTextAlignmentTile": "Вирівнювання тексту",
"@settingsSubtitleThemeTextAlignmentTile": {},
@@ -1307,7 +1307,7 @@
"@settingsVideoSectionTitle": {},
"settingsAllowInstalledAppAccessSubtitle": "Використовується для покращення відображення альбомів",
"@settingsAllowInstalledAppAccessSubtitle": {},
- "settingsSubtitleThemeTextPositionDialogTitle": "Положення Тексту",
+ "settingsSubtitleThemeTextPositionDialogTitle": "Положення тексту",
"@settingsSubtitleThemeTextPositionDialogTitle": {},
"settingsThumbnailShowRating": "Показати рейтинг",
"@settingsThumbnailShowRating": {},
@@ -1337,9 +1337,9 @@
"count": {}
}
},
- "settingsDisplayRefreshRateModeDialogTitle": "Частота Оновлення",
+ "settingsDisplayRefreshRateModeDialogTitle": "Частота оновлення",
"@settingsDisplayRefreshRateModeDialogTitle": {},
- "settingsCoordinateFormatDialogTitle": "Формат Координат",
+ "settingsCoordinateFormatDialogTitle": "Формат координат",
"@settingsCoordinateFormatDialogTitle": {},
"settingsScreenSaverPageTitle": "Заставка на Екран",
"@settingsScreenSaverPageTitle": {},
@@ -1354,5 +1354,15 @@
"placeholders": {
"count": {}
}
- }
+ },
+ "settingsViewerShowDescription": "Показати опис",
+ "@settingsViewerShowDescription": {},
+ "settingsModificationWarningDialogMessage": "Інші параметри будуть змінені.",
+ "@settingsModificationWarningDialogMessage": {},
+ "settingsDisplayUseTvInterface": "Інтерфейс Android TV",
+ "@settingsDisplayUseTvInterface": {},
+ "filterLocatedLabel": "Розташований",
+ "@filterLocatedLabel": {},
+ "filterTaggedLabel": "Позначений тегом",
+ "@filterTaggedLabel": {}
}
diff --git a/lib/model/actions/map_actions.dart b/lib/model/actions/map_actions.dart
new file mode 100644
index 000000000..07cc9189a
--- /dev/null
+++ b/lib/model/actions/map_actions.dart
@@ -0,0 +1,35 @@
+import 'package:aves/theme/icons.dart';
+import 'package:aves/widgets/common/extensions/build_context.dart';
+import 'package:flutter/widgets.dart';
+
+enum MapAction {
+ selectStyle,
+ zoomIn,
+ zoomOut,
+}
+
+extension ExtraMapAction on MapAction {
+ String getText(BuildContext context) {
+ switch (this) {
+ case MapAction.selectStyle:
+ return context.l10n.mapStyleTooltip;
+ case MapAction.zoomIn:
+ return context.l10n.mapZoomInTooltip;
+ case MapAction.zoomOut:
+ return context.l10n.mapZoomOutTooltip;
+ }
+ }
+
+ Widget getIcon() => Icon(_getIconData());
+
+ IconData _getIconData() {
+ switch (this) {
+ case MapAction.selectStyle:
+ return AIcons.layers;
+ case MapAction.zoomIn:
+ return AIcons.zoomIn;
+ case MapAction.zoomOut:
+ return AIcons.zoomOut;
+ }
+ }
+}
diff --git a/lib/model/device.dart b/lib/model/device.dart
index deefa5e6d..b69195128 100644
--- a/lib/model/device.dart
+++ b/lib/model/device.dart
@@ -27,8 +27,6 @@ class Device {
bool get isDynamicColorAvailable => _isDynamicColorAvailable;
- bool get isReadOnly => _isTelevision;
-
bool get isTelevision => _isTelevision;
bool get showPinShortcutFeedback => _showPinShortcutFeedback;
diff --git a/lib/model/entry.dart b/lib/model/entry.dart
index 86e1fcdce..6a9dc3625 100644
--- a/lib/model/entry.dart
+++ b/lib/model/entry.dart
@@ -3,7 +3,6 @@ import 'dart:io';
import 'dart:ui';
import 'package:aves/geo/countries.dart';
-import 'package:aves/model/device.dart';
import 'package:aves/model/entry_cache.dart';
import 'package:aves/model/entry_dirs.dart';
import 'package:aves/model/favourites.dart';
@@ -12,6 +11,7 @@ import 'package:aves/model/metadata/address.dart';
import 'package:aves/model/metadata/catalog.dart';
import 'package:aves/model/metadata/trash.dart';
import 'package:aves/model/multipage.dart';
+import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/trash.dart';
import 'package:aves/model/video/metadata.dart';
import 'package:aves/ref/mime_types.dart';
@@ -281,7 +281,7 @@ class AvesEntry {
bool get isMediaStoreMediaContent => isMediaStoreContent && {'/external/images/', '/external/video/'}.any(uri.contains);
- bool get canEdit => !device.isReadOnly && path != null && !trashed && isMediaStoreContent;
+ bool get canEdit => !settings.isReadOnly && path != null && !trashed && isMediaStoreContent;
bool get canEditDate => canEdit && (canEditExif || canEditXmp);
diff --git a/lib/model/entry_metadata_edition.dart b/lib/model/entry_metadata_edition.dart
index 21ba5d001..7aa91fae6 100644
--- a/lib/model/entry_metadata_edition.dart
+++ b/lib/model/entry_metadata_edition.dart
@@ -235,7 +235,10 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
final description = fields[DescriptionField.description];
if (canEditExif && editDescription) {
- metadata[MetadataType.exif] = {MetadataField.exifImageDescription.toPlatform!: description};
+ metadata[MetadataType.exif] = {
+ MetadataField.exifImageDescription.toPlatform!: null,
+ MetadataField.exifUserComment.toPlatform!: null,
+ };
}
if (canEditIptc) {
diff --git a/lib/model/metadata/fields.dart b/lib/model/metadata/fields.dart
index 552909243..17a344aa6 100644
--- a/lib/model/metadata/fields.dart
+++ b/lib/model/metadata/fields.dart
@@ -37,6 +37,7 @@ enum MetadataField {
exifGpsTrackRef,
exifGpsVersionId,
exifImageDescription,
+ exifUserComment,
mp4GpsCoordinates,
mp4RotationDegrees,
mp4Xmp,
@@ -119,6 +120,7 @@ extension ExtraMetadataField on MetadataField {
case MetadataField.exifGpsTrackRef:
case MetadataField.exifGpsVersionId:
case MetadataField.exifImageDescription:
+ case MetadataField.exifUserComment:
return MetadataType.exif;
case MetadataField.mp4GpsCoordinates:
case MetadataField.mp4RotationDegrees:
@@ -220,6 +222,8 @@ extension ExtraMetadataField on MetadataField {
return 'GPSVersionID';
case MetadataField.exifImageDescription:
return 'ImageDescription';
+ case MetadataField.exifUserComment:
+ return 'UserComment';
default:
return null;
}
diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart
index db0c59c8d..1dbcc1a18 100644
--- a/lib/model/settings/defaults.dart
+++ b/lib/model/settings/defaults.dart
@@ -24,6 +24,7 @@ class SettingsDefaults {
static const themeColorMode = AvesThemeColorMode.polychrome;
static const enableDynamicColor = false;
static const enableBlurEffect = true; // `enableBlurEffect` has a contextual default value
+ static const forceTvLayout = false;
// navigation
static const mustBackTwiceToExit = true;
@@ -56,8 +57,8 @@ class SettingsDefaults {
EntrySetAction.delete,
];
static const showThumbnailFavourite = true;
- static const showThumbnailTag = false;
- static const showThumbnailLocation = true;
+ static const thumbnailLocationIcon = ThumbnailOverlayLocationIcon.none;
+ static const thumbnailTagIcon = ThumbnailOverlayTagIcon.none;
static const showThumbnailMotionPhoto = true;
static const showThumbnailRating = true;
static const showThumbnailRaw = true;
@@ -79,6 +80,7 @@ class SettingsDefaults {
static const showOverlayOnOpening = true;
static const showOverlayMinimap = false;
static const showOverlayInfo = true;
+ static const showOverlayDescription = false;
static const showOverlayRatingTags = false;
static const showOverlayShootingDetails = false;
static const showOverlayThumbnailPreview = false;
diff --git a/lib/model/settings/enums/enums.dart b/lib/model/settings/enums/enums.dart
index ab1f42f9c..96f7fd39d 100644
--- a/lib/model/settings/enums/enums.dart
+++ b/lib/model/settings/enums/enums.dart
@@ -22,6 +22,10 @@ enum SlideshowVideoPlayback { skip, playMuted, playWithSound }
enum SubtitlePosition { top, bottom }
+enum ThumbnailOverlayLocationIcon { located, unlocated, none }
+
+enum ThumbnailOverlayTagIcon { tagged, untagged, none }
+
enum UnitSystem { metric, imperial }
enum VideoControls { play, playSeek, playOutside, none }
diff --git a/lib/model/settings/enums/thumbnail_overlay_location_icon.dart b/lib/model/settings/enums/thumbnail_overlay_location_icon.dart
new file mode 100644
index 000000000..a0f76c54e
--- /dev/null
+++ b/lib/model/settings/enums/thumbnail_overlay_location_icon.dart
@@ -0,0 +1,29 @@
+import 'package:aves/theme/icons.dart';
+import 'package:aves/widgets/common/extensions/build_context.dart';
+import 'package:flutter/material.dart';
+
+import 'enums.dart';
+
+extension ExtraThumbnailOverlayLocationIcon on ThumbnailOverlayLocationIcon {
+ String getName(BuildContext context) {
+ switch (this) {
+ case ThumbnailOverlayLocationIcon.located:
+ return context.l10n.filterLocatedLabel;
+ case ThumbnailOverlayLocationIcon.unlocated:
+ return context.l10n.filterNoLocationLabel;
+ case ThumbnailOverlayLocationIcon.none:
+ return context.l10n.settingsDisabled;
+ }
+ }
+
+ IconData getIcon(BuildContext context) {
+ switch (this) {
+ case ThumbnailOverlayLocationIcon.located:
+ return AIcons.location;
+ case ThumbnailOverlayLocationIcon.unlocated:
+ return AIcons.locationUnlocated;
+ case ThumbnailOverlayLocationIcon.none:
+ return AIcons.location;
+ }
+ }
+}
diff --git a/lib/model/settings/enums/thumbnail_overlay_tag_icon.dart b/lib/model/settings/enums/thumbnail_overlay_tag_icon.dart
new file mode 100644
index 000000000..00f006ec9
--- /dev/null
+++ b/lib/model/settings/enums/thumbnail_overlay_tag_icon.dart
@@ -0,0 +1,29 @@
+import 'package:aves/theme/icons.dart';
+import 'package:aves/widgets/common/extensions/build_context.dart';
+import 'package:flutter/material.dart';
+
+import 'enums.dart';
+
+extension ExtraThumbnailOverlayTagIcon on ThumbnailOverlayTagIcon {
+ String getName(BuildContext context) {
+ switch (this) {
+ case ThumbnailOverlayTagIcon.tagged:
+ return context.l10n.filterTaggedLabel;
+ case ThumbnailOverlayTagIcon.untagged:
+ return context.l10n.filterNoTagLabel;
+ case ThumbnailOverlayTagIcon.none:
+ return context.l10n.settingsDisabled;
+ }
+ }
+
+ IconData getIcon(BuildContext context) {
+ switch (this) {
+ case ThumbnailOverlayTagIcon.tagged:
+ return AIcons.tag;
+ case ThumbnailOverlayTagIcon.untagged:
+ return AIcons.tagUntagged;
+ case ThumbnailOverlayTagIcon.none:
+ return AIcons.tag;
+ }
+ }
+}
diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart
index aa747b127..27c944d82 100644
--- a/lib/model/settings/settings.dart
+++ b/lib/model/settings/settings.dart
@@ -13,6 +13,7 @@ import 'package:aves/model/settings/defaults.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/map_style.dart';
import 'package:aves/model/source/enums/enums.dart';
+import 'package:aves/services/accessibility_service.dart';
import 'package:aves/services/common/optional_event_channel.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/widgets/aves_app.dart';
@@ -70,6 +71,7 @@ class Settings extends ChangeNotifier {
static const themeColorModeKey = 'theme_color_mode';
static const enableDynamicColorKey = 'dynamic_color';
static const enableBlurEffectKey = 'enable_overlay_blur_effect';
+ static const forceTvLayoutKey = 'force_tv_layout';
// navigation
static const mustBackTwiceToExitKey = 'must_back_twice_to_exit';
@@ -92,8 +94,8 @@ class Settings extends ChangeNotifier {
static const collectionBrowsingQuickActionsKey = 'collection_browsing_quick_actions';
static const collectionSelectionQuickActionsKey = 'collection_selection_quick_actions';
static const showThumbnailFavouriteKey = 'show_thumbnail_favourite';
- static const showThumbnailTagKey = 'show_thumbnail_tag';
- static const showThumbnailLocationKey = 'show_thumbnail_location';
+ static const thumbnailLocationIconKey = 'thumbnail_location_icon';
+ static const thumbnailTagIconKey = 'thumbnail_tag_icon';
static const showThumbnailMotionPhotoKey = 'show_thumbnail_motion_photo';
static const showThumbnailRatingKey = 'show_thumbnail_rating';
static const showThumbnailRawKey = 'show_thumbnail_raw';
@@ -115,6 +117,7 @@ class Settings extends ChangeNotifier {
static const showOverlayOnOpeningKey = 'show_overlay_on_opening';
static const showOverlayMinimapKey = 'show_overlay_minimap';
static const showOverlayInfoKey = 'show_overlay_info';
+ static const showOverlayDescriptionKey = 'show_overlay_description';
static const showOverlayRatingTagsKey = 'show_overlay_rating_tags';
static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details';
static const showOverlayThumbnailPreviewKey = 'show_overlay_thumbnail_preview';
@@ -239,7 +242,11 @@ class Settings extends ChangeNotifier {
}
}
- if (device.isTelevision) {
+ applyTvSettings();
+ }
+
+ void applyTvSettings() {
+ if (settings.useTvLayout) {
themeBrightness = AvesThemeBrightness.dark;
mustBackTwiceToExit = false;
// address `TV-BU` / `TV-BY` requirements from https://developer.android.com/docs/quality-guidelines/tv-app-quality
@@ -270,23 +277,32 @@ class Settings extends ChangeNotifier {
}
}
+ Future sanitize() async {
+ if (timeToTakeAction == AccessibilityTimeout.system && !(await AccessibilityService.hasRecommendedTimeouts())) {
+ _set(timeToTakeActionKey, null);
+ }
+ if (viewerUseCutout != SettingsDefaults.viewerUseCutout && !(await windowService.isCutoutAware())) {
+ _set(viewerUseCutoutKey, null);
+ }
+ }
+
// app
bool get hasAcceptedTerms => getBool(hasAcceptedTermsKey) ?? SettingsDefaults.hasAcceptedTerms;
- set hasAcceptedTerms(bool newValue) => setAndNotify(hasAcceptedTermsKey, newValue);
+ set hasAcceptedTerms(bool newValue) => _set(hasAcceptedTermsKey, newValue);
bool get canUseAnalysisService => getBool(canUseAnalysisServiceKey) ?? SettingsDefaults.canUseAnalysisService;
- set canUseAnalysisService(bool newValue) => setAndNotify(canUseAnalysisServiceKey, newValue);
+ set canUseAnalysisService(bool newValue) => _set(canUseAnalysisServiceKey, newValue);
bool get isInstalledAppAccessAllowed => getBool(isInstalledAppAccessAllowedKey) ?? SettingsDefaults.isInstalledAppAccessAllowed;
- set isInstalledAppAccessAllowed(bool newValue) => setAndNotify(isInstalledAppAccessAllowedKey, newValue);
+ set isInstalledAppAccessAllowed(bool newValue) => _set(isInstalledAppAccessAllowedKey, newValue);
bool get isErrorReportingAllowed => getBool(isErrorReportingAllowedKey) ?? SettingsDefaults.isErrorReportingAllowed;
- set isErrorReportingAllowed(bool newValue) => setAndNotify(isErrorReportingAllowedKey, newValue);
+ set isErrorReportingAllowed(bool newValue) => _set(isErrorReportingAllowedKey, newValue);
static const localeSeparator = '-';
@@ -313,7 +329,7 @@ class Settings extends ChangeNotifier {
newValue.countryCode ?? '',
].join(localeSeparator);
}
- setAndNotify(localeKey, tag);
+ _set(localeKey, tag);
_appliedLocale = null;
}
@@ -343,91 +359,99 @@ class Settings extends ChangeNotifier {
String get catalogTimeZone => getString(catalogTimeZoneKey) ?? '';
- set catalogTimeZone(String newValue) => setAndNotify(catalogTimeZoneKey, newValue);
+ set catalogTimeZone(String newValue) => _set(catalogTimeZoneKey, newValue);
double getTileExtent(String routeName) => getDouble(tileExtentPrefixKey + routeName) ?? 0;
- void setTileExtent(String routeName, double newValue) => setAndNotify(tileExtentPrefixKey + routeName, newValue);
+ void setTileExtent(String routeName, double newValue) => _set(tileExtentPrefixKey + routeName, newValue);
TileLayout getTileLayout(String routeName) => getEnumOrDefault(tileLayoutPrefixKey + routeName, SettingsDefaults.tileLayout, TileLayout.values);
- void setTileLayout(String routeName, TileLayout newValue) => setAndNotify(tileLayoutPrefixKey + routeName, newValue.toString());
+ void setTileLayout(String routeName, TileLayout newValue) => _set(tileLayoutPrefixKey + routeName, newValue.toString());
String get entryRenamingPattern => getString(entryRenamingPatternKey) ?? SettingsDefaults.entryRenamingPattern;
- set entryRenamingPattern(String newValue) => setAndNotify(entryRenamingPatternKey, newValue);
+ set entryRenamingPattern(String newValue) => _set(entryRenamingPatternKey, newValue);
List? get topEntryIds => getStringList(topEntryIdsKey)?.map(int.tryParse).whereNotNull().toList();
- set topEntryIds(List? newValue) => setAndNotify(topEntryIdsKey, newValue?.map((id) => id.toString()).whereNotNull().toList());
+ set topEntryIds(List? newValue) => _set(topEntryIdsKey, newValue?.map((id) => id.toString()).whereNotNull().toList());
List get recentDestinationAlbums => getStringList(recentDestinationAlbumsKey) ?? [];
- set recentDestinationAlbums(List newValue) => setAndNotify(recentDestinationAlbumsKey, newValue.take(_recentFilterHistoryMax).toList());
+ set recentDestinationAlbums(List newValue) => _set(recentDestinationAlbumsKey, newValue.take(_recentFilterHistoryMax).toList());
List get recentTags => (getStringList(recentTagsKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toList();
- set recentTags(List newValue) => setAndNotify(recentTagsKey, newValue.take(_recentFilterHistoryMax).map((filter) => filter.toJson()).toList());
+ set recentTags(List newValue) => _set(recentTagsKey, newValue.take(_recentFilterHistoryMax).map((filter) => filter.toJson()).toList());
// display
DisplayRefreshRateMode get displayRefreshRateMode => getEnumOrDefault(displayRefreshRateModeKey, SettingsDefaults.displayRefreshRateMode, DisplayRefreshRateMode.values);
- set displayRefreshRateMode(DisplayRefreshRateMode newValue) => setAndNotify(displayRefreshRateModeKey, newValue.toString());
+ set displayRefreshRateMode(DisplayRefreshRateMode newValue) => _set(displayRefreshRateModeKey, newValue.toString());
AvesThemeBrightness get themeBrightness => getEnumOrDefault(themeBrightnessKey, SettingsDefaults.themeBrightness, AvesThemeBrightness.values);
- set themeBrightness(AvesThemeBrightness newValue) => setAndNotify(themeBrightnessKey, newValue.toString());
+ set themeBrightness(AvesThemeBrightness newValue) => _set(themeBrightnessKey, newValue.toString());
AvesThemeColorMode get themeColorMode => getEnumOrDefault(themeColorModeKey, SettingsDefaults.themeColorMode, AvesThemeColorMode.values);
- set themeColorMode(AvesThemeColorMode newValue) => setAndNotify(themeColorModeKey, newValue.toString());
+ set themeColorMode(AvesThemeColorMode newValue) => _set(themeColorModeKey, newValue.toString());
bool get enableDynamicColor => getBool(enableDynamicColorKey) ?? SettingsDefaults.enableDynamicColor;
- set enableDynamicColor(bool newValue) => setAndNotify(enableDynamicColorKey, newValue);
+ set enableDynamicColor(bool newValue) => _set(enableDynamicColorKey, newValue);
bool get enableBlurEffect => getBool(enableBlurEffectKey) ?? SettingsDefaults.enableBlurEffect;
- set enableBlurEffect(bool newValue) => setAndNotify(enableBlurEffectKey, newValue);
+ set enableBlurEffect(bool newValue) => _set(enableBlurEffectKey, newValue);
+
+ bool get forceTvLayout => getBool(forceTvLayoutKey) ?? SettingsDefaults.forceTvLayout;
+
+ set forceTvLayout(bool newValue) => _set(forceTvLayoutKey, newValue);
+
+ bool get useTvLayout => device.isTelevision || forceTvLayout;
+
+ bool get isReadOnly => useTvLayout;
// navigation
bool get mustBackTwiceToExit => getBool(mustBackTwiceToExitKey) ?? SettingsDefaults.mustBackTwiceToExit;
- set mustBackTwiceToExit(bool newValue) => setAndNotify(mustBackTwiceToExitKey, newValue);
+ set mustBackTwiceToExit(bool newValue) => _set(mustBackTwiceToExitKey, newValue);
KeepScreenOn get keepScreenOn => getEnumOrDefault(keepScreenOnKey, SettingsDefaults.keepScreenOn, KeepScreenOn.values);
- set keepScreenOn(KeepScreenOn newValue) => setAndNotify(keepScreenOnKey, newValue.toString());
+ set keepScreenOn(KeepScreenOn newValue) => _set(keepScreenOnKey, newValue.toString());
HomePageSetting get homePage => getEnumOrDefault(homePageKey, SettingsDefaults.homePage, HomePageSetting.values);
- set homePage(HomePageSetting newValue) => setAndNotify(homePageKey, newValue.toString());
+ set homePage(HomePageSetting newValue) => _set(homePageKey, newValue.toString());
bool get enableBottomNavigationBar => getBool(enableBottomNavigationBarKey) ?? SettingsDefaults.enableBottomNavigationBar;
- set enableBottomNavigationBar(bool newValue) => setAndNotify(enableBottomNavigationBarKey, newValue);
+ set enableBottomNavigationBar(bool newValue) => _set(enableBottomNavigationBarKey, newValue);
bool get confirmDeleteForever => getBool(confirmDeleteForeverKey) ?? SettingsDefaults.confirmDeleteForever;
- set confirmDeleteForever(bool newValue) => setAndNotify(confirmDeleteForeverKey, newValue);
+ set confirmDeleteForever(bool newValue) => _set(confirmDeleteForeverKey, newValue);
bool get confirmMoveToBin => getBool(confirmMoveToBinKey) ?? SettingsDefaults.confirmMoveToBin;
- set confirmMoveToBin(bool newValue) => setAndNotify(confirmMoveToBinKey, newValue);
+ set confirmMoveToBin(bool newValue) => _set(confirmMoveToBinKey, newValue);
bool get confirmMoveUndatedItems => getBool(confirmMoveUndatedItemsKey) ?? SettingsDefaults.confirmMoveUndatedItems;
- set confirmMoveUndatedItems(bool newValue) => setAndNotify(confirmMoveUndatedItemsKey, newValue);
+ set confirmMoveUndatedItems(bool newValue) => _set(confirmMoveUndatedItemsKey, newValue);
bool get confirmAfterMoveToBin => getBool(confirmAfterMoveToBinKey) ?? SettingsDefaults.confirmAfterMoveToBin;
- set confirmAfterMoveToBin(bool newValue) => setAndNotify(confirmAfterMoveToBinKey, newValue);
+ set confirmAfterMoveToBin(bool newValue) => _set(confirmAfterMoveToBinKey, newValue);
bool get setMetadataDateBeforeFileOp => getBool(setMetadataDateBeforeFileOpKey) ?? SettingsDefaults.setMetadataDateBeforeFileOp;
- set setMetadataDateBeforeFileOp(bool newValue) => setAndNotify(setMetadataDateBeforeFileOpKey, newValue);
+ set setMetadataDateBeforeFileOp(bool newValue) => _set(setMetadataDateBeforeFileOpKey, newValue);
List get drawerTypeBookmarks =>
(getStringList(drawerTypeBookmarksKey))?.map((v) {
@@ -436,103 +460,103 @@ class Settings extends ChangeNotifier {
}).toList() ??
SettingsDefaults.drawerTypeBookmarks;
- set drawerTypeBookmarks(List newValue) => setAndNotify(drawerTypeBookmarksKey, newValue.map((filter) => filter?.toJson() ?? '').toList());
+ set drawerTypeBookmarks(List newValue) => _set(drawerTypeBookmarksKey, newValue.map((filter) => filter?.toJson() ?? '').toList());
List? get drawerAlbumBookmarks => getStringList(drawerAlbumBookmarksKey);
- set drawerAlbumBookmarks(List? newValue) => setAndNotify(drawerAlbumBookmarksKey, newValue);
+ set drawerAlbumBookmarks(List? newValue) => _set(drawerAlbumBookmarksKey, newValue);
List get drawerPageBookmarks => getStringList(drawerPageBookmarksKey) ?? SettingsDefaults.drawerPageBookmarks;
- set drawerPageBookmarks(List newValue) => setAndNotify(drawerPageBookmarksKey, newValue);
+ set drawerPageBookmarks(List newValue) => _set(drawerPageBookmarksKey, newValue);
// collection
EntryGroupFactor get collectionSectionFactor => getEnumOrDefault(collectionGroupFactorKey, SettingsDefaults.collectionSectionFactor, EntryGroupFactor.values);
- set collectionSectionFactor(EntryGroupFactor newValue) => setAndNotify(collectionGroupFactorKey, newValue.toString());
+ set collectionSectionFactor(EntryGroupFactor newValue) => _set(collectionGroupFactorKey, newValue.toString());
EntrySortFactor get collectionSortFactor => getEnumOrDefault(collectionSortFactorKey, SettingsDefaults.collectionSortFactor, EntrySortFactor.values);
- set collectionSortFactor(EntrySortFactor newValue) => setAndNotify(collectionSortFactorKey, newValue.toString());
+ set collectionSortFactor(EntrySortFactor newValue) => _set(collectionSortFactorKey, newValue.toString());
bool get collectionSortReverse => getBool(collectionSortReverseKey) ?? false;
- set collectionSortReverse(bool newValue) => setAndNotify(collectionSortReverseKey, newValue);
+ set collectionSortReverse(bool newValue) => _set(collectionSortReverseKey, newValue);
List get collectionBrowsingQuickActions => getEnumListOrDefault(collectionBrowsingQuickActionsKey, SettingsDefaults.collectionBrowsingQuickActions, EntrySetAction.values);
- set collectionBrowsingQuickActions(List newValue) => setAndNotify(collectionBrowsingQuickActionsKey, newValue.map((v) => v.toString()).toList());
+ set collectionBrowsingQuickActions(List newValue) => _set(collectionBrowsingQuickActionsKey, newValue.map((v) => v.toString()).toList());
List get collectionSelectionQuickActions => getEnumListOrDefault(collectionSelectionQuickActionsKey, SettingsDefaults.collectionSelectionQuickActions, EntrySetAction.values);
- set collectionSelectionQuickActions(List newValue) => setAndNotify(collectionSelectionQuickActionsKey, newValue.map((v) => v.toString()).toList());
+ set collectionSelectionQuickActions(List newValue) => _set(collectionSelectionQuickActionsKey, newValue.map((v) => v.toString()).toList());
bool get showThumbnailFavourite => getBool(showThumbnailFavouriteKey) ?? SettingsDefaults.showThumbnailFavourite;
- set showThumbnailFavourite(bool newValue) => setAndNotify(showThumbnailFavouriteKey, newValue);
+ set showThumbnailFavourite(bool newValue) => _set(showThumbnailFavouriteKey, newValue);
- bool get showThumbnailTag => getBool(showThumbnailTagKey) ?? SettingsDefaults.showThumbnailTag;
+ ThumbnailOverlayLocationIcon get thumbnailLocationIcon => getEnumOrDefault(thumbnailLocationIconKey, SettingsDefaults.thumbnailLocationIcon, ThumbnailOverlayLocationIcon.values);
- set showThumbnailTag(bool newValue) => setAndNotify(showThumbnailTagKey, newValue);
+ set thumbnailLocationIcon(ThumbnailOverlayLocationIcon newValue) => _set(thumbnailLocationIconKey, newValue.toString());
- bool get showThumbnailLocation => getBool(showThumbnailLocationKey) ?? SettingsDefaults.showThumbnailLocation;
+ ThumbnailOverlayTagIcon get thumbnailTagIcon => getEnumOrDefault(thumbnailTagIconKey, SettingsDefaults.thumbnailTagIcon, ThumbnailOverlayTagIcon.values);
- set showThumbnailLocation(bool newValue) => setAndNotify(showThumbnailLocationKey, newValue);
+ set thumbnailTagIcon(ThumbnailOverlayTagIcon newValue) => _set(thumbnailTagIconKey, newValue.toString());
bool get showThumbnailMotionPhoto => getBool(showThumbnailMotionPhotoKey) ?? SettingsDefaults.showThumbnailMotionPhoto;
- set showThumbnailMotionPhoto(bool newValue) => setAndNotify(showThumbnailMotionPhotoKey, newValue);
+ set showThumbnailMotionPhoto(bool newValue) => _set(showThumbnailMotionPhotoKey, newValue);
bool get showThumbnailRating => getBool(showThumbnailRatingKey) ?? SettingsDefaults.showThumbnailRating;
- set showThumbnailRating(bool newValue) => setAndNotify(showThumbnailRatingKey, newValue);
+ set showThumbnailRating(bool newValue) => _set(showThumbnailRatingKey, newValue);
bool get showThumbnailRaw => getBool(showThumbnailRawKey) ?? SettingsDefaults.showThumbnailRaw;
- set showThumbnailRaw(bool newValue) => setAndNotify(showThumbnailRawKey, newValue);
+ set showThumbnailRaw(bool newValue) => _set(showThumbnailRawKey, newValue);
bool get showThumbnailVideoDuration => getBool(showThumbnailVideoDurationKey) ?? SettingsDefaults.showThumbnailVideoDuration;
- set showThumbnailVideoDuration(bool newValue) => setAndNotify(showThumbnailVideoDurationKey, newValue);
+ set showThumbnailVideoDuration(bool newValue) => _set(showThumbnailVideoDurationKey, newValue);
// filter grids
AlbumChipGroupFactor get albumGroupFactor => getEnumOrDefault(albumGroupFactorKey, SettingsDefaults.albumGroupFactor, AlbumChipGroupFactor.values);
- set albumGroupFactor(AlbumChipGroupFactor newValue) => setAndNotify(albumGroupFactorKey, newValue.toString());
+ set albumGroupFactor(AlbumChipGroupFactor newValue) => _set(albumGroupFactorKey, newValue.toString());
ChipSortFactor get albumSortFactor => getEnumOrDefault(albumSortFactorKey, SettingsDefaults.albumSortFactor, ChipSortFactor.values);
- set albumSortFactor(ChipSortFactor newValue) => setAndNotify(albumSortFactorKey, newValue.toString());
+ set albumSortFactor(ChipSortFactor newValue) => _set(albumSortFactorKey, newValue.toString());
ChipSortFactor get countrySortFactor => getEnumOrDefault(countrySortFactorKey, SettingsDefaults.countrySortFactor, ChipSortFactor.values);
- set countrySortFactor(ChipSortFactor newValue) => setAndNotify(countrySortFactorKey, newValue.toString());
+ set countrySortFactor(ChipSortFactor newValue) => _set(countrySortFactorKey, newValue.toString());
ChipSortFactor get tagSortFactor => getEnumOrDefault(tagSortFactorKey, SettingsDefaults.tagSortFactor, ChipSortFactor.values);
- set tagSortFactor(ChipSortFactor newValue) => setAndNotify(tagSortFactorKey, newValue.toString());
+ set tagSortFactor(ChipSortFactor newValue) => _set(tagSortFactorKey, newValue.toString());
bool get albumSortReverse => getBool(albumSortReverseKey) ?? false;
- set albumSortReverse(bool newValue) => setAndNotify(albumSortReverseKey, newValue);
+ set albumSortReverse(bool newValue) => _set(albumSortReverseKey, newValue);
bool get countrySortReverse => getBool(countrySortReverseKey) ?? false;
- set countrySortReverse(bool newValue) => setAndNotify(countrySortReverseKey, newValue);
+ set countrySortReverse(bool newValue) => _set(countrySortReverseKey, newValue);
bool get tagSortReverse => getBool(tagSortReverseKey) ?? false;
- set tagSortReverse(bool newValue) => setAndNotify(tagSortReverseKey, newValue);
+ set tagSortReverse(bool newValue) => _set(tagSortReverseKey, newValue);
Set get pinnedFilters => (getStringList(pinnedFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet();
- set pinnedFilters(Set newValue) => setAndNotify(pinnedFiltersKey, newValue.map((filter) => filter.toJson()).toList());
+ set pinnedFilters(Set newValue) => _set(pinnedFiltersKey, newValue.map((filter) => filter.toJson()).toList());
Set get hiddenFilters => (getStringList(hiddenFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet();
- set hiddenFilters(Set newValue) => setAndNotify(hiddenFiltersKey, newValue.map((filter) => filter.toJson()).toList());
+ set hiddenFilters(Set newValue) => _set(hiddenFiltersKey, newValue.map((filter) => filter.toJson()).toList());
void changeFilterVisibility(Set filters, bool visible) {
final _hiddenFilters = hiddenFilters;
@@ -549,127 +573,131 @@ class Settings extends ChangeNotifier {
List get viewerQuickActions => getEnumListOrDefault(viewerQuickActionsKey, SettingsDefaults.viewerQuickActions, EntryAction.values);
- set viewerQuickActions(List newValue) => setAndNotify(viewerQuickActionsKey, newValue.map((v) => v.toString()).toList());
+ set viewerQuickActions(List newValue) => _set(viewerQuickActionsKey, newValue.map((v) => v.toString()).toList());
bool get showOverlayOnOpening => getBool(showOverlayOnOpeningKey) ?? SettingsDefaults.showOverlayOnOpening;
- set showOverlayOnOpening(bool newValue) => setAndNotify(showOverlayOnOpeningKey, newValue);
+ set showOverlayOnOpening(bool newValue) => _set(showOverlayOnOpeningKey, newValue);
bool get showOverlayMinimap => getBool(showOverlayMinimapKey) ?? SettingsDefaults.showOverlayMinimap;
- set showOverlayMinimap(bool newValue) => setAndNotify(showOverlayMinimapKey, newValue);
+ set showOverlayMinimap(bool newValue) => _set(showOverlayMinimapKey, newValue);
bool get showOverlayInfo => getBool(showOverlayInfoKey) ?? SettingsDefaults.showOverlayInfo;
- set showOverlayInfo(bool newValue) => setAndNotify(showOverlayInfoKey, newValue);
+ set showOverlayInfo(bool newValue) => _set(showOverlayInfoKey, newValue);
+
+ bool get showOverlayDescription => getBool(showOverlayDescriptionKey) ?? SettingsDefaults.showOverlayDescription;
+
+ set showOverlayDescription(bool newValue) => _set(showOverlayDescriptionKey, newValue);
bool get showOverlayRatingTags => getBool(showOverlayRatingTagsKey) ?? SettingsDefaults.showOverlayRatingTags;
- set showOverlayRatingTags(bool newValue) => setAndNotify(showOverlayRatingTagsKey, newValue);
+ set showOverlayRatingTags(bool newValue) => _set(showOverlayRatingTagsKey, newValue);
bool get showOverlayShootingDetails => getBool(showOverlayShootingDetailsKey) ?? SettingsDefaults.showOverlayShootingDetails;
- set showOverlayShootingDetails(bool newValue) => setAndNotify(showOverlayShootingDetailsKey, newValue);
+ set showOverlayShootingDetails(bool newValue) => _set(showOverlayShootingDetailsKey, newValue);
bool get showOverlayThumbnailPreview => getBool(showOverlayThumbnailPreviewKey) ?? SettingsDefaults.showOverlayThumbnailPreview;
- set showOverlayThumbnailPreview(bool newValue) => setAndNotify(showOverlayThumbnailPreviewKey, newValue);
+ set showOverlayThumbnailPreview(bool newValue) => _set(showOverlayThumbnailPreviewKey, newValue);
bool get viewerGestureSideTapNext => getBool(viewerGestureSideTapNextKey) ?? SettingsDefaults.viewerGestureSideTapNext;
- set viewerGestureSideTapNext(bool newValue) => setAndNotify(viewerGestureSideTapNextKey, newValue);
+ set viewerGestureSideTapNext(bool newValue) => _set(viewerGestureSideTapNextKey, newValue);
bool get viewerUseCutout => getBool(viewerUseCutoutKey) ?? SettingsDefaults.viewerUseCutout;
- set viewerUseCutout(bool newValue) => setAndNotify(viewerUseCutoutKey, newValue);
+ set viewerUseCutout(bool newValue) => _set(viewerUseCutoutKey, newValue);
bool get viewerMaxBrightness => getBool(viewerMaxBrightnessKey) ?? SettingsDefaults.viewerMaxBrightness;
- set viewerMaxBrightness(bool newValue) => setAndNotify(viewerMaxBrightnessKey, newValue);
+ set viewerMaxBrightness(bool newValue) => _set(viewerMaxBrightnessKey, newValue);
bool get enableMotionPhotoAutoPlay => getBool(enableMotionPhotoAutoPlayKey) ?? SettingsDefaults.enableMotionPhotoAutoPlay;
- set enableMotionPhotoAutoPlay(bool newValue) => setAndNotify(enableMotionPhotoAutoPlayKey, newValue);
+ set enableMotionPhotoAutoPlay(bool newValue) => _set(enableMotionPhotoAutoPlayKey, newValue);
EntryBackground get imageBackground => getEnumOrDefault(imageBackgroundKey, SettingsDefaults.imageBackground, EntryBackground.values);
- set imageBackground(EntryBackground newValue) => setAndNotify(imageBackgroundKey, newValue.toString());
+ set imageBackground(EntryBackground newValue) => _set(imageBackgroundKey, newValue.toString());
// video
bool get enableVideoHardwareAcceleration => getBool(enableVideoHardwareAccelerationKey) ?? SettingsDefaults.enableVideoHardwareAcceleration;
- set enableVideoHardwareAcceleration(bool newValue) => setAndNotify(enableVideoHardwareAccelerationKey, newValue);
+ set enableVideoHardwareAcceleration(bool newValue) => _set(enableVideoHardwareAccelerationKey, newValue);
VideoAutoPlayMode get videoAutoPlayMode => getEnumOrDefault(videoAutoPlayModeKey, SettingsDefaults.videoAutoPlayMode, VideoAutoPlayMode.values);
- set videoAutoPlayMode(VideoAutoPlayMode newValue) => setAndNotify(videoAutoPlayModeKey, newValue.toString());
+ set videoAutoPlayMode(VideoAutoPlayMode newValue) => _set(videoAutoPlayModeKey, newValue.toString());
VideoLoopMode get videoLoopMode => getEnumOrDefault(videoLoopModeKey, SettingsDefaults.videoLoopMode, VideoLoopMode.values);
- set videoLoopMode(VideoLoopMode newValue) => setAndNotify(videoLoopModeKey, newValue.toString());
+ set videoLoopMode(VideoLoopMode newValue) => _set(videoLoopModeKey, newValue.toString());
bool get videoShowRawTimedText => getBool(videoShowRawTimedTextKey) ?? SettingsDefaults.videoShowRawTimedText;
- set videoShowRawTimedText(bool newValue) => setAndNotify(videoShowRawTimedTextKey, newValue);
+ set videoShowRawTimedText(bool newValue) => _set(videoShowRawTimedTextKey, newValue);
VideoControls get videoControls => getEnumOrDefault(videoControlsKey, SettingsDefaults.videoControls, VideoControls.values);
- set videoControls(VideoControls newValue) => setAndNotify(videoControlsKey, newValue.toString());
+ set videoControls(VideoControls newValue) => _set(videoControlsKey, newValue.toString());
bool get videoGestureDoubleTapTogglePlay => getBool(videoGestureDoubleTapTogglePlayKey) ?? SettingsDefaults.videoGestureDoubleTapTogglePlay;
- set videoGestureDoubleTapTogglePlay(bool newValue) => setAndNotify(videoGestureDoubleTapTogglePlayKey, newValue);
+ set videoGestureDoubleTapTogglePlay(bool newValue) => _set(videoGestureDoubleTapTogglePlayKey, newValue);
bool get videoGestureSideDoubleTapSeek => getBool(videoGestureSideDoubleTapSeekKey) ?? SettingsDefaults.videoGestureSideDoubleTapSeek;
- set videoGestureSideDoubleTapSeek(bool newValue) => setAndNotify(videoGestureSideDoubleTapSeekKey, newValue);
+ set videoGestureSideDoubleTapSeek(bool newValue) => _set(videoGestureSideDoubleTapSeekKey, newValue);
// subtitles
double get subtitleFontSize => getDouble(subtitleFontSizeKey) ?? SettingsDefaults.subtitleFontSize;
- set subtitleFontSize(double newValue) => setAndNotify(subtitleFontSizeKey, newValue);
+ set subtitleFontSize(double newValue) => _set(subtitleFontSizeKey, newValue);
TextAlign get subtitleTextAlignment => getEnumOrDefault(subtitleTextAlignmentKey, SettingsDefaults.subtitleTextAlignment, TextAlign.values);
- set subtitleTextAlignment(TextAlign newValue) => setAndNotify(subtitleTextAlignmentKey, newValue.toString());
+ set subtitleTextAlignment(TextAlign newValue) => _set(subtitleTextAlignmentKey, newValue.toString());
SubtitlePosition get subtitleTextPosition => getEnumOrDefault(subtitleTextPositionKey, SettingsDefaults.subtitleTextPosition, SubtitlePosition.values);
- set subtitleTextPosition(SubtitlePosition newValue) => setAndNotify(subtitleTextPositionKey, newValue.toString());
+ set subtitleTextPosition(SubtitlePosition newValue) => _set(subtitleTextPositionKey, newValue.toString());
bool get subtitleShowOutline => getBool(subtitleShowOutlineKey) ?? SettingsDefaults.subtitleShowOutline;
- set subtitleShowOutline(bool newValue) => setAndNotify(subtitleShowOutlineKey, newValue);
+ set subtitleShowOutline(bool newValue) => _set(subtitleShowOutlineKey, newValue);
Color get subtitleTextColor => Color(getInt(subtitleTextColorKey) ?? SettingsDefaults.subtitleTextColor.value);
- set subtitleTextColor(Color newValue) => setAndNotify(subtitleTextColorKey, newValue.value);
+ set subtitleTextColor(Color newValue) => _set(subtitleTextColorKey, newValue.value);
Color get subtitleBackgroundColor => Color(getInt(subtitleBackgroundColorKey) ?? SettingsDefaults.subtitleBackgroundColor.value);
- set subtitleBackgroundColor(Color newValue) => setAndNotify(subtitleBackgroundColorKey, newValue.value);
+ set subtitleBackgroundColor(Color newValue) => _set(subtitleBackgroundColorKey, newValue.value);
// info
double get infoMapZoom => getDouble(infoMapZoomKey) ?? SettingsDefaults.infoMapZoom;
- set infoMapZoom(double newValue) => setAndNotify(infoMapZoomKey, newValue);
+ set infoMapZoom(double newValue) => _set(infoMapZoomKey, newValue);
CoordinateFormat get coordinateFormat => getEnumOrDefault(coordinateFormatKey, SettingsDefaults.coordinateFormat, CoordinateFormat.values);
- set coordinateFormat(CoordinateFormat newValue) => setAndNotify(coordinateFormatKey, newValue.toString());
+ set coordinateFormat(CoordinateFormat newValue) => _set(coordinateFormatKey, newValue.toString());
UnitSystem get unitSystem => getEnumOrDefault(unitSystemKey, SettingsDefaults.unitSystem, UnitSystem.values);
- set unitSystem(UnitSystem newValue) => setAndNotify(unitSystemKey, newValue.toString());
+ set unitSystem(UnitSystem newValue) => _set(unitSystemKey, newValue.toString());
// tag editor
bool get tagEditorCurrentFilterSectionExpanded => getBool(tagEditorCurrentFilterSectionExpandedKey) ?? SettingsDefaults.tagEditorCurrentFilterSectionExpanded;
- set tagEditorCurrentFilterSectionExpanded(bool newValue) => setAndNotify(tagEditorCurrentFilterSectionExpandedKey, newValue);
+ set tagEditorCurrentFilterSectionExpanded(bool newValue) => _set(tagEditorCurrentFilterSectionExpandedKey, newValue);
// map
@@ -681,106 +709,106 @@ class Settings extends ChangeNotifier {
return available.contains(preferred) ? preferred : available.first;
}
- set mapStyle(EntryMapStyle? newValue) => setAndNotify(mapStyleKey, newValue?.toString());
+ set mapStyle(EntryMapStyle? newValue) => _set(mapStyleKey, newValue?.toString());
LatLng? get mapDefaultCenter {
final json = getString(mapDefaultCenterKey);
return json != null ? LatLng.fromJson(jsonDecode(json)) : null;
}
- set mapDefaultCenter(LatLng? newValue) => setAndNotify(mapDefaultCenterKey, newValue != null ? jsonEncode(newValue.toJson()) : null);
+ set mapDefaultCenter(LatLng? newValue) => _set(mapDefaultCenterKey, newValue != null ? jsonEncode(newValue.toJson()) : null);
// search
bool get saveSearchHistory => getBool(saveSearchHistoryKey) ?? SettingsDefaults.saveSearchHistory;
- set saveSearchHistory(bool newValue) => setAndNotify(saveSearchHistoryKey, newValue);
+ set saveSearchHistory(bool newValue) => _set(saveSearchHistoryKey, newValue);
List get searchHistory => (getStringList(searchHistoryKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toList();
- set searchHistory(List newValue) => setAndNotify(searchHistoryKey, newValue.map((filter) => filter.toJson()).toList());
+ set searchHistory(List newValue) => _set(searchHistoryKey, newValue.map((filter) => filter.toJson()).toList());
// bin
bool get enableBin => getBool(enableBinKey) ?? SettingsDefaults.enableBin;
- set enableBin(bool newValue) => setAndNotify(enableBinKey, newValue);
+ set enableBin(bool newValue) => _set(enableBinKey, newValue);
// accessibility
bool get showPinchGestureAlternatives => getBool(showPinchGestureAlternativesKey) ?? SettingsDefaults.showPinchGestureAlternatives;
- set showPinchGestureAlternatives(bool newValue) => setAndNotify(showPinchGestureAlternativesKey, newValue);
+ set showPinchGestureAlternatives(bool newValue) => _set(showPinchGestureAlternativesKey, newValue);
AccessibilityAnimations get accessibilityAnimations => getEnumOrDefault(accessibilityAnimationsKey, SettingsDefaults.accessibilityAnimations, AccessibilityAnimations.values);
- set accessibilityAnimations(AccessibilityAnimations newValue) => setAndNotify(accessibilityAnimationsKey, newValue.toString());
+ set accessibilityAnimations(AccessibilityAnimations newValue) => _set(accessibilityAnimationsKey, newValue.toString());
AccessibilityTimeout get timeToTakeAction => getEnumOrDefault(timeToTakeActionKey, SettingsDefaults.timeToTakeAction, AccessibilityTimeout.values);
- set timeToTakeAction(AccessibilityTimeout newValue) => setAndNotify(timeToTakeActionKey, newValue.toString());
+ set timeToTakeAction(AccessibilityTimeout newValue) => _set(timeToTakeActionKey, newValue.toString());
// file picker
bool get filePickerShowHiddenFiles => getBool(filePickerShowHiddenFilesKey) ?? SettingsDefaults.filePickerShowHiddenFiles;
- set filePickerShowHiddenFiles(bool newValue) => setAndNotify(filePickerShowHiddenFilesKey, newValue);
+ set filePickerShowHiddenFiles(bool newValue) => _set(filePickerShowHiddenFilesKey, newValue);
// screen saver
bool get screenSaverFillScreen => getBool(screenSaverFillScreenKey) ?? SettingsDefaults.slideshowFillScreen;
- set screenSaverFillScreen(bool newValue) => setAndNotify(screenSaverFillScreenKey, newValue);
+ set screenSaverFillScreen(bool newValue) => _set(screenSaverFillScreenKey, newValue);
bool get screenSaverAnimatedZoomEffect => getBool(screenSaverAnimatedZoomEffectKey) ?? SettingsDefaults.slideshowAnimatedZoomEffect;
- set screenSaverAnimatedZoomEffect(bool newValue) => setAndNotify(screenSaverAnimatedZoomEffectKey, newValue);
+ set screenSaverAnimatedZoomEffect(bool newValue) => _set(screenSaverAnimatedZoomEffectKey, newValue);
ViewerTransition get screenSaverTransition => getEnumOrDefault(screenSaverTransitionKey, SettingsDefaults.slideshowTransition, ViewerTransition.values);
- set screenSaverTransition(ViewerTransition newValue) => setAndNotify(screenSaverTransitionKey, newValue.toString());
+ set screenSaverTransition(ViewerTransition newValue) => _set(screenSaverTransitionKey, newValue.toString());
SlideshowVideoPlayback get screenSaverVideoPlayback => getEnumOrDefault(screenSaverVideoPlaybackKey, SettingsDefaults.slideshowVideoPlayback, SlideshowVideoPlayback.values);
- set screenSaverVideoPlayback(SlideshowVideoPlayback newValue) => setAndNotify(screenSaverVideoPlaybackKey, newValue.toString());
+ set screenSaverVideoPlayback(SlideshowVideoPlayback newValue) => _set(screenSaverVideoPlaybackKey, newValue.toString());
int get screenSaverInterval => getInt(screenSaverIntervalKey) ?? SettingsDefaults.slideshowInterval;
- set screenSaverInterval(int newValue) => setAndNotify(screenSaverIntervalKey, newValue);
+ set screenSaverInterval(int newValue) => _set(screenSaverIntervalKey, newValue);
Set get screenSaverCollectionFilters => (getStringList(screenSaverCollectionFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet();
- set screenSaverCollectionFilters(Set newValue) => setAndNotify(screenSaverCollectionFiltersKey, newValue.map((filter) => filter.toJson()).toList());
+ set screenSaverCollectionFilters(Set newValue) => _set(screenSaverCollectionFiltersKey, newValue.map((filter) => filter.toJson()).toList());
// slideshow
bool get slideshowRepeat => getBool(slideshowRepeatKey) ?? SettingsDefaults.slideshowRepeat;
- set slideshowRepeat(bool newValue) => setAndNotify(slideshowRepeatKey, newValue);
+ set slideshowRepeat(bool newValue) => _set(slideshowRepeatKey, newValue);
bool get slideshowShuffle => getBool(slideshowShuffleKey) ?? SettingsDefaults.slideshowShuffle;
- set slideshowShuffle(bool newValue) => setAndNotify(slideshowShuffleKey, newValue);
+ set slideshowShuffle(bool newValue) => _set(slideshowShuffleKey, newValue);
bool get slideshowFillScreen => getBool(slideshowFillScreenKey) ?? SettingsDefaults.slideshowFillScreen;
- set slideshowFillScreen(bool newValue) => setAndNotify(slideshowFillScreenKey, newValue);
+ set slideshowFillScreen(bool newValue) => _set(slideshowFillScreenKey, newValue);
bool get slideshowAnimatedZoomEffect => getBool(slideshowAnimatedZoomEffectKey) ?? SettingsDefaults.slideshowAnimatedZoomEffect;
- set slideshowAnimatedZoomEffect(bool newValue) => setAndNotify(slideshowAnimatedZoomEffectKey, newValue);
+ set slideshowAnimatedZoomEffect(bool newValue) => _set(slideshowAnimatedZoomEffectKey, newValue);
ViewerTransition get slideshowTransition => getEnumOrDefault(slideshowTransitionKey, SettingsDefaults.slideshowTransition, ViewerTransition.values);
- set slideshowTransition(ViewerTransition newValue) => setAndNotify(slideshowTransitionKey, newValue.toString());
+ set slideshowTransition(ViewerTransition newValue) => _set(slideshowTransitionKey, newValue.toString());
SlideshowVideoPlayback get slideshowVideoPlayback => getEnumOrDefault(slideshowVideoPlaybackKey, SettingsDefaults.slideshowVideoPlayback, SlideshowVideoPlayback.values);
- set slideshowVideoPlayback(SlideshowVideoPlayback newValue) => setAndNotify(slideshowVideoPlaybackKey, newValue.toString());
+ set slideshowVideoPlayback(SlideshowVideoPlayback newValue) => _set(slideshowVideoPlaybackKey, newValue.toString());
int get slideshowInterval => getInt(slideshowIntervalKey) ?? SettingsDefaults.slideshowInterval;
- set slideshowInterval(int newValue) => setAndNotify(slideshowIntervalKey, newValue);
+ set slideshowInterval(int newValue) => _set(slideshowIntervalKey, newValue);
// widget
@@ -789,27 +817,27 @@ class Settings extends ChangeNotifier {
return value != null ? Color(value) : null;
}
- void setWidgetOutline(int widgetId, Color? newValue) => setAndNotify('$widgetOutlinePrefixKey$widgetId', newValue?.value);
+ void setWidgetOutline(int widgetId, Color? newValue) => _set('$widgetOutlinePrefixKey$widgetId', newValue?.value);
WidgetShape getWidgetShape(int widgetId) => getEnumOrDefault('$widgetShapePrefixKey$widgetId', SettingsDefaults.widgetShape, WidgetShape.values);
- void setWidgetShape(int widgetId, WidgetShape newValue) => setAndNotify('$widgetShapePrefixKey$widgetId', newValue.toString());
+ void setWidgetShape(int widgetId, WidgetShape newValue) => _set('$widgetShapePrefixKey$widgetId', newValue.toString());
Set getWidgetCollectionFilters(int widgetId) => (getStringList('$widgetCollectionFiltersPrefixKey$widgetId') ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet();
- void setWidgetCollectionFilters(int widgetId, Set newValue) => setAndNotify('$widgetCollectionFiltersPrefixKey$widgetId', newValue.map((filter) => filter.toJson()).toList());
+ void setWidgetCollectionFilters(int widgetId, Set newValue) => _set('$widgetCollectionFiltersPrefixKey$widgetId', newValue.map((filter) => filter.toJson()).toList());
WidgetOpenPage getWidgetOpenPage(int widgetId) => getEnumOrDefault('$widgetOpenPagePrefixKey$widgetId', SettingsDefaults.widgetOpenPage, WidgetOpenPage.values);
- void setWidgetOpenPage(int widgetId, WidgetOpenPage newValue) => setAndNotify('$widgetOpenPagePrefixKey$widgetId', newValue.toString());
+ void setWidgetOpenPage(int widgetId, WidgetOpenPage newValue) => _set('$widgetOpenPagePrefixKey$widgetId', newValue.toString());
WidgetDisplayedItem getWidgetDisplayedItem(int widgetId) => getEnumOrDefault('$widgetDisplayedItemPrefixKey$widgetId', SettingsDefaults.widgetDisplayedItem, WidgetDisplayedItem.values);
- void setWidgetDisplayedItem(int widgetId, WidgetDisplayedItem newValue) => setAndNotify('$widgetDisplayedItemPrefixKey$widgetId', newValue.toString());
+ void setWidgetDisplayedItem(int widgetId, WidgetDisplayedItem newValue) => _set('$widgetDisplayedItemPrefixKey$widgetId', newValue.toString());
String? getWidgetUri(int widgetId) => getString('$widgetUriPrefixKey$widgetId');
- void setWidgetUri(int widgetId, String? newValue) => setAndNotify('$widgetUriPrefixKey$widgetId', newValue);
+ void setWidgetUri(int widgetId, String? newValue) => _set('$widgetUriPrefixKey$widgetId', newValue);
// convenience methods
@@ -876,7 +904,7 @@ class Settings extends ChangeNotifier {
return settingsStore.getStringList(key)?.map((s) => values.firstWhereOrNull((v) => v.toString() == s)).whereNotNull().toList() ?? defaultValue;
}
- void setAndNotify(String key, dynamic newValue) {
+ void _set(String key, dynamic newValue) {
var oldValue = settingsStore.get(key);
if (newValue == null) {
settingsStore.remove(key);
@@ -922,11 +950,11 @@ class Settings extends ChangeNotifier {
bool get isRotationLocked => getBool(platformAccelerometerRotationKey) ?? SettingsDefaults.isRotationLocked;
- set isRotationLocked(bool newValue) => setAndNotify(platformAccelerometerRotationKey, newValue);
+ set isRotationLocked(bool newValue) => _set(platformAccelerometerRotationKey, newValue);
bool get areAnimationsRemoved => getBool(platformTransitionAnimationScaleKey) ?? SettingsDefaults.areAnimationsRemoved;
- set areAnimationsRemoved(bool newValue) => setAndNotify(platformTransitionAnimationScaleKey, newValue);
+ set areAnimationsRemoved(bool newValue) => _set(platformTransitionAnimationScaleKey, newValue);
// import/export
@@ -990,8 +1018,6 @@ class Settings extends ChangeNotifier {
case setMetadataDateBeforeFileOpKey:
case collectionSortReverseKey:
case showThumbnailFavouriteKey:
- case showThumbnailTagKey:
- case showThumbnailLocationKey:
case showThumbnailMotionPhotoKey:
case showThumbnailRatingKey:
case showThumbnailRawKey:
@@ -1002,6 +1028,7 @@ class Settings extends ChangeNotifier {
case showOverlayOnOpeningKey:
case showOverlayMinimapKey:
case showOverlayInfoKey:
+ case showOverlayDescriptionKey:
case showOverlayRatingTagsKey:
case showOverlayShootingDetailsKey:
case showOverlayThumbnailPreviewKey:
@@ -1037,6 +1064,8 @@ class Settings extends ChangeNotifier {
case homePageKey:
case collectionGroupFactorKey:
case collectionSortFactorKey:
+ case thumbnailLocationIconKey:
+ case thumbnailTagIconKey:
case albumGroupFactorKey:
case albumSortFactorKey:
case countrySortFactorKey:
@@ -1084,6 +1113,7 @@ class Settings extends ChangeNotifier {
_updateStreamController.add(SettingsChangedEvent(key, oldValue, newValue));
}
});
+ await sanitize();
notifyListeners();
}
}
diff --git a/lib/model/video/metadata.dart b/lib/model/video/metadata.dart
index c6853c6ee..7245ca79b 100644
--- a/lib/model/video/metadata.dart
+++ b/lib/model/video/metadata.dart
@@ -22,7 +22,7 @@ import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/foundation.dart';
class VideoMetadataFormatter {
- static final _dateY4M2D2H2m2s2Pattern = RegExp(r'(\d{4})[-./](\d{1,2})[-./](\d{1,2})([ T](\d{1,2}):(\d{1,2}):(\d{1,2})( ([ap]\.? ?m\.?))?)?');
+ static final _dateY4M2D2H2m2s2Pattern = RegExp(r'(\d{4})[-./:](\d{1,2})[-./:](\d{1,2})([ T](\d{1,2}):(\d{1,2}):(\d{1,2})( ([ap]\.? ?m\.?))?)?');
static final _ambiguousDatePatterns = {
RegExp(r'^\d{2}[-/]\d{2}[-/]\d{4}$'),
};
@@ -127,6 +127,7 @@ class VideoMetadataFormatter {
// - `2022-01-28T5:07:46 p. m.Z`
// - `2012-1-1T12:00:00Z`
// - `2020.10.14`
+ // - `2016:11:16 18:00:00`
// - `2021` (not enough to build a date)
var match = _dateY4M2D2H2m2s2Pattern.firstMatch(dateString);
diff --git a/lib/ref/mime_types.dart b/lib/ref/mime_types.dart
index 9df697fc2..744fc9712 100644
--- a/lib/ref/mime_types.dart
+++ b/lib/ref/mime_types.dart
@@ -60,6 +60,7 @@ class MimeTypes {
static const mkv = 'video/mkv';
static const mkvX = 'video/x-matroska';
static const mov = 'video/quicktime';
+ static const movX = 'video/x-quicktime';
static const mp2p = 'video/mp2p';
static const mp2t = 'video/mp2t'; // .m2ts, .ts
static const mp2ts = 'video/mp2ts'; // .ts (prefer `mp2t` when possible)
@@ -89,7 +90,7 @@ class MimeTypes {
static const Set _knownOpaqueImages = {jpeg};
- static const Set _knownVideos = {v3gpp, asf, avi, aviMSVideo, aviVnd, aviXMSVideo, flv, flvX, mkv, mkvX, mov, mp2p, mp2t, mp2ts, mp4, mpeg, ogv, realVideo, webm, wmv};
+ static const Set _knownVideos = {v3gpp, asf, avi, aviMSVideo, aviVnd, aviXMSVideo, flv, flvX, mkv, mkvX, mov, movX, mp2p, mp2t, mp2ts, mp4, mpeg, ogv, realVideo, webm, wmv};
static final Set knownMediaTypes = {
anyImage,
@@ -107,30 +108,35 @@ class MimeTypes {
static bool isVisual(String mimeType) => isImage(mimeType) || isVideo(mimeType);
- static bool refersToSameType(String a, b) {
- switch (a) {
+ static String _collapsedType(String mimeType) {
+ switch (mimeType) {
case avi:
case aviMSVideo:
case aviVnd:
case aviXMSVideo:
- return [avi, aviVnd].contains(b);
+ return avi;
case bmp:
case bmpX:
- return [bmp, bmpX].contains(b);
+ return bmp;
case flv:
case flvX:
- return [flv, flvX].contains(b);
+ return flv;
case heic:
case heif:
- return [heic, heif].contains(b);
+ return heic;
+ case mov:
+ case movX:
+ return mov;
case psdVnd:
case psdX:
- return [psdVnd, psdX].contains(b);
+ return psdVnd;
default:
- return a == b;
+ return mimeType;
}
}
+ static bool refersToSameType(String a, b) => _collapsedType(a) == _collapsedType(b);
+
static String? forExtension(String extension) {
switch (extension) {
case '.jpg':
diff --git a/lib/services/accessibility_service.dart b/lib/services/accessibility_service.dart
index 61ac1bb34..42a9f81f0 100644
--- a/lib/services/accessibility_service.dart
+++ b/lib/services/accessibility_service.dart
@@ -1,4 +1,5 @@
import 'package:aves/services/common/services.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
class AccessibilityService {
@@ -24,14 +25,17 @@ class AccessibilityService {
return false;
}
+ static bool? _hasRecommendedTimeouts;
+
static Future hasRecommendedTimeouts() async {
+ if (_hasRecommendedTimeouts != null) return SynchronousFuture(_hasRecommendedTimeouts!);
try {
final result = await _platform.invokeMethod('hasRecommendedTimeouts');
- if (result != null) return result as bool;
+ _hasRecommendedTimeouts = result as bool?;
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
- return false;
+ return _hasRecommendedTimeouts ?? false;
}
static Future getRecommendedTimeToRead(Duration originalTimeoutDuration) async {
diff --git a/lib/services/android_app_service.dart b/lib/services/android_app_service.dart
index 5152c9ad1..1db5487ad 100644
--- a/lib/services/android_app_service.dart
+++ b/lib/services/android_app_service.dart
@@ -148,32 +148,34 @@ class PlatformAndroidAppService implements AndroidAppService {
}
@override
- Future shareEntries(Iterable entries) async {
- // loosen MIME type to a generic one, so we can share with badly defined apps
- // e.g. Google Lens declares receiving "image/jpeg" only, but it can actually handle more formats
- final urisByMimeType = groupBy(entries, (e) => e.mimeTypeAnySubtype).map((k, v) => MapEntry(k, v.map((e) => e.uri).toList()));
+ Future shareEntries(Iterable entries) {
+ return _share(groupBy(
+ entries,
+ // loosen MIME type to a generic one, so we can share with badly defined apps
+ // e.g. Google Lens declares receiving "image/jpeg" only, but it can actually handle more formats
+ (e) => e.mimeTypeAnySubtype,
+ ).map((k, v) => MapEntry(k, v.map((e) => e.uri).toList())));
+ }
+
+ @override
+ Future shareSingle(String uri, String mimeType) {
+ return _share({
+ mimeType: [uri]
+ });
+ }
+
+ Future _share(Map> urisByMimeType) async {
try {
final result = await _platform.invokeMethod('share', {
'urisByMimeType': urisByMimeType,
});
if (result != null) return result as bool;
} on PlatformException catch (e, stack) {
- await reportService.recordError(e, stack);
- }
- return false;
- }
-
- @override
- Future shareSingle(String uri, String mimeType) async {
- try {
- final result = await _platform.invokeMethod('share', {
- 'urisByMimeType': {
- mimeType: [uri]
- },
- });
- if (result != null) return result as bool;
- } on PlatformException catch (e, stack) {
- await reportService.recordError(e, stack);
+ if (e.code == 'share-large') {
+ throw TooManyItemsException();
+ } else {
+ await reportService.recordError(e, stack);
+ }
}
return false;
}
@@ -207,3 +209,5 @@ class PlatformAndroidAppService implements AndroidAppService {
}
}
}
+
+class TooManyItemsException implements Exception {}
diff --git a/lib/services/media/embedded_data_service.dart b/lib/services/media/embedded_data_service.dart
index 23b47849c..33f024ba2 100644
--- a/lib/services/media/embedded_data_service.dart
+++ b/lib/services/media/embedded_data_service.dart
@@ -6,6 +6,8 @@ import 'package:flutter/services.dart';
abstract class EmbeddedDataService {
Future> getExifThumbnails(AvesEntry entry);
+ Future extractGoogleDeviceItem(AvesEntry entry, String dataUri);
+
Future extractMotionPhotoImage(AvesEntry entry);
Future extractMotionPhotoVideo(AvesEntry entry);
@@ -33,6 +35,23 @@ class PlatformEmbeddedDataService implements EmbeddedDataService {
return [];
}
+ @override
+ Future extractGoogleDeviceItem(AvesEntry entry, String dataUri) async {
+ try {
+ final result = await _platform.invokeMethod('extractGoogleDeviceItem', {
+ 'mimeType': entry.mimeType,
+ 'uri': entry.uri,
+ 'sizeBytes': entry.sizeBytes,
+ 'displayName': ['${entry.bestTitle}', dataUri].join(Constants.separator),
+ 'dataUri': dataUri,
+ });
+ if (result != null) return result as Map;
+ } on PlatformException catch (e, stack) {
+ await reportService.recordError(e, stack);
+ }
+ return {};
+ }
+
@override
Future extractMotionPhotoImage(AvesEntry entry) async {
try {
diff --git a/lib/services/media/media_session_service.dart b/lib/services/media/media_session_service.dart
index 4914e9237..c6cb94b8a 100644
--- a/lib/services/media/media_session_service.dart
+++ b/lib/services/media/media_session_service.dart
@@ -1,18 +1,33 @@
import 'dart:async';
+import 'package:aves/services/common/optional_event_channel.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/widgets/viewer/video/controller.dart';
+import 'package:equatable/equatable.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
abstract class MediaSessionService {
+ Stream get mediaCommands;
+
Future update(AvesVideoController controller);
- Future release(String uri);
+ Future release();
}
class PlatformMediaSessionService implements MediaSessionService {
static const _platformObject = MethodChannel('deckers.thibault/aves/media_session');
+ final EventChannel _mediaCommandChannel = const OptionalEventChannel('deckers.thibault/aves/media_command');
+ final StreamController _streamController = StreamController.broadcast();
+
+ PlatformMediaSessionService() {
+ _mediaCommandChannel.receiveBroadcastStream().listen((event) => _onMediaCommand(event as Map?));
+ }
+
+ @override
+ Stream get mediaCommands => _streamController.stream.where((event) => event is MediaCommandEvent).cast();
+
@override
Future update(AvesVideoController controller) async {
final entry = controller.entry;
@@ -31,11 +46,9 @@ class PlatformMediaSessionService implements MediaSessionService {
}
@override
- Future release(String uri) async {
+ Future release() async {
try {
- await _platformObject.invokeMethod('release', {
- 'uri': uri,
- });
+ await _platformObject.invokeMethod('release');
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
@@ -54,4 +67,52 @@ class PlatformMediaSessionService implements MediaSessionService {
return 'stopped';
}
}
+
+ void _onMediaCommand(Map? fields) {
+ if (fields == null) return;
+ final command = fields['command'] as String?;
+ MediaCommandEvent? event;
+ switch (command) {
+ case 'play':
+ event = const MediaCommandEvent(MediaCommand.play);
+ break;
+ case 'pause':
+ event = const MediaCommandEvent(MediaCommand.pause);
+ break;
+ case 'stop':
+ event = const MediaCommandEvent(MediaCommand.stop);
+ break;
+ case 'seek':
+ final position = fields['position'] as int?;
+ if (position != null) {
+ event = MediaSeekCommandEvent(MediaCommand.stop, position: position);
+ }
+ break;
+ }
+ if (event != null) {
+ _streamController.add(event);
+ }
+ }
+}
+
+enum MediaCommand { play, pause, stop, seek }
+
+@immutable
+class MediaCommandEvent extends Equatable {
+ final MediaCommand command;
+
+ @override
+ List get props => [command];
+
+ const MediaCommandEvent(this.command);
+}
+
+@immutable
+class MediaSeekCommandEvent extends MediaCommandEvent {
+ final int position;
+
+ @override
+ List get props => [...super.props, position];
+
+ const MediaSeekCommandEvent(super.command, {required this.position});
}
diff --git a/lib/services/window_service.dart b/lib/services/window_service.dart
index 1c99b1161..576b832b8 100644
--- a/lib/services/window_service.dart
+++ b/lib/services/window_service.dart
@@ -1,4 +1,5 @@
import 'package:aves/services/common/services.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
@@ -11,9 +12,9 @@ abstract class WindowService {
Future requestOrientation([Orientation? orientation]);
- Future canSetCutoutMode();
+ Future isCutoutAware();
- Future setCutoutMode(bool use);
+ Future getCutoutInsets();
}
class PlatformWindowService implements WindowService {
@@ -79,25 +80,35 @@ class PlatformWindowService implements WindowService {
}
}
+ bool? _isCutoutAware;
+
@override
- Future canSetCutoutMode() async {
+ Future isCutoutAware() async {
+ if (_isCutoutAware != null) return SynchronousFuture(_isCutoutAware!);
try {
- final result = await _platform.invokeMethod('canSetCutoutMode');
- if (result != null) return result as bool;
+ final result = await _platform.invokeMethod('isCutoutAware');
+ _isCutoutAware = result as bool?;
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
- return false;
+ return _isCutoutAware ?? false;
}
@override
- Future setCutoutMode(bool use) async {
+ Future getCutoutInsets() async {
try {
- await _platform.invokeMethod('setCutoutMode', {
- 'use': use,
- });
+ final result = await _platform.invokeMethod('getCutoutInsets');
+ if (result != null) {
+ return EdgeInsets.only(
+ left: result['left']?.toDouble() ?? 0,
+ top: result['top']?.toDouble() ?? 0,
+ right: result['right']?.toDouble() ?? 0,
+ bottom: result['bottom']?.toDouble() ?? 0,
+ );
+ }
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
+ return EdgeInsets.zero;
}
}
diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart
index aa618c6c7..859ae55f6 100644
--- a/lib/utils/constants.dart
+++ b/lib/utils/constants.dart
@@ -1,6 +1,5 @@
import 'dart:ui';
-import 'package:aves/app_flavor.dart';
import 'package:flutter/material.dart';
import 'package:latlong2/latlong.dart';
@@ -69,367 +68,4 @@ class Constants {
static const int infoGroupMaxValueLength = 140;
static const String avesGithub = 'https://github.com/deckerst/aves';
-
- static const String apache2 = 'Apache License 2.0';
- static const String bsd2 = 'BSD 2-Clause "Simplified" License';
- static const String bsd3 = 'BSD 3-Clause "Revised" License';
- static const String eclipse1 = 'Eclipse Public License 1.0';
- static const String mit = 'MIT License';
-
- static const List androidDependencies = [
- Dependency(
- name: 'AndroidSVG',
- license: apache2,
- sourceUrl: 'https://github.com/BigBadaboom/androidsvg',
- ),
- Dependency(
- name: 'AndroidX (Core Kotlin, Exifinterface, Lifecycle Process, Multidex)',
- license: apache2,
- licenseUrl: 'https://android.googlesource.com/platform/frameworks/support/+/androidx-main/LICENSE.txt',
- sourceUrl: 'https://android.googlesource.com/platform/frameworks/support/+/androidx-main/core/core-ktx',
- ),
- Dependency(
- name: 'CWAC-Document',
- license: apache2,
- sourceUrl: 'https://github.com/commonsguy/cwac-document',
- ),
- Dependency(
- name: 'Glide',
- license: '$apache2, $bsd2',
- sourceUrl: 'https://github.com/bumptech/glide',
- ),
- Dependency(
- name: 'Metadata Extractor',
- license: apache2,
- sourceUrl: 'https://github.com/drewnoakes/metadata-extractor',
- ),
- Dependency(
- name: 'MP4 Parser (Aves fork)',
- license: apache2,
- sourceUrl: 'https://github.com/deckerst/mp4parser',
- ),
- Dependency(
- name: 'PixyMeta Android (Aves fork)',
- license: eclipse1,
- sourceUrl: 'https://github.com/deckerst/pixymeta-android',
- ),
- Dependency(
- name: 'Tiff Bitmap Factory (Aves fork)',
- license: mit,
- licenseUrl: 'https://github.com/deckerst/Android-TiffBitmapFactory/blob/master/license.txt',
- sourceUrl: 'https://github.com/deckerst/Android-TiffBitmapFactory',
- ),
- ];
-
- static const List _flutterPluginsCommon = [
- Dependency(
- name: 'Connectivity Plus',
- license: bsd3,
- licenseUrl: 'https://github.com/fluttercommunity/plus_plugins/blob/main/packages/connectivity_plus/connectivity_plus/LICENSE',
- sourceUrl: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/connectivity_plus',
- ),
- Dependency(
- name: 'Device Info Plus',
- license: bsd3,
- licenseUrl: 'https://github.com/fluttercommunity/plus_plugins/blob/main/packages/device_info_plus/device_info_plus/LICENSE',
- sourceUrl: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/device_info_plus',
- ),
- Dependency(
- name: 'Dynamic Color',
- license: bsd3,
- sourceUrl: 'https://github.com/material-foundation/material-dynamic-color-flutter',
- ),
- Dependency(
- name: 'fijkplayer (Aves fork)',
- license: mit,
- sourceUrl: 'https://github.com/deckerst/fijkplayer',
- ),
- Dependency(
- name: 'Flutter Display Mode',
- license: mit,
- sourceUrl: 'https://github.com/ajinasokan/flutter_displaymode',
- ),
- Dependency(
- name: 'Package Info Plus',
- license: bsd3,
- licenseUrl: 'https://github.com/fluttercommunity/plus_plugins/blob/main/packages/package_info_plus/package_info_plus/LICENSE',
- sourceUrl: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/package_info_plus',
- ),
- Dependency(
- name: 'Permission Handler',
- license: mit,
- sourceUrl: 'https://github.com/Baseflow/flutter-permission-handler',
- ),
- Dependency(
- name: 'Printing',
- license: apache2,
- sourceUrl: 'https://github.com/DavBfr/dart_pdf',
- ),
- Dependency(
- name: 'Screen Brightness',
- license: mit,
- sourceUrl: 'https://github.com/aaassseee/screen_brightness',
- ),
- Dependency(
- name: 'Shared Preferences',
- license: bsd3,
- licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/shared_preferences/shared_preferences/LICENSE',
- sourceUrl: 'https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences',
- ),
- Dependency(
- name: 'sqflite',
- license: bsd2,
- sourceUrl: 'https://github.com/tekartik/sqflite',
- ),
- Dependency(
- name: 'Streams Channel (Aves fork)',
- license: apache2,
- sourceUrl: 'https://github.com/deckerst/aves_streams_channel',
- ),
- Dependency(
- name: 'URL Launcher',
- license: bsd3,
- licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/url_launcher/url_launcher/LICENSE',
- sourceUrl: 'https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher',
- ),
- ];
-
- static const List _googleMobileServices = [
- Dependency(
- name: 'Google API Availability',
- license: mit,
- sourceUrl: 'https://github.com/Baseflow/flutter-google-api-availability',
- ),
- Dependency(
- name: 'Google Maps for Flutter',
- license: bsd3,
- licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/google_maps_flutter/google_maps_flutter/LICENSE',
- sourceUrl: 'https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter/google_maps_flutter',
- ),
- ];
-
- static const List _huaweiMobileServices = [
- Dependency(
- name: 'Huawei Mobile Services (Availability, Map)',
- license: apache2,
- licenseUrl: 'https://github.com/HMS-Core/hms-flutter-plugin/blob/master/LICENCE',
- sourceUrl: 'https://github.com/HMS-Core/hms-flutter-plugin',
- ),
- ];
-
- static const List _flutterPluginsHuaweiOnly = [
- ..._huaweiMobileServices,
- ];
-
- static const List _flutterPluginsIzzyOnly = [
- ..._googleMobileServices,
- ];
-
- static const List _flutterPluginsLibreOnly = [];
-
- static const List _flutterPluginsPlayOnly = [
- ..._googleMobileServices,
- Dependency(
- name: 'FlutterFire (Core, Crashlytics)',
- license: bsd3,
- sourceUrl: 'https://github.com/FirebaseExtended/flutterfire',
- ),
- ];
-
- static List flutterPlugins(AppFlavor flavor) => [
- ..._flutterPluginsCommon,
- if (flavor == AppFlavor.huawei) ..._flutterPluginsHuaweiOnly,
- if (flavor == AppFlavor.izzy) ..._flutterPluginsIzzyOnly,
- if (flavor == AppFlavor.libre) ..._flutterPluginsLibreOnly,
- if (flavor == AppFlavor.play) ..._flutterPluginsPlayOnly,
- ];
-
- static const List flutterPackages = [
- Dependency(
- name: 'Charts',
- license: apache2,
- sourceUrl: 'https://github.com/google/charts',
- ),
- Dependency(
- name: 'Custom rounded rectangle border',
- license: mit,
- sourceUrl: 'https://github.com/lekanbar/custom_rounded_rectangle_border',
- ),
- Dependency(
- name: 'Decorated Icon',
- license: mit,
- sourceUrl: 'https://github.com/benPesso/flutter_decorated_icon',
- ),
- Dependency(
- name: 'Expansion Tile Card (Aves fork)',
- license: bsd3,
- sourceUrl: 'https://github.com/deckerst/expansion_tile_card',
- ),
- Dependency(
- name: 'FlexColorPicker',
- license: bsd3,
- sourceUrl: 'https://github.com/rydmike/flex_color_picker',
- ),
- Dependency(
- name: 'Flutter Highlight',
- license: mit,
- sourceUrl: 'https://github.com/git-touch/highlight',
- ),
- Dependency(
- name: 'Flutter Map',
- license: bsd3,
- sourceUrl: 'https://github.com/fleaflet/flutter_map',
- ),
- Dependency(
- name: 'Flutter Markdown',
- license: bsd3,
- licenseUrl: 'https://github.com/flutter/packages/blob/master/packages/flutter_markdown/LICENSE',
- sourceUrl: 'https://github.com/flutter/packages/tree/master/packages/flutter_markdown',
- ),
- Dependency(
- name: 'Flutter Staggered Animations',
- license: mit,
- sourceUrl: 'https://github.com/mobiten/flutter_staggered_animations',
- ),
- Dependency(
- name: 'Material Design Icons Flutter',
- license: mit,
- sourceUrl: 'https://github.com/ziofat/material_design_icons_flutter',
- ),
- Dependency(
- name: 'Overlay Support',
- license: apache2,
- sourceUrl: 'https://github.com/boyan01/overlay_support',
- ),
- Dependency(
- name: 'Palette Generator',
- license: bsd3,
- licenseUrl: 'https://github.com/flutter/packages/blob/master/packages/palette_generator/LICENSE',
- sourceUrl: 'https://github.com/flutter/packages/tree/master/packages/palette_generator',
- ),
- Dependency(
- name: 'Panorama (Aves fork)',
- license: apache2,
- sourceUrl: 'https://github.com/zesage/panorama',
- ),
- Dependency(
- name: 'Percent Indicator',
- license: bsd2,
- sourceUrl: 'https://github.com/diegoveloper/flutter_percent_indicator',
- ),
- Dependency(
- name: 'Provider',
- license: mit,
- sourceUrl: 'https://github.com/rrousselGit/provider',
- ),
- Dependency(
- name: 'Smooth Page Indicator',
- license: mit,
- sourceUrl: 'https://github.com/Milad-Akarie/smooth_page_indicator',
- ),
- ];
-
- static const List dartPackages = [
- Dependency(
- name: 'Collection',
- license: bsd3,
- sourceUrl: 'https://github.com/dart-lang/collection',
- ),
- Dependency(
- name: 'Country Code',
- license: mit,
- sourceUrl: 'https://github.com/denixport/dart.country',
- ),
- Dependency(
- name: 'Equatable',
- license: mit,
- sourceUrl: 'https://github.com/felangel/equatable',
- ),
- Dependency(
- name: 'Event Bus',
- license: mit,
- sourceUrl: 'https://github.com/marcojakob/dart-event-bus',
- ),
- Dependency(
- name: 'Fluster',
- license: mit,
- sourceUrl: 'https://github.com/alfonsocejudo/fluster',
- ),
- Dependency(
- name: 'Flutter Lints',
- license: bsd3,
- licenseUrl: 'https://github.com/flutter/packages/blob/master/packages/flutter_lints/LICENSE',
- sourceUrl: 'https://github.com/flutter/packages/tree/master/packages/flutter_lints',
- ),
- Dependency(
- name: 'Get It',
- license: mit,
- sourceUrl: 'https://github.com/fluttercommunity/get_it',
- ),
- Dependency(
- name: 'Intl',
- license: bsd3,
- sourceUrl: 'https://github.com/dart-lang/intl',
- ),
- Dependency(
- name: 'LatLong2',
- license: apache2,
- sourceUrl: 'https://github.com/jifalops/dart-latlong',
- ),
- Dependency(
- name: 'Material Color Utilities',
- license: apache2,
- licenseUrl: 'https://github.com/material-foundation/material-color-utilities/tree/main/dart/LICENSE',
- sourceUrl: 'https://github.com/material-foundation/material-color-utilities/tree/main/dart',
- ),
- Dependency(
- name: 'Path',
- license: bsd3,
- sourceUrl: 'https://github.com/dart-lang/path',
- ),
- Dependency(
- name: 'PDF for Dart and Flutter',
- license: apache2,
- sourceUrl: 'https://github.com/DavBfr/dart_pdf',
- ),
- Dependency(
- name: 'Proj4dart',
- license: mit,
- sourceUrl: 'https://github.com/maRci002/proj4dart',
- ),
- Dependency(
- name: 'Stack Trace',
- license: bsd3,
- sourceUrl: 'https://github.com/dart-lang/stack_trace',
- ),
- Dependency(
- name: 'Transparent Image',
- license: mit,
- sourceUrl: 'https://github.com/brianegan/transparent_image',
- ),
- Dependency(
- name: 'Tuple',
- license: bsd2,
- sourceUrl: 'https://github.com/google/tuple.dart',
- ),
- Dependency(
- name: 'XML',
- license: mit,
- sourceUrl: 'https://github.com/renggli/dart-xml',
- ),
- ];
-}
-
-class Dependency {
- final String name;
- final String license;
- final String sourceUrl;
- final String licenseUrl;
-
- const Dependency({
- required this.name,
- required this.license,
- String? licenseUrl,
- required this.sourceUrl,
- }) : licenseUrl = licenseUrl ?? '$sourceUrl/blob/master/LICENSE';
}
diff --git a/lib/utils/dependencies.dart b/lib/utils/dependencies.dart
new file mode 100644
index 000000000..9bb4babd0
--- /dev/null
+++ b/lib/utils/dependencies.dart
@@ -0,0 +1,366 @@
+import 'package:aves/app_flavor.dart';
+
+class Dependencies {
+ static const String apache2 = 'Apache License 2.0';
+ static const String bsd2 = 'BSD 2-Clause "Simplified" License';
+ static const String bsd3 = 'BSD 3-Clause "Revised" License';
+ static const String eclipse1 = 'Eclipse Public License 1.0';
+ static const String mit = 'MIT License';
+
+ static const List androidDependencies = [
+ Dependency(
+ name: 'AndroidSVG',
+ license: apache2,
+ sourceUrl: 'https://github.com/BigBadaboom/androidsvg',
+ ),
+ Dependency(
+ name: 'AndroidX (Core Kotlin, Exifinterface, Lifecycle Process, Multidex)',
+ license: apache2,
+ licenseUrl: 'https://android.googlesource.com/platform/frameworks/support/+/androidx-main/LICENSE.txt',
+ sourceUrl: 'https://android.googlesource.com/platform/frameworks/support/+/androidx-main/core/core-ktx',
+ ),
+ Dependency(
+ name: 'CWAC-Document',
+ license: apache2,
+ sourceUrl: 'https://github.com/commonsguy/cwac-document',
+ ),
+ Dependency(
+ name: 'Glide',
+ license: '$apache2, $bsd2',
+ sourceUrl: 'https://github.com/bumptech/glide',
+ ),
+ Dependency(
+ name: 'Metadata Extractor',
+ license: apache2,
+ sourceUrl: 'https://github.com/drewnoakes/metadata-extractor',
+ ),
+ Dependency(
+ name: 'MP4 Parser (Aves fork)',
+ license: apache2,
+ sourceUrl: 'https://github.com/deckerst/mp4parser',
+ ),
+ Dependency(
+ name: 'PixyMeta Android (Aves fork)',
+ license: eclipse1,
+ sourceUrl: 'https://github.com/deckerst/pixymeta-android',
+ ),
+ Dependency(
+ name: 'Tiff Bitmap Factory (Aves fork)',
+ license: mit,
+ licenseUrl: 'https://github.com/deckerst/Android-TiffBitmapFactory/blob/master/license.txt',
+ sourceUrl: 'https://github.com/deckerst/Android-TiffBitmapFactory',
+ ),
+ ];
+
+ static const List _flutterPluginsCommon = [
+ Dependency(
+ name: 'Connectivity Plus',
+ license: bsd3,
+ licenseUrl: 'https://github.com/fluttercommunity/plus_plugins/blob/main/packages/connectivity_plus/connectivity_plus/LICENSE',
+ sourceUrl: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/connectivity_plus',
+ ),
+ Dependency(
+ name: 'Device Info Plus',
+ license: bsd3,
+ licenseUrl: 'https://github.com/fluttercommunity/plus_plugins/blob/main/packages/device_info_plus/device_info_plus/LICENSE',
+ sourceUrl: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/device_info_plus',
+ ),
+ Dependency(
+ name: 'Dynamic Color',
+ license: bsd3,
+ sourceUrl: 'https://github.com/material-foundation/material-dynamic-color-flutter',
+ ),
+ Dependency(
+ name: 'fijkplayer (Aves fork)',
+ license: mit,
+ sourceUrl: 'https://github.com/deckerst/fijkplayer',
+ ),
+ Dependency(
+ name: 'Flutter Display Mode',
+ license: mit,
+ sourceUrl: 'https://github.com/ajinasokan/flutter_displaymode',
+ ),
+ Dependency(
+ name: 'Package Info Plus',
+ license: bsd3,
+ licenseUrl: 'https://github.com/fluttercommunity/plus_plugins/blob/main/packages/package_info_plus/package_info_plus/LICENSE',
+ sourceUrl: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/package_info_plus',
+ ),
+ Dependency(
+ name: 'Permission Handler',
+ license: mit,
+ sourceUrl: 'https://github.com/Baseflow/flutter-permission-handler',
+ ),
+ Dependency(
+ name: 'Printing',
+ license: apache2,
+ sourceUrl: 'https://github.com/DavBfr/dart_pdf',
+ ),
+ Dependency(
+ name: 'Screen Brightness',
+ license: mit,
+ sourceUrl: 'https://github.com/aaassseee/screen_brightness',
+ ),
+ Dependency(
+ name: 'Shared Preferences',
+ license: bsd3,
+ licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/shared_preferences/shared_preferences/LICENSE',
+ sourceUrl: 'https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences',
+ ),
+ Dependency(
+ name: 'sqflite',
+ license: bsd2,
+ sourceUrl: 'https://github.com/tekartik/sqflite',
+ ),
+ Dependency(
+ name: 'Streams Channel (Aves fork)',
+ license: apache2,
+ sourceUrl: 'https://github.com/deckerst/aves_streams_channel',
+ ),
+ Dependency(
+ name: 'URL Launcher',
+ license: bsd3,
+ licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/url_launcher/url_launcher/LICENSE',
+ sourceUrl: 'https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher',
+ ),
+ ];
+
+ static const List _googleMobileServices = [
+ Dependency(
+ name: 'Google API Availability',
+ license: mit,
+ sourceUrl: 'https://github.com/Baseflow/flutter-google-api-availability',
+ ),
+ Dependency(
+ name: 'Google Maps for Flutter',
+ license: bsd3,
+ licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/google_maps_flutter/google_maps_flutter/LICENSE',
+ sourceUrl: 'https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter/google_maps_flutter',
+ ),
+ ];
+
+ static const List _huaweiMobileServices = [
+ Dependency(
+ name: 'Huawei Mobile Services (Availability, Map)',
+ license: apache2,
+ licenseUrl: 'https://github.com/HMS-Core/hms-flutter-plugin/blob/master/LICENCE',
+ sourceUrl: 'https://github.com/HMS-Core/hms-flutter-plugin',
+ ),
+ ];
+
+ static const List _flutterPluginsHuaweiOnly = [
+ ..._huaweiMobileServices,
+ ];
+
+ static const List _flutterPluginsIzzyOnly = [
+ ..._googleMobileServices,
+ ];
+
+ static const List _flutterPluginsLibreOnly = [];
+
+ static const List _flutterPluginsPlayOnly = [
+ ..._googleMobileServices,
+ Dependency(
+ name: 'FlutterFire (Core, Crashlytics)',
+ license: bsd3,
+ sourceUrl: 'https://github.com/FirebaseExtended/flutterfire',
+ ),
+ ];
+
+ static List flutterPlugins(AppFlavor flavor) => [
+ ..._flutterPluginsCommon,
+ if (flavor == AppFlavor.huawei) ..._flutterPluginsHuaweiOnly,
+ if (flavor == AppFlavor.izzy) ..._flutterPluginsIzzyOnly,
+ if (flavor == AppFlavor.libre) ..._flutterPluginsLibreOnly,
+ if (flavor == AppFlavor.play) ..._flutterPluginsPlayOnly,
+ ];
+
+ static const List flutterPackages = [
+ Dependency(
+ name: 'Charts (fzyzcjy fork)',
+ license: apache2,
+ sourceUrl: 'https://github.com/fzyzcjy/charts',
+ ),
+ Dependency(
+ name: 'Custom rounded rectangle border',
+ license: mit,
+ sourceUrl: 'https://github.com/lekanbar/custom_rounded_rectangle_border',
+ ),
+ Dependency(
+ name: 'Decorated Icon',
+ license: mit,
+ sourceUrl: 'https://github.com/benPesso/flutter_decorated_icon',
+ ),
+ Dependency(
+ name: 'Expansion Tile Card (Aves fork)',
+ license: bsd3,
+ sourceUrl: 'https://github.com/deckerst/expansion_tile_card',
+ ),
+ Dependency(
+ name: 'FlexColorPicker',
+ license: bsd3,
+ sourceUrl: 'https://github.com/rydmike/flex_color_picker',
+ ),
+ Dependency(
+ name: 'Flutter Highlight',
+ license: mit,
+ sourceUrl: 'https://github.com/git-touch/highlight',
+ ),
+ Dependency(
+ name: 'Flutter Map',
+ license: bsd3,
+ sourceUrl: 'https://github.com/fleaflet/flutter_map',
+ ),
+ Dependency(
+ name: 'Flutter Markdown',
+ license: bsd3,
+ licenseUrl: 'https://github.com/flutter/packages/blob/master/packages/flutter_markdown/LICENSE',
+ sourceUrl: 'https://github.com/flutter/packages/tree/master/packages/flutter_markdown',
+ ),
+ Dependency(
+ name: 'Flutter Staggered Animations',
+ license: mit,
+ sourceUrl: 'https://github.com/mobiten/flutter_staggered_animations',
+ ),
+ Dependency(
+ name: 'Material Design Icons Flutter',
+ license: mit,
+ sourceUrl: 'https://github.com/ziofat/material_design_icons_flutter',
+ ),
+ Dependency(
+ name: 'Overlay Support',
+ license: apache2,
+ sourceUrl: 'https://github.com/boyan01/overlay_support',
+ ),
+ Dependency(
+ name: 'Palette Generator',
+ license: bsd3,
+ licenseUrl: 'https://github.com/flutter/packages/blob/master/packages/palette_generator/LICENSE',
+ sourceUrl: 'https://github.com/flutter/packages/tree/master/packages/palette_generator',
+ ),
+ Dependency(
+ name: 'Panorama (Aves fork)',
+ license: apache2,
+ sourceUrl: 'https://github.com/zesage/panorama',
+ ),
+ Dependency(
+ name: 'Percent Indicator',
+ license: bsd2,
+ sourceUrl: 'https://github.com/diegoveloper/flutter_percent_indicator',
+ ),
+ Dependency(
+ name: 'Provider',
+ license: mit,
+ sourceUrl: 'https://github.com/rrousselGit/provider',
+ ),
+ Dependency(
+ name: 'Smooth Page Indicator',
+ license: mit,
+ sourceUrl: 'https://github.com/Milad-Akarie/smooth_page_indicator',
+ ),
+ ];
+
+ static const List dartPackages = [
+ Dependency(
+ name: 'Collection',
+ license: bsd3,
+ sourceUrl: 'https://github.com/dart-lang/collection',
+ ),
+ Dependency(
+ name: 'Country Code',
+ license: mit,
+ sourceUrl: 'https://github.com/denixport/dart.country',
+ ),
+ Dependency(
+ name: 'Equatable',
+ license: mit,
+ sourceUrl: 'https://github.com/felangel/equatable',
+ ),
+ Dependency(
+ name: 'Event Bus',
+ license: mit,
+ sourceUrl: 'https://github.com/marcojakob/dart-event-bus',
+ ),
+ Dependency(
+ name: 'Fluster',
+ license: mit,
+ sourceUrl: 'https://github.com/alfonsocejudo/fluster',
+ ),
+ Dependency(
+ name: 'Flutter Lints',
+ license: bsd3,
+ licenseUrl: 'https://github.com/flutter/packages/blob/master/packages/flutter_lints/LICENSE',
+ sourceUrl: 'https://github.com/flutter/packages/tree/master/packages/flutter_lints',
+ ),
+ Dependency(
+ name: 'Get It',
+ license: mit,
+ sourceUrl: 'https://github.com/fluttercommunity/get_it',
+ ),
+ Dependency(
+ name: 'Intl',
+ license: bsd3,
+ sourceUrl: 'https://github.com/dart-lang/intl',
+ ),
+ Dependency(
+ name: 'LatLong2',
+ license: apache2,
+ sourceUrl: 'https://github.com/jifalops/dart-latlong',
+ ),
+ Dependency(
+ name: 'Material Color Utilities',
+ license: apache2,
+ licenseUrl: 'https://github.com/material-foundation/material-color-utilities/tree/main/dart/LICENSE',
+ sourceUrl: 'https://github.com/material-foundation/material-color-utilities/tree/main/dart',
+ ),
+ Dependency(
+ name: 'Path',
+ license: bsd3,
+ sourceUrl: 'https://github.com/dart-lang/path',
+ ),
+ Dependency(
+ name: 'PDF for Dart and Flutter',
+ license: apache2,
+ sourceUrl: 'https://github.com/DavBfr/dart_pdf',
+ ),
+ Dependency(
+ name: 'Proj4dart',
+ license: mit,
+ sourceUrl: 'https://github.com/maRci002/proj4dart',
+ ),
+ Dependency(
+ name: 'Stack Trace',
+ license: bsd3,
+ sourceUrl: 'https://github.com/dart-lang/stack_trace',
+ ),
+ Dependency(
+ name: 'Transparent Image',
+ license: mit,
+ sourceUrl: 'https://github.com/brianegan/transparent_image',
+ ),
+ Dependency(
+ name: 'Tuple',
+ license: bsd2,
+ sourceUrl: 'https://github.com/google/tuple.dart',
+ ),
+ Dependency(
+ name: 'XML',
+ license: mit,
+ sourceUrl: 'https://github.com/renggli/dart-xml',
+ ),
+ ];
+}
+
+class Dependency {
+ final String name;
+ final String license;
+ final String sourceUrl;
+ final String licenseUrl;
+
+ const Dependency({
+ required this.name,
+ required this.license,
+ String? licenseUrl,
+ required this.sourceUrl,
+ }) : licenseUrl = licenseUrl ?? '$sourceUrl/blob/master/LICENSE';
+}
diff --git a/lib/utils/mime_utils.dart b/lib/utils/mime_utils.dart
index 46afcf635..15a0ababa 100644
--- a/lib/utils/mime_utils.dart
+++ b/lib/utils/mime_utils.dart
@@ -8,6 +8,7 @@ class MimeUtils {
case MimeTypes.ico:
return 'ICO';
case MimeTypes.mov:
+ case MimeTypes.movX:
return 'MOV';
case MimeTypes.psdVnd:
case MimeTypes.psdX:
diff --git a/lib/utils/xmp_utils.dart b/lib/utils/xmp_utils.dart
index f81fa46d8..72f2b3515 100644
--- a/lib/utils/xmp_utils.dart
+++ b/lib/utils/xmp_utils.dart
@@ -41,6 +41,7 @@ class Namespaces {
static const iptc4xmpExt = 'http://iptc.org/std/Iptc4xmpExt/2008-02-29/';
static const lr = 'http://ns.adobe.com/lightroom/1.0/';
static const mediapro = 'http://ns.iview-multimedia.com/mediapro/1.0/';
+ static const miCamera = 'http://ns.xiaomi.com/photos/1.0/camera/';
// also seen in the wild for prefix `MicrosoftPhoto`: 'http://ns.microsoft.com/photo/1.0'
static const microsoftPhoto = 'http://ns.microsoft.com/photo/1.0/';
@@ -111,6 +112,7 @@ class Namespaces {
iptc4xmpExt: 'IPTC Extension',
lr: 'Lightroom',
mediapro: 'MediaPro',
+ miCamera: 'Mi Camera',
microsoftPhoto: 'Microsoft Photo 1.0',
mp1: 'Microsoft Photo 1.1',
mp: 'Microsoft Photo 1.2',
diff --git a/lib/widgets/about/about_page.dart b/lib/widgets/about/about_page.dart
index a4d1c18ef..c5c1e654b 100644
--- a/lib/widgets/about/about_page.dart
+++ b/lib/widgets/about/about_page.dart
@@ -1,4 +1,4 @@
-import 'package:aves/model/device.dart';
+import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/about/app_ref.dart';
import 'package:aves/widgets/about/bug_report.dart';
import 'package:aves/widgets/about/credits.dart';
@@ -20,6 +20,7 @@ class AboutPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final appBarTitle = Text(context.l10n.aboutPageTitle);
+ final useTvLayout = settings.useTvLayout;
final body = CustomScrollView(
slivers: [
SliverPadding(
@@ -27,8 +28,8 @@ class AboutPage extends StatelessWidget {
sliver: SliverList(
delegate: SliverChildListDelegate(
[
- const AppReference(),
- if (!device.isTelevision) ...[
+ AppReference(showLogo: !useTvLayout),
+ if (!settings.useTvLayout) ...[
const Divider(),
const BugReport(),
],
@@ -46,7 +47,7 @@ class AboutPage extends StatelessWidget {
],
);
- if (device.isTelevision) {
+ if (useTvLayout) {
return Scaffold(
body: AvesPopScope(
handlers: const [TvNavigationPopHandler.pop],
@@ -55,7 +56,12 @@ class AboutPage extends StatelessWidget {
TvRail(
controller: context.read(),
),
- Expanded(child: body),
+ Expanded(
+ child: DirectionalSafeArea(
+ start: false,
+ child: body,
+ ),
+ ),
],
),
),
diff --git a/lib/widgets/about/app_ref.dart b/lib/widgets/about/app_ref.dart
index f9c05ac66..ce4a94ba4 100644
--- a/lib/widgets/about/app_ref.dart
+++ b/lib/widgets/about/app_ref.dart
@@ -10,7 +10,12 @@ import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
class AppReference extends StatefulWidget {
- const AppReference({super.key});
+ final bool showLogo;
+
+ const AppReference({
+ super.key,
+ required this.showLogo,
+ });
@override
State createState() => _AppReferenceState();
@@ -52,10 +57,12 @@ class _AppReferenceState extends State {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
- AvesLogo(
- size: style.fontSize! * MediaQuery.textScaleFactorOf(context) * 1.3,
- ),
- const SizedBox(width: 8),
+ if (widget.showLogo) ...[
+ AvesLogo(
+ size: style.fontSize! * MediaQuery.textScaleFactorOf(context) * 1.3,
+ ),
+ const SizedBox(width: 8),
+ ],
Text(
'${context.l10n.appName} ${snapshot.data?.version}',
style: style,
diff --git a/lib/widgets/about/licenses.dart b/lib/widgets/about/licenses.dart
index 08088bfdd..813776474 100644
--- a/lib/widgets/about/licenses.dart
+++ b/lib/widgets/about/licenses.dart
@@ -2,6 +2,7 @@ import 'package:aves/app_flavor.dart';
import 'package:aves/ref/brand_colors.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/utils/constants.dart';
+import 'package:aves/utils/dependencies.dart';
import 'package:aves/widgets/common/basic/link_chip.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
@@ -24,10 +25,10 @@ class _LicensesState extends State {
@override
void initState() {
super.initState();
- _platform = List.from(Constants.androidDependencies);
- _flutterPlugins = List.from(Constants.flutterPlugins(context.read()));
- _flutterPackages = List.from(Constants.flutterPackages);
- _dartPackages = List.from(Constants.dartPackages);
+ _platform = List.from(Dependencies.androidDependencies);
+ _flutterPlugins = List.from(Dependencies.flutterPlugins(context.read()));
+ _flutterPackages = List.from(Dependencies.flutterPackages);
+ _dartPackages = List.from(Dependencies.dartPackages);
_sortPackages();
}
diff --git a/lib/widgets/about/translators.dart b/lib/widgets/about/translators.dart
index 66e673776..d83094991 100644
--- a/lib/widgets/about/translators.dart
+++ b/lib/widgets/about/translators.dart
@@ -38,10 +38,14 @@ class AboutTranslators extends StatelessWidget {
Contributor('Allan Nordhøy', 'epost@anotheragency.no'),
Contributor('pemibe', 'pemibe4634@dmonies.com'),
Contributor('Linerly', 'linerly@protonmail.com'),
- Contributor('Olexandr Mazur', 'rozihrash.ya6w7@simplelogin.com'),
+ Contributor('Skrripy', 'rozihrash.ya6w7@simplelogin.com'),
+ Contributor('vesp', 'vesp@post.cz'),
+ Contributor('Dan', 'denqwerta@gmail.com'),
+ Contributor('Tijolinho', 'pedrohenrique29.alfenas@gmail.com'),
+ Contributor('Piotr K', '1337.kelt@gmail.com'),
+ Contributor('rehork', 'cooky@e.email'),
// Contributor('SAMIRAH AIL', 'samiratalzahrani@gmail.com'), // Arabic
// Contributor('Salih Ail', 'rrrfff444@gmail.com'), // Arabic
- // Contributor('Piotr K', '1337.kelt@gmail.com'), // Polish
// Contributor('امیر جهانگرد', 'ijahangard.a@gmail.com'), // Persian
// Contributor('slasb37', 'p84haghi@gmail.com'), // Persian
// Contributor('tryvseu', 'tryvseu@tuta.io'), // Nynorsk
diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart
index a7ff2eae7..445fae01c 100644
--- a/lib/widgets/aves_app.dart
+++ b/lib/widgets/aves_app.dart
@@ -1,4 +1,5 @@
import 'dart:async';
+import 'dart:math';
import 'dart:ui';
import 'package:aves/app_flavor.dart';
@@ -53,8 +54,9 @@ class AvesApp extends StatefulWidget {
final AppFlavor flavor;
// temporary exclude locales not ready yet for prime time
- static final _unsupportedLocales = {'ar', 'fa', 'gl', 'nn', 'pl', 'th'}.map(Locale.new).toSet();
+ static final _unsupportedLocales = {'ar', 'fa', 'gl', 'nn', 'th'}.map(Locale.new).toSet();
static final List supportedLocales = AppLocalizations.supportedLocales.where((v) => !_unsupportedLocales.contains(v)).toList();
+ static final ValueNotifier cutoutInsetsNotifier = ValueNotifier(EdgeInsets.zero);
static final GlobalKey navigatorKey = GlobalKey(debugLabel: 'app-navigator');
// do not monitor all `ModalRoute`s, which would include popup menus,
@@ -135,11 +137,7 @@ class _AvesAppState extends State with WidgetsBindingObserver {
final Set _changedUris = {};
Size? _screenSize;
- // Flutter has various page transition implementations for Android:
- // - `FadeUpwardsPageTransitionsBuilder` on Oreo / API 27 and below
- // - `OpenUpwardsPageTransitionsBuilder` on Pie / API 28
- // - `ZoomPageTransitionsBuilder` on Android 10 / API 29 and above (default in Flutter v3.0.0)
- final ValueNotifier _pageTransitionsBuilderNotifier = ValueNotifier(const FadeUpwardsPageTransitionsBuilder());
+ final ValueNotifier _pageTransitionsBuilderNotifier = ValueNotifier(defaultPageTransitionsBuilder);
final ValueNotifier _tvMediaQueryModifierNotifier = ValueNotifier(null);
final ValueNotifier _appModeNotifier = ValueNotifier(AppMode.main);
@@ -151,6 +149,12 @@ class _AvesAppState extends State with WidgetsBindingObserver {
final EventChannel _analysisCompletionChannel = const OptionalEventChannel('deckers.thibault/aves/analysis_events');
final EventChannel _errorChannel = const OptionalEventChannel('deckers.thibault/aves/error');
+ // Flutter has various page transition implementations for Android:
+ // - `FadeUpwardsPageTransitionsBuilder` on Oreo / API 27 and below
+ // - `OpenUpwardsPageTransitionsBuilder` on Pie / API 28
+ // - `ZoomPageTransitionsBuilder` on Android 10 / API 29 and above (default in Flutter v3.0.0)
+ static const defaultPageTransitionsBuilder = FadeUpwardsPageTransitionsBuilder();
+
@override
void initState() {
super.initState();
@@ -164,6 +168,7 @@ class _AvesAppState extends State with WidgetsBindingObserver {
_subscriptions.add(_newIntentChannel.receiveBroadcastStream().listen((event) => _onNewIntent(event as Map?)));
_subscriptions.add(_analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion()));
_subscriptions.add(_errorChannel.receiveBroadcastStream().listen((event) => _onError(event as String?)));
+ _updateCutoutInsets();
WidgetsBinding.instance.addObserver(this);
}
@@ -375,6 +380,15 @@ class _AvesAppState extends State with WidgetsBindingObserver {
}
}
+ @override
+ void didChangeMetrics() => _updateCutoutInsets();
+
+ Future _updateCutoutInsets() async {
+ if (await windowService.isCutoutAware()) {
+ AvesApp.cutoutInsetsNotifier.value = await windowService.getCutoutInsets();
+ }
+ }
+
Widget _getFirstPage({Map? intentData}) => settings.hasAcceptedTerms ? HomePage(intentData: intentData) : const WelcomePage();
Size? _getScreenSize() {
@@ -407,17 +421,11 @@ class _AvesAppState extends State with WidgetsBindingObserver {
final stopwatch = Stopwatch()..start();
await device.init();
- if (device.isTelevision) {
- _pageTransitionsBuilderNotifier.value = const TvPageTransitionsBuilder();
- _tvMediaQueryModifierNotifier.value = (mq) => mq.copyWith(
- textScaleFactor: 1.1,
- navigationMode: NavigationMode.directional,
- );
- }
await mobileServices.init();
await settings.init(monitorPlatformSettings: true);
settings.isRotationLocked = await windowService.isRotationLocked();
settings.areAnimationsRemoved = await AccessibilityService.areAnimationsRemoved();
+ await _onTvLayoutChanged();
_monitorSettings();
FijkLog.setLevel(FijkLogLevel.Warn);
@@ -426,6 +434,51 @@ class _AvesAppState extends State with WidgetsBindingObserver {
debugPrint('App setup in ${stopwatch.elapsed.inMilliseconds}ms');
}
+ Future _onTvLayoutChanged() async {
+ if (settings.useTvLayout) {
+ settings.applyTvSettings();
+
+ _pageTransitionsBuilderNotifier.value = const TvPageTransitionsBuilder();
+ _tvMediaQueryModifierNotifier.value = (mq) {
+ // cf https://developer.android.com/training/tv/start/layouts.html#overscan
+ final screenSize = mq.size;
+ const overscanFactor = .05;
+ final overscanInsets = EdgeInsets.symmetric(
+ vertical: screenSize.shortestSide * overscanFactor,
+ horizontal: screenSize.longestSide * overscanFactor,
+ );
+ final oldViewPadding = mq.viewPadding;
+ final newViewPadding = EdgeInsets.only(
+ top: max(oldViewPadding.top, overscanInsets.top),
+ right: max(oldViewPadding.right, overscanInsets.right),
+ bottom: max(oldViewPadding.bottom, overscanInsets.bottom),
+ left: max(oldViewPadding.left, overscanInsets.left),
+ );
+ var newPadding = newViewPadding - mq.viewInsets;
+ newPadding = EdgeInsets.only(
+ top: max(0.0, newPadding.top),
+ right: max(0.0, newPadding.right),
+ bottom: max(0.0, newPadding.bottom),
+ left: max(0.0, newPadding.left),
+ );
+
+ return mq.copyWith(
+ textScaleFactor: 1.1,
+ padding: newPadding,
+ viewPadding: newViewPadding,
+ navigationMode: NavigationMode.directional,
+ );
+ };
+ if (settings.forceTvLayout) {
+ await windowService.requestOrientation(Orientation.landscape);
+ }
+ } else {
+ _pageTransitionsBuilderNotifier.value = defaultPageTransitionsBuilder;
+ _tvMediaQueryModifierNotifier.value = null;
+ await windowService.requestOrientation(null);
+ }
+ }
+
void _monitorSettings() {
void applyIsInstalledAppAccessAllowed() {
if (settings.isInstalledAppAccessAllowed) {
@@ -439,15 +492,32 @@ class _AvesAppState extends State with WidgetsBindingObserver {
void applyKeepScreenOn() => settings.keepScreenOn.apply();
void applyIsRotationLocked() {
- if (!settings.isRotationLocked) {
+ if (!settings.isRotationLocked && !settings.useTvLayout) {
windowService.requestOrientation();
}
}
- settings.updateStream.where((event) => event.key == Settings.isInstalledAppAccessAllowedKey).listen((_) => applyIsInstalledAppAccessAllowed());
- settings.updateStream.where((event) => event.key == Settings.displayRefreshRateModeKey).listen((_) => applyDisplayRefreshRateMode());
- settings.updateStream.where((event) => event.key == Settings.keepScreenOnKey).listen((_) => applyKeepScreenOn());
- settings.updateStream.where((event) => event.key == Settings.platformAccelerometerRotationKey).listen((_) => applyIsRotationLocked());
+ void applyForceTvLayout() {
+ _onTvLayoutChanged();
+ unawaited(AvesApp.navigatorKey.currentState!.pushAndRemoveUntil(
+ MaterialPageRoute(
+ settings: const RouteSettings(name: HomePage.routeName),
+ builder: (_) => _getFirstPage(),
+ ),
+ (route) => false,
+ ));
+ }
+
+ final settingStream = settings.updateStream;
+ // app
+ settingStream.where((event) => event.key == Settings.isInstalledAppAccessAllowedKey).listen((_) => applyIsInstalledAppAccessAllowed());
+ // display
+ settingStream.where((event) => event.key == Settings.displayRefreshRateModeKey).listen((_) => applyDisplayRefreshRateMode());
+ settingStream.where((event) => event.key == Settings.forceTvLayoutKey).listen((_) => applyForceTvLayout());
+ // navigation
+ settingStream.where((event) => event.key == Settings.keepScreenOnKey).listen((_) => applyKeepScreenOn());
+ // platform settings
+ settingStream.where((event) => event.key == Settings.platformAccelerometerRotationKey).listen((_) => applyIsRotationLocked());
applyDisplayRefreshRateMode();
applyKeepScreenOn();
diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart
index a76e63f95..3c697f6eb 100644
--- a/lib/widgets/collection/app_bar.dart
+++ b/lib/widgets/collection/app_bar.dart
@@ -1,9 +1,7 @@
import 'dart:async';
-import 'dart:ui';
import 'package:aves/app_mode.dart';
import 'package:aves/model/actions/entry_set_actions.dart';
-import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/query.dart';
@@ -143,9 +141,7 @@ class _CollectionAppBarState extends State with SingleTickerPr
}
@override
- void didChangeMetrics() {
- _updateStatusBarHeight();
- }
+ void didChangeMetrics() => _updateStatusBarHeight();
@override
Widget build(BuildContext context) {
@@ -153,69 +149,76 @@ class _CollectionAppBarState extends State with SingleTickerPr
final selection = context.watch>();
final isSelecting = selection.isSelecting;
_isSelectingNotifier.value = isSelecting;
- return AnimatedBuilder(
- animation: collection.filterChangeNotifier,
- builder: (context, child) {
- final removableFilters = appMode != AppMode.pickMediaInternal;
- return Selector(
- selector: (context, query) => query.enabled,
- builder: (context, queryEnabled, child) {
- return Selector>(
- selector: (context, s) => s.collectionBrowsingQuickActions,
- builder: (context, _, child) {
- final isTelevision = device.isTelevision;
- final actions = _buildActions(context, selection);
- return AvesAppBar(
- contentHeight: appBarContentHeight,
- leading: _buildAppBarLeading(
- hasDrawer: appMode.canNavigate,
- isSelecting: isSelecting,
- ),
- title: _buildAppBarTitle(isSelecting),
- actions: isTelevision ? [] : actions,
- bottom: Column(
- children: [
- if (isTelevision)
- SizedBox(
- height: CaptionedButton.getTelevisionButtonHeight(context),
- child: ListView(
- padding: const EdgeInsets.symmetric(horizontal: 8),
- scrollDirection: Axis.horizontal,
- children: actions,
+ return NotificationListener(
+ // cancel notification bubbling so that the draggable scroll bar
+ // does not misinterpret filter bar scrolling for collection scrolling
+ onNotification: (notification) => true,
+ child: AnimatedBuilder(
+ animation: collection.filterChangeNotifier,
+ builder: (context, child) {
+ final removableFilters = appMode != AppMode.pickMediaInternal;
+ return Selector(
+ selector: (context, query) => query.enabled,
+ builder: (context, queryEnabled, child) {
+ return Selector>(
+ selector: (context, s) => s.collectionBrowsingQuickActions,
+ builder: (context, _, child) {
+ final useTvLayout = settings.useTvLayout;
+ final actions = _buildActions(context, selection);
+ final onFilterTap = removableFilters ? collection.removeFilter : null;
+ return AvesAppBar(
+ contentHeight: appBarContentHeight,
+ pinned: context.select, bool>((selection) => selection.isSelecting),
+ leading: _buildAppBarLeading(
+ hasDrawer: appMode.canNavigate,
+ isSelecting: isSelecting,
+ ),
+ title: _buildAppBarTitle(isSelecting),
+ actions: useTvLayout ? [] : actions,
+ bottom: Column(
+ children: [
+ if (useTvLayout)
+ SizedBox(
+ height: CaptionedButton.getTelevisionButtonHeight(context),
+ child: ListView(
+ padding: const EdgeInsets.symmetric(horizontal: 8),
+ scrollDirection: Axis.horizontal,
+ children: actions,
+ ),
),
- ),
- if (showFilterBar)
- NotificationListener(
- onNotification: (notification) {
- collection.addFilter(notification.reversedFilter);
- return true;
- },
- child: FilterBar(
- filters: visibleFilters,
- removable: removableFilters,
- onTap: removableFilters ? collection.removeFilter : null,
+ if (showFilterBar)
+ NotificationListener(
+ onNotification: (notification) {
+ collection.addFilter(notification.reversedFilter);
+ return true;
+ },
+ child: FilterBar(
+ filters: visibleFilters,
+ onTap: onFilterTap,
+ onRemove: onFilterTap,
+ ),
),
- ),
- if (queryEnabled)
- EntryQueryBar(
- queryNotifier: context.select>((query) => query.queryNotifier),
- focusNode: _queryBarFocusNode,
- ),
- ],
- ),
- transitionKey: isSelecting,
- );
- },
- );
- },
- );
- },
+ if (queryEnabled)
+ EntryQueryBar(
+ queryNotifier: context.select>((query) => query.queryNotifier),
+ focusNode: _queryBarFocusNode,
+ ),
+ ],
+ ),
+ transitionKey: isSelecting,
+ );
+ },
+ );
+ },
+ );
+ },
+ ),
);
}
double get appBarContentHeight {
double height = kToolbarHeight;
- if (device.isTelevision) {
+ if (settings.useTvLayout) {
height += CaptionedButton.getTelevisionButtonHeight(context);
}
if (showFilterBar) {
@@ -228,7 +231,7 @@ class _CollectionAppBarState extends State with SingleTickerPr
}
Widget? _buildAppBarLeading({required bool hasDrawer, required bool isSelecting}) {
- if (device.isTelevision) return null;
+ if (settings.useTvLayout) return null;
if (!hasDrawer) {
return const CloseButton();
@@ -310,7 +313,7 @@ class _CollectionAppBarState extends State with SingleTickerPr
selectedItemCount: selectedItemCount,
);
- return device.isTelevision
+ return settings.useTvLayout
? _buildTelevisionActions(
context: context,
appMode: appMode,
@@ -342,7 +345,13 @@ class _CollectionAppBarState extends State with SingleTickerPr
].where(isVisible).map((action) {
final enabled = canApply(action);
return CaptionedButton(
- iconButton: _buildButtonIcon(context, action, enabled: enabled, selection: selection),
+ iconButtonBuilder: (context, focusNode) => _buildButtonIcon(
+ context,
+ action,
+ enabled: enabled,
+ selection: selection,
+ focusNode: focusNode,
+ ),
captionText: _buildButtonCaption(context, action, enabled: enabled),
onPressed: enabled ? () => _onActionSelected(action) : null,
);
@@ -383,7 +392,7 @@ class _CollectionAppBarState extends State with SingleTickerPr
...(isSelecting ? selectionMenuActions : browsingMenuActions).where(isVisible).map(
(action) => _toMenuItem(action, enabled: canApply(action), selection: selection),
),
- if (isSelecting && !device.isReadOnly && appMode == AppMode.main && !isTrash)
+ if (isSelecting && !settings.isReadOnly && appMode == AppMode.main && !isTrash)
PopupMenuItem(
enabled: hasSelection,
padding: EdgeInsets.zero,
@@ -429,6 +438,7 @@ class _CollectionAppBarState extends State with SingleTickerPr
BuildContext context,
EntrySetAction action, {
required bool enabled,
+ FocusNode? focusNode,
required Selection selection,
}) {
final onPressed = enabled ? () => _onActionSelected(action) : null;
@@ -441,12 +451,14 @@ class _CollectionAppBarState extends State with SingleTickerPr
return TitleSearchToggler(
queryEnabled: queryEnabled,
onPressed: onPressed,
+ focusNode: focusNode,
);
},
);
case EntrySetAction.toggleFavourite:
return FavouriteToggler(
entries: _getExpandedSelectedItems(selection),
+ focusNode: focusNode,
onPressed: onPressed,
);
default:
@@ -454,6 +466,7 @@ class _CollectionAppBarState extends State with SingleTickerPr
key: _getActionKey(action),
icon: action.getIcon(),
onPressed: onPressed,
+ focusNode: focusNode,
tooltip: action.getText(context),
);
}
@@ -577,7 +590,7 @@ class _CollectionAppBarState extends State with SingleTickerPr
void _onQueryFocusRequest() => _queryBarFocusNode.requestFocus();
void _updateStatusBarHeight() {
- _statusBarHeight = EdgeInsets.fromWindowPadding(window.padding, window.devicePixelRatio).top;
+ _statusBarHeight = context.read().padding.top;
_updateAppBarHeight();
}
diff --git a/lib/widgets/collection/collection_grid.dart b/lib/widgets/collection/collection_grid.dart
index 051a3935b..a063ed908 100644
--- a/lib/widgets/collection/collection_grid.dart
+++ b/lib/widgets/collection/collection_grid.dart
@@ -1,7 +1,6 @@
import 'dart:async';
import 'package:aves/app_mode.dart';
-import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/favourites.dart';
import 'package:aves/model/filters/favourite.dart';
@@ -57,7 +56,7 @@ class CollectionGrid extends StatefulWidget {
static const double fixedExtentLayoutSpacing = 2;
static const double mosaicLayoutSpacing = 4;
- static int get columnCountDefault => device.isTelevision ? 6 : 4;
+ static int get columnCountDefault => settings.useTvLayout ? 6 : 4;
const CollectionGrid({
super.key,
@@ -176,7 +175,7 @@ class _CollectionGridContentState extends State<_CollectionGridContent> {
tileLayout: tileLayout,
isScrollingNotifier: _isScrollingNotifier,
);
- if (!device.isTelevision) return tile;
+ if (!settings.useTvLayout) return tile;
return Focus(
onFocusChange: (focused) {
@@ -281,7 +280,7 @@ class _CollectionSectionedContentState extends State<_CollectionSectionedContent
child: scrollView,
);
- final selector = GridSelectionGestureDetector(
+ final selector = GridSelectionGestureDetector(
scrollableKey: _scrollableKey,
selectable: widget.selectable,
items: collection.sortedEntries,
@@ -580,9 +579,7 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
});
}
- void _stopScrollMonitoringTimer() {
- _scrollMonitoringTimer?.cancel();
- }
+ void _stopScrollMonitoringTimer() => _scrollMonitoringTimer?.cancel();
Map _getCrumbs(List sectionLayouts) {
final crumbs = {};
diff --git a/lib/widgets/collection/collection_page.dart b/lib/widgets/collection/collection_page.dart
index ece27a063..39b2ea928 100644
--- a/lib/widgets/collection/collection_page.dart
+++ b/lib/widgets/collection/collection_page.dart
@@ -1,7 +1,6 @@
import 'dart:async';
import 'package:aves/app_mode.dart';
-import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/query.dart';
@@ -83,6 +82,7 @@ class _CollectionPageState extends State {
@override
Widget build(BuildContext context) {
+ final useTvLayout = settings.useTvLayout;
final liveFilter = _collection.filters.firstWhereOrNull((v) => v is QueryFilter && v.live) as QueryFilter?;
return SelectionProvider(
child: Selector, bool>(
@@ -105,11 +105,12 @@ class _CollectionPageState extends State {
TvNavigationPopHandler.pop,
_doubleBackPopHandler.pop,
],
- child: const GestureAreaProtectorStack(
- child: SafeArea(
+ child: GestureAreaProtectorStack(
+ child: DirectionalSafeArea(
+ start: !useTvLayout,
top: false,
bottom: false,
- child: CollectionGrid(
+ child: const CollectionGrid(
// key is expected by test driver
key: Key('collection-grid'),
settingsRouteKey: CollectionPage.routeName,
@@ -122,7 +123,7 @@ class _CollectionPageState extends State {
);
Widget page;
- if (device.isTelevision) {
+ if (useTvLayout) {
page = Scaffold(
body: Row(
children: [
diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart
index 93b35158f..3198fd9f1 100644
--- a/lib/widgets/collection/entry_set_action_delegate.dart
+++ b/lib/widgets/collection/entry_set_action_delegate.dart
@@ -17,6 +17,7 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/analysis_controller.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart';
+import 'package:aves/services/android_app_service.dart';
import 'package:aves/services/common/image_op_events.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart';
@@ -55,7 +56,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
required int selectedItemCount,
required bool isTrash,
}) {
- final canWrite = !device.isReadOnly;
+ final canWrite = !settings.isReadOnly;
final isMain = appMode == AppMode.main;
switch (action) {
// general
@@ -69,7 +70,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
return isSelecting && selectedItemCount == itemCount;
// browsing
case EntrySetAction.searchCollection:
- return !device.isTelevision && appMode.canNavigate && !isSelecting;
+ return !settings.useTvLayout && appMode.canNavigate && !isSelecting;
case EntrySetAction.toggleTitleSearch:
return !isSelecting;
case EntrySetAction.addShortcut:
@@ -82,7 +83,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
case EntrySetAction.stats:
return isMain;
case EntrySetAction.rescan:
- return !device.isTelevision && isMain && !isTrash;
+ return !settings.useTvLayout && isMain && !isTrash;
// selecting
case EntrySetAction.share:
case EntrySetAction.toggleFavourite:
@@ -252,11 +253,21 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
return groupedEntries.expand((entry) => entry.burstEntries ?? {entry}).toSet();
}
- void _share(BuildContext context) {
+ Future _share(BuildContext context) async {
final entries = _getTargetItems(context);
- androidAppService.shareEntries(entries).then((success) {
- if (!success) showNoMatchingAppDialog(context);
- });
+ try {
+ if (!await androidAppService.shareEntries(entries)) {
+ await showNoMatchingAppDialog(context);
+ }
+ } on TooManyItemsException catch (_) {
+ await showDialog(
+ context: context,
+ builder: (context) => AvesDialog(
+ content: Text(context.l10n.tooManyItemsErrorDialogMessage),
+ actions: const [OkButton()],
+ ),
+ );
+ }
}
void _rescan(BuildContext context) {
@@ -447,25 +458,20 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
if (unsupported.isEmpty) return supported;
final unsupportedTypes = unsupported.map((entry) => entry.mimeType).toSet().map(MimeUtils.displayType).toList()..sort();
+ final l10n = context.l10n;
final confirmed = await showDialog(
context: context,
- builder: (context) {
- final l10n = context.l10n;
- return AvesDialog(
- content: Text(l10n.unsupportedTypeDialogMessage(unsupportedTypes.length, unsupportedTypes.join(', '))),
- actions: [
+ builder: (context) => AvesDialog(
+ content: Text(l10n.unsupportedTypeDialogMessage(unsupportedTypes.length, unsupportedTypes.join(', '))),
+ actions: [
+ const CancelButton(),
+ if (supported.isNotEmpty)
TextButton(
- onPressed: () => Navigator.pop(context),
- child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
+ onPressed: () => Navigator.pop(context, true),
+ child: Text(l10n.continueButtonLabel),
),
- if (supported.isNotEmpty)
- TextButton(
- onPressed: () => Navigator.pop(context, true),
- child: Text(l10n.continueButtonLabel),
- ),
- ],
- );
- },
+ ],
+ ),
);
if (confirmed == null || !confirmed) return null;
@@ -536,21 +542,16 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
Future removeLocation(BuildContext context, Set entries) async {
final confirmed = await showDialog(
context: context,
- builder: (context) {
- return AvesDialog(
- content: Text(context.l10n.genericDangerWarningDialogMessage),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(context),
- child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
- ),
- TextButton(
- onPressed: () => Navigator.pop(context, true),
- child: Text(context.l10n.applyButtonLabel),
- ),
- ],
- );
- },
+ builder: (context) => AvesDialog(
+ content: Text(context.l10n.genericDangerWarningDialogMessage),
+ actions: [
+ const CancelButton(),
+ TextButton(
+ onPressed: () => Navigator.pop(context, true),
+ child: Text(context.l10n.applyButtonLabel),
+ ),
+ ],
+ ),
);
if (confirmed == null || !confirmed) return;
diff --git a/lib/widgets/collection/filter_bar.dart b/lib/widgets/collection/filter_bar.dart
index dfbb3b873..20f1a47ed 100644
--- a/lib/widgets/collection/filter_bar.dart
+++ b/lib/widgets/collection/filter_bar.dart
@@ -11,14 +11,13 @@ class FilterBar extends StatefulWidget {
static const double preferredHeight = AvesFilterChip.minChipHeight + verticalPadding;
final List filters;
- final bool removable;
- final FilterCallback? onTap;
+ final FilterCallback? onTap, onRemove;
FilterBar({
super.key,
required Set filters,
- this.removable = false,
this.onTap,
+ this.onRemove,
}) : filters = List.from(filters)..sort();
@override
@@ -31,8 +30,6 @@ class _FilterBarState extends State {
List get filters => widget.filters;
- FilterCallback? get onTap => widget.onTap;
-
@override
void didUpdateWidget(covariant FilterBar oldWidget) {
super.didUpdateWidget(oldWidget);
@@ -85,49 +82,51 @@ class _FilterBarState extends State {
// chip border clipping when the floating app bar is fading
color: Colors.transparent,
height: FilterBar.preferredHeight,
- child: NotificationListener(
- // cancel notification bubbling so that the draggable scroll bar
- // does not misinterpret filter bar scrolling for collection scrolling
- onNotification: (notification) => true,
- child: AnimatedList(
- key: _animatedListKey,
- initialItemCount: filters.length,
- scrollDirection: Axis.horizontal,
- padding: FilterBar.rowPadding,
- itemBuilder: (context, index, animation) {
- if (index >= filters.length) return const SizedBox();
- return _buildChip(filters.toList()[index]);
- },
- ),
+ child: AnimatedList(
+ key: _animatedListKey,
+ initialItemCount: filters.length,
+ scrollDirection: Axis.horizontal,
+ padding: FilterBar.rowPadding,
+ itemBuilder: (context, index, animation) {
+ if (index >= filters.length) return const SizedBox();
+ return _buildChip(filters.toList()[index]);
+ },
),
);
}
Widget _buildChip(CollectionFilter filter) {
+ final onTap = widget.onTap != null
+ ? (filter) {
+ _userTappedFilter = filter;
+ widget.onTap?.call(filter);
+ }
+ : null;
+ final onRemove = widget.onRemove != null
+ ? (filter) {
+ _userTappedFilter = filter;
+ widget.onRemove?.call(filter);
+ }
+ : null;
return _Chip(
filter: filter,
- removable: widget.removable,
single: filters.length == 1,
- onTap: onTap != null
- ? (filter) {
- _userTappedFilter = filter;
- onTap!(filter);
- }
- : null,
+ onTap: onTap,
+ onRemove: onRemove,
);
}
}
class _Chip extends StatelessWidget {
final CollectionFilter filter;
- final bool removable, single;
- final FilterCallback? onTap;
+ final bool single;
+ final FilterCallback? onTap, onRemove;
const _Chip({
required this.filter,
- required this.removable,
required this.single,
required this.onTap,
+ required this.onRemove,
});
@override
@@ -138,7 +137,6 @@ class _Chip extends StatelessWidget {
child: AvesFilterChip(
key: ValueKey(filter),
filter: filter,
- removable: removable,
maxWidth: single
? AvesFilterChip.computeMaxWidth(
context,
@@ -149,6 +147,7 @@ class _Chip extends StatelessWidget {
: null,
heroType: HeroType.always,
onTap: onTap,
+ onRemove: onRemove,
),
),
);
diff --git a/lib/widgets/common/action_controls/quick_choosers/common/button.dart b/lib/widgets/common/action_controls/quick_choosers/common/button.dart
index d83f728ad..6b6636b0d 100644
--- a/lib/widgets/common/action_controls/quick_choosers/common/button.dart
+++ b/lib/widgets/common/action_controls/quick_choosers/common/button.dart
@@ -8,12 +8,14 @@ import 'package:provider/provider.dart';
abstract class ChooserQuickButton extends StatefulWidget {
final bool blurred;
final ValueSetter? onChooserValue;
+ final FocusNode? focusNode;
final VoidCallback? onPressed;
const ChooserQuickButton({
super.key,
required this.blurred,
this.onChooserValue,
+ this.focusNode,
required this.onPressed,
});
}
@@ -71,6 +73,7 @@ abstract class ChooserQuickButtonState, U> exten
child: IconButton(
icon: icon,
onPressed: widget.onPressed,
+ focusNode: widget.focusNode,
tooltip: _hasChooser ? null : tooltip,
),
);
diff --git a/lib/widgets/common/action_controls/quick_choosers/rate_button.dart b/lib/widgets/common/action_controls/quick_choosers/rate_button.dart
index cb3882d75..e14e1cb8e 100644
--- a/lib/widgets/common/action_controls/quick_choosers/rate_button.dart
+++ b/lib/widgets/common/action_controls/quick_choosers/rate_button.dart
@@ -8,6 +8,7 @@ class RateButton extends ChooserQuickButton {
super.key,
required super.blurred,
super.onChooserValue,
+ super.focusNode,
required super.onPressed,
});
diff --git a/lib/widgets/common/action_controls/quick_choosers/share_button.dart b/lib/widgets/common/action_controls/quick_choosers/share_button.dart
index d4b234071..379c6b9a2 100644
--- a/lib/widgets/common/action_controls/quick_choosers/share_button.dart
+++ b/lib/widgets/common/action_controls/quick_choosers/share_button.dart
@@ -13,6 +13,7 @@ class ShareButton extends ChooserQuickButton {
required super.blurred,
required this.entries,
super.onChooserValue,
+ super.focusNode,
required super.onPressed,
});
diff --git a/lib/widgets/common/action_controls/quick_choosers/tag_button.dart b/lib/widgets/common/action_controls/quick_choosers/tag_button.dart
index cb048e3e6..6735d644a 100644
--- a/lib/widgets/common/action_controls/quick_choosers/tag_button.dart
+++ b/lib/widgets/common/action_controls/quick_choosers/tag_button.dart
@@ -17,6 +17,7 @@ class TagButton extends ChooserQuickButton {
super.key,
required super.blurred,
super.onChooserValue,
+ super.focusNode,
required super.onPressed,
});
diff --git a/lib/widgets/common/action_controls/togglers/favourite.dart b/lib/widgets/common/action_controls/togglers/favourite.dart
index cfa4d9b4f..c19c646d1 100644
--- a/lib/widgets/common/action_controls/togglers/favourite.dart
+++ b/lib/widgets/common/action_controls/togglers/favourite.dart
@@ -12,12 +12,14 @@ import 'package:provider/provider.dart';
class FavouriteToggler extends StatefulWidget {
final Set entries;
final bool isMenuItem;
+ final FocusNode? focusNode;
final VoidCallback? onPressed;
const FavouriteToggler({
super.key,
required this.entries,
this.isMenuItem = false,
+ this.focusNode,
this.onPressed,
});
@@ -76,6 +78,7 @@ class _FavouriteTogglerState extends State {
IconButton(
icon: Icon(isFavourite ? isFavouriteIcon : isNotFavouriteIcon),
onPressed: widget.onPressed,
+ focusNode: widget.focusNode,
tooltip: isFavourite ? context.l10n.entryActionRemoveFavourite : context.l10n.entryActionAddFavourite,
),
Sweeper(
diff --git a/lib/widgets/common/action_controls/togglers/mute.dart b/lib/widgets/common/action_controls/togglers/mute.dart
index 94c691f8c..e42bcab80 100644
--- a/lib/widgets/common/action_controls/togglers/mute.dart
+++ b/lib/widgets/common/action_controls/togglers/mute.dart
@@ -10,12 +10,14 @@ import 'package:flutter/material.dart';
class MuteToggler extends StatelessWidget {
final AvesVideoController? controller;
final bool isMenuItem;
+ final FocusNode? focusNode;
final VoidCallback? onPressed;
const MuteToggler({
super.key,
required this.controller,
this.isMenuItem = false,
+ this.focusNode,
this.onPressed,
});
@@ -40,6 +42,7 @@ class MuteToggler extends StatelessWidget {
: IconButton(
icon: icon,
onPressed: canDo ? onPressed : null,
+ focusNode: focusNode,
tooltip: text,
);
},
diff --git a/lib/widgets/common/action_controls/togglers/play.dart b/lib/widgets/common/action_controls/togglers/play.dart
index 814958b2b..354da0ddf 100644
--- a/lib/widgets/common/action_controls/togglers/play.dart
+++ b/lib/widgets/common/action_controls/togglers/play.dart
@@ -12,12 +12,14 @@ import 'package:provider/provider.dart';
class PlayToggler extends StatefulWidget {
final AvesVideoController? controller;
final bool isMenuItem;
+ final FocusNode? focusNode;
final VoidCallback? onPressed;
const PlayToggler({
super.key,
required this.controller,
this.isMenuItem = false,
+ this.focusNode,
this.onPressed,
});
@@ -86,6 +88,7 @@ class _PlayTogglerState extends State with SingleTickerProviderStat
progress: _playPauseAnimation,
),
onPressed: widget.onPressed,
+ focusNode: widget.focusNode,
tooltip: text,
);
}
diff --git a/lib/widgets/common/action_controls/togglers/title_search.dart b/lib/widgets/common/action_controls/togglers/title_search.dart
index 3b3d9ae55..dfe577994 100644
--- a/lib/widgets/common/action_controls/togglers/title_search.dart
+++ b/lib/widgets/common/action_controls/togglers/title_search.dart
@@ -8,12 +8,14 @@ import 'package:provider/provider.dart';
class TitleSearchToggler extends StatelessWidget {
final bool queryEnabled, isMenuItem;
+ final FocusNode? focusNode;
final VoidCallback? onPressed;
const TitleSearchToggler({
super.key,
required this.queryEnabled,
this.isMenuItem = false,
+ this.focusNode,
this.onPressed,
});
@@ -29,6 +31,7 @@ class TitleSearchToggler extends StatelessWidget {
: IconButton(
icon: icon,
onPressed: onPressed,
+ focusNode: focusNode,
tooltip: text,
);
}
diff --git a/lib/widgets/common/action_mixins/entry_editor.dart b/lib/widgets/common/action_mixins/entry_editor.dart
index 1f7efb28b..49c411271 100644
--- a/lib/widgets/common/action_mixins/entry_editor.dart
+++ b/lib/widgets/common/action_mixins/entry_editor.dart
@@ -123,21 +123,16 @@ mixin EntryEditorMixin {
if (entries.any((entry) => entry.isMotionPhoto) && types.contains(MetadataType.xmp)) {
final confirmed = await showDialog(
context: context,
- builder: (context) {
- return AvesDialog(
- content: Text(context.l10n.removeEntryMetadataMotionPhotoXmpWarningDialogMessage),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(context),
- child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
- ),
- TextButton(
- onPressed: () => Navigator.pop(context, true),
- child: Text(context.l10n.applyButtonLabel),
- ),
- ],
- );
- },
+ builder: (context) => AvesDialog(
+ content: Text(context.l10n.removeEntryMetadataMotionPhotoXmpWarningDialogMessage),
+ actions: [
+ const CancelButton(),
+ TextButton(
+ onPressed: () => Navigator.pop(context, true),
+ child: Text(context.l10n.applyButtonLabel),
+ ),
+ ],
+ ),
);
if (confirmed == null || !confirmed) return null;
}
diff --git a/lib/widgets/common/action_mixins/feedback.dart b/lib/widgets/common/action_mixins/feedback.dart
index 2ccd754c4..06352fa9d 100644
--- a/lib/widgets/common/action_mixins/feedback.dart
+++ b/lib/widgets/common/action_mixins/feedback.dart
@@ -136,21 +136,20 @@ mixin FeedbackMixin {
int? itemCount,
VoidCallback? onCancel,
void Function(Set processed)? onDone,
- }) {
- return showDialog(
- context: context,
- barrierDismissible: false,
- builder: (context) => ReportOverlay(
- opStream: opStream,
- itemCount: itemCount,
- onCancel: onCancel,
- onDone: (processed) {
- Navigator.pop(context);
- onDone?.call(processed);
- },
- ),
- );
- }
+ }) =>
+ showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (context) => ReportOverlay(
+ opStream: opStream,
+ itemCount: itemCount,
+ onCancel: onCancel,
+ onDone: (processed) {
+ Navigator.pop(context);
+ onDone?.call(processed);
+ },
+ ),
+ );
}
class ReportOverlay extends StatefulWidget {
diff --git a/lib/widgets/common/action_mixins/permission_aware.dart b/lib/widgets/common/action_mixins/permission_aware.dart
index 61a8a918b..b54f2411b 100644
--- a/lib/widgets/common/action_mixins/permission_aware.dart
+++ b/lib/widgets/common/action_mixins/permission_aware.dart
@@ -54,10 +54,7 @@ mixin PermissionAwareMixin {
return AvesDialog(
content: Text(l10n.storageAccessDialogMessage(directory, volume)),
actions: [
- TextButton(
- onPressed: () => Navigator.pop(context),
- child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
- ),
+ const CancelButton(),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text(MaterialLocalizations.of(context).okButtonLabel),
@@ -72,17 +69,10 @@ mixin PermissionAwareMixin {
if (!await deviceService.isSystemFilePickerEnabled()) {
await showDialog(
context: context,
- builder: (context) {
- return AvesDialog(
- content: Text(context.l10n.missingSystemFilePickerDialogMessage),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(context),
- child: Text(MaterialLocalizations.of(context).okButtonLabel),
- ),
- ],
- );
- },
+ builder: (context) => AvesDialog(
+ content: Text(context.l10n.missingSystemFilePickerDialogMessage),
+ actions: const [OkButton()],
+ ),
);
return false;
}
@@ -103,12 +93,7 @@ mixin PermissionAwareMixin {
final volume = dir.getVolumeDescription(context);
return AvesDialog(
content: Text(context.l10n.restrictedAccessDialogMessage(directory, volume)),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(context),
- child: Text(MaterialLocalizations.of(context).okButtonLabel),
- ),
- ],
+ actions: const [OkButton()],
);
},
);
diff --git a/lib/widgets/common/action_mixins/size_aware.dart b/lib/widgets/common/action_mixins/size_aware.dart
index c6fb9c59a..5e57bb021 100644
--- a/lib/widgets/common/action_mixins/size_aware.dart
+++ b/lib/widgets/common/action_mixins/size_aware.dart
@@ -85,12 +85,7 @@ mixin SizeAwareMixin {
final volume = destinationVolume.getDescription(context);
return AvesDialog(
content: Text(l10n.notEnoughSpaceDialogMessage(neededSize, freeSize, volume)),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(context),
- child: Text(MaterialLocalizations.of(context).okButtonLabel),
- ),
- ],
+ actions: const [OkButton()],
);
},
);
diff --git a/lib/widgets/common/basic/color_list_tile.dart b/lib/widgets/common/basic/color_list_tile.dart
index babf354e9..ae44a47fb 100644
--- a/lib/widgets/common/basic/color_list_tile.dart
+++ b/lib/widgets/common/basic/color_list_tile.dart
@@ -1,4 +1,4 @@
-import 'package:aves/model/device.dart';
+import 'package:aves/model/settings/settings.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/fx/borders.dart';
@@ -72,13 +72,13 @@ class _ColorPickerDialogState extends State {
@override
Widget build(BuildContext context) {
- final isTelevision = device.isTelevision;
+ final useTvLayout = settings.useTvLayout;
return AvesDialog(
scrollableContent: [
ColorPicker(
color: color,
onColorChanged: (v) => color = v,
- pickersEnabled: isTelevision
+ pickersEnabled: useTvLayout
? const {
ColorPickerType.primary: true,
ColorPickerType.accent: false,
@@ -90,14 +90,11 @@ class _ColorPickerDialogState extends State {
},
hasBorder: true,
borderRadius: 20,
- subheading: isTelevision ? const SizedBox(height: 16) : null,
+ subheading: useTvLayout ? const SizedBox(height: 16) : null,
)
],
actions: [
- TextButton(
- onPressed: () => Navigator.pop(context),
- child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
- ),
+ const CancelButton(),
TextButton(
onPressed: () => Navigator.pop(context, color),
child: Text(context.l10n.applyButtonLabel),
diff --git a/lib/widgets/common/basic/insets.dart b/lib/widgets/common/basic/insets.dart
index 33e91eb0b..fa1a73d46 100644
--- a/lib/widgets/common/basic/insets.dart
+++ b/lib/widgets/common/basic/insets.dart
@@ -1,5 +1,10 @@
-import 'package:aves/model/device.dart';
+import 'dart:math';
+
+import 'package:aves/model/settings/settings.dart';
+import 'package:aves/widgets/aves_app.dart';
+import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/extensions/media_query.dart';
+import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/common/tile_extent_controller.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@@ -123,8 +128,96 @@ class TvTileGridBottomPaddingSliver extends StatelessWidget {
Widget build(BuildContext context) {
return SliverToBoxAdapter(
child: SizedBox(
- height: device.isTelevision ? context.select((controller) => controller.spacing) : 0,
+ height: settings.useTvLayout ? context.select((controller) => controller.spacing) : 0,
),
);
}
}
+
+// `MediaQuery.padding` matches cutout areas but also includes other system UI like the status bar
+// so we cannot use `SafeArea` along `MediaQuery.removePadding()` to remove cutout areas
+class SafeCutoutArea extends StatelessWidget {
+ final Animation? animation;
+ final Widget child;
+
+ const SafeCutoutArea({
+ super.key,
+ this.animation,
+ required this.child,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return ValueListenableBuilder(
+ valueListenable: AvesApp.cutoutInsetsNotifier,
+ builder: (context, cutoutInsets, child) {
+ return ValueListenableBuilder(
+ valueListenable: animation ?? ValueNotifier(1),
+ builder: (context, factor, child) {
+ final effectiveInsets = cutoutInsets * factor;
+ return Padding(
+ padding: effectiveInsets,
+ child: MediaQueryDataProvider(
+ value: MediaQuery.of(context).removeCutoutInsets(effectiveInsets),
+ child: child!,
+ ),
+ );
+ },
+ child: child,
+ );
+ },
+ child: child,
+ );
+ }
+}
+
+extension ExtraMediaQueryData on MediaQueryData {
+ MediaQueryData removeCutoutInsets(EdgeInsets cutoutInsets) {
+ return copyWith(
+ padding: EdgeInsets.only(
+ left: max(0.0, padding.left - cutoutInsets.left),
+ top: max(0.0, padding.top - cutoutInsets.top),
+ right: max(0.0, padding.right - cutoutInsets.right),
+ bottom: max(0.0, padding.bottom - cutoutInsets.bottom),
+ ),
+ viewPadding: EdgeInsets.only(
+ left: max(0.0, viewPadding.left - cutoutInsets.left),
+ top: max(0.0, viewPadding.top - cutoutInsets.top),
+ right: max(0.0, viewPadding.right - cutoutInsets.right),
+ bottom: max(0.0, viewPadding.bottom - cutoutInsets.bottom),
+ ),
+ );
+ }
+}
+
+class DirectionalSafeArea extends StatelessWidget {
+ final bool start, top, end, bottom;
+ final EdgeInsets minimum;
+ final bool maintainBottomViewPadding;
+ final Widget child;
+
+ const DirectionalSafeArea({
+ super.key,
+ this.start = true,
+ this.top = true,
+ this.end = true,
+ this.bottom = true,
+ this.minimum = EdgeInsets.zero,
+ this.maintainBottomViewPadding = false,
+ required this.child,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ final isRtl = context.isRtl;
+ return SafeArea(
+ left: isRtl ? end : start,
+ top: top,
+ right: isRtl ? start : end,
+ bottom: bottom,
+ minimum: minimum,
+ maintainBottomViewPadding: maintainBottomViewPadding,
+ child: child,
+ );
+ }
+}
diff --git a/lib/widgets/common/behaviour/pop/tv_navigation.dart b/lib/widgets/common/behaviour/pop/tv_navigation.dart
index d996416a4..97480a9af 100644
--- a/lib/widgets/common/behaviour/pop/tv_navigation.dart
+++ b/lib/widgets/common/behaviour/pop/tv_navigation.dart
@@ -1,4 +1,3 @@
-import 'package:aves/model/device.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/home_page.dart';
import 'package:aves/model/settings/settings.dart';
@@ -13,7 +12,7 @@ import 'package:provider/provider.dart';
// address `TV-DB` requirement from https://developer.android.com/docs/quality-guidelines/tv-app-quality
class TvNavigationPopHandler {
static bool pop(BuildContext context) {
- if (!device.isTelevision || _isHome(context)) {
+ if (!settings.useTvLayout || _isHome(context)) {
return true;
}
diff --git a/lib/widgets/common/expandable_filter_row.dart b/lib/widgets/common/expandable_filter_row.dart
index cafcf96b2..54d0ebf63 100644
--- a/lib/widgets/common/expandable_filter_row.dart
+++ b/lib/widgets/common/expandable_filter_row.dart
@@ -67,10 +67,11 @@ class TitledExpandableFilterRow extends StatelessWidget {
class ExpandableFilterRow extends StatelessWidget {
final List filters;
final bool isExpanded;
- final bool removable, showGenericIcon;
+ final bool showGenericIcon;
final Widget? Function(CollectionFilter)? leadingBuilder;
final HeroType Function(CollectionFilter filter)? heroTypeBuilder;
final FilterCallback onTap;
+ final FilterCallback? onRemove;
final OffsetFilterCallback? onLongPress;
static const double horizontalPadding = 8;
@@ -80,11 +81,11 @@ class ExpandableFilterRow extends StatelessWidget {
super.key,
required this.filters,
required this.isExpanded,
- this.removable = false,
this.showGenericIcon = true,
this.leadingBuilder,
this.heroTypeBuilder,
required this.onTap,
+ this.onRemove,
required this.onLongPress,
});
@@ -143,11 +144,11 @@ class ExpandableFilterRow extends StatelessWidget {
// key `album-{path}` is expected by test driver
key: Key(filter.key),
filter: filter,
- removable: removable,
showGenericIcon: showGenericIcon,
leadingOverride: leadingBuilder?.call(filter),
heroType: heroTypeBuilder?.call(filter) ?? HeroType.onTap,
onTap: onTap,
+ onRemove: onRemove,
onLongPress: onLongPress,
);
}
diff --git a/lib/widgets/common/fx/borders.dart b/lib/widgets/common/fx/borders.dart
index 2e2fe275e..b9dc02a0d 100644
--- a/lib/widgets/common/fx/borders.dart
+++ b/lib/widgets/common/fx/borders.dart
@@ -13,15 +13,15 @@ class AvesBorder {
// 1 device pixel for curves is too thin
static double get curvedBorderWidth => window.devicePixelRatio > 2 ? 0.5 : 1.0;
- static BorderSide straightSide(BuildContext context) => BorderSide(
+ static BorderSide straightSide(BuildContext context, {double? width}) => BorderSide(
color: _borderColor(context),
- width: straightBorderWidth,
+ width: width ?? straightBorderWidth,
);
- static BorderSide curvedSide(BuildContext context) => BorderSide(
+ static BorderSide curvedSide(BuildContext context, {double? width}) => BorderSide(
color: _borderColor(context),
- width: curvedBorderWidth,
+ width: width ?? curvedBorderWidth,
);
- static Border border(BuildContext context) => Border.fromBorderSide(curvedSide(context));
+ static Border border(BuildContext context, {double? width}) => Border.fromBorderSide(curvedSide(context, width: width));
}
diff --git a/lib/widgets/common/grid/header.dart b/lib/widgets/common/grid/header.dart
index 341f969e5..bc941d63b 100644
--- a/lib/widgets/common/grid/header.dart
+++ b/lib/widgets/common/grid/header.dart
@@ -1,5 +1,5 @@
-import 'package:aves/model/device.dart';
import 'package:aves/model/selection.dart';
+import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/section_keys.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
@@ -33,18 +33,17 @@ class SectionHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
Widget child = _buildContent(context);
- if (device.isTelevision) {
- final colors = Theme.of(context).colorScheme;
+ if (settings.useTvLayout) {
+ final primaryColor = Theme.of(context).colorScheme.primary;
child = Material(
type: MaterialType.transparency,
child: InkResponse(
onTap: _onTap(context),
- onHover: (_) {},
+ containedInkWell: true,
highlightShape: BoxShape.rectangle,
borderRadius: const BorderRadius.all(Radius.circular(123)),
- containedInkWell: true,
- splashColor: colors.primary.withOpacity(0.12),
- hoverColor: colors.primary.withOpacity(0.04),
+ hoverColor: primaryColor.withOpacity(0.04),
+ splashColor: primaryColor.withOpacity(0.12),
child: child,
),
);
diff --git a/lib/widgets/common/grid/selector.dart b/lib/widgets/common/grid/selector.dart
index 20c0c98f7..e4643e362 100644
--- a/lib/widgets/common/grid/selector.dart
+++ b/lib/widgets/common/grid/selector.dart
@@ -6,6 +6,7 @@ import 'package:aves/utils/math_utils.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/extensions/media_query.dart';
import 'package:aves/widgets/common/grid/sections/list_layout.dart';
+import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@@ -37,7 +38,9 @@ class _GridSelectionGestureDetectorState extends State get items => widget.items;
@@ -56,12 +59,42 @@ class _GridSelectionGestureDetectorState extends State oldWidget) {
+ super.didUpdateWidget(oldWidget);
+ _unregisterWidget(oldWidget);
+ _registerWidget(widget);
+ }
+
+ @override
+ void dispose() {
+ _unregisterWidget(widget);
+ _stopScrollMonitoringTimer();
+ super.dispose();
+ }
+
+ void _registerWidget(GridSelectionGestureDetector widget) {
+ widget.scrollController.addListener(_onScrollChanged);
+ }
+
+ void _unregisterWidget(GridSelectionGestureDetector widget) {
+ widget.scrollController.removeListener(_onScrollChanged);
+ }
+
@override
Widget build(BuildContext context) {
final selectable = widget.selectable;
return GestureDetector(
onLongPressStart: selectable
? (details) {
+ if (_isScrolling) return;
+
final fromItem = _getItemAt(details.localPosition);
if (fromItem == null) return;
@@ -105,6 +138,16 @@ class _GridSelectionGestureDetectorState extends State