Merge branch 'develop'

This commit is contained in:
Thibault Deckers 2023-08-21 00:20:29 +02:00
commit fb8a97c5c6
339 changed files with 9765 additions and 4573 deletions

@ -1 +1 @@
Subproject commit f92f44110e87bad5ff168335c36da6f6053036e6 Subproject commit efbf63d9c66b9f6ec30e9ad4611189aa80003d31

View file

@ -4,6 +4,27 @@ All notable changes to this project will be documented in this file.
## <a id="unreleased"></a>[Unreleased] ## <a id="unreleased"></a>[Unreleased]
## <a id="v1.9.0"></a>[v1.9.0] - 2023-08-21
### Added
- Video: improved seek accuracy, HDR support, AV1 support, playback speed from x0.25 to x4
- support for animated AVIF (requires rescan)
- Collection: filtering by rating range
- Viewer: optionally show histogram on overlay
- Viewer: external export actions available as quick actions
- About: data usage
### Changed
- Accessibility: removing animations also removes the overscroll stretch effect
- target Android 14 (API 34)
- upgraded Flutter to stable v3.13.0
### Fixed
- flickering when starting videos
## <a id="v1.8.9"></a>[v1.8.9] - 2023-06-04 ## <a id="v1.8.9"></a>[v1.8.9] - 2023-06-04
### Changed ### Changed

View file

@ -2,6 +2,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
id 'com.android.application' id 'com.android.application'
id 'com.google.devtools.ksp' version "$ksp_version"
id 'kotlin-android' id 'kotlin-android'
id 'kotlin-kapt' id 'kotlin-kapt'
} }
@ -48,7 +49,7 @@ if (keystorePropertiesFile.exists()) {
android { android {
namespace 'deckers.thibault.aves' namespace 'deckers.thibault.aves'
compileSdk 33 compileSdk 34
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
compileOptions { compileOptions {
@ -75,7 +76,7 @@ android {
// which implementation `DocumentBuilderImpl` is provided by the OS and is not customizable on Android, // which implementation `DocumentBuilderImpl` is provided by the OS and is not customizable on Android,
// but the implementation on API <19 is not robust enough and fails to build XMP documents // but the implementation on API <19 is not robust enough and fails to build XMP documents
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 33 targetSdkVersion 34
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
manifestPlaceholders = [googleApiKey: keystoreProperties["googleApiKey"] ?: "<NONE>", manifestPlaceholders = [googleApiKey: keystoreProperties["googleApiKey"] ?: "<NONE>",
@ -175,10 +176,10 @@ android {
tasks.withType(KotlinCompile).configureEach { tasks.withType(KotlinCompile).configureEach {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions { kotlin {
jvmTarget = '1.8' jvmToolchain(8)
}
} }
flutter { flutter {
@ -202,7 +203,7 @@ repositories {
} }
dependencies { dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.appcompat:appcompat:1.6.1"
implementation 'androidx.core:core-ktx:1.10.1' implementation 'androidx.core:core-ktx:1.10.1'
@ -224,7 +225,7 @@ dependencies {
// - https://jitpack.io/p/deckerst/Android-TiffBitmapFactory // - https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
// - https://jitpack.io/p/deckerst/mp4parser // - https://jitpack.io/p/deckerst/mp4parser
// - https://jitpack.io/p/deckerst/pixymeta-android // - https://jitpack.io/p/deckerst/pixymeta-android
implementation 'com.github.deckerst:Android-TiffBitmapFactory:876e53870a' implementation 'com.github.deckerst:Android-TiffBitmapFactory:90c06eebf4'
implementation 'com.github.deckerst.mp4parser:isoparser:4cc0c5d06c' implementation 'com.github.deckerst.mp4parser:isoparser:4cc0c5d06c'
implementation 'com.github.deckerst.mp4parser:muxer:4cc0c5d06c' implementation 'com.github.deckerst.mp4parser:muxer:4cc0c5d06c'
implementation 'com.github.deckerst:pixymeta-android:706bd73d6e' implementation 'com.github.deckerst:pixymeta-android:706bd73d6e'
@ -232,10 +233,10 @@ dependencies {
// huawei flavor only // huawei flavor only
huaweiImplementation "com.huawei.agconnect:agconnect-core:$huawei_agconnect_version" huaweiImplementation "com.huawei.agconnect:agconnect-core:$huawei_agconnect_version"
testImplementation "org.junit.jupiter:junit-jupiter-engine:5.9.2" testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
kapt 'androidx.annotation:annotation:1.6.0' kapt 'androidx.annotation:annotation:1.6.0'
kapt "com.github.bumptech.glide:compiler:$glide_version" ksp "com.github.bumptech.glide:ksp:$glide_version"
compileOnly rootProject.findProject(':streams_channel') compileOnly rootProject.findProject(':streams_channel')
} }

View file

@ -15,6 +15,7 @@
TODO TLAD [Android 14 (API 34)] request/handle READ_MEDIA_VISUAL_USER_SELECTED permission TODO TLAD [Android 14 (API 34)] request/handle READ_MEDIA_VISUAL_USER_SELECTED permission
cf https://developer.android.com/about/versions/14/changes/partial-photo-video-access cf https://developer.android.com/about/versions/14/changes/partial-photo-video-access
--> -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission <uses-permission
@ -56,8 +57,9 @@
allow install on API 19, despite the `minSdkVersion` declared in dependencies: allow install on API 19, despite the `minSdkVersion` declared in dependencies:
- Google Maps is from API 20 - Google Maps is from API 20
- the Security library is from API 21 - the Security library is from API 21
- FFmpegKit for Flutter is from API 24
--> -->
<uses-sdk tools:overrideLibrary="io.flutter.plugins.googlemaps, androidx.security:security-crypto" /> <uses-sdk tools:overrideLibrary="io.flutter.plugins.googlemaps, androidx.security:security-crypto, com.arthenica.ffmpegkit.flutter" />
<!-- from Android 11, we should define <queries> to make other apps visible to this app --> <!-- from Android 11, we should define <queries> to make other apps visible to this app -->
<queries> <queries>
@ -295,7 +297,8 @@
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
<!-- as of Flutter v3.10.1, Impeller badly renders text, fails to render videos, and crashes with Google Maps --> <!-- as of Flutter v3.10.1 (stable) / v3.12.0-15.0.pre.105 (master),
Impeller badly renders text, fails to render videos, and crashes with Google Maps -->
<meta-data <meta-data
android:name="io.flutter.embedding.android.EnableImpeller" android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" /> android:value="false" />

View file

@ -9,11 +9,13 @@ import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.PermissionManager import deckers.thibault.aves.utils.PermissionManager
import deckers.thibault.aves.utils.StorageUtils import deckers.thibault.aves.utils.StorageUtils
import deckers.thibault.aves.utils.StorageUtils.getFolderSize
import deckers.thibault.aves.utils.StorageUtils.getPrimaryVolumePath import deckers.thibault.aves.utils.StorageUtils.getPrimaryVolumePath
import deckers.thibault.aves.utils.StorageUtils.getVolumePaths import deckers.thibault.aves.utils.StorageUtils.getVolumePaths
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.util.PathUtils
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@ -25,6 +27,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) { when (call.method) {
"getDataUsage" -> ioScope.launch { safe(call, result, ::getDataUsage) }
"getStorageVolumes" -> ioScope.launch { safe(call, result, ::getStorageVolumes) } "getStorageVolumes" -> ioScope.launch { safe(call, result, ::getStorageVolumes) }
"getVaultRoot" -> ioScope.launch { safe(call, result, ::getVaultRoot) } "getVaultRoot" -> ioScope.launch { safe(call, result, ::getVaultRoot) }
"getFreeSpace" -> ioScope.launch { safe(call, result, ::getFreeSpace) } "getFreeSpace" -> ioScope.launch { safe(call, result, ::getFreeSpace) }
@ -39,6 +42,37 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
} }
} }
private fun getDataUsage(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
var internalCache = getFolderSize(context.cacheDir)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
internalCache += getFolderSize(context.codeCacheDir)
}
val externalCache = context.externalCacheDirs.map(::getFolderSize).sum()
val dataDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) context.dataDir else File(context.applicationInfo.dataDir)
val database = getFolderSize(File(dataDir, "databases"))
val flutter = getFolderSize(File(PathUtils.getDataDirectory(context)))
val vaults = getFolderSize(File(StorageUtils.getVaultRoot(context)))
val trash = context.getExternalFilesDirs(null).mapNotNull { StorageUtils.trashDirFor(context, it.path) }.map(::getFolderSize).sum()
val internalData = getFolderSize(dataDir) - internalCache
val externalData = context.getExternalFilesDirs(null).map(::getFolderSize).sum()
val miscData = internalData + externalData - (database + flutter + vaults + trash)
result.success(
hashMapOf(
"database" to database,
"flutter" to flutter,
"vaults" to vaults,
"trash" to trash,
"miscData" to miscData,
"internalCache" to internalCache,
"externalCache" to externalCache,
)
)
}
private fun getStorageVolumes(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) { private fun getStorageVolumes(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
val volumes = ArrayList<Map<String, Any>>() val volumes = ArrayList<Map<String, Any>>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {

View file

@ -96,12 +96,7 @@ class SvgRegionFetcher internal constructor(
svg.renderToCanvas(canvas, renderOptions) svg.renderToCanvas(canvas, renderOptions)
bitmap = Bitmap.createBitmap(bitmap, bleedX, bleedY, targetBitmapWidth, targetBitmapHeight) bitmap = Bitmap.createBitmap(bitmap, bleedX, bleedY, targetBitmapWidth, targetBitmapHeight)
if (bitmap != null) {
result.success(bitmap.getBytes(canHaveAlpha = true, recycle = true)) result.success(bitmap.getBytes(canHaveAlpha = true, recycle = true))
} else {
result.error("fetch-null", "failed to decode region for uri=$uri regionRect=$regionRect", null)
}
} catch (e: Exception) { } catch (e: Exception) {
result.error("fetch-read-exception", "failed to initialize region decoder for uri=$uri regionRect=$regionRect", e.message) result.error("fetch-read-exception", "failed to initialize region decoder for uri=$uri regionRect=$regionRect", e.message)
} }

View file

@ -205,7 +205,12 @@ class ImageByteStreamHandler(private val context: Context, private val arguments
var len: Int var len: Int
while (inputStream.read(buffer).also { len = it } != -1) { while (inputStream.read(buffer).also { len = it } != -1) {
// cannot decode image on Flutter side when using `buffer` directly // cannot decode image on Flutter side when using `buffer` directly
if (MemoryUtils.canAllocate(len)) {
success(buffer.copyOf(len)) success(buffer.copyOf(len))
} else {
error("streamBytes-memory", "not enough memory to allocate $len bytes", null)
return
}
} }
} }

View file

@ -80,12 +80,16 @@ internal class TiffFetcher(val model: TiffImage, val width: Int, val height: Int
inDirectoryNumber = page inDirectoryNumber = page
inSampleSize = sampleSize inSampleSize = sampleSize
} }
try {
val bitmap = TiffBitmapFactory.decodeFileDescriptor(fd, options) val bitmap = TiffBitmapFactory.decodeFileDescriptor(fd, options)
if (bitmap == null) { if (bitmap == null) {
callback.onLoadFailed(Exception("null bitmap")) callback.onLoadFailed(Exception("Decoding full TIFF yielded null bitmap"))
} else { } else {
callback.onDataReady(bitmap) callback.onDataReady(bitmap)
} }
} catch (e: Exception) {
callback.onLoadFailed(e)
}
} }
override fun cleanup() {} override fun cleanup() {}

View file

@ -52,6 +52,9 @@ object Mp4ParserHelper {
} }
// creating `IsoFile` with a `File` or a `File.inputStream()` yields `No such device` // creating `IsoFile` with a `File` or a `File.inputStream()` yields `No such device`
IsoFile(channel, boxParser).use { isoFile -> IsoFile(channel, boxParser).use { isoFile ->
val fragmented = isoFile.boxes.any { box -> box is MovieFragmentBox || box is SegmentIndexBox }
if (fragmented) throw Exception("editing fragmented movies is not supported")
val lastContentBox = isoFile.boxes.reversed().firstOrNull { box -> val lastContentBox = isoFile.boxes.reversed().firstOrNull { box ->
when { when {
box == isoFile.movieBox -> false box == isoFile.movieBox -> false
@ -60,7 +63,7 @@ object Mp4ParserHelper {
else -> true else -> true
} }
} }
lastContentBox ?: throw Exception("failed to find last context box") lastContentBox ?: throw Exception("failed to find last content box")
val oldFileSize = isoFile.size val oldFileSize = isoFile.size
var appendOffset = (isoFile.getBoxOffset { box -> box == lastContentBox })!! + lastContentBox.size var appendOffset = (isoFile.getBoxOffset { box -> box == lastContentBox })!! + lastContentBox.size
@ -97,7 +100,6 @@ object Mp4ParserHelper {
if (trailing > 0) { if (trailing > 0) {
addFreeBoxEdit(appendOffset, trailing) addFreeBoxEdit(appendOffset, trailing)
} }
return edits return edits
} }
} }
@ -277,11 +279,13 @@ object Mp4ParserHelper {
// creating `IsoFile` with a `File` or a `File.inputStream()` yields `No such device` // creating `IsoFile` with a `File` or a `File.inputStream()` yields `No such device`
IsoFile(channel, metadataBoxParser()).use { isoFile -> IsoFile(channel, metadataBoxParser()).use { isoFile ->
val userDataBox = Path.getPath<UserDataBox>(isoFile.movieBox, UserDataBox.TYPE) val userDataBox = Path.getPath<UserDataBox>(isoFile.movieBox, UserDataBox.TYPE)
if (userDataBox != null) {
fields.putAll(extractBoxFields(userDataBox)) fields.putAll(extractBoxFields(userDataBox))
} }
} }
} }
} }
}
} catch (e: NoClassDefFoundError) { } catch (e: NoClassDefFoundError) {
Log.w(LOG_TAG, "failed to parse MP4 for mimeType=$mimeType uri=$uri", e) Log.w(LOG_TAG, "failed to parse MP4 for mimeType=$mimeType uri=$uri", e)
} catch (e: Exception) { } catch (e: Exception) {

View file

@ -36,7 +36,6 @@ fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int): Ap
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getApplicationInfo(packageName, PackageManager.ApplicationInfoFlags.of(flags.toLong())) getApplicationInfo(packageName, PackageManager.ApplicationInfoFlags.of(flags.toLong()))
} else { } else {
@Suppress("deprecation")
getApplicationInfo(packageName, flags) getApplicationInfo(packageName, flags)
} }
} }
@ -45,7 +44,6 @@ fun PackageManager.queryIntentActivitiesCompat(intent: Intent, flags: Int): List
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(flags.toLong())) queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(flags.toLong()))
} else { } else {
@Suppress("deprecation")
queryIntentActivities(intent, flags) queryIntentActivities(intent, flags)
} }
} }

View file

