diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3c51c196f..bdc15ca9e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,31 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+## [v1.6.3] - 2022-03-28
+
+### Added
+
+- Theme: light/dark/black and color highlights settings
+- Collection: bulk renaming
+- Video: speed and muted state indicators
+- Info: option to set date from other item
+- Info: improved DNG tags display
+- warn and optionally set metadata date before moving undated items
+- Settings: display refresh rate hint
+
+### Changed
+
+- Viewer: quick action defaults
+- cataloguing includes date sub-second data if present (requires rescan)
+
+### Removed
+
+- metadata editing support for DNG
+
+### Fixed
+
+- app launch despite faulty storage volumes on Android 11+
+
## [v1.6.2] - 2022-03-07
### Added
diff --git a/android/app/build.gradle b/android/app/build.gradle
index ccdb492b5..7b9253ca2 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -147,7 +147,7 @@ dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'com.caverock:androidsvg-aar:1.4'
implementation 'com.commonsware.cwac:document:0.4.1'
- implementation 'com.drewnoakes:metadata-extractor:2.16.0'
+ implementation 'com.drewnoakes:metadata-extractor:2.17.0'
// forked, built by JitPack, cf https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
implementation 'com.github.deckerst:Android-TiffBitmapFactory:876e53870a'
// forked, built by JitPack, cf https://jitpack.io/p/deckerst/pixymeta-android
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt
index f90d01668..13f1d7800 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt
@@ -164,11 +164,18 @@ class MainActivity : FlutterActivity() {
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- // save access permissions across reboots
- val takeFlags = (data.flags
- and (Intent.FLAG_GRANT_READ_URI_PERMISSION
- or Intent.FLAG_GRANT_WRITE_URI_PERMISSION))
- contentResolver.takePersistableUriPermission(treeUri, takeFlags)
+ val canPersist = (data.flags and Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0
+ if (canPersist) {
+ // save access permissions across reboots
+ val takeFlags = (data.flags
+ and (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ or Intent.FLAG_GRANT_WRITE_URI_PERMISSION))
+ try {
+ contentResolver.takePersistableUriPermission(treeUri, takeFlags)
+ } catch (e: SecurityException) {
+ Log.w(LOG_TAG, "failed to take persistable URI permission for uri=$treeUri", e)
+ }
+ }
}
// resume pending action
@@ -201,9 +208,11 @@ class MainActivity : FlutterActivity() {
}
Intent.ACTION_VIEW, Intent.ACTION_SEND, "com.android.camera.action.REVIEW" -> {
(intent.data ?: (intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri))?.let { uri ->
+ // MIME type is optional
+ val type = intent.type ?: intent.resolveType(context)
return hashMapOf(
INTENT_DATA_KEY_ACTION to INTENT_ACTION_VIEW,
- INTENT_DATA_KEY_MIME_TYPE to intent.type, // MIME type is optional
+ INTENT_DATA_KEY_MIME_TYPE to type,
INTENT_DATA_KEY_URI to uri.toString(),
)
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt
index 82c84520e..6f555b18e 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt
@@ -33,7 +33,10 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.util.PathUtils
-import kotlinx.coroutines.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
import org.beyka.tiffbitmapfactory.TiffBitmapFactory
import java.io.IOException
@@ -84,7 +87,7 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
}
}.mapValues { it.value?.path }.toMutableMap()
dirs["externalCacheDirs"] = context.externalCacheDirs.joinToString { it.path }
- dirs["externalFilesDirs"] = context.getExternalFilesDirs(null).joinToString { it.path }
+ dirs["externalFilesDirs"] = context.getExternalFilesDirs(null).joinToString { it?.path ?: "null" }
// used by flutter plugin `path_provider`
dirs.putAll(
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt
index e70895276..2d4b722c2 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt
@@ -47,6 +47,9 @@ import deckers.thibault.aves.metadata.MetadataExtractorHelper.PNG_ITXT_DIR_NAME
import deckers.thibault.aves.metadata.MetadataExtractorHelper.PNG_LAST_MODIFICATION_TIME_FORMAT
import deckers.thibault.aves.metadata.MetadataExtractorHelper.PNG_TIME_DIR_NAME
import deckers.thibault.aves.metadata.MetadataExtractorHelper.extractPngProfile
+import deckers.thibault.aves.metadata.MetadataExtractorHelper.getDateDigitizedMillis
+import deckers.thibault.aves.metadata.MetadataExtractorHelper.getDateModifiedMillis
+import deckers.thibault.aves.metadata.MetadataExtractorHelper.getDateOriginalMillis
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeBoolean
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeDateMillis
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeInt
@@ -74,7 +77,10 @@ import deckers.thibault.aves.utils.UriUtils.tryParseId
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
-import kotlinx.coroutines.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.text.ParseException
@@ -163,15 +169,24 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
// tags
val tags = dir.tags
if (dir is ExifDirectoryBase) {
- if (dir.isGeoTiff()) {
- // split GeoTIFF tags in their own directory
- val byGeoTiff = tags.groupBy { ExifTags.isGeoTiffTag(it.tagType) }
- metadataMap["GeoTIFF"] = HashMap().apply {
- byGeoTiff[true]?.map { exifTagMapper(it) }?.let { putAll(it) }
+ when {
+ dir.isGeoTiff() -> {
+ // split GeoTIFF tags in their own directory
+ val geoTiffDirMap = metadataMap["GeoTIFF"] ?: HashMap()
+ metadataMap["GeoTIFF"] = geoTiffDirMap
+ val byGeoTiff = tags.groupBy { ExifTags.isGeoTiffTag(it.tagType) }
+ byGeoTiff[true]?.map { exifTagMapper(it) }?.let { geoTiffDirMap.putAll(it) }
+ byGeoTiff[false]?.map { exifTagMapper(it) }?.let { dirMap.putAll(it) }
}
- byGeoTiff[false]?.map { exifTagMapper(it) }?.let { dirMap.putAll(it) }
- } else {
- dirMap.putAll(tags.map { exifTagMapper(it) })
+ mimeType == MimeTypes.DNG -> {
+ // split DNG tags in their own directory
+ val dngDirMap = metadataMap["DNG"] ?: HashMap()
+ metadataMap["DNG"] = dngDirMap
+ val byDng = tags.groupBy { ExifTags.isDngTag(it.tagType) }
+ byDng[true]?.map { exifTagMapper(it) }?.let { dngDirMap.putAll(it) }
+ byDng[false]?.map { exifTagMapper(it) }?.let { dirMap.putAll(it) }
+ }
+ else -> dirMap.putAll(tags.map { exifTagMapper(it) })
}
} else if (dir.isPngTextDir()) {
metadataMap.remove(thisDirName)
@@ -432,11 +447,17 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
// EXIF
for (dir in metadata.getDirectoriesOfType(ExifSubIFDDirectory::class.java)) {
- dir.getSafeDateMillis(ExifDirectoryBase.TAG_DATETIME_ORIGINAL) { metadataMap[KEY_DATE_MILLIS] = it }
+ dir.getDateOriginalMillis { metadataMap[KEY_DATE_MILLIS] = it }
+ if (!metadataMap.containsKey(KEY_DATE_MILLIS)) {
+ // fetch date modified from SubIFD directory first, as the sub-second tag is here
+ dir.getDateModifiedMillis { metadataMap[KEY_DATE_MILLIS] = it }
+ }
}
for (dir in metadata.getDirectoriesOfType(ExifIFD0Directory::class.java)) {
if (!metadataMap.containsKey(KEY_DATE_MILLIS)) {
- dir.getSafeDateMillis(ExifDirectoryBase.TAG_DATETIME) { metadataMap[KEY_DATE_MILLIS] = it }
+ // fallback to fetch date modified from IFD0 directory, without the sub-second tag
+ // in case there was no SubIFD directory
+ dir.getSafeDateMillis(ExifIFD0Directory.TAG_DATETIME, null)?.let { metadataMap[KEY_DATE_MILLIS] = it }
}
dir.getSafeInt(ExifDirectoryBase.TAG_ORIENTATION) {
val orientation = it
@@ -560,9 +581,9 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val exif = ExifInterface(input)
- exif.getSafeDateMillis(ExifInterface.TAG_DATETIME_ORIGINAL) { metadataMap[KEY_DATE_MILLIS] = it }
+ exif.getSafeDateMillis(ExifInterface.TAG_DATETIME_ORIGINAL, ExifInterface.TAG_SUBSEC_TIME_ORIGINAL) { metadataMap[KEY_DATE_MILLIS] = it }
if (!metadataMap.containsKey(KEY_DATE_MILLIS)) {
- exif.getSafeDateMillis(ExifInterface.TAG_DATETIME) { metadataMap[KEY_DATE_MILLIS] = it }
+ exif.getSafeDateMillis(ExifInterface.TAG_DATETIME, ExifInterface.TAG_SUBSEC_TIME) { metadataMap[KEY_DATE_MILLIS] = it }
}
exif.getSafeInt(ExifInterface.TAG_ORIENTATION, acceptZero = false) {
if (exif.isFlipped) flags = flags or MASK_IS_FLIPPED
@@ -901,9 +922,9 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = ImageMetadataReader.readMetadata(input)
val tag = when (field) {
- ExifInterface.TAG_DATETIME -> ExifDirectoryBase.TAG_DATETIME
- ExifInterface.TAG_DATETIME_DIGITIZED -> ExifDirectoryBase.TAG_DATETIME_DIGITIZED
- ExifInterface.TAG_DATETIME_ORIGINAL -> ExifDirectoryBase.TAG_DATETIME_ORIGINAL
+ ExifInterface.TAG_DATETIME -> ExifIFD0Directory.TAG_DATETIME
+ ExifInterface.TAG_DATETIME_DIGITIZED -> ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED
+ ExifInterface.TAG_DATETIME_ORIGINAL -> ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL
ExifInterface.TAG_GPS_DATESTAMP -> GpsDirectory.TAG_DATE_STAMP
else -> {
result.error("getDate-field", "unsupported ExifInterface field=$field", null)
@@ -912,11 +933,24 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
}
when (tag) {
- ExifDirectoryBase.TAG_DATETIME,
- ExifDirectoryBase.TAG_DATETIME_DIGITIZED,
- ExifDirectoryBase.TAG_DATETIME_ORIGINAL -> {
- for (dir in metadata.getDirectoriesOfType(ExifDirectoryBase::class.java)) {
- dir.getSafeDateMillis(tag) { dateMillis = it }
+ ExifIFD0Directory.TAG_DATETIME -> {
+ for (dir in metadata.getDirectoriesOfType(ExifSubIFDDirectory::class.java)) {
+ dir.getDateModifiedMillis { dateMillis = it }
+ }
+ if (dateMillis == null) {
+ for (dir in metadata.getDirectoriesOfType(ExifIFD0Directory::class.java)) {
+ dir.getSafeDateMillis(ExifIFD0Directory.TAG_DATETIME, null)?.let { dateMillis = it }
+ }
+ }
+ }
+ ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED -> {
+ for (dir in metadata.getDirectoriesOfType(ExifSubIFDDirectory::class.java)) {
+ dir.getDateDigitizedMillis { dateMillis = it }
+ }
+ }
+ ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL -> {
+ for (dir in metadata.getDirectoriesOfType(ExifSubIFDDirectory::class.java)) {
+ dir.getDateOriginalMillis { dateMillis = it }
}
}
GpsDirectory.TAG_DATE_STAMP -> {
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageOpStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageOpStreamHandler.kt
index 7a8f02ae1..568b0be85 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageOpStreamHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageOpStreamHandler.kt
@@ -199,27 +199,34 @@ class ImageOpStreamHandler(private val activity: Activity, private val arguments
}
private suspend fun rename() {
- if (arguments !is Map<*, *> || entryMapList.isEmpty()) {
+ if (arguments !is Map<*, *>) {
endOfStream()
return
}
- val newName = arguments["newName"] as String?
- if (newName == null) {
+ val rawEntryMap = arguments["entriesToNewName"] as Map<*, *>?
+ if (rawEntryMap == null || rawEntryMap.isEmpty()) {
error("rename-args", "failed because of missing arguments", null)
return
}
+ val entriesToNewName = HashMap()
+ rawEntryMap.forEach {
+ @Suppress("unchecked_cast")
+ val rawEntry = it.key as FieldMap
+ val newName = it.value as String
+ entriesToNewName[AvesEntry(rawEntry)] = newName
+ }
+
// assume same provider for all entries
- val firstEntry = entryMapList.first()
- val provider = (firstEntry["uri"] as String?)?.let { Uri.parse(it) }?.let { getProvider(it) }
+ val firstEntry = entriesToNewName.keys.first()
+ val provider = getProvider(firstEntry.uri)
if (provider == null) {
error("rename-provider", "failed to find provider for entry=$firstEntry", null)
return
}
- val entries = entryMapList.map(::AvesEntry)
- provider.renameMultiple(activity, newName, entries, ::isCancelledOp, object : ImageOpCallback {
+ provider.renameMultiple(activity, entriesToNewName, ::isCancelledOp, object : ImageOpCallback {
override fun onSuccess(fields: FieldMap) = success(fields)
override fun onFailure(throwable: Throwable) = error("rename-failure", "failed to rename", throwable.message)
})
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.kt
index 0bc307f92..1e742403c 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.kt
@@ -13,6 +13,7 @@ import deckers.thibault.aves.PendingStorageAccessResultHandler
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.PermissionManager
+import deckers.thibault.aves.utils.StorageUtils
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.EventChannel.EventSink
import kotlinx.coroutines.CoroutineScope
@@ -80,6 +81,11 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any?
return
}
+ if (uris.any { !StorageUtils.isMediaStoreContentUri(it) }) {
+ error("requestMediaFileAccess-nonmediastore", "request is only valid for Media Store content URIs, uris=$uris", null)
+ return
+ }
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
error("requestMediaFileAccess-unsupported", "media file bulk access is not allowed before Android R", null)
return
@@ -148,12 +154,17 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any?
fun onGranted(uri: Uri) {
ioScope.launch {
- activity.contentResolver.openInputStream(uri)?.use { input ->
- val buffer = ByteArray(BUFFER_SIZE)
- var len: Int
- while (input.read(buffer).also { len = it } != -1) {
- success(buffer.copyOf(len))
+ try {
+ activity.contentResolver.openInputStream(uri)?.use { input ->
+ val buffer = ByteArray(BUFFER_SIZE)
+ var len: Int
+ while (input.read(buffer).also { len = it } != -1) {
+ success(buffer.copyOf(len))
+ }
}
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "failed to open input stream for uri=$uri", e)
+ } finally {
endOfStream()
}
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/DngTags.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/DngTags.kt
new file mode 100644
index 000000000..313791999
--- /dev/null
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/DngTags.kt
@@ -0,0 +1,229 @@
+package deckers.thibault.aves.metadata
+
+// DNG v1.6.0.0
+// cf https://helpx.adobe.com/content/dam/help/en/photoshop/pdf/dng_spec_1_6_0_0.pdf
+object DngTags {
+ private const val DNG_VERSION = 0xC612
+ private const val DNG_BACKWARD_VERSION = 0xC613
+ private const val UNIQUE_CAMERA_MODEL = 0xC614
+ private const val LOCALIZED_CAMERA_MODEL = 0xC615
+ private const val CFA_PLANE_COLOR = 0xC616
+ private const val CFA_LAYOUT = 0xC617
+ private const val LINEARIZATION_TABLE = 0xC618
+ private const val BLACK_LEVEL_REPEAT_DIM = 0xC619
+ private const val BLACK_LEVEL = 0xC61A
+ private const val BLACK_LEVEL_DELTA_H = 0xC61B
+ private const val BLACK_LEVEL_DELTA_V = 0xC61C
+ private const val WHITE_LEVEL = 0xC61D
+ private const val DEFAULT_SCALE = 0xC61E
+ private const val DEFAULT_CROP_ORIGIN = 0xC61F
+ private const val DEFAULT_CROP_SIZE = 0xC620
+ private const val COLOR_MATRIX_1 = 0xC621
+ private const val COLOR_MATRIX_2 = 0xC622
+ private const val CAMERA_CALIBRATION_1 = 0xC623
+ private const val CAMERA_CALIBRATION_2 = 0xC624
+ private const val REDUCTION_MATRIX_1 = 0xC625
+ private const val REDUCTION_MATRIX_2 = 0xC626
+ private const val ANALOG_BALANCE = 0xC627
+ private const val AS_SHOT_NEUTRAL = 0xC628
+ private const val AS_SHOT_WHITE_XY = 0xC629
+ private const val BASELINE_EXPOSURE = 0xC62A
+ private const val BASELINE_NOISE = 0xC62B
+ private const val BASELINE_SHARPNESS = 0xC62C
+ private const val BAYER_GREEN_SPLIT = 0xC62D
+ private const val LINEAR_RESPONSE_LIMIT = 0xC62E
+ private const val CAMERA_SERIAL_NUMBER = 0xC62F
+ private const val LENS_INFO = 0xC630
+ private const val CHROMA_BLUR_RADIUS = 0xC631
+ private const val ANTI_ALIAS_STRENGTH = 0xC632
+ private const val SHADOW_SCALE = 0xC633
+ private const val DNG_PRIVATE_DATA = 0xC634
+ private const val MAKER_NOTE_SAFETY = 0xC635
+ private const val CALIBRATION_ILLUMINANT_1 = 0xC65A
+ private const val CALIBRATION_ILLUMINANT_2 = 0xC65B
+ private const val BEST_QUALITY_SCALE = 0xC65C
+ private const val RAW_DATA_UNIQUE_ID = 0xC65D
+ private const val ORIGINAL_RAW_FILE_NAME = 0xC68B
+ private const val ORIGINAL_RAW_FILE_DATA = 0xC68C
+ private const val ACTIVE_AREA = 0xC68D
+ private const val MASKED_AREAS = 0xC68E
+ private const val AS_SHOT_ICC_PROFILE = 0xC68F
+ private const val AS_SHOT_PRE_PROFILE_MATRIX = 0xC690
+ private const val CURRENT_ICC_PROFILE = 0xC691
+ private const val CURRENT_PRE_PROFILE_MATRIX = 0xC692
+ private const val COLORIMETRIC_REFERENCE = 0xC6BF
+ private const val CAMERA_CALIBRATION_SIGNATURE = 0xC6F3
+ private const val PROFILE_CALIBRATION_SIGNATURE = 0xC6F4
+ private const val EXTRA_CAMERA_PROFILES = 0xC6F5
+ private const val AS_SHOT_PROFILE_NAME = 0xC6F6
+ private const val NOISE_REDUCTION_APPLIED = 0xC6F7
+ private const val PROFILE_NAME = 0xC6F8
+ private const val PROFILE_HUE_SAT_MAP_DIMS = 0xC6F9
+ private const val PROFILE_HUE_SAT_MAP_DATA_1 = 0xC6FA
+ private const val PROFILE_HUE_SAT_MAP_DATA_2 = 0xC6FB
+ private const val PROFILE_TONE_CURVE = 0xC6FC
+ private const val PROFILE_EMBED_POLICY = 0xC6FD
+ private const val PROFILE_COPYRIGHT = 0xC6FE
+ private const val FORWARD_MATRIX_1 = 0xC714
+ private const val FORWARD_MATRIX_2 = 0xC715
+ private const val PREVIEW_APPLICATION_NAME = 0xC716
+ private const val PREVIEW_APPLICATION_VERSION = 0xC717
+ private const val PREVIEW_SETTINGS_NAME = 0xC718
+ private const val PREVIEW_SETTINGS_DIGEST = 0xC719
+ private const val PREVIEW_COLOR_SPACE = 0xC71A
+ private const val PREVIEW_DATE_TIME = 0xC71B
+ private const val RAW_IMAGE_DIGEST = 0xC71C
+ private const val ORIGINAL_RAW_FILE_DIGEST = 0xC71D
+ private const val SUB_TILE_BLOCK_SIZE = 0xC71E
+ private const val ROW_INTERLEAVE_FACTOR = 0xC71F
+ private const val PROFILE_LOOK_TABLE_DIMS = 0xC725
+ private const val PROFILE_LOOK_TABLE_DATA = 0xC726
+ private const val OPCODE_LIST_1 = 0xC740
+ private const val OPCODE_LIST_2 = 0xC741
+ private const val OPCODE_LIST_3 = 0xC74E
+ private const val NOISE_PROFILE = 0xC761
+ private const val ORIGINAL_DEFAULT_FINAL_SIZE = 0xC791
+ private const val ORIGINAL_BEST_QUALITY_FINAL_SIZE = 0xC792
+ private const val ORIGINAL_DEFAULT_CROP_SIZE = 0xC793
+ private const val PROFILE_HUE_SAT_MAP_ENCODING = 0xC7A3
+ private const val PROFILE_LOOK_TABLE_ENCODING = 0xC7A4
+ private const val BASELINE_EXPOSURE_OFFSET = 0xC7A5
+ private const val DEFAULT_BLACK_RENDER = 0xC7A6
+ private const val NEW_RAW_IMAGE_DIGEST = 0xC7A7
+ private const val RAW_TO_PREVIEW_GAIN = 0xC7A8
+ private const val DEFAULT_USER_CROP = 0xC7B5
+ private const val DEPTH_FORMAT = 0xC7E9
+ private const val DEPTH_NEAR = 0xC7EA
+ private const val DEPTH_FAR = 0xC7EB
+ private const val DEPTH_UNITS = 0xC7EC
+ private const val DEPTH_MEASURE_TYPE = 0xC7ED
+ private const val ENHANCE_PARAMS = 0xC7EE
+ private const val PROFILE_GAIN_TABLE_MAP = 0xCD2D
+ private const val SEMANTIC_NAME = 0xCD2E
+ private const val SEMANTIC_INSTANCE_ID = 0xCD30
+ private const val CALIBRATION_ILLUMINANT_3 = 0xCD31
+ private const val CAMERA_CALIBRATION_3 = 0xCD32
+ private const val COLOR_MATRIX_3 = 0xCD33
+ private const val FORWARD_MATRIX_3 = 0xCD34
+ private const val ILLUMINANT_DATA_1 = 0xCD35
+ private const val ILLUMINANT_DATA_2 = 0xCD36
+ private const val ILLUMINANT_DATA_3 = 0xCD37
+ private const val MASK_SUB_AREA = 0xCD38
+ private const val PROFILE_HUE_SAT_MAP_DATA_3 = 0xCD39
+ private const val REDUCTION_MATRIX_3 = 0xCD3A
+ private const val RGB_TABLES = 0xCD3F
+
+ val tagNameMap = hashMapOf(
+ DNG_VERSION to "DNG Version",
+ DNG_BACKWARD_VERSION to "DNG Backward Version",
+ UNIQUE_CAMERA_MODEL to "Unique Camera Model",
+ LOCALIZED_CAMERA_MODEL to "Localized Camera Model",
+ CFA_PLANE_COLOR to "CFA Plane Color",
+ CFA_LAYOUT to "CFA Layout",
+ LINEARIZATION_TABLE to "Linearization Table",
+ BLACK_LEVEL_REPEAT_DIM to "Black Level Repeat Dim",
+ BLACK_LEVEL to "Black Level",
+ BLACK_LEVEL_DELTA_H to "Black Level Delta H",
+ BLACK_LEVEL_DELTA_V to "Black Level Delta V",
+ WHITE_LEVEL to "White Level",
+ DEFAULT_SCALE to "Default Scale",
+ DEFAULT_CROP_ORIGIN to "Default Crop Origin",
+ DEFAULT_CROP_SIZE to "Default Crop Size",
+ COLOR_MATRIX_1 to "Color Matrix 1",
+ COLOR_MATRIX_2 to "Color Matrix 2",
+ CAMERA_CALIBRATION_1 to "Camera Calibration 1",
+ CAMERA_CALIBRATION_2 to "Camera Calibration 2",
+ REDUCTION_MATRIX_1 to "Reduction Matrix 1",
+ REDUCTION_MATRIX_2 to "Reduction Matrix 2",
+ ANALOG_BALANCE to "Analog Balance",
+ AS_SHOT_NEUTRAL to "As Shot Neutral",
+ AS_SHOT_WHITE_XY to "As Shot White XY",
+ BASELINE_EXPOSURE to "Baseline Exposure",
+ BASELINE_NOISE to "Baseline Noise",
+ BASELINE_SHARPNESS to "Baseline Sharpness",
+ BAYER_GREEN_SPLIT to "Bayer Green Split",
+ LINEAR_RESPONSE_LIMIT to "Linear Response Limit",
+ CAMERA_SERIAL_NUMBER to "Camera Serial Number",
+ LENS_INFO to "Lens Info",
+ CHROMA_BLUR_RADIUS to "Chroma Blur Radius",
+ ANTI_ALIAS_STRENGTH to "Anti Alias Strength",
+ SHADOW_SCALE to "Shadow Scale",
+ DNG_PRIVATE_DATA to "DNG Private Data",
+ MAKER_NOTE_SAFETY to "Maker Note Safety",
+ CALIBRATION_ILLUMINANT_1 to "Calibration Illuminant 1",
+ CALIBRATION_ILLUMINANT_2 to "Calibration Illuminant 2",
+ BEST_QUALITY_SCALE to "Best Quality Scale",
+ RAW_DATA_UNIQUE_ID to "Raw Data Unique ID",
+ ORIGINAL_RAW_FILE_NAME to "Original Raw File Name",
+ ORIGINAL_RAW_FILE_DATA to "Original Raw File Data",
+ ACTIVE_AREA to "Active Area",
+ MASKED_AREAS to "Masked Areas",
+ AS_SHOT_ICC_PROFILE to "As Shot ICC Profile",
+ AS_SHOT_PRE_PROFILE_MATRIX to "As Shot Pre Profile Matrix",
+ CURRENT_ICC_PROFILE to "Current ICC Profile",
+ CURRENT_PRE_PROFILE_MATRIX to "Current Pre Profile Matrix",
+ COLORIMETRIC_REFERENCE to "Colorimetric Reference",
+ CAMERA_CALIBRATION_SIGNATURE to "Camera Calibration Signature",
+ PROFILE_CALIBRATION_SIGNATURE to "Profile Calibration Signature",
+ EXTRA_CAMERA_PROFILES to "Extra Camera Profiles",
+ AS_SHOT_PROFILE_NAME to "As Shot Profile Name",
+ NOISE_REDUCTION_APPLIED to "Noise Reduction Applied",
+ PROFILE_NAME to "Profile Name",
+ PROFILE_HUE_SAT_MAP_DIMS to "Profile Hue Sat Map Dims",
+ PROFILE_HUE_SAT_MAP_DATA_1 to "Profile Hue Sat Map Data 1",
+ PROFILE_HUE_SAT_MAP_DATA_2 to "Profile Hue Sat Map Data 2",
+ PROFILE_TONE_CURVE to "Profile Tone Curve",
+ PROFILE_EMBED_POLICY to "Profile Embed Policy",
+ PROFILE_COPYRIGHT to "Profile Copyright",
+ FORWARD_MATRIX_1 to "Forward Matrix 1",
+ FORWARD_MATRIX_2 to "Forward Matrix 2",
+ PREVIEW_APPLICATION_NAME to "Preview Application Name",
+ PREVIEW_APPLICATION_VERSION to "Preview Application Version",
+ PREVIEW_SETTINGS_NAME to "Preview Settings Name",
+ PREVIEW_SETTINGS_DIGEST to "Preview Settings Digest",
+ PREVIEW_COLOR_SPACE to "Preview Color Space",
+ PREVIEW_DATE_TIME to "Preview Date Time",
+ RAW_IMAGE_DIGEST to "Raw Image Digest",
+ ORIGINAL_RAW_FILE_DIGEST to "Original Raw File Digest",
+ SUB_TILE_BLOCK_SIZE to "Sub Tile Block Size",
+ ROW_INTERLEAVE_FACTOR to "Row Interleave Factor",
+ PROFILE_LOOK_TABLE_DIMS to "Profile Look Table Dims",
+ PROFILE_LOOK_TABLE_DATA to "Profile Look Table Data",
+ OPCODE_LIST_1 to "Opcode List 1",
+ OPCODE_LIST_2 to "Opcode List 2",
+ OPCODE_LIST_3 to "Opcode List 3",
+ NOISE_PROFILE to "Noise Profile",
+ ORIGINAL_DEFAULT_FINAL_SIZE to "Original Default Final Size",
+ ORIGINAL_BEST_QUALITY_FINAL_SIZE to "Original Best Quality Final Size",
+ ORIGINAL_DEFAULT_CROP_SIZE to "Original Default Crop Size",
+ PROFILE_HUE_SAT_MAP_ENCODING to "Profile Hue Sat Map Encoding",
+ PROFILE_LOOK_TABLE_ENCODING to "Profile Look Table Encoding",
+ BASELINE_EXPOSURE_OFFSET to "Baseline Exposure Offset",
+ DEFAULT_BLACK_RENDER to "Default Black Render",
+ NEW_RAW_IMAGE_DIGEST to "New Raw Image Digest",
+ RAW_TO_PREVIEW_GAIN to "Raw To Preview Gain",
+ DEFAULT_USER_CROP to "Default User Crop",
+ DEPTH_FORMAT to "Depth Format",
+ DEPTH_NEAR to "Depth Near",
+ DEPTH_FAR to "Depth Far",
+ DEPTH_UNITS to "Depth Units",
+ DEPTH_MEASURE_TYPE to "Depth Measure Type",
+ ENHANCE_PARAMS to "Enhance Params",
+ PROFILE_GAIN_TABLE_MAP to "Profile Gain Table Map",
+ SEMANTIC_NAME to "Semantic Name",
+ SEMANTIC_INSTANCE_ID to "Semantic Instance ID",
+ CALIBRATION_ILLUMINANT_3 to "Calibration Illuminant 3",
+ CAMERA_CALIBRATION_3 to "Camera Calibration 3",
+ COLOR_MATRIX_3 to "Color Matrix 3",
+ FORWARD_MATRIX_3 to "Forward Matrix 3",
+ ILLUMINANT_DATA_1 to "Illuminant Data 1",
+ ILLUMINANT_DATA_2 to "Illuminant Data 2",
+ ILLUMINANT_DATA_3 to "Illuminant Data 3",
+ MASK_SUB_AREA to "Mask Sub Area",
+ PROFILE_HUE_SAT_MAP_DATA_3 to "Profile Hue Sat Map Data 3",
+ REDUCTION_MATRIX_3 to "Reduction Matrix 3",
+ RGB_TABLES to "RGB Tables",
+ )
+
+ val tags = tagNameMap.keys
+}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/ExifInterfaceHelper.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/ExifInterfaceHelper.kt
index 2fc085469..7b6317506 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/ExifInterfaceHelper.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/ExifInterfaceHelper.kt
@@ -363,13 +363,17 @@ object ExifInterfaceHelper {
}
}
- fun ExifInterface.getSafeDateMillis(tag: String, save: (value: Long) -> Unit) {
+ fun ExifInterface.getSafeDateMillis(tag: String, subSecTag: String?, save: (value: Long) -> Unit) {
if (this.hasAttribute(tag)) {
val dateString = this.getAttribute(tag)
if (dateString != null) {
try {
DATETIME_FORMAT.parse(dateString)?.let { date ->
- save(date.time)
+ var dateMillis = date.time
+ if (subSecTag != null && this.hasAttribute(subSecTag)) {
+ dateMillis += Metadata.parseSubSecond(this.getAttribute(subSecTag))
+ }
+ save(dateMillis)
}
} catch (e: ParseException) {
Log.w(LOG_TAG, "failed to parse date=$dateString", e)
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/ExifTags.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/ExifTags.kt
index 4dbe77450..ddcf16ebd 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/ExifTags.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/ExifTags.kt
@@ -1,155 +1,55 @@
package deckers.thibault.aves.metadata
-// Exif tags missing from `metadata-extractor`
+/*
+Exif tags missing from `metadata-extractor`
+
+Photoshop
+https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/
+https://www.adobe.io/content/dam/udp/en/open/standards/tiff/TIFFphotoshop.pdf
+ */
object ExifTags {
- // XPosition
- // Tag = 286 (011E.H)
private const val TAG_X_POSITION = 0x011e
-
- // YPosition
- // Tag = 287 (011F.H)
private const val TAG_Y_POSITION = 0x011f
-
- // ColorMap
- // Tag = 320 (0140.H)
+ private const val TAG_T4_OPTIONS = 0x0124
+ private const val TAG_T6_OPTIONS = 0x0125
private const val TAG_COLOR_MAP = 0x0140
-
- // ExtraSamples
- // Tag = 338 (0152.H)
- // values:
- // EXTRASAMPLE_UNSPECIFIED 0 // unspecified data
- // EXTRASAMPLE_ASSOCALPHA 1 // associated alpha data
- // EXTRASAMPLE_UNASSALPHA 2 // unassociated alpha data
private const val TAG_EXTRA_SAMPLES = 0x0152
-
- // SampleFormat
- // Tag = 339 (0153.H)
- // values:
- // SAMPLEFORMAT_UINT 1 // unsigned integer data
- // SAMPLEFORMAT_INT 2 // signed integer data
- // SAMPLEFORMAT_IEEEFP 3 // IEEE floating point data
- // SAMPLEFORMAT_VOID 4 // untyped data
- // SAMPLEFORMAT_COMPLEXINT 5 // complex signed int
- // SAMPLEFORMAT_COMPLEXIEEEFP 6 // complex ieee floating
private const val TAG_SAMPLE_FORMAT = 0x0153
-
-
- // Rating tag used by Windows, value in percent
- // Tag = 18249 (4749.H)
- // Type = SHORT
private const val TAG_RATING_PERCENT = 0x4749
-
- /*
- SGI
- tags 32995-32999
- */
-
- // Matteing
- // Tag = 32995 (80E3.H)
- // obsoleted by the 6.0 ExtraSamples (338)
+ private const val SONY_RAW_FILE_TYPE = 0x7000
+ private const val SONY_TONE_CURVE = 0x7010
private const val TAG_MATTEING = 0x80e3
- /*
- GeoTIFF
- */
-
- // ModelPixelScaleTag (optional)
- // Tag = 33550 (830E.H)
- // Type = DOUBLE
- // Count = 3
- const val TAG_MODEL_PIXEL_SCALE = 0x830e
-
- // ModelTiepointTag (conditional)
- // Tag = 33922 (8482.H)
- // Type = DOUBLE
- // Count = 6*K, K = number of tiepoints
- const val TAG_MODEL_TIEPOINT = 0x8482
-
- // ModelTransformationTag (conditional)
- // Tag = 34264 (85D8.H)
- // Type = DOUBLE
- // Count = 16
- const val TAG_MODEL_TRANSFORMATION = 0x85d8
-
- // GeoKeyDirectoryTag (mandatory)
- // Tag = 34735 (87AF.H)
- // Type = UNSIGNED SHORT
- // Count = variable, >= 4
- const val TAG_GEO_KEY_DIRECTORY = 0x87af
-
- // GeoDoubleParamsTag (optional)
- // Tag = 34736 (87BO.H)
- // Type = DOUBLE
- // Count = variable
- private const val TAG_GEO_DOUBLE_PARAMS = 0x87b0
-
- // GeoAsciiParamsTag (optional)
- // Tag = 34737 (87B1.H)
- // Type = ASCII
- // Count = variable
- private const val TAG_GEO_ASCII_PARAMS = 0x87b1
-
- /*
- Photoshop
- https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/
- https://www.adobe.io/content/dam/udp/en/open/standards/tiff/TIFFphotoshop.pdf
- */
-
- // ImageSourceData
- // Tag = 37724 (935C.H)
- // Type = UNDEFINED
+ // sensing method (0x9217) redundant with sensing method (0xA217)
+ private const val TAG_SENSING_METHOD = 0x9217
private const val TAG_IMAGE_SOURCE_DATA = 0x935c
-
- /*
- DNG
- https://www.adobe.com/content/dam/acom/en/products/photoshop/pdfs/dng_spec_1.4.0.0.pdf
- */
-
- // CameraSerialNumber
- // Tag = 50735 (C62F.H)
- // Type = ASCII
- // Count = variable
- private const val TAG_CAMERA_SERIAL_NUMBER = 0xc62f
-
- // OriginalRawFileName (optional)
- // Tag = 50827 (C68B.H)
- // Type = ASCII or BYTE
- // Count = variable
- private const val TAG_ORIGINAL_RAW_FILE_NAME = 0xc68b
-
- private val geotiffTags = listOf(
- TAG_GEO_ASCII_PARAMS,
- TAG_GEO_DOUBLE_PARAMS,
- TAG_GEO_KEY_DIRECTORY,
- TAG_MODEL_PIXEL_SCALE,
- TAG_MODEL_TIEPOINT,
- TAG_MODEL_TRANSFORMATION,
- )
+ private const val TAG_GDAL_METADATA = 0xa480
+ private const val TAG_GDAL_NO_DATA = 0xa481
private val tagNameMap = hashMapOf(
TAG_X_POSITION to "X Position",
TAG_Y_POSITION to "Y Position",
+ TAG_T4_OPTIONS to "T4 Options",
+ TAG_T6_OPTIONS to "T6 Options",
TAG_COLOR_MAP to "Color Map",
TAG_EXTRA_SAMPLES to "Extra Samples",
TAG_SAMPLE_FORMAT to "Sample Format",
TAG_RATING_PERCENT to "Rating Percent",
- // SGI
+ SONY_RAW_FILE_TYPE to "Sony Raw File Type",
+ SONY_TONE_CURVE to "Sony Tone Curve",
TAG_MATTEING to "Matteing",
- // GeoTIFF
- TAG_GEO_ASCII_PARAMS to "Geo Ascii Params",
- TAG_GEO_DOUBLE_PARAMS to "Geo Double Params",
- TAG_GEO_KEY_DIRECTORY to "Geo Key Directory",
- TAG_MODEL_PIXEL_SCALE to "Model Pixel Scale",
- TAG_MODEL_TIEPOINT to "Model Tiepoint",
- TAG_MODEL_TRANSFORMATION to "Model Transformation",
- // Photoshop
+ TAG_SENSING_METHOD to "Sensing Method (0x9217)",
TAG_IMAGE_SOURCE_DATA to "Image Source Data",
- // DNG
- TAG_CAMERA_SERIAL_NUMBER to "Camera Serial Number",
- TAG_ORIGINAL_RAW_FILE_NAME to "Original Raw File Name",
- )
+ TAG_GDAL_METADATA to "GDAL Metadata",
+ TAG_GDAL_NO_DATA to "GDAL No Data",
+ ).apply {
+ putAll(DngTags.tagNameMap)
+ putAll(GeoTiffTags.tagNameMap)
+ }
+
+ fun isDngTag(tag: Int) = DngTags.tags.contains(tag)
- fun isGeoTiffTag(tag: Int) = geotiffTags.contains(tag)
+ fun isGeoTiffTag(tag: Int) = GeoTiffTags.tags.contains(tag)
fun getTagName(tag: Int): String? {
return tagNameMap[tag]
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/GeoTiffTags.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/GeoTiffTags.kt
new file mode 100644
index 000000000..208165fc3
--- /dev/null
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/GeoTiffTags.kt
@@ -0,0 +1,50 @@
+package deckers.thibault.aves.metadata
+
+object GeoTiffTags {
+ // ModelPixelScaleTag (optional)
+ // Tag = 33550 (830E.H)
+ // Type = DOUBLE
+ // Count = 3
+ const val TAG_MODEL_PIXEL_SCALE = 0x830e
+
+ // ModelTiepointTag (conditional)
+ // Tag = 33922 (8482.H)
+ // Type = DOUBLE
+ // Count = 6*K, K = number of tiepoints
+ const val TAG_MODEL_TIEPOINT = 0x8482
+
+ // ModelTransformationTag (conditional)
+ // Tag = 34264 (85D8.H)
+ // Type = DOUBLE
+ // Count = 16
+ const val TAG_MODEL_TRANSFORMATION = 0x85d8
+
+ // GeoKeyDirectoryTag (mandatory)
+ // Tag = 34735 (87AF.H)
+ // Type = UNSIGNED SHORT
+ // Count = variable, >= 4
+ const val TAG_GEO_KEY_DIRECTORY = 0x87af
+
+ // GeoDoubleParamsTag (optional)
+ // Tag = 34736 (87BO.H)
+ // Type = DOUBLE
+ // Count = variable
+ private const val TAG_GEO_DOUBLE_PARAMS = 0x87b0
+
+ // GeoAsciiParamsTag (optional)
+ // Tag = 34737 (87B1.H)
+ // Type = ASCII
+ // Count = variable
+ private const val TAG_GEO_ASCII_PARAMS = 0x87b1
+
+ val tagNameMap = hashMapOf(
+ TAG_GEO_ASCII_PARAMS to "Geo Ascii Params",
+ TAG_GEO_DOUBLE_PARAMS to "Geo Double Params",
+ TAG_GEO_KEY_DIRECTORY to "Geo Key Directory",
+ TAG_MODEL_PIXEL_SCALE to "Model Pixel Scale",
+ TAG_MODEL_TIEPOINT to "Model Tiepoint",
+ TAG_MODEL_TRANSFORMATION to "Model Transformation",
+ )
+
+ val tags = tagNameMap.keys
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt
index 8e86a124e..805b7e3df 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt
@@ -65,6 +65,20 @@ object Metadata {
}
}
+ fun parseSubSecond(subSecond: String?): Int {
+ if (subSecond != null) {
+ try {
+ val millis = (".$subSecond".toDouble() * 1000).toInt()
+ if (millis in 0..999) {
+ return millis
+ }
+ } catch (e: NumberFormatException) {
+ // ignore
+ }
+ }
+ return 0
+ }
+
// not sure which standards are used for the different video formats,
// but looks like some form of ISO 8601 `basic format`:
// yyyyMMddTHHmmss(.sss)?(Z|+/-hhmm)?
@@ -96,18 +110,7 @@ object Metadata {
null
} ?: return 0
- var dateMillis = date.time
- if (subSecond != null) {
- try {
- val millis = (".$subSecond".toDouble() * 1000).toInt()
- if (millis in 0..999) {
- dateMillis += millis.toLong()
- }
- } catch (e: NumberFormatException) {
- // ignore
- }
- }
- return dateMillis
+ return date.time + parseSubSecond(subSecond)
}
// opening large PSD/TIFF files yields an OOM (both with `metadata-extractor` v2.15.0 and `ExifInterface` v1.3.1),
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MetadataExtractorHelper.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MetadataExtractorHelper.kt
index 61b17d9c9..8a244d3d0 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MetadataExtractorHelper.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MetadataExtractorHelper.kt
@@ -6,7 +6,9 @@ import com.drew.lang.Rational
import com.drew.lang.SequentialByteArrayReader
import com.drew.metadata.Directory
import com.drew.metadata.exif.ExifDirectoryBase
+import com.drew.metadata.exif.ExifIFD0Directory
import com.drew.metadata.exif.ExifReader
+import com.drew.metadata.exif.ExifSubIFDDirectory
import com.drew.metadata.iptc.IptcReader
import com.drew.metadata.png.PngDirectory
import deckers.thibault.aves.utils.LogUtils
@@ -53,11 +55,34 @@ object MetadataExtractorHelper {
if (this.containsTag(tag)) save(this.getRational(tag))
}
- fun Directory.getSafeDateMillis(tag: Int, save: (value: Long) -> Unit) {
+ fun Directory.getSafeDateMillis(tag: Int, subSecond: String?): Long? {
if (this.containsTag(tag)) {
- val date = this.getDate(tag, null, TimeZone.getDefault())
- if (date != null) save(date.time)
+ val date = this.getDate(tag, subSecond, TimeZone.getDefault())
+ if (date != null) return date.time
}
+ return null
+ }
+
+ // time tag and sub-second tag are *not* in the same directory
+ fun ExifSubIFDDirectory.getDateModifiedMillis(save: (value: Long) -> Unit) {
+ val parent = parent
+ if (parent is ExifIFD0Directory) {
+ val subSecond = getString(ExifSubIFDDirectory.TAG_SUBSECOND_TIME)
+ val dateMillis = parent.getSafeDateMillis(ExifIFD0Directory.TAG_DATETIME, subSecond)
+ if (dateMillis != null) save(dateMillis)
+ }
+ }
+
+ fun ExifSubIFDDirectory.getDateDigitizedMillis(save: (value: Long) -> Unit) {
+ val subSecond = getString(ExifSubIFDDirectory.TAG_SUBSECOND_TIME_DIGITIZED)
+ val dateMillis = this.getSafeDateMillis(ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED, subSecond)
+ if (dateMillis != null) save(dateMillis)
+ }
+
+ fun ExifSubIFDDirectory.getDateOriginalMillis(save: (value: Long) -> Unit) {
+ val subSecond = getString(ExifSubIFDDirectory.TAG_SUBSECOND_TIME_ORIGINAL)
+ val dateMillis = this.getSafeDateMillis(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL, subSecond)
+ if (dateMillis != null) save(dateMillis)
}
// geotiff
@@ -69,13 +94,13 @@ object MetadataExtractorHelper {
- If the ModelPixelScaleTag is included in an IFD, then a ModelTiepointTag SHALL also be included.
*/
fun ExifDirectoryBase.isGeoTiff(): Boolean {
- if (!this.containsTag(ExifTags.TAG_GEO_KEY_DIRECTORY)) return false
+ if (!this.containsTag(GeoTiffTags.TAG_GEO_KEY_DIRECTORY)) return false
- val modelTiepoint = this.containsTag(ExifTags.TAG_MODEL_TIEPOINT)
- val modelTransformation = this.containsTag(ExifTags.TAG_MODEL_TRANSFORMATION)
+ val modelTiepoint = this.containsTag(GeoTiffTags.TAG_MODEL_TIEPOINT)
+ val modelTransformation = this.containsTag(GeoTiffTags.TAG_MODEL_TRANSFORMATION)
if (!modelTiepoint && !modelTransformation) return false
- val modelPixelScale = this.containsTag(ExifTags.TAG_MODEL_PIXEL_SCALE)
+ val modelPixelScale = this.containsTag(GeoTiffTags.TAG_MODEL_PIXEL_SCALE)
if ((modelTransformation && modelPixelScale) || (modelPixelScale && !modelTiepoint)) return false
return true
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt
index b4ffd9b43..6eb90a59f 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt
@@ -185,7 +185,7 @@ class SourceEntry {
dir.getSafeInt(ExifIFD0Directory.TAG_IMAGE_WIDTH) { width = it }
dir.getSafeInt(ExifIFD0Directory.TAG_IMAGE_HEIGHT) { height = it }
dir.getSafeInt(ExifIFD0Directory.TAG_ORIENTATION) { sourceRotationDegrees = getRotationDegreesForExifCode(it) }
- dir.getSafeDateMillis(ExifIFD0Directory.TAG_DATETIME) { sourceDateTakenMillis = it }
+ dir.getSafeDateMillis(ExifIFD0Directory.TAG_DATETIME, null)?.let { sourceDateTakenMillis = it }
}
// dimensions reported in EXIF do not always match the image
@@ -218,7 +218,7 @@ class SourceEntry {
exif.getSafeInt(ExifInterface.TAG_IMAGE_WIDTH, acceptZero = false) { width = it }
exif.getSafeInt(ExifInterface.TAG_IMAGE_LENGTH, acceptZero = false) { height = it }
exif.getSafeInt(ExifInterface.TAG_ORIENTATION, acceptZero = false) { sourceRotationDegrees = exif.rotationDegrees }
- exif.getSafeDateMillis(ExifInterface.TAG_DATETIME) { sourceDateTakenMillis = it }
+ exif.getSafeDateMillis(ExifInterface.TAG_DATETIME, ExifInterface.TAG_SUBSEC_TIME) { sourceDateTakenMillis = it }
}
} catch (e: Exception) {
// ExifInterface initialization can fail with a RuntimeException
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt
index 82bbdcf2e..0c25fc37f 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt
@@ -62,8 +62,7 @@ abstract class ImageProvider {
open suspend fun renameMultiple(
activity: Activity,
- newFileName: String,
- entries: List,
+ entriesToNewName: Map,
isCancelledOp: CancelCheck,
callback: ImageOpCallback,
) {
@@ -143,7 +142,7 @@ abstract class ImageProvider {
var desiredNameWithoutExtension = if (sourceEntry.path != null) {
val sourceFileName = File(sourceEntry.path).name
- sourceFileName.replaceFirst(FILE_EXTENSION_PATTERN, "")
+ sourceFileName.substringBeforeLast(".")
} else {
sourceUri.lastPathSegment!!
}
@@ -757,7 +756,13 @@ abstract class ImageProvider {
ExifInterface.TAG_DATETIME_DIGITIZED,
).forEach { field ->
if (fields.contains(field)) {
- exif.getSafeDateMillis(field) { date ->
+ val subSecTag = when (field) {
+ ExifInterface.TAG_DATETIME -> ExifInterface.TAG_SUBSEC_TIME
+ ExifInterface.TAG_DATETIME_DIGITIZED -> ExifInterface.TAG_SUBSEC_TIME_DIGITIZED
+ ExifInterface.TAG_DATETIME_ORIGINAL -> ExifInterface.TAG_SUBSEC_TIME_ORIGINAL
+ else -> null
+ }
+ exif.getSafeDateMillis(field, subSecTag) { date ->
exif.setAttribute(field, ExifInterfaceHelper.DATETIME_FORMAT.format(date + shiftMillis))
}
}
@@ -964,8 +969,6 @@ abstract class ImageProvider {
companion object {
private val LOG_TAG = LogUtils.createTag()
- val FILE_EXTENSION_PATTERN = Regex("[.][^.]+$")
-
val supportedExportMimeTypes = listOf(MimeTypes.BMP, MimeTypes.JPEG, MimeTypes.PNG, MimeTypes.WEBP)
// used when skipping a move/creation op because the target file already exists
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt
index 4708e909d..f6eeb0242 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt
@@ -191,7 +191,6 @@ class MediaStoreImageProvider : ImageProvider() {
val pathColumn = cursor.getColumnIndexOrThrow(MediaColumns.PATH)
val mimeTypeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE)
- val titleColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.TITLE)
val widthColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.WIDTH)
val heightColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.HEIGHT)
val dateModifiedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)
@@ -229,7 +228,6 @@ class MediaStoreImageProvider : ImageProvider() {
"height" to height,
"sourceRotationDegrees" to if (orientationColumn != -1) cursor.getInt(orientationColumn) else 0,
"sizeBytes" to cursor.getLong(sizeColumn),
- "title" to cursor.getString(titleColumn),
"dateModifiedSecs" to dateModifiedSecs,
"sourceDateTakenMillis" to if (dateTakenColumn != -1) cursor.getLong(dateTakenColumn) else null,
"durationMillis" to durationMillis,
@@ -489,7 +487,7 @@ class MediaStoreImageProvider : ImageProvider() {
return skippedFieldMap
}
- val desiredNameWithoutExtension = desiredName.replaceFirst(FILE_EXTENSION_PATTERN, "")
+ val desiredNameWithoutExtension = desiredName.substringBeforeLast(".")
val targetNameWithoutExtension = resolveTargetFileNameWithoutExtension(
activity = activity,
dir = targetDir,
@@ -587,12 +585,14 @@ class MediaStoreImageProvider : ImageProvider() {
override suspend fun renameMultiple(
activity: Activity,
- newFileName: String,
- entries: List,
+ entriesToNewName: Map,
isCancelledOp: CancelCheck,
callback: ImageOpCallback,
) {
- for (entry in entries) {
+ for (kv in entriesToNewName) {
+ val entry = kv.key
+ val desiredName = kv.value
+
val sourceUri = entry.uri
val sourcePath = entry.path
val mimeType = entry.mimeType
@@ -602,19 +602,20 @@ class MediaStoreImageProvider : ImageProvider() {
"success" to false,
)
- if (sourcePath != null) {
+ // prevent naming with a `.` prefix as it would hide the file and remove it from the Media Store
+ if (sourcePath != null && !desiredName.startsWith('.')) {
try {
val newFields = if (isCancelledOp()) skippedFieldMap else renameSingle(
activity = activity,
mimeType = mimeType,
oldMediaUri = sourceUri,
oldPath = sourcePath,
- newFileName = newFileName,
+ desiredName = desiredName,
)
result["newFields"] = newFields
result["success"] = true
} catch (e: Exception) {
- Log.w(LOG_TAG, "failed to rename to newFileName=$newFileName entry with sourcePath=$sourcePath", e)
+ Log.w(LOG_TAG, "failed to rename to newFileName=$desiredName entry with sourcePath=$sourcePath", e)
}
}
callback.onSuccess(result)
@@ -626,10 +627,24 @@ class MediaStoreImageProvider : ImageProvider() {
mimeType: String,
oldMediaUri: Uri,
oldPath: String,
- newFileName: String,
+ desiredName: String,
): FieldMap {
+ val desiredNameWithoutExtension = desiredName.substringBeforeLast(".")
+
val oldFile = File(oldPath)
- val newFile = File(oldFile.parent, newFileName)
+ if (oldFile.nameWithoutExtension == desiredNameWithoutExtension) return skippedFieldMap
+
+ val dir = oldFile.parent ?: return skippedFieldMap
+ val targetNameWithoutExtension = resolveTargetFileNameWithoutExtension(
+ activity = activity,
+ dir = dir,
+ desiredNameWithoutExtension = desiredNameWithoutExtension,
+ mimeType = mimeType,
+ conflictStrategy = NameConflictStrategy.RENAME,
+ ) ?: return skippedFieldMap
+ val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}"
+
+ val newFile = File(dir, targetFileName)
return when {
oldFile == newFile -> skippedFieldMap
StorageUtils.canEditByFile(activity, oldPath) -> renameSingleByFile(activity, mimeType, oldMediaUri, oldPath, newFile)
@@ -681,8 +696,11 @@ class MediaStoreImageProvider : ImageProvider() {
newFile: File
): FieldMap {
Log.d(LOG_TAG, "rename document at uri=$oldMediaUri path=$oldPath")
+ val df = StorageUtils.getDocumentFile(activity, oldPath, oldMediaUri)
+ df ?: throw Exception("failed to get document at path=$oldPath")
+
@Suppress("BlockingMethodInNonBlockingContext")
- val renamed = StorageUtils.getDocumentFile(activity, oldPath, oldMediaUri)?.renameTo(newFile.name) ?: false
+ val renamed = df.renameTo(newFile.name)
if (!renamed) {
throw Exception("failed to rename document at path=$oldPath")
}
@@ -763,8 +781,6 @@ class MediaStoreImageProvider : ImageProvider() {
// we retrieve updated fields as the renamed/moved file became a new entry in the Media Store
val projection = arrayOf(
MediaStore.MediaColumns.DATE_MODIFIED,
- MediaStore.MediaColumns.DISPLAY_NAME,
- MediaStore.MediaColumns.TITLE,
)
try {
val cursor = context.contentResolver.query(uri, projection, null, null, null)
@@ -774,8 +790,6 @@ class MediaStoreImageProvider : ImageProvider() {
newFields["contentId"] = uri.tryParseId()
newFields["path"] = path
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED).let { if (it != -1) newFields["dateModifiedSecs"] = cursor.getInt(it) }
- cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME).let { if (it != -1) newFields["displayName"] = cursor.getString(it) }
- cursor.getColumnIndex(MediaStore.MediaColumns.TITLE).let { if (it != -1) newFields["title"] = cursor.getString(it) }
cursor.close()
return newFields
}
@@ -846,8 +860,6 @@ class MediaStoreImageProvider : ImageProvider() {
MediaColumns.PATH,
MediaStore.MediaColumns.MIME_TYPE,
MediaStore.MediaColumns.SIZE,
- // TODO TLAD use `DISPLAY_NAME` instead/along `TITLE`?
- MediaStore.MediaColumns.TITLE,
MediaStore.MediaColumns.WIDTH,
MediaStore.MediaColumns.HEIGHT,
MediaStore.MediaColumns.DATE_MODIFIED,
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt
index 15d64b552..fa810a2b4 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt
@@ -26,7 +26,7 @@ object MimeTypes {
private const val CR2 = "image/x-canon-cr2"
private const val CRW = "image/x-canon-crw"
private const val DCR = "image/x-kodak-dcr"
- private const val DNG = "image/x-adobe-dng"
+ const val DNG = "image/x-adobe-dng"
private const val ERF = "image/x-epson-erf"
private const val K25 = "image/x-kodak-k25"
private const val KDC = "image/x-kodak-kdc"
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt
index a420fb1f3..b2980e11b 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt
@@ -36,13 +36,14 @@ object StorageUtils {
const val TRASH_PATH_PLACEHOLDER = "#trash"
private fun isAppFile(context: Context, path: String): Boolean {
- return context.getExternalFilesDirs(null).any { filesDir -> path.startsWith(filesDir.path) }
+ val filesDirs = context.getExternalFilesDirs(null).filterNotNull()
+ return filesDirs.any { path.startsWith(it.path) }
}
private fun appExternalFilesDirFor(context: Context, path: String): File? {
- val filesDirs = context.getExternalFilesDirs(null)
+ val filesDirs = context.getExternalFilesDirs(null).filterNotNull()
val volumePath = getVolumePath(context, path)
- return volumePath?.let { filesDirs.firstOrNull { it.startsWith(volumePath) } } ?: filesDirs.first()
+ return volumePath?.let { filesDirs.firstOrNull { it.startsWith(volumePath) } } ?: filesDirs.firstOrNull()
}
fun trashDirFor(context: Context, path: String): File? {
@@ -115,6 +116,15 @@ object StorageUtils {
}
private fun findPrimaryVolumePath(context: Context): String? {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ val sm = context.getSystemService(Context.STORAGE_SERVICE) as? StorageManager
+ val path = sm?.primaryStorageVolume?.directory?.path
+ if (path != null) {
+ return ensureTrailingSeparator(path)
+ }
+ }
+
+ // fallback
try {
// we want:
// /storage/emulated/0/
@@ -130,9 +140,16 @@ object StorageUtils {
}
private fun findVolumePaths(context: Context): Array {
- // Final set of paths
- val paths = HashSet()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ val sm = context.getSystemService(Context.STORAGE_SERVICE) as? StorageManager
+ val paths = sm?.storageVolumes?.mapNotNull { it.directory?.path }
+ if (paths != null) {
+ return paths.map(::ensureTrailingSeparator).toTypedArray()
+ }
+ }
+ // fallback
+ val paths = HashSet()
try {
// Primary emulated SD-CARD
val rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET") ?: ""
@@ -143,7 +160,8 @@ object StorageUtils {
var validFiles: Boolean
do {
// `getExternalFilesDirs` sometimes include `null` when called right after getting read access
- // (e.g. on API 30 emulator) so we retry until the file system is ready
+ // (e.g. on API 30 emulator) so we retry until the file system is ready.
+ // TODO TLAD It can also include `null` when there is a faulty SD card.
val externalFilesDirs = context.getExternalFilesDirs(null)
validFiles = !externalFilesDirs.contains(null)
if (validFiles) {
diff --git a/android/build.gradle b/android/build.gradle
index 4174edca7..b58087225 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.1.1'
+ classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// GMS & Firebase Crashlytics are not actually used by all flavors
classpath 'com.google.gms:google-services:4.3.10'
diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/1.png b/fastlane/metadata/android/de/images/phoneScreenshots/1.png
index 0ef48db3d..149abe06e 100644
Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/1.png and b/fastlane/metadata/android/de/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/2.png b/fastlane/metadata/android/de/images/phoneScreenshots/2.png
index fb5080556..1df869df0 100644
Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/2.png and b/fastlane/metadata/android/de/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/3.png b/fastlane/metadata/android/de/images/phoneScreenshots/3.png
index 540e6c430..b1dca7f8d 100644
Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/3.png and b/fastlane/metadata/android/de/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/4.png b/fastlane/metadata/android/de/images/phoneScreenshots/4.png
index 674ee85e7..73b826187 100644
Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/4.png and b/fastlane/metadata/android/de/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/5.png b/fastlane/metadata/android/de/images/phoneScreenshots/5.png
index 19bfe9bcd..74bc802fa 100644
Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/5.png and b/fastlane/metadata/android/de/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/6.png b/fastlane/metadata/android/de/images/phoneScreenshots/6.png
index 2e8d3034d..56099d117 100644
Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/6.png and b/fastlane/metadata/android/de/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/en-US/changelogs/1069.txt b/fastlane/metadata/android/en-US/changelogs/1069.txt
new file mode 100644
index 000000000..93bf3bf8b
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1069.txt
@@ -0,0 +1,4 @@
+In v1.6.3:
+- enjoy the light theme
+- rename items in bulk
+Full changelog available on GitHub
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
index aef538c60..bf177de91 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png
index a6d84ce73..cab24996a 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png
index 039a7a267..96767e884 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
index 42a897ce9..5aa80520d 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png
index 7fc82a805..763fab5f2 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png
index e93411a46..f2e09ae08 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/1.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/1.png
index aedd59b04..4480d45e4 100644
Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/1.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/2.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/2.png
index 907ea242f..94170fd16 100644
Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/2.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/3.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/3.png
index fff50ac57..7e6ed547e 100644
Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/3.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/4.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/4.png
index 69a09e0b3..6d3fdb80f 100644
Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/4.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/5.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/5.png
index 77cbdf3b5..236a70926 100644
Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/5.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/6.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/6.png
index 1cb5582b6..cc9d9e252 100644
Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/6.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/1.png b/fastlane/metadata/android/fr/images/phoneScreenshots/1.png
index 4e9d3a559..c139324b1 100644
Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/1.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/2.png b/fastlane/metadata/android/fr/images/phoneScreenshots/2.png
index d6d30754d..47c0852ce 100644
Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/2.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/3.png b/fastlane/metadata/android/fr/images/phoneScreenshots/3.png
index d6d1136a2..d1d9df6a8 100644
Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/3.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/4.png b/fastlane/metadata/android/fr/images/phoneScreenshots/4.png
index 1e1b71624..792d081af 100644
Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/4.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/5.png b/fastlane/metadata/android/fr/images/phoneScreenshots/5.png
index 82e97daf0..1083c6be4 100644
Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/5.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/6.png b/fastlane/metadata/android/fr/images/phoneScreenshots/6.png
index dcc9e4eff..077def362 100644
Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/6.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/1.png b/fastlane/metadata/android/id/images/phoneScreenshots/1.png
index 1148786fd..f157c0be5 100644
Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/1.png and b/fastlane/metadata/android/id/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/2.png b/fastlane/metadata/android/id/images/phoneScreenshots/2.png
index f1a6c95bb..25db2f7d4 100644
Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/2.png and b/fastlane/metadata/android/id/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/3.png b/fastlane/metadata/android/id/images/phoneScreenshots/3.png
index 646a107e9..c5f809a92 100644
Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/3.png and b/fastlane/metadata/android/id/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/4.png b/fastlane/metadata/android/id/images/phoneScreenshots/4.png
index 887f67554..022e506a5 100644
Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/4.png and b/fastlane/metadata/android/id/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/5.png b/fastlane/metadata/android/id/images/phoneScreenshots/5.png
index 5684e687b..78286da77 100644
Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/5.png and b/fastlane/metadata/android/id/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/6.png b/fastlane/metadata/android/id/images/phoneScreenshots/6.png
index 43ed58d3e..75ba57bca 100644
Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/6.png and b/fastlane/metadata/android/id/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/1.png b/fastlane/metadata/android/ja/images/phoneScreenshots/1.png
index 8a2eb8f99..b7f99f9c7 100644
Binary files a/fastlane/metadata/android/ja/images/phoneScreenshots/1.png and b/fastlane/metadata/android/ja/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/2.png b/fastlane/metadata/android/ja/images/phoneScreenshots/2.png
index e840f74b2..8b4364e04 100644
Binary files a/fastlane/metadata/android/ja/images/phoneScreenshots/2.png and b/fastlane/metadata/android/ja/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/3.png b/fastlane/metadata/android/ja/images/phoneScreenshots/3.png
index a72c431f3..e8b879179 100644
Binary files a/fastlane/metadata/android/ja/images/phoneScreenshots/3.png and b/fastlane/metadata/android/ja/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/4.png b/fastlane/metadata/android/ja/images/phoneScreenshots/4.png
index e47324358..fd39d1ef1 100644
Binary files a/fastlane/metadata/android/ja/images/phoneScreenshots/4.png and b/fastlane/metadata/android/ja/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/5.png b/fastlane/metadata/android/ja/images/phoneScreenshots/5.png
index 2c0a91d4f..39b6769ea 100644
Binary files a/fastlane/metadata/android/ja/images/phoneScreenshots/5.png and b/fastlane/metadata/android/ja/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/6.png b/fastlane/metadata/android/ja/images/phoneScreenshots/6.png
index 392691f0e..d358bb072 100644
Binary files a/fastlane/metadata/android/ja/images/phoneScreenshots/6.png and b/fastlane/metadata/android/ja/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/1.png b/fastlane/metadata/android/ko/images/phoneScreenshots/1.png
index d8e080ffc..ba194324a 100644
Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/1.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/2.png b/fastlane/metadata/android/ko/images/phoneScreenshots/2.png
index d6928ee42..a601e74d5 100644
Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/2.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/3.png b/fastlane/metadata/android/ko/images/phoneScreenshots/3.png
index 7906b78cf..8206e30b9 100644
Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/3.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/4.png b/fastlane/metadata/android/ko/images/phoneScreenshots/4.png
index 5ef843272..a089a9252 100644
Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/4.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/5.png b/fastlane/metadata/android/ko/images/phoneScreenshots/5.png
index b1c3362c9..206888b33 100644
Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/5.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/6.png b/fastlane/metadata/android/ko/images/phoneScreenshots/6.png
index a733c7331..5ad1622f3 100644
Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/6.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/1.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/1.png
index 5721e71de..560bcfa3a 100644
Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/1.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/2.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/2.png
index 41555870c..e8abe4129 100644
Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/2.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/3.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/3.png
index b8bd3594f..b28935bfc 100644
Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/3.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/4.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/4.png
index 364cc6fbc..0e8c26458 100644
Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/4.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/5.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/5.png
index c75e08755..172a48660 100644
Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/5.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/6.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/6.png
index 5244a840b..372ddca85 100644
Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/6.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/1.png b/fastlane/metadata/android/ru/images/phoneScreenshots/1.png
index f1315be49..2facdbc61 100644
Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/1.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/2.png b/fastlane/metadata/android/ru/images/phoneScreenshots/2.png
index 7a310ca4f..ca1770023 100644
Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/2.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/3.png b/fastlane/metadata/android/ru/images/phoneScreenshots/3.png
index 30ad8972d..ac3b92614 100644
Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/3.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/4.png b/fastlane/metadata/android/ru/images/phoneScreenshots/4.png
index e91ec53c5..4a54f9f2e 100644
Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/4.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/5.png b/fastlane/metadata/android/ru/images/phoneScreenshots/5.png
index e7b6dc26b..38cb374c4 100644
Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/5.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/6.png b/fastlane/metadata/android/ru/images/phoneScreenshots/6.png
index b69e24c5c..f14b4f9bc 100644
Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/6.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/6.png differ
diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb
index 633b13c3b..1c49c6928 100644
--- a/lib/l10n/app_de.arb
+++ b/lib/l10n/app_de.arb
@@ -137,6 +137,13 @@
"accessibilityAnimationsRemove": "Verhinderung von Bildschirmeffekten",
"accessibilityAnimationsKeep": "Bildschirmeffekte beibehalten",
+ "displayRefreshRatePreferHighest": "Höchste Rate",
+ "displayRefreshRatePreferLowest": "Niedrigste Rate",
+
+ "themeBrightnessLight": "Hell",
+ "themeBrightnessDark": "Dunkel",
+ "themeBrightnessBlack": "Schwarz",
+
"albumTierNew": "Neu",
"albumTierPinned": "Angeheftet",
"albumTierSpecial": "Häufig verwendet",
@@ -170,6 +177,8 @@
"binEntriesConfirmationDialogMessage": "{count, plural, =1{Dieses Element in den Papierkorb verschieben?} other{Diese {count} Elemente in den Papierkorb verschieben?}}",
"deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Sicher, dass dieses Element gelöscht werden soll?} other{Sicher, dass diese {count} Elemente gelöscht werden sollen?}}",
+ "moveUndatedConfirmationDialogMessage": "Einige Artikel haben kein Metadaten-Datum. Ihr aktuelles Datum wird durch diesen Vorgang zurückgesetzt, es sei denn, es wurde ein Metadaten-Datum festgelegt.",
+ "moveUndatedConfirmationDialogSetDate": "Datum einstellen",
"videoResumeDialogMessage": "Soll bei {time} weiter abspielt werden?",
"videoStartOverButtonLabel": "NEU BEGINNEN",
@@ -189,6 +198,14 @@
"renameAlbumDialogLabel": "Neuer Name",
"renameAlbumDialogLabelAlreadyExistsHelper": "Verzeichnis existiert bereits",
+ "renameEntrySetPageTitle": "Umbenennen",
+ "renameEntrySetPagePatternFieldLabel": "Benennungsmuster",
+ "renameEntrySetPageInsertTooltip": "Feld einfügen",
+ "renameEntrySetPagePreview": "Vorschau",
+
+ "renameProcessorCounter": "Zähler",
+ "renameProcessorName": "Name",
+
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Sicher, dass dieses Album und der Inhalt gelöscht werden soll?} other{Sicher, dass dieses Album und deren {count} Elemente gelöscht werden sollen?}}",
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Sicher, dass diese Alben und deren Inhalt gelöscht werden sollen?} other{Sicher, dass diese Alben und deren {count} Elemente gelöscht werden sollen?}}",
@@ -201,6 +218,7 @@
"editEntryDateDialogTitle": "Datum & Uhrzeit",
"editEntryDateDialogSetCustom": "Datum einstellen",
"editEntryDateDialogCopyField": "Von anderem Datum kopieren",
+ "editEntryDateDialogCopyItem": "Von einem anderen Element kopieren",
"editEntryDateDialogExtractFromTitle": "Auszug aus dem Titel",
"editEntryDateDialogShift": "Verschieben",
"editEntryDateDialogSourceFileModifiedDate": "Änderungsdatum der Datei",
@@ -308,10 +326,12 @@
"collectionDeleteFailureFeedback": "{count, plural, =1{1 Element konnte nicht gelöscht werden} other{{count} Elemente konnten nicht gelöscht werden}}",
"collectionCopyFailureFeedback": "{count, plural, =1{1 Element konnte nicht kopiert werden} other{{count} Element konnten nicht kopiert werden}}",
"collectionMoveFailureFeedback": "{count, plural, =1{1 Element konnte nicht verschoben werden} other{{count} Elemente konnten nicht verschoben werden}}",
+ "collectionRenameFailureFeedback": "{count, plural, =1{Fehler beim Umbenennen eines Elements} other{Fehler beim Umbenennen {count} Elemente}}",
"collectionEditFailureFeedback": "{count, plural, =1{1 Element konnte nicht bearbeitet werden} other{{count} 1 Elemente konnten nicht bearbeitet werden}}",
"collectionExportFailureFeedback": "{count, plural, =1{1 Seite konnte nicht exportiert werden} other{{count} Seiten konnten nicht exportiert werden}}",
"collectionCopySuccessFeedback": "{count, plural, =1{1 Element kopier} other{ {count} Elemente kopiert}}",
"collectionMoveSuccessFeedback": "{count, plural, =1{1 Element verschoben} other{{count} Elemente verschoben}}",
+ "collectionRenameSuccessFeedback": "{count, plural, =1{1 Element unmebannt} other{{count} Elemente umbenannt}}",
"collectionEditSuccessFeedback": "{count, plural, =1{1 Element bearbeitet} other{ {count} Elemente bearbeitet}}",
"collectionEmptyFavourites": "Keine Favoriten",
@@ -393,6 +413,7 @@
"settingsConfirmationDialogTitle": "Bestätigungsdialoge",
"settingsConfirmationDialogDeleteItems": "Vor dem endgültigen Löschen von Elementen fragen",
"settingsConfirmationDialogMoveToBinItems": "Vor dem Verschieben von Elementen in den Papierkorb fragen",
+ "settingsConfirmationDialogMoveUndatedItems": "Vor Verschiebung von Objekten ohne Metadaten-Datum fragen",
"settingsNavigationDrawerTile": "Menü Navigation",
"settingsNavigationDrawerEditorTitle": "Menü Navigation",
@@ -501,6 +522,12 @@
"settingsTimeToTakeActionTile": "Zeit zum Reagieren",
"settingsTimeToTakeActionTitle": "Zeit zum Reagieren",
+ "settingsSectionDisplay": "Anzeige",
+ "settingsThemeBrightness": "Thema",
+ "settingsThemeColorHighlights": "Farbige Highlights",
+ "settingsDisplayRefreshRateModeTile": "Bildwiederholrate der Anzeige",
+ "settingsDisplayRefreshRateModeTitle": "Bildwiederholrate",
+
"settingsSectionLanguage": "Sprache & Formate",
"settingsLanguage": "Sprache",
"settingsCoordinateFormatTile": "Koordinatenformat",
@@ -570,4 +597,4 @@
"filePickerOpenFrom": "Öffnen von",
"filePickerNoItems": "Keine Elemente",
"filePickerUseThisFolder": "Diesen Ordner verwenden"
-}
\ No newline at end of file
+}
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index bfcd0e5f5..f5d38548b 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -177,6 +177,13 @@
"accessibilityAnimationsRemove": "Prevent screen effects",
"accessibilityAnimationsKeep": "Keep screen effects",
+ "displayRefreshRatePreferHighest": "Highest rate",
+ "displayRefreshRatePreferLowest": "Lowest rate",
+
+ "themeBrightnessLight": "Light",
+ "themeBrightnessDark": "Dark",
+ "themeBrightnessBlack": "Black",
+
"albumTierNew": "New",
"albumTierPinned": "Pinned",
"albumTierSpecial": "Common",
@@ -282,6 +289,8 @@
"count": {}
}
},
+ "moveUndatedConfirmationDialogMessage": "Save item dates before proceeding?",
+ "moveUndatedConfirmationDialogSetDate": "Save dates",
"videoResumeDialogMessage": "Do you want to resume playing at {time}?",
"@videoResumeDialogMessage": {
@@ -309,6 +318,14 @@
"renameAlbumDialogLabel": "New name",
"renameAlbumDialogLabelAlreadyExistsHelper": "Directory already exists",
+ "renameEntrySetPageTitle": "Rename",
+ "renameEntrySetPagePatternFieldLabel": "Naming pattern",
+ "renameEntrySetPageInsertTooltip": "Insert field",
+ "renameEntrySetPagePreview": "Preview",
+
+ "renameProcessorCounter": "Counter",
+ "renameProcessorName": "Name",
+
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Delete this album and its item?} other{Delete this album and its {count} items?}}",
"@deleteSingleAlbumConfirmationDialogMessage": {
"placeholders": {
@@ -331,6 +348,7 @@
"editEntryDateDialogTitle": "Date & Time",
"editEntryDateDialogSetCustom": "Set custom date",
"editEntryDateDialogCopyField": "Copy from other date",
+ "editEntryDateDialogCopyItem": "Copy from other item",
"editEntryDateDialogExtractFromTitle": "Extract from title",
"editEntryDateDialogShift": "Shift",
"editEntryDateDialogSourceFileModifiedDate": "File modified date",
@@ -453,6 +471,12 @@
"count": {}
}
},
+ "collectionRenameFailureFeedback": "{count, plural, =1{Failed to rename 1 item} other{Failed to rename {count} items}}",
+ "@collectionRenameFailureFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
"collectionEditFailureFeedback": "{count, plural, =1{Failed to edit 1 item} other{Failed to edit {count} items}}",
"@collectionEditFailureFeedback": {
"placeholders": {
@@ -477,6 +501,12 @@
"count": {}
}
},
+ "collectionRenameSuccessFeedback": "{count, plural, =1{Renamed 1 item} other{Renamed {count} items}}",
+ "@collectionRenameSuccessFeedback": {
+ "placeholders": {
+ "count": {}
+ }
+ },
"collectionEditSuccessFeedback": "{count, plural, =1{Edited 1 item} other{Edited {count} items}}",
"@collectionEditSuccessFeedback": {
"placeholders": {
@@ -563,6 +593,7 @@
"settingsConfirmationDialogTitle": "Confirmation Dialogs",
"settingsConfirmationDialogDeleteItems": "Ask before deleting items forever",
"settingsConfirmationDialogMoveToBinItems": "Ask before moving items to the recycle bin",
+ "settingsConfirmationDialogMoveUndatedItems": "Ask before moving undated items",
"settingsNavigationDrawerTile": "Navigation menu",
"settingsNavigationDrawerEditorTitle": "Navigation Menu",
@@ -671,6 +702,12 @@
"settingsTimeToTakeActionTile": "Time to take action",
"settingsTimeToTakeActionTitle": "Time to Take Action",
+ "settingsSectionDisplay": "Display",
+ "settingsThemeBrightness": "Theme",
+ "settingsThemeColorHighlights": "Color highlights",
+ "settingsDisplayRefreshRateModeTile": "Display refresh rate",
+ "settingsDisplayRefreshRateModeTitle": "Refresh Rate",
+
"settingsSectionLanguage": "Language & Formats",
"settingsLanguage": "Language",
"settingsCoordinateFormatTile": "Coordinate format",
diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb
index 99e1ce283..15da9e8a3 100644
--- a/lib/l10n/app_es.arb
+++ b/lib/l10n/app_es.arb
@@ -68,6 +68,8 @@
"entryActionRemoveFavourite": "Quitar de favoritos",
"videoActionCaptureFrame": "Capturar fotograma",
+ "videoActionMute": "Silenciar",
+ "videoActionUnmute": "Dejar de silenciar",
"videoActionPause": "Pausa",
"videoActionPlay": "Reproducir",
"videoActionReplay10": "Retroceder 10 segundos",
@@ -135,6 +137,13 @@
"accessibilityAnimationsRemove": "Prevenir efectos en pantalla",
"accessibilityAnimationsKeep": "Mantener efectos en pantalla",
+ "displayRefreshRatePreferHighest": "Alta tasa",
+ "displayRefreshRatePreferLowest": "Baja tasa",
+
+ "themeBrightnessLight": "Claro",
+ "themeBrightnessDark": "Obscuro",
+ "themeBrightnessBlack": "Negro",
+
"albumTierNew": "Nuevo",
"albumTierPinned": "Fijado",
"albumTierSpecial": "Común",
@@ -151,7 +160,6 @@
"restrictedAccessDialogMessage": "Esta aplicación no tiene permiso para modificar archivos de {directory} en «{volume}».\n\nPor favor use un gestor de archivos o la aplicación de galería preinstalada para mover los elementos a otro directorio.",
"notEnoughSpaceDialogTitle": "Espacio insuficiente",
"notEnoughSpaceDialogMessage": "Esta operación necesita {neededSize} de espacio libre en «{volume}» para completarse, pero sólo hay {freeSize} disponible.",
-
"missingSystemFilePickerDialogTitle": "Selector de archivos del sistema no disponible",
"missingSystemFilePickerDialogMessage": "El selector de archivos del sistema no se encuentra disponible o fue deshabilitado. Por favor habilítelo e intente nuevamente.",
@@ -168,8 +176,9 @@
"noMatchingAppDialogMessage": "No se encontraron aplicaciones para manejar esto.",
"binEntriesConfirmationDialogMessage": "{count, plural, =1{¿Mover este elemento al cesto de basura?} other{¿Mover estos {count} elementos al cesto de basura?}}",
-
"deleteEntriesConfirmationDialogMessage": "{count, plural, =1{¿Está seguro de borrar este elemento?} other{¿Está seguro de querer borrar {count} elementos?}}",
+ "moveUndatedConfirmationDialogMessage": "Algunos elementos no poseen fecha en sus metadatos. Su fecha actual será reemplazada por esta operación a menos que una fecha de metadatos sea fijada.",
+ "moveUndatedConfirmationDialogSetDate": "Fijar fecha",
"videoResumeDialogMessage": "¿Desea reanudar la reproducción a las {time}?",
"videoStartOverButtonLabel": "VOLVER A EMPEZAR",
@@ -189,6 +198,14 @@
"renameAlbumDialogLabel": "Renombrar",
"renameAlbumDialogLabelAlreadyExistsHelper": "El directorio ya existe",
+ "renameEntrySetPageTitle": "Renombrar",
+ "renameEntrySetPagePatternFieldLabel": "Patrón de nombramiento",
+ "renameEntrySetPageInsertTooltip": "Insertar campo",
+ "renameEntrySetPagePreview": "Vista previa",
+
+ "renameProcessorCounter": "Contador",
+ "renameProcessorName": "Nombre",
+
"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?}}",
"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?}}",
@@ -201,6 +218,7 @@
"editEntryDateDialogTitle": "Fecha y hora",
"editEntryDateDialogSetCustom": "Establecer fecha personalizada",
"editEntryDateDialogCopyField": "Copiar de otra fecha",
+ "editEntryDateDialogCopyItem": "Copiar de otro elemento",
"editEntryDateDialogExtractFromTitle": "Extraer del título",
"editEntryDateDialogShift": "Cambiar",
"editEntryDateDialogSourceFileModifiedDate": "Fecha de modificación del archivo",
@@ -308,10 +326,12 @@
"collectionDeleteFailureFeedback": "{count, plural, =1{Error al borrar 1 elemento} other{Error al borrar {count} elementos}}",
"collectionCopyFailureFeedback": "{count, plural, =1{Error al copiar 1 item} other{Error al copiar {count} elementos}}",
"collectionMoveFailureFeedback": "{count, plural, =1{Error al mover 1 elemento} other{Error al mover {count} elementos}}",
+ "collectionRenameFailureFeedback": "{count, plural, =1{Error al renombrar 1 elemento} other{Error al renombrar {count} elementos}}",
"collectionEditFailureFeedback": "{count, plural, =1{Error al editar 1 elemento} other{Error al editar {count} elementos}}",
"collectionExportFailureFeedback": "{count, plural, =1{Error al exportar 1 página} other{Error al exportar {count} páginas}}",
"collectionCopySuccessFeedback": "{count, plural, =1{1 elemento copiado} other{Copiados{count} elementos}}",
"collectionMoveSuccessFeedback": "{count, plural, =1{1 elemento movido} other{Movidos {count} elementos}}",
+ "collectionRenameSuccessFeedback": "{count, plural, =1{1 elemento renombrado} other{Renombrados {count} elementos}}",
"collectionEditSuccessFeedback": "{count, plural, =1{1 elemento editado} other{Editados {count} elementos}}",
"collectionEmptyFavourites": "Sin favoritos",
@@ -393,6 +413,7 @@
"settingsConfirmationDialogTitle": "Diálogos de confirmación",
"settingsConfirmationDialogDeleteItems": "Preguntar antes de eliminar elementos permanentemente",
"settingsConfirmationDialogMoveToBinItems": "Preguntar antes de mover elementos al cesto de basura",
+ "settingsConfirmationDialogMoveUndatedItems": "Preguntar antes de mover elementos sin una fecha de metadatos",
"settingsNavigationDrawerTile": "Menú de navegación",
"settingsNavigationDrawerEditorTitle": "Menú de navegación",
@@ -501,6 +522,12 @@
"settingsTimeToTakeActionTile": "Retraso para ejecutar una acción",
"settingsTimeToTakeActionTitle": "Retraso para ejecutar una acción",
+ "settingsSectionDisplay": "Pantalla",
+ "settingsThemeBrightness": "Tema",
+ "settingsThemeColorHighlights": "Acentos de color",
+ "settingsDisplayRefreshRateModeTile": "Tasa de refresco de la pantalla",
+ "settingsDisplayRefreshRateModeTitle": "Tasa de refresco",
+
"settingsSectionLanguage": "Idioma y formatos",
"settingsLanguage": "Idioma",
"settingsCoordinateFormatTile": "Formato de coordenadas",
diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb
index 1c9be0f58..70c61c831 100644
--- a/lib/l10n/app_fr.arb
+++ b/lib/l10n/app_fr.arb
@@ -137,6 +137,13 @@
"accessibilityAnimationsRemove": "Empêchez certains effets de l’écran",
"accessibilityAnimationsKeep": "Conserver les effets de l’écran",
+ "displayRefreshRatePreferHighest": "Fréquence maximale",
+ "displayRefreshRatePreferLowest": "Fréquence minimale",
+
+ "themeBrightnessLight": "Clair",
+ "themeBrightnessDark": "Sombre",
+ "themeBrightnessBlack": "Noir",
+
"albumTierNew": "Nouveaux",
"albumTierPinned": "Épinglés",
"albumTierSpecial": "Standards",
@@ -170,6 +177,8 @@
"binEntriesConfirmationDialogMessage": "{count, plural, =1{Mettre cet élément à la corbeille ?} other{Mettre ces {count} éléments à la corbeille ?}}",
"deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Supprimer cet élément ?} other{Supprimer ces {count} éléments ?}}",
+ "moveUndatedConfirmationDialogMessage": "Sauvegarder les dates des éléments avant de continuer?",
+ "moveUndatedConfirmationDialogSetDate": "Sauvegarder les dates",
"videoResumeDialogMessage": "Voulez-vous reprendre la lecture à {time} ?",
"videoStartOverButtonLabel": "RECOMMENCER",
@@ -189,6 +198,14 @@
"renameAlbumDialogLabel": "Nouveau nom",
"renameAlbumDialogLabelAlreadyExistsHelper": "Le dossier existe déjà",
+ "renameEntrySetPageTitle": "Renommage",
+ "renameEntrySetPagePatternFieldLabel": "Modèle de nommage",
+ "renameEntrySetPageInsertTooltip": "Ajouter un champ",
+ "renameEntrySetPagePreview": "Aperçu",
+
+ "renameProcessorCounter": "Compteur",
+ "renameProcessorName": "Nom",
+
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Supprimer cet album et son élément ?} other{Supprimer cet album et ses {count} éléments ?}}",
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Supprimer ces albums et leur élément ?} other{Supprimer ces albums et leurs {count} éléments ?}}",
@@ -201,6 +218,7 @@
"editEntryDateDialogTitle": "Date & Heure",
"editEntryDateDialogSetCustom": "Régler une date personnalisée",
"editEntryDateDialogCopyField": "Copier d’une autre date",
+ "editEntryDateDialogCopyItem": "Copier d’un autre élément",
"editEntryDateDialogExtractFromTitle": "Extraire du titre",
"editEntryDateDialogShift": "Décaler",
"editEntryDateDialogSourceFileModifiedDate": "Date de modification du fichier",
@@ -308,10 +326,12 @@
"collectionDeleteFailureFeedback": "{count, plural, =1{Échec de la suppression d’1 élément} other{Échec de la suppression de {count} éléments}}",
"collectionCopyFailureFeedback": "{count, plural, =1{Échec de la copie d’1 élément} other{Échec de la copie de {count} éléments}}",
"collectionMoveFailureFeedback": "{count, plural, =1{Échec du déplacement d’1 élément} other{Échec du déplacement de {count} éléments}}",
+ "collectionRenameFailureFeedback": "{count, plural, =1{Échec du renommage d’1 élément} other{Échec du renommage de {count} éléments}}",
"collectionEditFailureFeedback": "{count, plural, =1{Échec de la modification d’1 élément} other{Échec de la modification de {count} éléments}}",
"collectionExportFailureFeedback": "{count, plural, =1{Échec de l’export d’1 page} other{Échec de l’export de {count} pages}}",
"collectionCopySuccessFeedback": "{count, plural, =1{1 élément copié} other{{count} éléments copiés}}",
"collectionMoveSuccessFeedback": "{count, plural, =1{1 élément déplacé} other{{count} éléments déplacés}}",
+ "collectionRenameSuccessFeedback": "{count, plural, =1{1 élément renommé} other{{count} éléments renommés}}",
"collectionEditSuccessFeedback": "{count, plural, =1{1 élément modifié} other{{count} éléments modifiés}}",
"collectionEmptyFavourites": "Aucun favori",
@@ -393,6 +413,7 @@
"settingsConfirmationDialogTitle": "Demandes de confirmation",
"settingsConfirmationDialogDeleteItems": "Suppression définitive d’éléments",
"settingsConfirmationDialogMoveToBinItems": "Mise d’éléments à la corbeille",
+ "settingsConfirmationDialogMoveUndatedItems": "Déplacement d’éléments non datés",
"settingsNavigationDrawerTile": "Menu de navigation",
"settingsNavigationDrawerEditorTitle": "Menu de navigation",
@@ -501,6 +522,12 @@
"settingsTimeToTakeActionTile": "Délai pour effectuer une action",
"settingsTimeToTakeActionTitle": "Délai pour effectuer une action",
+ "settingsSectionDisplay": "Affichage",
+ "settingsThemeBrightness": "Thème",
+ "settingsThemeColorHighlights": "Surlignages colorés",
+ "settingsDisplayRefreshRateModeTile": "Fréquence d’actualisation de l'écran",
+ "settingsDisplayRefreshRateModeTitle": "Fréquence d’actualisation",
+
"settingsSectionLanguage": "Langue & Formats",
"settingsLanguage": "Langue",
"settingsCoordinateFormatTile": "Format de coordonnées",
diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb
index 93a384738..7c848b5f5 100644
--- a/lib/l10n/app_id.arb
+++ b/lib/l10n/app_id.arb
@@ -68,6 +68,8 @@
"entryActionRemoveFavourite": "Hapus dari favorit",
"videoActionCaptureFrame": "Tangkap bingkai",
+ "videoActionMute": "Mute",
+ "videoActionUnmute": "Unmute",
"videoActionPause": "Henti",
"videoActionPlay": "Mainkan",
"videoActionReplay10": "Mundurkan 10 detik",
@@ -112,6 +114,11 @@
"videoLoopModeShortOnly": "Hanya video pendek",
"videoLoopModeAlways": "Selalu",
+ "videoControlsPlay": "Putar",
+ "videoControlsPlaySeek": "Putar dan cari",
+ "videoControlsPlayOutside": "Buka dengan pemutar lain",
+ "videoControlsNone": "Tidak ada",
+
"mapStyleGoogleNormal": "Google Maps",
"mapStyleGoogleHybrid": "Google Maps (Hybrid)",
"mapStyleGoogleTerrain": "Google Maps (Terrain)",
@@ -130,6 +137,13 @@
"accessibilityAnimationsRemove": "Mencegah efek layar",
"accessibilityAnimationsKeep": "Simpan efek layar",
+ "displayRefreshRatePreferHighest": "Penyegaran tertinggi",
+ "displayRefreshRatePreferLowest": "Penyegaran terendah",
+
+ "themeBrightnessLight": "Terang",
+ "themeBrightnessDark": "Gelap",
+ "themeBrightnessBlack": "Hitam",
+
"albumTierNew": "Baru",
"albumTierPinned": "Disemat",
"albumTierSpecial": "Umum",
@@ -163,6 +177,8 @@
"binEntriesConfirmationDialogMessage": "{count, plural, =1{Pindahkan benda ini ke tong sampah?} other{Pindahkan {count} benda ke tempat sampah?}}",
"deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Anda yakin ingin menghapus benda ini?} other{Apakah Anda yakin ingin menghapus {count} benda?}}",
+ "moveUndatedConfirmationDialogMessage": "Beberapa benda tidak mempunyai tanggal metadata. Tanggal mereka sekarang akan diatur ulang dengan operasi ini kecuali ada tanggal metadata yang ditetapkan.",
+ "moveUndatedConfirmationDialogSetDate": "Atur tanggal",
"videoResumeDialogMessage": "Apakah Anda ingin melanjutkan di {time}?",
"videoStartOverButtonLabel": "ULANG DARI AWAL",
@@ -182,6 +198,14 @@
"renameAlbumDialogLabel": "Nama baru",
"renameAlbumDialogLabelAlreadyExistsHelper": "Direktori sudah ada",
+ "renameEntrySetPageTitle": "Ganti nama",
+ "renameEntrySetPagePatternFieldLabel": "Pola penamaan",
+ "renameEntrySetPageInsertTooltip": "Masukkan bidang",
+ "renameEntrySetPagePreview": "Pratinjau",
+
+ "renameProcessorCounter": "Menangkal",
+ "renameProcessorName": "Nama",
+
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Anda yakin ingin menghapus album ini dan bendanya?} other{Apakah Anda yakin ingin menghapus album ini dan {count} bendanya?}}",
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Yakin ingin menghapus album ini dan bendanya?} other{Anda yakin ingin menghapus album ini dan {count} bendanya?}}",
@@ -194,6 +218,7 @@
"editEntryDateDialogTitle": "Tanggal & Waktu",
"editEntryDateDialogSetCustom": "Atur tanggal khusus",
"editEntryDateDialogCopyField": "Salin dari tanggal lain",
+ "editEntryDateDialogCopyItem": "Salin dari benda lain",
"editEntryDateDialogExtractFromTitle": "Ekstrak dari judul",
"editEntryDateDialogShift": "Geser",
"editEntryDateDialogSourceFileModifiedDate": "Tanggal modifikasi file",
@@ -301,10 +326,12 @@
"collectionDeleteFailureFeedback": "{count, plural, other{Gagal untuk menghapus {count} benda}}",
"collectionCopyFailureFeedback": "{count, plural, other{Gagal untuk menyalin {count} benda}}",
"collectionMoveFailureFeedback": "{count, plural, other{Gagal untuk menggerakkan {count} benda}}",
+ "collectionRenameFailureFeedback": "{count, plural, other{Gagal untuk menggantikan nama {count} benda}}",
"collectionEditFailureFeedback": "{count, plural, other{Gagal untuk mengubah {count} benda}}",
"collectionExportFailureFeedback": "{count, plural, other{Gagal untuk mengekspor {count} halaman}}",
"collectionCopySuccessFeedback": "{count, plural, other{Menyalin {count} benda}}",
"collectionMoveSuccessFeedback": "{count, plural, other{{count} benda terpindah}}",
+ "collectionRenameSuccessFeedback": "{count, plural, other{Tergantikan nama untuk {count} benda}}",
"collectionEditSuccessFeedback": "{count, plural, other{Mengubah {count} benda}}",
"collectionEmptyFavourites": "Tidak ada favorit",
@@ -386,6 +413,8 @@
"settingsConfirmationDialogTitle": "Dialog Konfirmasi",
"settingsConfirmationDialogDeleteItems": "Tanya sebelum menghapus benda selamanya",
"settingsConfirmationDialogMoveToBinItems": "Tanya sebelum memindahkan benda ke tong sampah",
+ "settingsConfirmationDialogMoveUndatedItems": "Tanyakan sebelum memindahkan barang tanpa metadata tanggal",
+
"settingsNavigationDrawerTile": "Menu navigasi",
"settingsNavigationDrawerEditorTitle": "Menu Navigasi",
"settingsNavigationDrawerBanner": "Sentuh dan tahan untuk memindahkan dan menyusun ulang benda menu.",
@@ -429,6 +458,7 @@
"settingsViewerShowInformation": "Tampilkan informasi",
"settingsViewerShowInformationSubtitle": "Tampilkan judul, tanggal, lokasi, dll.",
"settingsViewerShowShootingDetails": "Tampilkan detail pemotretan",
+ "settingsViewerShowOverlayThumbnails": "Tampilkan thumbnail",
"settingsViewerEnableOverlayBlurEffect": "Efek Kabur",
"settingsVideoPageTitle": "Pengaturan Video",
@@ -454,6 +484,13 @@
"settingsSubtitleThemeTextAlignmentCenter": "Tengah",
"settingsSubtitleThemeTextAlignmentRight": "Kanan",
+ "settingsVideoControlsTile": "Kontrol",
+ "settingsVideoControlsTitle": "Kontrol",
+ "settingsVideoButtonsTile": "Tombol",
+ "settingsVideoButtonsTitle": "Tombol",
+ "settingsVideoGestureDoubleTapTogglePlay": "Ketuk dua kali untuk mainkan/hentikan",
+ "settingsVideoGestureSideDoubleTapSeek": "Ketuk dua kali di tepi layar untuk mencari kebelakang/kedepan",
+
"settingsSectionPrivacy": "Privasi",
"settingsAllowInstalledAppAccess": "Izinkan akses ke inventori aplikasi",
"settingsAllowInstalledAppAccessSubtitle": "Digunakan untuk meningkatkan tampilan album",
@@ -485,6 +522,12 @@
"settingsTimeToTakeActionTile": "Waktu untuk mengambil tindakan",
"settingsTimeToTakeActionTitle": "Saatnya Bertindak",
+ "settingsSectionDisplay": "Tampilan",
+ "settingsThemeBrightness": "Tema",
+ "settingsThemeColorHighlights": "Highlight warna",
+ "settingsDisplayRefreshRateModeTile": "Tingkat penyegaran tampilan",
+ "settingsDisplayRefreshRateModeTitle": "Tingkat Penyegaran",
+
"settingsSectionLanguage": "Bahasa & Format",
"settingsLanguage": "Bahasa",
"settingsCoordinateFormatTile": "Format koordinat",
diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb
index acc36340d..1b9d08226 100644
--- a/lib/l10n/app_ja.arb
+++ b/lib/l10n/app_ja.arb
@@ -137,6 +137,13 @@
"accessibilityAnimationsRemove": "画面エフェクトを利用しない",
"accessibilityAnimationsKeep": "画面エフェクトを利用",
+ "displayRefreshRatePreferHighest": "高レート",
+ "displayRefreshRatePreferLowest": "低レート",
+
+ "themeBrightnessLight": "ライト",
+ "themeBrightnessDark": "ダーク",
+ "themeBrightnessBlack": "黒",
+
"albumTierNew": "新規",
"albumTierPinned": "固定",
"albumTierSpecial": "全体",
@@ -170,6 +177,8 @@
"binEntriesConfirmationDialogMessage": "{count, plural, =1{このアイテムをごみ箱に移動しますか?} other{{count} 件のアイテムをごみ箱に移動しますか?}}",
"deleteEntriesConfirmationDialogMessage": "{count, plural, =1{このアイテムを削除しますか?} other{{count} 件のアイテムを削除しますか?}}",
+ "moveUndatedConfirmationDialogMessage": "いくつかのアイテムはメタデータ上に日付がありません。メタデータ上の日付が設定されない場合、この操作によりこれらの現在の日付はリセットされます",
+ "moveUndatedConfirmationDialogSetDate": "日付を設定",
"videoResumeDialogMessage": " {time} の時点から再生を再開しますか?",
"videoStartOverButtonLabel": "最初から再生",
@@ -189,6 +198,14 @@
"renameAlbumDialogLabel": "新しい名前",
"renameAlbumDialogLabelAlreadyExistsHelper": "ディレクトリが既に存在します",
+ "renameEntrySetPageTitle": "名前を変更",
+ "renameEntrySetPagePatternFieldLabel": "名前付けのパターン",
+ "renameEntrySetPageInsertTooltip": "フィールドを挿入",
+ "renameEntrySetPagePreview": "プレビュー",
+
+ "renameProcessorCounter": "連番",
+ "renameProcessorName": "名前",
+
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{このアルバムとアルバム内のアイテムを削除しますか?} other{このアルバムとアルバム内の {count} 件のアイテムを削除しますか?}}",
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{これらのアルバムとアルバム内のアイテムを削除しますか?} other{これらのアルバムとアルバム内の {count} 件のアイテムを削除しますか?}}",
@@ -201,6 +218,7 @@
"editEntryDateDialogTitle": "日時",
"editEntryDateDialogSetCustom": "日を設定する",
"editEntryDateDialogCopyField": "他の日からコピーする",
+ "editEntryDateDialogCopyItem": "他のアイテムからコピーする",
"editEntryDateDialogExtractFromTitle": "タイトルから抽出する",
"editEntryDateDialogShift": "シフト",
"editEntryDateDialogSourceFileModifiedDate": "ファイル更新日",
@@ -308,10 +326,12 @@
"collectionDeleteFailureFeedback": "{count, plural, other{{count} 件のアイテムを削除できませんでした}}",
"collectionCopyFailureFeedback": "{count, plural, other{{count} 件のアイテムをコピーできませんでした}}",
"collectionMoveFailureFeedback": "{count, plural, other{{count} 件のアイテムを移動できませんでした}}",
+ "collectionRenameFailureFeedback": "{count, plural, other{{count} 件のアイテム名を変更できませんでした}}",
"collectionEditFailureFeedback": "{count, plural, other{{count} 件のアイテムを編集できませんでした}}",
"collectionExportFailureFeedback": "{count, plural, other{{count} ページをエクスポートできませんでした}}",
"collectionCopySuccessFeedback": "{count, plural, other{{count} 件のアイテムをコピーしました}}",
"collectionMoveSuccessFeedback": "{count, plural, other{{count} 件のアイテムを移動しました}}",
+ "collectionRenameSuccessFeedback": "{count, plural, other{{count} 件のアイテム名を変更しました}}",
"collectionEditSuccessFeedback": "{count, plural, other{{count} 件のアイテムを編集しました}}",
"collectionEmptyFavourites": "お気に入りはありません",
@@ -393,6 +413,7 @@
"settingsConfirmationDialogTitle": "確認メッセージ",
"settingsConfirmationDialogDeleteItems": "アイテムを完全に削除する前に確認",
"settingsConfirmationDialogMoveToBinItems": "アイテムをごみ箱に移動する前に確認",
+ "settingsConfirmationDialogMoveUndatedItems": "メタデータ上に日付のないアイテムを移動する前に確認",
"settingsNavigationDrawerTile": "ナビゲーション メニュー",
"settingsNavigationDrawerEditorTitle": "ナビゲーション メニュー",
@@ -448,8 +469,6 @@
"settingsVideoLoopModeTile": "ループ モード",
"settingsVideoLoopModeTitle": "ループ モード",
- "settingsVideoQuickActionsTile": "動画のクイック アクション",
- "settingsVideoQuickActionEditorTitle": "クイック アクション",
"settingsSubtitleThemeTile": "字幕",
"settingsSubtitleThemeTitle": "字幕",
"settingsSubtitleThemeSample": "これはサンプルです。",
@@ -503,6 +522,12 @@
"settingsTimeToTakeActionTile": "操作までの時間",
"settingsTimeToTakeActionTitle": "操作までの時間",
+ "settingsSectionDisplay": "ディスプレイ",
+ "settingsThemeBrightness": "テーマ",
+ "settingsThemeColorHighlights": "カラー強調表示",
+ "settingsDisplayRefreshRateModeTile": "ディスプレイ リフレッシュ レート",
+ "settingsDisplayRefreshRateModeTitle": "リフレッシュレート",
+
"settingsSectionLanguage": "言語と形式",
"settingsLanguage": "言語",
"settingsCoordinateFormatTile": "座標形式",
diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb
index b5a4fd993..f46364bc0 100644
--- a/lib/l10n/app_ko.arb
+++ b/lib/l10n/app_ko.arb
@@ -137,6 +137,13 @@
"accessibilityAnimationsRemove": "화면 효과 제한",
"accessibilityAnimationsKeep": "화면 효과 유지",
+ "displayRefreshRatePreferHighest": "가장 높은 재생률",
+ "displayRefreshRatePreferLowest": "가장 낮은 재생률",
+
+ "themeBrightnessLight": "라이트",
+ "themeBrightnessDark": "다크",
+ "themeBrightnessBlack": "검은색",
+
"albumTierNew": "신규",
"albumTierPinned": "고정",
"albumTierSpecial": "기본",
@@ -170,6 +177,8 @@
"binEntriesConfirmationDialogMessage": "{count, plural, =1{이 항목을 휴지통으로 이동하시겠습니까?} other{항목 {count}개를 휴지통으로 이동하시겠습니까?}}",
"deleteEntriesConfirmationDialogMessage": "{count, plural, =1{이 항목을 삭제하시겠습니까?} other{항목 {count}개를 삭제하시겠습니까?}}",
+ "moveUndatedConfirmationDialogMessage": "이 작업을 계속하기 전에 항목의 날짜를 지정하시겠습니까?",
+ "moveUndatedConfirmationDialogSetDate": "날짜 지정하기",
"videoResumeDialogMessage": "{time}부터 재개하시겠습니까?",
"videoStartOverButtonLabel": "처음부터",
@@ -189,6 +198,14 @@
"renameAlbumDialogLabel": "앨범 이름",
"renameAlbumDialogLabelAlreadyExistsHelper": "사용 중인 이름입니다",
+ "renameEntrySetPageTitle": "이름 변경",
+ "renameEntrySetPagePatternFieldLabel": "이름 양식",
+ "renameEntrySetPageInsertTooltip": "필드 추가",
+ "renameEntrySetPagePreview": "미리보기",
+
+ "renameProcessorCounter": "숫자 증가",
+ "renameProcessorName": "이름",
+
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, other{이 앨범의 항목 {count}개를 삭제하시겠습니까?}}",
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, other{이 앨범들의 항목 {count}개를 삭제하시겠습니까?}}",
@@ -201,6 +218,7 @@
"editEntryDateDialogTitle": "날짜 및 시간",
"editEntryDateDialogSetCustom": "지정 날짜로 편집",
"editEntryDateDialogCopyField": "다른 날짜에서 지정",
+ "editEntryDateDialogCopyItem": "다른 항목에서 지정",
"editEntryDateDialogExtractFromTitle": "제목에서 추출",
"editEntryDateDialogShift": "시간 이동",
"editEntryDateDialogSourceFileModifiedDate": "파일 수정한 날짜",
@@ -308,10 +326,12 @@
"collectionDeleteFailureFeedback": "{count, plural, other{항목 {count}개를 삭제하지 못했습니다}}",
"collectionCopyFailureFeedback": "{count, plural, other{항목 {count}개를 복사하지 못했습니다}}",
"collectionMoveFailureFeedback": "{count, plural, other{항목 {count}개를 이동하지 못했습니다}}",
+ "collectionRenameFailureFeedback": "{count, plural, other{항목 {count}개의 이름을 변경하지 못했습니다}}",
"collectionEditFailureFeedback": "{count, plural, other{항목 {count}개를 편집하지 못했습니다}}",
"collectionExportFailureFeedback": "{count, plural, other{항목 {count}개를 내보내지 못했습니다}}",
"collectionCopySuccessFeedback": "{count, plural, other{항목 {count}개를 복사했습니다}}",
"collectionMoveSuccessFeedback": "{count, plural, other{항목 {count}개를 이동했습니다}}",
+ "collectionRenameSuccessFeedback": "{count, plural, other{항목 {count}개의 이름을 변경했습니다}}",
"collectionEditSuccessFeedback": "{count, plural, other{항목 {count}개를 편집했습니다}}",
"collectionEmptyFavourites": "즐겨찾기가 없습니다",
@@ -393,6 +413,7 @@
"settingsConfirmationDialogTitle": "확정 대화상자",
"settingsConfirmationDialogDeleteItems": "항목을 완전히 삭제 시",
"settingsConfirmationDialogMoveToBinItems": "항목을 휴지통으로 이동 시",
+ "settingsConfirmationDialogMoveUndatedItems": "날짜가 지정되지 않은 항목을 이동 시",
"settingsNavigationDrawerTile": "탐색 메뉴",
"settingsNavigationDrawerEditorTitle": "탐색 메뉴",
@@ -501,6 +522,12 @@
"settingsTimeToTakeActionTile": "액션 취하기 전 대기 시간",
"settingsTimeToTakeActionTitle": "액션 취하기 전 대기 시간",
+ "settingsSectionDisplay": "디스플레이",
+ "settingsThemeBrightness": "테마",
+ "settingsThemeColorHighlights": "색 강조",
+ "settingsDisplayRefreshRateModeTile": "화면 재생률",
+ "settingsDisplayRefreshRateModeTitle": "화면 재생률",
+
"settingsSectionLanguage": "언어 및 표시 형식",
"settingsLanguage": "언어",
"settingsCoordinateFormatTile": "좌표 표현",
diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb
index 55b03d590..8eedc96c6 100644
--- a/lib/l10n/app_pt.arb
+++ b/lib/l10n/app_pt.arb
@@ -137,6 +137,13 @@
"accessibilityAnimationsRemove": "Prevenir efeitos de tela",
"accessibilityAnimationsKeep": "Manter efeitos de tela",
+ "displayRefreshRatePreferHighest": "Taxa mais alta",
+ "displayRefreshRatePreferLowest": "Taxa mais baixa",
+
+ "themeBrightnessLight": "Claro",
+ "themeBrightnessDark": "Escuro",
+ "themeBrightnessBlack": "Preto",
+
"albumTierNew": "Novo",
"albumTierPinned": "Fixada",
"albumTierSpecial": "Comum",
@@ -170,6 +177,8 @@
"binEntriesConfirmationDialogMessage": "{count, plural, =1{Mover esse item para a lixeira?} other{Mova estes {count} itens para a lixeira?}}",
"deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Tem certeza de que deseja excluir este item?} other{Tem certeza de que deseja excluir estes {count} itens?}}",
+ "moveUndatedConfirmationDialogMessage": "Alguns itens não têm data de metadados. Sua data atual será redefinida por esta operação, a menos que um data de metadados é definida.",
+ "moveUndatedConfirmationDialogSetDate": "Definir data",
"videoResumeDialogMessage": "Deseja continuar jogando em {time}?",
"videoStartOverButtonLabel": "RECOMEÇAR",
@@ -189,6 +198,14 @@
"renameAlbumDialogLabel": "Novo nome",
"renameAlbumDialogLabelAlreadyExistsHelper": "O diretório já existe",
+ "renameEntrySetPageTitle": "Renomear",
+ "renameEntrySetPagePatternFieldLabel": "Padrão de nomeação",
+ "renameEntrySetPageInsertTooltip": "Inserir campo",
+ "renameEntrySetPagePreview": "Visualizar",
+
+ "renameProcessorCounter": "Contador",
+ "renameProcessorName": "Nome",
+
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Tem certeza de que deseja excluir este álbum e seu item?} other{Tem certeza de que deseja excluir este álbum e seus {count} itens?}}",
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Tem certeza de que deseja excluir estes álbuns e seus itens?} other{Tem certeza de que deseja excluir estes álbuns e seus {count} itens?}}",
@@ -201,6 +218,7 @@
"editEntryDateDialogTitle": "Data e hora",
"editEntryDateDialogSetCustom": "Definir data personalizada",
"editEntryDateDialogCopyField": "Copiar de outra data",
+ "editEntryDateDialogCopyItem": "Copiar de outro item",
"editEntryDateDialogExtractFromTitle": "Extrair do título",
"editEntryDateDialogShift": "Mudança",
"editEntryDateDialogSourceFileModifiedDate": "Data de modificação do arquivo",
@@ -308,10 +326,12 @@
"collectionDeleteFailureFeedback": "{count, plural, =1{Falha ao excluir 1 item} other{Falha ao excluir {count} itens}}",
"collectionCopyFailureFeedback": "{count, plural, =1{Falha ao copiar 1 item} other{Falha ao copiar {count} itens}}",
"collectionMoveFailureFeedback": "{count, plural, =1{Falha ao mover 1 item} other{Falha ao mover {count} itens}}",
+ "collectionRenameFailureFeedback": "{count, plural, =1{Falhei em renomear 1 item} other{Falha ao renomear {count} itens}}",
"collectionEditFailureFeedback": "{count, plural, =1{Falha ao editar 1 item} other{Falha ao editar {count} itens}}",
"collectionExportFailureFeedback": "{count, plural, =1{Falha ao exportar 1 página} other{Falha ao exportar {count} páginas}}",
"collectionCopySuccessFeedback": "{count, plural, =1{1 item copiado} other{Copiado {count} itens}}",
"collectionMoveSuccessFeedback": "{count, plural, =1{1 item movido} other{Mudou-se {count} itens}}",
+ "collectionRenameSuccessFeedback": "{count, plural, =1{1 item renomeado} other{Renomeado {count} itens}}",
"collectionEditSuccessFeedback": "{count, plural, =1{Editado 1 item} other{Editado {count} itens}}",
"collectionEmptyFavourites": "Nenhum favorito",
@@ -393,6 +413,7 @@
"settingsConfirmationDialogTitle": "Caixas de diálogo de confirmação",
"settingsConfirmationDialogDeleteItems": "Pergunte antes de excluir itens para sempre",
"settingsConfirmationDialogMoveToBinItems": "Pergunte antes de mover itens para a lixeira",
+ "settingsConfirmationDialogMoveUndatedItems": "Pergunte antes de mover itens sem data de metadados",
"settingsNavigationDrawerTile": "Menu de navegação",
"settingsNavigationDrawerEditorTitle": "Menu de navegação",
@@ -501,6 +522,12 @@
"settingsTimeToTakeActionTile": "Tempo para executar uma ação",
"settingsTimeToTakeActionTitle": "Tempo para executar uma ação",
+ "settingsSectionDisplay": "Tela",
+ "settingsThemeBrightness": "Tema",
+ "settingsThemeColorHighlights": "Destaques de cores",
+ "settingsDisplayRefreshRateModeTile": "Taxa de atualização de exibição",
+ "settingsDisplayRefreshRateModeTitle": "Taxa de atualização",
+
"settingsSectionLanguage": "Idioma e Formatos",
"settingsLanguage": "Língua",
"settingsCoordinateFormatTile": "Formato de coordenadas",
diff --git a/lib/main_common.dart b/lib/main_common.dart
index 1a1820c9d..775607d8b 100644
--- a/lib/main_common.dart
+++ b/lib/main_common.dart
@@ -30,8 +30,9 @@ void mainCommon(AppFlavor flavor) {
// Errors during the widget build phase will show by default:
// - in debug mode: error on red background
- // - in release mode: plain grey background
+ // - in profile/release mode: plain grey background
// This can be modified via `ErrorWidget.builder`
+ // ErrorWidget.builder = (details) => ErrorWidget(details.exception);
runApp(AvesApp(flavor: flavor));
}
diff --git a/lib/model/actions/chip_set_actions.dart b/lib/model/actions/chip_set_actions.dart
index 65ce55e5a..f78daabeb 100644
--- a/lib/model/actions/chip_set_actions.dart
+++ b/lib/model/actions/chip_set_actions.dart
@@ -124,7 +124,7 @@ extension ExtraChipSetAction on ChipSetAction {
return AIcons.unpin;
// selecting (single filter)
case ChipSetAction.rename:
- return AIcons.rename;
+ return AIcons.name;
case ChipSetAction.setCover:
return AIcons.setCover;
}
diff --git a/lib/model/actions/entry_actions.dart b/lib/model/actions/entry_actions.dart
index b9282b429..d43f54cf3 100644
--- a/lib/model/actions/entry_actions.dart
+++ b/lib/model/actions/entry_actions.dart
@@ -177,7 +177,8 @@ extension ExtraEntryAction on EntryAction {
switch (this) {
case EntryAction.debug:
return ShaderMask(
- shaderCallback: AColors.debugGradient.createShader,
+ shaderCallback: AvesColorsData.debugGradient.createShader,
+ blendMode: BlendMode.srcIn,
child: child,
);
default:
@@ -200,7 +201,7 @@ extension ExtraEntryAction on EntryAction {
case EntryAction.print:
return AIcons.print;
case EntryAction.rename:
- return AIcons.rename;
+ return AIcons.name;
case EntryAction.copy:
return AIcons.copy;
case EntryAction.move:
diff --git a/lib/model/actions/entry_info_actions.dart b/lib/model/actions/entry_info_actions.dart
index 1498c8066..313bdb2e1 100644
--- a/lib/model/actions/entry_info_actions.dart
+++ b/lib/model/actions/entry_info_actions.dart
@@ -55,7 +55,8 @@ extension ExtraEntryInfoAction on EntryInfoAction {
switch (this) {
case EntryInfoAction.debug:
return ShaderMask(
- shaderCallback: AColors.debugGradient.createShader,
+ shaderCallback: AvesColorsData.debugGradient.createShader,
+ blendMode: BlendMode.srcIn,
child: child,
);
default:
diff --git a/lib/model/actions/entry_set_actions.dart b/lib/model/actions/entry_set_actions.dart
index 7446ff8e4..1a9ad51b5 100644
--- a/lib/model/actions/entry_set_actions.dart
+++ b/lib/model/actions/entry_set_actions.dart
@@ -23,6 +23,7 @@ enum EntrySetAction {
restore,
copy,
move,
+ rename,
toggleFavourite,
rotateCCW,
rotateCW,
@@ -68,6 +69,7 @@ class EntrySetActions {
EntrySetAction.restore,
EntrySetAction.copy,
EntrySetAction.move,
+ EntrySetAction.rename,
EntrySetAction.toggleFavourite,
EntrySetAction.map,
EntrySetAction.stats,
@@ -81,6 +83,7 @@ class EntrySetActions {
EntrySetAction.delete,
EntrySetAction.copy,
EntrySetAction.move,
+ EntrySetAction.rename,
EntrySetAction.toggleFavourite,
EntrySetAction.map,
EntrySetAction.stats,
@@ -137,6 +140,8 @@ extension ExtraEntrySetAction on EntrySetAction {
return context.l10n.collectionActionCopy;
case EntrySetAction.move:
return context.l10n.collectionActionMove;
+ case EntrySetAction.rename:
+ return context.l10n.entryActionRename;
case EntrySetAction.toggleFavourite:
// different data depending on toggle state
return context.l10n.entryActionAddFavourite;
@@ -200,6 +205,8 @@ extension ExtraEntrySetAction on EntrySetAction {
return AIcons.copy;
case EntrySetAction.move:
return AIcons.move;
+ case EntrySetAction.rename:
+ return AIcons.name;
case EntrySetAction.toggleFavourite:
// different data depending on toggle state
return AIcons.favourite;
diff --git a/lib/model/entry.dart b/lib/model/entry.dart
index 2e5dedd16..0631fe22b 100644
--- a/lib/model/entry.dart
+++ b/lib/model/entry.dart
@@ -167,6 +167,7 @@ class AvesEntry {
_directory = null;
_filename = null;
_extension = null;
+ _bestTitle = null;
}
String? get path => _path;
@@ -258,9 +259,10 @@ class AvesEntry {
bool get canRotateAndFlip => canEdit && canEditExif;
// as of androidx.exifinterface:exifinterface:1.3.3
+ // `exifinterface` declares support for DNG, but `exifinterface` strips non-standard Exif tags when saving attributes,
+ // and DNG requires DNG-specific tags saved along standard Exif. So `exifinterface` actually breaks DNG files.
bool get canEditExif {
switch (mimeType.toLowerCase()) {
- case MimeTypes.dng:
case MimeTypes.jpeg:
case MimeTypes.png:
case MimeTypes.webp:
@@ -454,7 +456,7 @@ class AvesEntry {
String? _bestTitle;
String? get bestTitle {
- _bestTitle ??= _catalogMetadata?.xmpTitleDescription?.isNotEmpty == true ? _catalogMetadata!.xmpTitleDescription : sourceTitle;
+ _bestTitle ??= _catalogMetadata?.xmpTitleDescription?.isNotEmpty == true ? _catalogMetadata!.xmpTitleDescription : (filenameWithoutExtension ?? sourceTitle);
return _bestTitle;
}
diff --git a/lib/model/entry_metadata_edition.dart b/lib/model/entry_metadata_edition.dart
index 2b2ee6054..8a9856aa7 100644
--- a/lib/model/entry_metadata_edition.dart
+++ b/lib/model/entry_metadata_edition.dart
@@ -42,6 +42,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
switch (appliedModifier.action) {
case DateEditAction.setCustom:
case DateEditAction.copyField:
+ case DateEditAction.copyItem:
case DateEditAction.extractFromTitle:
editCreateDateXmp(descriptions, appliedModifier.setDateTime);
break;
@@ -319,6 +320,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
final date = parseUnknownDateFormat(bestTitle);
return date != null ? DateModifier.setCustom(mainMetadataDate(), date) : null;
case DateEditAction.setCustom:
+ case DateEditAction.copyItem:
return DateModifier.setCustom(mainMetadataDate(), modifier.setDateTime!);
case DateEditAction.shift:
case DateEditAction.remove:
diff --git a/lib/model/filters/album.dart b/lib/model/filters/album.dart
index 48fb78ab5..ffe4cd293 100644
--- a/lib/model/filters/album.dart
+++ b/lib/model/filters/album.dart
@@ -1,4 +1,3 @@
-import 'package:aves/image_providers/app_icon_image_provider.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/theme/colors.dart';
@@ -7,13 +6,11 @@ import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/widgets/common/identity/aves_icons.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
-import 'package:palette_generator/palette_generator.dart';
+import 'package:provider/provider.dart';
class AlbumFilter extends CollectionFilter {
static const type = 'album';
- static final Map _appColors = {};
-
final String album;
final String? displayName;
@@ -56,6 +53,7 @@ class AlbumFilter extends CollectionFilter {
@override
Future color(BuildContext context) {
+ final colors = context.watch();
// do not use async/await and rely on `SynchronousFuture`
// to prevent rebuilding of the `FutureBuilder` listening on this future
final albumType = androidFileUtils.getAlbumType(album);
@@ -63,31 +61,19 @@ class AlbumFilter extends CollectionFilter {
case AlbumType.regular:
break;
case AlbumType.app:
- if (_appColors.containsKey(album)) return SynchronousFuture(_appColors[album]!);
-
- final packageName = androidFileUtils.getAlbumAppPackageName(album);
- if (packageName != null) {
- return PaletteGenerator.fromImageProvider(
- AppIconImage(packageName: packageName, size: 24),
- ).then((palette) async {
- // `dominantColor` is most representative but can have low contrast with a dark background
- // `vibrantColor` is usually representative and has good contrast with a dark background
- final color = palette.vibrantColor?.color ?? (await super.color(context));
- _appColors[album] = color;
- return color;
- });
- }
+ final appColor = colors.appColor(album);
+ if (appColor != null) return appColor;
break;
case AlbumType.camera:
- return SynchronousFuture(AColors.albumCamera);
+ return SynchronousFuture(colors.albumCamera);
case AlbumType.download:
- return SynchronousFuture(AColors.albumDownload);
+ return SynchronousFuture(colors.albumDownload);
case AlbumType.screenRecordings:
- return SynchronousFuture(AColors.albumScreenRecordings);
+ return SynchronousFuture(colors.albumScreenRecordings);
case AlbumType.screenshots:
- return SynchronousFuture(AColors.albumScreenshots);
+ return SynchronousFuture(colors.albumScreenshots);
case AlbumType.videoCaptures:
- return SynchronousFuture(AColors.albumVideoCaptures);
+ return SynchronousFuture(colors.albumVideoCaptures);
}
return super.color(context);
}
diff --git a/lib/model/filters/favourite.dart b/lib/model/filters/favourite.dart
index afa6116a6..964992f8c 100644
--- a/lib/model/filters/favourite.dart
+++ b/lib/model/filters/favourite.dart
@@ -4,6 +4,7 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
class FavouriteFilter extends CollectionFilter {
static const type = 'favourite';
@@ -33,7 +34,10 @@ class FavouriteFilter extends CollectionFilter {
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.favourite, size: size);
@override
- Future color(BuildContext context) => SynchronousFuture(AColors.favourite);
+ Future color(BuildContext context) {
+ final colors = context.watch();
+ return SynchronousFuture(colors.favourite);
+ }
@override
String get category => type;
diff --git a/lib/model/filters/filters.dart b/lib/model/filters/filters.dart
index 2c9da7615..567d45a62 100644
--- a/lib/model/filters/filters.dart
+++ b/lib/model/filters/filters.dart
@@ -12,11 +12,12 @@ import 'package:aves/model/filters/rating.dart';
import 'package:aves/model/filters/tag.dart';
import 'package:aves/model/filters/trash.dart';
import 'package:aves/model/filters/type.dart';
-import 'package:aves/utils/color_utils.dart';
+import 'package:aves/theme/colors.dart';
import 'package:collection/collection.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
+import 'package:provider/provider.dart';
@immutable
abstract class CollectionFilter extends Equatable implements Comparable {
@@ -93,7 +94,10 @@ abstract class CollectionFilter extends Equatable implements Comparable null;
- Future color(BuildContext context) => SynchronousFuture(stringToColor(getLabel(context)));
+ Future color(BuildContext context) {
+ final colors = context.watch();
+ return SynchronousFuture(colors.fromString(getLabel(context)));
+ }
String get category;
diff --git a/lib/model/filters/mime.dart b/lib/model/filters/mime.dart
index 9bdc1c753..9e81157cc 100644
--- a/lib/model/filters/mime.dart
+++ b/lib/model/filters/mime.dart
@@ -1,12 +1,14 @@
+import 'dart:async';
+
import 'package:aves/model/filters/filters.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
-import 'package:aves/utils/color_utils.dart';
import 'package:aves/utils/mime_utils.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
+import 'package:provider/provider.dart';
class MimeFilter extends CollectionFilter {
static const type = 'mime';
@@ -15,7 +17,6 @@ class MimeFilter extends CollectionFilter {
late final EntryFilter _test;
late final String _label;
late final IconData _icon;
- late final Color _color;
static final image = MimeFilter(MimeTypes.anyImage);
static final video = MimeFilter(MimeTypes.anyVideo);
@@ -25,7 +26,6 @@ class MimeFilter extends CollectionFilter {
MimeFilter(this.mime) {
IconData? icon;
- Color? color;
var lowMime = mime.toLowerCase();
if (lowMime.endsWith('/*')) {
lowMime = lowMime.substring(0, lowMime.length - 2);
@@ -33,17 +33,14 @@ class MimeFilter extends CollectionFilter {
_label = lowMime.toUpperCase();
if (mime == MimeTypes.anyImage) {
icon = AIcons.image;
- color = AColors.image;
} else if (mime == MimeTypes.anyVideo) {
icon = AIcons.video;
- color = AColors.video;
}
} else {
_test = (entry) => entry.mimeType == lowMime;
_label = MimeUtils.displayType(lowMime);
}
_icon = icon ?? AIcons.vector;
- _color = color ?? stringToColor(_label);
}
MimeFilter.fromMap(Map json)
@@ -79,7 +76,17 @@ class MimeFilter extends CollectionFilter {
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(_icon, size: size);
@override
- Future color(BuildContext context) => SynchronousFuture(_color);
+ Future color(BuildContext context) {
+ final colors = context.watch();
+ switch (mime) {
+ case MimeTypes.anyImage:
+ return SynchronousFuture(colors.image);
+ case MimeTypes.anyVideo:
+ return SynchronousFuture(colors.video);
+ default:
+ return SynchronousFuture(colors.fromString(_label));
+ }
+ }
@override
String get category => type;
diff --git a/lib/model/filters/query.dart b/lib/model/filters/query.dart
index b7c5e210d..e8fe793ea 100644
--- a/lib/model/filters/query.dart
+++ b/lib/model/filters/query.dart
@@ -1,10 +1,11 @@
import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/filters.dart';
+import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
-import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
+import 'package:provider/provider.dart';
class QueryFilter extends CollectionFilter {
static const type = 'query';
@@ -67,7 +68,14 @@ class QueryFilter extends CollectionFilter {
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.text, size: size);
@override
- Future color(BuildContext context) => colorful ? super.color(context) : SynchronousFuture(AvesFilterChip.defaultOutlineColor);
+ Future color(BuildContext context) {
+ if (colorful) {
+ return super.color(context);
+ }
+
+ final colors = context.watch();
+ return SynchronousFuture(colors.neutral);
+ }
@override
String get category => type;
diff --git a/lib/model/filters/type.dart b/lib/model/filters/type.dart
index 1d2b986f0..e4565a671 100644
--- a/lib/model/filters/type.dart
+++ b/lib/model/filters/type.dart
@@ -4,6 +4,7 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
+import 'package:provider/provider.dart';
class TypeFilter extends CollectionFilter {
static const type = 'type';
@@ -18,7 +19,6 @@ class TypeFilter extends CollectionFilter {
final String itemType;
late final EntryFilter _test;
late final IconData _icon;
- late final Color _color;
static final animated = TypeFilter._private(_animated);
static final geotiff = TypeFilter._private(_geotiff);
@@ -35,32 +35,26 @@ class TypeFilter extends CollectionFilter {
case _animated:
_test = (entry) => entry.isAnimated;
_icon = AIcons.animated;
- _color = AColors.animated;
break;
case _geotiff:
_test = (entry) => entry.isGeotiff;
_icon = AIcons.geo;
- _color = AColors.geotiff;
break;
case _motionPhoto:
_test = (entry) => entry.isMotionPhoto;
_icon = AIcons.motionPhoto;
- _color = AColors.motionPhoto;
break;
case _panorama:
_test = (entry) => entry.isImage && entry.is360;
_icon = AIcons.threeSixty;
- _color = AColors.panorama;
break;
case _raw:
_test = (entry) => entry.isRaw;
_icon = AIcons.raw;
- _color = AColors.raw;
break;
case _sphericalVideo:
_test = (entry) => entry.isVideo && entry.is360;
_icon = AIcons.threeSixty;
- _color = AColors.sphericalVideo;
break;
}
}
@@ -106,7 +100,24 @@ class TypeFilter extends CollectionFilter {
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(_icon, size: size);
@override
- Future color(BuildContext context) => SynchronousFuture(_color);
+ Future color(BuildContext context) {
+ final colors = context.watch();
+ switch (itemType) {
+ case _animated:
+ return SynchronousFuture(colors.animated);
+ case _geotiff:
+ return SynchronousFuture(colors.geotiff);
+ case _motionPhoto:
+ return SynchronousFuture(colors.motionPhoto);
+ case _panorama:
+ return SynchronousFuture(colors.panorama);
+ case _raw:
+ return SynchronousFuture(colors.raw);
+ case _sphericalVideo:
+ return SynchronousFuture(colors.sphericalVideo);
+ }
+ return super.color(context);
+ }
@override
String get category => type;
diff --git a/lib/model/metadata/date_modifier.dart b/lib/model/metadata/date_modifier.dart
index 73d648463..3e0c20f94 100644
--- a/lib/model/metadata/date_modifier.dart
+++ b/lib/model/metadata/date_modifier.dart
@@ -24,30 +24,30 @@ class DateModifier extends Equatable {
List