Merge branch 'develop'
26
CHANGELOG.md
|
@ -4,6 +4,32 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## <a id="unreleased"></a>[Unreleased]
|
## <a id="unreleased"></a>[Unreleased]
|
||||||
|
|
||||||
|
## <a id="v1.7.9"></a>[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
|
||||||
|
|
||||||
## <a id="v1.7.8"></a>[v1.7.8] - 2022-12-20
|
## <a id="v1.7.8"></a>[v1.7.8] - 2022-12-20
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -15,9 +15,6 @@ Aves is a gallery and metadata explorer app. It is built for Android, with Flutt
|
||||||
[<img src="https://raw.githubusercontent.com/deckerst/common/main/assets/huawei-appgallery-badge-english-black.png"
|
[<img src="https://raw.githubusercontent.com/deckerst/common/main/assets/huawei-appgallery-badge-english-black.png"
|
||||||
alt='Get it on Huawei AppGallery'
|
alt='Get it on Huawei AppGallery'
|
||||||
height="80">](https://appgallery.huawei.com/app/C106014023)
|
height="80">](https://appgallery.huawei.com/app/C106014023)
|
||||||
[<img src="https://raw.githubusercontent.com/deckerst/common/main/assets/samsung-galaxy-store-badge-english.png"
|
|
||||||
alt='Get it on Samsung Galaxy Store'
|
|
||||||
height="80">](https://galaxy.store/aves)
|
|
||||||
[<img src="https://raw.githubusercontent.com/deckerst/common/main/assets/amazon-appstore-badge-english-black.png"
|
[<img src="https://raw.githubusercontent.com/deckerst/common/main/assets/amazon-appstore-badge-english-black.png"
|
||||||
alt='Get it on Amazon Appstore'
|
alt='Get it on Amazon Appstore'
|
||||||
height="80">](https://www.amazon.com/dp/B09XQHQQ72)
|
height="80">](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.
|
**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
|
## Screenshots
|
||||||
|
|
||||||
|
|
|
@ -191,7 +191,7 @@ dependencies {
|
||||||
implementation 'com.drewnoakes:metadata-extractor:2.18.0'
|
implementation 'com.drewnoakes:metadata-extractor:2.18.0'
|
||||||
implementation 'com.github.bumptech.glide:glide:4.14.2'
|
implementation 'com.github.bumptech.glide:glide:4.14.2'
|
||||||
// SLF4J implementation for `mp4parser`
|
// SLF4J implementation for `mp4parser`
|
||||||
implementation 'org.slf4j:slf4j-simple:2.0.3'
|
implementation 'org.slf4j:slf4j-simple:2.0.6'
|
||||||
|
|
||||||
// forked, built by JitPack:
|
// forked, built by JitPack:
|
||||||
// - https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
|
// - https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
|
||||||
|
|
|
@ -205,6 +205,14 @@ This change eventually prevents building the app with Flutter v3.3.3.
|
||||||
android:resource="@xml/app_widget_info" />
|
android:resource="@xml/app_widget_info" />
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name="androidx.media.session.MediaButtonReceiver"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".AnalysisService"
|
android:name=".AnalysisService"
|
||||||
android:description="@string/analysis_service_description"
|
android:description="@string/analysis_service_description"
|
||||||
|
|
|
@ -39,6 +39,7 @@ open class MainActivity : FlutterActivity() {
|
||||||
private lateinit var analysisStreamHandler: AnalysisStreamHandler
|
private lateinit var analysisStreamHandler: AnalysisStreamHandler
|
||||||
internal lateinit var intentDataMap: MutableMap<String, Any?>
|
internal lateinit var intentDataMap: MutableMap<String, Any?>
|
||||||
private lateinit var analysisHandler: AnalysisHandler
|
private lateinit var analysisHandler: AnalysisHandler
|
||||||
|
private lateinit var mediaSessionHandler: MediaSessionHandler
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
Log.i(LOG_TAG, "onCreate intent=$intent")
|
Log.i(LOG_TAG, "onCreate intent=$intent")
|
||||||
|
@ -70,9 +71,21 @@ open class MainActivity : FlutterActivity() {
|
||||||
|
|
||||||
val messenger = flutterEngine!!.dartExecutor
|
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
|
// dart -> platform -> dart
|
||||||
// - need Context
|
// - need Context
|
||||||
analysisHandler = AnalysisHandler(this, ::onAnalysisCompleted)
|
analysisHandler = AnalysisHandler(this, ::onAnalysisCompleted)
|
||||||
|
mediaSessionHandler = MediaSessionHandler(this, mediaCommandStreamHandler)
|
||||||
MethodChannel(messenger, AnalysisHandler.CHANNEL).setMethodCallHandler(analysisHandler)
|
MethodChannel(messenger, AnalysisHandler.CHANNEL).setMethodCallHandler(analysisHandler)
|
||||||
MethodChannel(messenger, AppAdapterHandler.CHANNEL).setMethodCallHandler(AppAdapterHandler(this))
|
MethodChannel(messenger, AppAdapterHandler.CHANNEL).setMethodCallHandler(AppAdapterHandler(this))
|
||||||
MethodChannel(messenger, DebugHandler.CHANNEL).setMethodCallHandler(DebugHandler(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, HomeWidgetHandler.CHANNEL).setMethodCallHandler(HomeWidgetHandler(this))
|
||||||
MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this))
|
MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this))
|
||||||
MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(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, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(this))
|
||||||
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this))
|
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this))
|
||||||
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(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) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||||
setupShortcuts()
|
setupShortcuts()
|
||||||
}
|
}
|
||||||
|
@ -166,6 +169,7 @@ open class MainActivity : FlutterActivity() {
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
Log.i(LOG_TAG, "onDestroy")
|
Log.i(LOG_TAG, "onDestroy")
|
||||||
|
mediaSessionHandler.dispose()
|
||||||
mediaStoreChangeStreamHandler.dispose()
|
mediaStoreChangeStreamHandler.dispose()
|
||||||
settingsChangeStreamHandler.dispose()
|
settingsChangeStreamHandler.dispose()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
@ -431,7 +435,7 @@ open class MainActivity : FlutterActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var errorStreamHandler: ErrorStreamHandler? = null
|
private var errorStreamHandler: ErrorStreamHandler? = null
|
||||||
|
|
||||||
suspend fun notifyError(error: String) {
|
suspend fun notifyError(error: String) {
|
||||||
Log.e(LOG_TAG, "notifyError error=$error")
|
Log.e(LOG_TAG, "notifyError error=$error")
|
||||||
|
|
|
@ -10,6 +10,7 @@ import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.os.TransactionTooLargeException
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.content.pm.ShortcutInfoCompat
|
import androidx.core.content.pm.ShortcutInfoCompat
|
||||||
|
@ -280,7 +281,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
|
||||||
val title = call.argument<String>("title")
|
val title = call.argument<String>("title")
|
||||||
val urisByMimeType = call.argument<Map<String, List<String>>>("urisByMimeType")
|
val urisByMimeType = call.argument<Map<String, List<String>>>("urisByMimeType")
|
||||||
if (urisByMimeType == null) {
|
if (urisByMimeType == null) {
|
||||||
result.error("setAs-args", "missing arguments", null)
|
result.error("share-args", "missing arguments", null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,15 +289,14 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
|
||||||
val mimeTypes = urisByMimeType.keys.toTypedArray()
|
val mimeTypes = urisByMimeType.keys.toTypedArray()
|
||||||
|
|
||||||
// simplify share intent for a single item, as some apps can handle one item but not more
|
// 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 uri = uriList.first()
|
||||||
val mimeType = mimeTypes.first()
|
val mimeType = mimeTypes.first()
|
||||||
|
|
||||||
val intent = Intent(Intent.ACTION_SEND)
|
Intent(Intent.ACTION_SEND)
|
||||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
.setType(mimeType)
|
.setType(mimeType)
|
||||||
.putExtra(Intent.EXTRA_STREAM, getShareableUri(context, uri))
|
.putExtra(Intent.EXTRA_STREAM, getShareableUri(context, uri))
|
||||||
safeStartActivityChooser(title, intent)
|
|
||||||
} else {
|
} else {
|
||||||
var mimeType = "*/*"
|
var mimeType = "*/*"
|
||||||
if (mimeTypes.size == 1) {
|
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)
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList)
|
.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList)
|
||||||
.setType(mimeType)
|
.setType(mimeType)
|
||||||
safeStartActivityChooser(title, intent)
|
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
val started = safeStartActivityChooser(title, intent)
|
||||||
result.success(started)
|
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 {
|
private fun safeStartActivity(intent: Intent): Boolean {
|
||||||
|
|
|
@ -11,25 +11,21 @@ import com.bumptech.glide.load.resource.bitmap.TransformationUtils
|
||||||
import com.drew.metadata.xmp.XmpDirectory
|
import com.drew.metadata.xmp.XmpDirectory
|
||||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
||||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
|
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
|
||||||
import deckers.thibault.aves.metadata.Metadata
|
import deckers.thibault.aves.metadata.*
|
||||||
import deckers.thibault.aves.metadata.MultiPage
|
import deckers.thibault.aves.metadata.XMP.doesPropExist
|
||||||
import deckers.thibault.aves.metadata.XMP.getSafeStructField
|
import deckers.thibault.aves.metadata.XMP.getSafeStructField
|
||||||
import deckers.thibault.aves.metadata.XMPPropName
|
|
||||||
import deckers.thibault.aves.metadata.metadataextractor.Helper
|
import deckers.thibault.aves.metadata.metadataextractor.Helper
|
||||||
import deckers.thibault.aves.model.FieldMap
|
import deckers.thibault.aves.model.FieldMap
|
||||||
import deckers.thibault.aves.model.provider.ContentImageProvider
|
import deckers.thibault.aves.model.provider.ContentImageProvider
|
||||||
import deckers.thibault.aves.model.provider.ImageProvider
|
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.BitmapUtils.getBytes
|
||||||
import deckers.thibault.aves.utils.FileUtils.transferFrom
|
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.canReadWithExifInterface
|
||||||
import deckers.thibault.aves.utils.MimeTypes.canReadWithMetadataExtractor
|
import deckers.thibault.aves.utils.MimeTypes.canReadWithMetadataExtractor
|
||||||
import deckers.thibault.aves.utils.MimeTypes.extensionFor
|
import deckers.thibault.aves.utils.MimeTypes.extensionFor
|
||||||
import deckers.thibault.aves.utils.MimeTypes.isImage
|
import deckers.thibault.aves.utils.MimeTypes.isImage
|
||||||
import deckers.thibault.aves.utils.MimeTypes.isVideo
|
import deckers.thibault.aves.utils.MimeTypes.isVideo
|
||||||
import deckers.thibault.aves.utils.StorageUtils
|
|
||||||
import io.flutter.plugin.common.MethodCall
|
import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
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) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"getExifThumbnails" -> ioScope.launch { safeSuspend(call, result, ::getExifThumbnails) }
|
"getExifThumbnails" -> ioScope.launch { safeSuspend(call, result, ::getExifThumbnails) }
|
||||||
|
"extractGoogleDeviceItem" -> ioScope.launch { safe(call, result, ::extractGoogleDeviceItem) }
|
||||||
"extractMotionPhotoImage" -> ioScope.launch { safe(call, result, ::extractMotionPhotoImage) }
|
"extractMotionPhotoImage" -> ioScope.launch { safe(call, result, ::extractMotionPhotoImage) }
|
||||||
"extractMotionPhotoVideo" -> ioScope.launch { safe(call, result, ::extractMotionPhotoVideo) }
|
"extractMotionPhotoVideo" -> ioScope.launch { safe(call, result, ::extractMotionPhotoVideo) }
|
||||||
"extractVideoEmbeddedPicture" -> ioScope.launch { safe(call, result, ::extractVideoEmbeddedPicture) }
|
"extractVideoEmbeddedPicture" -> ioScope.launch { safe(call, result, ::extractVideoEmbeddedPicture) }
|
||||||
|
@ -84,6 +81,68 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
|
||||||
result.success(thumbnails)
|
result.success(thumbnails)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun extractGoogleDeviceItem(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
val mimeType = call.argument<String>("mimeType")
|
||||||
|
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||||
|
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
|
||||||
|
val displayName = call.argument<String>("displayName")
|
||||||
|
val dataUri = call.argument<String>("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) {
|
private fun extractMotionPhotoImage(call: MethodCall, result: MethodChannel.Result) {
|
||||||
val mimeType = call.argument<String>("mimeType")
|
val mimeType = call.argument<String>("mimeType")
|
||||||
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
package deckers.thibault.aves.channel.calls
|
package deckers.thibault.aves.channel.calls
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.*
|
||||||
|
import android.media.AudioManager
|
||||||
import android.media.session.PlaybackState
|
import android.media.session.PlaybackState
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.support.v4.media.MediaMetadataCompat
|
import android.support.v4.media.MediaMetadataCompat
|
||||||
import android.support.v4.media.session.MediaSessionCompat
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
import android.support.v4.media.session.PlaybackStateCompat
|
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.safe
|
||||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
|
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.FlutterUtils
|
||||||
import deckers.thibault.aves.utils.LogUtils
|
|
||||||
import io.flutter.plugin.common.MethodCall
|
import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||||
|
@ -19,20 +20,36 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.launch
|
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 ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||||
|
|
||||||
private val sessions = HashMap<Uri, MediaSessionCompat>()
|
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) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"update" -> ioScope.launch { safeSuspend(call, result, ::update) }
|
"update" -> ioScope.launch { safeSuspend(call, result, ::updateSession) }
|
||||||
"release" -> ioScope.launch { safe(call, result, ::release) }
|
"release" -> ioScope.launch { safe(call, result, ::releaseSession) }
|
||||||
else -> result.notImplemented()
|
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<String>("uri")?.let { Uri.parse(it) }
|
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||||
val title = call.argument<String>("title")
|
val title = call.argument<String>("title")
|
||||||
val durationMillis = call.argument<Number>("durationMillis")?.toLong()
|
val durationMillis = call.argument<Number>("durationMillis")?.toLong()
|
||||||
|
@ -72,69 +89,51 @@ class MediaSessionHandler(private val context: Context) : MethodCallHandler {
|
||||||
.setActions(actions)
|
.setActions(actions)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
var session = sessions[uri]
|
FlutterUtils.runOnUiThread {
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
session = MediaSessionCompat(context, "aves-$uri")
|
val mbrIntent = MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_PLAY_PAUSE)
|
||||||
sessions[uri] = session
|
val mbrName = ComponentName(context, MediaButtonReceiver::class.java)
|
||||||
|
session = MediaSessionCompat(context, "aves", mbrName, mbrIntent).apply {
|
||||||
|
setCallback(mediaCommandHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session!!.apply {
|
||||||
val metadata = MediaMetadataCompat.Builder()
|
val metadata = MediaMetadataCompat.Builder()
|
||||||
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
|
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
|
||||||
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, title)
|
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, title)
|
||||||
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, durationMillis)
|
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, durationMillis)
|
||||||
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI, uri.toString())
|
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI, uri.toString())
|
||||||
.build()
|
.build()
|
||||||
session.setMetadata(metadata)
|
setMetadata(metadata)
|
||||||
|
setPlaybackState(playbackState)
|
||||||
val callback: MediaSessionCompat.Callback = object : MediaSessionCompat.Callback() {
|
if (!isActive) {
|
||||||
override fun onPlay() {
|
isActive = true
|
||||||
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 {
|
|
||||||
session.setCallback(callback)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
session.setPlaybackState(playbackState)
|
val isPlaying = state == PlaybackStateCompat.STATE_PLAYING
|
||||||
|
if (!wasPlaying && isPlaying) {
|
||||||
if (!session.isActive) {
|
context.registerReceiver(noisyAudioReceiver, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY))
|
||||||
session.isActive = true
|
isNoisyAudioReceiverRegistered = true
|
||||||
|
} else if (wasPlaying && !isPlaying) {
|
||||||
|
context.unregisterReceiver(noisyAudioReceiver)
|
||||||
|
isNoisyAudioReceiverRegistered = false
|
||||||
|
}
|
||||||
|
wasPlaying = isPlaying
|
||||||
}
|
}
|
||||||
|
|
||||||
result.success(null)
|
result.success(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun release(call: MethodCall, result: MethodChannel.Result) {
|
private fun releaseSession(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
|
||||||
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
session?.let {
|
||||||
|
it.release()
|
||||||
if (uri == null) {
|
session = null
|
||||||
result.error("release-args", "missing arguments", null)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sessions[uri]?.release()
|
|
||||||
|
|
||||||
result.success(null)
|
result.success(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOG_TAG = LogUtils.createTag<MediaSessionHandler>()
|
|
||||||
const val CHANNEL = "deckers.thibault/aves/media_session"
|
const val CHANNEL = "deckers.thibault/aves/media_session"
|
||||||
|
|
||||||
const val STATE_STOPPED = "stopped"
|
const val STATE_STOPPED = "stopped"
|
||||||
|
|
|
@ -3,6 +3,7 @@ package deckers.thibault.aves.channel.calls
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
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.ExifOrientationOp
|
||||||
import deckers.thibault.aves.model.FieldMap
|
import deckers.thibault.aves.model.FieldMap
|
||||||
import deckers.thibault.aves.model.provider.ImageProvider.ImageOpCallback
|
import deckers.thibault.aves.model.provider.ImageProvider.ImageOpCallback
|
||||||
|
@ -66,10 +67,8 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.editOrientation(contextWrapper, path, uri, mimeType, op, object : ImageOpCallback {
|
val callback = MetadataOpCallback("editOrientation", entryMap, result)
|
||||||
override fun onSuccess(fields: FieldMap) = result.success(fields)
|
provider.editOrientation(contextWrapper, path, uri, mimeType, op, callback)
|
||||||
override fun onFailure(throwable: Throwable) = result.error("editOrientation-failure", "failed to change orientation for mimeType=$mimeType uri=$uri", throwable)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun editDate(call: MethodCall, result: MethodChannel.Result) {
|
private fun editDate(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
@ -96,10 +95,8 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.editDate(contextWrapper, path, uri, mimeType, dateMillis, shiftMinutes, fields, object : ImageOpCallback {
|
val callback = MetadataOpCallback("editDate", entryMap, result)
|
||||||
override fun onSuccess(fields: FieldMap) = result.success(fields)
|
provider.editDate(contextWrapper, path, uri, mimeType, dateMillis, shiftMinutes, fields, callback)
|
||||||
override fun onFailure(throwable: Throwable) = result.error("editDate-failure", "failed to edit date for mimeType=$mimeType uri=$uri", throwable)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun editMetadata(call: MethodCall, result: MethodChannel.Result) {
|
private fun editMetadata(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
@ -125,10 +122,8 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.editMetadata(contextWrapper, path, uri, mimeType, metadata, autoCorrectTrailerOffset, callback = object : ImageOpCallback {
|
val callback = MetadataOpCallback("editMetadata", entryMap, result)
|
||||||
override fun onSuccess(fields: FieldMap) = result.success(fields)
|
provider.editMetadata(contextWrapper, path, uri, mimeType, metadata, autoCorrectTrailerOffset, callback)
|
||||||
override fun onFailure(throwable: Throwable) = result.error("editMetadata-failure", "failed to edit metadata for mimeType=$mimeType uri=$uri", throwable)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeTrailerVideo(call: MethodCall, result: MethodChannel.Result) {
|
private fun removeTrailerVideo(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
@ -152,10 +147,8 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.removeTrailerVideo(contextWrapper, path, uri, mimeType, object : ImageOpCallback {
|
val callback = MetadataOpCallback("removeTrailerVideo", entryMap, result)
|
||||||
override fun onSuccess(fields: FieldMap) = result.success(fields)
|
provider.removeTrailerVideo(contextWrapper, path, uri, mimeType, callback)
|
||||||
override fun onFailure(throwable: Throwable) = result.error("removeTrailerVideo-failure", "failed to remove trailer video for mimeType=$mimeType uri=$uri", throwable)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeTypes(call: MethodCall, result: MethodChannel.Result) {
|
private fun removeTypes(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
@ -180,13 +173,31 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.removeMetadataTypes(contextWrapper, path, uri, mimeType, types.toSet(), object : ImageOpCallback {
|
val callback = MetadataOpCallback("removeTypes", entryMap, result)
|
||||||
override fun onSuccess(fields: FieldMap) = result.success(fields)
|
provider.removeMetadataTypes(contextWrapper, path, uri, mimeType, types.toSet(), callback)
|
||||||
override fun onFailure(throwable: Throwable) = result.error("removeTypes-failure", "failed to remove metadata for mimeType=$mimeType uri=$uri", throwable)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CHANNEL = "deckers.thibault/aves/metadata_edit"
|
const val CHANNEL = "deckers.thibault/aves/metadata_edit"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
||||||
if (prop is XMPPropertyInfo) {
|
if (prop is XMPPropertyInfo) {
|
||||||
val path = prop.path
|
val path = prop.path
|
||||||
if (path?.isNotEmpty() == true) {
|
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) {
|
if (value?.isNotEmpty() == true) {
|
||||||
dirMap[path] = value
|
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)) {
|
if (!metadataMap.containsKey(KEY_XMP_TITLE) || !metadataMap.containsKey(KEY_XMP_SUBJECTS)) {
|
||||||
for (dir in metadata.getDirectoriesOfType(IptcDirectory::class.java)) {
|
for (dir in metadata.getDirectoriesOfType(IptcDirectory::class.java)) {
|
||||||
if (!metadataMap.containsKey(KEY_XMP_TITLE)) {
|
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)) {
|
if (!metadataMap.containsKey(KEY_XMP_SUBJECTS)) {
|
||||||
dir.keywords?.let { metadataMap[KEY_XMP_SUBJECTS] = it.joinToString(XMP_SUBJECTS_SEPARATOR) }
|
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):
|
// return description from these fields (by precedence):
|
||||||
// - XMP / dc:description
|
// - XMP / dc:description
|
||||||
// - IPTC / caption-abstract
|
// - IPTC / caption-abstract
|
||||||
|
// - Exif / UserComment
|
||||||
// - Exif / ImageDescription
|
// - Exif / ImageDescription
|
||||||
private fun getDescription(call: MethodCall, result: MethodChannel.Result) {
|
private fun getDescription(call: MethodCall, result: MethodChannel.Result) {
|
||||||
val mimeType = call.argument<String>("mimeType")
|
val mimeType = call.argument<String>("mimeType")
|
||||||
|
@ -1171,7 +1172,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
||||||
val xmpMeta = dir.xmpMeta
|
val xmpMeta = dir.xmpMeta
|
||||||
try {
|
try {
|
||||||
if (xmpMeta.doesPropExist(XMP.DC_DESCRIPTION_PROP_NAME)) {
|
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) {
|
} catch (e: XMPException) {
|
||||||
Log.w(LOG_TAG, "failed to read XMP directory for uri=$uri", e)
|
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) {
|
if (description == null) {
|
||||||
for (dir in metadata.getDirectoriesOfType(IptcDirectory::class.java)) {
|
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) {
|
if (description == null) {
|
||||||
for (dir in metadata.getDirectoriesOfType(ExifIFD0Directory::class.java)) {
|
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
|
// additional media key
|
||||||
private const val KEY_HAS_EMBEDDED_PICTURE = "Has Embedded Picture"
|
private const val KEY_HAS_EMBEDDED_PICTURE = "Has Embedded Picture"
|
||||||
|
|
||||||
|
private const val VALUE_SKIPPED_DATA = "[skipped]"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@ package deckers.thibault.aves.channel.calls.window
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import deckers.thibault.aves.utils.getDisplayCompat
|
||||||
import io.flutter.plugin.common.MethodCall
|
import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
|
@ -42,25 +43,30 @@ class ActivityWindowHandler(private val activity: Activity) : WindowHandler(acti
|
||||||
result.success(true)
|
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)
|
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setCutoutMode(call: MethodCall, result: MethodChannel.Result) {
|
override fun getCutoutInsets(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
|
||||||
val use = call.argument<Boolean>("use")
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||||
if (use == null) {
|
result.error("getCutoutInsets-sdk", "unsupported SDK version=${Build.VERSION.SDK_INT}", null)
|
||||||
result.error("setCutoutMode-args", "missing arguments", null)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
val cutout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
val mode = if (use) {
|
activity.getDisplayCompat()?.cutout
|
||||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
|
||||||
} else {
|
} else {
|
||||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
|
activity.window.decorView.rootWindowInsets.displayCutout
|
||||||
}
|
}
|
||||||
activity.window.attributes.layoutInDisplayCutoutMode = mode
|
|
||||||
}
|
val density = activity.resources.displayMetrics.density
|
||||||
result.success(true)
|
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,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,11 +17,11 @@ class ServiceWindowHandler(service: Service) : WindowHandler(service) {
|
||||||
result.success(false)
|
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)
|
result.success(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setCutoutMode(call: MethodCall, result: MethodChannel.Result) {
|
override fun getCutoutInsets(call: MethodCall, result: MethodChannel.Result) {
|
||||||
result.success(false)
|
result.success(HashMap<String, Any>())
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,8 +15,8 @@ abstract class WindowHandler(private val contextWrapper: ContextWrapper) : Metho
|
||||||
"keepScreenOn" -> Coresult.safe(call, result, ::keepScreenOn)
|
"keepScreenOn" -> Coresult.safe(call, result, ::keepScreenOn)
|
||||||
"isRotationLocked" -> Coresult.safe(call, result, ::isRotationLocked)
|
"isRotationLocked" -> Coresult.safe(call, result, ::isRotationLocked)
|
||||||
"requestOrientation" -> Coresult.safe(call, result, ::requestOrientation)
|
"requestOrientation" -> Coresult.safe(call, result, ::requestOrientation)
|
||||||
"canSetCutoutMode" -> Coresult.safe(call, result, ::canSetCutoutMode)
|
"isCutoutAware" -> Coresult.safe(call, result, ::isCutoutAware)
|
||||||
"setCutoutMode" -> Coresult.safe(call, result, ::setCutoutMode)
|
"getCutoutInsets" -> Coresult.safe(call, result, ::getCutoutInsets)
|
||||||
else -> result.notImplemented()
|
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 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 {
|
companion object {
|
||||||
private val LOG_TAG = LogUtils.createTag<WindowHandler>()
|
private val LOG_TAG = LogUtils.createTag<WindowHandler>()
|
||||||
|
|
|
@ -199,7 +199,9 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
|
||||||
activity.startActivityForResult(intent, MainActivity.PICK_COLLECTION_FILTERS_REQUEST)
|
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?) {
|
private fun success(result: Any?) {
|
||||||
handler.post {
|
handler.post {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package deckers.thibault.aves.channel.streams
|
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
|
||||||
import io.flutter.plugin.common.EventChannel.EventSink
|
import io.flutter.plugin.common.EventChannel.EventSink
|
||||||
|
|
||||||
|
@ -13,13 +15,16 @@ class AnalysisStreamHandler : EventChannel.StreamHandler {
|
||||||
this.eventSink = eventSink
|
this.eventSink = eventSink
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCancel(arguments: Any?) {}
|
override fun onCancel(arguments: Any?) {
|
||||||
|
Log.i(LOG_TAG, "onCancel arguments=$arguments")
|
||||||
|
}
|
||||||
|
|
||||||
fun notifyCompletion() {
|
fun notifyCompletion() {
|
||||||
eventSink?.success(true)
|
eventSink?.success(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val LOG_TAG = LogUtils.createTag<AnalysisStreamHandler>()
|
||||||
const val CHANNEL = "deckers.thibault/aves/analysis_events"
|
const val CHANNEL = "deckers.thibault/aves/analysis_events"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
package deckers.thibault.aves.channel.streams
|
package deckers.thibault.aves.channel.streams
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import deckers.thibault.aves.utils.FlutterUtils
|
import deckers.thibault.aves.utils.FlutterUtils
|
||||||
|
import deckers.thibault.aves.utils.LogUtils
|
||||||
import io.flutter.plugin.common.EventChannel
|
import io.flutter.plugin.common.EventChannel
|
||||||
import io.flutter.plugin.common.EventChannel.EventSink
|
import io.flutter.plugin.common.EventChannel.EventSink
|
||||||
|
|
||||||
|
@ -14,7 +16,9 @@ class ErrorStreamHandler : EventChannel.StreamHandler {
|
||||||
this.eventSink = eventSink
|
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) {
|
suspend fun notifyError(error: String) {
|
||||||
FlutterUtils.runOnUiThread {
|
FlutterUtils.runOnUiThread {
|
||||||
|
@ -23,6 +27,7 @@ class ErrorStreamHandler : EventChannel.StreamHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val LOG_TAG = LogUtils.createTag<ErrorStreamHandler>()
|
||||||
const val CHANNEL = "deckers.thibault/aves/error"
|
const val CHANNEL = "deckers.thibault/aves/error"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
package deckers.thibault.aves.channel.streams
|
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
|
||||||
import io.flutter.plugin.common.EventChannel.EventSink
|
import io.flutter.plugin.common.EventChannel.EventSink
|
||||||
|
|
||||||
|
@ -13,13 +15,16 @@ class IntentStreamHandler : EventChannel.StreamHandler {
|
||||||
this.eventSink = eventSink
|
this.eventSink = eventSink
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCancel(arguments: Any?) {}
|
override fun onCancel(arguments: Any?) {
|
||||||
|
Log.i(LOG_TAG, "onCancel arguments=$arguments")
|
||||||
|
}
|
||||||
|
|
||||||
fun notifyNewIntent(intentData: MutableMap<String, Any?>?) {
|
fun notifyNewIntent(intentData: MutableMap<String, Any?>?) {
|
||||||
eventSink?.success(intentData)
|
eventSink?.success(intentData)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val LOG_TAG = LogUtils.createTag<IntentStreamHandler>()
|
||||||
const val CHANNEL = "deckers.thibault/aves/new_intent_stream"
|
const val CHANNEL = "deckers.thibault/aves/new_intent_stream"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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<MediaCommandStreamHandler>()
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,7 +41,9 @@ class MediaStoreChangeStreamHandler(private val context: Context) : EventChannel
|
||||||
handler = Handler(Looper.getMainLooper())
|
handler = Handler(Looper.getMainLooper())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCancel(arguments: Any?) {}
|
override fun onCancel(arguments: Any?) {
|
||||||
|
Log.i(LOG_TAG, "onCancel arguments=$arguments")
|
||||||
|
}
|
||||||
|
|
||||||
fun dispose() {
|
fun dispose() {
|
||||||
context.contentResolver.unregisterContentObserver(contentObserver)
|
context.contentResolver.unregisterContentObserver(contentObserver)
|
||||||
|
|
|
@ -79,7 +79,9 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
|
||||||
handler = Handler(Looper.getMainLooper())
|
handler = Handler(Looper.getMainLooper())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCancel(arguments: Any?) {}
|
override fun onCancel(arguments: Any?) {
|
||||||
|
Log.i(LOG_TAG, "onCancel arguments=$arguments")
|
||||||
|
}
|
||||||
|
|
||||||
fun dispose() {
|
fun dispose() {
|
||||||
context.contentResolver.unregisterContentObserver(contentObserver)
|
context.contentResolver.unregisterContentObserver(contentObserver)
|
||||||
|
|
|
@ -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<GoogleDeviceContainerItem> = ArrayList()
|
||||||
|
private val offsets: MutableList<Int> = 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) {}
|
|
@ -33,7 +33,7 @@ object Mp4ParserHelper {
|
||||||
)
|
)
|
||||||
setBoxSkipper { type, size ->
|
setBoxSkipper { type, size ->
|
||||||
if (skippedTypes.contains(type)) return@setBoxSkipper true
|
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
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,3 +232,5 @@ object Mp4ParserHelper {
|
||||||
return stream.toByteArray()
|
return stream.toByteArray()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Mp4TooLargeException(val type: String, message: String) : RuntimeException(message)
|
||||||
|
|
|
@ -175,14 +175,14 @@ object MultiPage {
|
||||||
if (xmpMeta.doesPropExist(XMP.GCAMERA_VIDEO_OFFSET_PROP_NAME)) {
|
if (xmpMeta.doesPropExist(XMP.GCAMERA_VIDEO_OFFSET_PROP_NAME)) {
|
||||||
// `GCamera` motion photo
|
// `GCamera` motion photo
|
||||||
xmpMeta.getSafeLong(XMP.GCAMERA_VIDEO_OFFSET_PROP_NAME) { offsetFromEnd = it }
|
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
|
// `Container` motion photo
|
||||||
val count = xmpMeta.countPropArrayItems(XMP.CONTAINER_DIRECTORY_PROP_NAME)
|
val count = xmpMeta.countPropArrayItems(XMP.GCONTAINER_DIRECTORY_PROP_NAME)
|
||||||
if (count == 2) {
|
if (count == 2) {
|
||||||
// expect the video to be the second item
|
// expect the video to be the second item
|
||||||
val i = 2
|
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 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.CONTAINER_DIRECTORY_PROP_NAME, i, XMP.CONTAINER_ITEM_PROP_NAME, XMP.CONTAINER_ITEM_LENGTH_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) {
|
if (MimeTypes.isVideo(mime) && length != null) {
|
||||||
offsetFromEnd = length.toLong()
|
offsetFromEnd = length.toLong()
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,17 +42,17 @@ class GSpherical(xmlBytes: ByteArray) {
|
||||||
"StitchingSoftware" -> stitchingSoftware = readTag(parser, tag)
|
"StitchingSoftware" -> stitchingSoftware = readTag(parser, tag)
|
||||||
"ProjectionType" -> projectionType = readTag(parser, tag)
|
"ProjectionType" -> projectionType = readTag(parser, tag)
|
||||||
"StereoMode" -> stereoMode = readTag(parser, tag)
|
"StereoMode" -> stereoMode = readTag(parser, tag)
|
||||||
"SourceCount" -> sourceCount = Integer.parseInt(readTag(parser, tag))
|
"SourceCount" -> sourceCount = readTag(parser, tag).toInt()
|
||||||
"InitialViewHeadingDegrees" -> initialViewHeadingDegrees = Integer.parseInt(readTag(parser, tag))
|
"InitialViewHeadingDegrees" -> initialViewHeadingDegrees = readTag(parser, tag).toInt()
|
||||||
"InitialViewPitchDegrees" -> initialViewPitchDegrees = Integer.parseInt(readTag(parser, tag))
|
"InitialViewPitchDegrees" -> initialViewPitchDegrees = readTag(parser, tag).toInt()
|
||||||
"InitialViewRollDegrees" -> initialViewRollDegrees = Integer.parseInt(readTag(parser, tag))
|
"InitialViewRollDegrees" -> initialViewRollDegrees = readTag(parser, tag).toInt()
|
||||||
"Timestamp" -> timestamp = Integer.parseInt(readTag(parser, tag))
|
"Timestamp" -> timestamp = readTag(parser, tag).toInt()
|
||||||
"FullPanoWidthPixels" -> fullPanoWidthPixels = Integer.parseInt(readTag(parser, tag))
|
"FullPanoWidthPixels" -> fullPanoWidthPixels = readTag(parser, tag).toInt()
|
||||||
"FullPanoHeightPixels" -> fullPanoHeightPixels = Integer.parseInt(readTag(parser, tag))
|
"FullPanoHeightPixels" -> fullPanoHeightPixels = readTag(parser, tag).toInt()
|
||||||
"CroppedAreaImageWidthPixels" -> croppedAreaImageWidthPixels = Integer.parseInt(readTag(parser, tag))
|
"CroppedAreaImageWidthPixels" -> croppedAreaImageWidthPixels = readTag(parser, tag).toInt()
|
||||||
"CroppedAreaImageHeightPixels" -> croppedAreaImageHeightPixels = Integer.parseInt(readTag(parser, tag))
|
"CroppedAreaImageHeightPixels" -> croppedAreaImageHeightPixels = readTag(parser, tag).toInt()
|
||||||
"CroppedAreaLeftPixels" -> croppedAreaLeftPixels = Integer.parseInt(readTag(parser, tag))
|
"CroppedAreaLeftPixels" -> croppedAreaLeftPixels = readTag(parser, tag).toInt()
|
||||||
"CroppedAreaTopPixels" -> croppedAreaTopPixels = Integer.parseInt(readTag(parser, tag))
|
"CroppedAreaTopPixels" -> croppedAreaTopPixels = readTag(parser, tag).toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,11 +43,13 @@ object XMP {
|
||||||
private const val XMP_NS_URI = "http://ns.adobe.com/xap/1.0/"
|
private const val XMP_NS_URI = "http://ns.adobe.com/xap/1.0/"
|
||||||
|
|
||||||
// other namespaces
|
// 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 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 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 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 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 GPANO_NS_URI = "http://ns.google.com/photos/1.0/panorama/"
|
||||||
private const val PMTM_NS_URI = "http://www.hdrsoft.com/photomatix_settings01"
|
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 }
|
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
|
// motion photo
|
||||||
|
|
||||||
val GCAMERA_VIDEO_OFFSET_PROP_NAME = XMPPropName(GCAMERA_NS_URI, "MicroVideoOffset")
|
val GCAMERA_VIDEO_OFFSET_PROP_NAME = XMPPropName(GCAMERA_NS_URI, "MicroVideoOffset")
|
||||||
val CONTAINER_DIRECTORY_PROP_NAME = XMPPropName(CONTAINER_NS_URI, "Directory")
|
val GCONTAINER_DIRECTORY_PROP_NAME = XMPPropName(GCONTAINER_NS_URI, "Directory")
|
||||||
val CONTAINER_ITEM_PROP_NAME = XMPPropName(CONTAINER_NS_URI, "Item")
|
val GCONTAINER_ITEM_PROP_NAME = XMPPropName(GCONTAINER_NS_URI, "Item")
|
||||||
val CONTAINER_ITEM_LENGTH_PROP_NAME = XMPPropName(CONTAINER_ITEM_NS_URI, "Length")
|
val GCONTAINER_ITEM_LENGTH_PROP_NAME = XMPPropName(GCONTAINER_ITEM_NS_URI, "Length")
|
||||||
val CONTAINER_ITEM_MIME_PROP_NAME = XMPPropName(CONTAINER_ITEM_NS_URI, "Mime")
|
val GCONTAINER_ITEM_MIME_PROP_NAME = XMPPropName(GCONTAINER_ITEM_NS_URI, "Mime")
|
||||||
|
|
||||||
// panorama
|
// panorama
|
||||||
// cf https://developers.google.com/streetview/spherical-metadata
|
// cf https://developers.google.com/streetview/spherical-metadata
|
||||||
|
@ -189,14 +198,14 @@ object XMP {
|
||||||
if (doesPropExist(GCAMERA_VIDEO_OFFSET_PROP_NAME)) return true
|
if (doesPropExist(GCAMERA_VIDEO_OFFSET_PROP_NAME)) return true
|
||||||
|
|
||||||
// Container motion photo
|
// Container motion photo
|
||||||
if (doesPropExist(CONTAINER_DIRECTORY_PROP_NAME)) {
|
if (doesPropExist(GCONTAINER_DIRECTORY_PROP_NAME)) {
|
||||||
val count = countPropArrayItems(CONTAINER_DIRECTORY_PROP_NAME)
|
val count = countPropArrayItems(GCONTAINER_DIRECTORY_PROP_NAME)
|
||||||
if (count == 2) {
|
if (count == 2) {
|
||||||
var hasImage = false
|
var hasImage = false
|
||||||
var hasVideo = false
|
var hasVideo = false
|
||||||
for (i in 1 until count + 1) {
|
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 mime = getSafeStructField(listOf(GCONTAINER_DIRECTORY_PROP_NAME, i, GCONTAINER_ITEM_PROP_NAME, GCONTAINER_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 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
|
hasImage = hasImage || MimeTypes.isImage(mime) && length != null
|
||||||
hasVideo = hasVideo || MimeTypes.isVideo(mime) && length != null
|
hasVideo = hasVideo || MimeTypes.isVideo(mime) && length != null
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,8 +117,13 @@ object Helper {
|
||||||
|
|
||||||
// extensions
|
// extensions
|
||||||
|
|
||||||
fun Directory.getSafeString(tag: Int, save: (value: String) -> Unit) {
|
fun Directory.getSafeString(tag: Int, acceptBlank: Boolean = true, save: (value: String) -> Unit) {
|
||||||
if (this.containsTag(tag)) save(this.getString(tag))
|
if (this.containsTag(tag)) {
|
||||||
|
val string = this.getString(tag)
|
||||||
|
if (acceptBlank || string.isNotBlank()) {
|
||||||
|
save(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Directory.getSafeBoolean(tag: Int, save: (value: Boolean) -> Unit) {
|
fun Directory.getSafeBoolean(tag: Int, save: (value: Boolean) -> Unit) {
|
||||||
|
|
|
@ -55,7 +55,7 @@ internal class ContentImageProvider : ImageProvider() {
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME).let { if (it != -1) fields["title"] = cursor.getString(it) }
|
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(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()
|
cursor.close()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -73,8 +73,5 @@ internal class ContentImageProvider : ImageProvider() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOG_TAG = LogUtils.createTag<ContentImageProvider>()
|
private val LOG_TAG = LogUtils.createTag<ContentImageProvider>()
|
||||||
|
|
||||||
@Suppress("deprecation")
|
|
||||||
const val PATH = MediaStore.MediaColumns.DATA
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -761,8 +761,8 @@ abstract class ImageProvider {
|
||||||
"${XMP.GCAMERA_VIDEO_OFFSET_PROP_NAME}=\"$newTrailerOffset\"",
|
"${XMP.GCAMERA_VIDEO_OFFSET_PROP_NAME}=\"$newTrailerOffset\"",
|
||||||
).replace(
|
).replace(
|
||||||
// Container motion photo
|
// Container motion photo
|
||||||
"${XMP.CONTAINER_ITEM_LENGTH_PROP_NAME}=\"$trailerOffset\"",
|
"${XMP.GCONTAINER_ITEM_LENGTH_PROP_NAME}=\"$trailerOffset\"",
|
||||||
"${XMP.CONTAINER_ITEM_LENGTH_PROP_NAME}=\"$newTrailerOffset\"",
|
"${XMP.GCONTAINER_ITEM_LENGTH_PROP_NAME}=\"$newTrailerOffset\"",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,10 +55,10 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
val relativePathDirectory = ensureTrailingSeparator(directory)
|
val relativePathDirectory = ensureTrailingSeparator(directory)
|
||||||
val relativePath = PathSegments(context, relativePathDirectory).relativeDir
|
val relativePath = PathSegments(context, relativePathDirectory).relativeDir
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && relativePath != null) {
|
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%")
|
selectionArgs = arrayOf(relativePath, "$relativePathDirectory%")
|
||||||
} else {
|
} else {
|
||||||
selection = "${MediaColumns.PATH} LIKE ?"
|
selection = "${MediaStore.MediaColumns.DATA} LIKE ?"
|
||||||
selectionArgs = arrayOf("$relativePathDirectory%")
|
selectionArgs = arrayOf("$relativePathDirectory%")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,12 +139,12 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
fun checkObsoletePaths(context: Context, knownPathById: Map<Int?, String?>): List<Int> {
|
fun checkObsoletePaths(context: Context, knownPathById: Map<Int?, String?>): List<Int> {
|
||||||
val obsoleteIds = ArrayList<Int>()
|
val obsoleteIds = ArrayList<Int>()
|
||||||
fun check(context: Context, contentUri: Uri) {
|
fun check(context: Context, contentUri: Uri) {
|
||||||
val projection = arrayOf(MediaStore.MediaColumns._ID, MediaColumns.PATH)
|
val projection = arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA)
|
||||||
try {
|
try {
|
||||||
val cursor = context.contentResolver.query(contentUri, projection, null, null, null)
|
val cursor = context.contentResolver.query(contentUri, projection, null, null, null)
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
|
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
|
||||||
val pathColumn = cursor.getColumnIndexOrThrow(MediaColumns.PATH)
|
val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
val id = cursor.getInt(idColumn)
|
val id = cursor.getInt(idColumn)
|
||||||
val path = cursor.getString(pathColumn)
|
val path = cursor.getString(pathColumn)
|
||||||
|
@ -185,7 +185,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
|
|
||||||
// image & video
|
// image & video
|
||||||
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
|
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 mimeTypeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE)
|
||||||
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE)
|
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE)
|
||||||
val widthColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.WIDTH)
|
val widthColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.WIDTH)
|
||||||
|
@ -863,7 +863,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
|
|
||||||
fun getContentUriForPath(context: Context, path: String): Uri? {
|
fun getContentUriForPath(context: Context, path: String): Uri? {
|
||||||
val projection = arrayOf(MediaStore.MediaColumns._ID)
|
val projection = arrayOf(MediaStore.MediaColumns._ID)
|
||||||
val selection = "${MediaColumns.PATH} = ?"
|
val selection = "${MediaStore.MediaColumns.DATA} = ?"
|
||||||
val selectionArgs = arrayOf(path)
|
val selectionArgs = arrayOf(path)
|
||||||
|
|
||||||
fun check(context: Context, contentUri: Uri): Uri? {
|
fun check(context: Context, contentUri: Uri): Uri? {
|
||||||
|
@ -892,7 +892,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
|
|
||||||
private val BASE_PROJECTION = arrayOf(
|
private val BASE_PROJECTION = arrayOf(
|
||||||
MediaStore.MediaColumns._ID,
|
MediaStore.MediaColumns._ID,
|
||||||
MediaColumns.PATH,
|
MediaStore.MediaColumns.DATA,
|
||||||
MediaStore.MediaColumns.MIME_TYPE,
|
MediaStore.MediaColumns.MIME_TYPE,
|
||||||
MediaStore.MediaColumns.SIZE,
|
MediaStore.MediaColumns.SIZE,
|
||||||
MediaStore.MediaColumns.WIDTH,
|
MediaStore.MediaColumns.WIDTH,
|
||||||
|
@ -931,9 +931,6 @@ object MediaColumns {
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
const val DURATION = MediaStore.MediaColumns.DURATION
|
const val DURATION = MediaStore.MediaColumns.DURATION
|
||||||
|
|
||||||
@Suppress("deprecation")
|
|
||||||
const val PATH = MediaStore.MediaColumns.DATA
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias NewEntryHandler = (entry: FieldMap) -> Unit
|
typealias NewEntryHandler = (entry: FieldMap) -> Unit
|
||||||
|
|
|
@ -20,7 +20,7 @@ fun <E> MutableList<E>.compatRemoveIf(filter: (t: E) -> Boolean): Boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Boyer-Moore algorithm for pattern searching
|
// 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 n: Int = this.size
|
||||||
val m: Int = pattern.size
|
val m: Int = pattern.size
|
||||||
val badChar = Array(256) { 0 }
|
val badChar = Array(256) { 0 }
|
||||||
|
@ -30,7 +30,7 @@ fun ByteArray.indexOfBytes(pattern: ByteArray): Int {
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
var j: Int = m - 1
|
var j: Int = m - 1
|
||||||
var s = 0
|
var s = start
|
||||||
while (s <= (n - m)) {
|
while (s <= (n - m)) {
|
||||||
while (j >= 0 && pattern[j] == this[s + j]) {
|
while (j >= 0 && pattern[j] == this[s + j]) {
|
||||||
j -= 1
|
j -= 1
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package deckers.thibault.aves.utils
|
package deckers.thibault.aves.utils
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.pm.ResolveInfo
|
import android.content.pm.ResolveInfo
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import android.view.Display
|
||||||
|
|
||||||
inline fun <reified T> Intent.getParcelableExtraCompat(name: String): T? {
|
inline fun <reified T> Intent.getParcelableExtraCompat(name: String): T? {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
@ -16,6 +18,14 @@ inline fun <reified T> 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 {
|
fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int): ApplicationInfo {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
|
|
@ -90,7 +90,8 @@ object MimeTypes {
|
||||||
|
|
||||||
// as of `metadata-extractor` v2.14.0
|
// as of `metadata-extractor` v2.14.0
|
||||||
fun canReadWithMetadataExtractor(mimeType: String) = when (mimeType) {
|
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
|
else -> true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
android/app/src/main/res/values-cs/strings.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Aves</string>
|
||||||
|
<string name="wallpaper">Tapeta</string>
|
||||||
|
<string name="search_shortcut_short_label">Hledat</string>
|
||||||
|
<string name="videos_shortcut_short_label">Videa</string>
|
||||||
|
<string name="analysis_channel_name">Prohledat média</string>
|
||||||
|
<string name="analysis_service_description">Prohledat obrázky a videa</string>
|
||||||
|
<string name="analysis_notification_default_title">Prohledávání médií</string>
|
||||||
|
<string name="analysis_notification_action_stop">Zastavit</string>
|
||||||
|
<string name="app_widget_label">Fotorámeček</string>
|
||||||
|
</resources>
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:configure="deckers.thibault.aves.HomeWidgetSettingsActivity"
|
android:configure="deckers.thibault.aves.HomeWidgetSettingsActivity"
|
||||||
android:initialLayout="@layout/app_widget"
|
android:initialLayout="@layout/app_widget"
|
||||||
android:minWidth="40dp"
|
android:minWidth="40dp"
|
||||||
|
@ -9,4 +10,5 @@
|
||||||
android:targetCellHeight="2"
|
android:targetCellHeight="2"
|
||||||
android:updatePeriodMillis="3600000"
|
android:updatePeriodMillis="3600000"
|
||||||
android:widgetCategory="home_screen"
|
android:widgetCategory="home_screen"
|
||||||
android:widgetFeatures="reconfigurable" />
|
android:widgetFeatures="reconfigurable"
|
||||||
|
tools:targetApi="s" />
|
5
fastlane/metadata/android/cs/full_description.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<i>Aves</i> 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 <b>vícestránkový TIFF, SVG, starý AVI a mnohem více</b>! Prohledává vaši sbírku médií kvůli rozpoznání <b>pohyblivých fotografií</b>, <b>panoramatických snímků</b> (čili fotosféry), <b>360° videí</b>, nebo souborů <b>GeoTIFF</b>.
|
||||||
|
|
||||||
|
<b>Navigace a vyhledávání</b> jsou důležitou součástí aplikace <i>Aves</i>. Cílem je, aby uživatelé jednoduše přecházeli z alb k fotografiím, albům, mapám, atd.
|
||||||
|
|
||||||
|
<i>Aves</i> podporuje Android (od verze KitKat po Android 13, včetně Android TV) s funkcemi jako jsou <b>widgety</b>, <b>zkratky aplikací</b>, <b>spořič displeje</b> a <b>globální vyhledávání</b>. Rovněž jej lze použít pro <b>prohlížení a výběr médií</b>.
|
BIN
fastlane/metadata/android/cs/images/featureGraphic.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
fastlane/metadata/android/cs/images/phoneScreenshots/1.png
Normal file
After Width: | Height: | Size: 282 KiB |
BIN
fastlane/metadata/android/cs/images/phoneScreenshots/2.png
Normal file
After Width: | Height: | Size: 498 KiB |
BIN
fastlane/metadata/android/cs/images/phoneScreenshots/3.png
Normal file
After Width: | Height: | Size: 211 KiB |
BIN
fastlane/metadata/android/cs/images/phoneScreenshots/4.png
Normal file
After Width: | Height: | Size: 93 KiB |
BIN
fastlane/metadata/android/cs/images/phoneScreenshots/5.png
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
fastlane/metadata/android/cs/images/phoneScreenshots/6.png
Normal file
After Width: | Height: | Size: 341 KiB |
BIN
fastlane/metadata/android/cs/images/phoneScreenshots/7.png
Normal file
After Width: | Height: | Size: 341 KiB |
1
fastlane/metadata/android/cs/short_description.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Galerie a prohlížeč metadat
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -1,4 +0,0 @@
|
||||||
In v1.6.3:
|
|
||||||
- enjoy the light theme
|
|
||||||
- rename items in bulk
|
|
||||||
Full changelog available on GitHub
|
|
|
@ -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
|
|
|
@ -1,5 +0,0 @@
|
||||||
In v1.6.5:
|
|
||||||
- bottom navigation bar
|
|
||||||
- fast scroll with breadcrumbs
|
|
||||||
- settings search
|
|
||||||
Full changelog available on GitHub
|
|
|
@ -1,5 +0,0 @@
|
||||||
In v1.6.6:
|
|
||||||
- bottom navigation bar
|
|
||||||
- fast scroll with breadcrumbs
|
|
||||||
- settings search
|
|
||||||
Full changelog available on GitHub
|
|
|
@ -1,5 +0,0 @@
|
||||||
In v1.6.7:
|
|
||||||
- bottom navigation bar
|
|
||||||
- fast scroll with breadcrumbs
|
|
||||||
- settings search
|
|
||||||
Full changelog available on GitHub
|
|
|
@ -1,5 +0,0 @@
|
||||||
In v1.6.8:
|
|
||||||
- bottom navigation bar
|
|
||||||
- fast scroll with breadcrumbs
|
|
||||||
- settings search
|
|
||||||
Full changelog available on GitHub
|
|
|
@ -1,5 +0,0 @@
|
||||||
In v1.6.9:
|
|
||||||
- start slideshows
|
|
||||||
- change your wallpaper
|
|
||||||
- enjoy the app in Turkish
|
|
||||||
Full changelog available on GitHub
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
5
fastlane/metadata/android/en-US/changelogs/89.txt
Normal file
|
@ -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
|
5
fastlane/metadata/android/en-US/changelogs/8901.txt
Normal file
|
@ -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
|
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
<b>Navigasi dan pencarian</b> merupakan bagian penting dari <i>Aves</i>. Tujuannya adalah agar pengguna dengan mudah mengalir dari album ke foto ke tag ke peta, dll.
|
<b>Navigasi dan pencarian</b> merupakan bagian penting dari <i>Aves</i>. Tujuannya adalah agar pengguna dengan mudah mengalir dari album ke foto ke tag ke peta, dll.
|
||||||
|
|
||||||
<i>Aves</i> terintegrasi dengan Android (dari <b>API 19 ke 33</b>, yaitu dari KitKat ke Android 13) dengan fitur-fitur seperti <b>pintasan aplikasi</b> dan <b>pencarian global</b> penanganan. Ini juga berfungsi sebagai <b>penampil dan pemilih media</b>.
|
<i>Aves</i> mengintegrasi dengan Android (dari Kitkat ke Android 13) dengan fitur-fitur seperti <b>pintasan aplikasi</b>, <b>jalan pintas aplikasi</b>, <b>screen saver</b> dan <b>pencarian global</b> penanganan. Ini juga berfungsi sebagai <b>penampil dan pemilih media</b>.
|
|
@ -1,5 +1,5 @@
|
||||||
<i>Aves</i> can handle all sorts of images and videos, including your typical JPEGs and MP4s, but also more exotic things like <b>multi-page TIFFs, SVGs, old AVIs and more</b>! It scans your media collection to identify <b>motion photos</b>, <b>panoramas</b> (aka photo spheres), <b>360° videos</b>, as well as <b>GeoTIFF</b> files.
|
<i>Aves</i> kan handsama alle slags bileter og videoar, medteken JPEG og MP4, men au meir uvane ting som <b>fleirsida TIFF-ar, SVG-ar, gamle AVI-ar med meir</b>! Aves ser igjennom mediasamlinga di for å gjenkjenne <b>rørslebilete</b>, <b>panorama</b> (bilete med vidt oversyn), <b>360° videoar</b>, og au <b>GeoTIFF</b>-filer.
|
||||||
|
|
||||||
<b>Navigation and search</b> is an important part of <i>Aves</i>. The goal is for users to easily flow from albums to photos to tags to maps, etc.
|
<b>Navigering og søk</b> har mykje å sei i <i>Aves</i>. Målet er at ein skal lett kunne gå ifrå album, til bilete, til merkelappar, til kart, osv.
|
||||||
|
|
||||||
<i>Aves</i> integrates with Android (from KitKat to Android 13, including Android TV) with features such as <b>widgets</b>, <b>app shortcuts</b>, <b>screen saver</b> and <b>global search</b> handling. It also works as a <b>media viewer and picker</b>.
|
<i>Aves</i> integrates with Android (from KitKat to Android 13, including Android TV) with features such as <b>widgets</b>, <b>app shortcuts</b>, <b>screen saver</b> and <b>global search</b> handling. It also works as a <b>media viewer and picker</b>.
|
BIN
fastlane/metadata/android/pl/images/featureGraphic.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
fastlane/metadata/android/pl/images/phoneScreenshots/1.png
Normal file
After Width: | Height: | Size: 282 KiB |
BIN
fastlane/metadata/android/pl/images/phoneScreenshots/2.png
Normal file
After Width: | Height: | Size: 499 KiB |
BIN
fastlane/metadata/android/pl/images/phoneScreenshots/3.png
Normal file
After Width: | Height: | Size: 213 KiB |
BIN
fastlane/metadata/android/pl/images/phoneScreenshots/4.png
Normal file
After Width: | Height: | Size: 93 KiB |
BIN
fastlane/metadata/android/pl/images/phoneScreenshots/5.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
fastlane/metadata/android/pl/images/phoneScreenshots/6.png
Normal file
After Width: | Height: | Size: 341 KiB |
BIN
fastlane/metadata/android/pl/images/phoneScreenshots/7.png
Normal file
After Width: | Height: | Size: 340 KiB |
|
@ -16,5 +16,57 @@
|
||||||
"filePickerShowHiddenFiles": "إظهار الملفات المخفية",
|
"filePickerShowHiddenFiles": "إظهار الملفات المخفية",
|
||||||
"@filePickerShowHiddenFiles": {},
|
"@filePickerShowHiddenFiles": {},
|
||||||
"panoramaEnableSensorControl": "تمكين التحكم في المستشعر",
|
"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": {}
|
||||||
}
|
}
|
||||||
|
|
1358
lib/l10n/app_cs.arb
Normal file
|
@ -1196,5 +1196,15 @@
|
||||||
"filterNoAddressLabel": "Χωρίς διεύθυνση",
|
"filterNoAddressLabel": "Χωρίς διεύθυνση",
|
||||||
"@filterNoAddressLabel": {},
|
"@filterNoAddressLabel": {},
|
||||||
"settingsViewerShowRatingTags": "Εμφάνιση βαθμολογίας & ετικετών",
|
"settingsViewerShowRatingTags": "Εμφάνιση βαθμολογίας & ετικετών",
|
||||||
"@settingsViewerShowRatingTags": {}
|
"@settingsViewerShowRatingTags": {},
|
||||||
|
"filterLocatedLabel": "Με τοποθεσία",
|
||||||
|
"@filterLocatedLabel": {},
|
||||||
|
"filterTaggedLabel": "Με ετικέτα",
|
||||||
|
"@filterTaggedLabel": {},
|
||||||
|
"settingsModificationWarningDialogMessage": "Άλλες ρυθμίσεις θα τροποποιηθούν.",
|
||||||
|
"@settingsModificationWarningDialogMessage": {},
|
||||||
|
"settingsDisplayUseTvInterface": "Χρήση του Android TV περιβάλλον",
|
||||||
|
"@settingsDisplayUseTvInterface": {},
|
||||||
|
"settingsViewerShowDescription": "Εμφάνιση περιγραφής",
|
||||||
|
"@settingsViewerShowDescription": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,8 +139,10 @@
|
||||||
"filterFavouriteLabel": "Favorite",
|
"filterFavouriteLabel": "Favorite",
|
||||||
"filterNoDateLabel": "Undated",
|
"filterNoDateLabel": "Undated",
|
||||||
"filterNoAddressLabel": "No address",
|
"filterNoAddressLabel": "No address",
|
||||||
|
"filterLocatedLabel": "Located",
|
||||||
"filterNoLocationLabel": "Unlocated",
|
"filterNoLocationLabel": "Unlocated",
|
||||||
"filterNoRatingLabel": "Unrated",
|
"filterNoRatingLabel": "Unrated",
|
||||||
|
"filterTaggedLabel": "Tagged",
|
||||||
"filterNoTagLabel": "Untagged",
|
"filterNoTagLabel": "Untagged",
|
||||||
"filterNoTitleLabel": "Untitled",
|
"filterNoTitleLabel": "Untitled",
|
||||||
"filterOnThisDayLabel": "On this day",
|
"filterOnThisDayLabel": "On this day",
|
||||||
|
@ -375,13 +377,13 @@
|
||||||
"renameProcessorCounter": "Counter",
|
"renameProcessorCounter": "Counter",
|
||||||
"renameProcessorName": "Name",
|
"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": {
|
"@deleteSingleAlbumConfirmationDialogMessage": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {}
|
"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": {
|
"@deleteMultiAlbumConfirmationDialogMessage": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {}
|
"count": {}
|
||||||
|
@ -436,6 +438,8 @@
|
||||||
"genericFailureFeedback": "Failed",
|
"genericFailureFeedback": "Failed",
|
||||||
"genericDangerWarningDialogMessage": "Are you sure?",
|
"genericDangerWarningDialogMessage": "Are you sure?",
|
||||||
|
|
||||||
|
"tooManyItemsErrorDialogMessage": "Try again with fewer items.",
|
||||||
|
|
||||||
"menuActionConfigureView": "View",
|
"menuActionConfigureView": "View",
|
||||||
"menuActionSelect": "Select",
|
"menuActionSelect": "Select",
|
||||||
"menuActionSelectAll": "Select all",
|
"menuActionSelectAll": "Select all",
|
||||||
|
@ -656,6 +660,7 @@
|
||||||
"settingsSystemDefault": "System default",
|
"settingsSystemDefault": "System default",
|
||||||
"settingsDefault": "Default",
|
"settingsDefault": "Default",
|
||||||
"settingsDisabled": "Disabled",
|
"settingsDisabled": "Disabled",
|
||||||
|
"settingsModificationWarningDialogMessage": "Other settings will be modified.",
|
||||||
|
|
||||||
"settingsSearchFieldLabel": "Search settings",
|
"settingsSearchFieldLabel": "Search settings",
|
||||||
"settingsSearchEmpty": "No matching setting",
|
"settingsSearchEmpty": "No matching setting",
|
||||||
|
@ -731,6 +736,7 @@
|
||||||
"settingsViewerShowInformationSubtitle": "Show title, date, location, etc.",
|
"settingsViewerShowInformationSubtitle": "Show title, date, location, etc.",
|
||||||
"settingsViewerShowRatingTags": "Show rating & tags",
|
"settingsViewerShowRatingTags": "Show rating & tags",
|
||||||
"settingsViewerShowShootingDetails": "Show shooting details",
|
"settingsViewerShowShootingDetails": "Show shooting details",
|
||||||
|
"settingsViewerShowDescription": "Show description",
|
||||||
"settingsViewerShowOverlayThumbnails": "Show thumbnails",
|
"settingsViewerShowOverlayThumbnails": "Show thumbnails",
|
||||||
"settingsViewerEnableOverlayBlurEffect": "Blur effect",
|
"settingsViewerEnableOverlayBlurEffect": "Blur effect",
|
||||||
|
|
||||||
|
@ -815,6 +821,7 @@
|
||||||
"settingsThemeEnableDynamicColor": "Dynamic color",
|
"settingsThemeEnableDynamicColor": "Dynamic color",
|
||||||
"settingsDisplayRefreshRateModeTile": "Display refresh rate",
|
"settingsDisplayRefreshRateModeTile": "Display refresh rate",
|
||||||
"settingsDisplayRefreshRateModeDialogTitle": "Refresh Rate",
|
"settingsDisplayRefreshRateModeDialogTitle": "Refresh Rate",
|
||||||
|
"settingsDisplayUseTvInterface": "Android TV interface",
|
||||||
|
|
||||||
"settingsLanguageSectionTitle": "Language & Formats",
|
"settingsLanguageSectionTitle": "Language & Formats",
|
||||||
"settingsLanguageTile": "Language",
|
"settingsLanguageTile": "Language",
|
||||||
|
|
|
@ -371,9 +371,9 @@
|
||||||
"@renameProcessorCounter": {},
|
"@renameProcessorCounter": {},
|
||||||
"renameProcessorName": "Nombre",
|
"renameProcessorName": "Nombre",
|
||||||
"@renameProcessorName": {},
|
"@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": {},
|
"@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": {},
|
"@deleteMultiAlbumConfirmationDialogMessage": {},
|
||||||
"exportEntryDialogFormat": "Formato:",
|
"exportEntryDialogFormat": "Formato:",
|
||||||
"@exportEntryDialogFormat": {},
|
"@exportEntryDialogFormat": {},
|
||||||
|
@ -1196,5 +1196,15 @@
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {}
|
"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": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -297,5 +297,86 @@
|
||||||
"policyPageTitle": "سیاست حفظ حریم خصوصی",
|
"policyPageTitle": "سیاست حفظ حریم خصوصی",
|
||||||
"@policyPageTitle": {},
|
"@policyPageTitle": {},
|
||||||
"collectionPickPageTitle": "انتخاب",
|
"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": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@
|
||||||
"@chipActionGoToAlbumPage": {},
|
"@chipActionGoToAlbumPage": {},
|
||||||
"chipActionGoToCountryPage": "Afficher dans Pays",
|
"chipActionGoToCountryPage": "Afficher dans Pays",
|
||||||
"@chipActionGoToCountryPage": {},
|
"@chipActionGoToCountryPage": {},
|
||||||
"chipActionGoToTagPage": "Afficher dans Libellés",
|
"chipActionGoToTagPage": "Afficher dans Étiquettes",
|
||||||
"@chipActionGoToTagPage": {},
|
"@chipActionGoToTagPage": {},
|
||||||
"chipActionFilterOut": "Exclure",
|
"chipActionFilterOut": "Exclure",
|
||||||
"@chipActionFilterOut": {},
|
"@chipActionFilterOut": {},
|
||||||
|
@ -165,7 +165,7 @@
|
||||||
"@entryInfoActionEditTitleDescription": {},
|
"@entryInfoActionEditTitleDescription": {},
|
||||||
"entryInfoActionEditRating": "Modifier la notation",
|
"entryInfoActionEditRating": "Modifier la notation",
|
||||||
"@entryInfoActionEditRating": {},
|
"@entryInfoActionEditRating": {},
|
||||||
"entryInfoActionEditTags": "Modifier les libellés",
|
"entryInfoActionEditTags": "Modifier les étiquettes",
|
||||||
"@entryInfoActionEditTags": {},
|
"@entryInfoActionEditTags": {},
|
||||||
"entryInfoActionRemoveMetadata": "Retirer les métadonnées",
|
"entryInfoActionRemoveMetadata": "Retirer les métadonnées",
|
||||||
"@entryInfoActionRemoveMetadata": {},
|
"@entryInfoActionRemoveMetadata": {},
|
||||||
|
@ -179,7 +179,7 @@
|
||||||
"@filterNoLocationLabel": {},
|
"@filterNoLocationLabel": {},
|
||||||
"filterNoRatingLabel": "Sans notation",
|
"filterNoRatingLabel": "Sans notation",
|
||||||
"@filterNoRatingLabel": {},
|
"@filterNoRatingLabel": {},
|
||||||
"filterNoTagLabel": "Sans libellé",
|
"filterNoTagLabel": "Sans étiquette",
|
||||||
"@filterNoTagLabel": {},
|
"@filterNoTagLabel": {},
|
||||||
"filterNoTitleLabel": "Sans titre",
|
"filterNoTitleLabel": "Sans titre",
|
||||||
"@filterNoTitleLabel": {},
|
"@filterNoTitleLabel": {},
|
||||||
|
@ -391,9 +391,9 @@
|
||||||
"@renameProcessorCounter": {},
|
"@renameProcessorCounter": {},
|
||||||
"renameProcessorName": "Nom",
|
"renameProcessorName": "Nom",
|
||||||
"@renameProcessorName": {},
|
"@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": {},
|
"@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": {},
|
"@deleteMultiAlbumConfirmationDialogMessage": {},
|
||||||
"exportEntryDialogFormat": "Format :",
|
"exportEntryDialogFormat": "Format :",
|
||||||
"@exportEntryDialogFormat": {},
|
"@exportEntryDialogFormat": {},
|
||||||
|
@ -641,7 +641,7 @@
|
||||||
"@drawerAlbumPage": {},
|
"@drawerAlbumPage": {},
|
||||||
"drawerCountryPage": "Pays",
|
"drawerCountryPage": "Pays",
|
||||||
"@drawerCountryPage": {},
|
"@drawerCountryPage": {},
|
||||||
"drawerTagPage": "Libellés",
|
"drawerTagPage": "Étiquettes",
|
||||||
"@drawerTagPage": {},
|
"@drawerTagPage": {},
|
||||||
"sortByDate": "par date",
|
"sortByDate": "par date",
|
||||||
"@sortByDate": {},
|
"@sortByDate": {},
|
||||||
|
@ -713,9 +713,9 @@
|
||||||
"@countryPageTitle": {},
|
"@countryPageTitle": {},
|
||||||
"countryEmpty": "Aucun pays",
|
"countryEmpty": "Aucun pays",
|
||||||
"@countryEmpty": {},
|
"@countryEmpty": {},
|
||||||
"tagPageTitle": "Libellés",
|
"tagPageTitle": "Étiquettes",
|
||||||
"@tagPageTitle": {},
|
"@tagPageTitle": {},
|
||||||
"tagEmpty": "Aucun libellé",
|
"tagEmpty": "Aucune étiquette",
|
||||||
"@tagEmpty": {},
|
"@tagEmpty": {},
|
||||||
"binPageTitle": "Corbeille",
|
"binPageTitle": "Corbeille",
|
||||||
"@binPageTitle": {},
|
"@binPageTitle": {},
|
||||||
|
@ -731,7 +731,7 @@
|
||||||
"@searchCountriesSectionTitle": {},
|
"@searchCountriesSectionTitle": {},
|
||||||
"searchPlacesSectionTitle": "Lieux",
|
"searchPlacesSectionTitle": "Lieux",
|
||||||
"@searchPlacesSectionTitle": {},
|
"@searchPlacesSectionTitle": {},
|
||||||
"searchTagsSectionTitle": "Libellés",
|
"searchTagsSectionTitle": "Étiquettes",
|
||||||
"@searchTagsSectionTitle": {},
|
"@searchTagsSectionTitle": {},
|
||||||
"searchRatingSectionTitle": "Notations",
|
"searchRatingSectionTitle": "Notations",
|
||||||
"@searchRatingSectionTitle": {},
|
"@searchRatingSectionTitle": {},
|
||||||
|
@ -811,7 +811,7 @@
|
||||||
"@settingsThumbnailOverlayPageTitle": {},
|
"@settingsThumbnailOverlayPageTitle": {},
|
||||||
"settingsThumbnailShowFavouriteIcon": "Afficher l’icône de favori",
|
"settingsThumbnailShowFavouriteIcon": "Afficher l’icône de favori",
|
||||||
"@settingsThumbnailShowFavouriteIcon": {},
|
"@settingsThumbnailShowFavouriteIcon": {},
|
||||||
"settingsThumbnailShowTagIcon": "Afficher l’icône de libellé",
|
"settingsThumbnailShowTagIcon": "Afficher l’icône d’étiquette",
|
||||||
"@settingsThumbnailShowTagIcon": {},
|
"@settingsThumbnailShowTagIcon": {},
|
||||||
"settingsThumbnailShowLocationIcon": "Afficher l’icône de lieu",
|
"settingsThumbnailShowLocationIcon": "Afficher l’icône de lieu",
|
||||||
"@settingsThumbnailShowLocationIcon": {},
|
"@settingsThumbnailShowLocationIcon": {},
|
||||||
|
@ -1043,7 +1043,7 @@
|
||||||
"@statsTopCountriesSectionTitle": {},
|
"@statsTopCountriesSectionTitle": {},
|
||||||
"statsTopPlacesSectionTitle": "Top lieux",
|
"statsTopPlacesSectionTitle": "Top lieux",
|
||||||
"@statsTopPlacesSectionTitle": {},
|
"@statsTopPlacesSectionTitle": {},
|
||||||
"statsTopTagsSectionTitle": "Top libellés",
|
"statsTopTagsSectionTitle": "Top étiquettes",
|
||||||
"@statsTopTagsSectionTitle": {},
|
"@statsTopTagsSectionTitle": {},
|
||||||
"statsTopAlbumsSectionTitle": "Top albums",
|
"statsTopAlbumsSectionTitle": "Top albums",
|
||||||
"@statsTopAlbumsSectionTitle": {},
|
"@statsTopAlbumsSectionTitle": {},
|
||||||
|
@ -1123,11 +1123,11 @@
|
||||||
"@viewerInfoSearchSuggestionRights": {},
|
"@viewerInfoSearchSuggestionRights": {},
|
||||||
"wallpaperUseScrollEffect": "Utiliser l’effet de défilement sur l’écran d’accueil",
|
"wallpaperUseScrollEffect": "Utiliser l’effet de défilement sur l’écran d’accueil",
|
||||||
"@wallpaperUseScrollEffect": {},
|
"@wallpaperUseScrollEffect": {},
|
||||||
"tagEditorPageTitle": "Modifier les libellés",
|
"tagEditorPageTitle": "Modifier les étiquettes",
|
||||||
"@tagEditorPageTitle": {},
|
"@tagEditorPageTitle": {},
|
||||||
"tagEditorPageNewTagFieldLabel": "Nouveau libellé",
|
"tagEditorPageNewTagFieldLabel": "Nouvelle étiquette",
|
||||||
"@tagEditorPageNewTagFieldLabel": {},
|
"@tagEditorPageNewTagFieldLabel": {},
|
||||||
"tagEditorPageAddTagTooltip": "Ajouter le libellé",
|
"tagEditorPageAddTagTooltip": "Ajouter l’étiquette",
|
||||||
"@tagEditorPageAddTagTooltip": {},
|
"@tagEditorPageAddTagTooltip": {},
|
||||||
"tagEditorSectionRecent": "Ajouts récents",
|
"tagEditorSectionRecent": "Ajouts récents",
|
||||||
"@tagEditorSectionRecent": {},
|
"@tagEditorSectionRecent": {},
|
||||||
|
@ -1149,7 +1149,7 @@
|
||||||
"@filePickerUseThisFolder": {},
|
"@filePickerUseThisFolder": {},
|
||||||
"editEntryLocationDialogSetCustom": "Définir un lieu personnalisé",
|
"editEntryLocationDialogSetCustom": "Définir un lieu personnalisé",
|
||||||
"@editEntryLocationDialogSetCustom": {},
|
"@editEntryLocationDialogSetCustom": {},
|
||||||
"tagEditorSectionPlaceholders": "Libellés de substitution",
|
"tagEditorSectionPlaceholders": "Étiquettes de substitution",
|
||||||
"@tagEditorSectionPlaceholders": {},
|
"@tagEditorSectionPlaceholders": {},
|
||||||
"tagPlaceholderPlace": "Lieu",
|
"tagPlaceholderPlace": "Lieu",
|
||||||
"@tagPlaceholderPlace": {},
|
"@tagPlaceholderPlace": {},
|
||||||
|
@ -1179,7 +1179,7 @@
|
||||||
"@filterAspectRatioPortraitLabel": {},
|
"@filterAspectRatioPortraitLabel": {},
|
||||||
"filterAspectRatioLandscapeLabel": "Paysage",
|
"filterAspectRatioLandscapeLabel": "Paysage",
|
||||||
"@filterAspectRatioLandscapeLabel": {},
|
"@filterAspectRatioLandscapeLabel": {},
|
||||||
"settingsViewerShowRatingTags": "Afficher la notation et les libellés",
|
"settingsViewerShowRatingTags": "Afficher la notation et les étiquettes",
|
||||||
"@settingsViewerShowRatingTags": {},
|
"@settingsViewerShowRatingTags": {},
|
||||||
"entryActionShareImageOnly": "Partager l’image seulement",
|
"entryActionShareImageOnly": "Partager l’image seulement",
|
||||||
"@entryActionShareImageOnly": {},
|
"@entryActionShareImageOnly": {},
|
||||||
|
@ -1196,5 +1196,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settingsAccessibilityShowPinchGestureAlternatives": "Afficher des alternatives aux interactions multitactiles",
|
"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": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -337,7 +337,7 @@
|
||||||
"@binEntriesConfirmationDialogMessage": {},
|
"@binEntriesConfirmationDialogMessage": {},
|
||||||
"deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Anda yakin ingin menghapus benda ini?} other{Apakah Anda yakin ingin menghapus {count} benda?}}",
|
"deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Anda yakin ingin menghapus benda ini?} other{Apakah Anda yakin ingin menghapus {count} benda?}}",
|
||||||
"@deleteEntriesConfirmationDialogMessage": {},
|
"@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": {},
|
"@moveUndatedConfirmationDialogMessage": {},
|
||||||
"moveUndatedConfirmationDialogSetDate": "Atur tanggal",
|
"moveUndatedConfirmationDialogSetDate": "Atur tanggal",
|
||||||
"@moveUndatedConfirmationDialogSetDate": {},
|
"@moveUndatedConfirmationDialogSetDate": {},
|
||||||
|
@ -379,9 +379,9 @@
|
||||||
"@renameProcessorCounter": {},
|
"@renameProcessorCounter": {},
|
||||||
"renameProcessorName": "Nama",
|
"renameProcessorName": "Nama",
|
||||||
"@renameProcessorName": {},
|
"@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": {},
|
"@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": {},
|
"@deleteMultiAlbumConfirmationDialogMessage": {},
|
||||||
"exportEntryDialogFormat": "Format:",
|
"exportEntryDialogFormat": "Format:",
|
||||||
"@exportEntryDialogFormat": {},
|
"@exportEntryDialogFormat": {},
|
||||||
|
@ -867,7 +867,7 @@
|
||||||
"@settingsSlideshowFillScreen": {},
|
"@settingsSlideshowFillScreen": {},
|
||||||
"settingsSlideshowTransitionTile": "Transisi",
|
"settingsSlideshowTransitionTile": "Transisi",
|
||||||
"@settingsSlideshowTransitionTile": {},
|
"@settingsSlideshowTransitionTile": {},
|
||||||
"settingsSlideshowIntervalTile": "Interval",
|
"settingsSlideshowIntervalTile": "Jarak waktu",
|
||||||
"@settingsSlideshowIntervalTile": {},
|
"@settingsSlideshowIntervalTile": {},
|
||||||
"settingsSlideshowVideoPlaybackTile": "Putaran ulang video",
|
"settingsSlideshowVideoPlaybackTile": "Putaran ulang video",
|
||||||
"@settingsSlideshowVideoPlaybackTile": {},
|
"@settingsSlideshowVideoPlaybackTile": {},
|
||||||
|
@ -1157,7 +1157,7 @@
|
||||||
"@tagPlaceholderPlace": {},
|
"@tagPlaceholderPlace": {},
|
||||||
"editEntryLocationDialogSetCustom": "Terapkan lokasi kustom",
|
"editEntryLocationDialogSetCustom": "Terapkan lokasi kustom",
|
||||||
"@editEntryLocationDialogSetCustom": {},
|
"@editEntryLocationDialogSetCustom": {},
|
||||||
"subtitlePositionTop": "Atas",
|
"subtitlePositionTop": "Teratas",
|
||||||
"@subtitlePositionTop": {},
|
"@subtitlePositionTop": {},
|
||||||
"subtitlePositionBottom": "Bawah",
|
"subtitlePositionBottom": "Bawah",
|
||||||
"@subtitlePositionBottom": {},
|
"@subtitlePositionBottom": {},
|
||||||
|
@ -1196,5 +1196,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settingsAccessibilityShowPinchGestureAlternatives": "Tampilkan alternatif gestur multisentuh",
|
"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": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -389,9 +389,9 @@
|
||||||
"@renameProcessorCounter": {},
|
"@renameProcessorCounter": {},
|
||||||
"renameProcessorName": "Nome",
|
"renameProcessorName": "Nome",
|
||||||
"@renameProcessorName": {},
|
"@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": {},
|
"@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": {},
|
"@deleteMultiAlbumConfirmationDialogMessage": {},
|
||||||
"exportEntryDialogFormat": "Formato:",
|
"exportEntryDialogFormat": "Formato:",
|
||||||
"@exportEntryDialogFormat": {},
|
"@exportEntryDialogFormat": {},
|
||||||
|
@ -1196,5 +1196,17 @@
|
||||||
"entryActionShareVideoOnly": "Condividi solo video",
|
"entryActionShareVideoOnly": "Condividi solo video",
|
||||||
"@entryActionShareVideoOnly": {},
|
"@entryActionShareVideoOnly": {},
|
||||||
"filterNoAddressLabel": "Senza indirizzo",
|
"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": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,7 +175,7 @@
|
||||||
"@filterFavouriteLabel": {},
|
"@filterFavouriteLabel": {},
|
||||||
"filterNoDateLabel": "날짜 없음",
|
"filterNoDateLabel": "날짜 없음",
|
||||||
"@filterNoDateLabel": {},
|
"@filterNoDateLabel": {},
|
||||||
"filterNoLocationLabel": "장소 없음",
|
"filterNoLocationLabel": "위치 없음",
|
||||||
"@filterNoLocationLabel": {},
|
"@filterNoLocationLabel": {},
|
||||||
"filterNoRatingLabel": "별점 없음",
|
"filterNoRatingLabel": "별점 없음",
|
||||||
"@filterNoRatingLabel": {},
|
"@filterNoRatingLabel": {},
|
||||||
|
@ -1196,5 +1196,17 @@
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {}
|
"count": {}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"settingsViewerShowDescription": "설명 표시",
|
||||||
|
"@settingsViewerShowDescription": {},
|
||||||
|
"settingsModificationWarningDialogMessage": "다른 설정도 변경될 것입니다.",
|
||||||
|
"@settingsModificationWarningDialogMessage": {},
|
||||||
|
"settingsDisplayUseTvInterface": "안드로이드 TV 인터페이스 사용하기",
|
||||||
|
"@settingsDisplayUseTvInterface": {},
|
||||||
|
"filterTaggedLabel": "태그 있음",
|
||||||
|
"@filterTaggedLabel": {},
|
||||||
|
"filterLocatedLabel": "위치 있음",
|
||||||
|
"@filterLocatedLabel": {},
|
||||||
|
"tooManyItemsErrorDialogMessage": "항목 수를 줄이고 다시 시도하세요.",
|
||||||
|
"@tooManyItemsErrorDialogMessage": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -264,7 +264,7 @@
|
||||||
"@keepScreenOnViewerOnly": {},
|
"@keepScreenOnViewerOnly": {},
|
||||||
"keepScreenOnAlways": "Heile tida",
|
"keepScreenOnAlways": "Heile tida",
|
||||||
"@keepScreenOnAlways": {},
|
"@keepScreenOnAlways": {},
|
||||||
"accessibilityAnimationsRemove": "Hindra rørsle",
|
"accessibilityAnimationsRemove": "Hindra skjermrørsle",
|
||||||
"@accessibilityAnimationsRemove": {},
|
"@accessibilityAnimationsRemove": {},
|
||||||
"subtitlePositionTop": "På toppen",
|
"subtitlePositionTop": "På toppen",
|
||||||
"@subtitlePositionTop": {},
|
"@subtitlePositionTop": {},
|
||||||
|
@ -352,5 +352,403 @@
|
||||||
"description": "the name of a specific directory"
|
"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": {}
|
||||||
}
|
}
|
||||||
|
|
1184
lib/l10n/app_pl.arb
|
@ -1156,5 +1156,49 @@
|
||||||
"tagPlaceholderPlace": "Lugar",
|
"tagPlaceholderPlace": "Lugar",
|
||||||
"@tagPlaceholderPlace": {},
|
"@tagPlaceholderPlace": {},
|
||||||
"editEntryLocationDialogSetCustom": "Definir local personalizado",
|
"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": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1354,5 +1354,15 @@
|
||||||
"filterNoAddressLabel": "Nicio adresă",
|
"filterNoAddressLabel": "Nicio adresă",
|
||||||
"@filterNoAddressLabel": {},
|
"@filterNoAddressLabel": {},
|
||||||
"entryInfoActionRemoveLocation": "Eliminare locație",
|
"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": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1196,5 +1196,7 @@
|
||||||
"entryActionShareImageOnly": "Поделиться только изображением",
|
"entryActionShareImageOnly": "Поделиться только изображением",
|
||||||
"@entryActionShareImageOnly": {},
|
"@entryActionShareImageOnly": {},
|
||||||
"entryActionShareVideoOnly": "Поделиться только видео",
|
"entryActionShareVideoOnly": "Поделиться только видео",
|
||||||
"@entryActionShareVideoOnly": {}
|
"@entryActionShareVideoOnly": {},
|
||||||
|
"settingsViewerShowDescription": "Показать описание",
|
||||||
|
"@settingsViewerShowDescription": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -345,9 +345,9 @@
|
||||||
"@renameProcessorCounter": {},
|
"@renameProcessorCounter": {},
|
||||||
"renameProcessorName": "Ad",
|
"renameProcessorName": "Ad",
|
||||||
"@renameProcessorName": {},
|
"@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": {},
|
"@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": {},
|
"@deleteMultiAlbumConfirmationDialogMessage": {},
|
||||||
"exportEntryDialogFormat": "Biçim:",
|
"exportEntryDialogFormat": "Biçim:",
|
||||||
"@exportEntryDialogFormat": {},
|
"@exportEntryDialogFormat": {},
|
||||||
|
@ -1196,5 +1196,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settingsAccessibilityShowPinchGestureAlternatives": "Çoklu dokunma hareketi alternatiflerini göster",
|
"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": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -203,7 +203,7 @@
|
||||||
"@coordinateDmsWest": {},
|
"@coordinateDmsWest": {},
|
||||||
"unitSystemMetric": "Метричні",
|
"unitSystemMetric": "Метричні",
|
||||||
"@unitSystemMetric": {},
|
"@unitSystemMetric": {},
|
||||||
"unitSystemImperial": "Імперські",
|
"unitSystemImperial": "Англійські",
|
||||||
"@unitSystemImperial": {},
|
"@unitSystemImperial": {},
|
||||||
"videoLoopModeNever": "Ніколи",
|
"videoLoopModeNever": "Ніколи",
|
||||||
"@videoLoopModeNever": {},
|
"@videoLoopModeNever": {},
|
||||||
|
@ -267,9 +267,9 @@
|
||||||
"@wallpaperTargetLock": {},
|
"@wallpaperTargetLock": {},
|
||||||
"viewerTransitionNone": "Нічого",
|
"viewerTransitionNone": "Нічого",
|
||||||
"@viewerTransitionNone": {},
|
"@viewerTransitionNone": {},
|
||||||
"widgetDisplayedItemRandom": "Випадкові",
|
"widgetDisplayedItemRandom": "Випадковий",
|
||||||
"@widgetDisplayedItemRandom": {},
|
"@widgetDisplayedItemRandom": {},
|
||||||
"widgetDisplayedItemMostRecent": "Нещодавні",
|
"widgetDisplayedItemMostRecent": "Нещодавний",
|
||||||
"@widgetDisplayedItemMostRecent": {},
|
"@widgetDisplayedItemMostRecent": {},
|
||||||
"widgetOpenPageHome": "Відкрити головну сторінку",
|
"widgetOpenPageHome": "Відкрити головну сторінку",
|
||||||
"@widgetOpenPageHome": {},
|
"@widgetOpenPageHome": {},
|
||||||
|
@ -459,7 +459,7 @@
|
||||||
"@coverDialogTabApp": {},
|
"@coverDialogTabApp": {},
|
||||||
"coverDialogTabColor": "Колір",
|
"coverDialogTabColor": "Колір",
|
||||||
"@coverDialogTabColor": {},
|
"@coverDialogTabColor": {},
|
||||||
"appPickDialogTitle": "Вибрати Додаток",
|
"appPickDialogTitle": "Вибрати додаток",
|
||||||
"@appPickDialogTitle": {},
|
"@appPickDialogTitle": {},
|
||||||
"appPickDialogNone": "Нічого",
|
"appPickDialogNone": "Нічого",
|
||||||
"@appPickDialogNone": {},
|
"@appPickDialogNone": {},
|
||||||
|
@ -657,7 +657,7 @@
|
||||||
"minutes": {}
|
"minutes": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"doubleBackExitMessage": "Натисніть «назад» ще раз, щоб вийти.",
|
"doubleBackExitMessage": "Натисніть “назад” ще раз, щоб вийти.",
|
||||||
"@doubleBackExitMessage": {},
|
"@doubleBackExitMessage": {},
|
||||||
"actionRemove": "Видалити",
|
"actionRemove": "Видалити",
|
||||||
"@actionRemove": {},
|
"@actionRemove": {},
|
||||||
|
@ -729,9 +729,9 @@
|
||||||
"@widgetOpenPageCollection": {},
|
"@widgetOpenPageCollection": {},
|
||||||
"accessibilityAnimationsKeep": "Зберегти екранні ефекти",
|
"accessibilityAnimationsKeep": "Зберегти екранні ефекти",
|
||||||
"@accessibilityAnimationsKeep": {},
|
"@accessibilityAnimationsKeep": {},
|
||||||
"displayRefreshRatePreferHighest": "Найвищий рейтинг",
|
"displayRefreshRatePreferHighest": "Найвища частота",
|
||||||
"@displayRefreshRatePreferHighest": {},
|
"@displayRefreshRatePreferHighest": {},
|
||||||
"displayRefreshRatePreferLowest": "Найнижчий рейтинг",
|
"displayRefreshRatePreferLowest": "Найнижча частота",
|
||||||
"@displayRefreshRatePreferLowest": {},
|
"@displayRefreshRatePreferLowest": {},
|
||||||
"viewerTransitionSlide": "Ковзання",
|
"viewerTransitionSlide": "Ковзання",
|
||||||
"@viewerTransitionSlide": {},
|
"@viewerTransitionSlide": {},
|
||||||
|
@ -781,9 +781,9 @@
|
||||||
},
|
},
|
||||||
"videoStartOverButtonLabel": "ВІДТВОРИТИ СПОЧАТКУ",
|
"videoStartOverButtonLabel": "ВІДТВОРИТИ СПОЧАТКУ",
|
||||||
"@videoStartOverButtonLabel": {},
|
"@videoStartOverButtonLabel": {},
|
||||||
"newAlbumDialogTitle": "Новий Альбом",
|
"newAlbumDialogTitle": "Новий альбом",
|
||||||
"@newAlbumDialogTitle": {},
|
"@newAlbumDialogTitle": {},
|
||||||
"newAlbumDialogNameLabel": "Назва Альбому",
|
"newAlbumDialogNameLabel": "Назва альбому",
|
||||||
"@newAlbumDialogNameLabel": {},
|
"@newAlbumDialogNameLabel": {},
|
||||||
"hideFilterConfirmationDialogMessage": "Відповідні фотографії та відео будуть приховані з вашої колекції. Ви можете показати їх знову в налаштуваннях у розділі \"Конфіденційність\".\n\nВи впевнені, що хочете їх приховати?",
|
"hideFilterConfirmationDialogMessage": "Відповідні фотографії та відео будуть приховані з вашої колекції. Ви можете показати їх знову в налаштуваннях у розділі \"Конфіденційність\".\n\nВи впевнені, що хочете їх приховати?",
|
||||||
"@hideFilterConfirmationDialogMessage": {},
|
"@hideFilterConfirmationDialogMessage": {},
|
||||||
|
@ -795,7 +795,7 @@
|
||||||
"count": {}
|
"count": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Видалити цей альбом і його елемент?} few{Видалити цей альбом і {count} елементи?} other{Видалити цей альбом і {count} елементів?}}",
|
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Видалити цей альбом і елемент у ньому?} few{Видалити цей альбом і {count} елементи у ньому?} other{Видалити цей альбом і {count} елементів у ньому?}}",
|
||||||
"@deleteSingleAlbumConfirmationDialogMessage": {
|
"@deleteSingleAlbumConfirmationDialogMessage": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {}
|
"count": {}
|
||||||
|
@ -803,7 +803,7 @@
|
||||||
},
|
},
|
||||||
"nameConflictDialogSingleSourceMessage": "Деякі файли в папці призначення мають одну й ту саму назву.",
|
"nameConflictDialogSingleSourceMessage": "Деякі файли в папці призначення мають одну й ту саму назву.",
|
||||||
"@nameConflictDialogSingleSourceMessage": {},
|
"@nameConflictDialogSingleSourceMessage": {},
|
||||||
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Видалити ці альбоми та їх елементи?} few{Видалити ці альбоми та їх {count} елементи?} other{Видалити ці альбоми та їх {count} елементів?}}",
|
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Видалити ці альбоми та елемент в них?} few{Видалити ці альбоми та {count} елементи в них?} other{Видалити ці альбоми та {count} елементів в них?}}",
|
||||||
"@deleteMultiAlbumConfirmationDialogMessage": {
|
"@deleteMultiAlbumConfirmationDialogMessage": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {}
|
"count": {}
|
||||||
|
@ -923,11 +923,11 @@
|
||||||
"@settingsShowBottomNavigationBar": {},
|
"@settingsShowBottomNavigationBar": {},
|
||||||
"settingsConfirmationTile": "Діалоги підтвердження",
|
"settingsConfirmationTile": "Діалоги підтвердження",
|
||||||
"@settingsConfirmationTile": {},
|
"@settingsConfirmationTile": {},
|
||||||
"settingsConfirmationDialogTitle": "Діалоги Підтвердження",
|
"settingsConfirmationDialogTitle": "Діалоги підтвердження",
|
||||||
"@settingsConfirmationDialogTitle": {},
|
"@settingsConfirmationDialogTitle": {},
|
||||||
"settingsConfirmationBeforeDeleteItems": "Запитати, перш ніж видаляти предмети назавжди",
|
"settingsConfirmationBeforeDeleteItems": "Запитати, перш ніж видаляти елементи назавжди",
|
||||||
"@settingsConfirmationBeforeDeleteItems": {},
|
"@settingsConfirmationBeforeDeleteItems": {},
|
||||||
"settingsConfirmationBeforeMoveToBinItems": "Запитати перед тим, як переносити предмети до кошика",
|
"settingsConfirmationBeforeMoveToBinItems": "Запитати перед тим, як переносити елементи до кошика",
|
||||||
"@settingsConfirmationBeforeMoveToBinItems": {},
|
"@settingsConfirmationBeforeMoveToBinItems": {},
|
||||||
"settingsNavigationDrawerTabPages": "Сторінки",
|
"settingsNavigationDrawerTabPages": "Сторінки",
|
||||||
"@settingsNavigationDrawerTabPages": {},
|
"@settingsNavigationDrawerTabPages": {},
|
||||||
|
@ -945,7 +945,7 @@
|
||||||
"@settingsThumbnailShowRawIcon": {},
|
"@settingsThumbnailShowRawIcon": {},
|
||||||
"settingsThumbnailShowVideoDuration": "Показати тривалість відео",
|
"settingsThumbnailShowVideoDuration": "Показати тривалість відео",
|
||||||
"@settingsThumbnailShowVideoDuration": {},
|
"@settingsThumbnailShowVideoDuration": {},
|
||||||
"settingsCollectionQuickActionEditorPageTitle": "Швидкі Дії",
|
"settingsCollectionQuickActionEditorPageTitle": "Швидкі дії",
|
||||||
"@settingsCollectionQuickActionEditorPageTitle": {},
|
"@settingsCollectionQuickActionEditorPageTitle": {},
|
||||||
"settingsCollectionQuickActionTabBrowsing": "Перегляд",
|
"settingsCollectionQuickActionTabBrowsing": "Перегляд",
|
||||||
"@settingsCollectionQuickActionTabBrowsing": {},
|
"@settingsCollectionQuickActionTabBrowsing": {},
|
||||||
|
@ -965,7 +965,7 @@
|
||||||
"@settingsImageBackground": {},
|
"@settingsImageBackground": {},
|
||||||
"settingsViewerQuickActionsTile": "Швидкі дії",
|
"settingsViewerQuickActionsTile": "Швидкі дії",
|
||||||
"@settingsViewerQuickActionsTile": {},
|
"@settingsViewerQuickActionsTile": {},
|
||||||
"settingsViewerQuickActionEditorPageTitle": "Швидкі Дії",
|
"settingsViewerQuickActionEditorPageTitle": "Швидкі дії",
|
||||||
"@settingsViewerQuickActionEditorPageTitle": {},
|
"@settingsViewerQuickActionEditorPageTitle": {},
|
||||||
"settingsViewerQuickActionEditorBanner": "Торкніться і утримуйте для переміщення кнопок і вибору дій, які відображатимуться у переглядачі.",
|
"settingsViewerQuickActionEditorBanner": "Торкніться і утримуйте для переміщення кнопок і вибору дій, які відображатимуться у переглядачі.",
|
||||||
"@settingsViewerQuickActionEditorBanner": {},
|
"@settingsViewerQuickActionEditorBanner": {},
|
||||||
|
@ -1005,7 +1005,7 @@
|
||||||
"@settingsSlideshowIntervalTile": {},
|
"@settingsSlideshowIntervalTile": {},
|
||||||
"settingsSlideshowVideoPlaybackTile": "Відтворення відео",
|
"settingsSlideshowVideoPlaybackTile": "Відтворення відео",
|
||||||
"@settingsSlideshowVideoPlaybackTile": {},
|
"@settingsSlideshowVideoPlaybackTile": {},
|
||||||
"settingsSlideshowVideoPlaybackDialogTitle": "Відтворення Відео",
|
"settingsSlideshowVideoPlaybackDialogTitle": "Відтворення відео",
|
||||||
"@settingsSlideshowVideoPlaybackDialogTitle": {},
|
"@settingsSlideshowVideoPlaybackDialogTitle": {},
|
||||||
"settingsVideoPageTitle": "Налаштування Відео",
|
"settingsVideoPageTitle": "Налаштування Відео",
|
||||||
"@settingsVideoPageTitle": {},
|
"@settingsVideoPageTitle": {},
|
||||||
|
@ -1015,13 +1015,13 @@
|
||||||
"@settingsVideoEnableHardwareAcceleration": {},
|
"@settingsVideoEnableHardwareAcceleration": {},
|
||||||
"settingsVideoAutoPlay": "Автоматичне відтворення",
|
"settingsVideoAutoPlay": "Автоматичне відтворення",
|
||||||
"@settingsVideoAutoPlay": {},
|
"@settingsVideoAutoPlay": {},
|
||||||
"settingsVideoLoopModeDialogTitle": "Циклічний Режим",
|
"settingsVideoLoopModeDialogTitle": "Циклічний режим",
|
||||||
"@settingsVideoLoopModeDialogTitle": {},
|
"@settingsVideoLoopModeDialogTitle": {},
|
||||||
"settingsSubtitleThemeTile": "Субтитри",
|
"settingsSubtitleThemeTile": "Субтитри",
|
||||||
"@settingsSubtitleThemeTile": {},
|
"@settingsSubtitleThemeTile": {},
|
||||||
"settingsSubtitleThemePageTitle": "Субтитри",
|
"settingsSubtitleThemePageTitle": "Субтитри",
|
||||||
"@settingsSubtitleThemePageTitle": {},
|
"@settingsSubtitleThemePageTitle": {},
|
||||||
"settingsSubtitleThemeTextAlignmentDialogTitle": "Вирівнювання Тексту",
|
"settingsSubtitleThemeTextAlignmentDialogTitle": "Вирівнювання тексту",
|
||||||
"@settingsSubtitleThemeTextAlignmentDialogTitle": {},
|
"@settingsSubtitleThemeTextAlignmentDialogTitle": {},
|
||||||
"settingsSubtitleThemeTextPositionTile": "Положення тексту",
|
"settingsSubtitleThemeTextPositionTile": "Положення тексту",
|
||||||
"@settingsSubtitleThemeTextPositionTile": {},
|
"@settingsSubtitleThemeTextPositionTile": {},
|
||||||
|
@ -1047,7 +1047,7 @@
|
||||||
"@settingsVideoGestureDoubleTapTogglePlay": {},
|
"@settingsVideoGestureDoubleTapTogglePlay": {},
|
||||||
"settingsVideoGestureSideDoubleTapSeek": "Подвійне натискання на краї екрану для переходу назад/вперед",
|
"settingsVideoGestureSideDoubleTapSeek": "Подвійне натискання на краї екрану для переходу назад/вперед",
|
||||||
"@settingsVideoGestureSideDoubleTapSeek": {},
|
"@settingsVideoGestureSideDoubleTapSeek": {},
|
||||||
"settingsAllowErrorReporting": "Дозволити анонімну відправку повідомлення про помилки",
|
"settingsAllowErrorReporting": "Дозволити анонімну відправку повідомлень про помилки",
|
||||||
"@settingsAllowErrorReporting": {},
|
"@settingsAllowErrorReporting": {},
|
||||||
"settingsSaveSearchHistory": "Зберігати історію пошуку",
|
"settingsSaveSearchHistory": "Зберігати історію пошуку",
|
||||||
"@settingsSaveSearchHistory": {},
|
"@settingsSaveSearchHistory": {},
|
||||||
|
@ -1057,21 +1057,21 @@
|
||||||
"@settingsAllowMediaManagement": {},
|
"@settingsAllowMediaManagement": {},
|
||||||
"settingsHiddenItemsTile": "Приховані елементи",
|
"settingsHiddenItemsTile": "Приховані елементи",
|
||||||
"@settingsHiddenItemsTile": {},
|
"@settingsHiddenItemsTile": {},
|
||||||
"settingsHiddenItemsPageTitle": "Приховані Елементи",
|
"settingsHiddenItemsPageTitle": "Приховані елементи",
|
||||||
"@settingsHiddenItemsPageTitle": {},
|
"@settingsHiddenItemsPageTitle": {},
|
||||||
"settingsHiddenItemsTabFilters": "Приховані Фільтри",
|
"settingsHiddenItemsTabFilters": "Приховані фільтри",
|
||||||
"@settingsHiddenItemsTabFilters": {},
|
"@settingsHiddenItemsTabFilters": {},
|
||||||
"settingsHiddenFiltersBanner": "Фотографії та відео, що відповідають прихованим фільтрам, не з'являться у вашій колекції.",
|
"settingsHiddenFiltersBanner": "Фотографії та відео, що відповідають прихованим фільтрам, не з'являться у вашій колекції.",
|
||||||
"@settingsHiddenFiltersBanner": {},
|
"@settingsHiddenFiltersBanner": {},
|
||||||
"settingsHiddenFiltersEmpty": "Немає прихованих фільтрів",
|
"settingsHiddenFiltersEmpty": "Немає прихованих фільтрів",
|
||||||
"@settingsHiddenFiltersEmpty": {},
|
"@settingsHiddenFiltersEmpty": {},
|
||||||
"settingsHiddenItemsTabPaths": "Приховані Шляхи",
|
"settingsHiddenItemsTabPaths": "Приховані шляхи",
|
||||||
"@settingsHiddenItemsTabPaths": {},
|
"@settingsHiddenItemsTabPaths": {},
|
||||||
"addPathTooltip": "Додати шлях",
|
"addPathTooltip": "Додати шлях",
|
||||||
"@addPathTooltip": {},
|
"@addPathTooltip": {},
|
||||||
"settingsStorageAccessTile": "Доступ до сховища",
|
"settingsStorageAccessTile": "Доступ до сховища",
|
||||||
"@settingsStorageAccessTile": {},
|
"@settingsStorageAccessTile": {},
|
||||||
"settingsStorageAccessPageTitle": "Доступ до Сховища",
|
"settingsStorageAccessPageTitle": "Доступ до сховища",
|
||||||
"@settingsStorageAccessPageTitle": {},
|
"@settingsStorageAccessPageTitle": {},
|
||||||
"settingsStorageAccessBanner": "Деякі каталоги вимагають явного надання доступу для зміни файлів в них. Ви можете переглянути тут каталоги, до яких ви раніше надавали доступ.",
|
"settingsStorageAccessBanner": "Деякі каталоги вимагають явного надання доступу для зміни файлів в них. Ви можете переглянути тут каталоги, до яких ви раніше надавали доступ.",
|
||||||
"@settingsStorageAccessBanner": {},
|
"@settingsStorageAccessBanner": {},
|
||||||
|
@ -1083,7 +1083,7 @@
|
||||||
"@settingsAccessibilitySectionTitle": {},
|
"@settingsAccessibilitySectionTitle": {},
|
||||||
"settingsRemoveAnimationsTile": "Видалити анімації",
|
"settingsRemoveAnimationsTile": "Видалити анімації",
|
||||||
"@settingsRemoveAnimationsTile": {},
|
"@settingsRemoveAnimationsTile": {},
|
||||||
"settingsRemoveAnimationsDialogTitle": "Видалити Анімації",
|
"settingsRemoveAnimationsDialogTitle": "Видалити анімації",
|
||||||
"@settingsRemoveAnimationsDialogTitle": {},
|
"@settingsRemoveAnimationsDialogTitle": {},
|
||||||
"settingsTimeToTakeActionTile": "Час на виконання",
|
"settingsTimeToTakeActionTile": "Час на виконання",
|
||||||
"@settingsTimeToTakeActionTile": {},
|
"@settingsTimeToTakeActionTile": {},
|
||||||
|
@ -1097,7 +1097,7 @@
|
||||||
"@settingsThemeEnableDynamicColor": {},
|
"@settingsThemeEnableDynamicColor": {},
|
||||||
"settingsDisplayRefreshRateModeTile": "Частота оновлення дисплея",
|
"settingsDisplayRefreshRateModeTile": "Частота оновлення дисплея",
|
||||||
"@settingsDisplayRefreshRateModeTile": {},
|
"@settingsDisplayRefreshRateModeTile": {},
|
||||||
"settingsLanguageSectionTitle": "Мова та Формати",
|
"settingsLanguageSectionTitle": "Мова та формати",
|
||||||
"@settingsLanguageSectionTitle": {},
|
"@settingsLanguageSectionTitle": {},
|
||||||
"settingsLanguageTile": "Мова",
|
"settingsLanguageTile": "Мова",
|
||||||
"@settingsLanguageTile": {},
|
"@settingsLanguageTile": {},
|
||||||
|
@ -1105,7 +1105,7 @@
|
||||||
"@settingsCoordinateFormatTile": {},
|
"@settingsCoordinateFormatTile": {},
|
||||||
"settingsUnitSystemTile": "Одиниці виміру",
|
"settingsUnitSystemTile": "Одиниці виміру",
|
||||||
"@settingsUnitSystemTile": {},
|
"@settingsUnitSystemTile": {},
|
||||||
"settingsUnitSystemDialogTitle": "Одиниці Виміру",
|
"settingsUnitSystemDialogTitle": "Одиниці виміру",
|
||||||
"@settingsUnitSystemDialogTitle": {},
|
"@settingsUnitSystemDialogTitle": {},
|
||||||
"settingsWidgetPageTitle": "Фоторамка",
|
"settingsWidgetPageTitle": "Фоторамка",
|
||||||
"@settingsWidgetPageTitle": {},
|
"@settingsWidgetPageTitle": {},
|
||||||
|
@ -1159,7 +1159,7 @@
|
||||||
"@viewerInfoLabelCoordinates": {},
|
"@viewerInfoLabelCoordinates": {},
|
||||||
"viewerInfoLabelAddress": "Адреса",
|
"viewerInfoLabelAddress": "Адреса",
|
||||||
"@viewerInfoLabelAddress": {},
|
"@viewerInfoLabelAddress": {},
|
||||||
"mapStyleDialogTitle": "Стиль Карти",
|
"mapStyleDialogTitle": "Стиль карти",
|
||||||
"@mapStyleDialogTitle": {},
|
"@mapStyleDialogTitle": {},
|
||||||
"mapStyleTooltip": "Виберіть стиль карти",
|
"mapStyleTooltip": "Виберіть стиль карти",
|
||||||
"@mapStyleTooltip": {},
|
"@mapStyleTooltip": {},
|
||||||
|
@ -1197,7 +1197,7 @@
|
||||||
"@viewerInfoSearchSuggestionRights": {},
|
"@viewerInfoSearchSuggestionRights": {},
|
||||||
"wallpaperUseScrollEffect": "Використовувати ефект прокрутки на головному екрані",
|
"wallpaperUseScrollEffect": "Використовувати ефект прокрутки на головному екрані",
|
||||||
"@wallpaperUseScrollEffect": {},
|
"@wallpaperUseScrollEffect": {},
|
||||||
"tagEditorPageTitle": "Редагування Тегів",
|
"tagEditorPageTitle": "Редагування тегів",
|
||||||
"@tagEditorPageTitle": {},
|
"@tagEditorPageTitle": {},
|
||||||
"tagEditorPageNewTagFieldLabel": "Новий тег",
|
"tagEditorPageNewTagFieldLabel": "Новий тег",
|
||||||
"@tagEditorPageNewTagFieldLabel": {},
|
"@tagEditorPageNewTagFieldLabel": {},
|
||||||
|
@ -1245,7 +1245,7 @@
|
||||||
"@settingsActionImportDialogTitle": {},
|
"@settingsActionImportDialogTitle": {},
|
||||||
"appExportFavourites": "Обране",
|
"appExportFavourites": "Обране",
|
||||||
"@appExportFavourites": {},
|
"@appExportFavourites": {},
|
||||||
"settingsKeepScreenOnDialogTitle": "Тримати Екран Увімкненим",
|
"settingsKeepScreenOnDialogTitle": "Тримати екран увімкненим",
|
||||||
"@settingsKeepScreenOnDialogTitle": {},
|
"@settingsKeepScreenOnDialogTitle": {},
|
||||||
"settingsActionExportDialogTitle": "Експорт",
|
"settingsActionExportDialogTitle": "Експорт",
|
||||||
"@settingsActionExportDialogTitle": {},
|
"@settingsActionExportDialogTitle": {},
|
||||||
|
@ -1279,15 +1279,15 @@
|
||||||
"@settingsNavigationDrawerTabTypes": {},
|
"@settingsNavigationDrawerTabTypes": {},
|
||||||
"settingsThumbnailOverlayPageTitle": "Накладення",
|
"settingsThumbnailOverlayPageTitle": "Накладення",
|
||||||
"@settingsThumbnailOverlayPageTitle": {},
|
"@settingsThumbnailOverlayPageTitle": {},
|
||||||
"settingsNavigationDrawerEditorPageTitle": "Навігаційне Меню",
|
"settingsNavigationDrawerEditorPageTitle": "Навігаційне меню",
|
||||||
"@settingsNavigationDrawerEditorPageTitle": {},
|
"@settingsNavigationDrawerEditorPageTitle": {},
|
||||||
"settingsCollectionSelectionQuickActionEditorBanner": "Торкніться і утримуйте для переміщення кнопок і вибору дій, які будуть відображатися при виборі елементів.",
|
"settingsCollectionSelectionQuickActionEditorBanner": "Торкніться і утримуйте для переміщення кнопок і вибору дій, які будуть відображатися при виборі елементів.",
|
||||||
"@settingsCollectionSelectionQuickActionEditorBanner": {},
|
"@settingsCollectionSelectionQuickActionEditorBanner": {},
|
||||||
"settingsThumbnailSectionTitle": "Мініатюри",
|
"settingsThumbnailSectionTitle": "Мініатюри",
|
||||||
"@settingsThumbnailSectionTitle": {},
|
"@settingsThumbnailSectionTitle": {},
|
||||||
"settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Відображувані Кнопки",
|
"settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Відображувані кнопки",
|
||||||
"@settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": {},
|
"@settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": {},
|
||||||
"settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Доступні Кнопки",
|
"settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Доступні кнопки",
|
||||||
"@settingsViewerQuickActionEditorAvailableButtonsSectionTitle": {},
|
"@settingsViewerQuickActionEditorAvailableButtonsSectionTitle": {},
|
||||||
"settingsSubtitleThemeTextAlignmentTile": "Вирівнювання тексту",
|
"settingsSubtitleThemeTextAlignmentTile": "Вирівнювання тексту",
|
||||||
"@settingsSubtitleThemeTextAlignmentTile": {},
|
"@settingsSubtitleThemeTextAlignmentTile": {},
|
||||||
|
@ -1307,7 +1307,7 @@
|
||||||
"@settingsVideoSectionTitle": {},
|
"@settingsVideoSectionTitle": {},
|
||||||
"settingsAllowInstalledAppAccessSubtitle": "Використовується для покращення відображення альбомів",
|
"settingsAllowInstalledAppAccessSubtitle": "Використовується для покращення відображення альбомів",
|
||||||
"@settingsAllowInstalledAppAccessSubtitle": {},
|
"@settingsAllowInstalledAppAccessSubtitle": {},
|
||||||
"settingsSubtitleThemeTextPositionDialogTitle": "Положення Тексту",
|
"settingsSubtitleThemeTextPositionDialogTitle": "Положення тексту",
|
||||||
"@settingsSubtitleThemeTextPositionDialogTitle": {},
|
"@settingsSubtitleThemeTextPositionDialogTitle": {},
|
||||||
"settingsThumbnailShowRating": "Показати рейтинг",
|
"settingsThumbnailShowRating": "Показати рейтинг",
|
||||||
"@settingsThumbnailShowRating": {},
|
"@settingsThumbnailShowRating": {},
|
||||||
|
@ -1337,9 +1337,9 @@
|
||||||
"count": {}
|
"count": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settingsDisplayRefreshRateModeDialogTitle": "Частота Оновлення",
|
"settingsDisplayRefreshRateModeDialogTitle": "Частота оновлення",
|
||||||
"@settingsDisplayRefreshRateModeDialogTitle": {},
|
"@settingsDisplayRefreshRateModeDialogTitle": {},
|
||||||
"settingsCoordinateFormatDialogTitle": "Формат Координат",
|
"settingsCoordinateFormatDialogTitle": "Формат координат",
|
||||||
"@settingsCoordinateFormatDialogTitle": {},
|
"@settingsCoordinateFormatDialogTitle": {},
|
||||||
"settingsScreenSaverPageTitle": "Заставка на Екран",
|
"settingsScreenSaverPageTitle": "Заставка на Екран",
|
||||||
"@settingsScreenSaverPageTitle": {},
|
"@settingsScreenSaverPageTitle": {},
|
||||||
|
@ -1354,5 +1354,15 @@
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {}
|
"count": {}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"settingsViewerShowDescription": "Показати опис",
|
||||||
|
"@settingsViewerShowDescription": {},
|
||||||
|
"settingsModificationWarningDialogMessage": "Інші параметри будуть змінені.",
|
||||||
|
"@settingsModificationWarningDialogMessage": {},
|
||||||
|
"settingsDisplayUseTvInterface": "Інтерфейс Android TV",
|
||||||
|
"@settingsDisplayUseTvInterface": {},
|
||||||
|
"filterLocatedLabel": "Розташований",
|
||||||
|
"@filterLocatedLabel": {},
|
||||||
|
"filterTaggedLabel": "Позначений тегом",
|
||||||
|
"@filterTaggedLabel": {}
|
||||||
}
|
}
|
||||||
|
|
35
lib/model/actions/map_actions.dart
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,8 +27,6 @@ class Device {
|
||||||
|
|
||||||
bool get isDynamicColorAvailable => _isDynamicColorAvailable;
|
bool get isDynamicColorAvailable => _isDynamicColorAvailable;
|
||||||
|
|
||||||
bool get isReadOnly => _isTelevision;
|
|
||||||
|
|
||||||
bool get isTelevision => _isTelevision;
|
bool get isTelevision => _isTelevision;
|
||||||
|
|
||||||
bool get showPinShortcutFeedback => _showPinShortcutFeedback;
|
bool get showPinShortcutFeedback => _showPinShortcutFeedback;
|
||||||
|
|
|
@ -3,7 +3,6 @@ import 'dart:io';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:aves/geo/countries.dart';
|
import 'package:aves/geo/countries.dart';
|
||||||
import 'package:aves/model/device.dart';
|
|
||||||
import 'package:aves/model/entry_cache.dart';
|
import 'package:aves/model/entry_cache.dart';
|
||||||
import 'package:aves/model/entry_dirs.dart';
|
import 'package:aves/model/entry_dirs.dart';
|
||||||
import 'package:aves/model/favourites.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/catalog.dart';
|
||||||
import 'package:aves/model/metadata/trash.dart';
|
import 'package:aves/model/metadata/trash.dart';
|
||||||
import 'package:aves/model/multipage.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/source/trash.dart';
|
||||||
import 'package:aves/model/video/metadata.dart';
|
import 'package:aves/model/video/metadata.dart';
|
||||||
import 'package:aves/ref/mime_types.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 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);
|
bool get canEditDate => canEdit && (canEditExif || canEditXmp);
|
||||||
|
|
||||||
|
|
|
@ -235,7 +235,10 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
|
||||||
final description = fields[DescriptionField.description];
|
final description = fields[DescriptionField.description];
|
||||||
|
|
||||||
if (canEditExif && editDescription) {
|
if (canEditExif && editDescription) {
|
||||||
metadata[MetadataType.exif] = {MetadataField.exifImageDescription.toPlatform!: description};
|
metadata[MetadataType.exif] = {
|
||||||
|
MetadataField.exifImageDescription.toPlatform!: null,
|
||||||
|
MetadataField.exifUserComment.toPlatform!: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canEditIptc) {
|
if (canEditIptc) {
|
||||||
|
|