@ -1,13 +1,10 @@
package deckers.thibault.aves.utils package deckers.thibault.aves.utils
import android.app.ActivityManager
import android.app.Service
import android.content.ContentResolver import android.content.ContentResolver
import android.content.ContentUris import android.content.ContentUris
import android.content.Context import android.content.Context
import android.database.Cursor import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.provider.DocumentsContract
import android.provider.MediaStore import android.provider.MediaStore
import android.util.Log import android.util.Log
import deckers.thibault.aves.utils.UriUtils.tryParseId import deckers.thibault.aves.utils.UriUtils.tryParseId
@ -24,19 +21,6 @@ object ContextUtils {
.build() .build()
} }
fun Context.isMyServiceRunning(serviceClass: Class<out Service>): Boolean {
val am = this.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
am ?: return false
@Suppress("deprecation")
return am.getRunningServices(Integer.MAX_VALUE).any { it.service.className == serviceClass.name }
}
// `flag`: `DocumentsContract.Document.FLAG_SUPPORTS_COPY`, etc.
fun Context.queryDocumentProviderFlag(docUri: Uri, flag: Int): Boolean {
val flags = queryContentPropValue(docUri, "", DocumentsContract.Document.COLUMN_FLAGS) as Long?
return if (flags != null) (flags.toInt() and flag) == flag else false
}
fun Context.queryContentPropValue(uri: Uri, mimeType: String, column: String): Any? { fun Context.queryContentPropValue(uri: Uri, mimeType: String, column: String): Any? {
var contentUri: Uri = uri var contentUri: Uri = uri
if (StorageUtils.isMediaStoreContentUri(uri)) { if (StorageUtils.isMediaStoreContentUri(uri)) {

View file

@ -11,6 +11,7 @@ import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.embedding.engine.loader.FlutterLoader import io.flutter.embedding.engine.loader.FlutterLoader
import io.flutter.view.FlutterCallbackInformation import io.flutter.view.FlutterCallbackInformation
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
@ -60,7 +61,7 @@ object FlutterUtils {
suspend fun runOnUiThread(r: Runnable) { suspend fun runOnUiThread(r: Runnable) {
val mainLooper = Looper.getMainLooper() val mainLooper = Looper.getMainLooper()
if (Looper.myLooper() != mainLooper) { if (Looper.myLooper() != mainLooper) {
suspendCoroutine<Boolean> { cont -> suspendCoroutine { cont: Continuation<Boolean> ->
Handler(mainLooper).post { Handler(mainLooper).post {
r.run() r.run()
cont.resume(true) cont.resume(true)

View file

@ -716,6 +716,18 @@ object StorageUtils {
// convenience methods // convenience methods
fun getFolderSize(f: File): Long {
var size: Long = 0
if (f.isDirectory) {
for (file in f.listFiles()!!) {
size += getFolderSize(file)
}
} else {
size = f.length()
}
return size
}
fun ensureTrailingSeparator(dirPath: String): String { fun ensureTrailingSeparator(dirPath: String): String {
return if (dirPath.endsWith(File.separator)) dirPath else dirPath + File.separator return if (dirPath.endsWith(File.separator)) dirPath else dirPath + File.separator
} }

View file

@ -1,2 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources></resources> <resources>
<string name="analysis_channel_name">Сканаванне носьбітаў</string>
<string name="videos_shortcut_short_label">Відэа</string>
<string name="wallpaper">Шпалеры</string>
<string name="analysis_notification_default_title">Сканаванне носьбітаў</string>
<string name="app_name">Aves</string>
<string name="app_widget_label">Фотарамка</string>
<string name="safe_mode_shortcut_short_label">Бяспечны рэжым</string>
<string name="search_shortcut_short_label">Пошук</string>
<string name="analysis_notification_action_stop">Стоп</string>
</resources>

View file

@ -3,7 +3,7 @@
<string name="app_name">Aves</string> <string name="app_name">Aves</string>
<string name="app_widget_label">Marco de foto</string> <string name="app_widget_label">Marco de foto</string>
<string name="wallpaper">Fondo de pantalla</string> <string name="wallpaper">Fondo de pantalla</string>
<string name="search_shortcut_short_label">Búsqueda</string> <string name="search_shortcut_short_label">Buscar</string>
<string name="videos_shortcut_short_label">Vídeos</string> <string name="videos_shortcut_short_label">Vídeos</string>
<string name="analysis_channel_name">Explorar medios</string> <string name="analysis_channel_name">Explorar medios</string>
<string name="analysis_notification_default_title">Explorando medios</string> <string name="analysis_notification_default_title">Explorando medios</string>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Aves</string>
<string name="app_widget_label">ಫೋಟೋ ಫ್ರೇಮ್</string>
<string name="wallpaper">ವಾಲ್ಪೇಪರ್</string>
<string name="safe_mode_shortcut_short_label">ಸುರಕ್ಷಿತ ಮೋಡ್</string>
<string name="videos_shortcut_short_label">ವೀಡಿಯೊಗಳು</string>
<string name="analysis_channel_name">ಮೀಡಿಯಾ ಸ್ಕ್ಯಾನ್</string>
<string name="analysis_notification_default_title">ಮೀಡಿಯಾ ಸ್ಕ್ಯಾನ್ ಮಾಡಲಾಗುತ್ತಿದೆ</string>
<string name="analysis_notification_action_stop">ನಿಲ್ಲಿಸಿ</string>
<string name="search_shortcut_short_label">ಹುಡುಕಿ</string>
</resources>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Aves</string>
<string name="app_widget_label">ရုပ်ပုံဘောင်</string>
<string name="wallpaper">နောက်ခံ ရုပ်ပုံ</string>
<string name="safe_mode_shortcut_short_label">လုံခြုံရေးလုပ်ဆောင်ချက်</string>
<string name="analysis_channel_name">မီဒီယာ စကင်</string>
<string name="search_shortcut_short_label">ရှာရန်</string>
<string name="videos_shortcut_short_label">ဗီဒီယိုများ</string>
<string name="analysis_notification_default_title">မီဒီယာ ကိုစကင်ဖတ်နေသည်</string>
<string name="analysis_notification_action_stop">ရပ်ရန်</string>
</resources>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_widget_label">Okvir za sliko</string>
<string name="app_name">Aves</string>
<string name="safe_mode_shortcut_short_label">Varni način</string>
<string name="videos_shortcut_short_label">Videoposnetki</string>
<string name="wallpaper">Ozadje</string>
<string name="search_shortcut_short_label">Iskanje</string>
<string name="analysis_notification_default_title">Skeniram medijske datoteke</string>
<string name="analysis_notification_action_stop">Ustavi</string>
<string name="analysis_channel_name">Sken za medijske datoteke</string>
</resources>

View file

@ -1,11 +1,11 @@
buildscript { buildscript {
ext { ext {
kotlin_version = '1.8.21' kotlin_version = '1.8.21'
agp_version = '8.0.1' ksp_version = "$kotlin_version-1.0.11"
agp_version = '7.4.2'
glide_version = '4.15.1' glide_version = '4.15.1'
// AppGallery Connect plugin versions: https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-sdk-changenotes-0000001058732550 // AppGallery Connect plugin versions: https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-sdk-changenotes-0000001058732550
// TODO TLAD AppGallery Connect plugin v1.9.0.300 does not support Gradle 8+ huawei_agconnect_version = '1.9.1.300'
huawei_agconnect_version = '1.9.0.300'
abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4] abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
useCrashlytics = gradle.startParameter.taskNames.any { task -> task.containsIgnoreCase("play") } useCrashlytics = gradle.startParameter.taskNames.any { task -> task.containsIgnoreCase("play") }
useHms = gradle.startParameter.taskNames.any { task -> task.containsIgnoreCase("huawei") } useHms = gradle.startParameter.taskNames.any { task -> task.containsIgnoreCase("huawei") }

View file

@ -1,4 +1,4 @@
<i>Aves</i> can handle all sorts of images and videos, including your typical JPEGs and MP4s, but also more exotic things like <b>multi-page TIFFs, SVGs, old AVIs and more</b>! It scans your media collection to identify <b>motion photos</b>, <b>panoramas</b> (aka photo spheres), <b>360° videos</b>, as well as <b>GeoTIFF</b> files. <i>أيفيس</i> يمكنه التعامل مع جميع أنواع الصور ومقاطع الفيديو ، بما في ذلك ملفات JPEG و MP4 النموذجية ، ولكن أيضًا أشياء أكثر غرابة مثل <b>ملفات TIFF و SVG و AVI القديمة متعددة الصفحات والمزيد</b>! يقوم بمسح مجموعة الوسائط الخاصة بك لتحديد <b> الصور المتحركة</b>, <b>الإستعراضات</b> (المعروف أيضًا باسم الصور البانورامية), <b>360 درجة مقاطع الفيديو</b>, إلى جانب <b>GeoTIFF</b> الملفات.
<b>Navigation and search</b> is an important part of <i>Aves</i>. The goal is for users to easily flow from albums to photos to tags to maps, etc. <b>Navigation and search</b> is an important part of <i>Aves</i>. The goal is for users to easily flow from albums to photos to tags to maps, etc.

View file

@ -1,5 +1,5 @@
<i>Aves</i> can handle all sorts of images and videos, including your typical JPEGs and MP4s, but also more exotic things like <b>multi-page TIFFs, SVGs, old AVIs and more</b>! It scans your media collection to identify <b>motion photos</b>, <b>panoramas</b> (aka photo spheres), <b>360° videos</b>, as well as <b>GeoTIFF</b> files. <i>Aves</i> можа апрацоўваць разнастайныя выявы і відэа, у тым ліку звычайныя файлы JPEG і MP4, а таксама больш экзатычныя рэчы, такія як <b>шматстаронкавыя файлы TIFF, SVG, старыя файлы AVI і іншае</b>! Ён скануе вашу калекцыю мультымедыя для ідэнтыфікацыі <b>фотаздымкаў з рухам</b>, <b>панарам</b> (ён жа панарам), <b>360° відэа</b>, а таксама <b>GeoTIFF</b> файлы.
<b>Navigation and search</b> is an important part of <i>Aves</i>. The goal is for users to easily flow from albums to photos to tags to maps, etc. <b>Навігацыя і пошук</b> з'яўляюцца важнай часткай <i>Aves</i>. Мэта складаецца ў тым, каб карыстальнікі лёгка пераходзілі ад альбомаў да фатаграфій да тэгаў да карт і г.
<i>Aves</i> integrates with Android (from KitKat to Android 13, including Android TV) with features such as <b>widgets</b>, <b>app shortcuts</b>, <b>screen saver</b> and <b>global search</b> handling. It also works as a <b>media viewer and picker</b>. <i>Aves</i> інтэгруецца з Android (ад KitKat да Android 13, уключаючы Android TV) з такімі функцыямі, як <b>віджэты</b>, <b>ярлыкі праграм</b>, <b>застаўка</b> і апрацоўка <b>глабальнага пошуку</b>. Ён таксама працуе як <b>сродак прагляду і выбару мультымедыя</b>.

View file

@ -1 +1 @@
Gallery and metadata explorer Галерэя і правадыр метададзеных

View file

@ -0,0 +1,5 @@
In v1.9.0:
- play your animated AVIF, AV1, and HDR videos
- filter by rating ranges
- judge tonal distributions with the viewer histogram
Full changelog available on GitHub

View file

@ -0,0 +1,5 @@
In v1.9.0:
- play your animated AVIF, AV1, and HDR videos
- filter by rating ranges
- judge tonal distributions with the viewer histogram
Full changelog available on GitHub

View file

@ -1,4 +1,4 @@
<i>Aves</i> aplikazioak mota guztitako irudi eta bideoak, nahiz ohiko zure JPEG eta MP4 fitxategiak eta exotikoagoak diren <b>orri ugaritako TIFF, SVG, AVI zaharrak eta are gehiago</b> maneiatzen ditu! Zure media-bilduma eskaneatzen du <b>mugimendu-argazkiak</b>, <b>panoramikak</b> (argazki esferikoak bezala ere ezagunak), <b>360°-ko bideoak</b>, baita <b>GeoTIFF</b> fitxategiak ere. <i>Aves</i> aplikazioak mota guztitako irudi eta bideoak, nahiz zure ohiko JPEG eta MP4 fitxategiak, eta exotikoagoak diren <b>orri ugaritako TIFF, SVG, AVI zaharrak eta are gehiago</b> maneiatzen ditu! Zure media-bilduma eskaneatzen du <b>mugimendu-argazkiak</b>, <b>panoramikak</b> (argazki esferikoak bezala ere ezagunak), <b>360°-ko bideoak</b>, baita <b>GeoTIFF</b> fitxategiak ere.
<b>Nabigazioa eta bilaketa</b> <i>Aves</i> aplikazioaren zati garrantzitsu bat da. Helburua, erabiltzaileek albumetatik argazkietara, etiketetara, mapetara, etab. modu errazean mugi ahal izatea da. <b>Nabigazioa eta bilaketa</b> <i>Aves</i> aplikazioaren zati garrantzitsu bat da. Helburua, erabiltzaileek albumetatik argazkietara, etiketetara, mapetara, etab. modu errazean mugi ahal izatea da.

View file

@ -0,0 +1,5 @@
<i>Aves</i> can handle all sorts of images and videos, including your typical JPEGs and MP4s, but also more exotic things like <b>multi-page TIFFs, SVGs, old AVIs and more</b>! It scans your media collection to identify <b>motion photos</b>, <b>panoramas</b> (aka photo spheres), <b>360° videos</b>, as well as <b>GeoTIFF</b> files.
<b>Navigation and search</b> is an important part of <i>Aves</i>. The goal is for users to easily flow from albums to photos to tags to maps, etc.
<i>Aves</i> integrates with Android (from KitKat to Android 13, including Android TV) with features such as <b>widgets</b>, <b>app shortcuts</b>, <b>screen saver</b> and <b>global search</b> handling. It also works as a <b>media viewer and picker</b>.

View file

@ -0,0 +1 @@
Gallery and metadata explorer

View file

@ -0,0 +1,5 @@
<i>Aves</i> can handle all sorts of images and videos, including your typical JPEGs and MP4s, but also more exotic things like <b>multi-page TIFFs, SVGs, old AVIs and more</b>! It scans your media collection to identify <b>motion photos</b>, <b>panoramas</b> (aka photo spheres), <b>360° videos</b>, as well as <b>GeoTIFF</b> files.
<b>Navigation and search</b> is an important part of <i>Aves</i>. The goal is for users to easily flow from albums to photos to tags to maps, etc.
<i>Aves</i> integrates with Android (from KitKat to Android 13, including Android TV) with features such as <b>widgets</b>, <b>app shortcuts</b>, <b>screen saver</b> and <b>global search</b> handling. It also works as a <b>media viewer and picker</b>.

View file

@ -0,0 +1 @@
ဂယ်လာရီနဲ့metadataအက်ပ်

View file

@ -0,0 +1,5 @@
<i>Aves</i> can handle all sorts of images and videos, including your typical JPEGs and MP4s, but also more exotic things like <b>multi-page TIFFs, SVGs, old AVIs and more</b>! It scans your media collection to identify <b>motion photos</b>, <b>panoramas</b> (aka photo spheres), <b>360° videos</b>, as well as <b>GeoTIFF</b> files.
<b>Navigation and search</b> is an important part of <i>Aves</i>. The goal is for users to easily flow from albums to photos to tags to maps, etc.
<i>Aves</i> integrates with Android (from KitKat to Android 13, including Android TV) with features such as <b>widgets</b>, <b>app shortcuts</b>, <b>screen saver</b> and <b>global search</b> handling. It also works as a <b>media viewer and picker</b>.

View file

@ -0,0 +1 @@
Gallery and metadata explorer

View file

@ -111,5 +111,564 @@
"chipActionCreateAlbum": "Стварыць альбом", "chipActionCreateAlbum": "Стварыць альбом",
"@chipActionCreateAlbum": {}, "@chipActionCreateAlbum": {},
"entryActionConvert": "Канвертаваць", "entryActionConvert": "Канвертаваць",
"@entryActionConvert": {} "@entryActionConvert": {},
"entryActionRotateCCW": "Круціць супраць гадзінны стрэлкі",
"@entryActionRotateCCW": {},
"entryActionRestore": "Аднавіць",
"@entryActionRestore": {},
"entryActionRotateScreen": "Паварот экрана",
"@entryActionRotateScreen": {},
"entryActionViewSource": "Паглядзець крыніцу",
"@entryActionViewSource": {},
"entryActionConvertMotionPhotoToStillImage": "Пераўтварыць у нерухомую выяву",
"@entryActionConvertMotionPhotoToStillImage": {},
"entryActionViewMotionPhotoVideo": "Адкрыць відэа",
"@entryActionViewMotionPhotoVideo": {},
"entryActionSetAs": "Усталяваць як",
"@entryActionSetAs": {},
"entryActionAddFavourite": "Дадаць у абранае",
"@entryActionAddFavourite": {},
"videoActionUnmute": "Уключыць гук",
"@videoActionUnmute": {},
"videoActionCaptureFrame": "Захоп кадра",
"@videoActionCaptureFrame": {},
"viewerActionSettings": "Налады",
"@viewerActionSettings": {},
"videoActionSkip10": "Перамотка наперад на 10 секунд",
"@videoActionSkip10": {},
"videoActionReplay10": "Перамотка назад на 10 секунд",
"@videoActionReplay10": {},
"entryInfoActionEditTitleDescription": "Рэдагаваць назву і апісанне",
"@entryInfoActionEditTitleDescription": {},
"entryInfoActionRemoveMetadata": "Выдаліць метададзеныя",
"@entryInfoActionRemoveMetadata": {},
"editorTransformRotate": "Павярнуць",
"@editorTransformRotate": {},
"editorTransformCrop": "Абрэзаць",
"@editorTransformCrop": {},
"entryActionShowGeoTiffOnMap": "Паказаць як накладанне на карту",
"@entryActionShowGeoTiffOnMap": {},
"videoActionSelectStreams": "Выберыце трэкі",
"@videoActionSelectStreams": {},
"entryInfoActionEditLocation": "Рэдагаваць месцазнаходжанне",
"@entryInfoActionEditLocation": {},
"entryActionRemoveFavourite": "Выдаліць з абранага",
"@entryActionRemoveFavourite": {},
"videoActionPause": "Паўза",
"@videoActionPause": {},
"videoActionPlay": "Прайграць",
"@videoActionPlay": {},
"videoActionSetSpeed": "Хуткасць прайгравання",
"@videoActionSetSpeed": {},
"viewerActionLock": "Блакіроўка прагляду",
"@viewerActionLock": {},
"slideshowActionResume": "Аднавіць",
"@slideshowActionResume": {},
"viewerActionUnlock": "Разблакіроўка прагляду",
"@viewerActionUnlock": {},
"columnCount": "{count, plural, =1{1 column} other{{count} columns}}",
"@columnCount": {
"placeholders": {
"count": {}
}
},
"timeSeconds": "{seconds, plural, =1{1 second} other{{seconds} seconds}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
}
},
"timeMinutes": "{minutes, plural, =1{1 minute} other{{minutes} minutes}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
}
},
"timeDays": "{days, plural, =1{1 day} other{{days} days}}",
"@timeDays": {
"placeholders": {
"days": {}
}
},
"entryActionExport": "Экспарт",
"@entryActionExport": {},
"entryActionInfo": "Інфармацыя",
"@entryActionInfo": {},
"entryActionRename": "Перайменаваць",
"@entryActionRename": {},
"entryActionRotateCW": "Круціць па гадзіннікавай стрэлцы",
"@entryActionRotateCW": {},
"entryActionFlip": "Перавярнуць па гарызанталі",
"@entryActionFlip": {},
"entryActionPrint": "Друк",
"@entryActionPrint": {},
"entryActionShare": "Падзяліцца",
"@entryActionShare": {},
"entryActionShareImageOnly": "Падзяліцца толькі выявай",
"@entryActionShareImageOnly": {},
"entryActionShareVideoOnly": "Падзяліцца толькі відэа",
"@entryActionShareVideoOnly": {},
"entryActionEdit": "Рэдагаваць",
"@entryActionEdit": {},
"entryActionOpen": "Адкрыць з дапамогай",
"@entryActionOpen": {},
"entryActionOpenMap": "Паказаць у праграме карты",
"@entryActionOpenMap": {},
"videoActionMute": "Адключыць гук",
"@videoActionMute": {},
"slideshowActionShowInCollection": "Паказаць у калекцыі",
"@slideshowActionShowInCollection": {},
"entryInfoActionEditDate": "Рэдагаваць дату і час",
"@entryInfoActionEditDate": {},
"entryInfoActionEditRating": "Рэдагаваць рэйтынг",
"@entryInfoActionEditRating": {},
"entryInfoActionEditTags": "Рэдагаваць тэгі",
"@entryInfoActionEditTags": {},
"entryInfoActionExportMetadata": "Экспарт метададзеных",
"@entryInfoActionExportMetadata": {},
"entryInfoActionRemoveLocation": "Выдаліць месцазнаходжанне",
"@entryInfoActionRemoveLocation": {},
"editorActionTransform": "Трансфармаваць",
"@editorActionTransform": {},
"cropAspectRatioFree": "Свабодныя",
"@cropAspectRatioFree": {},
"cropAspectRatioOriginal": "Першапачатковае",
"@cropAspectRatioOriginal": {},
"cropAspectRatioSquare": "Квадратнае",
"@cropAspectRatioSquare": {},
"coordinateDmsNorth": "Поўнач",
"@coordinateDmsNorth": {},
"filterAspectRatioPortraitLabel": "Партрэтныя",
"@filterAspectRatioPortraitLabel": {},
"filterTypeMotionPhotoLabel": "Фота з рухам",
"@filterTypeMotionPhotoLabel": {},
"filterRecentlyAddedLabel": "Нядаўна дададзены",
"@filterRecentlyAddedLabel": {},
"filterTypeAnimatedLabel": "Аніміраваныя",
"@filterTypeAnimatedLabel": {},
"filterTypeRawLabel": "Без апрацоўкі",
"@filterTypeRawLabel": {},
"filterTypeSphericalVideoLabel": "Відэа 360°",
"@filterTypeSphericalVideoLabel": {},
"filterNoTitleLabel": "Без назвы",
"@filterNoTitleLabel": {},
"filterOnThisDayLabel": "У гэты дзень",
"@filterOnThisDayLabel": {},
"filterRatingRejectedLabel": "Адхілена",
"@filterRatingRejectedLabel": {},
"albumTierRegular": "Іншыя",
"@albumTierRegular": {},
"filterTypeGeotiffLabel": "GeoTIFF",
"@filterTypeGeotiffLabel": {},
"coordinateDms": "{coordinate} {direction}",
"@coordinateDms": {
"placeholders": {
"coordinate": {
"type": "String",
"example": "38° 41 47.72″"
},
"direction": {
"type": "String",
"example": "S"
}
}
},
"coordinateFormatDms": "Градусы, хвіліны і секунды",
"@coordinateFormatDms": {},
"mapStyleGoogleHybrid": "Карты Google (гібрыд)",
"@mapStyleGoogleHybrid": {},
"coordinateFormatDecimal": "Дзесятковы градус",
"@coordinateFormatDecimal": {},
"subtitlePositionBottom": "Ніз",
"@subtitlePositionBottom": {},
"videoControlsPlaySeek": "Прайграванне і перамотка назад/уперад",
"@videoControlsPlaySeek": {},
"nameConflictStrategyReplace": "Замяніць",
"@nameConflictStrategyReplace": {},
"filterAspectRatioLandscapeLabel": "Ландшафтныя",
"@filterAspectRatioLandscapeLabel": {},
"filterBinLabel": "Кошык",
"@filterBinLabel": {},
"filterFavouriteLabel": "Выбранае",
"@filterFavouriteLabel": {},
"filterNoDateLabel": "Без даты",
"@filterNoDateLabel": {},
"filterNoAddressLabel": "Без адрасу",
"@filterNoAddressLabel": {},
"filterLocatedLabel": "Месцазнаходжанне",
"@filterLocatedLabel": {},
"filterNoLocationLabel": "Без месцазнаходжання",
"@filterNoLocationLabel": {},
"filterNoRatingLabel": "Без рэйтынгу",
"@filterNoRatingLabel": {},
"filterTaggedLabel": "З тэгамі",
"@filterTaggedLabel": {},
"filterNoTagLabel": "Без тэгаў",
"@filterNoTagLabel": {},
"filterTypePanoramaLabel": "Панарама",
"@filterTypePanoramaLabel": {},
"filterMimeImageLabel": "Малюнак",
"@filterMimeImageLabel": {},
"filterMimeVideoLabel": "Відэа",
"@filterMimeVideoLabel": {},
"accessibilityAnimationsRemove": "Прадухіленне экранных эфектаў",
"@accessibilityAnimationsRemove": {},
"accessibilityAnimationsKeep": "Захаваць экранныя эфекты",
"@accessibilityAnimationsKeep": {},
"albumTierNew": "Новы",
"@albumTierNew": {},
"albumTierPinned": "Замацаваны",
"@albumTierPinned": {},
"albumTierApps": "Праграмы",
"@albumTierApps": {},
"albumTierVaults": "Сховішчы",
"@albumTierVaults": {},
"albumTierSpecial": "Стандартныя",
"@albumTierSpecial": {},
"coordinateDmsSouth": "Поўдзень",
"@coordinateDmsSouth": {},
"coordinateDmsEast": "Усход",
"@coordinateDmsEast": {},
"coordinateDmsWest": "Захад",
"@coordinateDmsWest": {},
"displayRefreshRatePreferHighest": "Найвышэйшая частата",
"@displayRefreshRatePreferHighest": {},
"displayRefreshRatePreferLowest": "Найменшая частата",
"@displayRefreshRatePreferLowest": {},
"keepScreenOnNever": "Ніколі",
"@keepScreenOnNever": {},
"keepScreenOnVideoPlayback": "Падчас прайгравання відэа",
"@keepScreenOnVideoPlayback": {},
"keepScreenOnViewerOnly": "Толькі ў праглядніку",
"@keepScreenOnViewerOnly": {},
"keepScreenOnAlways": "Заўсёды",
"@keepScreenOnAlways": {},
"lengthUnitPixel": "px",
"@lengthUnitPixel": {},
"lengthUnitPercent": "%",
"@lengthUnitPercent": {},
"mapStyleGoogleNormal": "Карты Google",
"@mapStyleGoogleNormal": {},
"mapStyleGoogleTerrain": "Карты Google (Рэльеф мясцовасці)",
"@mapStyleGoogleTerrain": {},
"mapStyleHuaweiNormal": "Карты Petal",
"@mapStyleHuaweiNormal": {},
"mapStyleHuaweiTerrain": "Карты Petal (Рэльеф мясцовасці)",
"@mapStyleHuaweiTerrain": {},
"mapStyleOsmHot": "Гуманітарная ОСМ",
"@mapStyleOsmHot": {},
"mapStyleStamenToner": "Тычынкавы тонер",
"@mapStyleStamenToner": {},
"mapStyleStamenWatercolor": "Тычынка Акварэль",
"@mapStyleStamenWatercolor": {},
"maxBrightnessNever": "Ніколі",
"@maxBrightnessNever": {},
"maxBrightnessAlways": "Заўсёды",
"@maxBrightnessAlways": {},
"nameConflictStrategyRename": "Перайменаваць",
"@nameConflictStrategyRename": {},
"nameConflictStrategySkip": "Прапусціць",
"@nameConflictStrategySkip": {},
"subtitlePositionTop": "Верх",
"@subtitlePositionTop": {},
"themeBrightnessLight": "Светлая",
"@themeBrightnessLight": {},
"themeBrightnessDark": "Цёмная",
"@themeBrightnessDark": {},
"themeBrightnessBlack": "Чорная",
"@themeBrightnessBlack": {},
"unitSystemMetric": "Метрычныя адзінкі вымярэння",
"@unitSystemMetric": {},
"unitSystemImperial": "Імперская",
"@unitSystemImperial": {},
"vaultLockTypePattern": "Шаблон",
"@vaultLockTypePattern": {},
"vaultLockTypePin": "PIN-код",
"@vaultLockTypePin": {},
"vaultLockTypePassword": "Пароль",
"@vaultLockTypePassword": {},
"settingsVideoEnablePip": "Карцінка ў карцінцы",
"@settingsVideoEnablePip": {},
"videoControlsPlayOutside": "Адкрыць у іншым прайгравальніку",
"@videoControlsPlayOutside": {},
"videoControlsPlay": "Прайграць",
"@videoControlsPlay": {},
"videoLoopModeNever": "Ніколі",
"@videoLoopModeNever": {},
"videoLoopModeShortOnly": "Толькі для кароткіх відэа",
"@videoLoopModeShortOnly": {},
"videoPlaybackSkip": "Прапусціць",
"@videoPlaybackSkip": {},
"videoPlaybackMuted": "Гуляць без гука",
"@videoPlaybackMuted": {},
"videoPlaybackWithSound": "Гуляць з гукам",
"@videoPlaybackWithSound": {},
"videoResumptionModeNever": "Ніколі",
"@videoResumptionModeNever": {},
"videoResumptionModeAlways": "Заўсёды",
"@videoResumptionModeAlways": {},
"viewerTransitionParallax": "Паралакс",
"@viewerTransitionParallax": {},
"videoLoopModeAlways": "Заўсёды",
"@videoLoopModeAlways": {},
"widgetDisplayedItemMostRecent": "Самы апошні",
"@widgetDisplayedItemMostRecent": {},
"storageVolumeDescriptionFallbackNonPrimary": "SD-карта",
"@storageVolumeDescriptionFallbackNonPrimary": {},
"rootDirectoryDescription": "каранёвы каталог",
"@rootDirectoryDescription": {},
"otherDirectoryDescription": "Каталог “{name}”",
"@otherDirectoryDescription": {
"placeholders": {
"name": {
"type": "String",
"example": "Pictures",
"description": "the name of a specific directory"
}
}
},
"storageAccessDialogMessage": "Калі ласка, выберыце {directory} «{volume}» на наступным экране, каб даць гэтай праграме доступ да яго.",
"@storageAccessDialogMessage": {
"placeholders": {
"directory": {
"type": "String",
"description": "the name of a directory, using the output of `rootDirectoryDescription` or `otherDirectoryDescription`"
},
"volume": {
"type": "String",
"example": "SD card",
"description": "the name of a storage volume"
}
}
},
"notEnoughSpaceDialogMessage": "Для завяршэння гэтай аперацыі патрабуецца {neededSize} вольнага месца на “{volume}”, але засталося толькі {freeSize}.",
"@notEnoughSpaceDialogMessage": {
"placeholders": {
"neededSize": {
"type": "String",
"example": "314 MB"
},
"freeSize": {
"type": "String",
"example": "123 MB"
},
"volume": {
"type": "String",
"example": "SD card",
"description": "the name of a storage volume"
}
}
},
"nameConflictDialogSingleSourceMessage": "Некаторыя файлы ў тэчцы прызначэння маюць аднолькавыя назвы.",
"@nameConflictDialogSingleSourceMessage": {},
"nameConflictDialogMultipleSourceMessage": "Некаторыя файлы маюць аднолькавыя назвы.",
"@nameConflictDialogMultipleSourceMessage": {},
"setCoverDialogLatest": "Апошні элемент",
"@setCoverDialogLatest": {},
"vaultDialogLockModeWhenScreenOff": "Блакіроўка пры выключэнні экрана",
"@vaultDialogLockModeWhenScreenOff": {},
"wallpaperTargetHome": "Галоўны экран",
"@wallpaperTargetHome": {},
"wallpaperTargetLock": "Экран блакіроўкі",
"@wallpaperTargetLock": {},
"wallpaperTargetHomeLock": "На абодва экраны",
"@wallpaperTargetHomeLock": {},
"widgetTapUpdateWidget": "Абнавіць віджэт",
"@widgetTapUpdateWidget": {},
"storageVolumeDescriptionFallbackPrimary": "Унутраная памяць",
"@storageVolumeDescriptionFallbackPrimary": {},
"restrictedAccessDialogMessage": "Гэтай праграме забаронена змяняць файлы ў {directory} «{volume}».\n\nКаб перамясціць элементы ў іншую дырэкторыю, выкарыстоўвайце папярэдне ўсталяваны дыспетчар файлаў або праграму галерэі.",
"@restrictedAccessDialogMessage": {
"placeholders": {
"directory": {
"type": "String",
"description": "the name of a directory, using the output of `rootDirectoryDescription` or `otherDirectoryDescription`"
},
"volume": {
"type": "String",
"example": "SD card",
"description": "the name of a storage volume"
}
}
},
"missingSystemFilePickerDialogMessage": "Сродак выбару сістэмных файлаў адсутнічае або адключаны. Уключыце яго і паўтарыце спробу.",
"@missingSystemFilePickerDialogMessage": {},
"unsupportedTypeDialogMessage": "{count, plural, =1{Гэта аперацыя не падтрымліваецца для элементаў наступнага тыпу: {types}.} other{Гэта аперацыя не падтрымліваецца для элементаў наступных тыпаў: {types}.}}",
"@unsupportedTypeDialogMessage": {
"placeholders": {
"count": {},
"types": {
"type": "String",
"example": "GIF, TIFF, MP4",
"description": "a list of unsupported types"
}
}
},
"addShortcutDialogLabel": "Ярлык хуткага доступу",
"@addShortcutDialogLabel": {},
"addShortcutButtonLabel": "ДАДАЦЬ",
"@addShortcutButtonLabel": {},
"noMatchingAppDialogMessage": "Няма праграм, якія б з гэтым справіліся.",
"@noMatchingAppDialogMessage": {},
"moveUndatedConfirmationDialogMessage": "Захаваць даты элементаў, перш чым працягнуць?",
"@moveUndatedConfirmationDialogMessage": {},
"moveUndatedConfirmationDialogSetDate": "Захаваць даты",
"@moveUndatedConfirmationDialogSetDate": {},
"videoResumeDialogMessage": "Вы хочаце аднавіць гульню ў {time}?",
"@videoResumeDialogMessage": {
"placeholders": {
"time": {
"type": "String",
"example": "13:37"
}
}
},
"videoStartOverButtonLabel": "ПАЧАЦЬ НАНАВА",
"@videoStartOverButtonLabel": {},
"videoResumeButtonLabel": "АДНАВІЦЬ",
"@videoResumeButtonLabel": {},
"setCoverDialogAuto": "Аўто",
"@setCoverDialogAuto": {},
"newAlbumDialogTitle": "Новы альбом",
"@newAlbumDialogTitle": {},
"newAlbumDialogNameLabel": "Назва альбома",
"@newAlbumDialogNameLabel": {},
"newAlbumDialogNameLabelAlreadyExistsHelper": "Каталог ужо існуе",
"@newAlbumDialogNameLabelAlreadyExistsHelper": {},
"newAlbumDialogStorageLabel": "Захоўванне:",
"@newAlbumDialogStorageLabel": {},
"newVaultDialogTitle": "Новае сховішча",
"@newVaultDialogTitle": {},
"configureVaultDialogTitle": "Наладзьце сховішча",
"@configureVaultDialogTitle": {},
"vaultDialogLockTypeLabel": "Тып блакіроўкі",
"@vaultDialogLockTypeLabel": {},
"pinDialogEnter": "Увядзіце PIN-код",
"@pinDialogEnter": {},
"patternDialogEnter": "Увядзіце графічны ключ",
"@patternDialogEnter": {},
"patternDialogConfirm": "Пацвердзіце графічны ключ",
"@patternDialogConfirm": {},
"pinDialogConfirm": "Пацвердзіце PIN-код",
"@pinDialogConfirm": {},
"passwordDialogEnter": "Увядзіце пароль",
"@passwordDialogEnter": {},
"passwordDialogConfirm": "Пацвердзіце пароль",
"@passwordDialogConfirm": {},
"authenticateToConfigureVault": "Прайдзіце аўтэнтыфікацыю, каб наладзіць сховішча",
"@authenticateToConfigureVault": {},
"authenticateToUnlockVault": "Прайдзіце аўтэнтыфікацыю, каб разблакіраваць сховішча",
"@authenticateToUnlockVault": {},
"statsTopTagsSectionTitle": "Лепшыя тэгі",
"@statsTopTagsSectionTitle": {},
"statsTopPlacesSectionTitle": "Лепшыя месцы",
"@statsTopPlacesSectionTitle": {},
"viewerInfoSearchSuggestionDescription": "Апісанне",
"@viewerInfoSearchSuggestionDescription": {},
"viewerInfoSearchSuggestionDate": "Дата і час",
"@viewerInfoSearchSuggestionDate": {},
"viewerInfoViewXmlLinkText": "Прагляд XML",
"@viewerInfoViewXmlLinkText": {},
"viewerInfoOpenLinkText": "Адкрыць",
"@viewerInfoOpenLinkText": {},
"mapAttributionStamen": "Даныя карты © [OpenStreetMap](https://www.openstreetmap.org/copyright) удзельнікі • Пліткі ад [Stamen Design](https://stamen.com), [CC BY 3.0](https://creativecommons.org/licenses/by/3.0)",
"@mapAttributionStamen": {},
"mapPointNorthUpTooltip": "Пакажыце поўнач уверх",
"@mapPointNorthUpTooltip": {},
"viewerInfoLabelCoordinates": "Каардынаты",
"@viewerInfoLabelCoordinates": {},
"viewerInfoLabelOwner": "Уладальнік",
"@viewerInfoLabelOwner": {},
"viewerInfoLabelDuration": "Працягласць",
"@viewerInfoLabelDuration": {},
"viewerInfoLabelPath": "Шлях",
"@viewerInfoLabelPath": {},
"viewerInfoLabelResolution": "Дазвол",
"@viewerInfoLabelResolution": {},
"viewerInfoBackToViewerTooltip": "Вярнуцца да аглядальніка",
"@viewerInfoBackToViewerTooltip": {},
"viewerInfoPageTitle": "Інфармацыя",
"@viewerInfoPageTitle": {},
"viewerErrorDoesNotExist": "Файл больш не існуе.",
"@viewerErrorDoesNotExist": {},
"filePickerUseThisFolder": "Выкарыстоўваць гэтую тэчку",
"@filePickerUseThisFolder": {},
"filePickerNoItems": "Няма элементаў",
"@filePickerNoItems": {},
"filePickerOpenFrom": "Адкрыць з",
"@filePickerOpenFrom": {},
"filePickerShowHiddenFiles": "Паказаць схаваныя файлы",
"@filePickerShowHiddenFiles": {},
"sourceViewerPageTitle": "Крыніца",
"@sourceViewerPageTitle": {},
"panoramaDisableSensorControl": "Адключыць сэнсарнае кіраванне",
"@panoramaDisableSensorControl": {},
"panoramaEnableSensorControl": "Уключыць сэнсарнае кіраванне",
"@panoramaEnableSensorControl": {},
"tagPlaceholderPlace": "Месца",
"@tagPlaceholderPlace": {},
"tagPlaceholderState": "Дзяржава",
"@tagPlaceholderState": {},
"tagEditorSectionPlaceholders": "Запаўняльнікі",
"@tagEditorSectionPlaceholders": {},
"tagEditorSectionRecent": "Апошнія",
"@tagEditorSectionRecent": {},
"tagEditorPageAddTagTooltip": "Дадаць тэг",
"@tagEditorPageAddTagTooltip": {},
"tagEditorPageNewTagFieldLabel": "Новы тэг",
"@tagEditorPageNewTagFieldLabel": {},
"wallpaperUseScrollEffect": "Выкарыстоўвайце эфект пракруткі на галоўным экране",
"@wallpaperUseScrollEffect": {},
"viewerInfoSearchSuggestionResolution": "Дазвол",
"@viewerInfoSearchSuggestionResolution": {},
"viewerInfoSearchSuggestionDimensions": "Памеры",
"@viewerInfoSearchSuggestionDimensions": {},
"videoControlsNone": "Без",
"@videoControlsNone": {},
"viewerErrorUnknown": "Ой!",
"@viewerErrorUnknown": {},
"viewerSetWallpaperButtonLabel": "УСТАНАВІЦЬ ШПАЛЕРЫ",
"@viewerSetWallpaperButtonLabel": {},
"statsTopAlbumsSectionTitle": "Лепшыя альбомы",
"@statsTopAlbumsSectionTitle": {},
"viewerInfoUnknown": "невядома",
"@viewerInfoUnknown": {},
"viewerInfoLabelTitle": "Назва",
"@viewerInfoLabelTitle": {},
"viewerInfoLabelDescription": "Апісанне",
"@viewerInfoLabelDescription": {},
"viewerInfoLabelDate": "Дата",
"@viewerInfoLabelDate": {},
"viewerInfoLabelAddress": "Адрас",
"@viewerInfoLabelAddress": {},
"mapZoomInTooltip": "Павелічэнне",
"@mapZoomInTooltip": {},
"mapStyleTooltip": "Выберыце стыль карты",
"@mapStyleTooltip": {},
"mapStyleDialogTitle": "Стыль карты",
"@mapStyleDialogTitle": {},
"mapZoomOutTooltip": "Змяншэння",
"@mapZoomOutTooltip": {},
"openMapPageTooltip": "Паглядзець на старонцы карты",
"@openMapPageTooltip": {},
"mapEmptyRegion": "У гэтым рэгіёне няма малюнкаў",
"@mapEmptyRegion": {},
"viewerInfoSearchEmpty": "Няма адпаведных ключоў",
"@viewerInfoSearchEmpty": {},
"viewerInfoSearchFieldLabel": "Пошук метададзеных",
"@viewerInfoSearchFieldLabel": {},
"tagEditorPageTitle": "Рэдагаваць тэгі",
"@tagEditorPageTitle": {},
"tagEditorDiscardDialogMessage": "Вы хочаце адмяніць змены?",
"@tagEditorDiscardDialogMessage": {},
"tagPlaceholderCountry": "Краіна",
"@tagPlaceholderCountry": {},
"filePickerDoNotShowHiddenFiles": "Не паказваць схаваныя файлы",
"@filePickerDoNotShowHiddenFiles": {},
"viewerInfoOpenEmbeddedFailureFeedback": "Не ўдалося атрымаць убудаваныя даныя",
"@viewerInfoOpenEmbeddedFailureFeedback": {},
"mapAttributionOsmHot": "Даныя карты © [OpenStreetMap](https://www.openstreetmap.org/copyright) удзельнікі • Пліткі ад [HOT](https://www.hotosm.org/) • Арганізаваны [OSM France](https://openstreetmap.fr/)",
"@mapAttributionOsmHot": {},
"viewerInfoLabelSize": "Памер",
"@viewerInfoLabelSize": {}
} }

View file

@ -1,5 +1,5 @@
{ {
"@@locale" : "ckb", "@@locale": "ckb",
"welcomeOptional": "ئارەزومەندانە", "welcomeOptional": "ئارەزومەندانە",
"@welcomeOptional": {}, "@welcomeOptional": {},
"welcomeTermsToggle": "ڕازیم بە مەرج و یاساکانی بەکارهێنان", "welcomeTermsToggle": "ڕازیم بە مەرج و یاساکانی بەکارهێنان",

View file

@ -1486,5 +1486,25 @@
"settingsVideoPlaybackPageTitle": "Přehrávání", "settingsVideoPlaybackPageTitle": "Přehrávání",
"@settingsVideoPlaybackPageTitle": {}, "@settingsVideoPlaybackPageTitle": {},
"settingsVideoResumptionModeTile": "Obnovit přehrávání", "settingsVideoResumptionModeTile": "Obnovit přehrávání",
"@settingsVideoResumptionModeTile": {} "@settingsVideoResumptionModeTile": {},
"editorActionTransform": "Transformovat",
"@editorActionTransform": {},
"cropAspectRatioSquare": "Čtverec",
"@cropAspectRatioSquare": {},
"cropAspectRatioFree": "Volný",
"@cropAspectRatioFree": {},
"aboutDataUsageSectionTitle": "Využitý prostor",
"@aboutDataUsageSectionTitle": {},
"aboutDataUsageData": "Data",
"@aboutDataUsageData": {},
"aboutDataUsageCache": "Mezipaměť",
"@aboutDataUsageCache": {},
"aboutDataUsageDatabase": "Databáze",
"@aboutDataUsageDatabase": {},
"aboutDataUsageMisc": "Různé",
"@aboutDataUsageMisc": {},
"aboutDataUsageInternal": "Interní",
"@aboutDataUsageInternal": {},
"aboutDataUsageExternal": "Externí",
"@aboutDataUsageExternal": {}
} }

View file

@ -1334,5 +1334,19 @@
"cropAspectRatioSquare": "Quadrat", "cropAspectRatioSquare": "Quadrat",
"@cropAspectRatioSquare": {}, "@cropAspectRatioSquare": {},
"widgetTapUpdateWidget": "Widget öffnen", "widgetTapUpdateWidget": "Widget öffnen",
"@widgetTapUpdateWidget": {} "@widgetTapUpdateWidget": {},
"aboutDataUsageSectionTitle": "Datennutzung",
"@aboutDataUsageSectionTitle": {},
"aboutDataUsageData": "Daten",
"@aboutDataUsageData": {},
"aboutDataUsageCache": "Cache",
"@aboutDataUsageCache": {},
"aboutDataUsageMisc": "Sonstiges",
"@aboutDataUsageMisc": {},
"aboutDataUsageInternal": "Intern",
"@aboutDataUsageInternal": {},
"aboutDataUsageExternal": "Extern",
"@aboutDataUsageExternal": {},
"aboutDataUsageDatabase": "Datenbank",
"@aboutDataUsageDatabase": {}
} }

View file

@ -233,6 +233,10 @@
"nameConflictStrategyReplace": "Replace", "nameConflictStrategyReplace": "Replace",
"nameConflictStrategySkip": "Skip", "nameConflictStrategySkip": "Skip",
"overlayHistogramNone": "None",
"overlayHistogramRGB": "RGB",
"overlayHistogramLuminance": "Luminance",
"subtitlePositionTop": "Top", "subtitlePositionTop": "Top",
"subtitlePositionBottom": "Bottom", "subtitlePositionBottom": "Bottom",
@ -533,6 +537,14 @@
"aboutBugReportInstruction": "Report on GitHub with the logs and system information", "aboutBugReportInstruction": "Report on GitHub with the logs and system information",
"aboutBugReportButton": "Report", "aboutBugReportButton": "Report",
"aboutDataUsageSectionTitle": "Data Usage",
"aboutDataUsageData": "Data",
"aboutDataUsageCache": "Cache",
"aboutDataUsageDatabase": "Database",
"aboutDataUsageMisc": "Misc",
"aboutDataUsageInternal": "Internal",
"aboutDataUsageExternal": "External",
"aboutCreditsSectionTitle": "Credits", "aboutCreditsSectionTitle": "Credits",
"aboutCreditsWorldAtlas1": "This app uses a TopoJSON file from", "aboutCreditsWorldAtlas1": "This app uses a TopoJSON file from",
"aboutCreditsWorldAtlas2": "under ISC License.", "aboutCreditsWorldAtlas2": "under ISC License.",
@ -801,6 +813,7 @@
"settingsViewerOverlayTile": "Overlay", "settingsViewerOverlayTile": "Overlay",
"settingsViewerOverlayPageTitle": "Overlay", "settingsViewerOverlayPageTitle": "Overlay",
"settingsViewerShowOverlayOnOpening": "Show on opening", "settingsViewerShowOverlayOnOpening": "Show on opening",
"settingsViewerShowHistogram": "Show histogram",
"settingsViewerShowMinimap": "Show minimap", "settingsViewerShowMinimap": "Show minimap",
"settingsViewerShowInformation": "Show information", "settingsViewerShowInformation": "Show information",
"settingsViewerShowInformationSubtitle": "Show title, date, location, etc.", "settingsViewerShowInformationSubtitle": "Show title, date, location, etc.",

View file

@ -1334,5 +1334,27 @@
"cropAspectRatioOriginal": "Original", "cropAspectRatioOriginal": "Original",
"@cropAspectRatioOriginal": {}, "@cropAspectRatioOriginal": {},
"widgetTapUpdateWidget": "Actualizar el widget", "widgetTapUpdateWidget": "Actualizar el widget",
"@widgetTapUpdateWidget": {} "@widgetTapUpdateWidget": {},
"aboutDataUsageData": "Datos",
"@aboutDataUsageData": {},
"aboutDataUsageCache": "Cache",
"@aboutDataUsageCache": {},
"aboutDataUsageDatabase": "Base de datos",
"@aboutDataUsageDatabase": {},
"aboutDataUsageInternal": "Interno",
"@aboutDataUsageInternal": {},
"aboutDataUsageExternal": "Exterior",
"@aboutDataUsageExternal": {},
"aboutDataUsageSectionTitle": "Uso de los datos",
"@aboutDataUsageSectionTitle": {},
"aboutDataUsageMisc": "Varios",
"@aboutDataUsageMisc": {},
"overlayHistogramLuminance": "Luminancia",
"@overlayHistogramLuminance": {},
"overlayHistogramNone": "Ninguna",
"@overlayHistogramNone": {},
"overlayHistogramRGB": "RGB",
"@overlayHistogramRGB": {},
"settingsViewerShowHistogram": "Mostrar el histograma",
"@settingsViewerShowHistogram": {}
} }

View file

@ -1492,5 +1492,27 @@
"cropAspectRatioSquare": "Karratua", "cropAspectRatioSquare": "Karratua",
"@cropAspectRatioSquare": {}, "@cropAspectRatioSquare": {},
"widgetTapUpdateWidget": "Eguneratu widgeta", "widgetTapUpdateWidget": "Eguneratu widgeta",
"@widgetTapUpdateWidget": {} "@widgetTapUpdateWidget": {},
"aboutDataUsageSectionTitle": "Datuen erabilera",
"@aboutDataUsageSectionTitle": {},
"aboutDataUsageData": "Datuak",
"@aboutDataUsageData": {},
"aboutDataUsageDatabase": "Datu-basea",
"@aboutDataUsageDatabase": {},
"aboutDataUsageMisc": "Askotariko",
"@aboutDataUsageMisc": {},
"aboutDataUsageInternal": "Barnekoa",
"@aboutDataUsageInternal": {},
"aboutDataUsageExternal": "Kanpokoa",
"@aboutDataUsageExternal": {},
"aboutDataUsageCache": "Cachea",
"@aboutDataUsageCache": {},
"overlayHistogramLuminance": "Luminantzia",
"@overlayHistogramLuminance": {},
"overlayHistogramNone": "Bat ere ez",
"@overlayHistogramNone": {},
"overlayHistogramRGB": "RGB",
"@overlayHistogramRGB": {},
"settingsViewerShowHistogram": "Erakutsi histograma",
"@settingsViewerShowHistogram": {}
} }

View file

@ -1334,5 +1334,27 @@
"cropAspectRatioOriginal": "Photo dorigine", "cropAspectRatioOriginal": "Photo dorigine",
"@cropAspectRatioOriginal": {}, "@cropAspectRatioOriginal": {},
"widgetTapUpdateWidget": "Mettre à jour le widget", "widgetTapUpdateWidget": "Mettre à jour le widget",
"@widgetTapUpdateWidget": {} "@widgetTapUpdateWidget": {},
"aboutDataUsageDatabase": "Base de données",
"@aboutDataUsageDatabase": {},
"aboutDataUsageMisc": "Divers",
"@aboutDataUsageMisc": {},
"aboutDataUsageInternal": "Interne",
"@aboutDataUsageInternal": {},
"aboutDataUsageExternal": "Externe",
"@aboutDataUsageExternal": {},
"aboutDataUsageSectionTitle": "Espace utilisé",
"@aboutDataUsageSectionTitle": {},
"aboutDataUsageData": "Données",
"@aboutDataUsageData": {},
"aboutDataUsageCache": "Cache",
"@aboutDataUsageCache": {},
"overlayHistogramRGB": "RVB",
"@overlayHistogramRGB": {},
"overlayHistogramLuminance": "Luminance",
"@overlayHistogramLuminance": {},
"settingsViewerShowHistogram": "Afficher l'histogramme",
"@settingsViewerShowHistogram": {},
"overlayHistogramNone": "Aucun",
"@overlayHistogramNone": {}
} }

View file

@ -1492,5 +1492,19 @@
"cropAspectRatioFree": "Kötetlen", "cropAspectRatioFree": "Kötetlen",
"@cropAspectRatioFree": {}, "@cropAspectRatioFree": {},
"widgetTapUpdateWidget": "Widget frissítése", "widgetTapUpdateWidget": "Widget frissítése",
"@widgetTapUpdateWidget": {} "@widgetTapUpdateWidget": {},
"aboutDataUsageData": "Adat",
"@aboutDataUsageData": {},
"aboutDataUsageDatabase": "Adatbázis",
"@aboutDataUsageDatabase": {},
"aboutDataUsageMisc": "Egyéb",
"@aboutDataUsageMisc": {},
"aboutDataUsageInternal": "Belső",
"@aboutDataUsageInternal": {},
"aboutDataUsageExternal": "Külső",
"@aboutDataUsageExternal": {},
"aboutDataUsageSectionTitle": "Adatforgalom",
"@aboutDataUsageSectionTitle": {},
"aboutDataUsageCache": "Gyorsítótár",
"@aboutDataUsageCache": {}
} }

View file

@ -1334,5 +1334,27 @@
"cropAspectRatioFree": "Bebas", "cropAspectRatioFree": "Bebas",
"@cropAspectRatioFree": {}, "@cropAspectRatioFree": {},
"widgetTapUpdateWidget": "Perbarui widget", "widgetTapUpdateWidget": "Perbarui widget",
"@widgetTapUpdateWidget": {} "@widgetTapUpdateWidget": {},
"aboutDataUsageData": "Data",
"@aboutDataUsageData": {},
"aboutDataUsageDatabase": "Basis Data",
"@aboutDataUsageDatabase": {},
"aboutDataUsageSectionTitle": "Penggunaan Data",
"@aboutDataUsageSectionTitle": {},
"aboutDataUsageCache": "Tembolok",
"@aboutDataUsageCache": {},
"aboutDataUsageExternal": "Eksternal",
"@aboutDataUsageExternal": {},
"aboutDataUsageMisc": "Lainnya",
"@aboutDataUsageMisc": {},
"aboutDataUsageInternal": "Internal",
"@aboutDataUsageInternal": {},
"overlayHistogramNone": "Tidak ada",
"@overlayHistogramNone": {},
"overlayHistogramRGB": "RGB",
"@overlayHistogramRGB": {},
"overlayHistogramLuminance": "Kecerahan",
"@overlayHistogramLuminance": {},
"settingsViewerShowHistogram": "Tampilkan histogram",
"@settingsViewerShowHistogram": {}
} }

