#301 info: edit description
This commit is contained in:
parent
1d4d0307d7
commit
c015c71fa9
33 changed files with 342 additions and 70 deletions
|
@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
|
||||||
- Viewer: optional gesture to show previous/next item
|
- Viewer: optional gesture to show previous/next item
|
||||||
- Albums / Countries / Tags: live title filter
|
- Albums / Countries / Tags: live title filter
|
||||||
- option to hide confirmation message after moving items to the bin
|
- option to hide confirmation message after moving items to the bin
|
||||||
|
- Collection / Info: edit description via Exif / IPTC / XMP
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
||||||
"hasContentResolverProp" -> ioScope.launch { safe(call, result, ::hasContentResolverProp) }
|
"hasContentResolverProp" -> ioScope.launch { safe(call, result, ::hasContentResolverProp) }
|
||||||
"getContentResolverProp" -> ioScope.launch { safe(call, result, ::getContentResolverProp) }
|
"getContentResolverProp" -> ioScope.launch { safe(call, result, ::getContentResolverProp) }
|
||||||
"getDate" -> ioScope.launch { safe(call, result, ::getDate) }
|
"getDate" -> ioScope.launch { safe(call, result, ::getDate) }
|
||||||
|
"getDescription" -> ioScope.launch { safe(call, result, ::getDescription) }
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -409,9 +410,8 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
||||||
// - XMP / photoshop:DateCreated
|
// - XMP / photoshop:DateCreated
|
||||||
// - PNG / TIME / LAST_MODIFICATION_TIME
|
// - PNG / TIME / LAST_MODIFICATION_TIME
|
||||||
// - Video / METADATA_KEY_DATE
|
// - Video / METADATA_KEY_DATE
|
||||||
// set `KEY_XMP_TITLE_DESCRIPTION` from these fields (by precedence):
|
// set `KEY_XMP_TITLE` from this field:
|
||||||
// - XMP / dc:title
|
// - XMP / dc:title
|
||||||
// - XMP / dc:description
|
|
||||||
// set `KEY_XMP_SUBJECTS` from these fields (by precedence):
|
// set `KEY_XMP_SUBJECTS` from these fields (by precedence):
|
||||||
// - XMP / dc:subject
|
// - XMP / dc:subject
|
||||||
// - IPTC / keywords
|
// - IPTC / keywords
|
||||||
|
@ -514,10 +514,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
||||||
val values = (1 until count + 1).map { xmpMeta.getArrayItem(XMP.DC_SCHEMA_NS, XMP.DC_SUBJECT_PROP_NAME, it).value }
|
val values = (1 until count + 1).map { xmpMeta.getArrayItem(XMP.DC_SCHEMA_NS, XMP.DC_SUBJECT_PROP_NAME, it).value }
|
||||||
metadataMap[KEY_XMP_SUBJECTS] = values.joinToString(XMP_SUBJECTS_SEPARATOR)
|
metadataMap[KEY_XMP_SUBJECTS] = values.joinToString(XMP_SUBJECTS_SEPARATOR)
|
||||||
}
|
}
|
||||||
xmpMeta.getSafeLocalizedText(XMP.DC_SCHEMA_NS, XMP.DC_TITLE_PROP_NAME, acceptBlank = false) { metadataMap[KEY_XMP_TITLE_DESCRIPTION] = it }
|
xmpMeta.getSafeLocalizedText(XMP.DC_SCHEMA_NS, XMP.DC_TITLE_PROP_NAME, acceptBlank = false) { metadataMap[KEY_XMP_TITLE] = it }
|
||||||
if (!metadataMap.containsKey(KEY_XMP_TITLE_DESCRIPTION)) {
|
|
||||||
xmpMeta.getSafeLocalizedText(XMP.DC_SCHEMA_NS, XMP.DC_DESCRIPTION_PROP_NAME, acceptBlank = false) { metadataMap[KEY_XMP_TITLE_DESCRIPTION] = it }
|
|
||||||
}
|
|
||||||
if (!metadataMap.containsKey(KEY_DATE_MILLIS)) {
|
if (!metadataMap.containsKey(KEY_DATE_MILLIS)) {
|
||||||
xmpMeta.getSafeDateMillis(XMP.XMP_SCHEMA_NS, XMP.XMP_CREATE_DATE_PROP_NAME) { metadataMap[KEY_DATE_MILLIS] = it }
|
xmpMeta.getSafeDateMillis(XMP.XMP_SCHEMA_NS, XMP.XMP_CREATE_DATE_PROP_NAME) { metadataMap[KEY_DATE_MILLIS] = it }
|
||||||
if (!metadataMap.containsKey(KEY_DATE_MILLIS)) {
|
if (!metadataMap.containsKey(KEY_DATE_MILLIS)) {
|
||||||
|
@ -1052,6 +1049,58 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
||||||
result.success(dateMillis)
|
result.success(dateMillis)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return description from these fields (by precedence):
|
||||||
|
// - XMP / dc:description
|
||||||
|
// - IPTC / caption-abstract
|
||||||
|
// - Exif / ImageDescription
|
||||||
|
private fun getDescription(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()
|
||||||
|
if (mimeType == null || uri == null) {
|
||||||
|
result.error("getDescription-args", "missing arguments", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var description: String? = null
|
||||||
|
if (canReadWithMetadataExtractor(mimeType)) {
|
||||||
|
try {
|
||||||
|
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
|
||||||
|
val metadata = MetadataExtractorHelper.safeRead(input)
|
||||||
|
|
||||||
|
for (dir in metadata.getDirectoriesOfType(XmpDirectory::class.java)) {
|
||||||
|
val xmpMeta = dir.xmpMeta
|
||||||
|
try {
|
||||||
|
if (xmpMeta.doesPropertyExist(XMP.DC_SCHEMA_NS, XMP.DC_DESCRIPTION_PROP_NAME)) {
|
||||||
|
xmpMeta.getSafeLocalizedText(XMP.DC_SCHEMA_NS, XMP.DC_DESCRIPTION_PROP_NAME) { description = it }
|
||||||
|
}
|
||||||
|
} catch (e: XMPException) {
|
||||||
|
Log.w(LOG_TAG, "failed to read XMP directory for uri=$uri", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (description == null) {
|
||||||
|
for (dir in metadata.getDirectoriesOfType(IptcDirectory::class.java)) {
|
||||||
|
dir.getSafeString(IptcDirectory.TAG_CAPTION) { description = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (description == null) {
|
||||||
|
for (dir in metadata.getDirectoriesOfType(ExifIFD0Directory::class.java)) {
|
||||||
|
dir.getSafeString(ExifIFD0Directory.TAG_IMAGE_DESCRIPTION) { description = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||||
|
} catch (e: NoClassDefFoundError) {
|
||||||
|
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||||
|
} catch (e: AssertionError) {
|
||||||
|
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.success(description)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOG_TAG = LogUtils.createTag<MetadataFetchHandler>()
|
private val LOG_TAG = LogUtils.createTag<MetadataFetchHandler>()
|
||||||
const val CHANNEL = "deckers.thibault/aves/metadata_fetch"
|
const val CHANNEL = "deckers.thibault/aves/metadata_fetch"
|
||||||
|
@ -1100,7 +1149,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
||||||
private const val KEY_LATITUDE = "latitude"
|
private const val KEY_LATITUDE = "latitude"
|
||||||
private const val KEY_LONGITUDE = "longitude"
|
private const val KEY_LONGITUDE = "longitude"
|
||||||
private const val KEY_XMP_SUBJECTS = "xmpSubjects"
|
private const val KEY_XMP_SUBJECTS = "xmpSubjects"
|
||||||
private const val KEY_XMP_TITLE_DESCRIPTION = "xmpTitleDescription"
|
private const val KEY_XMP_TITLE = "xmpTitleDescription"
|
||||||
private const val KEY_RATING = "rating"
|
private const val KEY_RATING = "rating"
|
||||||
|
|
||||||
private const val MASK_IS_ANIMATED = 1 shl 0
|
private const val MASK_IS_ANIMATED = 1 shl 0
|
||||||
|
|
|
@ -28,8 +28,8 @@ object XMP {
|
||||||
const val CONTAINER_SCHEMA_NS = "http://ns.google.com/photos/1.0/container/"
|
const val CONTAINER_SCHEMA_NS = "http://ns.google.com/photos/1.0/container/"
|
||||||
private const val CONTAINER_ITEM_SCHEMA_NS = "http://ns.google.com/photos/1.0/container/item/"
|
private const val CONTAINER_ITEM_SCHEMA_NS = "http://ns.google.com/photos/1.0/container/item/"
|
||||||
|
|
||||||
const val DC_DESCRIPTION_PROP_NAME = "dc:description"
|
|
||||||
const val DC_SUBJECT_PROP_NAME = "dc:subject"
|
const val DC_SUBJECT_PROP_NAME = "dc:subject"
|
||||||
|
const val DC_DESCRIPTION_PROP_NAME = "dc:description"
|
||||||
const val DC_TITLE_PROP_NAME = "dc:title"
|
const val DC_TITLE_PROP_NAME = "dc:title"
|
||||||
const val MS_RATING_PROP_NAME = "MicrosoftPhoto:Rating"
|
const val MS_RATING_PROP_NAME = "MicrosoftPhoto:Rating"
|
||||||
const val PS_DATE_CREATED_PROP_NAME = "photoshop:DateCreated"
|
const val PS_DATE_CREATED_PROP_NAME = "photoshop:DateCreated"
|
||||||
|
|
|
@ -238,6 +238,8 @@
|
||||||
|
|
||||||
"renameEntryDialogLabel": "Neuer Name",
|
"renameEntryDialogLabel": "Neuer Name",
|
||||||
|
|
||||||
|
"editEntryDialogTargetFieldsHeader": "Zu ändernde Felder",
|
||||||
|
|
||||||
"editEntryDateDialogTitle": "Datum & Uhrzeit",
|
"editEntryDateDialogTitle": "Datum & Uhrzeit",
|
||||||
"editEntryDateDialogSetCustom": "Datum einstellen",
|
"editEntryDateDialogSetCustom": "Datum einstellen",
|
||||||
"editEntryDateDialogCopyField": "Von anderem Datum kopieren",
|
"editEntryDateDialogCopyField": "Von anderem Datum kopieren",
|
||||||
|
@ -245,7 +247,6 @@
|
||||||
"editEntryDateDialogExtractFromTitle": "Auszug aus dem Titel",
|
"editEntryDateDialogExtractFromTitle": "Auszug aus dem Titel",
|
||||||
"editEntryDateDialogShift": "Verschieben",
|
"editEntryDateDialogShift": "Verschieben",
|
||||||
"editEntryDateDialogSourceFileModifiedDate": "Änderungsdatum der Datei",
|
"editEntryDateDialogSourceFileModifiedDate": "Änderungsdatum der Datei",
|
||||||
"editEntryDateDialogTargetFieldsHeader": "Zu ändernde Felder",
|
|
||||||
"editEntryDateDialogHours": "Stunden",
|
"editEntryDateDialogHours": "Stunden",
|
||||||
"editEntryDateDialogMinutes": "Minuten",
|
"editEntryDateDialogMinutes": "Minuten",
|
||||||
|
|
||||||
|
|
|
@ -115,6 +115,7 @@
|
||||||
|
|
||||||
"entryInfoActionEditDate": "Edit date & time",
|
"entryInfoActionEditDate": "Edit date & time",
|
||||||
"entryInfoActionEditLocation": "Edit location",
|
"entryInfoActionEditLocation": "Edit location",
|
||||||
|
"entryInfoActionEditDescription": "Edit description",
|
||||||
"entryInfoActionEditRating": "Edit rating",
|
"entryInfoActionEditRating": "Edit rating",
|
||||||
"entryInfoActionEditTags": "Edit tags",
|
"entryInfoActionEditTags": "Edit tags",
|
||||||
"entryInfoActionRemoveMetadata": "Remove metadata",
|
"entryInfoActionRemoveMetadata": "Remove metadata",
|
||||||
|
@ -368,6 +369,8 @@
|
||||||
|
|
||||||
"renameEntryDialogLabel": "New name",
|
"renameEntryDialogLabel": "New name",
|
||||||
|
|
||||||
|
"editEntryDialogTargetFieldsHeader": "Fields to modify",
|
||||||
|
|
||||||
"editEntryDateDialogTitle": "Date & Time",
|
"editEntryDateDialogTitle": "Date & Time",
|
||||||
"editEntryDateDialogSetCustom": "Set custom date",
|
"editEntryDateDialogSetCustom": "Set custom date",
|
||||||
"editEntryDateDialogCopyField": "Copy from other date",
|
"editEntryDateDialogCopyField": "Copy from other date",
|
||||||
|
@ -375,7 +378,6 @@
|
||||||
"editEntryDateDialogExtractFromTitle": "Extract from title",
|
"editEntryDateDialogExtractFromTitle": "Extract from title",
|
||||||
"editEntryDateDialogShift": "Shift",
|
"editEntryDateDialogShift": "Shift",
|
||||||
"editEntryDateDialogSourceFileModifiedDate": "File modified date",
|
"editEntryDateDialogSourceFileModifiedDate": "File modified date",
|
||||||
"editEntryDateDialogTargetFieldsHeader": "Fields to modify",
|
|
||||||
"editEntryDateDialogHours": "Hours",
|
"editEntryDateDialogHours": "Hours",
|
||||||
"editEntryDateDialogMinutes": "Minutes",
|
"editEntryDateDialogMinutes": "Minutes",
|
||||||
|
|
||||||
|
@ -386,6 +388,8 @@
|
||||||
|
|
||||||
"locationPickerUseThisLocationButton": "Use this location",
|
"locationPickerUseThisLocationButton": "Use this location",
|
||||||
|
|
||||||
|
"editEntryDescriptionDialogTitle": "Description",
|
||||||
|
|
||||||
"editEntryRatingDialogTitle": "Rating",
|
"editEntryRatingDialogTitle": "Rating",
|
||||||
|
|
||||||
"removeEntryMetadataDialogTitle": "Metadata Removal",
|
"removeEntryMetadataDialogTitle": "Metadata Removal",
|
||||||
|
|
|
@ -238,6 +238,8 @@
|
||||||
|
|
||||||
"renameEntryDialogLabel": "Renombrar",
|
"renameEntryDialogLabel": "Renombrar",
|
||||||
|
|
||||||
|
"editEntryDialogTargetFieldsHeader": "Campos a modificar",
|
||||||
|
|
||||||
"editEntryDateDialogTitle": "Fecha y hora",
|
"editEntryDateDialogTitle": "Fecha y hora",
|
||||||
"editEntryDateDialogSetCustom": "Establecer fecha personalizada",
|
"editEntryDateDialogSetCustom": "Establecer fecha personalizada",
|
||||||
"editEntryDateDialogCopyField": "Copiar de otra fecha",
|
"editEntryDateDialogCopyField": "Copiar de otra fecha",
|
||||||
|
@ -245,7 +247,6 @@
|
||||||
"editEntryDateDialogExtractFromTitle": "Extraer del título",
|
"editEntryDateDialogExtractFromTitle": "Extraer del título",
|
||||||
"editEntryDateDialogShift": "Cambiar",
|
"editEntryDateDialogShift": "Cambiar",
|
||||||
"editEntryDateDialogSourceFileModifiedDate": "Fecha de modificación del archivo",
|
"editEntryDateDialogSourceFileModifiedDate": "Fecha de modificación del archivo",
|
||||||
"editEntryDateDialogTargetFieldsHeader": "Campos a modificar",
|
|
||||||
"editEntryDateDialogHours": "Horas",
|
"editEntryDateDialogHours": "Horas",
|
||||||
"editEntryDateDialogMinutes": "Minutos",
|
"editEntryDateDialogMinutes": "Minutos",
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,8 @@
|
||||||
|
|
||||||
"renameEntryDialogLabel": "Nouveau nom",
|
"renameEntryDialogLabel": "Nouveau nom",
|
||||||
|
|
||||||
|
"editEntryDialogTargetFieldsHeader": "Champs à modifier",
|
||||||
|
|
||||||
"editEntryDateDialogTitle": "Date & Heure",
|
"editEntryDateDialogTitle": "Date & Heure",
|
||||||
"editEntryDateDialogSetCustom": "Régler une date personnalisée",
|
"editEntryDateDialogSetCustom": "Régler une date personnalisée",
|
||||||
"editEntryDateDialogCopyField": "Copier d’une autre date",
|
"editEntryDateDialogCopyField": "Copier d’une autre date",
|
||||||
|
@ -245,7 +247,6 @@
|
||||||
"editEntryDateDialogExtractFromTitle": "Extraire du titre",
|
"editEntryDateDialogExtractFromTitle": "Extraire du titre",
|
||||||
"editEntryDateDialogShift": "Décaler",
|
"editEntryDateDialogShift": "Décaler",
|
||||||
"editEntryDateDialogSourceFileModifiedDate": "Date de modification du fichier",
|
"editEntryDateDialogSourceFileModifiedDate": "Date de modification du fichier",
|
||||||
"editEntryDateDialogTargetFieldsHeader": "Champs à modifier",
|
|
||||||
"editEntryDateDialogHours": "Heures",
|
"editEntryDateDialogHours": "Heures",
|
||||||
"editEntryDateDialogMinutes": "Minutes",
|
"editEntryDateDialogMinutes": "Minutes",
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,8 @@
|
||||||
|
|
||||||
"renameEntryDialogLabel": "Nama baru",
|
"renameEntryDialogLabel": "Nama baru",
|
||||||
|
|
||||||
|
"editEntryDialogTargetFieldsHeader": "Bidang untuk dimodifikasikan",
|
||||||
|
|
||||||
"editEntryDateDialogTitle": "Tanggal & Waktu",
|
"editEntryDateDialogTitle": "Tanggal & Waktu",
|
||||||
"editEntryDateDialogSetCustom": "Atur tanggal khusus",
|
"editEntryDateDialogSetCustom": "Atur tanggal khusus",
|
||||||
"editEntryDateDialogCopyField": "Salin dari tanggal lain",
|
"editEntryDateDialogCopyField": "Salin dari tanggal lain",
|
||||||
|
@ -245,7 +247,6 @@
|
||||||
"editEntryDateDialogExtractFromTitle": "Ekstrak dari judul",
|
"editEntryDateDialogExtractFromTitle": "Ekstrak dari judul",
|
||||||
"editEntryDateDialogShift": "Geser",
|
"editEntryDateDialogShift": "Geser",
|
||||||
"editEntryDateDialogSourceFileModifiedDate": "Tanggal modifikasi file",
|
"editEntryDateDialogSourceFileModifiedDate": "Tanggal modifikasi file",
|
||||||
"editEntryDateDialogTargetFieldsHeader": "Bidang untuk dimodifikasikan",
|
|
||||||
"editEntryDateDialogHours": "Jam",
|
"editEntryDateDialogHours": "Jam",
|
||||||
"editEntryDateDialogMinutes": "Menit",
|
"editEntryDateDialogMinutes": "Menit",
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,8 @@
|
||||||
|
|
||||||
"renameEntryDialogLabel": "Nuovo nome",
|
"renameEntryDialogLabel": "Nuovo nome",
|
||||||
|
|
||||||
|
"editEntryDialogTargetFieldsHeader": "Campi da modificare",
|
||||||
|
|
||||||
"editEntryDateDialogTitle": "Data e ora",
|
"editEntryDateDialogTitle": "Data e ora",
|
||||||
"editEntryDateDialogSetCustom": "Imposta data personalizzata",
|
"editEntryDateDialogSetCustom": "Imposta data personalizzata",
|
||||||
"editEntryDateDialogCopyField": "Copia da un’altra data",
|
"editEntryDateDialogCopyField": "Copia da un’altra data",
|
||||||
|
@ -245,7 +247,6 @@
|
||||||
"editEntryDateDialogExtractFromTitle": "Estrai dal titolo",
|
"editEntryDateDialogExtractFromTitle": "Estrai dal titolo",
|
||||||
"editEntryDateDialogShift": "Turno",
|
"editEntryDateDialogShift": "Turno",
|
||||||
"editEntryDateDialogSourceFileModifiedDate": "Data di modifica del file",
|
"editEntryDateDialogSourceFileModifiedDate": "Data di modifica del file",
|
||||||
"editEntryDateDialogTargetFieldsHeader": "Campi da modificare",
|
|
||||||
"editEntryDateDialogHours": "Ore",
|
"editEntryDateDialogHours": "Ore",
|
||||||
"editEntryDateDialogMinutes": "Minuti",
|
"editEntryDateDialogMinutes": "Minuti",
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,8 @@
|
||||||
|
|
||||||
"renameEntryDialogLabel": "新しい名前",
|
"renameEntryDialogLabel": "新しい名前",
|
||||||
|
|
||||||
|
"editEntryDialogTargetFieldsHeader": "更新するフィールド",
|
||||||
|
|
||||||
"editEntryDateDialogTitle": "日時",
|
"editEntryDateDialogTitle": "日時",
|
||||||
"editEntryDateDialogSetCustom": "日を設定する",
|
"editEntryDateDialogSetCustom": "日を設定する",
|
||||||
"editEntryDateDialogCopyField": "他の日からコピーする",
|
"editEntryDateDialogCopyField": "他の日からコピーする",
|
||||||
|
@ -245,7 +247,6 @@
|
||||||
"editEntryDateDialogExtractFromTitle": "タイトルから抽出する",
|
"editEntryDateDialogExtractFromTitle": "タイトルから抽出する",
|
||||||
"editEntryDateDialogShift": "シフト",
|
"editEntryDateDialogShift": "シフト",
|
||||||
"editEntryDateDialogSourceFileModifiedDate": "ファイル更新日",
|
"editEntryDateDialogSourceFileModifiedDate": "ファイル更新日",
|
||||||
"editEntryDateDialogTargetFieldsHeader": "更新するフィールド",
|
|
||||||
"editEntryDateDialogHours": "時",
|
"editEntryDateDialogHours": "時",
|
||||||
"editEntryDateDialogMinutes": "分",
|
"editEntryDateDialogMinutes": "分",
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,8 @@
|
||||||
|
|
||||||
"renameEntryDialogLabel": "이름",
|
"renameEntryDialogLabel": "이름",
|
||||||
|
|
||||||
|
"editEntryDialogTargetFieldsHeader": "수정할 필드",
|
||||||
|
|
||||||
"editEntryDateDialogTitle": "날짜 및 시간",
|
"editEntryDateDialogTitle": "날짜 및 시간",
|
||||||
"editEntryDateDialogSetCustom": "지정 날짜로 편집",
|
"editEntryDateDialogSetCustom": "지정 날짜로 편집",
|
||||||
"editEntryDateDialogCopyField": "다른 날짜에서 지정",
|
"editEntryDateDialogCopyField": "다른 날짜에서 지정",
|
||||||
|
@ -245,7 +247,6 @@
|
||||||
"editEntryDateDialogExtractFromTitle": "제목에서 추출",
|
"editEntryDateDialogExtractFromTitle": "제목에서 추출",
|
||||||
"editEntryDateDialogShift": "시간 이동",
|
"editEntryDateDialogShift": "시간 이동",
|
||||||
"editEntryDateDialogSourceFileModifiedDate": "파일 수정한 날짜",
|
"editEntryDateDialogSourceFileModifiedDate": "파일 수정한 날짜",
|
||||||
"editEntryDateDialogTargetFieldsHeader": "수정할 필드",
|
|
||||||
"editEntryDateDialogHours": "시간",
|
"editEntryDateDialogHours": "시간",
|
||||||
"editEntryDateDialogMinutes": "분",
|
"editEntryDateDialogMinutes": "분",
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,8 @@
|
||||||
|
|
||||||
"renameEntryDialogLabel": "Novo nome",
|
"renameEntryDialogLabel": "Novo nome",
|
||||||
|
|
||||||
|
"editEntryDialogTargetFieldsHeader": "Campos para modificar",
|
||||||
|
|
||||||
"editEntryDateDialogTitle": "Data e hora",
|
"editEntryDateDialogTitle": "Data e hora",
|
||||||
"editEntryDateDialogSetCustom": "Definir data personalizada",
|
"editEntryDateDialogSetCustom": "Definir data personalizada",
|
||||||
"editEntryDateDialogCopyField": "Copiar de outra data",
|
"editEntryDateDialogCopyField": "Copiar de outra data",
|
||||||
|
@ -245,7 +247,6 @@
|
||||||
"editEntryDateDialogExtractFromTitle": "Extrair do título",
|
"editEntryDateDialogExtractFromTitle": "Extrair do título",
|
||||||
"editEntryDateDialogShift": "Mudança",
|
"editEntryDateDialogShift": "Mudança",
|
||||||
"editEntryDateDialogSourceFileModifiedDate": "Data de modificação do arquivo",
|
"editEntryDateDialogSourceFileModifiedDate": "Data de modificação do arquivo",
|
||||||
"editEntryDateDialogTargetFieldsHeader": "Campos para modificar",
|
|
||||||
"editEntryDateDialogHours": "Horas",
|
"editEntryDateDialogHours": "Horas",
|
||||||
"editEntryDateDialogMinutes": "Minutos",
|
"editEntryDateDialogMinutes": "Minutos",
|
||||||
|
|
||||||
|
|
|
@ -237,6 +237,8 @@
|
||||||
|
|
||||||
"renameEntryDialogLabel": "Новое название",
|
"renameEntryDialogLabel": "Новое название",
|
||||||
|
|
||||||
|
"editEntryDialogTargetFieldsHeader": "Поля для изменения",
|
||||||
|
|
||||||
"editEntryDateDialogTitle": "Дата и время",
|
"editEntryDateDialogTitle": "Дата и время",
|
||||||
"editEntryDateDialogSetCustom": "Установить дату",
|
"editEntryDateDialogSetCustom": "Установить дату",
|
||||||
"editEntryDateDialogCopyField": "Копировать с другой даты",
|
"editEntryDateDialogCopyField": "Копировать с другой даты",
|
||||||
|
@ -244,7 +246,6 @@
|
||||||
"editEntryDateDialogExtractFromTitle": "Извлечь из названия",
|
"editEntryDateDialogExtractFromTitle": "Извлечь из названия",
|
||||||
"editEntryDateDialogShift": "Сдвиг",
|
"editEntryDateDialogShift": "Сдвиг",
|
||||||
"editEntryDateDialogSourceFileModifiedDate": "Дата изменения файла",
|
"editEntryDateDialogSourceFileModifiedDate": "Дата изменения файла",
|
||||||
"editEntryDateDialogTargetFieldsHeader": "Поля для изменения",
|
|
||||||
"editEntryDateDialogHours": "Часов",
|
"editEntryDateDialogHours": "Часов",
|
||||||
"editEntryDateDialogMinutes": "Минут",
|
"editEntryDateDialogMinutes": "Минут",
|
||||||
|
|
||||||
|
|
|
@ -230,6 +230,8 @@
|
||||||
|
|
||||||
"renameEntryDialogLabel": "Yeni ad",
|
"renameEntryDialogLabel": "Yeni ad",
|
||||||
|
|
||||||
|
"editEntryDialogTargetFieldsHeader": "Değiştirilecek alanlar",
|
||||||
|
|
||||||
"editEntryDateDialogTitle": "Tarih ve Saat",
|
"editEntryDateDialogTitle": "Tarih ve Saat",
|
||||||
"editEntryDateDialogSetCustom": "Özel tarih ayarla",
|
"editEntryDateDialogSetCustom": "Özel tarih ayarla",
|
||||||
"editEntryDateDialogCopyField": "Başka bir tarihten kopyala",
|
"editEntryDateDialogCopyField": "Başka bir tarihten kopyala",
|
||||||
|
@ -237,7 +239,6 @@
|
||||||
"editEntryDateDialogExtractFromTitle": "Başlıktan ayıkla",
|
"editEntryDateDialogExtractFromTitle": "Başlıktan ayıkla",
|
||||||
"editEntryDateDialogShift": "Değişim",
|
"editEntryDateDialogShift": "Değişim",
|
||||||
"editEntryDateDialogSourceFileModifiedDate": "Dosya değiştirilme tarihi",
|
"editEntryDateDialogSourceFileModifiedDate": "Dosya değiştirilme tarihi",
|
||||||
"editEntryDateDialogTargetFieldsHeader": "Değiştirilecek alanlar",
|
|
||||||
"editEntryDateDialogHours": "Saat",
|
"editEntryDateDialogHours": "Saat",
|
||||||
"editEntryDateDialogMinutes": "Dakika",
|
"editEntryDateDialogMinutes": "Dakika",
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,8 @@
|
||||||
|
|
||||||
"renameEntryDialogLabel": "新名称",
|
"renameEntryDialogLabel": "新名称",
|
||||||
|
|
||||||
|
"editEntryDialogTargetFieldsHeader": "待修改的字段",
|
||||||
|
|
||||||
"editEntryDateDialogTitle": "日期和时间",
|
"editEntryDateDialogTitle": "日期和时间",
|
||||||
"editEntryDateDialogSetCustom": "设置自定义日期",
|
"editEntryDateDialogSetCustom": "设置自定义日期",
|
||||||
"editEntryDateDialogCopyField": "复制自其他日期",
|
"editEntryDateDialogCopyField": "复制自其他日期",
|
||||||
|
@ -245,7 +247,6 @@
|
||||||
"editEntryDateDialogExtractFromTitle": "从标题提取",
|
"editEntryDateDialogExtractFromTitle": "从标题提取",
|
||||||
"editEntryDateDialogShift": "转移",
|
"editEntryDateDialogShift": "转移",
|
||||||
"editEntryDateDialogSourceFileModifiedDate": "文件修改日期",
|
"editEntryDateDialogSourceFileModifiedDate": "文件修改日期",
|
||||||
"editEntryDateDialogTargetFieldsHeader": "待修改的字段",
|
|
||||||
"editEntryDateDialogHours": "时",
|
"editEntryDateDialogHours": "时",
|
||||||
"editEntryDateDialogMinutes": "分",
|
"editEntryDateDialogMinutes": "分",
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ enum EntryInfoAction {
|
||||||
// general
|
// general
|
||||||
editDate,
|
editDate,
|
||||||
editLocation,
|
editLocation,
|
||||||
|
editDescription,
|
||||||
editRating,
|
editRating,
|
||||||
editTags,
|
editTags,
|
||||||
removeMetadata,
|
removeMetadata,
|
||||||
|
@ -23,6 +24,7 @@ class EntryInfoActions {
|
||||||
static const common = [
|
static const common = [
|
||||||
EntryInfoAction.editDate,
|
EntryInfoAction.editDate,
|
||||||
EntryInfoAction.editLocation,
|
EntryInfoAction.editLocation,
|
||||||
|
EntryInfoAction.editDescription,
|
||||||
EntryInfoAction.editRating,
|
EntryInfoAction.editRating,
|
||||||
EntryInfoAction.editTags,
|
EntryInfoAction.editTags,
|
||||||
EntryInfoAction.removeMetadata,
|
EntryInfoAction.removeMetadata,
|
||||||
|
@ -43,6 +45,8 @@ extension ExtraEntryInfoAction on EntryInfoAction {
|
||||||
return context.l10n.entryInfoActionEditDate;
|
return context.l10n.entryInfoActionEditDate;
|
||||||
case EntryInfoAction.editLocation:
|
case EntryInfoAction.editLocation:
|
||||||
return context.l10n.entryInfoActionEditLocation;
|
return context.l10n.entryInfoActionEditLocation;
|
||||||
|
case EntryInfoAction.editDescription:
|
||||||
|
return context.l10n.entryInfoActionEditDescription;
|
||||||
case EntryInfoAction.editRating:
|
case EntryInfoAction.editRating:
|
||||||
return context.l10n.entryInfoActionEditRating;
|
return context.l10n.entryInfoActionEditRating;
|
||||||
case EntryInfoAction.editTags:
|
case EntryInfoAction.editTags:
|
||||||
|
@ -84,6 +88,8 @@ extension ExtraEntryInfoAction on EntryInfoAction {
|
||||||
return AIcons.date;
|
return AIcons.date;
|
||||||
case EntryInfoAction.editLocation:
|
case EntryInfoAction.editLocation:
|
||||||
return AIcons.location;
|
return AIcons.location;
|
||||||
|
case EntryInfoAction.editDescription:
|
||||||
|
return AIcons.description;
|
||||||
case EntryInfoAction.editRating:
|
case EntryInfoAction.editRating:
|
||||||
return AIcons.editRating;
|
return AIcons.editRating;
|
||||||
case EntryInfoAction.editTags:
|
case EntryInfoAction.editTags:
|
||||||
|
|
|
@ -31,6 +31,7 @@ enum EntrySetAction {
|
||||||
flip,
|
flip,
|
||||||
editDate,
|
editDate,
|
||||||
editLocation,
|
editLocation,
|
||||||
|
editDescription,
|
||||||
editRating,
|
editRating,
|
||||||
editTags,
|
editTags,
|
||||||
removeMetadata,
|
removeMetadata,
|
||||||
|
@ -99,6 +100,7 @@ class EntrySetActions {
|
||||||
static const edit = [
|
static const edit = [
|
||||||
EntrySetAction.editDate,
|
EntrySetAction.editDate,
|
||||||
EntrySetAction.editLocation,
|
EntrySetAction.editLocation,
|
||||||
|
EntrySetAction.editDescription,
|
||||||
EntrySetAction.editRating,
|
EntrySetAction.editRating,
|
||||||
EntrySetAction.editTags,
|
EntrySetAction.editTags,
|
||||||
EntrySetAction.removeMetadata,
|
EntrySetAction.removeMetadata,
|
||||||
|
@ -162,6 +164,8 @@ extension ExtraEntrySetAction on EntrySetAction {
|
||||||
return context.l10n.entryInfoActionEditDate;
|
return context.l10n.entryInfoActionEditDate;
|
||||||
case EntrySetAction.editLocation:
|
case EntrySetAction.editLocation:
|
||||||
return context.l10n.entryInfoActionEditLocation;
|
return context.l10n.entryInfoActionEditLocation;
|
||||||
|
case EntrySetAction.editDescription:
|
||||||
|
return context.l10n.entryInfoActionEditDescription;
|
||||||
case EntrySetAction.editRating:
|
case EntrySetAction.editRating:
|
||||||
return context.l10n.entryInfoActionEditRating;
|
return context.l10n.entryInfoActionEditRating;
|
||||||
case EntrySetAction.editTags:
|
case EntrySetAction.editTags:
|
||||||
|
@ -229,6 +233,8 @@ extension ExtraEntrySetAction on EntrySetAction {
|
||||||
return AIcons.date;
|
return AIcons.date;
|
||||||
case EntrySetAction.editLocation:
|
case EntrySetAction.editLocation:
|
||||||
return AIcons.location;
|
return AIcons.location;
|
||||||
|
case EntrySetAction.editDescription:
|
||||||
|
return AIcons.description;
|
||||||
case EntrySetAction.editRating:
|
case EntrySetAction.editRating:
|
||||||
return AIcons.editRating;
|
return AIcons.editRating;
|
||||||
case EntrySetAction.editTags:
|
case EntrySetAction.editTags:
|
||||||
|
|
|
@ -257,6 +257,8 @@ class AvesEntry {
|
||||||
|
|
||||||
bool get canEditLocation => canEdit && canEditExif;
|
bool get canEditLocation => canEdit && canEditExif;
|
||||||
|
|
||||||
|
bool get canEditDescription => canEdit && (canEditExif || canEditXmp);
|
||||||
|
|
||||||
bool get canEditRating => canEdit && canEditXmp;
|
bool get canEditRating => canEdit && canEditXmp;
|
||||||
|
|
||||||
bool get canEditTags => canEdit && canEditXmp;
|
bool get canEditTags => canEdit && canEditXmp;
|
||||||
|
|
|
@ -140,6 +140,54 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
|
||||||
return _changeOrientation(() => metadataEditService.flip(this));
|
return _changeOrientation(() => metadataEditService.flip(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// write:
|
||||||
|
// - Exif / ImageDescription
|
||||||
|
// - IPTC / caption-abstract, if IPTC exists
|
||||||
|
// - XMP / dc:description
|
||||||
|
Future<Set<EntryDataType>> editDescription(String? description) async {
|
||||||
|
final Set<EntryDataType> dataTypes = {};
|
||||||
|
final Map<MetadataType, dynamic> metadata = {};
|
||||||
|
|
||||||
|
final missingDate = await _missingDateCheckAndExifEdit(dataTypes);
|
||||||
|
|
||||||
|
if (canEditExif) {
|
||||||
|
metadata[MetadataType.exif] = {MetadataField.exifImageDescription.exifInterfaceTag!: description};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canEditIptc) {
|
||||||
|
final iptc = await metadataFetchService.getIptc(this);
|
||||||
|
if (iptc != null) {
|
||||||
|
editIptcValues(iptc, IPTC.applicationRecord, IPTC.captionAbstractTag, {if (description != null) description});
|
||||||
|
metadata[MetadataType.iptc] = iptc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canEditXmp) {
|
||||||
|
metadata[MetadataType.xmp] = await _editXmp((descriptions) {
|
||||||
|
final modified = XMP.setAttribute(
|
||||||
|
descriptions,
|
||||||
|
XMP.dcDescription,
|
||||||
|
description,
|
||||||
|
namespace: Namespaces.dc,
|
||||||
|
strat: XmpEditStrategy.always,
|
||||||
|
);
|
||||||
|
if (modified && missingDate != null) {
|
||||||
|
editCreateDateXmp(descriptions, missingDate);
|
||||||
|
}
|
||||||
|
return modified;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final newFields = await metadataEditService.editMetadata(this, metadata);
|
||||||
|
if (newFields.isNotEmpty) {
|
||||||
|
dataTypes.addAll({
|
||||||
|
EntryDataType.basic,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataTypes;
|
||||||
|
}
|
||||||
|
|
||||||
// write:
|
// write:
|
||||||
// - IPTC / keywords, if IPTC exists
|
// - IPTC / keywords, if IPTC exists
|
||||||
// - XMP / dc:subject
|
// - XMP / dc:subject
|
||||||
|
@ -152,7 +200,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
|
||||||
if (canEditIptc) {
|
if (canEditIptc) {
|
||||||
final iptc = await metadataFetchService.getIptc(this);
|
final iptc = await metadataFetchService.getIptc(this);
|
||||||
if (iptc != null) {
|
if (iptc != null) {
|
||||||
editTagsIptc(iptc, tags);
|
editIptcValues(iptc, IPTC.applicationRecord, IPTC.keywordsTag, tags);
|
||||||
metadata[MetadataType.iptc] = iptc;
|
metadata[MetadataType.iptc] = iptc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -245,9 +293,18 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void editIptcValues(List<Map<String, dynamic>> iptc, int record, int tag, Set<String> values) {
|
||||||
|
iptc.removeWhere((v) => v['record'] == record && v['tag'] == tag);
|
||||||
|
iptc.add({
|
||||||
|
'record': record,
|
||||||
|
'tag': tag,
|
||||||
|
'values': values.map((v) => utf8.encode(v)).toList(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
static void editCreateDateXmp(List<XmlNode> descriptions, DateTime? date) {
|
static bool editCreateDateXmp(List<XmlNode> descriptions, DateTime? date) {
|
||||||
XMP.setAttribute(
|
return XMP.setAttribute(
|
||||||
descriptions,
|
descriptions,
|
||||||
XMP.xmpCreateDate,
|
XMP.xmpCreateDate,
|
||||||
date != null ? XMP.toXmpDate(date) : null,
|
date != null ? XMP.toXmpDate(date) : null,
|
||||||
|
@ -256,16 +313,6 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
|
||||||
static void editTagsIptc(List<Map<String, dynamic>> iptc, Set<String> tags) {
|
|
||||||
iptc.removeWhere((v) => v['record'] == IPTC.applicationRecord && v['tag'] == IPTC.keywordsTag);
|
|
||||||
iptc.add({
|
|
||||||
'record': IPTC.applicationRecord,
|
|
||||||
'tag': IPTC.keywordsTag,
|
|
||||||
'values': tags.map((v) => utf8.encode(v)).toList(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
static bool editTagsXmp(List<XmlNode> descriptions, Set<String> tags) {
|
static bool editTagsXmp(List<XmlNode> descriptions, Set<String> tags) {
|
||||||
return XMP.setStringBag(
|
return XMP.setStringBag(
|
||||||
|
@ -366,7 +413,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DateModifier?> _applyDateModifierToEntry(DateModifier modifier) async {
|
Future<DateModifier?> _applyDateModifierToEntry(DateModifier modifier) async {
|
||||||
Set<MetadataField> mainMetadataDate() => {canEditExif ? MetadataField.exifDateOriginal : MetadataField.xmpCreateDate};
|
Set<MetadataField> mainMetadataDate() => {canEditExif ? MetadataField.exifDateOriginal : MetadataField.xmpXmpCreateDate};
|
||||||
|
|
||||||
switch (modifier.action) {
|
switch (modifier.action) {
|
||||||
case DateEditAction.copyField:
|
case DateEditAction.copyField:
|
||||||
|
|
|
@ -6,12 +6,12 @@ import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class DateModifier extends Equatable {
|
class DateModifier extends Equatable {
|
||||||
static const writableDateFields = [
|
static const writableFields = [
|
||||||
MetadataField.exifDate,
|
MetadataField.exifDate,
|
||||||
MetadataField.exifDateOriginal,
|
MetadataField.exifDateOriginal,
|
||||||
MetadataField.exifDateDigitized,
|
MetadataField.exifDateDigitized,
|
||||||
MetadataField.exifGpsDatestamp,
|
MetadataField.exifGpsDatestamp,
|
||||||
MetadataField.xmpCreateDate,
|
MetadataField.xmpXmpCreateDate,
|
||||||
];
|
];
|
||||||
|
|
||||||
final DateEditAction action;
|
final DateEditAction action;
|
||||||
|
|
|
@ -36,7 +36,8 @@ enum MetadataField {
|
||||||
exifGpsTrack,
|
exifGpsTrack,
|
||||||
exifGpsTrackRef,
|
exifGpsTrackRef,
|
||||||
exifGpsVersionId,
|
exifGpsVersionId,
|
||||||
xmpCreateDate,
|
exifImageDescription,
|
||||||
|
xmpXmpCreateDate,
|
||||||
}
|
}
|
||||||
|
|
||||||
class MetadataFields {
|
class MetadataFields {
|
||||||
|
@ -114,8 +115,9 @@ extension ExtraMetadataField on MetadataField {
|
||||||
case MetadataField.exifGpsTrack:
|
case MetadataField.exifGpsTrack:
|
||||||
case MetadataField.exifGpsTrackRef:
|
case MetadataField.exifGpsTrackRef:
|
||||||
case MetadataField.exifGpsVersionId:
|
case MetadataField.exifGpsVersionId:
|
||||||
|
case MetadataField.exifImageDescription:
|
||||||
return MetadataType.exif;
|
return MetadataType.exif;
|
||||||
case MetadataField.xmpCreateDate:
|
case MetadataField.xmpXmpCreateDate:
|
||||||
return MetadataType.xmp;
|
return MetadataType.xmp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,8 +194,27 @@ extension ExtraMetadataField on MetadataField {
|
||||||
return 'GPSTrackRef';
|
return 'GPSTrackRef';
|
||||||
case MetadataField.exifGpsVersionId:
|
case MetadataField.exifGpsVersionId:
|
||||||
return 'GPSVersionID';
|
return 'GPSVersionID';
|
||||||
case MetadataField.xmpCreateDate:
|
case MetadataField.exifImageDescription:
|
||||||
|
return 'ImageDescription';
|
||||||
|
case MetadataField.xmpXmpCreateDate:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get title {
|
||||||
|
switch (this) {
|
||||||
|
case MetadataField.exifDate:
|
||||||
|
return 'Exif date';
|
||||||
|
case MetadataField.exifDateOriginal:
|
||||||
|
return 'Exif original date';
|
||||||
|
case MetadataField.exifDateDigitized:
|
||||||
|
return 'Exif digitized date';
|
||||||
|
case MetadataField.exifGpsDatestamp:
|
||||||
|
return 'Exif GPS date';
|
||||||
|
case MetadataField.xmpXmpCreateDate:
|
||||||
|
return 'XMP xmp:CreateDate';
|
||||||
|
default:
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,4 +3,5 @@ class IPTC {
|
||||||
|
|
||||||
// ApplicationRecord tags
|
// ApplicationRecord tags
|
||||||
static const int keywordsTag = 25;
|
static const int keywordsTag = 25;
|
||||||
|
static const int captionAbstractTag = 120;
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,8 @@ abstract class MetadataFetchService {
|
||||||
Future<String?> getContentResolverProp(AvesEntry entry, String prop);
|
Future<String?> getContentResolverProp(AvesEntry entry, String prop);
|
||||||
|
|
||||||
Future<DateTime?> getDate(AvesEntry entry, MetadataField field);
|
Future<DateTime?> getDate(AvesEntry entry, MetadataField field);
|
||||||
|
|
||||||
|
Future<String?> getDescription(AvesEntry entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformMetadataFetchService implements MetadataFetchService {
|
class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
|
@ -75,7 +77,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
// 'latitude': latitude (double)
|
// 'latitude': latitude (double)
|
||||||
// 'longitude': longitude (double)
|
// 'longitude': longitude (double)
|
||||||
// 'xmpSubjects': ';' separated XMP subjects (string)
|
// 'xmpSubjects': ';' separated XMP subjects (string)
|
||||||
// 'xmpTitleDescription': XMP title or XMP description (string)
|
// 'xmpTitleDescription': XMP title (string)
|
||||||
final result = await _platform.invokeMethod('getCatalogMetadata', <String, dynamic>{
|
final result = await _platform.invokeMethod('getCatalogMetadata', <String, dynamic>{
|
||||||
'mimeType': entry.mimeType,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
|
@ -268,4 +270,20 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> getDescription(AvesEntry entry) async {
|
||||||
|
try {
|
||||||
|
return await _platform.invokeMethod('getDescription', <String, dynamic>{
|
||||||
|
'mimeType': entry.mimeType,
|
||||||
|
'uri': entry.uri,
|
||||||
|
'sizeBytes': entry.sizeBytes,
|
||||||
|
});
|
||||||
|
} on PlatformException catch (e, stack) {
|
||||||
|
if (!entry.isMissingAtPath) {
|
||||||
|
await reportService.recordError(e, stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ class AIcons {
|
||||||
static const IconData checked = Icons.done_outlined;
|
static const IconData checked = Icons.done_outlined;
|
||||||
static const IconData counter = Icons.plus_one_outlined;
|
static const IconData counter = Icons.plus_one_outlined;
|
||||||
static const IconData date = Icons.calendar_today_outlined;
|
static const IconData date = Icons.calendar_today_outlined;
|
||||||
|
static const IconData description = Icons.description_outlined;
|
||||||
static const IconData disc = Icons.fiber_manual_record;
|
static const IconData disc = Icons.fiber_manual_record;
|
||||||
static const IconData display = Icons.light_mode_outlined;
|
static const IconData display = Icons.light_mode_outlined;
|
||||||
static const IconData error = Icons.error_outline;
|
static const IconData error = Icons.error_outline;
|
||||||
|
|
|
@ -35,6 +35,7 @@ class XMP {
|
||||||
static const rdfRoot = 'RDF';
|
static const rdfRoot = 'RDF';
|
||||||
static const rdfDescription = 'Description';
|
static const rdfDescription = 'Description';
|
||||||
static const containerDirectory = 'Directory';
|
static const containerDirectory = 'Directory';
|
||||||
|
static const dcDescription = 'description';
|
||||||
static const dcSubject = 'subject';
|
static const dcSubject = 'subject';
|
||||||
static const msPhotoRating = 'Rating';
|
static const msPhotoRating = 'Rating';
|
||||||
static const xmpRating = 'Rating';
|
static const xmpRating = 'Rating';
|
||||||
|
|
|
@ -496,6 +496,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
case EntrySetAction.flip:
|
case EntrySetAction.flip:
|
||||||
case EntrySetAction.editDate:
|
case EntrySetAction.editDate:
|
||||||
case EntrySetAction.editLocation:
|
case EntrySetAction.editLocation:
|
||||||
|
case EntrySetAction.editDescription:
|
||||||
case EntrySetAction.editRating:
|
case EntrySetAction.editRating:
|
||||||
case EntrySetAction.editTags:
|
case EntrySetAction.editTags:
|
||||||
case EntrySetAction.removeMetadata:
|
case EntrySetAction.removeMetadata:
|
||||||
|
|
|
@ -92,6 +92,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
case EntrySetAction.flip:
|
case EntrySetAction.flip:
|
||||||
case EntrySetAction.editDate:
|
case EntrySetAction.editDate:
|
||||||
case EntrySetAction.editLocation:
|
case EntrySetAction.editLocation:
|
||||||
|
case EntrySetAction.editDescription:
|
||||||
case EntrySetAction.editRating:
|
case EntrySetAction.editRating:
|
||||||
case EntrySetAction.editTags:
|
case EntrySetAction.editTags:
|
||||||
case EntrySetAction.removeMetadata:
|
case EntrySetAction.removeMetadata:
|
||||||
|
@ -143,6 +144,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
case EntrySetAction.flip:
|
case EntrySetAction.flip:
|
||||||
case EntrySetAction.editDate:
|
case EntrySetAction.editDate:
|
||||||
case EntrySetAction.editLocation:
|
case EntrySetAction.editLocation:
|
||||||
|
case EntrySetAction.editDescription:
|
||||||
case EntrySetAction.editRating:
|
case EntrySetAction.editRating:
|
||||||
case EntrySetAction.editTags:
|
case EntrySetAction.editTags:
|
||||||
case EntrySetAction.removeMetadata:
|
case EntrySetAction.removeMetadata:
|
||||||
|
@ -219,6 +221,9 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
case EntrySetAction.editLocation:
|
case EntrySetAction.editLocation:
|
||||||
_editLocation(context);
|
_editLocation(context);
|
||||||
break;
|
break;
|
||||||
|
case EntrySetAction.editDescription:
|
||||||
|
_editDescription(context);
|
||||||
|
break;
|
||||||
case EntrySetAction.editRating:
|
case EntrySetAction.editRating:
|
||||||
_editRating(context);
|
_editRating(context);
|
||||||
break;
|
break;
|
||||||
|
@ -490,6 +495,16 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
await _edit(context, entries, (entry) => entry.editLocation(location));
|
await _edit(context, entries, (entry) => entry.editLocation(location));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _editDescription(BuildContext context) async {
|
||||||
|
final entries = await _getEditableTargetItems(context, canEdit: (entry) => entry.canEditDescription);
|
||||||
|
if (entries == null || entries.isEmpty) return;
|
||||||
|
|
||||||
|
final description = await selectDescription(context, entries);
|
||||||
|
if (description == null) return;
|
||||||
|
|
||||||
|
await _edit(context, entries, (entry) => entry.editDescription(description));
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _editRating(BuildContext context) async {
|
Future<void> _editRating(BuildContext context) async {
|
||||||
final entries = await _getEditableTargetItems(context, canEdit: (entry) => entry.canEditRating);
|
final entries = await _getEditableTargetItems(context, canEdit: (entry) => entry.canEditRating);
|
||||||
if (entries == null || entries.isEmpty) return;
|
if (entries == null || entries.isEmpty) return;
|
||||||
|
|
|
@ -3,9 +3,11 @@ import 'package:aves/model/metadata/date_modifier.dart';
|
||||||
import 'package:aves/model/metadata/enums.dart';
|
import 'package:aves/model/metadata/enums.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/entry_editors/edit_date_dialog.dart';
|
import 'package:aves/widgets/dialogs/entry_editors/edit_date_dialog.dart';
|
||||||
|
import 'package:aves/widgets/dialogs/entry_editors/edit_description_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/entry_editors/edit_location_dialog.dart';
|
import 'package:aves/widgets/dialogs/entry_editors/edit_location_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/entry_editors/edit_rating_dialog.dart';
|
import 'package:aves/widgets/dialogs/entry_editors/edit_rating_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/entry_editors/edit_tags_dialog.dart';
|
import 'package:aves/widgets/dialogs/entry_editors/edit_tags_dialog.dart';
|
||||||
|
@ -17,39 +19,49 @@ mixin EntryEditorMixin {
|
||||||
Future<DateModifier?> selectDateModifier(BuildContext context, Set<AvesEntry> entries, CollectionLens? collection) async {
|
Future<DateModifier?> selectDateModifier(BuildContext context, Set<AvesEntry> entries, CollectionLens? collection) async {
|
||||||
if (entries.isEmpty) return null;
|
if (entries.isEmpty) return null;
|
||||||
|
|
||||||
final modifier = await showDialog<DateModifier>(
|
return showDialog<DateModifier>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => EditEntryDateDialog(
|
builder: (context) => EditEntryDateDialog(
|
||||||
entry: entries.first,
|
entry: entries.first,
|
||||||
collection: collection,
|
collection: collection,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return modifier;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<LatLng?> selectLocation(BuildContext context, Set<AvesEntry> entries, CollectionLens? collection) async {
|
Future<LatLng?> selectLocation(BuildContext context, Set<AvesEntry> entries, CollectionLens? collection) async {
|
||||||
if (entries.isEmpty) return null;
|
if (entries.isEmpty) return null;
|
||||||
|
|
||||||
final location = await showDialog<LatLng>(
|
return showDialog<LatLng>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => EditEntryLocationDialog(
|
builder: (context) => EditEntryLocationDialog(
|
||||||
entry: entries.first,
|
entry: entries.first,
|
||||||
collection: collection,
|
collection: collection,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return location;
|
}
|
||||||
|
|
||||||
|
Future<String?> selectDescription(BuildContext context, Set<AvesEntry> entries) async {
|
||||||
|
if (entries.isEmpty) return null;
|
||||||
|
|
||||||
|
final initialDescription = await metadataFetchService.getDescription(entries.first) ?? '';
|
||||||
|
|
||||||
|
return showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => EditEntryDescriptionDialog(
|
||||||
|
initialDescription: initialDescription,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int?> selectRating(BuildContext context, Set<AvesEntry> entries) async {
|
Future<int?> selectRating(BuildContext context, Set<AvesEntry> entries) async {
|
||||||
if (entries.isEmpty) return null;
|
if (entries.isEmpty) return null;
|
||||||
|
|
||||||
final rating = await showDialog<int>(
|
return showDialog<int>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => EditEntryRatingDialog(
|
builder: (context) => EditEntryRatingDialog(
|
||||||
entry: entries.first,
|
entry: entries.first,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return rating;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<AvesEntry, Set<String>>?> selectTags(BuildContext context, Set<AvesEntry> entries) async {
|
Future<Map<AvesEntry, Set<String>>?> selectTags(BuildContext context, Set<AvesEntry> entries) async {
|
||||||
|
|
|
@ -38,7 +38,7 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
||||||
late ValueNotifier<int> _shiftHour, _shiftMinute;
|
late ValueNotifier<int> _shiftHour, _shiftMinute;
|
||||||
late ValueNotifier<String> _shiftSign;
|
late ValueNotifier<String> _shiftSign;
|
||||||
bool _showOptions = false;
|
bool _showOptions = false;
|
||||||
final Set<MetadataField> _fields = {...DateModifier.writableDateFields};
|
final Set<MetadataField> _fields = {...DateModifier.writableFields};
|
||||||
|
|
||||||
DateTime get copyItemDate => _copyItemSource.bestDate ?? DateTime.now();
|
DateTime get copyItemDate => _copyItemSource.bestDate ?? DateTime.now();
|
||||||
|
|
||||||
|
@ -276,14 +276,14 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
||||||
children: [
|
children: [
|
||||||
ExpansionPanel(
|
ExpansionPanel(
|
||||||
headerBuilder: (context, isExpanded) => ListTile(
|
headerBuilder: (context, isExpanded) => ListTile(
|
||||||
title: Text(context.l10n.editEntryDateDialogTargetFieldsHeader),
|
title: Text(context.l10n.editEntryDialogTargetFieldsHeader),
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: DateModifier.writableDateFields
|
children: DateModifier.writableFields
|
||||||
.map((field) => SwitchListTile(
|
.map((field) => SwitchListTile(
|
||||||
value: _fields.contains(field),
|
value: _fields.contains(field),
|
||||||
onChanged: (selected) => setState(() => selected ? _fields.add(field) : _fields.remove(field)),
|
onChanged: (selected) => setState(() => selected ? _fields.add(field) : _fields.remove(field)),
|
||||||
title: Text(_fieldTitle(field)),
|
title: Text(field.title),
|
||||||
))
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
|
@ -330,23 +330,6 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _fieldTitle(MetadataField field) {
|
|
||||||
switch (field) {
|
|
||||||
case MetadataField.exifDate:
|
|
||||||
return 'Exif date';
|
|
||||||
case MetadataField.exifDateOriginal:
|
|
||||||
return 'Exif original date';
|
|
||||||
case MetadataField.exifDateDigitized:
|
|
||||||
return 'Exif digitized date';
|
|
||||||
case MetadataField.exifGpsDatestamp:
|
|
||||||
return 'Exif GPS date';
|
|
||||||
case MetadataField.xmpCreateDate:
|
|
||||||
return 'XMP xmp:CreateDate';
|
|
||||||
default:
|
|
||||||
return field.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _editDate() async {
|
Future<void> _editDate() async {
|
||||||
final _date = await showDatePicker(
|
final _date = await showDatePicker(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
|
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class EditEntryDescriptionDialog extends StatefulWidget {
|
||||||
|
final String initialDescription;
|
||||||
|
|
||||||
|
const EditEntryDescriptionDialog({
|
||||||
|
super.key,
|
||||||
|
required this.initialDescription,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<EditEntryDescriptionDialog> createState() => _EditEntryDescriptionDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EditEntryDescriptionDialogState extends State<EditEntryDescriptionDialog> {
|
||||||
|
late final TextEditingController _textController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_textController = TextEditingController(text: widget.initialDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MediaQueryDataProvider(
|
||||||
|
child: Builder(builder: (context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
|
||||||
|
return AvesDialog(
|
||||||
|
title: l10n.editEntryDescriptionDialogTitle,
|
||||||
|
content: TextField(
|
||||||
|
controller: _textController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
maxLines: null,
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => _submit(context),
|
||||||
|
child: Text(l10n.applyButtonLabel),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _submit(BuildContext context) => Navigator.pop(context, _textController.text);
|
||||||
|
}
|
|
@ -79,7 +79,7 @@ class _EditEntryLocationDialogState extends State<EditEntryLocationDialog> {
|
||||||
controller: _latitudeController,
|
controller: _latitudeController,
|
||||||
focusNode: _latitudeFocusNode,
|
focusNode: _latitudeFocusNode,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: context.l10n.editEntryLocationDialogLatitude,
|
labelText: l10n.editEntryLocationDialogLatitude,
|
||||||
hintText: coordinateFormatter.format(Constants.pointNemo.latitude),
|
hintText: coordinateFormatter.format(Constants.pointNemo.latitude),
|
||||||
),
|
),
|
||||||
onChanged: (_) => _validate(),
|
onChanged: (_) => _validate(),
|
||||||
|
@ -88,7 +88,7 @@ class _EditEntryLocationDialogState extends State<EditEntryLocationDialog> {
|
||||||
controller: _longitudeController,
|
controller: _longitudeController,
|
||||||
focusNode: _longitudeFocusNode,
|
focusNode: _longitudeFocusNode,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: context.l10n.editEntryLocationDialogLongitude,
|
labelText: l10n.editEntryLocationDialogLongitude,
|
||||||
hintText: coordinateFormatter.format(Constants.pointNemo.longitude),
|
hintText: coordinateFormatter.format(Constants.pointNemo.longitude),
|
||||||
),
|
),
|
||||||
onChanged: (_) => _validate(),
|
onChanged: (_) => _validate(),
|
||||||
|
@ -131,7 +131,7 @@ class _EditEntryLocationDialogState extends State<EditEntryLocationDialog> {
|
||||||
builder: (context, isValid, child) {
|
builder: (context, isValid, child) {
|
||||||
return TextButton(
|
return TextButton(
|
||||||
onPressed: isValid ? () => _submit(context) : null,
|
onPressed: isValid ? () => _submit(context) : null,
|
||||||
child: Text(context.l10n.applyButtonLabel),
|
child: Text(l10n.applyButtonLabel),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -35,6 +35,7 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
|
||||||
// general
|
// general
|
||||||
case EntryInfoAction.editDate:
|
case EntryInfoAction.editDate:
|
||||||
case EntryInfoAction.editLocation:
|
case EntryInfoAction.editLocation:
|
||||||
|
case EntryInfoAction.editDescription:
|
||||||
case EntryInfoAction.editRating:
|
case EntryInfoAction.editRating:
|
||||||
case EntryInfoAction.editTags:
|
case EntryInfoAction.editTags:
|
||||||
case EntryInfoAction.removeMetadata:
|
case EntryInfoAction.removeMetadata:
|
||||||
|
@ -59,6 +60,8 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
|
||||||
return entry.canEditDate;
|
return entry.canEditDate;
|
||||||
case EntryInfoAction.editLocation:
|
case EntryInfoAction.editLocation:
|
||||||
return entry.canEditLocation;
|
return entry.canEditLocation;
|
||||||
|
case EntryInfoAction.editDescription:
|
||||||
|
return entry.canEditDescription;
|
||||||
case EntryInfoAction.editRating:
|
case EntryInfoAction.editRating:
|
||||||
return entry.canEditRating;
|
return entry.canEditRating;
|
||||||
case EntryInfoAction.editTags:
|
case EntryInfoAction.editTags:
|
||||||
|
@ -89,6 +92,9 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
|
||||||
case EntryInfoAction.editLocation:
|
case EntryInfoAction.editLocation:
|
||||||
await _editLocation(context);
|
await _editLocation(context);
|
||||||
break;
|
break;
|
||||||
|
case EntryInfoAction.editDescription:
|
||||||
|
await _editDescription(context);
|
||||||
|
break;
|
||||||
case EntryInfoAction.editRating:
|
case EntryInfoAction.editRating:
|
||||||
await _editRating(context);
|
await _editRating(context);
|
||||||
break;
|
break;
|
||||||
|
@ -131,6 +137,13 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
|
||||||
await edit(context, () => entry.editLocation(location));
|
await edit(context, () => entry.editLocation(location));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _editDescription(BuildContext context) async {
|
||||||
|
final description = await selectDescription(context, {entry});
|
||||||
|
if (description == null) return;
|
||||||
|
|
||||||
|
await edit(context, () => entry.editDescription(description));
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _editRating(BuildContext context) async {
|
Future<void> _editRating(BuildContext context) async {
|
||||||
final rating = await selectRating(context, {entry});
|
final rating = await selectRating(context, {entry});
|
||||||
if (rating == null) return;
|
if (rating == null) return;
|
||||||
|
|
|
@ -1,43 +1,61 @@
|
||||||
{
|
{
|
||||||
"de": [
|
"de": [
|
||||||
|
"entryInfoActionEditDescription",
|
||||||
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext"
|
"settingsViewerGestureSideTapNext"
|
||||||
],
|
],
|
||||||
|
|
||||||
"es": [
|
"es": [
|
||||||
|
"entryInfoActionEditDescription",
|
||||||
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems"
|
"settingsConfirmationAfterMoveToBinItems"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
|
"entryInfoActionEditDescription",
|
||||||
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsViewerGestureSideTapNext"
|
"settingsViewerGestureSideTapNext"
|
||||||
],
|
],
|
||||||
|
|
||||||
"id": [
|
"id": [
|
||||||
|
"entryInfoActionEditDescription",
|
||||||
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext"
|
"settingsViewerGestureSideTapNext"
|
||||||
],
|
],
|
||||||
|
|
||||||
"it": [
|
"it": [
|
||||||
|
"entryInfoActionEditDescription",
|
||||||
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext"
|
"settingsViewerGestureSideTapNext"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ja": [
|
"ja": [
|
||||||
|
"entryInfoActionEditDescription",
|
||||||
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext"
|
"settingsViewerGestureSideTapNext"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ko": [
|
"ko": [
|
||||||
|
"entryInfoActionEditDescription",
|
||||||
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsViewerGestureSideTapNext"
|
"settingsViewerGestureSideTapNext"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
|
"entryInfoActionEditDescription",
|
||||||
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext"
|
"settingsViewerGestureSideTapNext"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
|
"entryInfoActionEditDescription",
|
||||||
"filterOnThisDayLabel",
|
"filterOnThisDayLabel",
|
||||||
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext",
|
"settingsViewerGestureSideTapNext",
|
||||||
"settingsSlideshowFillScreen",
|
"settingsSlideshowFillScreen",
|
||||||
|
@ -49,6 +67,7 @@
|
||||||
"tr": [
|
"tr": [
|
||||||
"slideshowActionResume",
|
"slideshowActionResume",
|
||||||
"slideshowActionShowInCollection",
|
"slideshowActionShowInCollection",
|
||||||
|
"entryInfoActionEditDescription",
|
||||||
"filterOnThisDayLabel",
|
"filterOnThisDayLabel",
|
||||||
"slideshowVideoPlaybackSkip",
|
"slideshowVideoPlaybackSkip",
|
||||||
"slideshowVideoPlaybackMuted",
|
"slideshowVideoPlaybackMuted",
|
||||||
|
@ -60,6 +79,7 @@
|
||||||
"wallpaperTargetHome",
|
"wallpaperTargetHome",
|
||||||
"wallpaperTargetLock",
|
"wallpaperTargetLock",
|
||||||
"wallpaperTargetHomeLock",
|
"wallpaperTargetHomeLock",
|
||||||
|
"editEntryDescriptionDialogTitle",
|
||||||
"menuActionSlideshow",
|
"menuActionSlideshow",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext",
|
"settingsViewerGestureSideTapNext",
|
||||||
|
@ -81,6 +101,8 @@
|
||||||
],
|
],
|
||||||
|
|
||||||
"zh": [
|
"zh": [
|
||||||
|
"entryInfoActionEditDescription",
|
||||||
|
"editEntryDescriptionDialogTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext"
|
"settingsViewerGestureSideTapNext"
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in a new issue