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="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
### Changed

View file

@ -2,6 +2,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id 'com.android.application'
id 'com.google.devtools.ksp' version "$ksp_version"
id 'kotlin-android'
id 'kotlin-kapt'
}
@ -48,7 +49,7 @@ if (keystorePropertiesFile.exists()) {
android {
namespace 'deckers.thibault.aves'
compileSdk 33
compileSdk 34
ndkVersion flutter.ndkVersion
compileOptions {
@ -75,7 +76,7 @@ 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
minSdkVersion 19
targetSdkVersion 33
targetSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
manifestPlaceholders = [googleApiKey: keystoreProperties["googleApiKey"] ?: "<NONE>",
@ -175,10 +176,10 @@ android {
tasks.withType(KotlinCompile).configureEach {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
kotlin {
jvmToolchain(8)
}
flutter {
@ -202,7 +203,7 @@ repositories {
}
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.core:core-ktx:1.10.1'
@ -224,7 +225,7 @@ dependencies {
// - https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
// - https://jitpack.io/p/deckerst/mp4parser
// - 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:muxer:4cc0c5d06c'
implementation 'com.github.deckerst:pixymeta-android:706bd73d6e'
@ -232,10 +233,10 @@ dependencies {
// huawei flavor only
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 "com.github.bumptech.glide:compiler:$glide_version"
ksp "com.github.bumptech.glide:ksp:$glide_version"
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
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_VIDEO" />
<uses-permission
@ -56,8 +57,9 @@
allow install on API 19, despite the `minSdkVersion` declared in dependencies:
- Google Maps is from API 20
- 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 -->
<queries>
@ -295,7 +297,8 @@
<meta-data
android:name="flutterEmbedding"
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
android:name="io.flutter.embedding.android.EnableImpeller"
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.utils.PermissionManager
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.getVolumePaths
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.util.PathUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@ -25,6 +27,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getDataUsage" -> ioScope.launch { safe(call, result, ::getDataUsage) }
"getStorageVolumes" -> ioScope.launch { safe(call, result, ::getStorageVolumes) }
"getVaultRoot" -> ioScope.launch { safe(call, result, ::getVaultRoot) }
"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) {
val volumes = ArrayList<Map<String, Any>>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {

View file

@ -96,12 +96,7 @@ class SvgRegionFetcher internal constructor(
svg.renderToCanvas(canvas, renderOptions)
bitmap = Bitmap.createBitmap(bitmap, bleedX, bleedY, targetBitmapWidth, targetBitmapHeight)
if (bitmap != null) {
result.success(bitmap.getBytes(canHaveAlpha = true, recycle = true))
} else {
result.error("fetch-null", "failed to decode region for uri=$uri regionRect=$regionRect", null)
}
result.success(bitmap.getBytes(canHaveAlpha = true, recycle = true))
} catch (e: Exception) {
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
while (inputStream.read(buffer).also { len = it } != -1) {
// cannot decode image on Flutter side when using `buffer` directly
success(buffer.copyOf(len))
if (MemoryUtils.canAllocate(len)) {
success(buffer.copyOf(len))
} else {
error("streamBytes-memory", "not enough memory to allocate $len bytes", null)
return
}
}
}

View file

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

View file

@ -52,6 +52,9 @@ object Mp4ParserHelper {
}
// creating `IsoFile` with a `File` or a `File.inputStream()` yields `No such device`
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 ->
when {
box == isoFile.movieBox -> false
@ -60,7 +63,7 @@ object Mp4ParserHelper {
else -> true
}
}
lastContentBox ?: throw Exception("failed to find last context box")
lastContentBox ?: throw Exception("failed to find last content box")
val oldFileSize = isoFile.size
var appendOffset = (isoFile.getBoxOffset { box -> box == lastContentBox })!! + lastContentBox.size
@ -97,7 +100,6 @@ object Mp4ParserHelper {
if (trailing > 0) {
addFreeBoxEdit(appendOffset, trailing)
}
return edits
}
}
@ -277,7 +279,9 @@ object Mp4ParserHelper {
// creating `IsoFile` with a `File` or a `File.inputStream()` yields `No such device`
IsoFile(channel, metadataBoxParser()).use { isoFile ->
val userDataBox = Path.getPath<UserDataBox>(isoFile.movieBox, UserDataBox.TYPE)
fields.putAll(extractBoxFields(userDataBox))
if (userDataBox != null) {
fields.putAll(extractBoxFields(userDataBox))
}
}
}
}

View file

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

View file

@ -1,13 +1,10 @@
package deckers.thibault.aves.utils
import android.app.ActivityManager
import android.app.Service
import android.content.ContentResolver
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.util.Log
import deckers.thibault.aves.utils.UriUtils.tryParseId
@ -24,19 +21,6 @@ object ContextUtils {
.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? {
var contentUri: Uri = 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.loader.FlutterLoader
import io.flutter.view.FlutterCallbackInformation
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@ -60,7 +61,7 @@ object FlutterUtils {
suspend fun runOnUiThread(r: Runnable) {
val mainLooper = Looper.getMainLooper()
if (Looper.myLooper() != mainLooper) {
suspendCoroutine<Boolean> { cont ->
suspendCoroutine { cont: Continuation<Boolean> ->
Handler(mainLooper).post {
r.run()
cont.resume(true)

View file

@ -716,6 +716,18 @@ object StorageUtils {
// 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 {
return if (dirPath.endsWith(File.separator)) dirPath else dirPath + File.separator
}

View file

@ -1,2 +1,12 @@
<?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_widget_label">Marco de foto</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="analysis_channel_name">Explorar 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 {
ext {
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'
// 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.0.300'
huawei_agconnect_version = '1.9.1.300'
abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
useCrashlytics = gradle.startParameter.taskNames.any { task -> task.containsIgnoreCase("play") }
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.

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.

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": {},
"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": {},
"welcomeTermsToggle": "ڕازیم بە مەرج و یاساکانی بەکارهێنان",

View file

@ -1486,5 +1486,25 @@
"settingsVideoPlaybackPageTitle": "Přehrávání",
"@settingsVideoPlaybackPageTitle": {},
"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": {},
"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",
"nameConflictStrategySkip": "Skip",
"overlayHistogramNone": "None",
"overlayHistogramRGB": "RGB",
"overlayHistogramLuminance": "Luminance",
"subtitlePositionTop": "Top",
"subtitlePositionBottom": "Bottom",
@ -533,6 +537,14 @@
"aboutBugReportInstruction": "Report on GitHub with the logs and system information",
"aboutBugReportButton": "Report",
"aboutDataUsageSectionTitle": "Data Usage",
"aboutDataUsageData": "Data",
"aboutDataUsageCache": "Cache",
"aboutDataUsageDatabase": "Database",
"aboutDataUsageMisc": "Misc",
"aboutDataUsageInternal": "Internal",
"aboutDataUsageExternal": "External",
"aboutCreditsSectionTitle": "Credits",
"aboutCreditsWorldAtlas1": "This app uses a TopoJSON file from",
"aboutCreditsWorldAtlas2": "under ISC License.",
@ -801,6 +813,7 @@
"settingsViewerOverlayTile": "Overlay",
"settingsViewerOverlayPageTitle": "Overlay",
"settingsViewerShowOverlayOnOpening": "Show on opening",
"settingsViewerShowHistogram": "Show histogram",
"settingsViewerShowMinimap": "Show minimap",
"settingsViewerShowInformation": "Show information",
"settingsViewerShowInformationSubtitle": "Show title, date, location, etc.",

View file

@ -1334,5 +1334,27 @@
"cropAspectRatioOriginal": "Original",
"@cropAspectRatioOriginal": {},
"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": {},
"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": {},
"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": {},
"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": {},
"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": {},
"applyButtonLabel": "APPLICA",
"@applyButtonLabel": {},
"deleteButtonLabel": "CANCELLA",
"deleteButtonLabel": "ELIMINA",
"@deleteButtonLabel": {},
"nextButtonLabel": "AVANTI",
"@nextButtonLabel": {},
@ -345,7 +345,7 @@
"@noMatchingAppDialogMessage": {},
"binEntriesConfirmationDialogMessage": "{count, plural, =1{Spostare questo elemento nel cestino?} other{Spostare questi {count} elementi nel cestino?}}",
"@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": {},
"moveUndatedConfirmationDialogMessage": "Salvare le date degli elementi prima di procedere?",
"@moveUndatedConfirmationDialogMessage": {},
@ -389,9 +389,9 @@
"@renameProcessorCounter": {},
"renameProcessorName": "Nome",
"@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": {},
"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": {},
"exportEntryDialogFormat": "Formato:",
"@exportEntryDialogFormat": {},
@ -579,7 +579,7 @@
"@dateYesterday": {},
"dateThisMonth": "Questo mese",
"@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": {},
"collectionCopyFailureFeedback": "{count, plural, =1{Impossibile copiare 1 elemento} other{Impossibile copiare {count} elementi}}",
"@collectionCopyFailureFeedback": {},
@ -775,7 +775,7 @@
"@settingsConfirmationTile": {},
"settingsConfirmationDialogTitle": "Richieste di conferma",
"@settingsConfirmationDialogTitle": {},
"settingsConfirmationBeforeDeleteItems": "Chiedi prima di cancellare gli elementi definitivamente",
"settingsConfirmationBeforeDeleteItems": "Chiedi prima di eliminare gli elementi definitivamente",
"@settingsConfirmationBeforeDeleteItems": {},
"settingsConfirmationBeforeMoveToBinItems": "Chiedi prima di spostare gli elementi nel cestino",
"@settingsConfirmationBeforeMoveToBinItems": {},
@ -955,7 +955,7 @@
"@settingsSaveSearchHistory": {},
"settingsEnableBin": "Usa il cestino",
"@settingsEnableBin": {},
"settingsEnableBinSubtitle": "Conserva gli elementi cancellati per 30 giorni",
"settingsEnableBinSubtitle": "Conserva gli elementi eliminati per 30 giorni",
"@settingsEnableBinSubtitle": {},
"settingsHiddenItemsTile": "Elementi nascosti",
"@settingsHiddenItemsTile": {},
@ -1316,5 +1316,37 @@
"settingsVideoPlaybackTile": "Riproduzione",
"@settingsVideoPlaybackTile": {},
"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": {},
"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": {},
"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": {},
"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": {},
"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": {},
"videoActionSelectStreams": "Выбрать дорожку",
"@videoActionSelectStreams": {},
"videoActionSetSpeed": "Скорость вопспроизведения",
"videoActionSetSpeed": "Скорость воспроизведения",
"@videoActionSetSpeed": {},
"viewerActionSettings": "Настройки",
"@viewerActionSettings": {},
@ -1334,5 +1334,27 @@
"searchStatesSectionTitle": "Регионы",
"@searchStatesSectionTitle": {},
"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": {},
"setCoverDialogAuto": "Авто",
"@setCoverDialogAuto": {},
@ -1492,5 +1492,27 @@
"cropAspectRatioSquare": "Площа",
"@cropAspectRatioSquare": {},
"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": {},
"entryInfoActionEditLocation": "編輯座標",
"@entryInfoActionEditLocation": {},
"entryInfoActionEditTitleDescription": "編輯標題和述",
"entryInfoActionEditTitleDescription": "編輯標題和述",
"@entryInfoActionEditTitleDescription": {},
"entryInfoActionExportMetadata": "匯出元資料",
"@entryInfoActionExportMetadata": {},
@ -671,11 +671,11 @@
"@settingsConfirmationTile": {},
"settingsConfirmationDialogTitle": "確認對話框",
"@settingsConfirmationDialogTitle": {},
"settingsConfirmationBeforeDeleteItems": "永久刪除項目前詢問",
"settingsConfirmationBeforeDeleteItems": "永久刪除項目前詢問",
"@settingsConfirmationBeforeDeleteItems": {},
"settingsConfirmationBeforeMoveToBinItems": "移動項目到資源回收桶前詢問",
"settingsConfirmationBeforeMoveToBinItems": "移動項目到資源回收桶前詢問",
"@settingsConfirmationBeforeMoveToBinItems": {},
"settingsConfirmationBeforeMoveUndatedItems": "移動沒有日期項目前詢問",
"settingsConfirmationBeforeMoveUndatedItems": "日期不詳的項目移動前先詢問",
"@settingsConfirmationBeforeMoveUndatedItems": {},
"settingsConfirmationAfterMoveToBinItems": "移動項目到資源回收桶後顯示訊息",
"@settingsConfirmationAfterMoveToBinItems": {},
@ -1297,7 +1297,7 @@
"@viewerInfoSearchEmpty": {},
"viewerInfoSearchSuggestionDate": "日期和時間",
"@viewerInfoSearchSuggestionDate": {},
"viewerInfoSearchSuggestionDescription": "述",
"viewerInfoSearchSuggestionDescription": "述",
"@viewerInfoSearchSuggestionDescription": {},
"viewerInfoSearchSuggestionDimensions": "範圍",
"@viewerInfoSearchSuggestionDimensions": {},

View file

@ -49,18 +49,28 @@ class Contributors {
Contributor('Макар Разин', 'makarrazin14@gmail.com'),
Contributor('Leon', 'leonhoog@outlook.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('Salih Ail', 'rrrfff444@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('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('GoRaN', 'gorangharib.909@gmail.com'), // Kurdish (Central)
// Contributor('Rohit Burman', 'rohitburman31p@rediffmail.com'), // Hindi
// Contributor('Subham Jena', 'subhamjena8465@gmail.com'), // Odia
// Contributor('Raman', 'xysed@tutanota.com'), // Malayalam
// Contributor('mytja', 'mamnju21@gmail.com'), // Slovenian
// Contributor('Nattapong K', 'mixer5056@gmail.com'), // Thai
};
}

View file

@ -92,6 +92,11 @@ class Dependencies {
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',
),
Dependency(
name: 'Media Kit',
license: mit,
sourceUrl: 'https://github.com/media-kit/media-kit',
),
Dependency(
name: 'Package Info Plus',
license: bsd3,
@ -365,11 +370,6 @@ class Dependencies {
license: mit,
sourceUrl: 'https://github.com/brianegan/transparent_image',
),
Dependency(
name: 'Tuple',
license: bsd2,
sourceUrl: 'https://github.com/google/tuple.dart',
),
Dependency(
name: 'Vector Math',
license: '$zlib, $bsd3',

View file

@ -13,7 +13,6 @@ import 'package:collection/collection.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';
import 'package:tuple/tuple.dart';
final Covers covers = Covers._private();
@ -40,11 +39,11 @@ class Covers {
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;
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({
@ -113,7 +112,7 @@ class Covers {
}
AlbumType effectiveAlbumType(String albumPath) {
final filterPackage = of(AlbumFilter(albumPath, null))?.item2;
final filterPackage = of(AlbumFilter(albumPath, null))?.$2;
if (filterPackage != null) {
return filterPackage.isEmpty ? AlbumType.regular : AlbumType.app;
} else {
@ -122,7 +121,7 @@ class Covers {
}
String? effectiveAlbumPackage(String albumPath) {
final filterPackage = of(AlbumFilter(albumPath, null))?.item2;
final filterPackage = of(AlbumFilter(albumPath, null))?.$2;
return filterPackage ?? appInventory.getAlbumAppPackageName(albumPath);
}

View file

@ -200,6 +200,7 @@ class AvesEntry with AvesEntryBase {
_bestTitle = null;
}
@override
String? get path => _path;
// 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);
}
@override
bool get isAnimated => catalogMetadata?.isAnimated ?? false;
@override
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/metadata/catalog.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/metadata/svg_metadata_service.dart';
@ -23,7 +24,7 @@ extension ExtraAvesEntryCatalog on AvesEntry {
catalogMetadata = CatalogMetadata(id: id);
} else {
// pre-processing
if (isVideo && (!isSized || durationMillis == 0)) {
if ((isVideo && (!isSized || durationMillis == 0)) || mimeType == MimeTypes.avif) {
// exotic video that is not sized during loading
final fields = await VideoMetadataFormatter.getLoadingMetadata(this);
await applyNewFields(fields, persist: persist);
@ -33,7 +34,7 @@ extension ExtraAvesEntryCatalog on AvesEntry {
catalogMetadata = await metadataFetchService.getCatalogMetadata(this, background: background);
// post-processing
if (isVideo && (catalogMetadata?.dateMillis ?? 0) == 0) {
if ((isVideo && (catalogMetadata?.dateMillis ?? 0) == 0) || (mimeType == MimeTypes.avif && durationMillis != null)) {
catalogMetadata = await VideoMetadataFormatter.getCatalogMetadata(this);
}
if (isGeotiff && !hasGps) {

View file

@ -71,7 +71,7 @@ extension ExtraAvesEntryInfo on AvesEntry {
Future<List<MetadataDirectory>> _getStreamDirectories(BuildContext context) async {
final directories = <MetadataDirectory>[];
final mediaInfo = await VideoMetadataFormatter.getVideoMetadata(this);
final mediaInfo = await videoMetadataFetcher.getMetadata(this);
final formattedMediaTags = VideoMetadataFormatter.formatInfo(mediaInfo);
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/ref/metadata/exif.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/mime_types.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/services/metadata/xmp.dart';
import 'package:aves/utils/time_utils.dart';
@ -82,7 +82,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
return dataTypes;
}
static final removalLocation = LatLng(0, 0);
static const removalLocation = LatLng(0, 0);
Future<Set<EntryDataType>> editLocation(LatLng? latLng) async {
final dataTypes = <EntryDataType>{};

View file

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

View file

@ -64,7 +64,7 @@ class AlbumFilter extends CoveredCollectionFilter {
@override
Future<Color> color(BuildContext context) {
// 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);
final colors = context.read<AvesColorsData>();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,9 +1,6 @@
import 'dart:ui';
import 'package:aves/model/filters/recent.dart';
import 'package:aves/model/naming_pattern.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/countries_page.dart';
import 'package:aves/widgets/filter_grids/tags_page.dart';
@ -75,6 +72,7 @@ class SettingsDefaults {
];
static const showOverlayOnOpening = true;
static const showOverlayMinimap = false;
static const overlayHistogramStyle = OverlayHistogramStyle.none;
static const showOverlayInfo = true;
static const showOverlayDescription = false;
static const showOverlayRatingTags = false;
@ -84,26 +82,6 @@ class SettingsDefaults {
static const viewerUseCutout = true;
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
static const infoMapZoom = 12.0;
static const coordinateFormat = CoordinateFormat.dms;

View file

@ -7,9 +7,9 @@ extension ExtraAccessibilityTimeout on AccessibilityTimeout {
switch (this) {
case AccessibilityTimeout.system:
if (hasAction) {
return Duration(milliseconds: await (AccessibilityService.getRecommendedTimeToTakeAction(Durations.opToastActionDisplay)));
return Duration(milliseconds: await (AccessibilityService.getRecommendedTimeToTakeAction(ADurations.opToastActionDisplay)));
} else {
return Duration(milliseconds: await (AccessibilityService.getRecommendedTimeToRead(Durations.opToastTextDisplay)));
return Duration(milliseconds: await (AccessibilityService.getRecommendedTimeToRead(ADurations.opToastTextDisplay)));
}
case AccessibilityTimeout.s1:
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: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))));
int compareAlbumsByName(String a, String b) {
int compareAlbumsByName(String? a, String? b) {
a ??= '';
b ??= '';
final ua = getAlbumDisplayName(null, a);
final ub = getAlbumDisplayName(null, b);
final c = compareAsciiUpperCaseNatural(ua, ub);

View file

@ -84,10 +84,10 @@ class CollectionLens with ChangeNotifier {
}
_subscriptions.add(settings.updateStream
.where((event) => [
Settings.collectionBurstPatternsKey,
Settings.collectionSortFactorKey,
Settings.collectionGroupFactorKey,
Settings.collectionSortReverseKey,
SettingKeys.collectionBurstPatternsKey,
SettingKeys.collectionSortFactorKey,
SettingKeys.collectionGroupFactorKey,
SettingKeys.collectionSortReverseKey,
].contains(event.key))
.listen((_) => _onSettingsChanged()));
refresh();
@ -245,7 +245,7 @@ class CollectionLens with ChangeNotifier {
}
case EntrySortFactor.name:
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);
case EntrySortFactor.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 {
CollectionSource() {
settings.updateStream.where((event) => event.key == Settings.localeKey).listen((_) => invalidateAlbumDisplayNames());
settings.updateStream.where((event) => event.key == Settings.hiddenFiltersKey).listen((event) {
settings.updateStream.where((event) => event.key == SettingKeys.localeKey).listen((_) => invalidateAlbumDisplayNames());
settings.updateStream.where((event) => event.key == SettingKeys.hiddenFiltersKey).listen((event) {
final oldValue = event.oldValue;
if (oldValue is List<String>?) {
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);
await covers.set(
filter: newFilter,
entryId: existingCover?.item1,
packageName: existingCover?.item2,
color: existingCover?.item3,
entryId: existingCover?.$1,
packageName: existingCover?.$2,
color: existingCover?.$3,
);
renameNewAlbum(sourceAlbum, destinationAlbum);
@ -547,7 +547,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
}
AvesEntry? coverEntry(CollectionFilter filter) {
final id = covers.of(filter)?.item1;
final id = covers.of(filter)?.$1;
if (id != null) {
final entry = visibleEntries.firstWhereOrNull((entry) => entry.id == id);
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:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:tuple/tuple.dart';
mixin LocationMixin on CountryMixin, StateMixin {
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)
// cf https://en.wikipedia.org/wiki/Decimal_degrees#Precision
final latLngFactor = pow(10, 2);
Tuple2<int, int> approximateLatLng(AvesEntry entry) {
(int latitude, int longitude) approximateLatLng(AvesEntry entry) {
// entry has coordinates
final catalogMetadata = entry.catalogMetadata!;
final lat = catalogMetadata.latitude!;
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 knownLocations = <Tuple2<int, int>, AddressDetails?>{};
final knownLocations = <(int, int), AddressDetails?>{};
located.forEach((entry) {
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/hevc.dart';
import 'package:aves/ref/languages.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/ref/mp4.dart';
import 'package:aves/services/common/services.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/string_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:collection/collection.dart';
import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/foundation.dart';
class VideoMetadataFormatter {
@ -26,7 +25,8 @@ class VideoMetadataFormatter {
static final _ambiguousDatePatterns = {
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 Map<String, String> _codecNames = {
Codecs.ac3: 'AC-3',
@ -44,20 +44,8 @@ class VideoMetadataFormatter {
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 {
final mediaInfo = await getVideoMetadata(entry);
final mediaInfo = await videoMetadataFetcher.getMetadata(entry);
final fields = <String, int>{};
final streams = mediaInfo[Keys.streams];
@ -77,12 +65,26 @@ class VideoMetadataFormatter {
final durationMicros = mediaInfo[Keys.durationMicros];
if (durationMicros is num) {
fields['durationMillis'] = (durationMicros / 1000).round();
} else {
final duration = _parseDuration(mediaInfo[Keys.duration]);
if (duration != null) {
fields['durationMillis'] = duration.inMilliseconds;
}
}
return fields;
}
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),
// 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
if (dateMillis != null && !DateTime.fromMillisecondsSinceEpoch(dateMillis).isAtSameDayAs(epoch)) {
return (entry.catalogMetadata ?? CatalogMetadata(id: entry.id)).copyWith(
catalogMetadata = catalogMetadata.copyWith(
dateMillis: dateMillis,
);
}
return entry.catalogMetadata;
return catalogMetadata;
}
static bool isAmbiguousDate(String dateString) {
@ -194,14 +196,21 @@ class VideoMetadataFormatter {
switch (key) {
case Keys.codecLevel:
case Keys.codecTag:
case Keys.codecTagString:
case Keys.durationTs:
case Keys.fpsNum:
case Keys.handlerName:
case Keys.index:
case Keys.isAvc:
case Keys.probeScore:
case Keys.programCount:
case Keys.refs:
case Keys.sarNum:
case Keys.selectedAudioStream:
case Keys.selectedTextStream:
case Keys.selectedVideoStream:
case Keys.statisticsTags:
case Keys.streamCount:
case Keys.streams:
case Keys.streamType:
case Keys.tbrNum:
@ -219,10 +228,14 @@ class VideoMetadataFormatter {
case Keys.bitrate:
case Keys.bps:
save('Bit Rate', _formatMetric(value, 'b/s'));
case Keys.bitsPerRawSample:
save('Bits Per Raw Sample', value);
case Keys.byteCount:
save('Size', _formatFilesize(value));
case Keys.channelLayout:
save('Channel Layout', _formatChannelLayout(value));
case Keys.chromaLocation:
save('Chroma Location', value);
case Keys.codecName:
if (value != 'none') {
save('Format', _formatCodecName(value));
@ -233,6 +246,18 @@ class VideoMetadataFormatter {
// user-friendly descriptions for related enums are defined in libavutil/pixfmt.h
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:
{
final profile = int.tryParse(value);
@ -242,9 +267,9 @@ class VideoMetadataFormatter {
case Codecs.h264:
case Codecs.hevc:
{
final levelString = info[Keys.codecLevel];
if (levelString != null) {
final level = int.tryParse(levelString) ?? 0;
final levelValue = info[Keys.codecLevel];
if (levelValue != null) {
final level = levelValue is int ? levelValue : int.tryParse(levelValue) ?? 0;
if (codec == Codecs.h264) {
profileString = H264.formatProfile(profile, level);
} else {
@ -268,6 +293,8 @@ class VideoMetadataFormatter {
save('Compatible Brands', formattedBrands);
case Keys.creationTime:
save('Creation Time', _formatDate(value));
case Keys.dar:
save('Display Aspect Ratio', value);
case Keys.date:
if (value is String && value != '0') {
final charCount = value.length;
@ -277,10 +304,18 @@ class VideoMetadataFormatter {
save('Duration', _formatDuration(value));
case Keys.durationMicros:
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:
save('Frame Rate', '${roundToPrecision(info[Keys.fpsNum] / info[Keys.fpsDen], decimals: 3).toString()} FPS');
case Keys.frameCount:
save('Frame Count', value);
case Keys.handlerName:
save('Handler Name', value);
case Keys.hasBFrames:
save('Has B-Frames', value);
case Keys.height:
save('Height', '$value pixels');
case Keys.language:
@ -295,6 +330,8 @@ class VideoMetadataFormatter {
save('Media Type', value);
case Keys.minorVersion:
if (value != '0') save('Minor Version', value);
case Keys.nalLengthSize:
save('NAL Length Size', _formatFilesize(value));
case Keys.quicktimeLocationAccuracyHorizontal:
save('QuickTime Location Horizontal Accuracy', value);
case Keys.quicktimeCreationDate:
@ -304,25 +341,41 @@ class VideoMetadataFormatter {
case Keys.quicktimeSoftware:
// redundant with `QuickTime Metadata` directory
break;
case Keys.rFrameRate:
save('R Frame Rate', value);
case Keys.rotate:
save('Rotation', '$value°');
case Keys.sampleFormat:
save('Sample Format', (value as String).toUpperCase());
case Keys.sampleRate:
save('Sample Rate', _formatMetric(value, 'Hz'));
case Keys.sar:
save('Sample Aspect Ratio', value);
case Keys.sarDen:
final sarNum = info[Keys.sarNum];
final sarDen = info[Keys.sarDen];
// skip common square pixels (1:1)
if (sarNum != sarDen) save('SAR', '$sarNum:$sarDen');
case Keys.segmentCount:
save('Segment Count', value);
case Keys.sourceOshash:
save('Source OSHash', value);
case Keys.startMicros:
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:
save('Stats Writing App', value);
case Keys.statisticsWritingDateUtc:
save('Stats Writing Date', _formatDate(value));
case Keys.timeBase:
save('Time Base', value);
case Keys.track:
if (value != '0') save('Track', value);
case Keys.vendorId:
save('Vendor ID', value);
case Keys.width:
save('Width', '$value pixels');
case Keys.xiaomiSlowMoment:
@ -340,7 +393,12 @@ class VideoMetadataFormatter {
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('_', ' ');
@ -352,28 +410,49 @@ class VideoMetadataFormatter {
return date.toIso8601String();
}
// input example: '00:00:05.408000000'
static String _formatDuration(String value) {
final match = _durationPattern.firstMatch(value);
// input example: '00:00:05.408000000' or '5.408000'
static Duration? _parseDuration(String? value) {
if (value == null) return null;
var match = _durationHmsmPattern.firstMatch(value);
if (match != null) {
final h = int.tryParse(match.group(1)!);
final m = int.tryParse(match.group(2)!);
final s = int.tryParse(match.group(3)!);
final millis = double.tryParse(match.group(4)!);
if (h != null && m != null && s != null && millis != null) {
return formatPreciseDuration(Duration(
return Duration(
hours: h,
minutes: m,
seconds: s,
milliseconds: (millis * 1000).toInt(),
));
);
}
}
return value;
match = _durationSmPattern.firstMatch(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;
}
static String _formatFilesize(String value) {
final size = int.tryParse(value);
// 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;
}

View file

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

View file

@ -1,9 +1,9 @@
import 'package:latlong2/latlong.dart';
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(36.451000, 28.223615),
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/view/view.dart';
import 'package:aves_model/aves_model.dart';
import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
@ -52,7 +51,6 @@ Future<void> _init() async {
await device.init();
await mobileServices.init();
await settings.init(monitorPlatformSettings: false);
FijkLog.setLevel(FijkLogLevel.Warn);
await reportService.init();
final analyzer = Analyzer();

View file

@ -3,13 +3,12 @@ import 'dart:collection';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:tuple/tuple.dart';
final ServicePolicy servicePolicy = ServicePolicy._private();
class ServicePolicy {
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 LinkedHashMap<Object, _Task> _runningQueue = LinkedHashMap();
@ -30,8 +29,8 @@ class ServicePolicy {
key ??= platformCall.hashCode;
final toResume = _paused.remove(key);
if (toResume != null) {
priority = toResume.item1;
task = toResume.item2 as _Task<T>;
priority = toResume.$1;
task = toResume.$2 as _Task<T>;
completer = task.completer;
} else {
completer = Completer<T>();
@ -56,8 +55,8 @@ class ServicePolicy {
Future<T>? resume<T>(Object key) {
final toResume = _paused.remove(key);
if (toResume != null) {
final priority = toResume.item1;
final task = toResume.item2 as _Task<T>;
final priority = toResume.$1;
final task = toResume.$2 as _Task<T>;
_getQueue(priority)[key] = task;
_pickNext();
return task.completer.future;
@ -97,7 +96,7 @@ class ServicePolicy {
}
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);

View file

@ -1,8 +1,7 @@
import 'package:aves/model/availability.dart';
import 'package:aves/model/db/db_metadata.dart';
import 'package:aves/model/db/db_metadata_sqflite.dart';
import 'package:aves/model/settings/store/store.dart';
import 'package:aves/model/settings/store/store_shared_pref.dart';
import 'package:aves/model/settings/store_shared_pref.dart';
import 'package:aves/services/app_service.dart';
import 'package:aves/services/device_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/storage_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_platform/aves_report_platform.dart';
import 'package:aves_services/aves_services.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:path/path.dart' as p;
@ -30,6 +33,8 @@ final SettingsStore settingsStore = SharedPrefSettingsStore();
final p.Context pContext = getIt<p.Context>();
final AvesAvailability availability = getIt<AvesAvailability>();
final MetadataDb metadataDb = getIt<MetadataDb>();
final AvesVideoControllerFactory videoControllerFactory = getIt<AvesVideoControllerFactory>();
final AvesVideoMetadataFetcher videoMetadataFetcher = getIt<AvesVideoMetadataFetcher>();
final AppService appService = getIt<AppService>();
final DeviceService deviceService = getIt<DeviceService>();
@ -50,6 +55,8 @@ void initPlatformServices() {
getIt.registerLazySingleton<p.Context>(p.Context.new);
getIt.registerLazySingleton<AvesAvailability>(LiveAvesAvailability.new);
getIt.registerLazySingleton<MetadataDb>(SqfliteMetadataDb.new);
getIt.registerLazySingleton<AvesVideoControllerFactory>(MpvVideoControllerFactory.new);
getIt.registerLazySingleton<AvesVideoMetadataFetcher>(FfmpegVideoMetadataFetcher.new);
getIt.registerLazySingleton<AppService>(PlatformAppService.new);
getIt.registerLazySingleton<DeviceService>(PlatformDeviceService.new);

View file

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

View file

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

View file

@ -8,6 +8,8 @@ import 'package:flutter/services.dart';
import 'package:streams_channel/streams_channel.dart';
abstract class StorageService {
Future<Map<String, int>> getDataUsage();
Future<Set<StorageVolume>> getStorageVolumes();
Future<String> getVaultRoot();
@ -45,6 +47,17 @@ class PlatformStorageService implements StorageService {
static const _platform = MethodChannel('deckers.thibault/aves/storage');
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
Future<Set<StorageVolume>> getStorageVolumes() async {
try {

View file

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

View file

@ -1,6 +1,6 @@
import 'package:flutter/foundation.dart';
class Durations {
class ADurations {
// Flutter animations (with margin)
static const popupMenuAnimation = Duration(milliseconds: 300 + 20); // ref `_kMenuDuration` used in `_PopupMenuRoute`
// 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 brightnessMax = Icons.brightness_high_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 date = Icons.calendar_today_outlined;
static const dateByDay = Icons.today_outlined;
@ -42,11 +42,11 @@ class AIcons {
static const mainStorage = Icons.smartphone_outlined;
static const mimeType = Icons.code_outlined;
static const opacity = Icons.opacity;
static const privacy = MdiIcons.shieldAccountOutline;
static final privacy = MdiIcons.shieldAccountOutline;
static const rating = Icons.star_border_outlined;
static const ratingFull = Icons.star;
static const ratingRejected = MdiIcons.starMinusOutline;
static const ratingUnrated = MdiIcons.starOffOutline;
static final ratingRejected = MdiIcons.starMinusOutline;
static final ratingUnrated = MdiIcons.starOffOutline;
static const raw = Icons.raw_on_outlined;
static const shooting = Icons.camera_outlined;
static const removableStorage = Icons.sd_storage_outlined;
@ -56,7 +56,7 @@ class AIcons {
static const size = Icons.data_usage_outlined;
static const text = Icons.format_quote_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 volumeMax = Icons.volume_up_outlined;
@ -79,35 +79,35 @@ class AIcons {
static const clear = Icons.clear_outlined;
static const clipboard = Icons.content_copy_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 debug = Icons.whatshot_outlined;
static const delete = Icons.delete_outlined;
static const edit = Icons.edit_outlined;
static const emptyBin = Icons.delete_sweep_outlined;
static const export = Icons.open_with_outlined;
static const fileExport = MdiIcons.fileExportOutline;
static const fileImport = MdiIcons.fileImportOutline;
static final fileExport = MdiIcons.fileExportOutline;
static final fileImport = MdiIcons.fileImportOutline;
static const flip = Icons.flip_outlined;
static const favourite = Icons.favorite_border;
static const favouriteActive = Icons.favorite;
static const filter = MdiIcons.filterOutline;
static const filterOff = MdiIcons.filterOffOutline;
static final filter = MdiIcons.filterOutline;
static final filterOff = MdiIcons.filterOffOutline;
static const geoBounds = Icons.public_outlined;
static const goUp = Icons.arrow_upward_outlined;
static const hide = Icons.visibility_off_outlined;
static const info = Icons.info_outlined;
static const layers = Icons.layers_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 unmute = Icons.volume_up_outlined;
static const name = Icons.abc_outlined;
static const newTier = Icons.fiber_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 unpin = MdiIcons.pinOffOutline;
static final unpin = MdiIcons.pinOffOutline;
static const play = Icons.play_arrow;
static const pause = Icons.pause;
static const print = Icons.print_outlined;
@ -123,10 +123,10 @@ class AIcons {
static const search = Icons.search_outlined;
static const select = Icons.select_all_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 show = Icons.visibility_outlined;
static const showFullscreen = MdiIcons.arrowExpand;
static final showFullscreen = MdiIcons.arrowExpand;
static const slideshow = Icons.slideshow_outlined;
static const speed = Icons.speed_outlined;
static const stats = Icons.donut_small_outlined;
@ -136,7 +136,7 @@ class AIcons {
static const streamText = Icons.closed_caption_outlined;
static const vaultLock = Icons.lock_outline;
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 view = Icons.grid_view_outlined;
static const viewerLock = Icons.lock_outline;
@ -176,6 +176,6 @@ class AIcons {
static const selected = Icons.check_circle_outline;
static const unselected = Icons.radio_button_unchecked;
static const github = MdiIcons.github;
static const legal = MdiIcons.scaleBalance;
static final github = MdiIcons.github;
static final legal = MdiIcons.scaleBalance;
}

View file

@ -1,8 +1,6 @@
import 'dart:math';
import 'dart:ui';
import 'package:tuple/tuple.dart';
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();
@ -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);
// cf https://en.wikipedia.org/wiki/Intersection_(geometry)#Two_line_segments
Offset? segmentIntersection(Tuple2<Offset, Offset> s1, Tuple2<Offset, Offset> s2) {
final x1 = s1.item1.dx;
final y1 = s1.item1.dy;
final x2 = s1.item2.dx;
final y2 = s1.item2.dy;
Offset? segmentIntersection((Offset, Offset) s1, (Offset, Offset) s2) {
final x1 = s1.$1.dx;
final y1 = s1.$1.dy;
final x2 = s1.$2.dx;
final y2 = s1.$2.dy;
final x3 = s2.item1.dx;
final y3 = s2.item1.dy;
final x4 = s2.item2.dx;
final y4 = s2.item2.dy;
final x3 = s2.$1.dx;
final y3 = s2.$1.dy;
final x4 = s2.$2.dx;
final y4 = s2.$2.dy;
final a1 = x2 - x1;
final b1 = -(x4 - x3);

View file

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

View file

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

View file

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

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