View file

@ -19,7 +19,7 @@
"@focalLength": {}, "@focalLength": {},
"applyButtonLabel": "APPLICA", "applyButtonLabel": "APPLICA",
"@applyButtonLabel": {}, "@applyButtonLabel": {},
"deleteButtonLabel": "CANCELLA", "deleteButtonLabel": "ELIMINA",
"@deleteButtonLabel": {}, "@deleteButtonLabel": {},
"nextButtonLabel": "AVANTI", "nextButtonLabel": "AVANTI",
"@nextButtonLabel": {}, "@nextButtonLabel": {},
@ -345,7 +345,7 @@
"@noMatchingAppDialogMessage": {}, "@noMatchingAppDialogMessage": {},
"binEntriesConfirmationDialogMessage": "{count, plural, =1{Spostare questo elemento nel cestino?} other{Spostare questi {count} elementi nel cestino?}}", "binEntriesConfirmationDialogMessage": "{count, plural, =1{Spostare questo elemento nel cestino?} other{Spostare questi {count} elementi nel cestino?}}",
"@binEntriesConfirmationDialogMessage": {}, "@binEntriesConfirmationDialogMessage": {},
"deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Cancellare questo elemento?} other{Cancellare questi {count} elementi?}}", "deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Eliminare questo elemento?} other{Eliminare questi {count} elementi?}}",
"@deleteEntriesConfirmationDialogMessage": {}, "@deleteEntriesConfirmationDialogMessage": {},
"moveUndatedConfirmationDialogMessage": "Salvare le date degli elementi prima di procedere?", "moveUndatedConfirmationDialogMessage": "Salvare le date degli elementi prima di procedere?",
"@moveUndatedConfirmationDialogMessage": {}, "@moveUndatedConfirmationDialogMessage": {},
@ -389,9 +389,9 @@
"@renameProcessorCounter": {}, "@renameProcessorCounter": {},
"renameProcessorName": "Nome", "renameProcessorName": "Nome",
"@renameProcessorName": {}, "@renameProcessorName": {},
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Cancellare questo album e lelemento in esso?} other{Cancellare questo album e i {count} elementi in esso?}}", "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Eliminare questo album e lelemento in esso?} other{Eliminare questo album e i {count} elementi in esso?}}",
"@deleteSingleAlbumConfirmationDialogMessage": {}, "@deleteSingleAlbumConfirmationDialogMessage": {},
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Cancellare questi album e lelemento in essi?} other{Cancellare questi album e i {count} elementi in essi?}}", "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Eliminare questi album e lelemento in essi?} other{Eliminare questi album e i {count} elementi in essi?}}",
"@deleteMultiAlbumConfirmationDialogMessage": {}, "@deleteMultiAlbumConfirmationDialogMessage": {},
"exportEntryDialogFormat": "Formato:", "exportEntryDialogFormat": "Formato:",
"@exportEntryDialogFormat": {}, "@exportEntryDialogFormat": {},
@ -579,7 +579,7 @@
"@dateYesterday": {}, "@dateYesterday": {},
"dateThisMonth": "Questo mese", "dateThisMonth": "Questo mese",
"@dateThisMonth": {}, "@dateThisMonth": {},
"collectionDeleteFailureFeedback": "{count, plural, =1{Impossibile cancellare 1 elemento} other{Impossibile cancellare {count} elementi}}", "collectionDeleteFailureFeedback": "{count, plural, =1{Impossibile eliminare 1 elemento} other{Impossibile eliminare {count} elementi}}",
"@collectionDeleteFailureFeedback": {}, "@collectionDeleteFailureFeedback": {},
"collectionCopyFailureFeedback": "{count, plural, =1{Impossibile copiare 1 elemento} other{Impossibile copiare {count} elementi}}", "collectionCopyFailureFeedback": "{count, plural, =1{Impossibile copiare 1 elemento} other{Impossibile copiare {count} elementi}}",
"@collectionCopyFailureFeedback": {}, "@collectionCopyFailureFeedback": {},
@ -775,7 +775,7 @@
"@settingsConfirmationTile": {}, "@settingsConfirmationTile": {},
"settingsConfirmationDialogTitle": "Richieste di conferma", "settingsConfirmationDialogTitle": "Richieste di conferma",
"@settingsConfirmationDialogTitle": {}, "@settingsConfirmationDialogTitle": {},
"settingsConfirmationBeforeDeleteItems": "Chiedi prima di cancellare gli elementi definitivamente", "settingsConfirmationBeforeDeleteItems": "Chiedi prima di eliminare gli elementi definitivamente",
"@settingsConfirmationBeforeDeleteItems": {}, "@settingsConfirmationBeforeDeleteItems": {},
"settingsConfirmationBeforeMoveToBinItems": "Chiedi prima di spostare gli elementi nel cestino", "settingsConfirmationBeforeMoveToBinItems": "Chiedi prima di spostare gli elementi nel cestino",
"@settingsConfirmationBeforeMoveToBinItems": {}, "@settingsConfirmationBeforeMoveToBinItems": {},
@ -955,7 +955,7 @@
"@settingsSaveSearchHistory": {}, "@settingsSaveSearchHistory": {},
"settingsEnableBin": "Usa il cestino", "settingsEnableBin": "Usa il cestino",
"@settingsEnableBin": {}, "@settingsEnableBin": {},
"settingsEnableBinSubtitle": "Conserva gli elementi cancellati per 30 giorni", "settingsEnableBinSubtitle": "Conserva gli elementi eliminati per 30 giorni",
"@settingsEnableBinSubtitle": {}, "@settingsEnableBinSubtitle": {},
"settingsHiddenItemsTile": "Elementi nascosti", "settingsHiddenItemsTile": "Elementi nascosti",
"@settingsHiddenItemsTile": {}, "@settingsHiddenItemsTile": {},
@ -1316,5 +1316,37 @@
"settingsVideoPlaybackTile": "Riproduzione", "settingsVideoPlaybackTile": "Riproduzione",
"@settingsVideoPlaybackTile": {}, "@settingsVideoPlaybackTile": {},
"settingsCollectionBurstPatternsTile": "Modelli di burst", "settingsCollectionBurstPatternsTile": "Modelli di burst",
"@settingsCollectionBurstPatternsTile": {} "@settingsCollectionBurstPatternsTile": {},
"saveCopyButtonLabel": "SALVA COPIA",
"@saveCopyButtonLabel": {},
"applyTooltip": "Applica",
"@applyTooltip": {},
"editorActionTransform": "Trasforma",
"@editorActionTransform": {},
"editorTransformCrop": "Ritaglia",
"@editorTransformCrop": {},
"editorTransformRotate": "Ruota",
"@editorTransformRotate": {},
"cropAspectRatioFree": "Libero",
"@cropAspectRatioFree": {},
"cropAspectRatioOriginal": "Originale",
"@cropAspectRatioOriginal": {},
"cropAspectRatioSquare": "Quadrato",
"@cropAspectRatioSquare": {},
"widgetTapUpdateWidget": "Aggiorna widget",
"@widgetTapUpdateWidget": {},
"aboutDataUsageCache": "Cache",
"@aboutDataUsageCache": {},
"aboutDataUsageMisc": "Varie",
"@aboutDataUsageMisc": {},
"aboutDataUsageDatabase": "Database",
"@aboutDataUsageDatabase": {},
"aboutDataUsageSectionTitle": "Utilizzo dati",
"@aboutDataUsageSectionTitle": {},
"aboutDataUsageData": "Dati",
"@aboutDataUsageData": {},
"aboutDataUsageInternal": "Interno",
"@aboutDataUsageInternal": {},
"aboutDataUsageExternal": "Esterno",
"@aboutDataUsageExternal": {}
} }

64
lib/l10n/app_kn.arb Normal file
View file

@ -0,0 +1,64 @@
{
"appName": "Aves",
"@appName": {},
"welcomeOptional": "ಐಚ್ಛಿಕ",
"@welcomeOptional": {},
"welcomeTermsToggle": "ನಾನು ನಿಯಮಗಳು ಮತ್ತು ಷರತ್ತುಗಳನ್ನು ಒಪ್ಪುತ್ತೇನೆ",
"@welcomeTermsToggle": {},
"applyButtonLabel": "ಅನ್ವಯಿಸು",
"@applyButtonLabel": {},
"deleteButtonLabel": "ಅಳಿಸಿ",
"@deleteButtonLabel": {},
"nextButtonLabel": "ಮುಂದೆ",
"@nextButtonLabel": {},
"showButtonLabel": "ತೋರಿಸು",
"@showButtonLabel": {},
"hideButtonLabel": "ಮುಚ್ಚಿಡು",
"@hideButtonLabel": {},
"continueButtonLabel": "ಮುಂದುವರಿಸು",
"@continueButtonLabel": {},
"saveCopyButtonLabel": "ನಕಲು ಉಳಿಸಿ",
"@saveCopyButtonLabel": {},
"applyTooltip": "ಅನ್ವಯಿಸು",
"@applyTooltip": {},
"cancelTooltip": "ರದ್ದುಗೊಳಿಸಿ",
"@cancelTooltip": {},
"changeTooltip": "ಬದಲಾಯಿಸು",
"@changeTooltip": {},
"clearTooltip": "ಸ್ಪಷ್ಟ ಮಾಡು",
"@clearTooltip": {},
"previousTooltip": "ಹಿಂದಿನ",
"@previousTooltip": {},
"nextTooltip": "ಮುಂದಿನ",
"@nextTooltip": {},
"showTooltip": "ತೋರಿಸು",
"@showTooltip": {},
"actionRemove": "ತೆಗೆದುಹಾಕಿ",
"@actionRemove": {},
"resetTooltip": "ರೀಸೆಟ್ ಮಾಡಿ",
"@resetTooltip": {},
"saveTooltip": "ಉಳಿಸಿ",
"@saveTooltip": {},
"pickTooltip": "ಆಯ್ಕೆ",
"@pickTooltip": {},
"doubleBackExitMessage": "ನಿರ್ಗಮಿಸಲು ಮತ್ತೆ \"ಹಿಂದೆ\" ಟ್ಯಾಪ್ ಮಾಡಿ.",
"@doubleBackExitMessage": {},
"doNotAskAgain": "ಇನ್ನೊಮ್ಮೆ ಕೇಳಬೇಡಿ",
"@doNotAskAgain": {},
"sourceStateLoading": "ಲೋಡ್ ಆಗುತ್ತಿದೆ",
"@sourceStateLoading": {},
"sourceStateCataloguing": "ಪಟ್ಟಿಮಾಡುವುದು",
"@sourceStateCataloguing": {},
"sourceStateLocatingCountries": "ದೇಶಗಳನ್ನು ಪತ್ತೆ ಮಾಡಲಾಗುತ್ತಿದೆ",
"@sourceStateLocatingCountries": {},
"sourceStateLocatingPlaces": "ಸ್ಥಳಗಳನ್ನು ಪತ್ತೆ ಮಾಡಲಾಗುತ್ತಿದೆ",
"@sourceStateLocatingPlaces": {},
"chipActionDelete": "ಅಳಿಸಿ",
"@chipActionDelete": {},
"chipActionGoToAlbumPage": "ಆಲ್ಬಮ್‌ಗಳಲ್ಲಿ ತೋರಿಸು",
"@chipActionGoToAlbumPage": {},
"hideTooltip": "ಮರೆಮಾಡಿ",
"@hideTooltip": {},
"welcomeMessage": "Aves ಗೆ ಸ್ವಾಗತ",
"@welcomeMessage": {}
}

View file

@ -1334,5 +1334,27 @@
"editorActionTransform": "변형", "editorActionTransform": "변형",
"@editorActionTransform": {}, "@editorActionTransform": {},
"widgetTapUpdateWidget": "위젯 갱신", "widgetTapUpdateWidget": "위젯 갱신",
"@widgetTapUpdateWidget": {} "@widgetTapUpdateWidget": {},
"aboutDataUsageDatabase": "데이터베이스",
"@aboutDataUsageDatabase": {},
"aboutDataUsageMisc": "기타",
"@aboutDataUsageMisc": {},
"aboutDataUsageSectionTitle": "사용 중인 저장공간",
"@aboutDataUsageSectionTitle": {},
"aboutDataUsageExternal": "외부 저장소",
"@aboutDataUsageExternal": {},
"aboutDataUsageInternal": "내부 저장소",
"@aboutDataUsageInternal": {},
"aboutDataUsageData": "데이터",
"@aboutDataUsageData": {},
"aboutDataUsageCache": "캐시",
"@aboutDataUsageCache": {},
"overlayHistogramRGB": "RGB",
"@overlayHistogramRGB": {},
"overlayHistogramNone": "없음",
"@overlayHistogramNone": {},
"overlayHistogramLuminance": "휘도",
"@overlayHistogramLuminance": {},
"settingsViewerShowHistogram": "히스토그램 표시",
"@settingsViewerShowHistogram": {}
} }

119
lib/l10n/app_my.arb Normal file
View file

@ -0,0 +1,119 @@
{
"sourceStateLocatingPlaces": "နေရာ တည်နေရာများ",
"@sourceStateLocatingPlaces": {},
"chipActionDelete": "ဖျက်",
"@chipActionDelete": {},
"welcomeMessage": "Avesမှကြိုဆိုပါတယ်",
"@welcomeMessage": {},
"columnCount": "{count, plural, =1{1 ကော်လံ} other{{count} ကော်လံများ}}",
"@columnCount": {
"placeholders": {
"count": {}
}
},
"appName": "Aves",
"@appName": {},
"welcomeOptional": "ရွေးချယ်နိုင်သော။",
"@welcomeOptional": {},
"welcomeTermsToggle": "ကျွနု်ပ်",
"@welcomeTermsToggle": {},
"itemCount": "{count, plural, =1{1 အိုင်တမ်} other{{count} အိုင်တမ်များ}}",
"@itemCount": {
"placeholders": {
"count": {}
}
},
"timeSeconds": "{seconds, plural, =1{1 စက္ကန့်} other{{seconds} စက္ကန့်များ}}",
"@timeSeconds": {
"placeholders": {
"seconds": {}
}
},
"timeMinutes": "{minutes, plural, =1{1 မိနစ်} other{{minutes} မိနစ်များ}}",
"@timeMinutes": {
"placeholders": {
"minutes": {}
}
},
"timeDays": "{days, plural, =1{1 ရက်} other{{days} ရက်များ}}",
"@timeDays": {
"placeholders": {
"days": {}
}
},
"focalLength": "{length} မီလီမီတာ",
"@focalLength": {
"placeholders": {
"length": {
"type": "String",
"example": "5.4"
}
}
},
"applyButtonLabel": "အတည်ပြု",
"@applyButtonLabel": {},
"cancelTooltip": "ပယ်ဖျက်မည်",
"@cancelTooltip": {},
"previousTooltip": "နောက်သို့",
"@previousTooltip": {},
"deleteButtonLabel": "ဖျက်မည်",
"@deleteButtonLabel": {},
"nextButtonLabel": "ရှေ့သို့",
"@nextButtonLabel": {},
"showButtonLabel": "ပြရန်",
"@showButtonLabel": {},
"hideButtonLabel": "ဝှက်ရန်",
"@hideButtonLabel": {},
"resetTooltip": "ပြန်ပြုပြင်မည်",
"@resetTooltip": {},
"continueButtonLabel": "ရှေ့ဆက်သွားရန်",
"@continueButtonLabel": {},
"clearTooltip": "ရှင်းလင်းမည်",
"@clearTooltip": {},
"saveCopyButtonLabel": "မိတ္တူကိုသိမ်းမည်",
"@saveCopyButtonLabel": {},
"showTooltip": "ပြရန်",
"@showTooltip": {},
"actionRemove": "ဖယ်ရှားမည်",
"@actionRemove": {},
"sourceStateLoading": "ခဏစောင့်ပါ",
"@sourceStateLoading": {},
"chipActionGoToCountryPage": "နိုင်ငံတွေထဲမှာပြရန်",
"@chipActionGoToCountryPage": {},
"applyTooltip": "အတည်ပြု",
"@applyTooltip": {},
"changeTooltip": "ပြောင်းမည်",
"@changeTooltip": {},
"nextTooltip": "ရှေ့သို့",
"@nextTooltip": {},
"chipActionGoToPlacePage": "နေရာများထဲတွင်ပြရန်",
"@chipActionGoToPlacePage": {},
"chipActionGoToTagPage": "အမှတ်အသားများတွင်ပြရန်",
"@chipActionGoToTagPage": {},
"chipActionFilterOut": "စစ်ထုတ်ရန်",
"@chipActionFilterOut": {},
"chipActionFilterIn": "စစ်သွင်းရန်",
"@chipActionFilterIn": {},
"chipActionHide": "ဝှက်",
"@chipActionHide": {},
"chipActionLock": "သော့ခတ်",
"@chipActionLock": {},
"chipActionPin": "အပေါ်ဆုံးများပင်တွဲရန်",
"@chipActionPin": {},
"chipActionUnpin": "အပေါ်ဆုံးမှပင်ဖြုတ်ရန်",
"@chipActionUnpin": {},
"hideTooltip": "ဝှက်ရန်",
"@hideTooltip": {},
"pickTooltip": "ရွေးရန်",
"@pickTooltip": {},
"saveTooltip": "သိမ်းမည်",
"@saveTooltip": {},
"doubleBackExitMessage": "ထွက်ရန်\"နောက်\"ကိုထပ်နိှပ်ပါ။",
"@doubleBackExitMessage": {},
"doNotAskAgain": "ထပ်မမေးပါနှင့်",
"@doNotAskAgain": {},
"sourceStateLocatingCountries": "နိုင်ငံတည်နေရာများ",
"@sourceStateLocatingCountries": {},
"chipActionGoToAlbumPage": "အယ်လ်ဘမ်တွေထဲမှာပြရန်",
"@chipActionGoToAlbumPage": {}
}

View file

@ -1420,5 +1420,65 @@
"settingsConfirmationVaultDataLoss": "Vis advarsel om hvelv-datatap", "settingsConfirmationVaultDataLoss": "Vis advarsel om hvelv-datatap",
"@settingsConfirmationVaultDataLoss": {}, "@settingsConfirmationVaultDataLoss": {},
"lengthUnitPercent": "%", "lengthUnitPercent": "%",
"@lengthUnitPercent": {} "@lengthUnitPercent": {},
"editorTransformCrop": "Beskjær",
"@editorTransformCrop": {},
"editorTransformRotate": "Roter",
"@editorTransformRotate": {},
"statePageTitle": "Tilstander",
"@statePageTitle": {},
"stateEmpty": "Ingen tilstander",
"@stateEmpty": {},
"settingsVideoPlaybackTile": "Avspilling",
"@settingsVideoPlaybackTile": {},
"settingsVideoResumptionModeTile": "Gjenoppta avspilling",
"@settingsVideoResumptionModeTile": {},
"settingsVideoResumptionModeDialogTitle": "Gjenoppta avspilling",
"@settingsVideoResumptionModeDialogTitle": {},
"maxBrightnessAlways": "Alltid",
"@maxBrightnessAlways": {},
"videoResumptionModeNever": "Aldri",
"@videoResumptionModeNever": {},
"maxBrightnessNever": "Aldri",
"@maxBrightnessNever": {},
"videoResumptionModeAlways": "Alltid",
"@videoResumptionModeAlways": {},
"exportEntryDialogQuality": "Kvalitet",
"@exportEntryDialogQuality": {},
"aboutDataUsageSectionTitle": "Databruk",
"@aboutDataUsageSectionTitle": {},
"aboutDataUsageData": "Data",
"@aboutDataUsageData": {},
"aboutDataUsageMisc": "Ymse",
"@aboutDataUsageMisc": {},
"settingsVideoPlaybackPageTitle": "Avspilling",
"@settingsVideoPlaybackPageTitle": {},
"settingsVideoBackgroundMode": "Bakgrunnsmodus",
"@settingsVideoBackgroundMode": {},
"saveCopyButtonLabel": "Lagre kopi",
"@saveCopyButtonLabel": {},
"applyTooltip": "Bruk",
"@applyTooltip": {},
"overlayHistogramRGB": "RGB",
"@overlayHistogramRGB": {},
"patternDialogConfirm": "Bekreft mønster",
"@patternDialogConfirm": {},
"aboutDataUsageCache": "Hurtiglager",
"@aboutDataUsageCache": {},
"aboutDataUsageDatabase": "Database",
"@aboutDataUsageDatabase": {},
"settingsAskEverytime": "Spør hver gang",
"@settingsAskEverytime": {},
"settingsVideoBackgroundModeDialogTitle": "Bakgrunnsmodus",
"@settingsVideoBackgroundModeDialogTitle": {},
"tagEditorDiscardDialogMessage": "Forkast endringer?",
"@tagEditorDiscardDialogMessage": {},
"tagPlaceholderState": "Tilstand?",
"@tagPlaceholderState": {},
"chipActionShowCountryStates": "Vis tilstander",
"@chipActionShowCountryStates": {},
"searchStatesSectionTitle": "Tilstander",
"@searchStatesSectionTitle": {},
"vaultLockTypePattern": "Mønster",
"@vaultLockTypePattern": {}
} }

View file

@ -1492,5 +1492,27 @@
"cropAspectRatioSquare": "Kwadrat", "cropAspectRatioSquare": "Kwadrat",
"@cropAspectRatioSquare": {}, "@cropAspectRatioSquare": {},
"widgetTapUpdateWidget": "Zaktualizuj widżet", "widgetTapUpdateWidget": "Zaktualizuj widżet",
"@widgetTapUpdateWidget": {} "@widgetTapUpdateWidget": {},
"aboutDataUsageExternal": "Zewnętrzny",
"@aboutDataUsageExternal": {},
"aboutDataUsageSectionTitle": "Wykorzystanie danych",
"@aboutDataUsageSectionTitle": {},
"aboutDataUsageInternal": "Wewnętrzny",
"@aboutDataUsageInternal": {},
"aboutDataUsageData": "Dane",
"@aboutDataUsageData": {},
"aboutDataUsageDatabase": "Baza danych",
"@aboutDataUsageDatabase": {},
"aboutDataUsageCache": "Pamięć podręczna",
"@aboutDataUsageCache": {},
"aboutDataUsageMisc": "Różne",
"@aboutDataUsageMisc": {},
"overlayHistogramNone": "Brak",
"@overlayHistogramNone": {},
"overlayHistogramRGB": "RGB",
"@overlayHistogramRGB": {},
"overlayHistogramLuminance": "Jasność",
"@overlayHistogramLuminance": {},
"settingsViewerShowHistogram": "Pokaż histogram",
"@settingsViewerShowHistogram": {}
} }

View file

@ -1316,5 +1316,23 @@
"videoResumptionModeNever": "Nunca", "videoResumptionModeNever": "Nunca",
"@videoResumptionModeNever": {}, "@videoResumptionModeNever": {},
"tagEditorDiscardDialogMessage": "Pretende rejeitar as alterações?", "tagEditorDiscardDialogMessage": "Pretende rejeitar as alterações?",
"@tagEditorDiscardDialogMessage": {} "@tagEditorDiscardDialogMessage": {},
"saveCopyButtonLabel": "SALVAR CÓPIA",
"@saveCopyButtonLabel": {},
"applyTooltip": "Aplicar",
"@applyTooltip": {},
"editorActionTransform": "Transformar",
"@editorActionTransform": {},
"editorTransformCrop": "Cortar",
"@editorTransformCrop": {},
"editorTransformRotate": "Girar",
"@editorTransformRotate": {},
"cropAspectRatioFree": "Livre",
"@cropAspectRatioFree": {},
"cropAspectRatioOriginal": "Original",
"@cropAspectRatioOriginal": {},
"cropAspectRatioSquare": "Quadrada",
"@cropAspectRatioSquare": {},
"widgetTapUpdateWidget": "Atualizar o widget",
"@widgetTapUpdateWidget": {}
} }

View file

@ -149,7 +149,7 @@
"@videoActionSkip10": {}, "@videoActionSkip10": {},
"videoActionSelectStreams": "Выбрать дорожку", "videoActionSelectStreams": "Выбрать дорожку",
"@videoActionSelectStreams": {}, "@videoActionSelectStreams": {},
"videoActionSetSpeed": "Скорость вопспроизведения", "videoActionSetSpeed": "Скорость воспроизведения",
"@videoActionSetSpeed": {}, "@videoActionSetSpeed": {},
"viewerActionSettings": "Настройки", "viewerActionSettings": "Настройки",
"@viewerActionSettings": {}, "@viewerActionSettings": {},
@ -1334,5 +1334,27 @@
"searchStatesSectionTitle": "Регионы", "searchStatesSectionTitle": "Регионы",
"@searchStatesSectionTitle": {}, "@searchStatesSectionTitle": {},
"settingsCollectionBurstPatternsNone": "Без вспышки", "settingsCollectionBurstPatternsNone": "Без вспышки",
"@settingsCollectionBurstPatternsNone": {} "@settingsCollectionBurstPatternsNone": {},
"aboutDataUsageSectionTitle": "Использование данных",
"@aboutDataUsageSectionTitle": {},
"aboutDataUsageData": "Данные",
"@aboutDataUsageData": {},
"aboutDataUsageCache": "Кэш",
"@aboutDataUsageCache": {},
"aboutDataUsageDatabase": "База данных",
"@aboutDataUsageDatabase": {},
"aboutDataUsageMisc": "Разнообразное",
"@aboutDataUsageMisc": {},
"aboutDataUsageInternal": "Внутреннее",
"@aboutDataUsageInternal": {},
"aboutDataUsageExternal": "Внешнее",
"@aboutDataUsageExternal": {},
"overlayHistogramNone": "Откл.",
"@overlayHistogramNone": {},
"overlayHistogramRGB": "RGB",
"@overlayHistogramRGB": {},
"settingsViewerShowHistogram": "Показать гистограмму",
"@settingsViewerShowHistogram": {},
"overlayHistogramLuminance": "Яркость",
"@overlayHistogramLuminance": {}
} }

10
lib/l10n/app_sl.arb Normal file
View file

@ -0,0 +1,10 @@
{
"appName": "Aves",
"@appName": {},
"welcomeMessage": "Dobrodošli v Avesu",
"@welcomeMessage": {},
"welcomeOptional": "Izbirno",
"@welcomeOptional": {},
"welcomeTermsToggle": "Sprejmem pogoje uporabe",
"@welcomeTermsToggle": {}
}

View file

@ -341,7 +341,7 @@
} }
} }
}, },
"videoResumeButtonLabel": "ПРОДОВЖИТИ", "videoResumeButtonLabel": "ВІДНОВИТИ",
"@videoResumeButtonLabel": {}, "@videoResumeButtonLabel": {},
"setCoverDialogAuto": "Авто", "setCoverDialogAuto": "Авто",
"@setCoverDialogAuto": {}, "@setCoverDialogAuto": {},
@ -1492,5 +1492,27 @@
"cropAspectRatioSquare": "Площа", "cropAspectRatioSquare": "Площа",
"@cropAspectRatioSquare": {}, "@cropAspectRatioSquare": {},
"widgetTapUpdateWidget": "Оновити віджет", "widgetTapUpdateWidget": "Оновити віджет",
"@widgetTapUpdateWidget": {} "@widgetTapUpdateWidget": {},
"aboutDataUsageSectionTitle": "Використання даних",
"@aboutDataUsageSectionTitle": {},
"aboutDataUsageMisc": "Різне",
"@aboutDataUsageMisc": {},
"aboutDataUsageInternal": "Внутрішній",
"@aboutDataUsageInternal": {},
"aboutDataUsageExternal": "Зовнішній",
"@aboutDataUsageExternal": {},
"aboutDataUsageCache": "Кеш",
"@aboutDataUsageCache": {},
"aboutDataUsageDatabase": "База даних",
"@aboutDataUsageDatabase": {},
"aboutDataUsageData": "Дані",
"@aboutDataUsageData": {},
"overlayHistogramNone": "Нічого",
"@overlayHistogramNone": {},
"overlayHistogramRGB": "RGB",
"@overlayHistogramRGB": {},
"overlayHistogramLuminance": "Яскравість",
"@overlayHistogramLuminance": {},
"settingsViewerShowHistogram": "Показати гістограму",
"@settingsViewerShowHistogram": {}
} }

View file

@ -130,7 +130,7 @@
"@slideshowActionResume": {}, "@slideshowActionResume": {},
"entryInfoActionEditLocation": "編輯座標", "entryInfoActionEditLocation": "編輯座標",
"@entryInfoActionEditLocation": {}, "@entryInfoActionEditLocation": {},
"entryInfoActionEditTitleDescription": "編輯標題和述", "entryInfoActionEditTitleDescription": "編輯標題和述",
"@entryInfoActionEditTitleDescription": {}, "@entryInfoActionEditTitleDescription": {},
"entryInfoActionExportMetadata": "匯出元資料", "entryInfoActionExportMetadata": "匯出元資料",
"@entryInfoActionExportMetadata": {}, "@entryInfoActionExportMetadata": {},
@ -671,11 +671,11 @@
"@settingsConfirmationTile": {}, "@settingsConfirmationTile": {},
"settingsConfirmationDialogTitle": "確認對話框", "settingsConfirmationDialogTitle": "確認對話框",
"@settingsConfirmationDialogTitle": {}, "@settingsConfirmationDialogTitle": {},
"settingsConfirmationBeforeDeleteItems": "永久刪除項目前詢問", "settingsConfirmationBeforeDeleteItems": "永久刪除項目前詢問",
"@settingsConfirmationBeforeDeleteItems": {}, "@settingsConfirmationBeforeDeleteItems": {},
"settingsConfirmationBeforeMoveToBinItems": "移動項目到資源回收桶前詢問", "settingsConfirmationBeforeMoveToBinItems": "移動項目到資源回收桶前詢問",
"@settingsConfirmationBeforeMoveToBinItems": {}, "@settingsConfirmationBeforeMoveToBinItems": {},
"settingsConfirmationBeforeMoveUndatedItems": "移動沒有日期項目前詢問", "settingsConfirmationBeforeMoveUndatedItems": "日期不詳的項目移動前先詢問",
"@settingsConfirmationBeforeMoveUndatedItems": {}, "@settingsConfirmationBeforeMoveUndatedItems": {},
"settingsConfirmationAfterMoveToBinItems": "移動項目到資源回收桶後顯示訊息", "settingsConfirmationAfterMoveToBinItems": "移動項目到資源回收桶後顯示訊息",
"@settingsConfirmationAfterMoveToBinItems": {}, "@settingsConfirmationAfterMoveToBinItems": {},
@ -1297,7 +1297,7 @@
"@viewerInfoSearchEmpty": {}, "@viewerInfoSearchEmpty": {},
"viewerInfoSearchSuggestionDate": "日期和時間", "viewerInfoSearchSuggestionDate": "日期和時間",
"@viewerInfoSearchSuggestionDate": {}, "@viewerInfoSearchSuggestionDate": {},
"viewerInfoSearchSuggestionDescription": "述", "viewerInfoSearchSuggestionDescription": "述",
"@viewerInfoSearchSuggestionDescription": {}, "@viewerInfoSearchSuggestionDescription": {},
"viewerInfoSearchSuggestionDimensions": "範圍", "viewerInfoSearchSuggestionDimensions": "範圍",
"@viewerInfoSearchSuggestionDimensions": {}, "@viewerInfoSearchSuggestionDimensions": {},

View file

@ -49,18 +49,28 @@ class Contributors {
Contributor('Макар Разин', 'makarrazin14@gmail.com'), Contributor('Макар Разин', 'makarrazin14@gmail.com'),
Contributor('Leon', 'leonhoog@outlook.com'), Contributor('Leon', 'leonhoog@outlook.com'),
Contributor('stephen-cusi', 'magiskcurry@qq.com'), Contributor('stephen-cusi', 'magiskcurry@qq.com'),
Contributor('atilluF', '110931720+atilluF@users.noreply.github.com'),
Contributor('Davide Neri', 'davnerix@gmail.com'),
Contributor('ShiftCtrlAltDel', 'who--is@yandex.ru'),
Contributor('lol lol', 'besonderspositiverpanda@ji5.de'),
Contributor('Fabian Rennebeck', 'propago47@posteo.org'),
Contributor('Henry The Mole', 'htmole@gmail.com'),
// Contributor('SAMIRAH AIL', 'samiratalzahrani@gmail.com'), // Arabic // Contributor('SAMIRAH AIL', 'samiratalzahrani@gmail.com'), // Arabic
// Contributor('Salih Ail', 'rrrfff444@gmail.com'), // Arabic // Contributor('Salih Ail', 'rrrfff444@gmail.com'), // Arabic
// Contributor('nasreddineloukriz', 'nasreddineloukriz@gmail.com'), // Arabic // Contributor('nasreddineloukriz', 'nasreddineloukriz@gmail.com'), // Arabic
// Contributor('Mohamed Zeroug', 'mzeroug19@gmail.com'), // Arabic
// Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese
// Contributor('Idj', 'joneltmp+goahn@gmail.com'), // Hebrew
// Contributor('Rohit Burman', 'rohitburman31p@rediffmail.com'), // Hindi
// Contributor('Chethan', 'chethan@users.noreply.hosted.weblate.org'), // Kannada
// Contributor('GoRaN', 'gorangharib.909@gmail.com'), // Kurdish (Central)
// Contributor('Raman', 'xysed@tutanota.com'), // Malayalam
// Contributor('Subham Jena', 'subhamjena8465@gmail.com'), // Odia
// Contributor('امیر جهانگرد', 'ijahangard.a@gmail.com'), // Persian // Contributor('امیر جهانگرد', 'ijahangard.a@gmail.com'), // Persian
// Contributor('slasb37', 'p84haghi@gmail.com'), // Persian // Contributor('slasb37', 'p84haghi@gmail.com'), // Persian
// Contributor('Nattapong K', 'mixer5056@gmail.com'), // Thai
// Contributor('Idj', 'joneltmp+goahn@gmail.com'), // Hebrew
// Contributor('Martin Frandel', 'martinko.fr@gmail.com'), // Slovak // Contributor('Martin Frandel', 'martinko.fr@gmail.com'), // Slovak
// Contributor('GoRaN', 'gorangharib.909@gmail.com'), // Kurdish (Central) // Contributor('mytja', 'mamnju21@gmail.com'), // Slovenian
// Contributor('Rohit Burman', 'rohitburman31p@rediffmail.com'), // Hindi // Contributor('Nattapong K', 'mixer5056@gmail.com'), // Thai
// Contributor('Subham Jena', 'subhamjena8465@gmail.com'), // Odia
// Contributor('Raman', 'xysed@tutanota.com'), // Malayalam
}; };
} }

View file

@ -92,6 +92,11 @@ class Dependencies {
licenseUrl: 'https://github.com/flutter/packages/blob/main/packages/local_auth/local_auth/LICENSE', licenseUrl: 'https://github.com/flutter/packages/blob/main/packages/local_auth/local_auth/LICENSE',
sourceUrl: 'https://github.com/flutter/packages/tree/main/packages/local_auth/local_auth', sourceUrl: 'https://github.com/flutter/packages/tree/main/packages/local_auth/local_auth',
), ),
Dependency(
name: 'Media Kit',
license: mit,
sourceUrl: 'https://github.com/media-kit/media-kit',
),
Dependency( Dependency(
name: 'Package Info Plus', name: 'Package Info Plus',
license: bsd3, license: bsd3,
@ -365,11 +370,6 @@ class Dependencies {
license: mit, license: mit,
sourceUrl: 'https://github.com/brianegan/transparent_image', sourceUrl: 'https://github.com/brianegan/transparent_image',
), ),
Dependency(
name: 'Tuple',
license: bsd2,
sourceUrl: 'https://github.com/google/tuple.dart',
),
Dependency( Dependency(
name: 'Vector Math', name: 'Vector Math',
license: '$zlib, $bsd3', license: '$zlib, $bsd3',

View file

@ -13,7 +13,6 @@ import 'package:collection/collection.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';
import 'package:tuple/tuple.dart';
final Covers covers = Covers._private(); final Covers covers = Covers._private();
@ -40,11 +39,11 @@ class Covers {
Set<CoverRow> get all => Set.unmodifiable(_rows); Set<CoverRow> get all => Set.unmodifiable(_rows);
Tuple3<int?, String?, Color?>? of(CollectionFilter filter) { (int? entryId, String? packageName, Color? color)? of(CollectionFilter filter) {
if (filter is AlbumFilter && vaults.isLocked(filter.album)) return null; if (filter is AlbumFilter && vaults.isLocked(filter.album)) return null;
final row = _rows.firstWhereOrNull((row) => row.filter == filter); final row = _rows.firstWhereOrNull((row) => row.filter == filter);
return row != null ? Tuple3(row.entryId, row.packageName, row.color) : null; return row != null ? (row.entryId, row.packageName, row.color) : null;
} }
Future<void> set({ Future<void> set({
@ -113,7 +112,7 @@ class Covers {
} }
AlbumType effectiveAlbumType(String albumPath) { AlbumType effectiveAlbumType(String albumPath) {
final filterPackage = of(AlbumFilter(albumPath, null))?.item2; final filterPackage = of(AlbumFilter(albumPath, null))?.$2;
if (filterPackage != null) { if (filterPackage != null) {
return filterPackage.isEmpty ? AlbumType.regular : AlbumType.app; return filterPackage.isEmpty ? AlbumType.regular : AlbumType.app;
} else { } else {
@ -122,7 +121,7 @@ class Covers {
} }
String? effectiveAlbumPackage(String albumPath) { String? effectiveAlbumPackage(String albumPath) {
final filterPackage = of(AlbumFilter(albumPath, null))?.item2; final filterPackage = of(AlbumFilter(albumPath, null))?.$2;
return filterPackage ?? appInventory.getAlbumAppPackageName(albumPath); return filterPackage ?? appInventory.getAlbumAppPackageName(albumPath);
} }

View file

@ -200,6 +200,7 @@ class AvesEntry with AvesEntryBase {
_bestTitle = null; _bestTitle = null;
} }
@override
String? get path => _path; String? get path => _path;
// directory path, without the trailing separator // directory path, without the trailing separator
@ -293,6 +294,9 @@ class AvesEntry with AvesEntryBase {
return d == null ? null : DateTime(d.year, d.month, d.day); return d == null ? null : DateTime(d.year, d.month, d.day);
} }
@override
bool get isAnimated => catalogMetadata?.isAnimated ?? false;
@override @override
int? get durationMillis => _durationMillis; int? get durationMillis => _durationMillis;

View file

@ -3,6 +3,7 @@ import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/geotiff.dart'; import 'package:aves/model/geotiff.dart';
import 'package:aves/model/metadata/catalog.dart'; import 'package:aves/model/metadata/catalog.dart';
import 'package:aves/model/video/metadata.dart'; import 'package:aves/model/video/metadata.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/services/metadata/svg_metadata_service.dart'; import 'package:aves/services/metadata/svg_metadata_service.dart';
@ -23,7 +24,7 @@ extension ExtraAvesEntryCatalog on AvesEntry {
catalogMetadata = CatalogMetadata(id: id); catalogMetadata = CatalogMetadata(id: id);
} else { } else {
// pre-processing // pre-processing
if (isVideo && (!isSized || durationMillis == 0)) { if ((isVideo && (!isSized || durationMillis == 0)) || mimeType == MimeTypes.avif) {
// exotic video that is not sized during loading // exotic video that is not sized during loading
final fields = await VideoMetadataFormatter.getLoadingMetadata(this); final fields = await VideoMetadataFormatter.getLoadingMetadata(this);
await applyNewFields(fields, persist: persist); await applyNewFields(fields, persist: persist);
@ -33,7 +34,7 @@ extension ExtraAvesEntryCatalog on AvesEntry {
catalogMetadata = await metadataFetchService.getCatalogMetadata(this, background: background); catalogMetadata = await metadataFetchService.getCatalogMetadata(this, background: background);
// post-processing // post-processing
if (isVideo && (catalogMetadata?.dateMillis ?? 0) == 0) { if ((isVideo && (catalogMetadata?.dateMillis ?? 0) == 0) || (mimeType == MimeTypes.avif && durationMillis != null)) {
catalogMetadata = await VideoMetadataFormatter.getCatalogMetadata(this); catalogMetadata = await VideoMetadataFormatter.getCatalogMetadata(this);
} }
if (isGeotiff && !hasGps) { if (isGeotiff && !hasGps) {

View file

@ -71,7 +71,7 @@ extension ExtraAvesEntryInfo on AvesEntry {
Future<List<MetadataDirectory>> _getStreamDirectories(BuildContext context) async { Future<List<MetadataDirectory>> _getStreamDirectories(BuildContext context) async {
final directories = <MetadataDirectory>[]; final directories = <MetadataDirectory>[];
final mediaInfo = await VideoMetadataFormatter.getVideoMetadata(this); final mediaInfo = await videoMetadataFetcher.getMetadata(this);
final formattedMediaTags = VideoMetadataFormatter.formatInfo(mediaInfo); final formattedMediaTags = VideoMetadataFormatter.formatInfo(mediaInfo);
if (formattedMediaTags.isNotEmpty) { if (formattedMediaTags.isNotEmpty) {

View file

@ -8,8 +8,8 @@ import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/metadata/date_modifier.dart'; import 'package:aves/model/metadata/date_modifier.dart';
import 'package:aves/ref/metadata/exif.dart'; import 'package:aves/ref/metadata/exif.dart';
import 'package:aves/ref/metadata/iptc.dart'; import 'package:aves/ref/metadata/iptc.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/ref/metadata/xmp.dart'; import 'package:aves/ref/metadata/xmp.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/services/metadata/xmp.dart'; import 'package:aves/services/metadata/xmp.dart';
import 'package:aves/utils/time_utils.dart'; import 'package:aves/utils/time_utils.dart';
@ -82,7 +82,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
return dataTypes; return dataTypes;
} }
static final removalLocation = LatLng(0, 0); static const removalLocation = LatLng(0, 0);
Future<Set<EntryDataType>> editLocation(LatLng? latLng) async { Future<Set<EntryDataType>> editLocation(LatLng? latLng) async {
final dataTypes = <EntryDataType>{}; final dataTypes = <EntryDataType>{};

View file

@ -26,7 +26,9 @@ extension ExtraAvesEntryProps on AvesEntry {
bool get isImage => MimeTypes.isImage(mimeType); bool get isImage => MimeTypes.isImage(mimeType);
bool get isVideo => MimeTypes.isVideo(mimeType); bool get isVideo => MimeTypes.isVideo(mimeType) || (mimeType == MimeTypes.avif && isAnimated);
bool get isPureVideo => isVideo && !isAnimated;
// size // size
@ -34,9 +36,9 @@ extension ExtraAvesEntryProps on AvesEntry {
bool get isSized => width > 0 && height > 0; bool get isSized => width > 0 && height > 0;
Size videoDisplaySize(double sar) { Size videoDisplaySize(double? sar) {
final size = displaySize; final size = displaySize;
if (sar != 1) { if (sar != null && sar != 1) {
final dar = displayAspectRatio * sar; final dar = displayAspectRatio * sar;
final w = size.width; final w = size.width;
final h = size.height; final h = size.height;
@ -68,8 +70,6 @@ extension ExtraAvesEntryProps on AvesEntry {
// catalog // catalog
bool get isAnimated => catalogMetadata?.isAnimated ?? false;
bool get isGeotiff => catalogMetadata?.isGeotiff ?? false; bool get isGeotiff => catalogMetadata?.isGeotiff ?? false;
bool get is360 => catalogMetadata?.is360 ?? false; bool get is360 => catalogMetadata?.is360 ?? false;

View file

@ -64,7 +64,7 @@ class AlbumFilter extends CoveredCollectionFilter {
@override @override
Future<Color> color(BuildContext context) { Future<Color> color(BuildContext context) {
// custom color has precedence over others, even custom app color // custom color has precedence over others, even custom app color
final customColor = covers.of(this)?.item3; final customColor = covers.of(this)?.$3;
if (customColor != null) return SynchronousFuture(customColor); if (customColor != null) return SynchronousFuture(customColor);
final colors = context.read<AvesColorsData>(); final colors = context.read<AvesColorsData>();

View file

@ -157,7 +157,7 @@ abstract class CoveredCollectionFilter extends CollectionFilter {
@override @override
Future<Color> color(BuildContext context) { Future<Color> color(BuildContext context) {
final customColor = covers.of(this)?.item3; final customColor = covers.of(this)?.$3;
if (customColor != null) { if (customColor != null) {
return SynchronousFuture(customColor); return SynchronousFuture(customColor);
} }

View file

@ -68,14 +68,12 @@ class MimeFilter extends CollectionFilter {
@override @override
String getLabel(BuildContext context) { String getLabel(BuildContext context) {
switch (mime) { final l10n = context.l10n;
case MimeTypes.anyImage: return switch (mime) {
return context.l10n.filterMimeImageLabel; MimeTypes.anyImage => l10n.filterMimeImageLabel,
case MimeTypes.anyVideo: MimeTypes.anyVideo => l10n.filterMimeVideoLabel,
return context.l10n.filterMimeVideoLabel; _ => _label,
default: };
return _label;
}
} }
@override @override

View file

@ -60,16 +60,13 @@ class MissingFilter extends CollectionFilter {
@override @override
String getLabel(BuildContext context) { String getLabel(BuildContext context) {
switch (metadataType) { final l10n = context.l10n;
case _date: return switch (metadataType) {
return context.l10n.filterNoDateLabel; _date => l10n.filterNoDateLabel,
case _fineAddress: _fineAddress => l10n.filterNoAddressLabel,
return context.l10n.filterNoAddressLabel; _title => l10n.filterNoTitleLabel,
case _title: _ => metadataType,
return context.l10n.filterNoTitleLabel; };
default:
return metadataType;
}
} }
@override @override

View file

@ -86,16 +86,13 @@ class PlaceholderFilter extends CollectionFilter {
@override @override
String getLabel(BuildContext context) { String getLabel(BuildContext context) {
switch (placeholder) { final l10n = context.l10n;
case _country: return switch (placeholder) {
return context.l10n.tagPlaceholderCountry; _country => l10n.tagPlaceholderCountry,
case _state: _state => l10n.tagPlaceholderState,
return context.l10n.tagPlaceholderState; _place => l10n.tagPlaceholderPlace,
case _place: _ => placeholder,
return context.l10n.tagPlaceholderPlace; };
default:
return placeholder;
}
} }
@override @override

View file

@ -8,18 +8,34 @@ class RatingFilter extends CollectionFilter {
static const type = 'rating'; static const type = 'rating';
final int rating; final int rating;
final String op;
late final EntryFilter _test; late final EntryFilter _test;
@override static const opEqual = '=';
List<Object?> get props => [rating, reversed]; static const opOrLower = '<=';
static const opOrGreater = '>=';
RatingFilter(this.rating, {super.reversed = false}) { @override
_test = (entry) => entry.rating == rating; List<Object?> get props => [rating, op, reversed];
RatingFilter(this.rating, {this.op = opEqual, super.reversed = false}) {
_test = switch (op) {
opOrLower => (entry) => entry.rating <= rating && entry.rating > 0,
opOrGreater => (entry) => entry.rating >= rating,
opEqual || _ => (entry) => entry.rating == rating,
};
} }
RatingFilter copyWith(String op) => RatingFilter(
rating,
op: op,
reversed: reversed,
);
factory RatingFilter.fromMap(Map<String, dynamic> json) { factory RatingFilter.fromMap(Map<String, dynamic> json) {
return RatingFilter( return RatingFilter(
json['rating'] ?? 0, json['rating'] ?? 0,
op: json['op'] ?? opEqual,
reversed: json['reversed'] ?? false, reversed: json['reversed'] ?? false,
); );
} }
@ -28,6 +44,7 @@ class RatingFilter extends CollectionFilter {
Map<String, dynamic> toMap() => { Map<String, dynamic> toMap() => {
'type': type, 'type': type,
'rating': rating, 'rating': rating,
'op': op,
'reversed': reversed, 'reversed': reversed,
}; };
@ -38,37 +55,42 @@ class RatingFilter extends CollectionFilter {
bool get exclusiveProp => true; bool get exclusiveProp => true;
@override @override
String get universalLabel => '$rating'; String get universalLabel => '$op $rating';
@override @override
String getLabel(BuildContext context) => formatRating(context, rating); String getLabel(BuildContext context) => switch (op) {
opOrLower || opOrGreater => '${UniChars.whiteMediumStar} ${formatRatingRange(context, rating, op)}',
opEqual || _ => formatRating(context, rating),
};
@override @override
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) { Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) {
switch (rating) { return switch (rating) {
case -1: -1 => Icon(AIcons.ratingRejected, size: size),
return Icon(AIcons.ratingRejected, size: size); 0 => Icon(AIcons.ratingUnrated, size: size),
case 0: _ => null,
return Icon(AIcons.ratingUnrated, size: size); };
default:
return null;
}
} }
@override @override
String get category => type; String get category => type;
@override @override
String get key => '$type-$reversed-$rating'; String get key => '$type-$reversed-$rating-$op';
static String formatRating(BuildContext context, int rating) { static String formatRating(BuildContext context, int rating) {
switch (rating) { return switch (rating) {
case -1: -1 => context.l10n.filterRatingRejectedLabel,
return context.l10n.filterRatingRejectedLabel; 0 => context.l10n.filterNoRatingLabel,
case 0: _ => UniChars.whiteMediumStar * rating,
return context.l10n.filterNoRatingLabel; };
default:
return UniChars.whiteMediumStar * rating;
} }
static String formatRatingRange(BuildContext context, int rating, String op) {
return switch (op) {
opOrLower => '1~$rating',
opOrGreater => '$rating~5',
opEqual || _ => '$rating',
};
} }
} }

View file

@ -80,22 +80,16 @@ class TypeFilter extends CollectionFilter {
@override @override
String getLabel(BuildContext context) { String getLabel(BuildContext context) {
switch (itemType) { final l10n = context.l10n;
case _animated: return switch (itemType) {
return context.l10n.filterTypeAnimatedLabel; _animated => l10n.filterTypeAnimatedLabel,
case _motionPhoto: _motionPhoto => l10n.filterTypeMotionPhotoLabel,
return context.l10n.filterTypeMotionPhotoLabel; _panorama => l10n.filterTypePanoramaLabel,
case _panorama: _raw => l10n.filterTypeRawLabel,
return context.l10n.filterTypePanoramaLabel; _sphericalVideo => l10n.filterTypeSphericalVideoLabel,
case _raw: _geotiff => l10n.filterTypeGeotiffLabel,
return context.l10n.filterTypeRawLabel; _ => itemType,
case _sphericalVideo: };
return context.l10n.filterTypeSphericalVideoLabel;
case _geotiff:
return context.l10n.filterTypeGeotiffLabel;
default:
return itemType;
}
} }
@override @override

View file

@ -55,6 +55,7 @@ class CatalogMetadata {
int? id, int? id,
String? mimeType, String? mimeType,
int? dateMillis, int? dateMillis,
bool? isAnimated,
bool? isMultiPage, bool? isMultiPage,
int? rotationDegrees, int? rotationDegrees,
double? latitude, double? latitude,
@ -64,7 +65,7 @@ class CatalogMetadata {
id: id ?? this.id, id: id ?? this.id,
mimeType: mimeType ?? this.mimeType, mimeType: mimeType ?? this.mimeType,
dateMillis: dateMillis ?? this.dateMillis, dateMillis: dateMillis ?? this.dateMillis,
isAnimated: isAnimated, isAnimated: isAnimated ?? this.isAnimated,
isFlipped: isFlipped, isFlipped: isFlipped,
isGeotiff: isGeotiff, isGeotiff: isGeotiff,
is360: is360, is360: is360,

View file

@ -1,9 +1,6 @@
import 'dart:ui';
import 'package:aves/model/filters/recent.dart'; import 'package:aves/model/filters/recent.dart';
import 'package:aves/model/naming_pattern.dart'; import 'package:aves/model/naming_pattern.dart';
import 'package:aves/ref/mime_types.dart'; import 'package:aves/ref/mime_types.dart';
import 'package:aves/utils/colors.dart';
import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart';
import 'package:aves/widgets/filter_grids/countries_page.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart';
import 'package:aves/widgets/filter_grids/tags_page.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart';
@ -75,6 +72,7 @@ class SettingsDefaults {
]; ];
static const showOverlayOnOpening = true; static const showOverlayOnOpening = true;
static const showOverlayMinimap = false; static const showOverlayMinimap = false;
static const overlayHistogramStyle = OverlayHistogramStyle.none;
static const showOverlayInfo = true; static const showOverlayInfo = true;
static const showOverlayDescription = false; static const showOverlayDescription = false;
static const showOverlayRatingTags = false; static const showOverlayRatingTags = false;
@ -84,26 +82,6 @@ class SettingsDefaults {
static const viewerUseCutout = true; static const viewerUseCutout = true;
static const enableMotionPhotoAutoPlay = false; static const enableMotionPhotoAutoPlay = false;
// video
static const enableVideoHardwareAcceleration = true;
static const videoAutoPlayMode = VideoAutoPlayMode.disabled;
static const videoBackgroundMode = VideoBackgroundMode.disabled;
static const videoLoopMode = VideoLoopMode.shortOnly;
static const videoResumptionMode = VideoResumptionMode.ask;
static const videoShowRawTimedText = false;
static const videoControls = VideoControls.play;
static const videoGestureDoubleTapTogglePlay = false;
static const videoGestureSideDoubleTapSeek = true;
static const videoGestureVerticalDragBrightnessVolume = false;
// subtitles
static const subtitleFontSize = 20.0;
static const subtitleTextAlignment = TextAlign.center;
static const subtitleTextPosition = SubtitlePosition.bottom;
static const subtitleShowOutline = true;
static const subtitleTextColor = Color(0xFFFFFFFF);
static const subtitleBackgroundColor = ColorUtils.transparentBlack;
// info // info
static const infoMapZoom = 12.0; static const infoMapZoom = 12.0;
static const coordinateFormat = CoordinateFormat.dms; static const coordinateFormat = CoordinateFormat.dms;

View file

@ -7,9 +7,9 @@ extension ExtraAccessibilityTimeout on AccessibilityTimeout {
switch (this) { switch (this) {
case AccessibilityTimeout.system: case AccessibilityTimeout.system:
if (hasAction) { if (hasAction) {
return Duration(milliseconds: await (AccessibilityService.getRecommendedTimeToTakeAction(Durations.opToastActionDisplay))); return Duration(milliseconds: await (AccessibilityService.getRecommendedTimeToTakeAction(ADurations.opToastActionDisplay)));
} else { } else {
return Duration(milliseconds: await (AccessibilityService.getRecommendedTimeToRead(Durations.opToastTextDisplay))); return Duration(milliseconds: await (AccessibilityService.getRecommendedTimeToRead(ADurations.opToastTextDisplay)));
} }
case AccessibilityTimeout.s1: case AccessibilityTimeout.s1:
return const Duration(seconds: 1); return const Duration(seconds: 1);

View file

@ -0,0 +1,109 @@
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/settings/defaults.dart';
import 'package:aves/widgets/aves_app.dart';
import 'package:aves_model/aves_model.dart';
import 'package:collection/collection.dart';
import 'package:flutter/widgets.dart';
mixin AppSettings on SettingsAccess {
static const int _recentFilterHistoryMax = 10;
bool get hasAcceptedTerms => getBool(SettingKeys.hasAcceptedTermsKey) ?? SettingsDefaults.hasAcceptedTerms;
set hasAcceptedTerms(bool newValue) => set(SettingKeys.hasAcceptedTermsKey, newValue);
bool get canUseAnalysisService => getBool(SettingKeys.canUseAnalysisServiceKey) ?? SettingsDefaults.canUseAnalysisService;
set canUseAnalysisService(bool newValue) => set(SettingKeys.canUseAnalysisServiceKey, newValue);
bool get isInstalledAppAccessAllowed => getBool(SettingKeys.isInstalledAppAccessAllowedKey) ?? SettingsDefaults.isInstalledAppAccessAllowed;
set isInstalledAppAccessAllowed(bool newValue) => set(SettingKeys.isInstalledAppAccessAllowedKey, newValue);
bool get isErrorReportingAllowed => getBool(SettingKeys.isErrorReportingAllowedKey) ?? SettingsDefaults.isErrorReportingAllowed;
set isErrorReportingAllowed(bool newValue) => set(SettingKeys.isErrorReportingAllowedKey, newValue);
static const localeSeparator = '-';
Locale? get locale {
// exceptionally allow getting locale before settings are initialized
final tag = initialized ? getString(SettingKeys.localeKey) : null;
if (tag != null) {
final codes = tag.split(localeSeparator);
return Locale.fromSubtags(
languageCode: codes[0],
scriptCode: codes[1] == '' ? null : codes[1],
countryCode: codes[2] == '' ? null : codes[2],
);
}
return null;
}
set locale(Locale? newValue) {
String? tag;
if (newValue != null) {
tag = [
newValue.languageCode,
newValue.scriptCode ?? '',
newValue.countryCode ?? '',
].join(localeSeparator);
}
set(SettingKeys.localeKey, tag);
_appliedLocale = null;
}
List<Locale> _systemLocalesFallback = [];
set systemLocalesFallback(List<Locale> locales) => _systemLocalesFallback = locales;
Locale? _appliedLocale;
void resetAppliedLocale() => _appliedLocale = null;
Locale get appliedLocale {
if (_appliedLocale == null) {
final _locale = locale;
final preferredLocales = <Locale>[];
if (_locale != null) {
preferredLocales.add(_locale);
} else {
preferredLocales.addAll(WidgetsBinding.instance.platformDispatcher.locales);
if (preferredLocales.isEmpty) {
// the `window` locales may be empty in a window-less service context
preferredLocales.addAll(_systemLocalesFallback);
}
}
_appliedLocale = basicLocaleListResolution(preferredLocales, AvesApp.supportedLocales);
}
return _appliedLocale!;
}
int get catalogTimeZoneRawOffsetMillis => getInt(SettingKeys.catalogTimeZoneRawOffsetMillisKey) ?? 0;
set catalogTimeZoneRawOffsetMillis(int newValue) => set(SettingKeys.catalogTimeZoneRawOffsetMillisKey, newValue);
double getTileExtent(String routeName) => getDouble(SettingKeys.tileExtentPrefixKey + routeName) ?? 0;
void setTileExtent(String routeName, double newValue) => set(SettingKeys.tileExtentPrefixKey + routeName, newValue);
TileLayout getTileLayout(String routeName) => getEnumOrDefault(SettingKeys.tileLayoutPrefixKey + routeName, SettingsDefaults.tileLayout, TileLayout.values);
void setTileLayout(String routeName, TileLayout newValue) => set(SettingKeys.tileLayoutPrefixKey + routeName, newValue.toString());
String get entryRenamingPattern => getString(SettingKeys.entryRenamingPatternKey) ?? SettingsDefaults.entryRenamingPattern;
set entryRenamingPattern(String newValue) => set(SettingKeys.entryRenamingPatternKey, newValue);
List<int>? get topEntryIds => getStringList(SettingKeys.topEntryIdsKey)?.map(int.tryParse).whereNotNull().toList();
set topEntryIds(List<int>? newValue) => set(SettingKeys.topEntryIdsKey, newValue?.map((id) => id.toString()).whereNotNull().toList());
List<String> get recentDestinationAlbums => getStringList(SettingKeys.recentDestinationAlbumsKey) ?? [];
set recentDestinationAlbums(List<String> newValue) => set(SettingKeys.recentDestinationAlbumsKey, newValue.take(_recentFilterHistoryMax).toList());
List<CollectionFilter> get recentTags => (getStringList(SettingKeys.recentTagsKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toList();
set recentTags(List<CollectionFilter> newValue) => set(SettingKeys.recentTagsKey, newValue.take(_recentFilterHistoryMax).map((filter) => filter.toJson()).toList());
}

View file

@ -0,0 +1,56 @@
import 'package:aves/model/settings/defaults.dart';
import 'package:aves_model/aves_model.dart';
mixin CollectionSettings on SettingsAccess {
List<String> get collectionBurstPatterns => getStringList(SettingKeys.collectionBurstPatternsKey) ?? [];
set collectionBurstPatterns(List<String> newValue) => set(SettingKeys.collectionBurstPatternsKey, newValue);
EntryGroupFactor get collectionSectionFactor => getEnumOrDefault(SettingKeys.collectionGroupFactorKey, SettingsDefaults.collectionSectionFactor, EntryGroupFactor.values);
set collectionSectionFactor(EntryGroupFactor newValue) => set(SettingKeys.collectionGroupFactorKey, newValue.toString());
EntrySortFactor get collectionSortFactor => getEnumOrDefault(SettingKeys.collectionSortFactorKey, SettingsDefaults.collectionSortFactor, EntrySortFactor.values);
set collectionSortFactor(EntrySortFactor newValue) => set(SettingKeys.collectionSortFactorKey, newValue.toString());
bool get collectionSortReverse => getBool(SettingKeys.collectionSortReverseKey) ?? false;
set collectionSortReverse(bool newValue) => set(SettingKeys.collectionSortReverseKey, newValue);
List<EntrySetAction> get collectionBrowsingQuickActions => getEnumListOrDefault(SettingKeys.collectionBrowsingQuickActionsKey, SettingsDefaults.collectionBrowsingQuickActions, EntrySetAction.values);
set collectionBrowsingQuickActions(List<EntrySetAction> newValue) => set(SettingKeys.collectionBrowsingQuickActionsKey, newValue.map((v) => v.toString()).toList());
List<EntrySetAction> get collectionSelectionQuickActions => getEnumListOrDefault(SettingKeys.collectionSelectionQuickActionsKey, SettingsDefaults.collectionSelectionQuickActions, EntrySetAction.values);
set collectionSelectionQuickActions(List<EntrySetAction> newValue) => set(SettingKeys.collectionSelectionQuickActionsKey, newValue.map((v) => v.toString()).toList());
bool get showThumbnailFavourite => getBool(SettingKeys.showThumbnailFavouriteKey) ?? SettingsDefaults.showThumbnailFavourite;
set showThumbnailFavourite(bool newValue) => set(SettingKeys.showThumbnailFavouriteKey, newValue);
ThumbnailOverlayLocationIcon get thumbnailLocationIcon => getEnumOrDefault(SettingKeys.thumbnailLocationIconKey, SettingsDefaults.thumbnailLocationIcon, ThumbnailOverlayLocationIcon.values);
set thumbnailLocationIcon(ThumbnailOverlayLocationIcon newValue) => set(SettingKeys.thumbnailLocationIconKey, newValue.toString());
ThumbnailOverlayTagIcon get thumbnailTagIcon => getEnumOrDefault(SettingKeys.thumbnailTagIconKey, SettingsDefaults.thumbnailTagIcon, ThumbnailOverlayTagIcon.values);
set thumbnailTagIcon(ThumbnailOverlayTagIcon newValue) => set(SettingKeys.thumbnailTagIconKey, newValue.toString());
bool get showThumbnailMotionPhoto => getBool(SettingKeys.showThumbnailMotionPhotoKey) ?? SettingsDefaults.showThumbnailMotionPhoto;
set showThumbnailMotionPhoto(bool newValue) => set(SettingKeys.showThumbnailMotionPhotoKey, newValue);
bool get showThumbnailRating => getBool(SettingKeys.showThumbnailRatingKey) ?? SettingsDefaults.showThumbnailRating;
set showThumbnailRating(bool newValue) => set(SettingKeys.showThumbnailRatingKey, newValue);
bool get showThumbnailRaw => getBool(SettingKeys.showThumbnailRawKey) ?? SettingsDefaults.showThumbnailRaw;
set showThumbnailRaw(bool newValue) => set(SettingKeys.showThumbnailRawKey, newValue);
bool get showThumbnailVideoDuration => getBool(SettingKeys.showThumbnailVideoDurationKey) ?? SettingsDefaults.showThumbnailVideoDuration;
set showThumbnailVideoDuration(bool newValue) => set(SettingKeys.showThumbnailVideoDurationKey, newValue);
}

View file

@ -0,0 +1,37 @@
import 'package:aves/model/device.dart';
import 'package:aves/model/settings/defaults.dart';
import 'package:aves_model/aves_model.dart';
mixin DisplaySettings on SettingsAccess {
DisplayRefreshRateMode get displayRefreshRateMode => getEnumOrDefault(SettingKeys.displayRefreshRateModeKey, SettingsDefaults.displayRefreshRateMode, DisplayRefreshRateMode.values);
set displayRefreshRateMode(DisplayRefreshRateMode newValue) => set(SettingKeys.displayRefreshRateModeKey, newValue.toString());
AvesThemeBrightness get themeBrightness => getEnumOrDefault(SettingKeys.themeBrightnessKey, SettingsDefaults.themeBrightness, AvesThemeBrightness.values);
set themeBrightness(AvesThemeBrightness newValue) => set(SettingKeys.themeBrightnessKey, newValue.toString());
AvesThemeColorMode get themeColorMode => getEnumOrDefault(SettingKeys.themeColorModeKey, SettingsDefaults.themeColorMode, AvesThemeColorMode.values);
set themeColorMode(AvesThemeColorMode newValue) => set(SettingKeys.themeColorModeKey, newValue.toString());
bool get enableDynamicColor => getBool(SettingKeys.enableDynamicColorKey) ?? SettingsDefaults.enableDynamicColor;
set enableDynamicColor(bool newValue) => set(SettingKeys.enableDynamicColorKey, newValue);
bool get enableBlurEffect => getBool(SettingKeys.enableBlurEffectKey) ?? SettingsDefaults.enableBlurEffect;
set enableBlurEffect(bool newValue) => set(SettingKeys.enableBlurEffectKey, newValue);
MaxBrightness get maxBrightness => getEnumOrDefault(SettingKeys.maxBrightnessKey, SettingsDefaults.maxBrightness, MaxBrightness.values);
set maxBrightness(MaxBrightness newValue) => set(SettingKeys.maxBrightnessKey, newValue.toString());
bool get forceTvLayout => getBool(SettingKeys.forceTvLayoutKey) ?? SettingsDefaults.forceTvLayout;
set forceTvLayout(bool newValue) => set(SettingKeys.forceTvLayoutKey, newValue);
bool get useTvLayout => device.isTelevision || forceTvLayout;
bool get isReadOnly => useTvLayout;
}

View file

@ -0,0 +1,74 @@
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/settings/defaults.dart';
import 'package:aves/model/settings/modules/search.dart';
import 'package:aves_model/aves_model.dart';
import 'package:collection/collection.dart';
mixin FilterGridsSettings on SettingsAccess, SearchSettings {
AlbumChipGroupFactor get albumGroupFactor => getEnumOrDefault(SettingKeys.albumGroupFactorKey, SettingsDefaults.albumGroupFactor, AlbumChipGroupFactor.values);
set albumGroupFactor(AlbumChipGroupFactor newValue) => set(SettingKeys.albumGroupFactorKey, newValue.toString());
ChipSortFactor get albumSortFactor => getEnumOrDefault(SettingKeys.albumSortFactorKey, SettingsDefaults.chipListSortFactor, ChipSortFactor.values);
set albumSortFactor(ChipSortFactor newValue) => set(SettingKeys.albumSortFactorKey, newValue.toString());
ChipSortFactor get countrySortFactor => getEnumOrDefault(SettingKeys.countrySortFactorKey, SettingsDefaults.chipListSortFactor, ChipSortFactor.values);
set countrySortFactor(ChipSortFactor newValue) => set(SettingKeys.countrySortFactorKey, newValue.toString());
ChipSortFactor get stateSortFactor => getEnumOrDefault(SettingKeys.stateSortFactorKey, SettingsDefaults.chipListSortFactor, ChipSortFactor.values);
set stateSortFactor(ChipSortFactor newValue) => set(SettingKeys.stateSortFactorKey, newValue.toString());
ChipSortFactor get placeSortFactor => getEnumOrDefault(SettingKeys.placeSortFactorKey, SettingsDefaults.chipListSortFactor, ChipSortFactor.values);
set placeSortFactor(ChipSortFactor newValue) => set(SettingKeys.placeSortFactorKey, newValue.toString());
ChipSortFactor get tagSortFactor => getEnumOrDefault(SettingKeys.tagSortFactorKey, SettingsDefaults.chipListSortFactor, ChipSortFactor.values);
set tagSortFactor(ChipSortFactor newValue) => set(SettingKeys.tagSortFactorKey, newValue.toString());
bool get albumSortReverse => getBool(SettingKeys.albumSortReverseKey) ?? false;
set albumSortReverse(bool newValue) => set(SettingKeys.albumSortReverseKey, newValue);
bool get countrySortReverse => getBool(SettingKeys.countrySortReverseKey) ?? false;
set countrySortReverse(bool newValue) => set(SettingKeys.countrySortReverseKey, newValue);
bool get stateSortReverse => getBool(SettingKeys.stateSortReverseKey) ?? false;
set stateSortReverse(bool newValue) => set(SettingKeys.stateSortReverseKey, newValue);
bool get placeSortReverse => getBool(SettingKeys.placeSortReverseKey) ?? false;
set placeSortReverse(bool newValue) => set(SettingKeys.placeSortReverseKey, newValue);
bool get tagSortReverse => getBool(SettingKeys.tagSortReverseKey) ?? false;
set tagSortReverse(bool newValue) => set(SettingKeys.tagSortReverseKey, newValue);
Set<CollectionFilter> get pinnedFilters => (getStringList(SettingKeys.pinnedFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet();
set pinnedFilters(Set<CollectionFilter> newValue) => set(SettingKeys.pinnedFiltersKey, newValue.map((filter) => filter.toJson()).toList());
Set<CollectionFilter> get hiddenFilters => (getStringList(SettingKeys.hiddenFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet();
set hiddenFilters(Set<CollectionFilter> newValue) => set(SettingKeys.hiddenFiltersKey, newValue.map((filter) => filter.toJson()).toList());
void changeFilterVisibility(Set<CollectionFilter> filters, bool visible) {
final _hiddenFilters = hiddenFilters;
if (visible) {
_hiddenFilters.removeAll(filters);
} else {
_hiddenFilters.addAll(filters);
searchHistory = searchHistory..removeWhere(filters.contains);
}
hiddenFilters = _hiddenFilters;
}
bool get showAlbumPickQuery => getBool(SettingKeys.showAlbumPickQueryKey) ?? false;
set showAlbumPickQuery(bool newValue) => set(SettingKeys.showAlbumPickQueryKey, newValue);
}

View file

@ -0,0 +1,16 @@
import 'package:aves/model/settings/defaults.dart';
import 'package:aves_model/aves_model.dart';
mixin InfoSettings on SettingsAccess {
double get infoMapZoom => getDouble(SettingKeys.infoMapZoomKey) ?? SettingsDefaults.infoMapZoom;
set infoMapZoom(double newValue) => set(SettingKeys.infoMapZoomKey, newValue);
CoordinateFormat get coordinateFormat => getEnumOrDefault(SettingKeys.coordinateFormatKey, SettingsDefaults.coordinateFormat, CoordinateFormat.values);
set coordinateFormat(CoordinateFormat newValue) => set(SettingKeys.coordinateFormatKey, newValue.toString());
UnitSystem get unitSystem => getEnumOrDefault(SettingKeys.unitSystemKey, SettingsDefaults.unitSystem, UnitSystem.values);
set unitSystem(UnitSystem newValue) => set(SettingKeys.unitSystemKey, newValue.toString());
}

View file

@ -0,0 +1,62 @@
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/settings/defaults.dart';
import 'package:aves_model/aves_model.dart';
mixin NavigationSettings on SettingsAccess {
bool get mustBackTwiceToExit => getBool(SettingKeys.mustBackTwiceToExitKey) ?? SettingsDefaults.mustBackTwiceToExit;
set mustBackTwiceToExit(bool newValue) => set(SettingKeys.mustBackTwiceToExitKey, newValue);
KeepScreenOn get keepScreenOn => getEnumOrDefault(SettingKeys.keepScreenOnKey, SettingsDefaults.keepScreenOn, KeepScreenOn.values);
set keepScreenOn(KeepScreenOn newValue) => set(SettingKeys.keepScreenOnKey, newValue.toString());
HomePageSetting get homePage => getEnumOrDefault(SettingKeys.homePageKey, SettingsDefaults.homePage, HomePageSetting.values);
set homePage(HomePageSetting newValue) => set(SettingKeys.homePageKey, newValue.toString());
bool get enableBottomNavigationBar => getBool(SettingKeys.enableBottomNavigationBarKey) ?? SettingsDefaults.enableBottomNavigationBar;
set enableBottomNavigationBar(bool newValue) => set(SettingKeys.enableBottomNavigationBarKey, newValue);
bool get confirmCreateVault => getBool(SettingKeys.confirmCreateVaultKey) ?? SettingsDefaults.confirm;
set confirmCreateVault(bool newValue) => set(SettingKeys.confirmCreateVaultKey, newValue);
bool get confirmDeleteForever => getBool(SettingKeys.confirmDeleteForeverKey) ?? SettingsDefaults.confirm;
set confirmDeleteForever(bool newValue) => set(SettingKeys.confirmDeleteForeverKey, newValue);
bool get confirmMoveToBin => getBool(SettingKeys.confirmMoveToBinKey) ?? SettingsDefaults.confirm;
set confirmMoveToBin(bool newValue) => set(SettingKeys.confirmMoveToBinKey, newValue);
bool get confirmMoveUndatedItems => getBool(SettingKeys.confirmMoveUndatedItemsKey) ?? SettingsDefaults.confirm;
set confirmMoveUndatedItems(bool newValue) => set(SettingKeys.confirmMoveUndatedItemsKey, newValue);
bool get confirmAfterMoveToBin => getBool(SettingKeys.confirmAfterMoveToBinKey) ?? SettingsDefaults.confirm;
set confirmAfterMoveToBin(bool newValue) => set(SettingKeys.confirmAfterMoveToBinKey, newValue);
bool get setMetadataDateBeforeFileOp => getBool(SettingKeys.setMetadataDateBeforeFileOpKey) ?? SettingsDefaults.setMetadataDateBeforeFileOp;
set setMetadataDateBeforeFileOp(bool newValue) => set(SettingKeys.setMetadataDateBeforeFileOpKey, newValue);
List<CollectionFilter?> get drawerTypeBookmarks =>
(getStringList(SettingKeys.drawerTypeBookmarksKey))?.map((v) {
if (v.isEmpty) return null;
return CollectionFilter.fromJson(v);
}).toList() ??
SettingsDefaults.drawerTypeBookmarks;
set drawerTypeBookmarks(List<CollectionFilter?> newValue) => set(SettingKeys.drawerTypeBookmarksKey, newValue.map((filter) => filter?.toJson() ?? '').toList());
List<String>? get drawerAlbumBookmarks => getStringList(SettingKeys.drawerAlbumBookmarksKey);
set drawerAlbumBookmarks(List<String>? newValue) => set(SettingKeys.drawerAlbumBookmarksKey, newValue);
List<String> get drawerPageBookmarks => getStringList(SettingKeys.drawerPageBookmarksKey) ?? SettingsDefaults.drawerPageBookmarks;
set drawerPageBookmarks(List<String> newValue) => set(SettingKeys.drawerPageBookmarksKey, newValue);
}

View file

@ -0,0 +1,14 @@
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/settings/defaults.dart';
import 'package:aves_model/aves_model.dart';
import 'package:collection/collection.dart';
mixin SearchSettings on SettingsAccess {
bool get saveSearchHistory => getBool(SettingKeys.saveSearchHistoryKey) ?? SettingsDefaults.saveSearchHistory;
set saveSearchHistory(bool newValue) => set(SettingKeys.saveSearchHistoryKey, newValue);
List<CollectionFilter> get searchHistory => (getStringList(SettingKeys.searchHistoryKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toList();
set searchHistory(List<CollectionFilter> newValue) => set(SettingKeys.searchHistoryKey, newValue.map((filter) => filter.toJson()).toList());
}

View file

@ -0,0 +1,56 @@
import 'package:aves/model/settings/defaults.dart';
import 'package:aves_model/aves_model.dart';
mixin ViewerSettings on SettingsAccess {
List<EntryAction> get viewerQuickActions => getEnumListOrDefault(SettingKeys.viewerQuickActionsKey, SettingsDefaults.viewerQuickActions, EntryAction.values);
set viewerQuickActions(List<EntryAction> newValue) => set(SettingKeys.viewerQuickActionsKey, newValue.map((v) => v.toString()).toList());
bool get showOverlayOnOpening => getBool(SettingKeys.showOverlayOnOpeningKey) ?? SettingsDefaults.showOverlayOnOpening;
set showOverlayOnOpening(bool newValue) => set(SettingKeys.showOverlayOnOpeningKey, newValue);
bool get showOverlayMinimap => getBool(SettingKeys.showOverlayMinimapKey) ?? SettingsDefaults.showOverlayMinimap;
set showOverlayMinimap(bool newValue) => set(SettingKeys.showOverlayMinimapKey, newValue);
OverlayHistogramStyle get overlayHistogramStyle => getEnumOrDefault(SettingKeys.overlayHistogramStyleKey, SettingsDefaults.overlayHistogramStyle, OverlayHistogramStyle.values);
set overlayHistogramStyle(OverlayHistogramStyle newValue) => set(SettingKeys.overlayHistogramStyleKey, newValue.toString());
bool get showOverlayInfo => getBool(SettingKeys.showOverlayInfoKey) ?? SettingsDefaults.showOverlayInfo;
set showOverlayInfo(bool newValue) => set(SettingKeys.showOverlayInfoKey, newValue);
bool get showOverlayDescription => getBool(SettingKeys.showOverlayDescriptionKey) ?? SettingsDefaults.showOverlayDescription;
set showOverlayDescription(bool newValue) => set(SettingKeys.showOverlayDescriptionKey, newValue);
bool get showOverlayRatingTags => getBool(SettingKeys.showOverlayRatingTagsKey) ?? SettingsDefaults.showOverlayRatingTags;
set showOverlayRatingTags(bool newValue) => set(SettingKeys.showOverlayRatingTagsKey, newValue);
bool get showOverlayShootingDetails => getBool(SettingKeys.showOverlayShootingDetailsKey) ?? SettingsDefaults.showOverlayShootingDetails;
set showOverlayShootingDetails(bool newValue) => set(SettingKeys.showOverlayShootingDetailsKey, newValue);
bool get showOverlayThumbnailPreview => getBool(SettingKeys.showOverlayThumbnailPreviewKey) ?? SettingsDefaults.showOverlayThumbnailPreview;
set showOverlayThumbnailPreview(bool newValue) => set(SettingKeys.showOverlayThumbnailPreviewKey, newValue);
bool get viewerGestureSideTapNext => getBool(SettingKeys.viewerGestureSideTapNextKey) ?? SettingsDefaults.viewerGestureSideTapNext;
set viewerGestureSideTapNext(bool newValue) => set(SettingKeys.viewerGestureSideTapNextKey, newValue);
bool get viewerUseCutout => getBool(SettingKeys.viewerUseCutoutKey) ?? SettingsDefaults.viewerUseCutout;
set viewerUseCutout(bool newValue) => set(SettingKeys.viewerUseCutoutKey, newValue);
bool get enableMotionPhotoAutoPlay => getBool(SettingKeys.enableMotionPhotoAutoPlayKey) ?? SettingsDefaults.enableMotionPhotoAutoPlay;
set enableMotionPhotoAutoPlay(bool newValue) => set(SettingKeys.enableMotionPhotoAutoPlayKey, newValue);
EntryBackground get imageBackground => getEnumOrDefault(SettingKeys.imageBackgroundKey, SettingsDefaults.imageBackground, EntryBackground.values);
set imageBackground(EntryBackground newValue) => set(SettingKeys.imageBackgroundKey, newValue.toString());
}

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
import 'package:aves/model/settings/store/store.dart'; import 'package:aves_model/aves_model.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';

View file

@ -19,7 +19,9 @@ mixin AlbumMixin on SourceBase {
Set<AlbumFilter> getNewAlbumFilters(BuildContext context) => Set.unmodifiable(_newAlbums.map((v) => AlbumFilter(v, getAlbumDisplayName(context, v)))); Set<AlbumFilter> getNewAlbumFilters(BuildContext context) => Set.unmodifiable(_newAlbums.map((v) => AlbumFilter(v, getAlbumDisplayName(context, v))));
int compareAlbumsByName(String a, String b) { int compareAlbumsByName(String? a, String? b) {
a ??= '';
b ??= '';
final ua = getAlbumDisplayName(null, a); final ua = getAlbumDisplayName(null, a);
final ub = getAlbumDisplayName(null, b); final ub = getAlbumDisplayName(null, b);
final c = compareAsciiUpperCaseNatural(ua, ub); final c = compareAsciiUpperCaseNatural(ua, ub);

View file

@ -84,10 +84,10 @@ class CollectionLens with ChangeNotifier {
} }
_subscriptions.add(settings.updateStream _subscriptions.add(settings.updateStream
.where((event) => [ .where((event) => [
Settings.collectionBurstPatternsKey, SettingKeys.collectionBurstPatternsKey,
Settings.collectionSortFactorKey, SettingKeys.collectionSortFactorKey,
Settings.collectionGroupFactorKey, SettingKeys.collectionGroupFactorKey,
Settings.collectionSortReverseKey, SettingKeys.collectionSortReverseKey,
].contains(event.key)) ].contains(event.key))
.listen((_) => _onSettingsChanged())); .listen((_) => _onSettingsChanged()));
refresh(); refresh();
@ -245,7 +245,7 @@ class CollectionLens with ChangeNotifier {
} }
case EntrySortFactor.name: case EntrySortFactor.name:
final byAlbum = groupBy<AvesEntry, EntryAlbumSectionKey>(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory)); final byAlbum = groupBy<AvesEntry, EntryAlbumSectionKey>(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory));
final compare = sortReverse ? (a, b) => source.compareAlbumsByName(b.directory!, a.directory!) : (a, b) => source.compareAlbumsByName(a.directory!, b.directory!); final int Function(EntryAlbumSectionKey, EntryAlbumSectionKey) compare = sortReverse ? (a, b) => source.compareAlbumsByName(b.directory, a.directory) : (a, b) => source.compareAlbumsByName(a.directory, b.directory);
sections = SplayTreeMap<EntryAlbumSectionKey, List<AvesEntry>>.of(byAlbum, compare); sections = SplayTreeMap<EntryAlbumSectionKey, List<AvesEntry>>.of(byAlbum, compare);
case EntrySortFactor.rating: case EntrySortFactor.rating:
sections = groupBy<AvesEntry, EntryRatingSectionKey>(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating)); sections = groupBy<AvesEntry, EntryRatingSectionKey>(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating));

View file

@ -61,8 +61,8 @@ mixin SourceBase {
abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, PlaceMixin, StateMixin, LocationMixin, TagMixin, TrashMixin { abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, PlaceMixin, StateMixin, LocationMixin, TagMixin, TrashMixin {
CollectionSource() { CollectionSource() {
settings.updateStream.where((event) => event.key == Settings.localeKey).listen((_) => invalidateAlbumDisplayNames()); settings.updateStream.where((event) => event.key == SettingKeys.localeKey).listen((_) => invalidateAlbumDisplayNames());
settings.updateStream.where((event) => event.key == Settings.hiddenFiltersKey).listen((event) { settings.updateStream.where((event) => event.key == SettingKeys.hiddenFiltersKey).listen((event) {
final oldValue = event.oldValue; final oldValue = event.oldValue;
if (oldValue is List<String>?) { if (oldValue is List<String>?) {
final oldHiddenFilters = (oldValue ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet(); final oldHiddenFilters = (oldValue ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet();
@ -274,9 +274,9 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
final existingCover = covers.of(oldFilter); final existingCover = covers.of(oldFilter);
await covers.set( await covers.set(
filter: newFilter, filter: newFilter,
entryId: existingCover?.item1, entryId: existingCover?.$1,
packageName: existingCover?.item2, packageName: existingCover?.$2,
color: existingCover?.item3, color: existingCover?.$3,
); );
renameNewAlbum(sourceAlbum, destinationAlbum); renameNewAlbum(sourceAlbum, destinationAlbum);
@ -547,7 +547,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
} }
AvesEntry? coverEntry(CollectionFilter filter) { AvesEntry? coverEntry(CollectionFilter filter) {
final id = covers.of(filter)?.item1; final id = covers.of(filter)?.$1;
if (id != null) { if (id != null) {
final entry = visibleEntries.firstWhereOrNull((entry) => entry.id == id); final entry = visibleEntries.firstWhereOrNull((entry) => entry.id == id);
if (entry != null) return entry; if (entry != null) return entry;

View file

@ -14,7 +14,6 @@ import 'package:aves/services/common/services.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:tuple/tuple.dart';
mixin LocationMixin on CountryMixin, StateMixin { mixin LocationMixin on CountryMixin, StateMixin {
static const commitCountThreshold = 200; static const commitCountThreshold = 200;
@ -96,16 +95,16 @@ mixin LocationMixin on CountryMixin, StateMixin {
// - 652 calls (22%) when approximating to 2 decimal places (~1km - town or village) // - 652 calls (22%) when approximating to 2 decimal places (~1km - town or village)
// cf https://en.wikipedia.org/wiki/Decimal_degrees#Precision // cf https://en.wikipedia.org/wiki/Decimal_degrees#Precision
final latLngFactor = pow(10, 2); final latLngFactor = pow(10, 2);
Tuple2<int, int> approximateLatLng(AvesEntry entry) { (int latitude, int longitude) approximateLatLng(AvesEntry entry) {
// entry has coordinates // entry has coordinates
final catalogMetadata = entry.catalogMetadata!; final catalogMetadata = entry.catalogMetadata!;
final lat = catalogMetadata.latitude!; final lat = catalogMetadata.latitude!;
final lng = catalogMetadata.longitude!; final lng = catalogMetadata.longitude!;
return Tuple2<int, int>((lat * latLngFactor).round(), (lng * latLngFactor).round()); return ((lat * latLngFactor).round(), (lng * latLngFactor).round());
} }
final located = visibleEntries.where((entry) => entry.hasGps).toSet().difference(todo); final located = visibleEntries.where((entry) => entry.hasGps).toSet().difference(todo);
final knownLocations = <Tuple2<int, int>, AddressDetails?>{}; final knownLocations = <(int, int), AddressDetails?>{};
located.forEach((entry) { located.forEach((entry) {
knownLocations.putIfAbsent(approximateLatLng(entry), () => entry.addressDetails); knownLocations.putIfAbsent(approximateLatLng(entry), () => entry.addressDetails);
}); });

View file

@ -8,6 +8,7 @@ import 'package:aves/model/video/profiles/aac.dart';
import 'package:aves/model/video/profiles/h264.dart'; import 'package:aves/model/video/profiles/h264.dart';
import 'package:aves/model/video/profiles/hevc.dart'; import 'package:aves/model/video/profiles/hevc.dart';
import 'package:aves/ref/languages.dart'; import 'package:aves/ref/languages.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/ref/mp4.dart'; import 'package:aves/ref/mp4.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/theme/format.dart'; import 'package:aves/theme/format.dart';
@ -15,10 +16,8 @@ import 'package:aves/utils/file_utils.dart';
import 'package:aves/utils/math_utils.dart'; import 'package:aves/utils/math_utils.dart';
import 'package:aves/utils/string_utils.dart'; import 'package:aves/utils/string_utils.dart';
import 'package:aves/utils/time_utils.dart'; import 'package:aves/utils/time_utils.dart';
import 'package:aves/widgets/viewer/video/fijkplayer.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
class VideoMetadataFormatter { class VideoMetadataFormatter {
@ -26,7 +25,8 @@ class VideoMetadataFormatter {
static final _ambiguousDatePatterns = { static final _ambiguousDatePatterns = {
RegExp(r'^\d{2}[-/]\d{2}[-/]\d{4}$'), RegExp(r'^\d{2}[-/]\d{2}[-/]\d{4}$'),
}; };
static final _durationPattern = RegExp(r'(\d+):(\d+):(\d+)(.\d+)'); static final _durationHmsmPattern = RegExp(r'(\d+):(\d+):(\d+)(.\d+)');
static final _durationSmPattern = RegExp(r'(\d+)(.\d+)');
static final _locationPattern = RegExp(r'([+-][.0-9]+)'); static final _locationPattern = RegExp(r'([+-][.0-9]+)');
static final Map<String, String> _codecNames = { static final Map<String, String> _codecNames = {
Codecs.ac3: 'AC-3', Codecs.ac3: 'AC-3',
@ -44,20 +44,8 @@ class VideoMetadataFormatter {
Codecs.webm: 'WebM', Codecs.webm: 'WebM',
}; };
static Future<Map> getVideoMetadata(AvesEntry entry) async {
final player = FijkPlayer();
final info = await player.setDataSourceUntilPrepared(entry.uri).then((v) {
return player.getInfo();
}).catchError((error) {
debugPrint('failed to get video metadata for entry=$entry, error=$error');
return {};
});
await player.release();
return info;
}
static Future<Map<String, int>> getLoadingMetadata(AvesEntry entry) async { static Future<Map<String, int>> getLoadingMetadata(AvesEntry entry) async {
final mediaInfo = await getVideoMetadata(entry); final mediaInfo = await videoMetadataFetcher.getMetadata(entry);
final fields = <String, int>{}; final fields = <String, int>{};
final streams = mediaInfo[Keys.streams]; final streams = mediaInfo[Keys.streams];
@ -77,12 +65,26 @@ class VideoMetadataFormatter {
final durationMicros = mediaInfo[Keys.durationMicros]; final durationMicros = mediaInfo[Keys.durationMicros];
if (durationMicros is num) { if (durationMicros is num) {
fields['durationMillis'] = (durationMicros / 1000).round(); fields['durationMillis'] = (durationMicros / 1000).round();
} else {
final duration = _parseDuration(mediaInfo[Keys.duration]);
if (duration != null) {
fields['durationMillis'] = duration.inMilliseconds;
}
} }
return fields; return fields;
} }
static Future<CatalogMetadata?> getCatalogMetadata(AvesEntry entry) async { static Future<CatalogMetadata?> getCatalogMetadata(AvesEntry entry) async {
final mediaInfo = await getVideoMetadata(entry); var catalogMetadata = entry.catalogMetadata ?? CatalogMetadata(id: entry.id);
final mediaInfo = await videoMetadataFetcher.getMetadata(entry);
if (entry.mimeType == MimeTypes.avif) {
final duration = _parseDuration(mediaInfo[Keys.duration]);
if (duration == null) return null;
catalogMetadata = catalogMetadata.copyWith(isAnimated: true);
}
// only consider values with at least 8 characters (yyyymmdd), // only consider values with at least 8 characters (yyyymmdd),
// ignoring unset values like `0`, as well as year values like `2021` // ignoring unset values like `0`, as well as year values like `2021`
@ -102,12 +104,12 @@ class VideoMetadataFormatter {
// exclude date if it is suspiciously close to epoch // exclude date if it is suspiciously close to epoch
if (dateMillis != null && !DateTime.fromMillisecondsSinceEpoch(dateMillis).isAtSameDayAs(epoch)) { if (dateMillis != null && !DateTime.fromMillisecondsSinceEpoch(dateMillis).isAtSameDayAs(epoch)) {
return (entry.catalogMetadata ?? CatalogMetadata(id: entry.id)).copyWith( catalogMetadata = catalogMetadata.copyWith(
dateMillis: dateMillis, dateMillis: dateMillis,
); );
} }
return entry.catalogMetadata; return catalogMetadata;
} }
static bool isAmbiguousDate(String dateString) { static bool isAmbiguousDate(String dateString) {
@ -194,14 +196,21 @@ class VideoMetadataFormatter {
switch (key) { switch (key) {
case Keys.codecLevel: case Keys.codecLevel:
case Keys.codecTag:
case Keys.codecTagString:
case Keys.durationTs:
case Keys.fpsNum: case Keys.fpsNum:
case Keys.handlerName:
case Keys.index: case Keys.index:
case Keys.isAvc:
case Keys.probeScore:
case Keys.programCount:
case Keys.refs:
case Keys.sarNum: case Keys.sarNum:
case Keys.selectedAudioStream: case Keys.selectedAudioStream:
case Keys.selectedTextStream: case Keys.selectedTextStream:
case Keys.selectedVideoStream: case Keys.selectedVideoStream:
case Keys.statisticsTags: case Keys.statisticsTags:
case Keys.streamCount:
case Keys.streams: case Keys.streams:
case Keys.streamType: case Keys.streamType:
case Keys.tbrNum: case Keys.tbrNum:
@ -219,10 +228,14 @@ class VideoMetadataFormatter {
case Keys.bitrate: case Keys.bitrate:
case Keys.bps: case Keys.bps:
save('Bit Rate', _formatMetric(value, 'b/s')); save('Bit Rate', _formatMetric(value, 'b/s'));
case Keys.bitsPerRawSample:
save('Bits Per Raw Sample', value);
case Keys.byteCount: case Keys.byteCount:
save('Size', _formatFilesize(value)); save('Size', _formatFilesize(value));
case Keys.channelLayout: case Keys.channelLayout:
save('Channel Layout', _formatChannelLayout(value)); save('Channel Layout', _formatChannelLayout(value));
case Keys.chromaLocation:
save('Chroma Location', value);
case Keys.codecName: case Keys.codecName:
if (value != 'none') { if (value != 'none') {
save('Format', _formatCodecName(value)); save('Format', _formatCodecName(value));
@ -233,6 +246,18 @@ class VideoMetadataFormatter {
// user-friendly descriptions for related enums are defined in libavutil/pixfmt.h // user-friendly descriptions for related enums are defined in libavutil/pixfmt.h
save('Pixel Format', (value as String).toUpperCase()); save('Pixel Format', (value as String).toUpperCase());
} }
case Keys.codedHeight:
save('Coded Height', '$value pixels');
case Keys.codedWidth:
save('Coded Width', '$value pixels');
case Keys.colorPrimaries:
save('Color Primaries', (value as String).toUpperCase());
case Keys.colorRange:
save('Color Range', (value as String).toUpperCase());
case Keys.colorSpace:
save('Color Space', (value as String).toUpperCase());
case Keys.colorTransfer:
save('Color Transfer', (value as String).toUpperCase());
case Keys.codecProfileId: case Keys.codecProfileId:
{ {
final profile = int.tryParse(value); final profile = int.tryParse(value);
@ -242,9 +267,9 @@ class VideoMetadataFormatter {
case Codecs.h264: case Codecs.h264:
case Codecs.hevc: case Codecs.hevc:
{ {
final levelString = info[Keys.codecLevel]; final levelValue = info[Keys.codecLevel];
if (levelString != null) { if (levelValue != null) {
final level = int.tryParse(levelString) ?? 0; final level = levelValue is int ? levelValue : int.tryParse(levelValue) ?? 0;
if (codec == Codecs.h264) { if (codec == Codecs.h264) {
profileString = H264.formatProfile(profile, level); profileString = H264.formatProfile(profile, level);
} else { } else {
@ -268,6 +293,8 @@ class VideoMetadataFormatter {
save('Compatible Brands', formattedBrands); save('Compatible Brands', formattedBrands);
case Keys.creationTime: case Keys.creationTime:
save('Creation Time', _formatDate(value)); save('Creation Time', _formatDate(value));
case Keys.dar:
save('Display Aspect Ratio', value);
case Keys.date: case Keys.date:
if (value is String && value != '0') { if (value is String && value != '0') {
final charCount = value.length; final charCount = value.length;
@ -277,10 +304,18 @@ class VideoMetadataFormatter {
save('Duration', _formatDuration(value)); save('Duration', _formatDuration(value));
case Keys.durationMicros: case Keys.durationMicros:
if (value != 0) save('Duration', formatPreciseDuration(Duration(microseconds: value))); if (value != 0) save('Duration', formatPreciseDuration(Duration(microseconds: value)));
case Keys.extraDataSize:
save('Extra Data Size', _formatFilesize(value));
case Keys.fieldOrder:
save('Field Order', value);
case Keys.fpsDen: case Keys.fpsDen:
save('Frame Rate', '${roundToPrecision(info[Keys.fpsNum] / info[Keys.fpsDen], decimals: 3).toString()} FPS'); save('Frame Rate', '${roundToPrecision(info[Keys.fpsNum] / info[Keys.fpsDen], decimals: 3).toString()} FPS');
case Keys.frameCount: case Keys.frameCount:
save('Frame Count', value); save('Frame Count', value);
case Keys.handlerName:
save('Handler Name', value);
case Keys.hasBFrames:
save('Has B-Frames', value);
case Keys.height: case Keys.height:
save('Height', '$value pixels'); save('Height', '$value pixels');
case Keys.language: case Keys.language:
@ -295,6 +330,8 @@ class VideoMetadataFormatter {
save('Media Type', value); save('Media Type', value);
case Keys.minorVersion: case Keys.minorVersion:
if (value != '0') save('Minor Version', value); if (value != '0') save('Minor Version', value);
case Keys.nalLengthSize:
save('NAL Length Size', _formatFilesize(value));
case Keys.quicktimeLocationAccuracyHorizontal: case Keys.quicktimeLocationAccuracyHorizontal:
save('QuickTime Location Horizontal Accuracy', value); save('QuickTime Location Horizontal Accuracy', value);
case Keys.quicktimeCreationDate: case Keys.quicktimeCreationDate:
@ -304,25 +341,41 @@ class VideoMetadataFormatter {
case Keys.quicktimeSoftware: case Keys.quicktimeSoftware:
// redundant with `QuickTime Metadata` directory // redundant with `QuickTime Metadata` directory
break; break;
case Keys.rFrameRate:
save('R Frame Rate', value);
case Keys.rotate: case Keys.rotate:
save('Rotation', '$value°'); save('Rotation', '$value°');
case Keys.sampleFormat:
save('Sample Format', (value as String).toUpperCase());
case Keys.sampleRate: case Keys.sampleRate:
save('Sample Rate', _formatMetric(value, 'Hz')); save('Sample Rate', _formatMetric(value, 'Hz'));
case Keys.sar:
save('Sample Aspect Ratio', value);
case Keys.sarDen: case Keys.sarDen:
final sarNum = info[Keys.sarNum]; final sarNum = info[Keys.sarNum];
final sarDen = info[Keys.sarDen]; final sarDen = info[Keys.sarDen];
// skip common square pixels (1:1) // skip common square pixels (1:1)
if (sarNum != sarDen) save('SAR', '$sarNum:$sarDen'); if (sarNum != sarDen) save('SAR', '$sarNum:$sarDen');
case Keys.segmentCount:
save('Segment Count', value);
case Keys.sourceOshash: case Keys.sourceOshash:
save('Source OSHash', value); save('Source OSHash', value);
case Keys.startMicros: case Keys.startMicros:
if (value != 0) save('Start', formatPreciseDuration(Duration(microseconds: value))); if (value != 0) save('Start', formatPreciseDuration(Duration(microseconds: value)));
case Keys.startPts:
save('Start PTS', value);
case Keys.startTime:
save('Start', _formatDuration(value));
case Keys.statisticsWritingApp: case Keys.statisticsWritingApp:
save('Stats Writing App', value); save('Stats Writing App', value);
case Keys.statisticsWritingDateUtc: case Keys.statisticsWritingDateUtc:
save('Stats Writing Date', _formatDate(value)); save('Stats Writing Date', _formatDate(value));
case Keys.timeBase:
save('Time Base', value);
case Keys.track: case Keys.track:
if (value != '0') save('Track', value); if (value != '0') save('Track', value);
case Keys.vendorId:
save('Vendor ID', value);
case Keys.width: case Keys.width:
save('Width', '$value pixels'); save('Width', '$value pixels');
case Keys.xiaomiSlowMoment: case Keys.xiaomiSlowMoment:
@ -340,7 +393,12 @@ class VideoMetadataFormatter {
static String _formatBrand(String value) => Mp4.brands[value] ?? value; static String _formatBrand(String value) => Mp4.brands[value] ?? value;
static String _formatChannelLayout(value) => ChannelLayouts.names[value] ?? 'unknown ($value)'; static String _formatChannelLayout(dynamic value) {
if (value is int) {
return ChannelLayouts.names[value] ?? 'unknown ($value)';
}
return '$value';
}
static String _formatCodecName(String value) => _codecNames[value] ?? value.toUpperCase().replaceAll('_', ' '); static String _formatCodecName(String value) => _codecNames[value] ?? value.toUpperCase().replaceAll('_', ' ');
@ -352,28 +410,49 @@ class VideoMetadataFormatter {
return date.toIso8601String(); return date.toIso8601String();
} }
// input example: '00:00:05.408000000' // input example: '00:00:05.408000000' or '5.408000'
static String _formatDuration(String value) { static Duration? _parseDuration(String? value) {
final match = _durationPattern.firstMatch(value); if (value == null) return null;
var match = _durationHmsmPattern.firstMatch(value);
if (match != null) { if (match != null) {
final h = int.tryParse(match.group(1)!); final h = int.tryParse(match.group(1)!);
final m = int.tryParse(match.group(2)!); final m = int.tryParse(match.group(2)!);
final s = int.tryParse(match.group(3)!); final s = int.tryParse(match.group(3)!);
final millis = double.tryParse(match.group(4)!); final millis = double.tryParse(match.group(4)!);
if (h != null && m != null && s != null && millis != null) { if (h != null && m != null && s != null && millis != null) {
return formatPreciseDuration(Duration( return Duration(
hours: h, hours: h,
minutes: m, minutes: m,
seconds: s, seconds: s,
milliseconds: (millis * 1000).toInt(), milliseconds: (millis * 1000).toInt(),
)); );
} }
} }
return value;
}
static String _formatFilesize(String value) { match = _durationSmPattern.firstMatch(value);
final size = int.tryParse(value); if (match != null) {
final s = int.tryParse(match.group(1)!);
final millis = double.tryParse(match.group(2)!);
if (s != null && millis != null) {
return Duration(
seconds: s,
milliseconds: (millis * 1000).toInt(),
);
}
}
return null;
}
// input example: '00:00:05.408000000' or '5.408000'
static String _formatDuration(String value) {
final duration = _parseDuration(value);
return duration != null ? formatPreciseDuration(duration) : value;
}
static String _formatFilesize(dynamic value) {
final size = value is int ? value : int.tryParse(value);
return size != null ? formatFileSize('en_US', size) : value; return size != null ? formatFileSize('en_US', size) : value;
} }

View file

@ -10,25 +10,19 @@ class BurstPatterns {
]; ];
static String getName(String pattern) { static String getName(String pattern) {
switch (pattern) { return switch (pattern) {
case samsung: samsung => 'Samsung',
return 'Samsung'; sony => 'Sony',
case sony: _ => pattern,
return 'Sony'; };
default:
return pattern;
}
} }
static String getExample(String pattern) { static String getExample(String pattern) {
switch (pattern) { return switch (pattern) {
case samsung: samsung => '20151021_072800_007',
return '20151021_072800_007'; sony => 'DSC_0007_BURST20151021072800123',
case sony: _ => '?',
return 'DSC_0007_BURST20151021072800123'; };
default:
return '?';
}
} }
static const byManufacturer = { static const byManufacturer = {

View file

@ -1,9 +1,9 @@
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
class PointsOfInterest { class PointsOfInterest {
static final pointNemo = LatLng(-48.876667, -123.393333); static const pointNemo = LatLng(-48.876667, -123.393333);
static final wonders = [ static const wonders = [
LatLng(29.979167, 31.134167), LatLng(29.979167, 31.134167),
LatLng(36.451000, 28.223615), LatLng(36.451000, 28.223615),
LatLng(32.5355, 44.4275), LatLng(32.5355, 44.4275),

View file

@ -10,7 +10,6 @@ import 'package:aves/services/common/services.dart';
import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/view/view.dart'; import 'package:aves/view/view.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@ -52,7 +51,6 @@ Future<void> _init() async {
await device.init(); await device.init();
await mobileServices.init(); await mobileServices.init();
await settings.init(monitorPlatformSettings: false); await settings.init(monitorPlatformSettings: false);
FijkLog.setLevel(FijkLogLevel.Warn);
await reportService.init(); await reportService.init();
final analyzer = Analyzer(); final analyzer = Analyzer();

View file

@ -3,13 +3,12 @@ import 'dart:collection';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:tuple/tuple.dart';
final ServicePolicy servicePolicy = ServicePolicy._private(); final ServicePolicy servicePolicy = ServicePolicy._private();
class ServicePolicy { class ServicePolicy {
final StreamController<QueueState> _queueStreamController = StreamController.broadcast(); final StreamController<QueueState> _queueStreamController = StreamController.broadcast();
final Map<Object, Tuple2<int, _Task>> _paused = {}; final Map<Object, (int, _Task)> _paused = {};
final SplayTreeMap<int, LinkedHashMap<Object, _Task>> _queues = SplayTreeMap(); final SplayTreeMap<int, LinkedHashMap<Object, _Task>> _queues = SplayTreeMap();
final LinkedHashMap<Object, _Task> _runningQueue = LinkedHashMap(); final LinkedHashMap<Object, _Task> _runningQueue = LinkedHashMap();
@ -30,8 +29,8 @@ class ServicePolicy {
key ??= platformCall.hashCode; key ??= platformCall.hashCode;
final toResume = _paused.remove(key); final toResume = _paused.remove(key);
if (toResume != null) { if (toResume != null) {
priority = toResume.item1; priority = toResume.$1;
task = toResume.item2 as _Task<T>; task = toResume.$2 as _Task<T>;
completer = task.completer; completer = task.completer;
} else { } else {
completer = Completer<T>(); completer = Completer<T>();
@ -56,8 +55,8 @@ class ServicePolicy {
Future<T>? resume<T>(Object key) { Future<T>? resume<T>(Object key) {
final toResume = _paused.remove(key); final toResume = _paused.remove(key);
if (toResume != null) { if (toResume != null) {
final priority = toResume.item1; final priority = toResume.$1;
final task = toResume.item2 as _Task<T>; final task = toResume.$2 as _Task<T>;
_getQueue(priority)[key] = task; _getQueue(priority)[key] = task;
_pickNext(); _pickNext();
return task.completer.future; return task.completer.future;
@ -97,7 +96,7 @@ class ServicePolicy {
} }
bool pause(Object key, Iterable<int> priorities) { bool pause(Object key, Iterable<int> priorities) {
return _takeOut(key, priorities, (priority, task) => _paused.putIfAbsent(key, () => Tuple2(priority, task))); return _takeOut(key, priorities, (priority, task) => _paused.putIfAbsent(key, () => (priority, task)));
} }
bool isPaused(Object key) => _paused.containsKey(key); bool isPaused(Object key) => _paused.containsKey(key);

View file

@ -1,8 +1,7 @@
import 'package:aves/model/availability.dart'; import 'package:aves/model/availability.dart';
import 'package:aves/model/db/db_metadata.dart'; import 'package:aves/model/db/db_metadata.dart';
import 'package:aves/model/db/db_metadata_sqflite.dart'; import 'package:aves/model/db/db_metadata_sqflite.dart';
import 'package:aves/model/settings/store/store.dart'; import 'package:aves/model/settings/store_shared_pref.dart';
import 'package:aves/model/settings/store/store_shared_pref.dart';
import 'package:aves/services/app_service.dart'; import 'package:aves/services/app_service.dart';
import 'package:aves/services/device_service.dart'; import 'package:aves/services/device_service.dart';
import 'package:aves/services/media/embedded_data_service.dart'; import 'package:aves/services/media/embedded_data_service.dart';
@ -15,10 +14,14 @@ import 'package:aves/services/metadata/metadata_fetch_service.dart';
import 'package:aves/services/security_service.dart'; import 'package:aves/services/security_service.dart';
import 'package:aves/services/storage_service.dart'; import 'package:aves/services/storage_service.dart';
import 'package:aves/services/window_service.dart'; import 'package:aves/services/window_service.dart';
import 'package:aves_model/aves_model.dart';
import 'package:aves_report/aves_report.dart'; import 'package:aves_report/aves_report.dart';
import 'package:aves_report_platform/aves_report_platform.dart'; import 'package:aves_report_platform/aves_report_platform.dart';
import 'package:aves_services/aves_services.dart'; import 'package:aves_services/aves_services.dart';
import 'package:aves_services_platform/aves_services_platform.dart'; import 'package:aves_services_platform/aves_services_platform.dart';
import 'package:aves_video/aves_video.dart';
import 'package:aves_video_ffmpeg/aves_video_ffmpeg.dart';
import 'package:aves_video_mpv/aves_video_mpv.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
@ -30,6 +33,8 @@ final SettingsStore settingsStore = SharedPrefSettingsStore();
final p.Context pContext = getIt<p.Context>(); final p.Context pContext = getIt<p.Context>();
final AvesAvailability availability = getIt<AvesAvailability>(); final AvesAvailability availability = getIt<AvesAvailability>();
final MetadataDb metadataDb = getIt<MetadataDb>(); final MetadataDb metadataDb = getIt<MetadataDb>();
final AvesVideoControllerFactory videoControllerFactory = getIt<AvesVideoControllerFactory>();
final AvesVideoMetadataFetcher videoMetadataFetcher = getIt<AvesVideoMetadataFetcher>();
final AppService appService = getIt<AppService>(); final AppService appService = getIt<AppService>();
final DeviceService deviceService = getIt<DeviceService>(); final DeviceService deviceService = getIt<DeviceService>();
@ -50,6 +55,8 @@ void initPlatformServices() {
getIt.registerLazySingleton<p.Context>(p.Context.new); getIt.registerLazySingleton<p.Context>(p.Context.new);
getIt.registerLazySingleton<AvesAvailability>(LiveAvesAvailability.new); getIt.registerLazySingleton<AvesAvailability>(LiveAvesAvailability.new);
getIt.registerLazySingleton<MetadataDb>(SqfliteMetadataDb.new); getIt.registerLazySingleton<MetadataDb>(SqfliteMetadataDb.new);
getIt.registerLazySingleton<AvesVideoControllerFactory>(MpvVideoControllerFactory.new);
getIt.registerLazySingleton<AvesVideoMetadataFetcher>(FfmpegVideoMetadataFetcher.new);
getIt.registerLazySingleton<AppService>(PlatformAppService.new); getIt.registerLazySingleton<AppService>(PlatformAppService.new);
getIt.registerLazySingleton<DeviceService>(PlatformDeviceService.new); getIt.registerLazySingleton<DeviceService>(PlatformDeviceService.new);

View file

@ -8,13 +8,11 @@ extension ExtraNameConflictStrategy on NameConflictStrategy {
String toPlatform() => name; String toPlatform() => name;
String getName(BuildContext context) { String getName(BuildContext context) {
switch (this) { final l10n = context.l10n;
case NameConflictStrategy.rename: return switch (this) {
return context.l10n.nameConflictStrategyRename; NameConflictStrategy.rename => l10n.nameConflictStrategyRename,
case NameConflictStrategy.replace: NameConflictStrategy.replace => l10n.nameConflictStrategyReplace,
return context.l10n.nameConflictStrategyReplace; NameConflictStrategy.skip => l10n.nameConflictStrategySkip,
case NameConflictStrategy.skip: };
return context.l10n.nameConflictStrategySkip;
}
} }
} }

View file

@ -1,8 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/entry.dart';
import 'package:aves_utils/aves_utils.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves_utils/aves_utils.dart';
import 'package:aves_video/aves_video.dart'; import 'package:aves_video/aves_video.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';

View file

@ -8,6 +8,8 @@ import 'package:flutter/services.dart';
import 'package:streams_channel/streams_channel.dart'; import 'package:streams_channel/streams_channel.dart';
abstract class StorageService { abstract class StorageService {
Future<Map<String, int>> getDataUsage();
Future<Set<StorageVolume>> getStorageVolumes(); Future<Set<StorageVolume>> getStorageVolumes();
Future<String> getVaultRoot(); Future<String> getVaultRoot();
@ -45,6 +47,17 @@ class PlatformStorageService implements StorageService {
static const _platform = MethodChannel('deckers.thibault/aves/storage'); static const _platform = MethodChannel('deckers.thibault/aves/storage');
static final _stream = StreamsChannel('deckers.thibault/aves/activity_result_stream'); static final _stream = StreamsChannel('deckers.thibault/aves/activity_result_stream');
@override
Future<Map<String, int>> getDataUsage() async {
try {
final result = await _platform.invokeMethod('getDataUsage');
if (result != null) return (result as Map).cast<String, int>();
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
return {};
}
@override @override
Future<Set<StorageVolume>> getStorageVolumes() async { Future<Set<StorageVolume>> getStorageVolumes() async {
try { try {

View file

@ -18,10 +18,12 @@ class AColors {
} }
class AvesColorsProvider extends StatelessWidget { class AvesColorsProvider extends StatelessWidget {
final bool allowMonochrome;
final Widget child; final Widget child;
const AvesColorsProvider({ const AvesColorsProvider({
super.key, super.key,
this.allowMonochrome = true,
required this.child, required this.child,
}); });
@ -30,12 +32,14 @@ class AvesColorsProvider extends StatelessWidget {
return ProxyProvider<Settings, AvesColorsData>( return ProxyProvider<Settings, AvesColorsData>(
update: (context, settings, __) { update: (context, settings, __) {
final isDark = Theme.of(context).brightness == Brightness.dark; final isDark = Theme.of(context).brightness == Brightness.dark;
switch (settings.themeColorMode) { var mode = settings.themeColorMode;
case AvesThemeColorMode.monochrome: if (!allowMonochrome && mode == AvesThemeColorMode.monochrome) {
return isDark ? _MonochromeOnDark() : _MonochromeOnLight(); mode = AvesThemeColorMode.polychrome;
case AvesThemeColorMode.polychrome:
return isDark ? NeonOnDark() : PastelOnLight();
} }
return switch (mode) {
AvesThemeColorMode.monochrome => isDark ? _MonochromeOnDark() : _MonochromeOnLight(),
AvesThemeColorMode.polychrome => isDark ? NeonOnDark() : PastelOnLight(),
};
}, },
child: child, child: child,
); );

View file

@ -1,6 +1,6 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
class Durations { class ADurations {
// Flutter animations (with margin) // Flutter animations (with margin)
static const popupMenuAnimation = Duration(milliseconds: 300 + 20); // ref `_kMenuDuration` used in `_PopupMenuRoute` static const popupMenuAnimation = Duration(milliseconds: 300 + 20); // ref `_kMenuDuration` used in `_PopupMenuRoute`
// page transition duration also available via `ModalRoute.of(context)!.transitionDuration * timeDilation` // page transition duration also available via `ModalRoute.of(context)!.transitionDuration * timeDilation`

View file

@ -17,7 +17,7 @@ class AIcons {
static const brightnessMin = Icons.brightness_low_outlined; static const brightnessMin = Icons.brightness_low_outlined;
static const brightnessMax = Icons.brightness_high_outlined; static const brightnessMax = Icons.brightness_high_outlined;
static const checked = Icons.done_outlined; static const checked = Icons.done_outlined;
static const count = MdiIcons.counter; static final count = MdiIcons.counter;
static const counter = Icons.plus_one_outlined; static const counter = Icons.plus_one_outlined;
static const date = Icons.calendar_today_outlined; static const date = Icons.calendar_today_outlined;
static const dateByDay = Icons.today_outlined; static const dateByDay = Icons.today_outlined;
@ -42,11 +42,11 @@ class AIcons {
static const mainStorage = Icons.smartphone_outlined; static const mainStorage = Icons.smartphone_outlined;
static const mimeType = Icons.code_outlined; static const mimeType = Icons.code_outlined;
static const opacity = Icons.opacity; static const opacity = Icons.opacity;
static const privacy = MdiIcons.shieldAccountOutline; static final privacy = MdiIcons.shieldAccountOutline;
static const rating = Icons.star_border_outlined; static const rating = Icons.star_border_outlined;
static const ratingFull = Icons.star; static const ratingFull = Icons.star;
static const ratingRejected = MdiIcons.starMinusOutline; static final ratingRejected = MdiIcons.starMinusOutline;
static const ratingUnrated = MdiIcons.starOffOutline; static final ratingUnrated = MdiIcons.starOffOutline;
static const raw = Icons.raw_on_outlined; static const raw = Icons.raw_on_outlined;
static const shooting = Icons.camera_outlined; static const shooting = Icons.camera_outlined;
static const removableStorage = Icons.sd_storage_outlined; static const removableStorage = Icons.sd_storage_outlined;
@ -56,7 +56,7 @@ class AIcons {
static const size = Icons.data_usage_outlined; static const size = Icons.data_usage_outlined;
static const text = Icons.format_quote_outlined; static const text = Icons.format_quote_outlined;
static const tag = Icons.local_offer_outlined; static const tag = Icons.local_offer_outlined;
static const tagUntagged = MdiIcons.tagOffOutline; static final tagUntagged = MdiIcons.tagOffOutline;
static const volumeMin = Icons.volume_mute_outlined; static const volumeMin = Icons.volume_mute_outlined;
static const volumeMax = Icons.volume_up_outlined; static const volumeMax = Icons.volume_up_outlined;
@ -79,35 +79,35 @@ class AIcons {
static const clear = Icons.clear_outlined; static const clear = Icons.clear_outlined;
static const clipboard = Icons.content_copy_outlined; static const clipboard = Icons.content_copy_outlined;
static const convert = Icons.transform_outlined; static const convert = Icons.transform_outlined;
static const convertToStillImage = MdiIcons.movieRemoveOutline; static final convertToStillImage = MdiIcons.movieRemoveOutline;
static const copy = Icons.file_copy_outlined; static const copy = Icons.file_copy_outlined;
static const debug = Icons.whatshot_outlined; static const debug = Icons.whatshot_outlined;
static const delete = Icons.delete_outlined; static const delete = Icons.delete_outlined;
static const edit = Icons.edit_outlined; static const edit = Icons.edit_outlined;
static const emptyBin = Icons.delete_sweep_outlined; static const emptyBin = Icons.delete_sweep_outlined;
static const export = Icons.open_with_outlined; static const export = Icons.open_with_outlined;
static const fileExport = MdiIcons.fileExportOutline; static final fileExport = MdiIcons.fileExportOutline;
static const fileImport = MdiIcons.fileImportOutline; static final fileImport = MdiIcons.fileImportOutline;
static const flip = Icons.flip_outlined; static const flip = Icons.flip_outlined;
static const favourite = Icons.favorite_border; static const favourite = Icons.favorite_border;
static const favouriteActive = Icons.favorite; static const favouriteActive = Icons.favorite;
static const filter = MdiIcons.filterOutline; static final filter = MdiIcons.filterOutline;
static const filterOff = MdiIcons.filterOffOutline; static final filterOff = MdiIcons.filterOffOutline;
static const geoBounds = Icons.public_outlined; static const geoBounds = Icons.public_outlined;
static const goUp = Icons.arrow_upward_outlined; static const goUp = Icons.arrow_upward_outlined;
static const hide = Icons.visibility_off_outlined; static const hide = Icons.visibility_off_outlined;
static const info = Icons.info_outlined; static const info = Icons.info_outlined;
static const layers = Icons.layers_outlined; static const layers = Icons.layers_outlined;
static const map = Icons.map_outlined; static const map = Icons.map_outlined;
static const move = MdiIcons.fileMoveOutline; static final move = MdiIcons.fileMoveOutline;
static const mute = Icons.volume_off_outlined; static const mute = Icons.volume_off_outlined;
static const unmute = Icons.volume_up_outlined; static const unmute = Icons.volume_up_outlined;
static const name = Icons.abc_outlined; static const name = Icons.abc_outlined;
static const newTier = Icons.fiber_new_outlined; static const newTier = Icons.fiber_new_outlined;
static const openOutside = Icons.open_in_new_outlined; static const openOutside = Icons.open_in_new_outlined;
static const openVideo = MdiIcons.moviePlayOutline; static final openVideo = MdiIcons.moviePlayOutline;
static const pin = Icons.push_pin_outlined; static const pin = Icons.push_pin_outlined;
static const unpin = MdiIcons.pinOffOutline; static final unpin = MdiIcons.pinOffOutline;
static const play = Icons.play_arrow; static const play = Icons.play_arrow;
static const pause = Icons.pause; static const pause = Icons.pause;
static const print = Icons.print_outlined; static const print = Icons.print_outlined;
@ -123,10 +123,10 @@ class AIcons {
static const search = Icons.search_outlined; static const search = Icons.search_outlined;
static const select = Icons.select_all_outlined; static const select = Icons.select_all_outlined;
static const setAs = Icons.wallpaper_outlined; static const setAs = Icons.wallpaper_outlined;
static const setCover = MdiIcons.imageEditOutline; static final setCover = MdiIcons.imageEditOutline;
static const share = Icons.share_outlined; static const share = Icons.share_outlined;
static const show = Icons.visibility_outlined; static const show = Icons.visibility_outlined;
static const showFullscreen = MdiIcons.arrowExpand; static final showFullscreen = MdiIcons.arrowExpand;
static const slideshow = Icons.slideshow_outlined; static const slideshow = Icons.slideshow_outlined;
static const speed = Icons.speed_outlined; static const speed = Icons.speed_outlined;
static const stats = Icons.donut_small_outlined; static const stats = Icons.donut_small_outlined;
@ -136,7 +136,7 @@ class AIcons {
static const streamText = Icons.closed_caption_outlined; static const streamText = Icons.closed_caption_outlined;
static const vaultLock = Icons.lock_outline; static const vaultLock = Icons.lock_outline;
static const vaultAdd = Icons.enhanced_encryption_outlined; static const vaultAdd = Icons.enhanced_encryption_outlined;
static const vaultConfigure = MdiIcons.shieldLockOutline; static final vaultConfigure = MdiIcons.shieldLockOutline;
static const videoSettings = Icons.video_settings_outlined; static const videoSettings = Icons.video_settings_outlined;
static const view = Icons.grid_view_outlined; static const view = Icons.grid_view_outlined;
static const viewerLock = Icons.lock_outline; static const viewerLock = Icons.lock_outline;
@ -176,6 +176,6 @@ class AIcons {
static const selected = Icons.check_circle_outline; static const selected = Icons.check_circle_outline;
static const unselected = Icons.radio_button_unchecked; static const unselected = Icons.radio_button_unchecked;
static const github = MdiIcons.github; static final github = MdiIcons.github;
static const legal = MdiIcons.scaleBalance; static final legal = MdiIcons.scaleBalance;
} }

View file

@ -1,8 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'dart:ui'; import 'dart:ui';
import 'package:tuple/tuple.dart';
int highestPowerOf2(num x) => x < 1 ? 0 : pow(2, (log(x) / ln2).floor()).toInt(); int highestPowerOf2(num x) => x < 1 ? 0 : pow(2, (log(x) / ln2).floor()).toInt();
int smallestPowerOf2(num x) => x < 1 ? 1 : pow(2, (log(x) / ln2).ceil()).toInt(); int smallestPowerOf2(num x) => x < 1 ? 1 : pow(2, (log(x) / ln2).ceil()).toInt();
@ -10,16 +8,16 @@ int smallestPowerOf2(num x) => x < 1 ? 1 : pow(2, (log(x) / ln2).ceil()).toInt()
double roundToPrecision(final double value, {required final int decimals}) => (value * pow(10, decimals)).round() / pow(10, decimals); double roundToPrecision(final double value, {required final int decimals}) => (value * pow(10, decimals)).round() / pow(10, decimals);
// cf https://en.wikipedia.org/wiki/Intersection_(geometry)#Two_line_segments // cf https://en.wikipedia.org/wiki/Intersection_(geometry)#Two_line_segments
Offset? segmentIntersection(Tuple2<Offset, Offset> s1, Tuple2<Offset, Offset> s2) { Offset? segmentIntersection((Offset, Offset) s1, (Offset, Offset) s2) {
final x1 = s1.item1.dx; final x1 = s1.$1.dx;
final y1 = s1.item1.dy; final y1 = s1.$1.dy;
final x2 = s1.item2.dx; final x2 = s1.$2.dx;
final y2 = s1.item2.dy; final y2 = s1.$2.dy;
final x3 = s2.item1.dx; final x3 = s2.$1.dx;
final y3 = s2.item1.dy; final y3 = s2.$1.dy;
final x4 = s2.item2.dx; final x4 = s2.$2.dx;
final y4 = s2.item2.dy; final y4 = s2.$2.dy;
final a1 = x2 - x1; final a1 = x2 - x1;
final b1 = -(x4 - x3); final b1 = -(x4 - x3);

View file

@ -5,43 +5,34 @@ import 'package:flutter/widgets.dart';
extension ExtraChipActionView on ChipAction { extension ExtraChipActionView on ChipAction {
String getText(BuildContext context) { String getText(BuildContext context) {
switch (this) { final l10n = context.l10n;
case ChipAction.goToAlbumPage: return switch (this) {
return context.l10n.chipActionGoToAlbumPage; ChipAction.goToAlbumPage => l10n.chipActionGoToAlbumPage,
case ChipAction.goToCountryPage: ChipAction.goToCountryPage => l10n.chipActionGoToCountryPage,
return context.l10n.chipActionGoToCountryPage; ChipAction.goToPlacePage => l10n.chipActionGoToPlacePage,
case ChipAction.goToPlacePage: ChipAction.goToTagPage => l10n.chipActionGoToTagPage,
return context.l10n.chipActionGoToPlacePage; ChipAction.ratingOrGreater ||
case ChipAction.goToTagPage: ChipAction.ratingOrLower =>
return context.l10n.chipActionGoToTagPage;
case ChipAction.reverse:
// different data depending on state // different data depending on state
return context.l10n.chipActionFilterOut; toString(),
case ChipAction.hide: ChipAction.reverse =>
return context.l10n.chipActionHide; // different data depending on state
case ChipAction.lockVault: l10n.chipActionFilterOut,
return context.l10n.chipActionLock; ChipAction.hide => l10n.chipActionHide,
} ChipAction.lockVault => l10n.chipActionLock,
};
} }
Widget getIcon() => Icon(_getIconData()); Widget getIcon() => Icon(_getIconData());
IconData _getIconData() { IconData _getIconData() => switch (this) {
switch (this) { ChipAction.goToAlbumPage => AIcons.album,
case ChipAction.goToAlbumPage: ChipAction.goToCountryPage => AIcons.country,
return AIcons.album; ChipAction.goToPlacePage => AIcons.place,
case ChipAction.goToCountryPage: ChipAction.goToTagPage => AIcons.tag,
return AIcons.country; ChipAction.ratingOrGreater || ChipAction.ratingOrLower => AIcons.rating,
case ChipAction.goToPlacePage: ChipAction.reverse => AIcons.reverse,
return AIcons.place; ChipAction.hide => AIcons.hide,
case ChipAction.goToTagPage: ChipAction.lockVault => AIcons.vaultLock,
return AIcons.tag; };
case ChipAction.reverse:
return AIcons.reverse;
case ChipAction.hide:
return AIcons.hide;
case ChipAction.lockVault:
return AIcons.vaultLock;
}
}
} }

View file

@ -5,106 +5,69 @@ import 'package:flutter/material.dart';
extension ExtraChipSetActionView on ChipSetAction { extension ExtraChipSetActionView on ChipSetAction {
String getText(BuildContext context) { String getText(BuildContext context) {
switch (this) { final l10n = context.l10n;
return switch (this) {
// general // general
case ChipSetAction.configureView: ChipSetAction.configureView => l10n.menuActionConfigureView,
return context.l10n.menuActionConfigureView; ChipSetAction.select => l10n.menuActionSelect,
case ChipSetAction.select: ChipSetAction.selectAll => l10n.menuActionSelectAll,
return context.l10n.menuActionSelect; ChipSetAction.selectNone => l10n.menuActionSelectNone,
case ChipSetAction.selectAll:
return context.l10n.menuActionSelectAll;
case ChipSetAction.selectNone:
return context.l10n.menuActionSelectNone;
// browsing // browsing
case ChipSetAction.search: ChipSetAction.search => MaterialLocalizations.of(context).searchFieldLabel,
return MaterialLocalizations.of(context).searchFieldLabel; ChipSetAction.toggleTitleSearch =>
case ChipSetAction.toggleTitleSearch:
// different data depending on toggle state // different data depending on toggle state
return context.l10n.collectionActionShowTitleSearch; l10n.collectionActionShowTitleSearch,
case ChipSetAction.createAlbum: ChipSetAction.createAlbum => l10n.chipActionCreateAlbum,
return context.l10n.chipActionCreateAlbum; ChipSetAction.createVault => l10n.chipActionCreateVault,
case ChipSetAction.createVault:
return context.l10n.chipActionCreateVault;
// browsing or selecting // browsing or selecting
case ChipSetAction.map: ChipSetAction.map => l10n.menuActionMap,
return context.l10n.menuActionMap; ChipSetAction.slideshow => l10n.menuActionSlideshow,
case ChipSetAction.slideshow: ChipSetAction.stats => l10n.menuActionStats,
return context.l10n.menuActionSlideshow;
case ChipSetAction.stats:
return context.l10n.menuActionStats;
// selecting (single/multiple filters) // selecting (single/multiple filters)
case ChipSetAction.delete: ChipSetAction.delete => l10n.chipActionDelete,
return context.l10n.chipActionDelete; ChipSetAction.hide => l10n.chipActionHide,
case ChipSetAction.hide: ChipSetAction.pin => l10n.chipActionPin,
return context.l10n.chipActionHide; ChipSetAction.unpin => l10n.chipActionUnpin,
case ChipSetAction.pin: ChipSetAction.lockVault => l10n.chipActionLock,
return context.l10n.chipActionPin; ChipSetAction.showCountryStates => l10n.chipActionShowCountryStates,
case ChipSetAction.unpin:
return context.l10n.chipActionUnpin;
case ChipSetAction.lockVault:
return context.l10n.chipActionLock;
case ChipSetAction.showCountryStates:
return context.l10n.chipActionShowCountryStates;
// selecting (single filter) // selecting (single filter)
case ChipSetAction.rename: ChipSetAction.rename => l10n.chipActionRename,
return context.l10n.chipActionRename; ChipSetAction.setCover => l10n.chipActionSetCover,
case ChipSetAction.setCover: ChipSetAction.configureVault => l10n.chipActionConfigureVault,
return context.l10n.chipActionSetCover; };
case ChipSetAction.configureVault:
return context.l10n.chipActionConfigureVault;
}
} }
Widget getIcon() => Icon(_getIconData()); Widget getIcon() => Icon(_getIconData());
IconData _getIconData() { IconData _getIconData() {
switch (this) { return switch (this) {
// general // general
case ChipSetAction.configureView: ChipSetAction.configureView => AIcons.view,
return AIcons.view; ChipSetAction.select => AIcons.select,
case ChipSetAction.select: ChipSetAction.selectAll => AIcons.selected,
return AIcons.select; ChipSetAction.selectNone => AIcons.unselected,
case ChipSetAction.selectAll:
return AIcons.selected;
case ChipSetAction.selectNone:
return AIcons.unselected;
// browsing // browsing
case ChipSetAction.search: ChipSetAction.search => AIcons.search,
return AIcons.search; ChipSetAction.toggleTitleSearch =>
case ChipSetAction.toggleTitleSearch:
// different data depending on toggle state // different data depending on toggle state
return AIcons.filter; AIcons.filter,
case ChipSetAction.createAlbum: ChipSetAction.createAlbum => AIcons.add,
return AIcons.add; ChipSetAction.createVault => AIcons.vaultAdd,
case ChipSetAction.createVault:
return AIcons.vaultAdd;
// browsing or selecting // browsing or selecting
case ChipSetAction.map: ChipSetAction.map => AIcons.map,
return AIcons.map; ChipSetAction.slideshow => AIcons.slideshow,
case ChipSetAction.slideshow: ChipSetAction.stats => AIcons.stats,
return AIcons.slideshow;
case ChipSetAction.stats:
return AIcons.stats;
// selecting (single/multiple filters) // selecting (single/multiple filters)
case ChipSetAction.delete: ChipSetAction.delete => AIcons.delete,
return AIcons.delete; ChipSetAction.hide => AIcons.hide,
case ChipSetAction.hide: ChipSetAction.pin => AIcons.pin,
return AIcons.hide; ChipSetAction.unpin => AIcons.unpin,
case ChipSetAction.pin: ChipSetAction.lockVault => AIcons.vaultLock,
return AIcons.pin; ChipSetAction.showCountryStates => AIcons.state,
case ChipSetAction.unpin:
return AIcons.unpin;
case ChipSetAction.lockVault:
return AIcons.vaultLock;
case ChipSetAction.showCountryStates:
return AIcons.state;
// selecting (single filter) // selecting (single filter)
case ChipSetAction.rename: ChipSetAction.rename => AIcons.name,
return AIcons.name; ChipSetAction.setCover => AIcons.setCover,
case ChipSetAction.setCover: ChipSetAction.configureVault => AIcons.vaultConfigure,
return AIcons.setCover; };
case ChipSetAction.configureVault:
return AIcons.vaultConfigure;
}
} }
} }

View file

@ -6,216 +6,137 @@ import 'package:flutter/widgets.dart';
extension ExtraEntryActionView on EntryAction { extension ExtraEntryActionView on EntryAction {
String getText(BuildContext context) { String getText(BuildContext context) {
switch (this) { final l10n = context.l10n;
case EntryAction.info: return switch (this) {
return context.l10n.entryActionInfo; EntryAction.info => l10n.entryActionInfo,
case EntryAction.addShortcut: EntryAction.addShortcut => l10n.collectionActionAddShortcut,
return context.l10n.collectionActionAddShortcut; EntryAction.copyToClipboard => l10n.entryActionCopyToClipboard,
case EntryAction.copyToClipboard: EntryAction.delete => l10n.entryActionDelete,
return context.l10n.entryActionCopyToClipboard; EntryAction.restore => l10n.entryActionRestore,
case EntryAction.delete: EntryAction.convert => l10n.entryActionConvert,
return context.l10n.entryActionDelete; EntryAction.print => l10n.entryActionPrint,
case EntryAction.restore: EntryAction.rename => l10n.entryActionRename,
return context.l10n.entryActionRestore; EntryAction.copy => l10n.collectionActionCopy,
case EntryAction.convert: EntryAction.move => l10n.collectionActionMove,
return context.l10n.entryActionConvert; EntryAction.share => l10n.entryActionShare,
case EntryAction.print: EntryAction.toggleFavourite =>
return context.l10n.entryActionPrint;
case EntryAction.rename:
return context.l10n.entryActionRename;
case EntryAction.copy:
return context.l10n.collectionActionCopy;
case EntryAction.move:
return context.l10n.collectionActionMove;
case EntryAction.share:
return context.l10n.entryActionShare;
case EntryAction.toggleFavourite:
// different data depending on toggle state // different data depending on toggle state
return context.l10n.entryActionAddFavourite; l10n.entryActionAddFavourite,
// raster // raster
case EntryAction.rotateCCW: EntryAction.rotateCCW => l10n.entryActionRotateCCW,
return context.l10n.entryActionRotateCCW; EntryAction.rotateCW => l10n.entryActionRotateCW,
case EntryAction.rotateCW: EntryAction.flip => l10n.entryActionFlip,
return context.l10n.entryActionRotateCW;
case EntryAction.flip:
return context.l10n.entryActionFlip;
// vector // vector
case EntryAction.viewSource: EntryAction.viewSource => l10n.entryActionViewSource,
return context.l10n.entryActionViewSource;
// video // video
case EntryAction.lockViewer: EntryAction.lockViewer => l10n.viewerActionLock,
return context.l10n.viewerActionLock; EntryAction.videoCaptureFrame => l10n.videoActionCaptureFrame,
case EntryAction.videoCaptureFrame: EntryAction.videoToggleMute =>
return context.l10n.videoActionCaptureFrame;
case EntryAction.videoToggleMute:
// different data depending on toggle state // different data depending on toggle state
return context.l10n.videoActionMute; l10n.videoActionMute,
case EntryAction.videoSelectStreams: EntryAction.videoSelectStreams => l10n.videoActionSelectStreams,
return context.l10n.videoActionSelectStreams; EntryAction.videoSetSpeed => l10n.videoActionSetSpeed,
case EntryAction.videoSetSpeed: EntryAction.videoSettings => l10n.viewerActionSettings,
return context.l10n.videoActionSetSpeed; EntryAction.videoTogglePlay =>
case EntryAction.videoSettings:
return context.l10n.viewerActionSettings;
case EntryAction.videoTogglePlay:
// different data depending on toggle state // different data depending on toggle state
return context.l10n.videoActionPlay; l10n.videoActionPlay,
case EntryAction.videoReplay10: EntryAction.videoReplay10 => l10n.videoActionReplay10,
return context.l10n.videoActionReplay10; EntryAction.videoSkip10 => l10n.videoActionSkip10,
case EntryAction.videoSkip10:
return context.l10n.videoActionSkip10;
// external // external
case EntryAction.edit: EntryAction.edit => l10n.entryActionEdit,
return context.l10n.entryActionEdit; EntryAction.open || EntryAction.openVideo => l10n.entryActionOpen,
case EntryAction.open: EntryAction.openMap => l10n.entryActionOpenMap,
case EntryAction.openVideo: EntryAction.setAs => l10n.entryActionSetAs,
return context.l10n.entryActionOpen;
case EntryAction.openMap:
return context.l10n.entryActionOpenMap;
case EntryAction.setAs:
return context.l10n.entryActionSetAs;
// platform // platform
case EntryAction.rotateScreen: EntryAction.rotateScreen => l10n.entryActionRotateScreen,
return context.l10n.entryActionRotateScreen;
// metadata // metadata
case EntryAction.editDate: EntryAction.editDate => l10n.entryInfoActionEditDate,
return context.l10n.entryInfoActionEditDate; EntryAction.editLocation => l10n.entryInfoActionEditLocation,
case EntryAction.editLocation: EntryAction.editTitleDescription => l10n.entryInfoActionEditTitleDescription,
return context.l10n.entryInfoActionEditLocation; EntryAction.editRating => l10n.entryInfoActionEditRating,
case EntryAction.editTitleDescription: EntryAction.editTags => l10n.entryInfoActionEditTags,
return context.l10n.entryInfoActionEditTitleDescription; EntryAction.removeMetadata => l10n.entryInfoActionRemoveMetadata,
case EntryAction.editRating: EntryAction.exportMetadata => l10n.entryInfoActionExportMetadata,
return context.l10n.entryInfoActionEditRating;
case EntryAction.editTags:
return context.l10n.entryInfoActionEditTags;
case EntryAction.removeMetadata:
return context.l10n.entryInfoActionRemoveMetadata;
case EntryAction.exportMetadata:
return context.l10n.entryInfoActionExportMetadata;
// metadata / GeoTIFF // metadata / GeoTIFF
case EntryAction.showGeoTiffOnMap: EntryAction.showGeoTiffOnMap => l10n.entryActionShowGeoTiffOnMap,
return context.l10n.entryActionShowGeoTiffOnMap;
// metadata / motion photo // metadata / motion photo
case EntryAction.convertMotionPhotoToStillImage: EntryAction.convertMotionPhotoToStillImage => l10n.entryActionConvertMotionPhotoToStillImage,
return context.l10n.entryActionConvertMotionPhotoToStillImage; EntryAction.viewMotionPhotoVideo => l10n.entryActionViewMotionPhotoVideo,
case EntryAction.viewMotionPhotoVideo:
return context.l10n.entryActionViewMotionPhotoVideo;
// debug // debug
case EntryAction.debug: EntryAction.debug => 'Debug',
return 'Debug'; };
}
} }
Widget getIcon() { Widget getIcon() {
final child = Icon(getIconData()); final child = Icon(getIconData());
switch (this) { return switch (this) {
case EntryAction.debug: EntryAction.debug => ShaderMask(
return ShaderMask(
shaderCallback: AvesColorsData.debugGradient.createShader, shaderCallback: AvesColorsData.debugGradient.createShader,
blendMode: BlendMode.srcIn, blendMode: BlendMode.srcIn,
child: child, child: child,
); ),
default: _ => child,
return child; };
}
} }
IconData getIconData() { IconData getIconData() {
switch (this) { return switch (this) {
case EntryAction.info: EntryAction.info => AIcons.info,
return AIcons.info; EntryAction.addShortcut => AIcons.addShortcut,
case EntryAction.addShortcut: EntryAction.copyToClipboard => AIcons.clipboard,
return AIcons.addShortcut; EntryAction.delete => AIcons.delete,
case EntryAction.copyToClipboard: EntryAction.restore => AIcons.restore,
return AIcons.clipboard; EntryAction.convert => AIcons.convert,
case EntryAction.delete: EntryAction.print => AIcons.print,
return AIcons.delete; EntryAction.rename => AIcons.name,
case EntryAction.restore: EntryAction.copy => AIcons.copy,
return AIcons.restore; EntryAction.move => AIcons.move,
case EntryAction.convert: EntryAction.share => AIcons.share,
return AIcons.convert; EntryAction.toggleFavourite =>
case EntryAction.print:
return AIcons.print;
case EntryAction.rename:
return AIcons.name;
case EntryAction.copy:
return AIcons.copy;
case EntryAction.move:
return AIcons.move;
case EntryAction.share:
return AIcons.share;
case EntryAction.toggleFavourite:
// different data depending on toggle state // different data depending on toggle state
return AIcons.favourite; AIcons.favourite,
// raster // raster
case EntryAction.rotateCCW: EntryAction.rotateCCW => AIcons.rotateLeft,
return AIcons.rotateLeft; EntryAction.rotateCW => AIcons.rotateRight,
case EntryAction.rotateCW: EntryAction.flip => AIcons.flip,
return AIcons.rotateRight;
case EntryAction.flip:
return AIcons.flip;
// vector // vector
case EntryAction.viewSource: EntryAction.viewSource => AIcons.vector,
return AIcons.vector;
// video // video
case EntryAction.lockViewer: EntryAction.lockViewer => AIcons.viewerLock,
return AIcons.viewerLock; EntryAction.videoCaptureFrame => AIcons.captureFrame,
case EntryAction.videoCaptureFrame: EntryAction.videoToggleMute =>
return AIcons.captureFrame;
case EntryAction.videoToggleMute:
// different data depending on toggle state // different data depending on toggle state
return AIcons.mute; AIcons.mute,
case EntryAction.videoSelectStreams: EntryAction.videoSelectStreams => AIcons.streams,
return AIcons.streams; EntryAction.videoSetSpeed => AIcons.speed,
case EntryAction.videoSetSpeed: EntryAction.videoSettings => AIcons.videoSettings,
return AIcons.speed; EntryAction.videoTogglePlay =>
case EntryAction.videoSettings:
return AIcons.videoSettings;
case EntryAction.videoTogglePlay:
// different data depending on toggle state // different data depending on toggle state
return AIcons.play; AIcons.play,
case EntryAction.videoReplay10: EntryAction.videoReplay10 => AIcons.replay10,
return AIcons.replay10; EntryAction.videoSkip10 => AIcons.skip10,
case EntryAction.videoSkip10:
return AIcons.skip10;
// external // external
case EntryAction.edit: EntryAction.edit => AIcons.edit,
return AIcons.edit; EntryAction.open || EntryAction.openVideo => AIcons.openOutside,
case EntryAction.open: EntryAction.openMap => AIcons.map,
case EntryAction.openVideo: EntryAction.setAs => AIcons.setAs,
return AIcons.openOutside;
case EntryAction.openMap:
return AIcons.map;
case EntryAction.setAs:
return AIcons.setAs;
// platform // platform
case EntryAction.rotateScreen: EntryAction.rotateScreen => AIcons.rotateScreen,
return AIcons.rotateScreen;
// metadata // metadata
case EntryAction.editDate: EntryAction.editDate => AIcons.date,
return AIcons.date; EntryAction.editLocation => AIcons.location,
case EntryAction.editLocation: EntryAction.editTitleDescription => AIcons.description,
return AIcons.location; EntryAction.editRating => AIcons.rating,
case EntryAction.editTitleDescription: EntryAction.editTags => AIcons.tag,
return AIcons.description; EntryAction.removeMetadata => AIcons.clear,
case EntryAction.editRating: EntryAction.exportMetadata => AIcons.fileExport,
return AIcons.rating;
case EntryAction.editTags:
return AIcons.tag;
case EntryAction.removeMetadata:
return AIcons.clear;
case EntryAction.exportMetadata:
return AIcons.fileExport;
// metadata / GeoTIFF // metadata / GeoTIFF
case EntryAction.showGeoTiffOnMap: EntryAction.showGeoTiffOnMap => AIcons.map,
return AIcons.map;
// metadata / motion photo // metadata / motion photo
case EntryAction.convertMotionPhotoToStillImage: EntryAction.convertMotionPhotoToStillImage => AIcons.convertToStillImage,
return AIcons.convertToStillImage; EntryAction.viewMotionPhotoVideo => AIcons.openVideo,
case EntryAction.viewMotionPhotoVideo:
return AIcons.openVideo;
// debug // debug
case EntryAction.debug: EntryAction.debug => AIcons.debug,
return AIcons.debug; };
}
} }
} }

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