diff --git a/.flutter b/.flutter index f92f44110..efbf63d9c 160000 --- a/.flutter +++ b/.flutter @@ -1 +1 @@ -Subproject commit f92f44110e87bad5ff168335c36da6f6053036e6 +Subproject commit efbf63d9c66b9f6ec30e9ad4611189aa80003d31 diff --git a/CHANGELOG.md b/CHANGELOG.md index 02327bea3..4f2fd27f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [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 + ## [v1.8.9] - 2023-06-04 ### Changed diff --git a/android/app/build.gradle b/android/app/build.gradle index b48594695..9d9ca5a1c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -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"] ?: "", @@ -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') } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a8084c988..d5193fd09 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -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 --> + - + @@ -295,7 +297,8 @@ - + diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/StorageHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/StorageHandler.kt index 1dcc4bd32..ed5d4a2ad 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/StorageHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/StorageHandler.kt @@ -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>() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/SvgRegionFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/SvgRegionFetcher.kt index 43a7aa081..614a87f0e 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/SvgRegionFetcher.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/SvgRegionFetcher.kt @@ -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) } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt index c140996d2..56e40cc9d 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt @@ -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 + } } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffGlideModule.kt b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffGlideModule.kt index 074f06332..20ad40cb5 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffGlideModule.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffGlideModule.kt @@ -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) } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Mp4ParserHelper.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Mp4ParserHelper.kt index 8bb9e096c..75e74b7d7 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Mp4ParserHelper.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Mp4ParserHelper.kt @@ -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(isoFile.movieBox, UserDataBox.TYPE) - fields.putAll(extractBoxFields(userDataBox)) + if (userDataBox != null) { + fields.putAll(extractBoxFields(userDataBox)) + } } } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/Compat.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/Compat.kt index fb75f47ff..90a9b154f 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/Compat.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/Compat.kt @@ -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) } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/ContextUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/ContextUtils.kt index df443830d..30d79ebd3 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/ContextUtils.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/ContextUtils.kt @@ -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): 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)) { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/FlutterUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/FlutterUtils.kt index 5275e0ed3..d3c56d23c 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/FlutterUtils.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/FlutterUtils.kt @@ -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 { cont -> + suspendCoroutine { cont: Continuation -> Handler(mainLooper).post { r.run() cont.resume(true) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt index f1385bdd9..0675a30f6 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt @@ -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 } diff --git a/android/app/src/main/res/values-be/strings.xml b/android/app/src/main/res/values-be/strings.xml index a6b3daec9..5d51f2ea8 100644 --- a/android/app/src/main/res/values-be/strings.xml +++ b/android/app/src/main/res/values-be/strings.xml @@ -1,2 +1,12 @@ - \ No newline at end of file + + Сканаванне носьбітаў + Відэа + Шпалеры + Сканаванне носьбітаў + Aves + Фотарамка + Бяспечны рэжым + Пошук + Стоп + \ No newline at end of file diff --git a/android/app/src/main/res/values-es/strings.xml b/android/app/src/main/res/values-es/strings.xml index 9fbcf09b4..960d77cba 100644 --- a/android/app/src/main/res/values-es/strings.xml +++ b/android/app/src/main/res/values-es/strings.xml @@ -3,7 +3,7 @@ Aves Marco de foto Fondo de pantalla - Búsqueda + Buscar Vídeos Explorar medios Explorando medios diff --git a/android/app/src/main/res/values-kn/strings.xml b/android/app/src/main/res/values-kn/strings.xml new file mode 100644 index 000000000..49b7a9e0c --- /dev/null +++ b/android/app/src/main/res/values-kn/strings.xml @@ -0,0 +1,12 @@ + + + Aves + ಫೋಟೋ ಫ್ರೇಮ್ + ವಾಲ್ಪೇಪರ್ + ಸುರಕ್ಷಿತ ಮೋಡ್ + ವೀಡಿಯೊಗಳು + ಮೀಡಿಯಾ ಸ್ಕ್ಯಾನ್ + ಮೀಡಿಯಾ ಸ್ಕ್ಯಾನ್ ಮಾಡಲಾಗುತ್ತಿದೆ + ನಿಲ್ಲಿಸಿ + ಹುಡುಕಿ + \ No newline at end of file diff --git a/android/app/src/main/res/values-my/strings.xml b/android/app/src/main/res/values-my/strings.xml new file mode 100644 index 000000000..14f06b878 --- /dev/null +++ b/android/app/src/main/res/values-my/strings.xml @@ -0,0 +1,12 @@ + + + Aves + ရုပ်ပုံဘောင် + နောက်ခံ ရုပ်ပုံ + လုံခြုံရေးလုပ်ဆောင်ချက် + မီဒီယာ စကင် + ရှာရန် + ဗီဒီယိုများ + မီဒီယာ ကိုစကင်ဖတ်နေသည် + ရပ်ရန် + \ No newline at end of file diff --git a/android/app/src/main/res/values-sl/strings.xml b/android/app/src/main/res/values-sl/strings.xml new file mode 100644 index 000000000..e599b09c8 --- /dev/null +++ b/android/app/src/main/res/values-sl/strings.xml @@ -0,0 +1,12 @@ + + + Okvir za sliko + Aves + Varni način + Videoposnetki + Ozadje + Iskanje + Skeniram medijske datoteke + Ustavi + Sken za medijske datoteke + \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index c173b9fdb..fd0f3742e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -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") } diff --git a/fastlane/metadata/android/ar/full_description.txt b/fastlane/metadata/android/ar/full_description.txt index 6c92748f8..167ed3b35 100644 --- a/fastlane/metadata/android/ar/full_description.txt +++ b/fastlane/metadata/android/ar/full_description.txt @@ -1,4 +1,4 @@ -Aves can handle all sorts of images and videos, including your typical JPEGs and MP4s, but also more exotic things like multi-page TIFFs, SVGs, old AVIs and more! It scans your media collection to identify motion photos, panoramas (aka photo spheres), 360° videos, as well as GeoTIFF files. +أيفيس يمكنه التعامل مع جميع أنواع الصور ومقاطع الفيديو ، بما في ذلك ملفات JPEG و MP4 النموذجية ، ولكن أيضًا أشياء أكثر غرابة مثل ملفات TIFF و SVG و AVI القديمة متعددة الصفحات والمزيد! يقوم بمسح مجموعة الوسائط الخاصة بك لتحديد الصور المتحركة, الإستعراضات (المعروف أيضًا باسم الصور البانورامية), 360 درجة مقاطع الفيديو, إلى جانب GeoTIFF الملفات. Navigation and search is an important part of Aves. The goal is for users to easily flow from albums to photos to tags to maps, etc. diff --git a/fastlane/metadata/android/be/full_description.txt b/fastlane/metadata/android/be/full_description.txt index 6c92748f8..9f6558c1a 100644 --- a/fastlane/metadata/android/be/full_description.txt +++ b/fastlane/metadata/android/be/full_description.txt @@ -1,5 +1,5 @@ -Aves can handle all sorts of images and videos, including your typical JPEGs and MP4s, but also more exotic things like multi-page TIFFs, SVGs, old AVIs and more! It scans your media collection to identify motion photos, panoramas (aka photo spheres), 360° videos, as well as GeoTIFF files. +Aves можа апрацоўваць разнастайныя выявы і відэа, у тым ліку звычайныя файлы JPEG і MP4, а таксама больш экзатычныя рэчы, такія як шматстаронкавыя файлы TIFF, SVG, старыя файлы AVI і іншае! Ён скануе вашу калекцыю мультымедыя для ідэнтыфікацыі фотаздымкаў з рухам, панарам (ён жа панарам), 360° відэа, а таксама GeoTIFF файлы. -Navigation and search is an important part of Aves. The goal is for users to easily flow from albums to photos to tags to maps, etc. +Навігацыя і пошук з'яўляюцца важнай часткай Aves. Мэта складаецца ў тым, каб карыстальнікі лёгка пераходзілі ад альбомаў да фатаграфій да тэгаў да карт і г.д. -Aves integrates with Android (from KitKat to Android 13, including Android TV) with features such as widgets, app shortcuts, screen saver and global search handling. It also works as a media viewer and picker. \ No newline at end of file +Aves інтэгруецца з Android (ад KitKat да Android 13, уключаючы Android TV) з такімі функцыямі, як віджэты, ярлыкі праграм, застаўка і апрацоўка глабальнага пошуку. Ён таксама працуе як сродак прагляду і выбару мультымедыя. \ No newline at end of file diff --git a/fastlane/metadata/android/be/short_description.txt b/fastlane/metadata/android/be/short_description.txt index 8c9445bd5..8bab46b14 100644 --- a/fastlane/metadata/android/be/short_description.txt +++ b/fastlane/metadata/android/be/short_description.txt @@ -1 +1 @@ -Gallery and metadata explorer \ No newline at end of file +Галерэя і правадыр метададзеных \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/101.txt b/fastlane/metadata/android/en-US/changelogs/101.txt new file mode 100644 index 000000000..ac542c6e0 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/101.txt @@ -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 \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/10101.txt b/fastlane/metadata/android/en-US/changelogs/10101.txt new file mode 100644 index 000000000..ac542c6e0 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/10101.txt @@ -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 \ No newline at end of file diff --git a/fastlane/metadata/android/eu/full_description.txt b/fastlane/metadata/android/eu/full_description.txt index e615df16a..c64149f18 100644 --- a/fastlane/metadata/android/eu/full_description.txt +++ b/fastlane/metadata/android/eu/full_description.txt @@ -1,4 +1,4 @@ -Aves aplikazioak mota guztitako irudi eta bideoak, nahiz ohiko zure JPEG eta MP4 fitxategiak eta exotikoagoak diren orri ugaritako TIFF, SVG, AVI zaharrak eta are gehiago maneiatzen ditu! Zure media-bilduma eskaneatzen du mugimendu-argazkiak, panoramikak (argazki esferikoak bezala ere ezagunak), 360°-ko bideoak, baita GeoTIFF fitxategiak ere. +Aves aplikazioak mota guztitako irudi eta bideoak, nahiz zure ohiko JPEG eta MP4 fitxategiak, eta exotikoagoak diren orri ugaritako TIFF, SVG, AVI zaharrak eta are gehiago maneiatzen ditu! Zure media-bilduma eskaneatzen du mugimendu-argazkiak, panoramikak (argazki esferikoak bezala ere ezagunak), 360°-ko bideoak, baita GeoTIFF fitxategiak ere. Nabigazioa eta bilaketa Aves aplikazioaren zati garrantzitsu bat da. Helburua, erabiltzaileek albumetatik argazkietara, etiketetara, mapetara, etab. modu errazean mugi ahal izatea da. diff --git a/fastlane/metadata/android/kn/full_description.txt b/fastlane/metadata/android/kn/full_description.txt new file mode 100644 index 000000000..6c92748f8 --- /dev/null +++ b/fastlane/metadata/android/kn/full_description.txt @@ -0,0 +1,5 @@ +Aves can handle all sorts of images and videos, including your typical JPEGs and MP4s, but also more exotic things like multi-page TIFFs, SVGs, old AVIs and more! It scans your media collection to identify motion photos, panoramas (aka photo spheres), 360° videos, as well as GeoTIFF files. + +Navigation and search is an important part of Aves. The goal is for users to easily flow from albums to photos to tags to maps, etc. + +Aves integrates with Android (from KitKat to Android 13, including Android TV) with features such as widgets, app shortcuts, screen saver and global search handling. It also works as a media viewer and picker. \ No newline at end of file diff --git a/fastlane/metadata/android/kn/short_description.txt b/fastlane/metadata/android/kn/short_description.txt new file mode 100644 index 000000000..8c9445bd5 --- /dev/null +++ b/fastlane/metadata/android/kn/short_description.txt @@ -0,0 +1 @@ +Gallery and metadata explorer \ No newline at end of file diff --git a/fastlane/metadata/android/my/full_description.txt b/fastlane/metadata/android/my/full_description.txt new file mode 100644 index 000000000..6c92748f8 --- /dev/null +++ b/fastlane/metadata/android/my/full_description.txt @@ -0,0 +1,5 @@ +Aves can handle all sorts of images and videos, including your typical JPEGs and MP4s, but also more exotic things like multi-page TIFFs, SVGs, old AVIs and more! It scans your media collection to identify motion photos, panoramas (aka photo spheres), 360° videos, as well as GeoTIFF files. + +Navigation and search is an important part of Aves. The goal is for users to easily flow from albums to photos to tags to maps, etc. + +Aves integrates with Android (from KitKat to Android 13, including Android TV) with features such as widgets, app shortcuts, screen saver and global search handling. It also works as a media viewer and picker. \ No newline at end of file diff --git a/fastlane/metadata/android/my/short_description.txt b/fastlane/metadata/android/my/short_description.txt new file mode 100644 index 000000000..b63761e03 --- /dev/null +++ b/fastlane/metadata/android/my/short_description.txt @@ -0,0 +1 @@ +ဂယ်လာရီနဲ့metadataအက်ပ် \ No newline at end of file diff --git a/fastlane/metadata/android/sl/full_description.txt b/fastlane/metadata/android/sl/full_description.txt new file mode 100644 index 000000000..6c92748f8 --- /dev/null +++ b/fastlane/metadata/android/sl/full_description.txt @@ -0,0 +1,5 @@ +Aves can handle all sorts of images and videos, including your typical JPEGs and MP4s, but also more exotic things like multi-page TIFFs, SVGs, old AVIs and more! It scans your media collection to identify motion photos, panoramas (aka photo spheres), 360° videos, as well as GeoTIFF files. + +Navigation and search is an important part of Aves. The goal is for users to easily flow from albums to photos to tags to maps, etc. + +Aves integrates with Android (from KitKat to Android 13, including Android TV) with features such as widgets, app shortcuts, screen saver and global search handling. It also works as a media viewer and picker. \ No newline at end of file diff --git a/fastlane/metadata/android/sl/short_description.txt b/fastlane/metadata/android/sl/short_description.txt new file mode 100644 index 000000000..8c9445bd5 --- /dev/null +++ b/fastlane/metadata/android/sl/short_description.txt @@ -0,0 +1 @@ +Gallery and metadata explorer \ No newline at end of file diff --git a/lib/l10n/app_be.arb b/lib/l10n/app_be.arb index 00a103472..f1e73a26a 100644 --- a/lib/l10n/app_be.arb +++ b/lib/l10n/app_be.arb @@ -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": {} } diff --git a/lib/l10n/app_ckb.arb b/lib/l10n/app_ckb.arb index 82c467290..48b0bc2ed 100644 --- a/lib/l10n/app_ckb.arb +++ b/lib/l10n/app_ckb.arb @@ -1,5 +1,5 @@ { - "@@locale" : "ckb", + "@@locale": "ckb", "welcomeOptional": "ئارەزومەندانە", "@welcomeOptional": {}, "welcomeTermsToggle": "ڕازیم بە مەرج و یاساکانی بەکارهێنان", diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb index 1f52e0060..24fce93a7 100644 --- a/lib/l10n/app_cs.arb +++ b/lib/l10n/app_cs.arb @@ -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": {} } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 7512c6b81..48077018d 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -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": {} } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index bcd049bbd..aa4bd3f2a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -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.", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 98fca8db9..115b245af 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -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": {} } diff --git a/lib/l10n/app_eu.arb b/lib/l10n/app_eu.arb index 04972bb2e..d7f8d71f4 100644 --- a/lib/l10n/app_eu.arb +++ b/lib/l10n/app_eu.arb @@ -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": {} } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index a204ea360..a36d37b5c 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1334,5 +1334,27 @@ "cropAspectRatioOriginal": "Photo d’origine", "@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": {} } diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index 35f720b45..ac6d72b72 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -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": {} } diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index 16eddb018..985361de3 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -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": {} } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index fc004d70c..0089374c0 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -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 l’elemento in esso?} other{Cancellare questo album e i {count} elementi in esso?}}", + "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Eliminare questo album e l’elemento in esso?} other{Eliminare questo album e i {count} elementi in esso?}}", "@deleteSingleAlbumConfirmationDialogMessage": {}, - "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Cancellare questi album e l’elemento in essi?} other{Cancellare questi album e i {count} elementi in essi?}}", + "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Eliminare questi album e l’elemento 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": {} } diff --git a/lib/l10n/app_kn.arb b/lib/l10n/app_kn.arb new file mode 100644 index 000000000..feef36e0b --- /dev/null +++ b/lib/l10n/app_kn.arb @@ -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": {} +} diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index d3d75fe63..009a53bdf 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -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": {} } diff --git a/lib/l10n/app_my.arb b/lib/l10n/app_my.arb new file mode 100644 index 000000000..2d0975a33 --- /dev/null +++ b/lib/l10n/app_my.arb @@ -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": {} +} diff --git a/lib/l10n/app_nb.arb b/lib/l10n/app_nb.arb index b35b9e78a..447bbfa17 100644 --- a/lib/l10n/app_nb.arb +++ b/lib/l10n/app_nb.arb @@ -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": {} } diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 5a8cc2ea0..dbbc9cdc4 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -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": {} } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index e695fe3ec..c973733af 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -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": {} } diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 9d3de9f7b..e1a2c5446 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -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": {} } diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb new file mode 100644 index 000000000..79b155d05 --- /dev/null +++ b/lib/l10n/app_sl.arb @@ -0,0 +1,10 @@ +{ + "appName": "Aves", + "@appName": {}, + "welcomeMessage": "Dobrodošli v Avesu", + "@welcomeMessage": {}, + "welcomeOptional": "Izbirno", + "@welcomeOptional": {}, + "welcomeTermsToggle": "Sprejmem pogoje uporabe", + "@welcomeTermsToggle": {} +} diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index 72f8ad891..743185bac 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -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": {} } diff --git a/lib/l10n/app_zh_Hant.arb b/lib/l10n/app_zh_Hant.arb index c0a9bfa14..5699f2b92 100644 --- a/lib/l10n/app_zh_Hant.arb +++ b/lib/l10n/app_zh_Hant.arb @@ -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": {}, diff --git a/lib/model/app/contributors.dart b/lib/model/app/contributors.dart index d12d06d11..bcc2ecded 100644 --- a/lib/model/app/contributors.dart +++ b/lib/model/app/contributors.dart @@ -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 }; } diff --git a/lib/model/app/dependencies.dart b/lib/model/app/dependencies.dart index 794029ea8..1c72094d2 100644 --- a/lib/model/app/dependencies.dart +++ b/lib/model/app/dependencies.dart @@ -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', diff --git a/lib/model/covers.dart b/lib/model/covers.dart index b36dee1a9..340b8f8cc 100644 --- a/lib/model/covers.dart +++ b/lib/model/covers.dart @@ -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 get all => Set.unmodifiable(_rows); - Tuple3? 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 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); } diff --git a/lib/model/entry/entry.dart b/lib/model/entry/entry.dart index 13b1a5b5b..1c7075143 100644 --- a/lib/model/entry/entry.dart +++ b/lib/model/entry/entry.dart @@ -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; diff --git a/lib/model/entry/extensions/catalog.dart b/lib/model/entry/extensions/catalog.dart index 73ee03ac6..626d55e32 100644 --- a/lib/model/entry/extensions/catalog.dart +++ b/lib/model/entry/extensions/catalog.dart @@ -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) { diff --git a/lib/model/entry/extensions/info.dart b/lib/model/entry/extensions/info.dart index 04b064a1d..6aa21abd3 100644 --- a/lib/model/entry/extensions/info.dart +++ b/lib/model/entry/extensions/info.dart @@ -71,7 +71,7 @@ extension ExtraAvesEntryInfo on AvesEntry { Future> _getStreamDirectories(BuildContext context) async { final directories = []; - final mediaInfo = await VideoMetadataFormatter.getVideoMetadata(this); + final mediaInfo = await videoMetadataFetcher.getMetadata(this); final formattedMediaTags = VideoMetadataFormatter.formatInfo(mediaInfo); if (formattedMediaTags.isNotEmpty) { diff --git a/lib/model/entry/extensions/metadata_edition.dart b/lib/model/entry/extensions/metadata_edition.dart index c28c26d8d..e0ddc829f 100644 --- a/lib/model/entry/extensions/metadata_edition.dart +++ b/lib/model/entry/extensions/metadata_edition.dart @@ -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> editLocation(LatLng? latLng) async { final dataTypes = {}; diff --git a/lib/model/entry/extensions/props.dart b/lib/model/entry/extensions/props.dart index f5d064c2f..16e71676a 100644 --- a/lib/model/entry/extensions/props.dart +++ b/lib/model/entry/extensions/props.dart @@ -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; diff --git a/lib/model/filters/album.dart b/lib/model/filters/album.dart index 34e4ab626..d2126c938 100644 --- a/lib/model/filters/album.dart +++ b/lib/model/filters/album.dart @@ -64,7 +64,7 @@ class AlbumFilter extends CoveredCollectionFilter { @override Future 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(); diff --git a/lib/model/filters/filters.dart b/lib/model/filters/filters.dart index 212b6c92d..b6e08e2b9 100644 --- a/lib/model/filters/filters.dart +++ b/lib/model/filters/filters.dart @@ -157,7 +157,7 @@ abstract class CoveredCollectionFilter extends CollectionFilter { @override Future color(BuildContext context) { - final customColor = covers.of(this)?.item3; + final customColor = covers.of(this)?.$3; if (customColor != null) { return SynchronousFuture(customColor); } diff --git a/lib/model/filters/mime.dart b/lib/model/filters/mime.dart index 1cdc1f052..341780c53 100644 --- a/lib/model/filters/mime.dart +++ b/lib/model/filters/mime.dart @@ -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 diff --git a/lib/model/filters/missing.dart b/lib/model/filters/missing.dart index 9128d1eb1..9d83f82c3 100644 --- a/lib/model/filters/missing.dart +++ b/lib/model/filters/missing.dart @@ -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 diff --git a/lib/model/filters/placeholder.dart b/lib/model/filters/placeholder.dart index bbeab3bed..ea8fdb093 100644 --- a/lib/model/filters/placeholder.dart +++ b/lib/model/filters/placeholder.dart @@ -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 diff --git a/lib/model/filters/rating.dart b/lib/model/filters/rating.dart index 631d13765..c9bd56290 100644 --- a/lib/model/filters/rating.dart +++ b/lib/model/filters/rating.dart @@ -8,18 +8,34 @@ class RatingFilter extends CollectionFilter { static const type = 'rating'; final int rating; + final String op; late final EntryFilter _test; - @override - List 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 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 json) { return RatingFilter( json['rating'] ?? 0, + op: json['op'] ?? opEqual, reversed: json['reversed'] ?? false, ); } @@ -28,6 +44,7 @@ class RatingFilter extends CollectionFilter { Map 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', + }; } } diff --git a/lib/model/filters/type.dart b/lib/model/filters/type.dart index b94dcb6ac..a095887dc 100644 --- a/lib/model/filters/type.dart +++ b/lib/model/filters/type.dart @@ -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 diff --git a/lib/model/metadata/catalog.dart b/lib/model/metadata/catalog.dart index ee09e90b0..0d389eae2 100644 --- a/lib/model/metadata/catalog.dart +++ b/lib/model/metadata/catalog.dart @@ -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, diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart index 8c24ecf29..416364766 100644 --- a/lib/model/settings/defaults.dart +++ b/lib/model/settings/defaults.dart @@ -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; diff --git a/lib/model/settings/enums/accessibility_timeout.dart b/lib/model/settings/enums/accessibility_timeout.dart index 6c274a652..dddd22b39 100644 --- a/lib/model/settings/enums/accessibility_timeout.dart +++ b/lib/model/settings/enums/accessibility_timeout.dart @@ -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); diff --git a/lib/model/settings/modules/app.dart b/lib/model/settings/modules/app.dart new file mode 100644 index 000000000..a4b1f9f86 --- /dev/null +++ b/lib/model/settings/modules/app.dart @@ -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 _systemLocalesFallback = []; + + set systemLocalesFallback(List locales) => _systemLocalesFallback = locales; + + Locale? _appliedLocale; + + void resetAppliedLocale() => _appliedLocale = null; + + Locale get appliedLocale { + if (_appliedLocale == null) { + final _locale = locale; + final preferredLocales = []; + 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? get topEntryIds => getStringList(SettingKeys.topEntryIdsKey)?.map(int.tryParse).whereNotNull().toList(); + + set topEntryIds(List? newValue) => set(SettingKeys.topEntryIdsKey, newValue?.map((id) => id.toString()).whereNotNull().toList()); + + List get recentDestinationAlbums => getStringList(SettingKeys.recentDestinationAlbumsKey) ?? []; + + set recentDestinationAlbums(List newValue) => set(SettingKeys.recentDestinationAlbumsKey, newValue.take(_recentFilterHistoryMax).toList()); + + List get recentTags => (getStringList(SettingKeys.recentTagsKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toList(); + + set recentTags(List newValue) => set(SettingKeys.recentTagsKey, newValue.take(_recentFilterHistoryMax).map((filter) => filter.toJson()).toList()); +} diff --git a/lib/model/settings/modules/collection.dart b/lib/model/settings/modules/collection.dart new file mode 100644 index 000000000..cb973b78c --- /dev/null +++ b/lib/model/settings/modules/collection.dart @@ -0,0 +1,56 @@ +import 'package:aves/model/settings/defaults.dart'; +import 'package:aves_model/aves_model.dart'; + +mixin CollectionSettings on SettingsAccess { + List get collectionBurstPatterns => getStringList(SettingKeys.collectionBurstPatternsKey) ?? []; + + set collectionBurstPatterns(List 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 get collectionBrowsingQuickActions => getEnumListOrDefault(SettingKeys.collectionBrowsingQuickActionsKey, SettingsDefaults.collectionBrowsingQuickActions, EntrySetAction.values); + + set collectionBrowsingQuickActions(List newValue) => set(SettingKeys.collectionBrowsingQuickActionsKey, newValue.map((v) => v.toString()).toList()); + + List get collectionSelectionQuickActions => getEnumListOrDefault(SettingKeys.collectionSelectionQuickActionsKey, SettingsDefaults.collectionSelectionQuickActions, EntrySetAction.values); + + set collectionSelectionQuickActions(List 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); +} diff --git a/lib/model/settings/modules/display.dart b/lib/model/settings/modules/display.dart new file mode 100644 index 000000000..390dd8761 --- /dev/null +++ b/lib/model/settings/modules/display.dart @@ -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; +} diff --git a/lib/model/settings/modules/filter_grids.dart b/lib/model/settings/modules/filter_grids.dart new file mode 100644 index 000000000..4476349a2 --- /dev/null +++ b/lib/model/settings/modules/filter_grids.dart @@ -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 get pinnedFilters => (getStringList(SettingKeys.pinnedFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet(); + + set pinnedFilters(Set newValue) => set(SettingKeys.pinnedFiltersKey, newValue.map((filter) => filter.toJson()).toList()); + + Set get hiddenFilters => (getStringList(SettingKeys.hiddenFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet(); + + set hiddenFilters(Set newValue) => set(SettingKeys.hiddenFiltersKey, newValue.map((filter) => filter.toJson()).toList()); + + void changeFilterVisibility(Set 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); +} diff --git a/lib/model/settings/modules/info.dart b/lib/model/settings/modules/info.dart new file mode 100644 index 000000000..18e809455 --- /dev/null +++ b/lib/model/settings/modules/info.dart @@ -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()); +} diff --git a/lib/model/settings/modules/navigation.dart b/lib/model/settings/modules/navigation.dart new file mode 100644 index 000000000..a18610077 --- /dev/null +++ b/lib/model/settings/modules/navigation.dart @@ -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 get drawerTypeBookmarks => + (getStringList(SettingKeys.drawerTypeBookmarksKey))?.map((v) { + if (v.isEmpty) return null; + return CollectionFilter.fromJson(v); + }).toList() ?? + SettingsDefaults.drawerTypeBookmarks; + + set drawerTypeBookmarks(List newValue) => set(SettingKeys.drawerTypeBookmarksKey, newValue.map((filter) => filter?.toJson() ?? '').toList()); + + List? get drawerAlbumBookmarks => getStringList(SettingKeys.drawerAlbumBookmarksKey); + + set drawerAlbumBookmarks(List? newValue) => set(SettingKeys.drawerAlbumBookmarksKey, newValue); + + List get drawerPageBookmarks => getStringList(SettingKeys.drawerPageBookmarksKey) ?? SettingsDefaults.drawerPageBookmarks; + + set drawerPageBookmarks(List newValue) => set(SettingKeys.drawerPageBookmarksKey, newValue); +} diff --git a/lib/model/settings/modules/search.dart b/lib/model/settings/modules/search.dart new file mode 100644 index 000000000..a6dba9bee --- /dev/null +++ b/lib/model/settings/modules/search.dart @@ -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 get searchHistory => (getStringList(SettingKeys.searchHistoryKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toList(); + + set searchHistory(List newValue) => set(SettingKeys.searchHistoryKey, newValue.map((filter) => filter.toJson()).toList()); +} diff --git a/lib/model/settings/modules/viewer.dart b/lib/model/settings/modules/viewer.dart new file mode 100644 index 000000000..5d5762468 --- /dev/null +++ b/lib/model/settings/modules/viewer.dart @@ -0,0 +1,56 @@ +import 'package:aves/model/settings/defaults.dart'; +import 'package:aves_model/aves_model.dart'; + +mixin ViewerSettings on SettingsAccess { + List get viewerQuickActions => getEnumListOrDefault(SettingKeys.viewerQuickActionsKey, SettingsDefaults.viewerQuickActions, EntryAction.values); + + set viewerQuickActions(List 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()); +} diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index 966b545db..27956405a 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -9,10 +9,17 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/settings/defaults.dart'; import 'package:aves/model/settings/enums/map_style.dart'; +import 'package:aves/model/settings/modules/app.dart'; +import 'package:aves/model/settings/modules/collection.dart'; +import 'package:aves/model/settings/modules/display.dart'; +import 'package:aves/model/settings/modules/filter_grids.dart'; +import 'package:aves/model/settings/modules/info.dart'; +import 'package:aves/model/settings/modules/navigation.dart'; +import 'package:aves/model/settings/modules/search.dart'; +import 'package:aves/model/settings/modules/viewer.dart'; import 'package:aves/ref/bursts.dart'; import 'package:aves/services/accessibility_service.dart'; import 'package:aves/services/common/services.dart'; -import 'package:aves/widgets/aves_app.dart'; import 'package:aves/widgets/common/search/page.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart'; @@ -21,6 +28,7 @@ import 'package:aves/widgets/filter_grids/tags_page.dart'; import 'package:aves_map/aves_map.dart'; import 'package:aves_model/aves_model.dart'; import 'package:aves_utils/aves_utils.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:collection/collection.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/services.dart'; @@ -29,202 +37,28 @@ import 'package:latlong2/latlong.dart'; final Settings settings = Settings._private(); -class Settings extends ChangeNotifier { +class Settings with ChangeNotifier, SettingsAccess, AppSettings, DisplaySettings, NavigationSettings, SearchSettings, CollectionSettings, FilterGridsSettings, ViewerSettings, VideoSettings, SubtitlesSettings, InfoSettings { final List _subscriptions = []; final EventChannel _platformSettingsChangeChannel = const OptionalEventChannel('deckers.thibault/aves/settings_change'); final StreamController _updateStreamController = StreamController.broadcast(); + final StreamController _updateTileExtentStreamController = StreamController.broadcast(); + @override Stream get updateStream => _updateStreamController.stream; + Stream get updateTileExtentStream => _updateTileExtentStreamController.stream; + + @override + bool get initialized => store.initialized; + + @override + SettingsStore get store => settingsStore; + Settings._private(); - static const int _recentFilterHistoryMax = 10; - static const Set _internalKeys = { - hasAcceptedTermsKey, - catalogTimeZoneRawOffsetMillisKey, - searchHistoryKey, - platformAccelerometerRotationKey, - platformTransitionAnimationScaleKey, - topEntryIdsKey, - recentDestinationAlbumsKey, - recentTagsKey, - }; - static const _widgetKeyPrefix = 'widget_'; - - // app - static const hasAcceptedTermsKey = 'has_accepted_terms'; - static const canUseAnalysisServiceKey = 'can_use_analysis_service'; - static const isInstalledAppAccessAllowedKey = 'is_installed_app_access_allowed'; - static const isErrorReportingAllowedKey = 'is_crashlytics_enabled'; - static const localeKey = 'locale'; - static const catalogTimeZoneRawOffsetMillisKey = 'catalog_time_zone_raw_offset_millis'; - static const tileExtentPrefixKey = 'tile_extent_'; - static const tileLayoutPrefixKey = 'tile_layout_'; - static const entryRenamingPatternKey = 'entry_renaming_pattern'; - static const topEntryIdsKey = 'top_entry_ids'; - static const recentDestinationAlbumsKey = 'recent_destination_albums'; - static const recentTagsKey = 'recent_tags'; - - // display - static const displayRefreshRateModeKey = 'display_refresh_rate_mode'; - static const themeBrightnessKey = 'theme_brightness'; - static const themeColorModeKey = 'theme_color_mode'; - static const enableDynamicColorKey = 'dynamic_color'; - static const enableBlurEffectKey = 'enable_overlay_blur_effect'; - static const maxBrightnessKey = 'max_brightness'; - static const forceTvLayoutKey = 'force_tv_layout'; - - // navigation - static const mustBackTwiceToExitKey = 'must_back_twice_to_exit'; - static const keepScreenOnKey = 'keep_screen_on'; - static const homePageKey = 'home_page'; - static const enableBottomNavigationBarKey = 'show_bottom_navigation_bar'; - static const confirmCreateVaultKey = 'confirm_create_vault'; - static const confirmDeleteForeverKey = 'confirm_delete_forever'; - static const confirmMoveToBinKey = 'confirm_move_to_bin'; - static const confirmMoveUndatedItemsKey = 'confirm_move_undated_items'; - static const confirmAfterMoveToBinKey = 'confirm_after_move_to_bin'; - static const setMetadataDateBeforeFileOpKey = 'set_metadata_date_before_file_op'; - static const drawerTypeBookmarksKey = 'drawer_type_bookmarks'; - static const drawerAlbumBookmarksKey = 'drawer_album_bookmarks'; - static const drawerPageBookmarksKey = 'drawer_page_bookmarks'; - - // collection - static const collectionBurstPatternsKey = 'collection_burst_patterns'; - static const collectionGroupFactorKey = 'collection_group_factor'; - static const collectionSortFactorKey = 'collection_sort_factor'; - static const collectionSortReverseKey = 'collection_sort_reverse'; - static const collectionBrowsingQuickActionsKey = 'collection_browsing_quick_actions'; - static const collectionSelectionQuickActionsKey = 'collection_selection_quick_actions'; - static const showThumbnailFavouriteKey = 'show_thumbnail_favourite'; - static const thumbnailLocationIconKey = 'thumbnail_location_icon'; - static const thumbnailTagIconKey = 'thumbnail_tag_icon'; - static const showThumbnailMotionPhotoKey = 'show_thumbnail_motion_photo'; - static const showThumbnailRatingKey = 'show_thumbnail_rating'; - static const showThumbnailRawKey = 'show_thumbnail_raw'; - static const showThumbnailVideoDurationKey = 'show_thumbnail_video_duration'; - - // filter grids - static const albumGroupFactorKey = 'album_group_factor'; - static const albumSortFactorKey = 'album_sort_factor'; - static const countrySortFactorKey = 'country_sort_factor'; - static const stateSortFactorKey = 'state_sort_factor'; - static const placeSortFactorKey = 'place_sort_factor'; - static const tagSortFactorKey = 'tag_sort_factor'; - static const albumSortReverseKey = 'album_sort_reverse'; - static const countrySortReverseKey = 'country_sort_reverse'; - static const stateSortReverseKey = 'state_sort_reverse'; - static const placeSortReverseKey = 'place_sort_reverse'; - static const tagSortReverseKey = 'tag_sort_reverse'; - static const pinnedFiltersKey = 'pinned_filters'; - static const hiddenFiltersKey = 'hidden_filters'; - static const showAlbumPickQueryKey = 'show_album_pick_query'; - - // viewer - static const viewerQuickActionsKey = 'viewer_quick_actions'; - static const showOverlayOnOpeningKey = 'show_overlay_on_opening'; - static const showOverlayMinimapKey = 'show_overlay_minimap'; - static const showOverlayInfoKey = 'show_overlay_info'; - static const showOverlayDescriptionKey = 'show_overlay_description'; - static const showOverlayRatingTagsKey = 'show_overlay_rating_tags'; - static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details'; - static const showOverlayThumbnailPreviewKey = 'show_overlay_thumbnail_preview'; - static const viewerGestureSideTapNextKey = 'viewer_gesture_side_tap_next'; - static const viewerUseCutoutKey = 'viewer_use_cutout'; - static const enableMotionPhotoAutoPlayKey = 'motion_photo_auto_play'; - static const imageBackgroundKey = 'image_background'; - - // video - static const enableVideoHardwareAccelerationKey = 'video_hwaccel_mediacodec'; - static const videoBackgroundModeKey = 'video_background_mode'; - static const videoAutoPlayModeKey = 'video_auto_play_mode'; - static const videoLoopModeKey = 'video_loop'; - static const videoResumptionModeKey = 'video_resumption_mode'; - static const videoControlsKey = 'video_controls'; - static const videoGestureDoubleTapTogglePlayKey = 'video_gesture_double_tap_toggle_play'; - static const videoGestureSideDoubleTapSeekKey = 'video_gesture_side_double_tap_skip'; - static const videoGestureVerticalDragBrightnessVolumeKey = 'video_gesture_vertical_drag_brightness_volume'; - - // subtitles - static const subtitleFontSizeKey = 'subtitle_font_size'; - static const subtitleTextAlignmentKey = 'subtitle_text_alignment'; - static const subtitleTextPositionKey = 'subtitle_text_position'; - static const subtitleShowOutlineKey = 'subtitle_show_outline'; - static const subtitleTextColorKey = 'subtitle_text_color'; - static const subtitleBackgroundColorKey = 'subtitle_background_color'; - - // info - static const infoMapZoomKey = 'info_map_zoom'; - static const coordinateFormatKey = 'coordinates_format'; - static const unitSystemKey = 'unit_system'; - - // tag editor - - static const tagEditorCurrentFilterSectionExpandedKey = 'tag_editor_current_filter_section_expanded'; - static const tagEditorExpandedSectionKey = 'tag_editor_expanded_section'; - - // converter - - static const convertMimeTypeKey = 'convert_mime_type'; - static const convertQualityKey = 'convert_quality'; - static const convertWriteMetadataKey = 'convert_write_metadata'; - - // map - static const mapStyleKey = 'info_map_style'; - static const mapDefaultCenterKey = 'map_default_center'; - - // search - static const saveSearchHistoryKey = 'save_search_history'; - static const searchHistoryKey = 'search_history'; - - // bin - static const enableBinKey = 'enable_bin'; - - // accessibility - static const showPinchGestureAlternativesKey = 'show_pinch_gesture_alternatives'; - static const accessibilityAnimationsKey = 'accessibility_animations'; - static const timeToTakeActionKey = 'time_to_take_action'; - - // file picker - static const filePickerShowHiddenFilesKey = 'file_picker_show_hidden_files'; - - // screen saver - static const screenSaverFillScreenKey = 'screen_saver_fill_screen'; - static const screenSaverAnimatedZoomEffectKey = 'screen_saver_animated_zoom_effect'; - static const screenSaverTransitionKey = 'screen_saver_transition'; - static const screenSaverVideoPlaybackKey = 'screen_saver_video_playback'; - static const screenSaverIntervalKey = 'screen_saver_interval'; - static const screenSaverCollectionFiltersKey = 'screen_saver_collection_filters'; - - // slideshow - static const slideshowRepeatKey = 'slideshow_loop'; - static const slideshowShuffleKey = 'slideshow_shuffle'; - static const slideshowFillScreenKey = 'slideshow_fill_screen'; - static const slideshowAnimatedZoomEffectKey = 'slideshow_animated_zoom_effect'; - static const slideshowTransitionKey = 'slideshow_transition'; - static const slideshowVideoPlaybackKey = 'slideshow_video_playback'; - static const slideshowIntervalKey = 'slideshow_interval'; - - // widget - static const widgetOutlinePrefixKey = '${_widgetKeyPrefix}outline_'; - static const widgetShapePrefixKey = '${_widgetKeyPrefix}shape_'; - static const widgetCollectionFiltersPrefixKey = '${_widgetKeyPrefix}collection_filters_'; - static const widgetOpenPagePrefixKey = '${_widgetKeyPrefix}open_page_'; - static const widgetDisplayedItemPrefixKey = '${_widgetKeyPrefix}displayed_item_'; - static const widgetUriPrefixKey = '${_widgetKeyPrefix}uri_'; - - // platform settings - // cf Android `Settings.System.ACCELEROMETER_ROTATION` - static const platformAccelerometerRotationKey = 'accelerometer_rotation'; - - // cf Android `Settings.Global.TRANSITION_ANIMATION_SCALE` - static const platformTransitionAnimationScaleKey = 'transition_animation_scale'; - - bool get initialized => settingsStore.initialized; - Future init({required bool monitorPlatformSettings}) async { - await settingsStore.init(); - _appliedLocale = null; + await store.init(); + resetAppliedLocale(); if (monitorPlatformSettings) { _subscriptions ..forEach((sub) => sub.cancel()) @@ -233,18 +67,16 @@ class Settings extends ChangeNotifier { } } - Future reload() => settingsStore.reload(); + Future reload() => store.reload(); Future reset({required bool includeInternalKeys}) async { if (includeInternalKeys) { - await settingsStore.clear(); + await store.clear(); } else { - await Future.forEach(settingsStore.getKeys().whereNot(isInternalKey), settingsStore.remove); + await Future.forEach(store.getKeys().whereNot(SettingKeys.isInternalKey), store.remove); } } - bool isInternalKey(String key) => _internalKeys.contains(key) || key.startsWith(_widgetKeyPrefix); - Future setContextualDefaults(AppFlavor flavor) async { // performance final performanceClass = await deviceService.getPerformanceClass(); @@ -304,725 +136,181 @@ class Settings extends ChangeNotifier { Future sanitize() async { if (timeToTakeAction == AccessibilityTimeout.system && !await AccessibilityService.hasRecommendedTimeouts()) { - _set(timeToTakeActionKey, null); + set(SettingKeys.timeToTakeActionKey, null); } if (viewerUseCutout != SettingsDefaults.viewerUseCutout && !await windowService.isCutoutAware()) { - _set(viewerUseCutoutKey, null); + set(SettingKeys.viewerUseCutoutKey, null); } if (videoBackgroundMode == VideoBackgroundMode.pip && !device.supportPictureInPicture) { - _set(videoBackgroundModeKey, null); + set(SettingKeys.videoBackgroundModeKey, null); } collectionBurstPatterns = collectionBurstPatterns.where(BurstPatterns.options.contains).toList(); } - // app - - bool get hasAcceptedTerms => getBool(hasAcceptedTermsKey) ?? SettingsDefaults.hasAcceptedTerms; - - set hasAcceptedTerms(bool newValue) => _set(hasAcceptedTermsKey, newValue); - - bool get canUseAnalysisService => getBool(canUseAnalysisServiceKey) ?? SettingsDefaults.canUseAnalysisService; - - set canUseAnalysisService(bool newValue) => _set(canUseAnalysisServiceKey, newValue); - - bool get isInstalledAppAccessAllowed => getBool(isInstalledAppAccessAllowedKey) ?? SettingsDefaults.isInstalledAppAccessAllowed; - - set isInstalledAppAccessAllowed(bool newValue) => _set(isInstalledAppAccessAllowedKey, newValue); - - bool get isErrorReportingAllowed => getBool(isErrorReportingAllowedKey) ?? SettingsDefaults.isErrorReportingAllowed; - - set isErrorReportingAllowed(bool newValue) => _set(isErrorReportingAllowedKey, newValue); - - static const localeSeparator = '-'; - - Locale? get locale { - // exceptionally allow getting locale before settings are initialized - final tag = initialized ? getString(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(localeKey, tag); - _appliedLocale = null; - } - - List _systemLocalesFallback = []; - - set systemLocalesFallback(List locales) => _systemLocalesFallback = locales; - - Locale? _appliedLocale; - - Locale get appliedLocale { - if (_appliedLocale == null) { - final _locale = locale; - final preferredLocales = []; - 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(catalogTimeZoneRawOffsetMillisKey) ?? 0; - - set catalogTimeZoneRawOffsetMillis(int newValue) => _set(catalogTimeZoneRawOffsetMillisKey, newValue); - - double getTileExtent(String routeName) => getDouble(tileExtentPrefixKey + routeName) ?? 0; - - void setTileExtent(String routeName, double newValue) => _set(tileExtentPrefixKey + routeName, newValue); - - TileLayout getTileLayout(String routeName) => getEnumOrDefault(tileLayoutPrefixKey + routeName, SettingsDefaults.tileLayout, TileLayout.values); - - void setTileLayout(String routeName, TileLayout newValue) => _set(tileLayoutPrefixKey + routeName, newValue.toString()); - - String get entryRenamingPattern => getString(entryRenamingPatternKey) ?? SettingsDefaults.entryRenamingPattern; - - set entryRenamingPattern(String newValue) => _set(entryRenamingPatternKey, newValue); - - List? get topEntryIds => getStringList(topEntryIdsKey)?.map(int.tryParse).whereNotNull().toList(); - - set topEntryIds(List? newValue) => _set(topEntryIdsKey, newValue?.map((id) => id.toString()).whereNotNull().toList()); - - List get recentDestinationAlbums => getStringList(recentDestinationAlbumsKey) ?? []; - - set recentDestinationAlbums(List newValue) => _set(recentDestinationAlbumsKey, newValue.take(_recentFilterHistoryMax).toList()); - - List get recentTags => (getStringList(recentTagsKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toList(); - - set recentTags(List newValue) => _set(recentTagsKey, newValue.take(_recentFilterHistoryMax).map((filter) => filter.toJson()).toList()); - - // display - - DisplayRefreshRateMode get displayRefreshRateMode => getEnumOrDefault(displayRefreshRateModeKey, SettingsDefaults.displayRefreshRateMode, DisplayRefreshRateMode.values); - - set displayRefreshRateMode(DisplayRefreshRateMode newValue) => _set(displayRefreshRateModeKey, newValue.toString()); - - AvesThemeBrightness get themeBrightness => getEnumOrDefault(themeBrightnessKey, SettingsDefaults.themeBrightness, AvesThemeBrightness.values); - - set themeBrightness(AvesThemeBrightness newValue) => _set(themeBrightnessKey, newValue.toString()); - - AvesThemeColorMode get themeColorMode => getEnumOrDefault(themeColorModeKey, SettingsDefaults.themeColorMode, AvesThemeColorMode.values); - - set themeColorMode(AvesThemeColorMode newValue) => _set(themeColorModeKey, newValue.toString()); - - bool get enableDynamicColor => getBool(enableDynamicColorKey) ?? SettingsDefaults.enableDynamicColor; - - set enableDynamicColor(bool newValue) => _set(enableDynamicColorKey, newValue); - - bool get enableBlurEffect => getBool(enableBlurEffectKey) ?? SettingsDefaults.enableBlurEffect; - - set enableBlurEffect(bool newValue) => _set(enableBlurEffectKey, newValue); - - MaxBrightness get maxBrightness => getEnumOrDefault(maxBrightnessKey, SettingsDefaults.maxBrightness, MaxBrightness.values); - - set maxBrightness(MaxBrightness newValue) => _set(maxBrightnessKey, newValue.toString()); - - bool get forceTvLayout => getBool(forceTvLayoutKey) ?? SettingsDefaults.forceTvLayout; - - set forceTvLayout(bool newValue) => _set(forceTvLayoutKey, newValue); - - bool get useTvLayout => device.isTelevision || forceTvLayout; - - bool get isReadOnly => useTvLayout; - - // navigation - - bool get mustBackTwiceToExit => getBool(mustBackTwiceToExitKey) ?? SettingsDefaults.mustBackTwiceToExit; - - set mustBackTwiceToExit(bool newValue) => _set(mustBackTwiceToExitKey, newValue); - - KeepScreenOn get keepScreenOn => getEnumOrDefault(keepScreenOnKey, SettingsDefaults.keepScreenOn, KeepScreenOn.values); - - set keepScreenOn(KeepScreenOn newValue) => _set(keepScreenOnKey, newValue.toString()); - - HomePageSetting get homePage => getEnumOrDefault(homePageKey, SettingsDefaults.homePage, HomePageSetting.values); - - set homePage(HomePageSetting newValue) => _set(homePageKey, newValue.toString()); - - bool get enableBottomNavigationBar => getBool(enableBottomNavigationBarKey) ?? SettingsDefaults.enableBottomNavigationBar; - - set enableBottomNavigationBar(bool newValue) => _set(enableBottomNavigationBarKey, newValue); - - bool get confirmCreateVault => getBool(confirmCreateVaultKey) ?? SettingsDefaults.confirm; - - set confirmCreateVault(bool newValue) => _set(confirmCreateVaultKey, newValue); - - bool get confirmDeleteForever => getBool(confirmDeleteForeverKey) ?? SettingsDefaults.confirm; - - set confirmDeleteForever(bool newValue) => _set(confirmDeleteForeverKey, newValue); - - bool get confirmMoveToBin => getBool(confirmMoveToBinKey) ?? SettingsDefaults.confirm; - - set confirmMoveToBin(bool newValue) => _set(confirmMoveToBinKey, newValue); - - bool get confirmMoveUndatedItems => getBool(confirmMoveUndatedItemsKey) ?? SettingsDefaults.confirm; - - set confirmMoveUndatedItems(bool newValue) => _set(confirmMoveUndatedItemsKey, newValue); - - bool get confirmAfterMoveToBin => getBool(confirmAfterMoveToBinKey) ?? SettingsDefaults.confirm; - - set confirmAfterMoveToBin(bool newValue) => _set(confirmAfterMoveToBinKey, newValue); - - bool get setMetadataDateBeforeFileOp => getBool(setMetadataDateBeforeFileOpKey) ?? SettingsDefaults.setMetadataDateBeforeFileOp; - - set setMetadataDateBeforeFileOp(bool newValue) => _set(setMetadataDateBeforeFileOpKey, newValue); - - List get drawerTypeBookmarks => - (getStringList(drawerTypeBookmarksKey))?.map((v) { - if (v.isEmpty) return null; - return CollectionFilter.fromJson(v); - }).toList() ?? - SettingsDefaults.drawerTypeBookmarks; - - set drawerTypeBookmarks(List newValue) => _set(drawerTypeBookmarksKey, newValue.map((filter) => filter?.toJson() ?? '').toList()); - - List? get drawerAlbumBookmarks => getStringList(drawerAlbumBookmarksKey); - - set drawerAlbumBookmarks(List? newValue) => _set(drawerAlbumBookmarksKey, newValue); - - List get drawerPageBookmarks => getStringList(drawerPageBookmarksKey) ?? SettingsDefaults.drawerPageBookmarks; - - set drawerPageBookmarks(List newValue) => _set(drawerPageBookmarksKey, newValue); - - // collection - - List get collectionBurstPatterns => getStringList(collectionBurstPatternsKey) ?? []; - - set collectionBurstPatterns(List newValue) => _set(collectionBurstPatternsKey, newValue); - - EntryGroupFactor get collectionSectionFactor => getEnumOrDefault(collectionGroupFactorKey, SettingsDefaults.collectionSectionFactor, EntryGroupFactor.values); - - set collectionSectionFactor(EntryGroupFactor newValue) => _set(collectionGroupFactorKey, newValue.toString()); - - EntrySortFactor get collectionSortFactor => getEnumOrDefault(collectionSortFactorKey, SettingsDefaults.collectionSortFactor, EntrySortFactor.values); - - set collectionSortFactor(EntrySortFactor newValue) => _set(collectionSortFactorKey, newValue.toString()); - - bool get collectionSortReverse => getBool(collectionSortReverseKey) ?? false; - - set collectionSortReverse(bool newValue) => _set(collectionSortReverseKey, newValue); - - List get collectionBrowsingQuickActions => getEnumListOrDefault(collectionBrowsingQuickActionsKey, SettingsDefaults.collectionBrowsingQuickActions, EntrySetAction.values); - - set collectionBrowsingQuickActions(List newValue) => _set(collectionBrowsingQuickActionsKey, newValue.map((v) => v.toString()).toList()); - - List get collectionSelectionQuickActions => getEnumListOrDefault(collectionSelectionQuickActionsKey, SettingsDefaults.collectionSelectionQuickActions, EntrySetAction.values); - - set collectionSelectionQuickActions(List newValue) => _set(collectionSelectionQuickActionsKey, newValue.map((v) => v.toString()).toList()); - - bool get showThumbnailFavourite => getBool(showThumbnailFavouriteKey) ?? SettingsDefaults.showThumbnailFavourite; - - set showThumbnailFavourite(bool newValue) => _set(showThumbnailFavouriteKey, newValue); - - ThumbnailOverlayLocationIcon get thumbnailLocationIcon => getEnumOrDefault(thumbnailLocationIconKey, SettingsDefaults.thumbnailLocationIcon, ThumbnailOverlayLocationIcon.values); - - set thumbnailLocationIcon(ThumbnailOverlayLocationIcon newValue) => _set(thumbnailLocationIconKey, newValue.toString()); - - ThumbnailOverlayTagIcon get thumbnailTagIcon => getEnumOrDefault(thumbnailTagIconKey, SettingsDefaults.thumbnailTagIcon, ThumbnailOverlayTagIcon.values); - - set thumbnailTagIcon(ThumbnailOverlayTagIcon newValue) => _set(thumbnailTagIconKey, newValue.toString()); - - bool get showThumbnailMotionPhoto => getBool(showThumbnailMotionPhotoKey) ?? SettingsDefaults.showThumbnailMotionPhoto; - - set showThumbnailMotionPhoto(bool newValue) => _set(showThumbnailMotionPhotoKey, newValue); - - bool get showThumbnailRating => getBool(showThumbnailRatingKey) ?? SettingsDefaults.showThumbnailRating; - - set showThumbnailRating(bool newValue) => _set(showThumbnailRatingKey, newValue); - - bool get showThumbnailRaw => getBool(showThumbnailRawKey) ?? SettingsDefaults.showThumbnailRaw; - - set showThumbnailRaw(bool newValue) => _set(showThumbnailRawKey, newValue); - - bool get showThumbnailVideoDuration => getBool(showThumbnailVideoDurationKey) ?? SettingsDefaults.showThumbnailVideoDuration; - - set showThumbnailVideoDuration(bool newValue) => _set(showThumbnailVideoDurationKey, newValue); - - // filter grids - - AlbumChipGroupFactor get albumGroupFactor => getEnumOrDefault(albumGroupFactorKey, SettingsDefaults.albumGroupFactor, AlbumChipGroupFactor.values); - - set albumGroupFactor(AlbumChipGroupFactor newValue) => _set(albumGroupFactorKey, newValue.toString()); - - ChipSortFactor get albumSortFactor => getEnumOrDefault(albumSortFactorKey, SettingsDefaults.chipListSortFactor, ChipSortFactor.values); - - set albumSortFactor(ChipSortFactor newValue) => _set(albumSortFactorKey, newValue.toString()); - - ChipSortFactor get countrySortFactor => getEnumOrDefault(countrySortFactorKey, SettingsDefaults.chipListSortFactor, ChipSortFactor.values); - - set countrySortFactor(ChipSortFactor newValue) => _set(countrySortFactorKey, newValue.toString()); - - ChipSortFactor get stateSortFactor => getEnumOrDefault(stateSortFactorKey, SettingsDefaults.chipListSortFactor, ChipSortFactor.values); - - set stateSortFactor(ChipSortFactor newValue) => _set(stateSortFactorKey, newValue.toString()); - - ChipSortFactor get placeSortFactor => getEnumOrDefault(placeSortFactorKey, SettingsDefaults.chipListSortFactor, ChipSortFactor.values); - - set placeSortFactor(ChipSortFactor newValue) => _set(placeSortFactorKey, newValue.toString()); - - ChipSortFactor get tagSortFactor => getEnumOrDefault(tagSortFactorKey, SettingsDefaults.chipListSortFactor, ChipSortFactor.values); - - set tagSortFactor(ChipSortFactor newValue) => _set(tagSortFactorKey, newValue.toString()); - - bool get albumSortReverse => getBool(albumSortReverseKey) ?? false; - - set albumSortReverse(bool newValue) => _set(albumSortReverseKey, newValue); - - bool get countrySortReverse => getBool(countrySortReverseKey) ?? false; - - set countrySortReverse(bool newValue) => _set(countrySortReverseKey, newValue); - - bool get stateSortReverse => getBool(stateSortReverseKey) ?? false; - - set stateSortReverse(bool newValue) => _set(stateSortReverseKey, newValue); - - bool get placeSortReverse => getBool(placeSortReverseKey) ?? false; - - set placeSortReverse(bool newValue) => _set(placeSortReverseKey, newValue); - - bool get tagSortReverse => getBool(tagSortReverseKey) ?? false; - - set tagSortReverse(bool newValue) => _set(tagSortReverseKey, newValue); - - Set get pinnedFilters => (getStringList(pinnedFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet(); - - set pinnedFilters(Set newValue) => _set(pinnedFiltersKey, newValue.map((filter) => filter.toJson()).toList()); - - Set get hiddenFilters => (getStringList(hiddenFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet(); - - set hiddenFilters(Set newValue) => _set(hiddenFiltersKey, newValue.map((filter) => filter.toJson()).toList()); - - void changeFilterVisibility(Set 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(showAlbumPickQueryKey) ?? false; - - set showAlbumPickQuery(bool newValue) => _set(showAlbumPickQueryKey, newValue); - - // viewer - - List get viewerQuickActions => getEnumListOrDefault(viewerQuickActionsKey, SettingsDefaults.viewerQuickActions, EntryAction.values); - - set viewerQuickActions(List newValue) => _set(viewerQuickActionsKey, newValue.map((v) => v.toString()).toList()); - - bool get showOverlayOnOpening => getBool(showOverlayOnOpeningKey) ?? SettingsDefaults.showOverlayOnOpening; - - set showOverlayOnOpening(bool newValue) => _set(showOverlayOnOpeningKey, newValue); - - bool get showOverlayMinimap => getBool(showOverlayMinimapKey) ?? SettingsDefaults.showOverlayMinimap; - - set showOverlayMinimap(bool newValue) => _set(showOverlayMinimapKey, newValue); - - bool get showOverlayInfo => getBool(showOverlayInfoKey) ?? SettingsDefaults.showOverlayInfo; - - set showOverlayInfo(bool newValue) => _set(showOverlayInfoKey, newValue); - - bool get showOverlayDescription => getBool(showOverlayDescriptionKey) ?? SettingsDefaults.showOverlayDescription; - - set showOverlayDescription(bool newValue) => _set(showOverlayDescriptionKey, newValue); - - bool get showOverlayRatingTags => getBool(showOverlayRatingTagsKey) ?? SettingsDefaults.showOverlayRatingTags; - - set showOverlayRatingTags(bool newValue) => _set(showOverlayRatingTagsKey, newValue); - - bool get showOverlayShootingDetails => getBool(showOverlayShootingDetailsKey) ?? SettingsDefaults.showOverlayShootingDetails; - - set showOverlayShootingDetails(bool newValue) => _set(showOverlayShootingDetailsKey, newValue); - - bool get showOverlayThumbnailPreview => getBool(showOverlayThumbnailPreviewKey) ?? SettingsDefaults.showOverlayThumbnailPreview; - - set showOverlayThumbnailPreview(bool newValue) => _set(showOverlayThumbnailPreviewKey, newValue); - - bool get viewerGestureSideTapNext => getBool(viewerGestureSideTapNextKey) ?? SettingsDefaults.viewerGestureSideTapNext; - - set viewerGestureSideTapNext(bool newValue) => _set(viewerGestureSideTapNextKey, newValue); - - bool get viewerUseCutout => getBool(viewerUseCutoutKey) ?? SettingsDefaults.viewerUseCutout; - - set viewerUseCutout(bool newValue) => _set(viewerUseCutoutKey, newValue); - - bool get enableMotionPhotoAutoPlay => getBool(enableMotionPhotoAutoPlayKey) ?? SettingsDefaults.enableMotionPhotoAutoPlay; - - set enableMotionPhotoAutoPlay(bool newValue) => _set(enableMotionPhotoAutoPlayKey, newValue); - - EntryBackground get imageBackground => getEnumOrDefault(imageBackgroundKey, SettingsDefaults.imageBackground, EntryBackground.values); - - set imageBackground(EntryBackground newValue) => _set(imageBackgroundKey, newValue.toString()); - - // video - - bool get enableVideoHardwareAcceleration => getBool(enableVideoHardwareAccelerationKey) ?? SettingsDefaults.enableVideoHardwareAcceleration; - - set enableVideoHardwareAcceleration(bool newValue) => _set(enableVideoHardwareAccelerationKey, newValue); - - VideoAutoPlayMode get videoAutoPlayMode => getEnumOrDefault(videoAutoPlayModeKey, SettingsDefaults.videoAutoPlayMode, VideoAutoPlayMode.values); - - set videoAutoPlayMode(VideoAutoPlayMode newValue) => _set(videoAutoPlayModeKey, newValue.toString()); - - VideoBackgroundMode get videoBackgroundMode => getEnumOrDefault(videoBackgroundModeKey, SettingsDefaults.videoBackgroundMode, VideoBackgroundMode.values); - - set videoBackgroundMode(VideoBackgroundMode newValue) => _set(videoBackgroundModeKey, newValue.toString()); - - VideoLoopMode get videoLoopMode => getEnumOrDefault(videoLoopModeKey, SettingsDefaults.videoLoopMode, VideoLoopMode.values); - - set videoLoopMode(VideoLoopMode newValue) => _set(videoLoopModeKey, newValue.toString()); - - VideoResumptionMode get videoResumptionMode => getEnumOrDefault(videoResumptionModeKey, SettingsDefaults.videoResumptionMode, VideoResumptionMode.values); - - set videoResumptionMode(VideoResumptionMode newValue) => _set(videoResumptionModeKey, newValue.toString()); - - VideoControls get videoControls => getEnumOrDefault(videoControlsKey, SettingsDefaults.videoControls, VideoControls.values); - - set videoControls(VideoControls newValue) => _set(videoControlsKey, newValue.toString()); - - bool get videoGestureDoubleTapTogglePlay => getBool(videoGestureDoubleTapTogglePlayKey) ?? SettingsDefaults.videoGestureDoubleTapTogglePlay; - - set videoGestureDoubleTapTogglePlay(bool newValue) => _set(videoGestureDoubleTapTogglePlayKey, newValue); - - bool get videoGestureSideDoubleTapSeek => getBool(videoGestureSideDoubleTapSeekKey) ?? SettingsDefaults.videoGestureSideDoubleTapSeek; - - set videoGestureSideDoubleTapSeek(bool newValue) => _set(videoGestureSideDoubleTapSeekKey, newValue); - - bool get videoGestureVerticalDragBrightnessVolume => getBool(videoGestureVerticalDragBrightnessVolumeKey) ?? SettingsDefaults.videoGestureVerticalDragBrightnessVolume; - - set videoGestureVerticalDragBrightnessVolume(bool newValue) => _set(videoGestureVerticalDragBrightnessVolumeKey, newValue); - - // subtitles - - double get subtitleFontSize => getDouble(subtitleFontSizeKey) ?? SettingsDefaults.subtitleFontSize; - - set subtitleFontSize(double newValue) => _set(subtitleFontSizeKey, newValue); - - TextAlign get subtitleTextAlignment => getEnumOrDefault(subtitleTextAlignmentKey, SettingsDefaults.subtitleTextAlignment, TextAlign.values); - - set subtitleTextAlignment(TextAlign newValue) => _set(subtitleTextAlignmentKey, newValue.toString()); - - SubtitlePosition get subtitleTextPosition => getEnumOrDefault(subtitleTextPositionKey, SettingsDefaults.subtitleTextPosition, SubtitlePosition.values); - - set subtitleTextPosition(SubtitlePosition newValue) => _set(subtitleTextPositionKey, newValue.toString()); - - bool get subtitleShowOutline => getBool(subtitleShowOutlineKey) ?? SettingsDefaults.subtitleShowOutline; - - set subtitleShowOutline(bool newValue) => _set(subtitleShowOutlineKey, newValue); - - Color get subtitleTextColor => Color(getInt(subtitleTextColorKey) ?? SettingsDefaults.subtitleTextColor.value); - - set subtitleTextColor(Color newValue) => _set(subtitleTextColorKey, newValue.value); - - Color get subtitleBackgroundColor => Color(getInt(subtitleBackgroundColorKey) ?? SettingsDefaults.subtitleBackgroundColor.value); - - set subtitleBackgroundColor(Color newValue) => _set(subtitleBackgroundColorKey, newValue.value); - - // info - - double get infoMapZoom => getDouble(infoMapZoomKey) ?? SettingsDefaults.infoMapZoom; - - set infoMapZoom(double newValue) => _set(infoMapZoomKey, newValue); - - CoordinateFormat get coordinateFormat => getEnumOrDefault(coordinateFormatKey, SettingsDefaults.coordinateFormat, CoordinateFormat.values); - - set coordinateFormat(CoordinateFormat newValue) => _set(coordinateFormatKey, newValue.toString()); - - UnitSystem get unitSystem => getEnumOrDefault(unitSystemKey, SettingsDefaults.unitSystem, UnitSystem.values); - - set unitSystem(UnitSystem newValue) => _set(unitSystemKey, newValue.toString()); - // tag editor - bool get tagEditorCurrentFilterSectionExpanded => getBool(tagEditorCurrentFilterSectionExpandedKey) ?? SettingsDefaults.tagEditorCurrentFilterSectionExpanded; + bool get tagEditorCurrentFilterSectionExpanded => getBool(SettingKeys.tagEditorCurrentFilterSectionExpandedKey) ?? SettingsDefaults.tagEditorCurrentFilterSectionExpanded; - set tagEditorCurrentFilterSectionExpanded(bool newValue) => _set(tagEditorCurrentFilterSectionExpandedKey, newValue); + set tagEditorCurrentFilterSectionExpanded(bool newValue) => set(SettingKeys.tagEditorCurrentFilterSectionExpandedKey, newValue); - String? get tagEditorExpandedSection => getString(tagEditorExpandedSectionKey); + String? get tagEditorExpandedSection => getString(SettingKeys.tagEditorExpandedSectionKey); - set tagEditorExpandedSection(String? newValue) => _set(tagEditorExpandedSectionKey, newValue); + set tagEditorExpandedSection(String? newValue) => set(SettingKeys.tagEditorExpandedSectionKey, newValue); // converter - String get convertMimeType => getString(convertMimeTypeKey) ?? SettingsDefaults.convertMimeType; + String get convertMimeType => getString(SettingKeys.convertMimeTypeKey) ?? SettingsDefaults.convertMimeType; - set convertMimeType(String newValue) => _set(convertMimeTypeKey, newValue); + set convertMimeType(String newValue) => set(SettingKeys.convertMimeTypeKey, newValue); - int get convertQuality => getInt(convertQualityKey) ?? SettingsDefaults.convertQuality; + int get convertQuality => getInt(SettingKeys.convertQualityKey) ?? SettingsDefaults.convertQuality; - set convertQuality(int newValue) => _set(convertQualityKey, newValue); + set convertQuality(int newValue) => set(SettingKeys.convertQualityKey, newValue); - bool get convertWriteMetadata => getBool(convertWriteMetadataKey) ?? SettingsDefaults.convertWriteMetadata; + bool get convertWriteMetadata => getBool(SettingKeys.convertWriteMetadataKey) ?? SettingsDefaults.convertWriteMetadata; - set convertWriteMetadata(bool newValue) => _set(convertWriteMetadataKey, newValue); + set convertWriteMetadata(bool newValue) => set(SettingKeys.convertWriteMetadataKey, newValue); // map EntryMapStyle? get mapStyle { - final preferred = getEnumOrDefault(mapStyleKey, null, EntryMapStyle.values); + final preferred = getEnumOrDefault(SettingKeys.mapStyleKey, null, EntryMapStyle.values); if (preferred == null) return null; final available = availability.mapStyles; return available.contains(preferred) ? preferred : available.first; } - set mapStyle(EntryMapStyle? newValue) => _set(mapStyleKey, newValue?.toString()); + set mapStyle(EntryMapStyle? newValue) => set(SettingKeys.mapStyleKey, newValue?.toString()); LatLng? get mapDefaultCenter { - final json = getString(mapDefaultCenterKey); + final json = getString(SettingKeys.mapDefaultCenterKey); return json != null ? LatLng.fromJson(jsonDecode(json)) : null; } - set mapDefaultCenter(LatLng? newValue) => _set(mapDefaultCenterKey, newValue != null ? jsonEncode(newValue.toJson()) : null); - - // search - - bool get saveSearchHistory => getBool(saveSearchHistoryKey) ?? SettingsDefaults.saveSearchHistory; - - set saveSearchHistory(bool newValue) => _set(saveSearchHistoryKey, newValue); - - List get searchHistory => (getStringList(searchHistoryKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toList(); - - set searchHistory(List newValue) => _set(searchHistoryKey, newValue.map((filter) => filter.toJson()).toList()); + set mapDefaultCenter(LatLng? newValue) => set(SettingKeys.mapDefaultCenterKey, newValue != null ? jsonEncode(newValue.toJson()) : null); // bin - bool get enableBin => getBool(enableBinKey) ?? SettingsDefaults.enableBin; + bool get enableBin => getBool(SettingKeys.enableBinKey) ?? SettingsDefaults.enableBin; - set enableBin(bool newValue) => _set(enableBinKey, newValue); + set enableBin(bool newValue) => set(SettingKeys.enableBinKey, newValue); // accessibility - bool get showPinchGestureAlternatives => getBool(showPinchGestureAlternativesKey) ?? SettingsDefaults.showPinchGestureAlternatives; + bool get showPinchGestureAlternatives => getBool(SettingKeys.showPinchGestureAlternativesKey) ?? SettingsDefaults.showPinchGestureAlternatives; - set showPinchGestureAlternatives(bool newValue) => _set(showPinchGestureAlternativesKey, newValue); + set showPinchGestureAlternatives(bool newValue) => set(SettingKeys.showPinchGestureAlternativesKey, newValue); - AccessibilityAnimations get accessibilityAnimations => getEnumOrDefault(accessibilityAnimationsKey, SettingsDefaults.accessibilityAnimations, AccessibilityAnimations.values); + AccessibilityAnimations get accessibilityAnimations => getEnumOrDefault(SettingKeys.accessibilityAnimationsKey, SettingsDefaults.accessibilityAnimations, AccessibilityAnimations.values); - set accessibilityAnimations(AccessibilityAnimations newValue) => _set(accessibilityAnimationsKey, newValue.toString()); + set accessibilityAnimations(AccessibilityAnimations newValue) => set(SettingKeys.accessibilityAnimationsKey, newValue.toString()); - AccessibilityTimeout get timeToTakeAction => getEnumOrDefault(timeToTakeActionKey, SettingsDefaults.timeToTakeAction, AccessibilityTimeout.values); + AccessibilityTimeout get timeToTakeAction => getEnumOrDefault(SettingKeys.timeToTakeActionKey, SettingsDefaults.timeToTakeAction, AccessibilityTimeout.values); - set timeToTakeAction(AccessibilityTimeout newValue) => _set(timeToTakeActionKey, newValue.toString()); + set timeToTakeAction(AccessibilityTimeout newValue) => set(SettingKeys.timeToTakeActionKey, newValue.toString()); // file picker - bool get filePickerShowHiddenFiles => getBool(filePickerShowHiddenFilesKey) ?? SettingsDefaults.filePickerShowHiddenFiles; + bool get filePickerShowHiddenFiles => getBool(SettingKeys.filePickerShowHiddenFilesKey) ?? SettingsDefaults.filePickerShowHiddenFiles; - set filePickerShowHiddenFiles(bool newValue) => _set(filePickerShowHiddenFilesKey, newValue); + set filePickerShowHiddenFiles(bool newValue) => set(SettingKeys.filePickerShowHiddenFilesKey, newValue); // screen saver - bool get screenSaverFillScreen => getBool(screenSaverFillScreenKey) ?? SettingsDefaults.slideshowFillScreen; + bool get screenSaverFillScreen => getBool(SettingKeys.screenSaverFillScreenKey) ?? SettingsDefaults.slideshowFillScreen; - set screenSaverFillScreen(bool newValue) => _set(screenSaverFillScreenKey, newValue); + set screenSaverFillScreen(bool newValue) => set(SettingKeys.screenSaverFillScreenKey, newValue); - bool get screenSaverAnimatedZoomEffect => getBool(screenSaverAnimatedZoomEffectKey) ?? SettingsDefaults.slideshowAnimatedZoomEffect; + bool get screenSaverAnimatedZoomEffect => getBool(SettingKeys.screenSaverAnimatedZoomEffectKey) ?? SettingsDefaults.slideshowAnimatedZoomEffect; - set screenSaverAnimatedZoomEffect(bool newValue) => _set(screenSaverAnimatedZoomEffectKey, newValue); + set screenSaverAnimatedZoomEffect(bool newValue) => set(SettingKeys.screenSaverAnimatedZoomEffectKey, newValue); - ViewerTransition get screenSaverTransition => getEnumOrDefault(screenSaverTransitionKey, SettingsDefaults.slideshowTransition, ViewerTransition.values); + ViewerTransition get screenSaverTransition => getEnumOrDefault(SettingKeys.screenSaverTransitionKey, SettingsDefaults.slideshowTransition, ViewerTransition.values); - set screenSaverTransition(ViewerTransition newValue) => _set(screenSaverTransitionKey, newValue.toString()); + set screenSaverTransition(ViewerTransition newValue) => set(SettingKeys.screenSaverTransitionKey, newValue.toString()); - SlideshowVideoPlayback get screenSaverVideoPlayback => getEnumOrDefault(screenSaverVideoPlaybackKey, SettingsDefaults.slideshowVideoPlayback, SlideshowVideoPlayback.values); + SlideshowVideoPlayback get screenSaverVideoPlayback => getEnumOrDefault(SettingKeys.screenSaverVideoPlaybackKey, SettingsDefaults.slideshowVideoPlayback, SlideshowVideoPlayback.values); - set screenSaverVideoPlayback(SlideshowVideoPlayback newValue) => _set(screenSaverVideoPlaybackKey, newValue.toString()); + set screenSaverVideoPlayback(SlideshowVideoPlayback newValue) => set(SettingKeys.screenSaverVideoPlaybackKey, newValue.toString()); - int get screenSaverInterval => getInt(screenSaverIntervalKey) ?? SettingsDefaults.slideshowInterval; + int get screenSaverInterval => getInt(SettingKeys.screenSaverIntervalKey) ?? SettingsDefaults.slideshowInterval; - set screenSaverInterval(int newValue) => _set(screenSaverIntervalKey, newValue); + set screenSaverInterval(int newValue) => set(SettingKeys.screenSaverIntervalKey, newValue); - Set get screenSaverCollectionFilters => (getStringList(screenSaverCollectionFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet(); + Set get screenSaverCollectionFilters => (getStringList(SettingKeys.screenSaverCollectionFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet(); - set screenSaverCollectionFilters(Set newValue) => _set(screenSaverCollectionFiltersKey, newValue.map((filter) => filter.toJson()).toList()); + set screenSaverCollectionFilters(Set newValue) => set(SettingKeys.screenSaverCollectionFiltersKey, newValue.map((filter) => filter.toJson()).toList()); // slideshow - bool get slideshowRepeat => getBool(slideshowRepeatKey) ?? SettingsDefaults.slideshowRepeat; + bool get slideshowRepeat => getBool(SettingKeys.slideshowRepeatKey) ?? SettingsDefaults.slideshowRepeat; - set slideshowRepeat(bool newValue) => _set(slideshowRepeatKey, newValue); + set slideshowRepeat(bool newValue) => set(SettingKeys.slideshowRepeatKey, newValue); - bool get slideshowShuffle => getBool(slideshowShuffleKey) ?? SettingsDefaults.slideshowShuffle; + bool get slideshowShuffle => getBool(SettingKeys.slideshowShuffleKey) ?? SettingsDefaults.slideshowShuffle; - set slideshowShuffle(bool newValue) => _set(slideshowShuffleKey, newValue); + set slideshowShuffle(bool newValue) => set(SettingKeys.slideshowShuffleKey, newValue); - bool get slideshowFillScreen => getBool(slideshowFillScreenKey) ?? SettingsDefaults.slideshowFillScreen; + bool get slideshowFillScreen => getBool(SettingKeys.slideshowFillScreenKey) ?? SettingsDefaults.slideshowFillScreen; - set slideshowFillScreen(bool newValue) => _set(slideshowFillScreenKey, newValue); + set slideshowFillScreen(bool newValue) => set(SettingKeys.slideshowFillScreenKey, newValue); - bool get slideshowAnimatedZoomEffect => getBool(slideshowAnimatedZoomEffectKey) ?? SettingsDefaults.slideshowAnimatedZoomEffect; + bool get slideshowAnimatedZoomEffect => getBool(SettingKeys.slideshowAnimatedZoomEffectKey) ?? SettingsDefaults.slideshowAnimatedZoomEffect; - set slideshowAnimatedZoomEffect(bool newValue) => _set(slideshowAnimatedZoomEffectKey, newValue); + set slideshowAnimatedZoomEffect(bool newValue) => set(SettingKeys.slideshowAnimatedZoomEffectKey, newValue); - ViewerTransition get slideshowTransition => getEnumOrDefault(slideshowTransitionKey, SettingsDefaults.slideshowTransition, ViewerTransition.values); + ViewerTransition get slideshowTransition => getEnumOrDefault(SettingKeys.slideshowTransitionKey, SettingsDefaults.slideshowTransition, ViewerTransition.values); - set slideshowTransition(ViewerTransition newValue) => _set(slideshowTransitionKey, newValue.toString()); + set slideshowTransition(ViewerTransition newValue) => set(SettingKeys.slideshowTransitionKey, newValue.toString()); - SlideshowVideoPlayback get slideshowVideoPlayback => getEnumOrDefault(slideshowVideoPlaybackKey, SettingsDefaults.slideshowVideoPlayback, SlideshowVideoPlayback.values); + SlideshowVideoPlayback get slideshowVideoPlayback => getEnumOrDefault(SettingKeys.slideshowVideoPlaybackKey, SettingsDefaults.slideshowVideoPlayback, SlideshowVideoPlayback.values); - set slideshowVideoPlayback(SlideshowVideoPlayback newValue) => _set(slideshowVideoPlaybackKey, newValue.toString()); + set slideshowVideoPlayback(SlideshowVideoPlayback newValue) => set(SettingKeys.slideshowVideoPlaybackKey, newValue.toString()); - int get slideshowInterval => getInt(slideshowIntervalKey) ?? SettingsDefaults.slideshowInterval; + int get slideshowInterval => getInt(SettingKeys.slideshowIntervalKey) ?? SettingsDefaults.slideshowInterval; - set slideshowInterval(int newValue) => _set(slideshowIntervalKey, newValue); + set slideshowInterval(int newValue) => set(SettingKeys.slideshowIntervalKey, newValue); // widget Color? getWidgetOutline(int widgetId) { - final value = getInt('$widgetOutlinePrefixKey$widgetId'); + final value = getInt('${SettingKeys.widgetOutlinePrefixKey}$widgetId'); return value != null ? Color(value) : null; } - void setWidgetOutline(int widgetId, Color? newValue) => _set('$widgetOutlinePrefixKey$widgetId', newValue?.value); + void setWidgetOutline(int widgetId, Color? newValue) => set('${SettingKeys.widgetOutlinePrefixKey}$widgetId', newValue?.value); - WidgetShape getWidgetShape(int widgetId) => getEnumOrDefault('$widgetShapePrefixKey$widgetId', SettingsDefaults.widgetShape, WidgetShape.values); + WidgetShape getWidgetShape(int widgetId) => getEnumOrDefault('${SettingKeys.widgetShapePrefixKey}$widgetId', SettingsDefaults.widgetShape, WidgetShape.values); - void setWidgetShape(int widgetId, WidgetShape newValue) => _set('$widgetShapePrefixKey$widgetId', newValue.toString()); + void setWidgetShape(int widgetId, WidgetShape newValue) => set('${SettingKeys.widgetShapePrefixKey}$widgetId', newValue.toString()); - Set getWidgetCollectionFilters(int widgetId) => (getStringList('$widgetCollectionFiltersPrefixKey$widgetId') ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet(); + Set getWidgetCollectionFilters(int widgetId) => (getStringList('${SettingKeys.widgetCollectionFiltersPrefixKey}$widgetId') ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet(); - void setWidgetCollectionFilters(int widgetId, Set newValue) => _set('$widgetCollectionFiltersPrefixKey$widgetId', newValue.map((filter) => filter.toJson()).toList()); + void setWidgetCollectionFilters(int widgetId, Set newValue) => set('${SettingKeys.widgetCollectionFiltersPrefixKey}$widgetId', newValue.map((filter) => filter.toJson()).toList()); - WidgetOpenPage getWidgetOpenPage(int widgetId) => getEnumOrDefault('$widgetOpenPagePrefixKey$widgetId', SettingsDefaults.widgetOpenPage, WidgetOpenPage.values); + WidgetOpenPage getWidgetOpenPage(int widgetId) => getEnumOrDefault('${SettingKeys.widgetOpenPagePrefixKey}$widgetId', SettingsDefaults.widgetOpenPage, WidgetOpenPage.values); - void setWidgetOpenPage(int widgetId, WidgetOpenPage newValue) => _set('$widgetOpenPagePrefixKey$widgetId', newValue.toString()); + void setWidgetOpenPage(int widgetId, WidgetOpenPage newValue) => set('${SettingKeys.widgetOpenPagePrefixKey}$widgetId', newValue.toString()); - WidgetDisplayedItem getWidgetDisplayedItem(int widgetId) => getEnumOrDefault('$widgetDisplayedItemPrefixKey$widgetId', SettingsDefaults.widgetDisplayedItem, WidgetDisplayedItem.values); + WidgetDisplayedItem getWidgetDisplayedItem(int widgetId) => getEnumOrDefault('${SettingKeys.widgetDisplayedItemPrefixKey}$widgetId', SettingsDefaults.widgetDisplayedItem, WidgetDisplayedItem.values); - void setWidgetDisplayedItem(int widgetId, WidgetDisplayedItem newValue) => _set('$widgetDisplayedItemPrefixKey$widgetId', newValue.toString()); + void setWidgetDisplayedItem(int widgetId, WidgetDisplayedItem newValue) => set('${SettingKeys.widgetDisplayedItemPrefixKey}$widgetId', newValue.toString()); - String? getWidgetUri(int widgetId) => getString('$widgetUriPrefixKey$widgetId'); + String? getWidgetUri(int widgetId) => getString('${SettingKeys.widgetUriPrefixKey}$widgetId'); - void setWidgetUri(int widgetId, String? newValue) => _set('$widgetUriPrefixKey$widgetId', newValue); - - // convenience methods - - bool? getBool(String key) { - try { - return settingsStore.getBool(key); - } catch (error) { - // ignore, could be obsolete value of different type - return null; - } - } - - int? getInt(String key) { - try { - return settingsStore.getInt(key); - } catch (error) { - // ignore, could be obsolete value of different type - return null; - } - } - - double? getDouble(String key) { - try { - return settingsStore.getDouble(key); - } catch (error) { - // ignore, could be obsolete value of different type - return null; - } - } - - String? getString(String key) { - try { - return settingsStore.getString(key); - } catch (error) { - // ignore, could be obsolete value of different type - return null; - } - } - - List? getStringList(String key) { - try { - return settingsStore.getStringList(key); - } catch (error) { - // ignore, could be obsolete value of different type - return null; - } - } - - T getEnumOrDefault(String key, T defaultValue, Iterable values) { - try { - final valueString = settingsStore.getString(key); - for (final v in values) { - if (v.toString() == valueString) { - return v; - } - } - } catch (error) { - // ignore, could be obsolete value of different type - } - return defaultValue; - } - - List getEnumListOrDefault(String key, List defaultValue, Iterable values) { - return settingsStore.getStringList(key)?.map((s) => values.firstWhereOrNull((v) => v.toString() == s)).whereNotNull().toList() ?? defaultValue; - } - - void _set(String key, dynamic newValue) { - var oldValue = settingsStore.get(key); - if (newValue == null) { - settingsStore.remove(key); - } else if (newValue is String) { - oldValue = getString(key); - settingsStore.setString(key, newValue); - } else if (newValue is List) { - oldValue = getStringList(key); - settingsStore.setStringList(key, newValue); - } else if (newValue is int) { - oldValue = getInt(key); - settingsStore.setInt(key, newValue); - } else if (newValue is double) { - oldValue = getDouble(key); - settingsStore.setDouble(key, newValue); - } else if (newValue is bool) { - oldValue = getBool(key); - settingsStore.setBool(key, newValue); - } - if (oldValue != newValue) { - _updateStreamController.add(SettingsChangedEvent(key, oldValue, newValue)); - notifyListeners(); - } - } + void setWidgetUri(int widgetId, String? newValue) => set('${SettingKeys.widgetUriPrefixKey}$widgetId', newValue); // platform settings void _onPlatformSettingsChanged(Map? fields) { fields?.forEach((key, value) { switch (key) { - case platformAccelerometerRotationKey: + case SettingKeys.platformAccelerometerRotationKey: if (value is num) { isRotationLocked = value == 0; } - case platformTransitionAnimationScaleKey: + case SettingKeys.platformTransitionAnimationScaleKey: if (value is num) { areAnimationsRemoved = value == 0; } @@ -1030,18 +318,18 @@ class Settings extends ChangeNotifier { }); } - bool get isRotationLocked => getBool(platformAccelerometerRotationKey) ?? SettingsDefaults.isRotationLocked; + bool get isRotationLocked => getBool(SettingKeys.platformAccelerometerRotationKey) ?? SettingsDefaults.isRotationLocked; - set isRotationLocked(bool newValue) => _set(platformAccelerometerRotationKey, newValue); + set isRotationLocked(bool newValue) => set(SettingKeys.platformAccelerometerRotationKey, newValue); - bool get areAnimationsRemoved => getBool(platformTransitionAnimationScaleKey) ?? SettingsDefaults.areAnimationsRemoved; + bool get areAnimationsRemoved => getBool(SettingKeys.platformTransitionAnimationScaleKey) ?? SettingsDefaults.areAnimationsRemoved; - set areAnimationsRemoved(bool newValue) => _set(platformTransitionAnimationScaleKey, newValue); + set areAnimationsRemoved(bool newValue) => set(SettingKeys.platformTransitionAnimationScaleKey, newValue); // import/export Map export() => Map.fromEntries( - settingsStore.getKeys().whereNot(isInternalKey).map((k) => MapEntry(k, settingsStore.get(k))), + store.getKeys().whereNot(SettingKeys.isInternalKey).map((k) => MapEntry(k, store.get(k))), ); Future import(dynamic jsonMap) async { @@ -1051,171 +339,169 @@ class Settings extends ChangeNotifier { // apply user modifications jsonMap.forEach((key, newValue) { - final oldValue = settingsStore.get(key); + final oldValue = store.get(key); if (newValue == null) { - settingsStore.remove(key); - } else if (key.startsWith(tileExtentPrefixKey)) { + store.remove(key); + } else if (key.startsWith(SettingKeys.tileExtentPrefixKey)) { if (newValue is double) { - settingsStore.setDouble(key, newValue); + store.setDouble(key, newValue); } else { debugPrint('failed to import key=$key, value=$newValue is not a double'); } - } else if (key.startsWith(tileLayoutPrefixKey)) { + } else if (key.startsWith(SettingKeys.tileLayoutPrefixKey)) { if (newValue is String) { - settingsStore.setString(key, newValue); + store.setString(key, newValue); } else { debugPrint('failed to import key=$key, value=$newValue is not a string'); } } else { switch (key) { - case subtitleTextColorKey: - case subtitleBackgroundColorKey: - case convertQualityKey: - case screenSaverIntervalKey: - case slideshowIntervalKey: + case SettingKeys.subtitleTextColorKey: + case SettingKeys.subtitleBackgroundColorKey: + case SettingKeys.convertQualityKey: + case SettingKeys.screenSaverIntervalKey: + case SettingKeys.slideshowIntervalKey: if (newValue is int) { - settingsStore.setInt(key, newValue); + store.setInt(key, newValue); } else { debugPrint('failed to import key=$key, value=$newValue is not an int'); } - case subtitleFontSizeKey: - case infoMapZoomKey: + case SettingKeys.subtitleFontSizeKey: + case SettingKeys.infoMapZoomKey: if (newValue is double) { - settingsStore.setDouble(key, newValue); + store.setDouble(key, newValue); } else { debugPrint('failed to import key=$key, value=$newValue is not a double'); } - case isInstalledAppAccessAllowedKey: - case isErrorReportingAllowedKey: - case enableDynamicColorKey: - case enableBlurEffectKey: - case enableBottomNavigationBarKey: - case mustBackTwiceToExitKey: - case confirmCreateVaultKey: - case confirmDeleteForeverKey: - case confirmMoveToBinKey: - case confirmMoveUndatedItemsKey: - case confirmAfterMoveToBinKey: - case setMetadataDateBeforeFileOpKey: - case collectionSortReverseKey: - case showThumbnailFavouriteKey: - case showThumbnailMotionPhotoKey: - case showThumbnailRatingKey: - case showThumbnailRawKey: - case showThumbnailVideoDurationKey: - case albumSortReverseKey: - case countrySortReverseKey: - case stateSortReverseKey: - case placeSortReverseKey: - case tagSortReverseKey: - case showAlbumPickQueryKey: - case showOverlayOnOpeningKey: - case showOverlayMinimapKey: - case showOverlayInfoKey: - case showOverlayDescriptionKey: - case showOverlayRatingTagsKey: - case showOverlayShootingDetailsKey: - case showOverlayThumbnailPreviewKey: - case viewerGestureSideTapNextKey: - case viewerUseCutoutKey: - case enableMotionPhotoAutoPlayKey: - case enableVideoHardwareAccelerationKey: - case videoGestureDoubleTapTogglePlayKey: - case videoGestureSideDoubleTapSeekKey: - case videoGestureVerticalDragBrightnessVolumeKey: - case subtitleShowOutlineKey: - case tagEditorCurrentFilterSectionExpandedKey: - case convertWriteMetadataKey: - case saveSearchHistoryKey: - case showPinchGestureAlternativesKey: - case filePickerShowHiddenFilesKey: - case screenSaverFillScreenKey: - case screenSaverAnimatedZoomEffectKey: - case slideshowRepeatKey: - case slideshowShuffleKey: - case slideshowFillScreenKey: - case slideshowAnimatedZoomEffectKey: + case SettingKeys.isInstalledAppAccessAllowedKey: + case SettingKeys.isErrorReportingAllowedKey: + case SettingKeys.enableDynamicColorKey: + case SettingKeys.enableBlurEffectKey: + case SettingKeys.enableBottomNavigationBarKey: + case SettingKeys.mustBackTwiceToExitKey: + case SettingKeys.confirmCreateVaultKey: + case SettingKeys.confirmDeleteForeverKey: + case SettingKeys.confirmMoveToBinKey: + case SettingKeys.confirmMoveUndatedItemsKey: + case SettingKeys.confirmAfterMoveToBinKey: + case SettingKeys.setMetadataDateBeforeFileOpKey: + case SettingKeys.collectionSortReverseKey: + case SettingKeys.showThumbnailFavouriteKey: + case SettingKeys.showThumbnailMotionPhotoKey: + case SettingKeys.showThumbnailRatingKey: + case SettingKeys.showThumbnailRawKey: + case SettingKeys.showThumbnailVideoDurationKey: + case SettingKeys.albumSortReverseKey: + case SettingKeys.countrySortReverseKey: + case SettingKeys.stateSortReverseKey: + case SettingKeys.placeSortReverseKey: + case SettingKeys.tagSortReverseKey: + case SettingKeys.showAlbumPickQueryKey: + case SettingKeys.showOverlayOnOpeningKey: + case SettingKeys.showOverlayMinimapKey: + case SettingKeys.showOverlayInfoKey: + case SettingKeys.showOverlayDescriptionKey: + case SettingKeys.showOverlayRatingTagsKey: + case SettingKeys.showOverlayShootingDetailsKey: + case SettingKeys.showOverlayThumbnailPreviewKey: + case SettingKeys.viewerGestureSideTapNextKey: + case SettingKeys.viewerUseCutoutKey: + case SettingKeys.enableMotionPhotoAutoPlayKey: + case SettingKeys.enableVideoHardwareAccelerationKey: + case SettingKeys.videoGestureDoubleTapTogglePlayKey: + case SettingKeys.videoGestureSideDoubleTapSeekKey: + case SettingKeys.videoGestureVerticalDragBrightnessVolumeKey: + case SettingKeys.subtitleShowOutlineKey: + case SettingKeys.tagEditorCurrentFilterSectionExpandedKey: + case SettingKeys.convertWriteMetadataKey: + case SettingKeys.saveSearchHistoryKey: + case SettingKeys.showPinchGestureAlternativesKey: + case SettingKeys.filePickerShowHiddenFilesKey: + case SettingKeys.screenSaverFillScreenKey: + case SettingKeys.screenSaverAnimatedZoomEffectKey: + case SettingKeys.slideshowRepeatKey: + case SettingKeys.slideshowShuffleKey: + case SettingKeys.slideshowFillScreenKey: + case SettingKeys.slideshowAnimatedZoomEffectKey: if (newValue is bool) { - settingsStore.setBool(key, newValue); + store.setBool(key, newValue); } else { debugPrint('failed to import key=$key, value=$newValue is not a bool'); } - case localeKey: - case displayRefreshRateModeKey: - case themeBrightnessKey: - case themeColorModeKey: - case maxBrightnessKey: - case keepScreenOnKey: - case homePageKey: - case collectionGroupFactorKey: - case collectionSortFactorKey: - case thumbnailLocationIconKey: - case thumbnailTagIconKey: - case albumGroupFactorKey: - case albumSortFactorKey: - case countrySortFactorKey: - case stateSortFactorKey: - case placeSortFactorKey: - case tagSortFactorKey: - case imageBackgroundKey: - case videoAutoPlayModeKey: - case videoBackgroundModeKey: - case videoLoopModeKey: - case videoResumptionModeKey: - case videoControlsKey: - case subtitleTextAlignmentKey: - case subtitleTextPositionKey: - case tagEditorExpandedSectionKey: - case convertMimeTypeKey: - case mapStyleKey: - case mapDefaultCenterKey: - case coordinateFormatKey: - case unitSystemKey: - case accessibilityAnimationsKey: - case timeToTakeActionKey: - case screenSaverTransitionKey: - case screenSaverVideoPlaybackKey: - case slideshowTransitionKey: - case slideshowVideoPlaybackKey: + case SettingKeys.localeKey: + case SettingKeys.displayRefreshRateModeKey: + case SettingKeys.themeBrightnessKey: + case SettingKeys.themeColorModeKey: + case SettingKeys.maxBrightnessKey: + case SettingKeys.keepScreenOnKey: + case SettingKeys.homePageKey: + case SettingKeys.collectionGroupFactorKey: + case SettingKeys.collectionSortFactorKey: + case SettingKeys.thumbnailLocationIconKey: + case SettingKeys.thumbnailTagIconKey: + case SettingKeys.albumGroupFactorKey: + case SettingKeys.albumSortFactorKey: + case SettingKeys.countrySortFactorKey: + case SettingKeys.stateSortFactorKey: + case SettingKeys.placeSortFactorKey: + case SettingKeys.tagSortFactorKey: + case SettingKeys.imageBackgroundKey: + case SettingKeys.videoAutoPlayModeKey: + case SettingKeys.videoBackgroundModeKey: + case SettingKeys.videoLoopModeKey: + case SettingKeys.videoResumptionModeKey: + case SettingKeys.videoControlsKey: + case SettingKeys.subtitleTextAlignmentKey: + case SettingKeys.subtitleTextPositionKey: + case SettingKeys.tagEditorExpandedSectionKey: + case SettingKeys.convertMimeTypeKey: + case SettingKeys.mapStyleKey: + case SettingKeys.mapDefaultCenterKey: + case SettingKeys.coordinateFormatKey: + case SettingKeys.unitSystemKey: + case SettingKeys.accessibilityAnimationsKey: + case SettingKeys.timeToTakeActionKey: + case SettingKeys.screenSaverTransitionKey: + case SettingKeys.screenSaverVideoPlaybackKey: + case SettingKeys.slideshowTransitionKey: + case SettingKeys.slideshowVideoPlaybackKey: if (newValue is String) { - settingsStore.setString(key, newValue); + store.setString(key, newValue); } else { debugPrint('failed to import key=$key, value=$newValue is not a string'); } - case drawerTypeBookmarksKey: - case drawerAlbumBookmarksKey: - case drawerPageBookmarksKey: - case collectionBurstPatternsKey: - case pinnedFiltersKey: - case hiddenFiltersKey: - case collectionBrowsingQuickActionsKey: - case collectionSelectionQuickActionsKey: - case viewerQuickActionsKey: - case screenSaverCollectionFiltersKey: + case SettingKeys.drawerTypeBookmarksKey: + case SettingKeys.drawerAlbumBookmarksKey: + case SettingKeys.drawerPageBookmarksKey: + case SettingKeys.collectionBurstPatternsKey: + case SettingKeys.pinnedFiltersKey: + case SettingKeys.hiddenFiltersKey: + case SettingKeys.collectionBrowsingQuickActionsKey: + case SettingKeys.collectionSelectionQuickActionsKey: + case SettingKeys.viewerQuickActionsKey: + case SettingKeys.screenSaverCollectionFiltersKey: if (newValue is List) { - settingsStore.setStringList(key, newValue.cast()); + store.setStringList(key, newValue.cast()); } else { debugPrint('failed to import key=$key, value=$newValue is not a list'); } } } if (oldValue != newValue) { - _updateStreamController.add(SettingsChangedEvent(key, oldValue, newValue)); + notifyKeyChange(key, oldValue, newValue); } }); await sanitize(); notifyListeners(); } } -} -@immutable -class SettingsChangedEvent { - final String key; - final dynamic oldValue; - final dynamic newValue; - - // old and new values as stored, e.g. `List` for collections - const SettingsChangedEvent(this.key, this.oldValue, this.newValue); + @override + void notifyKeyChange(String key, dynamic oldValue, dynamic newValue) { + _updateStreamController.add(SettingsChangedEvent(key, oldValue, newValue)); + if (key.startsWith(SettingKeys.tileExtentPrefixKey)) { + _updateTileExtentStreamController.add(SettingsChangedEvent(key, oldValue, newValue)); + } + } } diff --git a/lib/model/settings/store/store_shared_pref.dart b/lib/model/settings/store_shared_pref.dart similarity index 96% rename from lib/model/settings/store/store_shared_pref.dart rename to lib/model/settings/store_shared_pref.dart index 10e2477c0..6547d8c36 100644 --- a/lib/model/settings/store/store_shared_pref.dart +++ b/lib/model/settings/store_shared_pref.dart @@ -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'; diff --git a/lib/model/source/album.dart b/lib/model/source/album.dart index 1a2c09bd4..2e6057334 100644 --- a/lib/model/source/album.dart +++ b/lib/model/source/album.dart @@ -19,7 +19,9 @@ mixin AlbumMixin on SourceBase { Set 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); diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart index dfc922a3b..23af9b736 100644 --- a/lib/model/source/collection_lens.dart +++ b/lib/model/source/collection_lens.dart @@ -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(_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>.of(byAlbum, compare); case EntrySortFactor.rating: sections = groupBy(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating)); diff --git a/lib/model/source/collection_source.dart b/lib/model/source/collection_source.dart index e12ee7da3..385131a9f 100644 --- a/lib/model/source/collection_source.dart +++ b/lib/model/source/collection_source.dart @@ -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?) { 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; diff --git a/lib/model/source/location/location.dart b/lib/model/source/location/location.dart index d0dadab88..09f89d38d 100644 --- a/lib/model/source/location/location.dart +++ b/lib/model/source/location/location.dart @@ -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 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((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 = , AddressDetails?>{}; + final knownLocations = <(int, int), AddressDetails?>{}; located.forEach((entry) { knownLocations.putIfAbsent(approximateLatLng(entry), () => entry.addressDetails); }); diff --git a/lib/model/video/metadata.dart b/lib/model/video/metadata.dart index 25593eb39..56023b89b 100644 --- a/lib/model/video/metadata.dart +++ b/lib/model/video/metadata.dart @@ -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 _codecNames = { Codecs.ac3: 'AC-3', @@ -44,20 +44,8 @@ class VideoMetadataFormatter { Codecs.webm: 'WebM', }; - static Future 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> getLoadingMetadata(AvesEntry entry) async { - final mediaInfo = await getVideoMetadata(entry); + final mediaInfo = await videoMetadataFetcher.getMetadata(entry); final fields = {}; 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 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; } diff --git a/lib/ref/bursts.dart b/lib/ref/bursts.dart index 51b587664..65e590a19 100644 --- a/lib/ref/bursts.dart +++ b/lib/ref/bursts.dart @@ -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 = { diff --git a/lib/ref/poi.dart b/lib/ref/poi.dart index 951239d39..1b7b3ee01 100644 --- a/lib/ref/poi.dart +++ b/lib/ref/poi.dart @@ -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), diff --git a/lib/services/analysis_service.dart b/lib/services/analysis_service.dart index 071561f35..eeb91fb8b 100644 --- a/lib/services/analysis_service.dart +++ b/lib/services/analysis_service.dart @@ -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 _init() async { await device.init(); await mobileServices.init(); await settings.init(monitorPlatformSettings: false); - FijkLog.setLevel(FijkLogLevel.Warn); await reportService.init(); final analyzer = Analyzer(); diff --git a/lib/services/common/service_policy.dart b/lib/services/common/service_policy.dart index c2b7f5627..e2535533d 100644 --- a/lib/services/common/service_policy.dart +++ b/lib/services/common/service_policy.dart @@ -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 _queueStreamController = StreamController.broadcast(); - final Map> _paused = {}; + final Map _paused = {}; final SplayTreeMap> _queues = SplayTreeMap(); final LinkedHashMap _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; + priority = toResume.$1; + task = toResume.$2 as _Task; completer = task.completer; } else { completer = Completer(); @@ -56,8 +55,8 @@ class ServicePolicy { Future? resume(Object key) { final toResume = _paused.remove(key); if (toResume != null) { - final priority = toResume.item1; - final task = toResume.item2 as _Task; + final priority = toResume.$1; + final task = toResume.$2 as _Task; _getQueue(priority)[key] = task; _pickNext(); return task.completer.future; @@ -97,7 +96,7 @@ class ServicePolicy { } bool pause(Object key, Iterable 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); diff --git a/lib/services/common/services.dart b/lib/services/common/services.dart index 89ce1c47a..905ab96c0 100644 --- a/lib/services/common/services.dart +++ b/lib/services/common/services.dart @@ -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(); final AvesAvailability availability = getIt(); final MetadataDb metadataDb = getIt(); +final AvesVideoControllerFactory videoControllerFactory = getIt(); +final AvesVideoMetadataFetcher videoMetadataFetcher = getIt(); final AppService appService = getIt(); final DeviceService deviceService = getIt(); @@ -50,6 +55,8 @@ void initPlatformServices() { getIt.registerLazySingleton(p.Context.new); getIt.registerLazySingleton(LiveAvesAvailability.new); getIt.registerLazySingleton(SqfliteMetadataDb.new); + getIt.registerLazySingleton(MpvVideoControllerFactory.new); + getIt.registerLazySingleton(FfmpegVideoMetadataFetcher.new); getIt.registerLazySingleton(PlatformAppService.new); getIt.registerLazySingleton(PlatformDeviceService.new); diff --git a/lib/services/media/enums.dart b/lib/services/media/enums.dart index de31c378d..4f47d6d44 100644 --- a/lib/services/media/enums.dart +++ b/lib/services/media/enums.dart @@ -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, + }; } } diff --git a/lib/services/media/media_session_service.dart b/lib/services/media/media_session_service.dart index 2d287f910..8b970a7a6 100644 --- a/lib/services/media/media_session_service.dart +++ b/lib/services/media/media_session_service.dart @@ -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'; diff --git a/lib/services/storage_service.dart b/lib/services/storage_service.dart index 674f0433e..268076c27 100644 --- a/lib/services/storage_service.dart +++ b/lib/services/storage_service.dart @@ -8,6 +8,8 @@ import 'package:flutter/services.dart'; import 'package:streams_channel/streams_channel.dart'; abstract class StorageService { + Future> getDataUsage(); + Future> getStorageVolumes(); Future 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> getDataUsage() async { + try { + final result = await _platform.invokeMethod('getDataUsage'); + if (result != null) return (result as Map).cast(); + } on PlatformException catch (e, stack) { + await reportService.recordError(e, stack); + } + return {}; + } + @override Future> getStorageVolumes() async { try { diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 95ec57532..c57ed4b1b 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -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( 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, ); diff --git a/lib/theme/durations.dart b/lib/theme/durations.dart index 189766023..486c51874 100644 --- a/lib/theme/durations.dart +++ b/lib/theme/durations.dart @@ -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` diff --git a/lib/theme/icons.dart b/lib/theme/icons.dart index 8f46027fa..876e6420d 100644 --- a/lib/theme/icons.dart +++ b/lib/theme/icons.dart @@ -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; } diff --git a/lib/utils/math_utils.dart b/lib/utils/math_utils.dart index e882db4aa..82be6858d 100644 --- a/lib/utils/math_utils.dart +++ b/lib/utils/math_utils.dart @@ -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 s1, Tuple2 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); diff --git a/lib/view/src/actions/chip.dart b/lib/view/src/actions/chip.dart index cf4b7f0a1..6c6dcc6cc 100644 --- a/lib/view/src/actions/chip.dart +++ b/lib/view/src/actions/chip.dart @@ -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, + }; } diff --git a/lib/view/src/actions/chip_set.dart b/lib/view/src/actions/chip_set.dart index f178505ab..596c45e26 100644 --- a/lib/view/src/actions/chip_set.dart +++ b/lib/view/src/actions/chip_set.dart @@ -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, + }; } } diff --git a/lib/view/src/actions/entry.dart b/lib/view/src/actions/entry.dart index 960673705..356ce01cf 100644 --- a/lib/view/src/actions/entry.dart +++ b/lib/view/src/actions/entry.dart @@ -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, + }; } } diff --git a/lib/view/src/actions/entry_set.dart b/lib/view/src/actions/entry_set.dart index 880250bc6..9fb163be7 100644 --- a/lib/view/src/actions/entry_set.dart +++ b/lib/view/src/actions/entry_set.dart @@ -5,142 +5,88 @@ import 'package:flutter/material.dart'; extension ExtraEntrySetActionView on EntrySetAction { String getText(BuildContext context) { - switch (this) { + return switch (this) { // general - case EntrySetAction.configureView: - return context.l10n.menuActionConfigureView; - case EntrySetAction.select: - return context.l10n.menuActionSelect; - case EntrySetAction.selectAll: - return context.l10n.menuActionSelectAll; - case EntrySetAction.selectNone: - return context.l10n.menuActionSelectNone; + EntrySetAction.configureView => context.l10n.menuActionConfigureView, + EntrySetAction.select => context.l10n.menuActionSelect, + EntrySetAction.selectAll => context.l10n.menuActionSelectAll, + EntrySetAction.selectNone => context.l10n.menuActionSelectNone, // browsing - case EntrySetAction.searchCollection: - return MaterialLocalizations.of(context).searchFieldLabel; - case EntrySetAction.toggleTitleSearch: + EntrySetAction.searchCollection => MaterialLocalizations.of(context).searchFieldLabel, + EntrySetAction.toggleTitleSearch => // different data depending on toggle state - return context.l10n.collectionActionShowTitleSearch; - case EntrySetAction.addShortcut: - return context.l10n.collectionActionAddShortcut; - case EntrySetAction.emptyBin: - return context.l10n.collectionActionEmptyBin; + context.l10n.collectionActionShowTitleSearch, + EntrySetAction.addShortcut => context.l10n.collectionActionAddShortcut, + EntrySetAction.emptyBin => context.l10n.collectionActionEmptyBin, // browsing or selecting - case EntrySetAction.map: - return context.l10n.menuActionMap; - case EntrySetAction.slideshow: - return context.l10n.menuActionSlideshow; - case EntrySetAction.stats: - return context.l10n.menuActionStats; - case EntrySetAction.rescan: - return context.l10n.collectionActionRescan; + EntrySetAction.map => context.l10n.menuActionMap, + EntrySetAction.slideshow => context.l10n.menuActionSlideshow, + EntrySetAction.stats => context.l10n.menuActionStats, + EntrySetAction.rescan => context.l10n.collectionActionRescan, // selecting - case EntrySetAction.share: - return context.l10n.entryActionShare; - case EntrySetAction.delete: - return context.l10n.entryActionDelete; - case EntrySetAction.restore: - return context.l10n.entryActionRestore; - case EntrySetAction.copy: - return context.l10n.collectionActionCopy; - case EntrySetAction.move: - return context.l10n.collectionActionMove; - case EntrySetAction.rename: - return context.l10n.entryActionRename; - case EntrySetAction.convert: - return context.l10n.entryActionConvert; - case EntrySetAction.toggleFavourite: + EntrySetAction.share => context.l10n.entryActionShare, + EntrySetAction.delete => context.l10n.entryActionDelete, + EntrySetAction.restore => context.l10n.entryActionRestore, + EntrySetAction.copy => context.l10n.collectionActionCopy, + EntrySetAction.move => context.l10n.collectionActionMove, + EntrySetAction.rename => context.l10n.entryActionRename, + EntrySetAction.convert => context.l10n.entryActionConvert, + EntrySetAction.toggleFavourite => // different data depending on toggle state - return context.l10n.entryActionAddFavourite; - case EntrySetAction.rotateCCW: - return context.l10n.entryActionRotateCCW; - case EntrySetAction.rotateCW: - return context.l10n.entryActionRotateCW; - case EntrySetAction.flip: - return context.l10n.entryActionFlip; - case EntrySetAction.editDate: - return context.l10n.entryInfoActionEditDate; - case EntrySetAction.editLocation: - return context.l10n.entryInfoActionEditLocation; - case EntrySetAction.editTitleDescription: - return context.l10n.entryInfoActionEditTitleDescription; - case EntrySetAction.editRating: - return context.l10n.entryInfoActionEditRating; - case EntrySetAction.editTags: - return context.l10n.entryInfoActionEditTags; - case EntrySetAction.removeMetadata: - return context.l10n.entryInfoActionRemoveMetadata; - } + context.l10n.entryActionAddFavourite, + EntrySetAction.rotateCCW => context.l10n.entryActionRotateCCW, + EntrySetAction.rotateCW => context.l10n.entryActionRotateCW, + EntrySetAction.flip => context.l10n.entryActionFlip, + EntrySetAction.editDate => context.l10n.entryInfoActionEditDate, + EntrySetAction.editLocation => context.l10n.entryInfoActionEditLocation, + EntrySetAction.editTitleDescription => context.l10n.entryInfoActionEditTitleDescription, + EntrySetAction.editRating => context.l10n.entryInfoActionEditRating, + EntrySetAction.editTags => context.l10n.entryInfoActionEditTags, + EntrySetAction.removeMetadata => context.l10n.entryInfoActionRemoveMetadata, + }; } Widget getIcon() => Icon(_getIconData()); IconData _getIconData() { - switch (this) { + return switch (this) { // general - case EntrySetAction.configureView: - return AIcons.view; - case EntrySetAction.select: - return AIcons.select; - case EntrySetAction.selectAll: - return AIcons.selected; - case EntrySetAction.selectNone: - return AIcons.unselected; + EntrySetAction.configureView => AIcons.view, + EntrySetAction.select => AIcons.select, + EntrySetAction.selectAll => AIcons.selected, + EntrySetAction.selectNone => AIcons.unselected, // browsing - case EntrySetAction.searchCollection: - return AIcons.search; - case EntrySetAction.toggleTitleSearch: + EntrySetAction.searchCollection => AIcons.search, + EntrySetAction.toggleTitleSearch => // different data depending on toggle state - return AIcons.filter; - case EntrySetAction.addShortcut: - return AIcons.addShortcut; - case EntrySetAction.emptyBin: - return AIcons.emptyBin; + AIcons.filter, + EntrySetAction.addShortcut => AIcons.addShortcut, + EntrySetAction.emptyBin => AIcons.emptyBin, // browsing or selecting - case EntrySetAction.map: - return AIcons.map; - case EntrySetAction.slideshow: - return AIcons.slideshow; - case EntrySetAction.stats: - return AIcons.stats; - case EntrySetAction.rescan: - return AIcons.refresh; + EntrySetAction.map => AIcons.map, + EntrySetAction.slideshow => AIcons.slideshow, + EntrySetAction.stats => AIcons.stats, + EntrySetAction.rescan => AIcons.refresh, // selecting - case EntrySetAction.share: - return AIcons.share; - case EntrySetAction.delete: - return AIcons.delete; - case EntrySetAction.restore: - return AIcons.restore; - case EntrySetAction.copy: - return AIcons.copy; - case EntrySetAction.move: - return AIcons.move; - case EntrySetAction.rename: - return AIcons.name; - case EntrySetAction.convert: - return AIcons.convert; - case EntrySetAction.toggleFavourite: + EntrySetAction.share => AIcons.share, + EntrySetAction.delete => AIcons.delete, + EntrySetAction.restore => AIcons.restore, + EntrySetAction.copy => AIcons.copy, + EntrySetAction.move => AIcons.move, + EntrySetAction.rename => AIcons.name, + EntrySetAction.convert => AIcons.convert, + EntrySetAction.toggleFavourite => // different data depending on toggle state - return AIcons.favourite; - case EntrySetAction.rotateCCW: - return AIcons.rotateLeft; - case EntrySetAction.rotateCW: - return AIcons.rotateRight; - case EntrySetAction.flip: - return AIcons.flip; - case EntrySetAction.editDate: - return AIcons.date; - case EntrySetAction.editLocation: - return AIcons.location; - case EntrySetAction.editTitleDescription: - return AIcons.description; - case EntrySetAction.editRating: - return AIcons.rating; - case EntrySetAction.editTags: - return AIcons.tag; - case EntrySetAction.removeMetadata: - return AIcons.clear; - } + AIcons.favourite, + EntrySetAction.rotateCCW => AIcons.rotateLeft, + EntrySetAction.rotateCW => AIcons.rotateRight, + EntrySetAction.flip => AIcons.flip, + EntrySetAction.editDate => AIcons.date, + EntrySetAction.editLocation => AIcons.location, + EntrySetAction.editTitleDescription => AIcons.description, + EntrySetAction.editRating => AIcons.rating, + EntrySetAction.editTags => AIcons.tag, + EntrySetAction.removeMetadata => AIcons.clear, + }; } } diff --git a/lib/view/src/actions/map.dart b/lib/view/src/actions/map.dart index 05a2ceb71..ee7db32fc 100644 --- a/lib/view/src/actions/map.dart +++ b/lib/view/src/actions/map.dart @@ -5,26 +5,21 @@ import 'package:flutter/widgets.dart'; extension ExtraMapActionView on MapAction { String getText(BuildContext context) { - switch (this) { - case MapAction.selectStyle: - return context.l10n.mapStyleTooltip; - case MapAction.zoomIn: - return context.l10n.mapZoomInTooltip; - case MapAction.zoomOut: - return context.l10n.mapZoomOutTooltip; - } + final l10n = context.l10n; + return switch (this) { + MapAction.selectStyle => l10n.mapStyleTooltip, + MapAction.zoomIn => l10n.mapZoomInTooltip, + MapAction.zoomOut => l10n.mapZoomOutTooltip, + }; } Widget getIcon() => Icon(_getIconData()); IconData _getIconData() { - switch (this) { - case MapAction.selectStyle: - return AIcons.layers; - case MapAction.zoomIn: - return AIcons.zoomIn; - case MapAction.zoomOut: - return AIcons.zoomOut; - } + return switch (this) { + MapAction.selectStyle => AIcons.layers, + MapAction.zoomIn => AIcons.zoomIn, + MapAction.zoomOut => AIcons.zoomOut, + }; } } diff --git a/lib/view/src/actions/map_cluster.dart b/lib/view/src/actions/map_cluster.dart index cc605675f..cf016797c 100644 --- a/lib/view/src/actions/map_cluster.dart +++ b/lib/view/src/actions/map_cluster.dart @@ -5,22 +5,19 @@ import 'package:flutter/widgets.dart'; extension ExtraMapClusterActionView on MapClusterAction { String getText(BuildContext context) { - switch (this) { - case MapClusterAction.editLocation: - return context.l10n.entryInfoActionEditLocation; - case MapClusterAction.removeLocation: - return context.l10n.entryInfoActionRemoveLocation; - } + final l10n = context.l10n; + return switch (this) { + MapClusterAction.editLocation => l10n.entryInfoActionEditLocation, + MapClusterAction.removeLocation => l10n.entryInfoActionRemoveLocation, + }; } Widget getIcon() => Icon(_getIconData()); IconData _getIconData() { - switch (this) { - case MapClusterAction.editLocation: - return AIcons.edit; - case MapClusterAction.removeLocation: - return AIcons.clear; - } + return switch (this) { + MapClusterAction.editLocation => AIcons.edit, + MapClusterAction.removeLocation => AIcons.clear, + }; } } diff --git a/lib/view/src/actions/share.dart b/lib/view/src/actions/share.dart index d9a224a00..fd1042e7d 100644 --- a/lib/view/src/actions/share.dart +++ b/lib/view/src/actions/share.dart @@ -5,22 +5,19 @@ import 'package:flutter/widgets.dart'; extension ExtraShareActionView on ShareAction { String getText(BuildContext context) { - switch (this) { - case ShareAction.imageOnly: - return context.l10n.entryActionShareImageOnly; - case ShareAction.videoOnly: - return context.l10n.entryActionShareVideoOnly; - } + final l10n = context.l10n; + return switch (this) { + ShareAction.imageOnly => l10n.entryActionShareImageOnly, + ShareAction.videoOnly => l10n.entryActionShareVideoOnly, + }; } Widget getIcon() => Icon(_getIconData()); IconData _getIconData() { - switch (this) { - case ShareAction.imageOnly: - return AIcons.image; - case ShareAction.videoOnly: - return AIcons.video; - } + return switch (this) { + ShareAction.imageOnly => AIcons.image, + ShareAction.videoOnly => AIcons.video, + }; } } diff --git a/lib/view/src/actions/slideshow.dart b/lib/view/src/actions/slideshow.dart index 84c729bad..69635df33 100644 --- a/lib/view/src/actions/slideshow.dart +++ b/lib/view/src/actions/slideshow.dart @@ -5,26 +5,21 @@ import 'package:flutter/widgets.dart'; extension ExtraSlideshowActionView on SlideshowAction { String getText(BuildContext context) { - switch (this) { - case SlideshowAction.resume: - return context.l10n.slideshowActionResume; - case SlideshowAction.showInCollection: - return context.l10n.slideshowActionShowInCollection; - case SlideshowAction.settings: - return context.l10n.viewerActionSettings; - } + final l10n = context.l10n; + return switch (this) { + SlideshowAction.resume => l10n.slideshowActionResume, + SlideshowAction.showInCollection => l10n.slideshowActionShowInCollection, + SlideshowAction.settings => l10n.viewerActionSettings, + }; } Widget getIcon() => Icon(_getIconData()); IconData _getIconData() { - switch (this) { - case SlideshowAction.resume: - return AIcons.play; - case SlideshowAction.showInCollection: - return AIcons.allCollection; - case SlideshowAction.settings: - return AIcons.settings; - } + return switch (this) { + SlideshowAction.resume => AIcons.play, + SlideshowAction.showInCollection => AIcons.allCollection, + SlideshowAction.settings => AIcons.settings, + }; } } diff --git a/lib/view/src/editor/enums.dart b/lib/view/src/editor/enums.dart index 43b9fe95d..13282f8a8 100644 --- a/lib/view/src/editor/enums.dart +++ b/lib/view/src/editor/enums.dart @@ -6,52 +6,42 @@ import 'package:flutter/widgets.dart'; extension ExtraEditorActionView on EditorAction { String getText(BuildContext context) { - switch (this) { - case EditorAction.transform: - return context.l10n.editorActionTransform; - } + final l10n = context.l10n; + return switch (this) { + EditorAction.transform => l10n.editorActionTransform, + }; } Widget getIcon() => Icon(_getIconData()); IconData _getIconData() { - switch (this) { - case EditorAction.transform: - return AIcons.transform; - } + return switch (this) { + EditorAction.transform => AIcons.transform, + }; } } extension ExtraCropAspectRatioView on CropAspectRatio { String getText(BuildContext context) { - switch (this) { - case CropAspectRatio.free: - return context.l10n.cropAspectRatioFree; - case CropAspectRatio.original: - return context.l10n.cropAspectRatioOriginal; - case CropAspectRatio.square: - return context.l10n.cropAspectRatioSquare; - case CropAspectRatio.ar_16_9: - return '16${UniChars.ratio}9'; - case CropAspectRatio.ar_4_3: - return '4${UniChars.ratio}3'; - } + final l10n = context.l10n; + return switch (this) { + CropAspectRatio.free => l10n.cropAspectRatioFree, + CropAspectRatio.original => l10n.cropAspectRatioOriginal, + CropAspectRatio.square => l10n.cropAspectRatioSquare, + CropAspectRatio.ar_16_9 => '16${UniChars.ratio}9', + CropAspectRatio.ar_4_3 => '4${UniChars.ratio}3', + }; } Widget getIcon() => Icon(_getIconData()); IconData _getIconData() { - switch (this) { - case CropAspectRatio.free: - return AIcons.aspectRatioFree; - case CropAspectRatio.original: - return AIcons.aspectRatioOriginal; - case CropAspectRatio.square: - return AIcons.aspectRatioSquare; - case CropAspectRatio.ar_16_9: - return AIcons.aspectRatio_16_9; - case CropAspectRatio.ar_4_3: - return AIcons.aspectRatio_4_3; - } + return switch (this) { + CropAspectRatio.free => AIcons.aspectRatioFree, + CropAspectRatio.original => AIcons.aspectRatioOriginal, + CropAspectRatio.square => AIcons.aspectRatioSquare, + CropAspectRatio.ar_16_9 => AIcons.aspectRatio_16_9, + CropAspectRatio.ar_4_3 => AIcons.aspectRatio_4_3, + }; } } diff --git a/lib/view/src/metadata/date_edit_action.dart b/lib/view/src/metadata/date_edit_action.dart index de735ff63..6b9005483 100644 --- a/lib/view/src/metadata/date_edit_action.dart +++ b/lib/view/src/metadata/date_edit_action.dart @@ -4,19 +4,14 @@ import 'package:flutter/widgets.dart'; extension ExtraDateEditActionView on DateEditAction { String getText(BuildContext context) { - switch (this) { - case DateEditAction.setCustom: - return context.l10n.editEntryDateDialogSetCustom; - case DateEditAction.copyField: - return context.l10n.editEntryDateDialogCopyField; - case DateEditAction.copyItem: - return context.l10n.editEntryDialogCopyFromItem; - case DateEditAction.extractFromTitle: - return context.l10n.editEntryDateDialogExtractFromTitle; - case DateEditAction.shift: - return context.l10n.editEntryDateDialogShift; - case DateEditAction.remove: - return context.l10n.actionRemove; - } + final l10n = context.l10n; + return switch (this) { + DateEditAction.setCustom => l10n.editEntryDateDialogSetCustom, + DateEditAction.copyField => l10n.editEntryDateDialogCopyField, + DateEditAction.copyItem => l10n.editEntryDialogCopyFromItem, + DateEditAction.extractFromTitle => l10n.editEntryDateDialogExtractFromTitle, + DateEditAction.shift => l10n.editEntryDateDialogShift, + DateEditAction.remove => l10n.actionRemove, + }; } } diff --git a/lib/view/src/metadata/date_field_source.dart b/lib/view/src/metadata/date_field_source.dart index 88355826b..ccb58d841 100644 --- a/lib/view/src/metadata/date_field_source.dart +++ b/lib/view/src/metadata/date_field_source.dart @@ -4,17 +4,12 @@ import 'package:flutter/widgets.dart'; extension ExtraDateFieldSourceView on DateFieldSource { String getText(BuildContext context) { - switch (this) { - case DateFieldSource.fileModifiedDate: - return context.l10n.editEntryDateDialogSourceFileModifiedDate; - case DateFieldSource.exifDate: - return 'Exif date'; - case DateFieldSource.exifDateOriginal: - return 'Exif original date'; - case DateFieldSource.exifDateDigitized: - return 'Exif digitized date'; - case DateFieldSource.exifGpsDate: - return 'Exif GPS date'; - } + return switch (this) { + DateFieldSource.fileModifiedDate => context.l10n.editEntryDateDialogSourceFileModifiedDate, + DateFieldSource.exifDate => 'Exif date', + DateFieldSource.exifDateOriginal => 'Exif original date', + DateFieldSource.exifDateDigitized => 'Exif digitized date', + DateFieldSource.exifGpsDate => 'Exif GPS date', + }; } } diff --git a/lib/view/src/metadata/length_unit.dart b/lib/view/src/metadata/length_unit.dart index d4766d902..2ec818db3 100644 --- a/lib/view/src/metadata/length_unit.dart +++ b/lib/view/src/metadata/length_unit.dart @@ -4,11 +4,10 @@ import 'package:flutter/widgets.dart'; extension ExtraLengthUnitView on LengthUnit { String getText(BuildContext context) { - switch (this) { - case LengthUnit.px: - return context.l10n.lengthUnitPixel; - case LengthUnit.percent: - return context.l10n.lengthUnitPercent; - } + final l10n = context.l10n; + return switch (this) { + LengthUnit.px => l10n.lengthUnitPixel, + LengthUnit.percent => l10n.lengthUnitPercent, + }; } } diff --git a/lib/view/src/metadata/location_edit_action.dart b/lib/view/src/metadata/location_edit_action.dart index 469a15c3d..ad8b54004 100644 --- a/lib/view/src/metadata/location_edit_action.dart +++ b/lib/view/src/metadata/location_edit_action.dart @@ -4,15 +4,12 @@ import 'package:flutter/widgets.dart'; extension ExtraLocationEditActionView on LocationEditAction { String getText(BuildContext context) { - switch (this) { - case LocationEditAction.chooseOnMap: - return context.l10n.editEntryLocationDialogChooseOnMap; - case LocationEditAction.copyItem: - return context.l10n.editEntryDialogCopyFromItem; - case LocationEditAction.setCustom: - return context.l10n.editEntryLocationDialogSetCustom; - case LocationEditAction.remove: - return context.l10n.actionRemove; - } + final l10n = context.l10n; + return switch (this) { + LocationEditAction.chooseOnMap => l10n.editEntryLocationDialogChooseOnMap, + LocationEditAction.copyItem => l10n.editEntryDialogCopyFromItem, + LocationEditAction.setCustom => l10n.editEntryLocationDialogSetCustom, + LocationEditAction.remove => l10n.actionRemove, + }; } } diff --git a/lib/view/src/metadata/metadata_type.dart b/lib/view/src/metadata/metadata_type.dart index 5eee2bf45..33b085901 100644 --- a/lib/view/src/metadata/metadata_type.dart +++ b/lib/view/src/metadata/metadata_type.dart @@ -3,27 +3,17 @@ import 'package:aves_model/aves_model.dart'; extension ExtraMetadataTypeView on MetadataType { // match `metadata-extractor` directory names String getText() { - switch (this) { - case MetadataType.comment: - return 'Comment'; - case MetadataType.exif: - return 'Exif'; - case MetadataType.iccProfile: - return 'ICC Profile'; - case MetadataType.iptc: - return 'IPTC'; - case MetadataType.jfif: - return 'JFIF'; - case MetadataType.jpegAdobe: - return 'Adobe JPEG'; - case MetadataType.jpegDucky: - return 'Ducky'; - case MetadataType.mp4: - return 'MP4'; - case MetadataType.photoshopIrb: - return 'Photoshop'; - case MetadataType.xmp: - return 'XMP'; - } + return switch (this) { + MetadataType.comment => 'Comment', + MetadataType.exif => 'Exif', + MetadataType.iccProfile => 'ICC Profile', + MetadataType.iptc => 'IPTC', + MetadataType.jfif => 'JFIF', + MetadataType.jpegAdobe => 'Adobe JPEG', + MetadataType.jpegDucky => 'Ducky', + MetadataType.mp4 => 'MP4', + MetadataType.photoshopIrb => 'Photoshop', + MetadataType.xmp => 'XMP', + }; } } diff --git a/lib/view/src/settings/enums.dart b/lib/view/src/settings/enums.dart index 8b7abc0c5..bdc6d08f4 100644 --- a/lib/view/src/settings/enums.dart +++ b/lib/view/src/settings/enums.dart @@ -5,280 +5,241 @@ import 'package:flutter/widgets.dart'; extension ExtraAccessibilityAnimationsView on AccessibilityAnimations { String getName(BuildContext context) { - switch (this) { - case AccessibilityAnimations.system: - return context.l10n.settingsSystemDefault; - case AccessibilityAnimations.disabled: - return context.l10n.accessibilityAnimationsRemove; - case AccessibilityAnimations.enabled: - return context.l10n.accessibilityAnimationsKeep; - } + final l10n = context.l10n; + return switch (this) { + AccessibilityAnimations.system => l10n.settingsSystemDefault, + AccessibilityAnimations.disabled => l10n.accessibilityAnimationsRemove, + AccessibilityAnimations.enabled => l10n.accessibilityAnimationsKeep, + }; } } extension ExtraAccessibilityTimeoutView on AccessibilityTimeout { String getName(BuildContext context) { - switch (this) { - case AccessibilityTimeout.system: - return context.l10n.settingsSystemDefault; - case AccessibilityTimeout.s1: - return context.l10n.timeSeconds(1); - case AccessibilityTimeout.s3: - return context.l10n.timeSeconds(3); - case AccessibilityTimeout.s5: - return context.l10n.timeSeconds(5); - case AccessibilityTimeout.s10: - return context.l10n.timeSeconds(10); - case AccessibilityTimeout.s30: - return context.l10n.timeSeconds(30); - } + final l10n = context.l10n; + return switch (this) { + AccessibilityTimeout.system => l10n.settingsSystemDefault, + AccessibilityTimeout.s1 => l10n.timeSeconds(1), + AccessibilityTimeout.s3 => l10n.timeSeconds(3), + AccessibilityTimeout.s5 => l10n.timeSeconds(5), + AccessibilityTimeout.s10 => l10n.timeSeconds(10), + AccessibilityTimeout.s30 => l10n.timeSeconds(30), + }; } } extension ExtraAvesThemeBrightnessView on AvesThemeBrightness { String getName(BuildContext context) { - switch (this) { - case AvesThemeBrightness.system: - return context.l10n.settingsSystemDefault; - case AvesThemeBrightness.light: - return context.l10n.themeBrightnessLight; - case AvesThemeBrightness.dark: - return context.l10n.themeBrightnessDark; - case AvesThemeBrightness.black: - return context.l10n.themeBrightnessBlack; - } + final l10n = context.l10n; + return switch (this) { + AvesThemeBrightness.system => l10n.settingsSystemDefault, + AvesThemeBrightness.light => l10n.themeBrightnessLight, + AvesThemeBrightness.dark => l10n.themeBrightnessDark, + AvesThemeBrightness.black => l10n.themeBrightnessBlack, + }; } } extension ExtraCoordinateFormatView on CoordinateFormat { String getName(BuildContext context) { - switch (this) { - case CoordinateFormat.dms: - return context.l10n.coordinateFormatDms; - case CoordinateFormat.decimal: - return context.l10n.coordinateFormatDecimal; - } + final l10n = context.l10n; + return switch (this) { + CoordinateFormat.dms => l10n.coordinateFormatDms, + CoordinateFormat.decimal => l10n.coordinateFormatDecimal, + }; } } extension ExtraDisplayRefreshRateModeView on DisplayRefreshRateMode { String getName(BuildContext context) { - switch (this) { - case DisplayRefreshRateMode.auto: - return context.l10n.settingsSystemDefault; - case DisplayRefreshRateMode.highest: - return context.l10n.displayRefreshRatePreferHighest; - case DisplayRefreshRateMode.lowest: - return context.l10n.displayRefreshRatePreferLowest; - } + final l10n = context.l10n; + return switch (this) { + DisplayRefreshRateMode.auto => l10n.settingsSystemDefault, + DisplayRefreshRateMode.highest => l10n.displayRefreshRatePreferHighest, + DisplayRefreshRateMode.lowest => l10n.displayRefreshRatePreferLowest, + }; } } extension ExtraEntryMapStyleView on EntryMapStyle { String getName(BuildContext context) { - switch (this) { - case EntryMapStyle.googleNormal: - return context.l10n.mapStyleGoogleNormal; - case EntryMapStyle.googleHybrid: - return context.l10n.mapStyleGoogleHybrid; - case EntryMapStyle.googleTerrain: - return context.l10n.mapStyleGoogleTerrain; - case EntryMapStyle.hmsNormal: - return context.l10n.mapStyleHuaweiNormal; - case EntryMapStyle.hmsTerrain: - return context.l10n.mapStyleHuaweiTerrain; - case EntryMapStyle.osmHot: - return context.l10n.mapStyleOsmHot; - case EntryMapStyle.stamenToner: - return context.l10n.mapStyleStamenToner; - case EntryMapStyle.stamenWatercolor: - return context.l10n.mapStyleStamenWatercolor; - } + final l10n = context.l10n; + return switch (this) { + EntryMapStyle.googleNormal => l10n.mapStyleGoogleNormal, + EntryMapStyle.googleHybrid => l10n.mapStyleGoogleHybrid, + EntryMapStyle.googleTerrain => l10n.mapStyleGoogleTerrain, + EntryMapStyle.hmsNormal => l10n.mapStyleHuaweiNormal, + EntryMapStyle.hmsTerrain => l10n.mapStyleHuaweiTerrain, + EntryMapStyle.osmHot => l10n.mapStyleOsmHot, + EntryMapStyle.stamenToner => l10n.mapStyleStamenToner, + EntryMapStyle.stamenWatercolor => l10n.mapStyleStamenWatercolor, + }; } } extension ExtraHomePageSettingView on HomePageSetting { String getName(BuildContext context) { - switch (this) { - case HomePageSetting.collection: - return context.l10n.drawerCollectionAll; - case HomePageSetting.albums: - return context.l10n.drawerAlbumPage; - case HomePageSetting.tags: - return context.l10n.drawerTagPage; - } + final l10n = context.l10n; + return switch (this) { + HomePageSetting.collection => l10n.drawerCollectionAll, + HomePageSetting.albums => l10n.drawerAlbumPage, + HomePageSetting.tags => l10n.drawerTagPage, + }; } } extension ExtraKeepScreenOnView on KeepScreenOn { String getName(BuildContext context) { - switch (this) { - case KeepScreenOn.never: - return context.l10n.keepScreenOnNever; - case KeepScreenOn.videoPlayback: - return context.l10n.keepScreenOnVideoPlayback; - case KeepScreenOn.viewerOnly: - return context.l10n.keepScreenOnViewerOnly; - case KeepScreenOn.always: - return context.l10n.keepScreenOnAlways; - } + final l10n = context.l10n; + return switch (this) { + KeepScreenOn.never => l10n.keepScreenOnNever, + KeepScreenOn.videoPlayback => l10n.keepScreenOnVideoPlayback, + KeepScreenOn.viewerOnly => l10n.keepScreenOnViewerOnly, + KeepScreenOn.always => l10n.keepScreenOnAlways, + }; } } extension ExtraMaxBrightnessView on MaxBrightness { String getName(BuildContext context) { - switch (this) { - case MaxBrightness.never: - return context.l10n.maxBrightnessNever; - case MaxBrightness.viewerOnly: - return context.l10n.keepScreenOnViewerOnly; - case MaxBrightness.always: - return context.l10n.maxBrightnessAlways; - } + final l10n = context.l10n; + return switch (this) { + MaxBrightness.never => l10n.maxBrightnessNever, + MaxBrightness.viewerOnly => l10n.keepScreenOnViewerOnly, + MaxBrightness.always => l10n.maxBrightnessAlways, + }; } } extension ExtraSlideshowVideoPlaybackView on SlideshowVideoPlayback { String getName(BuildContext context) { - switch (this) { - case SlideshowVideoPlayback.skip: - return context.l10n.videoPlaybackSkip; - case SlideshowVideoPlayback.playMuted: - return context.l10n.videoPlaybackMuted; - case SlideshowVideoPlayback.playWithSound: - return context.l10n.videoPlaybackWithSound; - } + final l10n = context.l10n; + return switch (this) { + SlideshowVideoPlayback.skip => l10n.videoPlaybackSkip, + SlideshowVideoPlayback.playMuted => l10n.videoPlaybackMuted, + SlideshowVideoPlayback.playWithSound => l10n.videoPlaybackWithSound, + }; + } +} + +extension ExtraOverlayHistogramStyleView on OverlayHistogramStyle { + String getName(BuildContext context) { + final l10n = context.l10n; + return switch (this) { + OverlayHistogramStyle.none => l10n.overlayHistogramNone, + OverlayHistogramStyle.rgb => l10n.overlayHistogramRGB, + OverlayHistogramStyle.luminance => l10n.overlayHistogramLuminance, + }; } } extension ExtraSubtitlePositionView on SubtitlePosition { String getName(BuildContext context) { - switch (this) { - case SubtitlePosition.top: - return context.l10n.subtitlePositionTop; - case SubtitlePosition.bottom: - return context.l10n.subtitlePositionBottom; - } + final l10n = context.l10n; + return switch (this) { + SubtitlePosition.top => l10n.subtitlePositionTop, + SubtitlePosition.bottom => l10n.subtitlePositionBottom, + }; } } extension ExtraUnitSystemView on UnitSystem { String getName(BuildContext context) { - switch (this) { - case UnitSystem.metric: - return context.l10n.unitSystemMetric; - case UnitSystem.imperial: - return context.l10n.unitSystemImperial; - } + final l10n = context.l10n; + return switch (this) { + UnitSystem.metric => l10n.unitSystemMetric, + UnitSystem.imperial => l10n.unitSystemImperial, + }; } } extension ExtraVideoAutoPlayModeView on VideoAutoPlayMode { String getName(BuildContext context) { - switch (this) { - case VideoAutoPlayMode.disabled: - return context.l10n.settingsDisabled; - case VideoAutoPlayMode.playMuted: - return context.l10n.videoPlaybackMuted; - case VideoAutoPlayMode.playWithSound: - return context.l10n.videoPlaybackWithSound; - } + final l10n = context.l10n; + return switch (this) { + VideoAutoPlayMode.disabled => l10n.settingsDisabled, + VideoAutoPlayMode.playMuted => l10n.videoPlaybackMuted, + VideoAutoPlayMode.playWithSound => l10n.videoPlaybackWithSound, + }; } } extension ExtraVideoBackgroundModeView on VideoBackgroundMode { String getName(BuildContext context) { - switch (this) { - case VideoBackgroundMode.disabled: - return context.l10n.settingsDisabled; - case VideoBackgroundMode.pip: - return context.l10n.settingsVideoEnablePip; - } + final l10n = context.l10n; + return switch (this) { + VideoBackgroundMode.disabled => l10n.settingsDisabled, + VideoBackgroundMode.pip => l10n.settingsVideoEnablePip, + }; } } extension ExtraVideoControlsView on VideoControls { String getName(BuildContext context) { - switch (this) { - case VideoControls.play: - return context.l10n.videoControlsPlay; - case VideoControls.playSeek: - return context.l10n.videoControlsPlaySeek; - case VideoControls.playOutside: - return context.l10n.videoControlsPlayOutside; - case VideoControls.none: - return context.l10n.videoControlsNone; - } + final l10n = context.l10n; + return switch (this) { + VideoControls.play => l10n.videoControlsPlay, + VideoControls.playSeek => l10n.videoControlsPlaySeek, + VideoControls.playOutside => l10n.videoControlsPlayOutside, + VideoControls.none => l10n.videoControlsNone, + }; } } extension ExtraVideoLoopModeView on VideoLoopMode { String getName(BuildContext context) { - switch (this) { - case VideoLoopMode.never: - return context.l10n.videoLoopModeNever; - case VideoLoopMode.shortOnly: - return context.l10n.videoLoopModeShortOnly; - case VideoLoopMode.always: - return context.l10n.videoLoopModeAlways; - } + final l10n = context.l10n; + return switch (this) { + VideoLoopMode.never => l10n.videoLoopModeNever, + VideoLoopMode.shortOnly => l10n.videoLoopModeShortOnly, + VideoLoopMode.always => l10n.videoLoopModeAlways, + }; } } extension ExtraVideoResumptionModeView on VideoResumptionMode { String getName(BuildContext context) { - switch (this) { - case VideoResumptionMode.never: - return context.l10n.videoResumptionModeNever; - case VideoResumptionMode.ask: - return context.l10n.settingsAskEverytime; - case VideoResumptionMode.always: - return context.l10n.videoResumptionModeAlways; - } + final l10n = context.l10n; + return switch (this) { + VideoResumptionMode.never => l10n.videoResumptionModeNever, + VideoResumptionMode.ask => l10n.settingsAskEverytime, + VideoResumptionMode.always => l10n.videoResumptionModeAlways, + }; } } extension ExtraViewerTransitionView on ViewerTransition { String getName(BuildContext context) { - switch (this) { - case ViewerTransition.slide: - return context.l10n.viewerTransitionSlide; - case ViewerTransition.parallax: - return context.l10n.viewerTransitionParallax; - case ViewerTransition.fade: - return context.l10n.viewerTransitionFade; - case ViewerTransition.zoomIn: - return context.l10n.viewerTransitionZoomIn; - case ViewerTransition.none: - return context.l10n.viewerTransitionNone; - case ViewerTransition.random: - return context.l10n.widgetDisplayedItemRandom; - } + final l10n = context.l10n; + return switch (this) { + ViewerTransition.slide => l10n.viewerTransitionSlide, + ViewerTransition.parallax => l10n.viewerTransitionParallax, + ViewerTransition.fade => l10n.viewerTransitionFade, + ViewerTransition.zoomIn => l10n.viewerTransitionZoomIn, + ViewerTransition.none => l10n.viewerTransitionNone, + ViewerTransition.random => l10n.widgetDisplayedItemRandom, + }; } } extension ExtraWidgetDisplayedItemView on WidgetDisplayedItem { String getName(BuildContext context) { - switch (this) { - case WidgetDisplayedItem.random: - return context.l10n.widgetDisplayedItemRandom; - case WidgetDisplayedItem.mostRecent: - return context.l10n.widgetDisplayedItemMostRecent; - } + final l10n = context.l10n; + return switch (this) { + WidgetDisplayedItem.random => l10n.widgetDisplayedItemRandom, + WidgetDisplayedItem.mostRecent => l10n.widgetDisplayedItemMostRecent, + }; } } extension ExtraWidgetOpenPageView on WidgetOpenPage { String getName(BuildContext context) { - switch (this) { - case WidgetOpenPage.home: - return context.l10n.widgetOpenPageHome; - case WidgetOpenPage.collection: - return context.l10n.widgetOpenPageCollection; - case WidgetOpenPage.viewer: - return context.l10n.widgetOpenPageViewer; - case WidgetOpenPage.updateWidget: - return context.l10n.widgetTapUpdateWidget; - } + final l10n = context.l10n; + return switch (this) { + WidgetOpenPage.home => l10n.widgetOpenPageHome, + WidgetOpenPage.collection => l10n.widgetOpenPageCollection, + WidgetOpenPage.viewer => l10n.widgetOpenPageViewer, + WidgetOpenPage.updateWidget => l10n.widgetTapUpdateWidget, + }; } } diff --git a/lib/view/src/settings/thumbnail_overlay_location_icon.dart b/lib/view/src/settings/thumbnail_overlay_location_icon.dart index e479cf91f..3ff80ed45 100644 --- a/lib/view/src/settings/thumbnail_overlay_location_icon.dart +++ b/lib/view/src/settings/thumbnail_overlay_location_icon.dart @@ -5,23 +5,18 @@ import 'package:flutter/widgets.dart'; extension ExtraThumbnailOverlayLocationIconView on ThumbnailOverlayLocationIcon { String getName(BuildContext context) { - switch (this) { - case ThumbnailOverlayLocationIcon.located: - return context.l10n.filterLocatedLabel; - case ThumbnailOverlayLocationIcon.unlocated: - return context.l10n.filterNoLocationLabel; - case ThumbnailOverlayLocationIcon.none: - return context.l10n.settingsDisabled; - } + final l10n = context.l10n; + return switch (this) { + ThumbnailOverlayLocationIcon.located => l10n.filterLocatedLabel, + ThumbnailOverlayLocationIcon.unlocated => l10n.filterNoLocationLabel, + ThumbnailOverlayLocationIcon.none => l10n.settingsDisabled, + }; } IconData getIcon(BuildContext context) { - switch (this) { - case ThumbnailOverlayLocationIcon.unlocated: - return AIcons.locationUnlocated; - case ThumbnailOverlayLocationIcon.located: - case ThumbnailOverlayLocationIcon.none: - return AIcons.location; - } + return switch (this) { + ThumbnailOverlayLocationIcon.unlocated => AIcons.locationUnlocated, + ThumbnailOverlayLocationIcon.located || ThumbnailOverlayLocationIcon.none => AIcons.location, + }; } } diff --git a/lib/view/src/settings/thumbnail_overlay_tag_icon.dart b/lib/view/src/settings/thumbnail_overlay_tag_icon.dart index 983d18ffe..deae99e3b 100644 --- a/lib/view/src/settings/thumbnail_overlay_tag_icon.dart +++ b/lib/view/src/settings/thumbnail_overlay_tag_icon.dart @@ -5,24 +5,19 @@ import 'package:flutter/widgets.dart'; extension ExtraThumbnailOverlayTagIconView on ThumbnailOverlayTagIcon { String getName(BuildContext context) { - switch (this) { - case ThumbnailOverlayTagIcon.tagged: - return context.l10n.filterTaggedLabel; - case ThumbnailOverlayTagIcon.untagged: - return context.l10n.filterNoTagLabel; - case ThumbnailOverlayTagIcon.none: - return context.l10n.settingsDisabled; - } + final l10n = context.l10n; + return switch (this) { + ThumbnailOverlayTagIcon.tagged => l10n.filterTaggedLabel, + ThumbnailOverlayTagIcon.untagged => l10n.filterNoTagLabel, + ThumbnailOverlayTagIcon.none => l10n.settingsDisabled, + }; } IconData getIcon(BuildContext context) { - switch (this) { - case ThumbnailOverlayTagIcon.tagged: - return AIcons.tag; - case ThumbnailOverlayTagIcon.untagged: - return AIcons.tagUntagged; - case ThumbnailOverlayTagIcon.none: - return AIcons.tag; - } + return switch (this) { + ThumbnailOverlayTagIcon.tagged => AIcons.tag, + ThumbnailOverlayTagIcon.untagged => AIcons.tagUntagged, + ThumbnailOverlayTagIcon.none => AIcons.tag, + }; } } diff --git a/lib/view/src/source/album.dart b/lib/view/src/source/album.dart index 5706431dc..2ac79467a 100644 --- a/lib/view/src/source/album.dart +++ b/lib/view/src/source/album.dart @@ -4,21 +4,14 @@ import 'package:flutter/widgets.dart'; extension ExtraAlbumTypeView on AlbumType { String? getName(BuildContext context) { - switch (this) { - case AlbumType.camera: - return context.l10n.albumCamera; - case AlbumType.download: - return context.l10n.albumDownload; - case AlbumType.screenshots: - return context.l10n.albumScreenshots; - case AlbumType.screenRecordings: - return context.l10n.albumScreenRecordings; - case AlbumType.videoCaptures: - return context.l10n.albumVideoCaptures; - case AlbumType.regular: - case AlbumType.vault: - case AlbumType.app: - return null; - } + final l10n = context.l10n; + return switch (this) { + AlbumType.camera => l10n.albumCamera, + AlbumType.download => l10n.albumDownload, + AlbumType.screenshots => l10n.albumScreenshots, + AlbumType.screenRecordings => l10n.albumScreenRecordings, + AlbumType.videoCaptures => l10n.albumVideoCaptures, + AlbumType.regular || AlbumType.vault || AlbumType.app => null, + }; } } diff --git a/lib/view/src/source/group.dart b/lib/view/src/source/group.dart index 851b72292..45e04767c 100644 --- a/lib/view/src/source/group.dart +++ b/lib/view/src/source/group.dart @@ -6,57 +6,41 @@ import 'package:flutter/widgets.dart'; extension ExtraEntryGroupFactorView on EntryGroupFactor { String getName(BuildContext context) { final l10n = context.l10n; - switch (this) { - case EntryGroupFactor.album: - return l10n.collectionGroupAlbum; - case EntryGroupFactor.month: - return l10n.collectionGroupMonth; - case EntryGroupFactor.day: - return l10n.collectionGroupDay; - case EntryGroupFactor.none: - return l10n.collectionGroupNone; - } + return switch (this) { + EntryGroupFactor.album => l10n.collectionGroupAlbum, + EntryGroupFactor.month => l10n.collectionGroupMonth, + EntryGroupFactor.day => l10n.collectionGroupDay, + EntryGroupFactor.none => l10n.collectionGroupNone, + }; } IconData get icon { - switch (this) { - case EntryGroupFactor.album: - return AIcons.album; - case EntryGroupFactor.month: - return AIcons.dateByMonth; - case EntryGroupFactor.day: - return AIcons.dateByDay; - case EntryGroupFactor.none: - return AIcons.clear; - } + return switch (this) { + EntryGroupFactor.album => AIcons.album, + EntryGroupFactor.month => AIcons.dateByMonth, + EntryGroupFactor.day => AIcons.dateByDay, + EntryGroupFactor.none => AIcons.clear, + }; } } extension ExtraAlbumChipGroupFactorView on AlbumChipGroupFactor { String getName(BuildContext context) { final l10n = context.l10n; - switch (this) { - case AlbumChipGroupFactor.importance: - return l10n.albumGroupTier; - case AlbumChipGroupFactor.mimeType: - return l10n.albumGroupType; - case AlbumChipGroupFactor.volume: - return l10n.albumGroupVolume; - case AlbumChipGroupFactor.none: - return l10n.albumGroupNone; - } + return switch (this) { + AlbumChipGroupFactor.importance => l10n.albumGroupTier, + AlbumChipGroupFactor.mimeType => l10n.albumGroupType, + AlbumChipGroupFactor.volume => l10n.albumGroupVolume, + AlbumChipGroupFactor.none => l10n.albumGroupNone, + }; } IconData get icon { - switch (this) { - case AlbumChipGroupFactor.importance: - return AIcons.important; - case AlbumChipGroupFactor.mimeType: - return AIcons.mimeType; - case AlbumChipGroupFactor.volume: - return AIcons.removableStorage; - case AlbumChipGroupFactor.none: - return AIcons.clear; - } + return switch (this) { + AlbumChipGroupFactor.importance => AIcons.important, + AlbumChipGroupFactor.mimeType => AIcons.mimeType, + AlbumChipGroupFactor.volume => AIcons.removableStorage, + AlbumChipGroupFactor.none => AIcons.clear, + }; } } diff --git a/lib/view/src/source/layout.dart b/lib/view/src/source/layout.dart index 58d3e8161..bb17e1138 100644 --- a/lib/view/src/source/layout.dart +++ b/lib/view/src/source/layout.dart @@ -6,24 +6,18 @@ import 'package:flutter/widgets.dart'; extension ExtraTileLayoutView on TileLayout { String getName(BuildContext context) { final l10n = context.l10n; - switch (this) { - case TileLayout.mosaic: - return l10n.tileLayoutMosaic; - case TileLayout.grid: - return l10n.tileLayoutGrid; - case TileLayout.list: - return l10n.tileLayoutList; - } + return switch (this) { + TileLayout.mosaic => l10n.tileLayoutMosaic, + TileLayout.grid => l10n.tileLayoutGrid, + TileLayout.list => l10n.tileLayoutList, + }; } IconData get icon { - switch (this) { - case TileLayout.mosaic: - return AIcons.layoutMosaic; - case TileLayout.grid: - return AIcons.layoutGrid; - case TileLayout.list: - return AIcons.layoutList; - } + return switch (this) { + TileLayout.mosaic => AIcons.layoutMosaic, + TileLayout.grid => AIcons.layoutGrid, + TileLayout.list => AIcons.layoutList, + }; } } diff --git a/lib/view/src/source/sort.dart b/lib/view/src/source/sort.dart index 705d0238f..56188edd2 100644 --- a/lib/view/src/source/sort.dart +++ b/lib/view/src/source/sort.dart @@ -6,84 +6,60 @@ import 'package:flutter/widgets.dart'; extension ExtraEntrySortFactorView on EntrySortFactor { String getName(BuildContext context) { final l10n = context.l10n; - switch (this) { - case EntrySortFactor.date: - return l10n.sortByDate; - case EntrySortFactor.name: - return l10n.sortByAlbumFileName; - case EntrySortFactor.rating: - return l10n.sortByRating; - case EntrySortFactor.size: - return l10n.sortBySize; - } + return switch (this) { + EntrySortFactor.date => l10n.sortByDate, + EntrySortFactor.name => l10n.sortByAlbumFileName, + EntrySortFactor.rating => l10n.sortByRating, + EntrySortFactor.size => l10n.sortBySize, + }; } IconData get icon { - switch (this) { - case EntrySortFactor.date: - return AIcons.date; - case EntrySortFactor.name: - return AIcons.name; - case EntrySortFactor.rating: - return AIcons.rating; - case EntrySortFactor.size: - return AIcons.size; - } + return switch (this) { + EntrySortFactor.date => AIcons.date, + EntrySortFactor.name => AIcons.name, + EntrySortFactor.rating => AIcons.rating, + EntrySortFactor.size => AIcons.size, + }; } String getOrderName(BuildContext context, bool reverse) { final l10n = context.l10n; - switch (this) { - case EntrySortFactor.date: - return reverse ? l10n.sortOrderOldestFirst : l10n.sortOrderNewestFirst; - case EntrySortFactor.name: - return reverse ? l10n.sortOrderZtoA : l10n.sortOrderAtoZ; - case EntrySortFactor.rating: - return reverse ? l10n.sortOrderLowestFirst : l10n.sortOrderHighestFirst; - case EntrySortFactor.size: - return reverse ? l10n.sortOrderSmallestFirst : l10n.sortOrderLargestFirst; - } + return switch (this) { + EntrySortFactor.date => reverse ? l10n.sortOrderOldestFirst : l10n.sortOrderNewestFirst, + EntrySortFactor.name => reverse ? l10n.sortOrderZtoA : l10n.sortOrderAtoZ, + EntrySortFactor.rating => reverse ? l10n.sortOrderLowestFirst : l10n.sortOrderHighestFirst, + EntrySortFactor.size => reverse ? l10n.sortOrderSmallestFirst : l10n.sortOrderLargestFirst, + }; } } extension ExtraChipSortFactorView on ChipSortFactor { String getName(BuildContext context) { final l10n = context.l10n; - switch (this) { - case ChipSortFactor.date: - return l10n.sortByDate; - case ChipSortFactor.name: - return l10n.sortByName; - case ChipSortFactor.count: - return l10n.sortByItemCount; - case ChipSortFactor.size: - return l10n.sortBySize; - } + return switch (this) { + ChipSortFactor.date => l10n.sortByDate, + ChipSortFactor.name => l10n.sortByName, + ChipSortFactor.count => l10n.sortByItemCount, + ChipSortFactor.size => l10n.sortBySize, + }; } IconData get icon { - switch (this) { - case ChipSortFactor.date: - return AIcons.date; - case ChipSortFactor.name: - return AIcons.name; - case ChipSortFactor.count: - return AIcons.count; - case ChipSortFactor.size: - return AIcons.size; - } + return switch (this) { + ChipSortFactor.date => AIcons.date, + ChipSortFactor.name => AIcons.name, + ChipSortFactor.count => AIcons.count, + ChipSortFactor.size => AIcons.size, + }; } String getOrderName(BuildContext context, bool reverse) { final l10n = context.l10n; - switch (this) { - case ChipSortFactor.date: - return reverse ? l10n.sortOrderOldestFirst : l10n.sortOrderNewestFirst; - case ChipSortFactor.name: - return reverse ? l10n.sortOrderZtoA : l10n.sortOrderAtoZ; - case ChipSortFactor.count: - case ChipSortFactor.size: - return reverse ? l10n.sortOrderSmallestFirst : l10n.sortOrderLargestFirst; - } + return switch (this) { + ChipSortFactor.date => reverse ? l10n.sortOrderOldestFirst : l10n.sortOrderNewestFirst, + ChipSortFactor.name => reverse ? l10n.sortOrderZtoA : l10n.sortOrderAtoZ, + ChipSortFactor.count || ChipSortFactor.size => reverse ? l10n.sortOrderSmallestFirst : l10n.sortOrderLargestFirst, + }; } } diff --git a/lib/view/src/source/state.dart b/lib/view/src/source/state.dart index 7b23db094..9adc287d1 100644 --- a/lib/view/src/source/state.dart +++ b/lib/view/src/source/state.dart @@ -3,17 +3,12 @@ import 'package:aves_model/aves_model.dart'; extension ExtraSourceStateView on SourceState { String? getName(AppLocalizations l10n) { - switch (this) { - case SourceState.loading: - return l10n.sourceStateLoading; - case SourceState.cataloguing: - return l10n.sourceStateCataloguing; - case SourceState.locatingCountries: - return l10n.sourceStateLocatingCountries; - case SourceState.locatingPlaces: - return l10n.sourceStateLocatingPlaces; - case SourceState.ready: - return null; - } + return switch (this) { + SourceState.loading => l10n.sourceStateLoading, + SourceState.cataloguing => l10n.sourceStateCataloguing, + SourceState.locatingCountries => l10n.sourceStateLocatingCountries, + SourceState.locatingPlaces => l10n.sourceStateLocatingPlaces, + SourceState.ready => null, + }; } } diff --git a/lib/view/src/source/vault.dart b/lib/view/src/source/vault.dart index e72b42ad9..10c9ad16a 100644 --- a/lib/view/src/source/vault.dart +++ b/lib/view/src/source/vault.dart @@ -4,15 +4,12 @@ import 'package:flutter/widgets.dart'; extension ExtraVaultLockTypeView on VaultLockType { String getText(BuildContext context) { - switch (this) { - case VaultLockType.system: - return context.l10n.settingsSystemDefault; - case VaultLockType.pattern: - return context.l10n.vaultLockTypePattern; - case VaultLockType.pin: - return context.l10n.vaultLockTypePin; - case VaultLockType.password: - return context.l10n.vaultLockTypePassword; - } + final l10n = context.l10n; + return switch (this) { + VaultLockType.system => l10n.settingsSystemDefault, + VaultLockType.pattern => l10n.vaultLockTypePattern, + VaultLockType.pin => l10n.vaultLockTypePin, + VaultLockType.password => l10n.vaultLockTypePassword, + }; } } diff --git a/lib/view/src/wallpaper_target.dart b/lib/view/src/wallpaper_target.dart index 597f0bdb4..23d4dcb41 100644 --- a/lib/view/src/wallpaper_target.dart +++ b/lib/view/src/wallpaper_target.dart @@ -4,13 +4,11 @@ import 'package:flutter/widgets.dart'; extension ExtraWallpaperTargetView on WallpaperTarget { String getName(BuildContext context) { - switch (this) { - case WallpaperTarget.home: - return context.l10n.wallpaperTargetHome; - case WallpaperTarget.lock: - return context.l10n.wallpaperTargetLock; - case WallpaperTarget.homeLock: - return context.l10n.wallpaperTargetHomeLock; - } + final l10n = context.l10n; + return switch (this) { + WallpaperTarget.home => l10n.wallpaperTargetHome, + WallpaperTarget.lock => l10n.wallpaperTargetLock, + WallpaperTarget.homeLock => l10n.wallpaperTargetHomeLock, + }; } } diff --git a/lib/widgets/about/about_mobile_page.dart b/lib/widgets/about/about_mobile_page.dart index 995c49bf7..53aee3388 100644 --- a/lib/widgets/about/about_mobile_page.dart +++ b/lib/widgets/about/about_mobile_page.dart @@ -1,6 +1,7 @@ import 'package:aves/widgets/about/app_ref.dart'; import 'package:aves/widgets/about/bug_report.dart'; import 'package:aves/widgets/about/credits.dart'; +import 'package:aves/widgets/about/data_usage.dart'; import 'package:aves/widgets/about/licenses.dart'; import 'package:aves/widgets/about/translators.dart'; import 'package:aves/widgets/common/basic/insets.dart'; @@ -31,6 +32,8 @@ class AboutMobilePage extends StatelessWidget { const Divider(), const BugReport(), const Divider(), + const AboutDataUsage(), + const Divider(), const AboutCredits(), const Divider(), const AboutTranslators(), diff --git a/lib/widgets/about/app_ref.dart b/lib/widgets/about/app_ref.dart index af20e6d55..99d814f00 100644 --- a/lib/widgets/about/app_ref.dart +++ b/lib/widgets/about/app_ref.dart @@ -19,7 +19,7 @@ class AppReference extends StatefulWidget { static List buildLinks(BuildContext context) { final l10n = context.l10n; return [ - const LinkChip( + LinkChip( leading: Icon( AIcons.github, size: 24, @@ -28,7 +28,7 @@ class AppReference extends StatefulWidget { urlString: AppReference.avesGithub, ), LinkChip( - leading: const Icon( + leading: Icon( AIcons.legal, size: 22, ), @@ -36,7 +36,7 @@ class AppReference extends StatefulWidget { urlString: '${AppReference.avesGithub}/blob/main/LICENSE', ), LinkChip( - leading: const Icon( + leading: Icon( AIcons.privacy, size: 22, ), diff --git a/lib/widgets/about/bug_report.dart b/lib/widgets/about/bug_report.dart index 6cb94eb44..6c5ad4a43 100644 --- a/lib/widgets/about/bug_report.dart +++ b/lib/widgets/about/bug_report.dart @@ -49,7 +49,7 @@ class _BugReportState extends State with FeedbackMixin { final animationDuration = context.select((v) => v.expansionTileAnimation); return ExpansionPanelList( expansionCallback: (index, isExpanded) { - setState(() => _showInstructions = !isExpanded); + setState(() => _showInstructions = isExpanded); }, animationDuration: animationDuration, expandedHeaderPadding: EdgeInsets.zero, @@ -174,16 +174,16 @@ class _BugReportState extends State with FeedbackMixin { ); if (success != null) { if (success) { - showFeedback(context, context.l10n.genericSuccessFeedback); + showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback); } else { - showFeedback(context, context.l10n.genericFailureFeedback); + showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback); } } } Future _copySystemInfo() async { await Clipboard.setData(ClipboardData(text: await _infoLoader)); - showFeedback(context, context.l10n.genericSuccessFeedback); + showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback); } Future _goToGithub() => AvesApp.launchUrl(bugReportUrl); diff --git a/lib/widgets/about/data_usage.dart b/lib/widgets/about/data_usage.dart new file mode 100644 index 000000000..a522355cc --- /dev/null +++ b/lib/widgets/about/data_usage.dart @@ -0,0 +1,161 @@ +import 'package:aves/ref/brand_colors.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:aves/theme/colors.dart'; +import 'package:aves/theme/durations.dart'; +import 'package:aves/theme/styles.dart'; +import 'package:aves/utils/file_utils.dart'; +import 'package:aves/widgets/common/action_mixins/feedback.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/identity/aves_donut.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class AboutDataUsage extends StatefulWidget { + const AboutDataUsage({super.key}); + + @override + State createState() => _AboutDataUsageState(); +} + +class _AboutDataUsageState extends State with FeedbackMixin { + late Future> _loader; + bool _isExpanded = false; + + @override + void initState() { + super.initState(); + _loader = storageService.getDataUsage(); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final animationDuration = context.select((v) => v.expansionTileAnimation); + return ExpansionPanelList( + expansionCallback: (index, isExpanded) { + setState(() => _isExpanded = isExpanded); + }, + animationDuration: animationDuration, + expandedHeaderPadding: EdgeInsets.zero, + elevation: 0, + children: [ + ExpansionPanel( + headerBuilder: (context, isExpanded) => ConstrainedBox( + constraints: const BoxConstraints(minHeight: kMinInteractiveDimension), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16), + alignment: AlignmentDirectional.centerStart, + child: Text(l10n.aboutDataUsageSectionTitle, style: AStyles.knownTitleText), + ), + ), + body: FutureBuilder>( + future: _loader, + builder: (context, snapshot) { + final data = snapshot.data; + if (data == null) return const SizedBox(); + + final dataMap = { + DataUsageDonut.bin: data['trash'] ?? 0, + DataUsageDonut.database: data['database'] ?? 0, + DataUsageDonut.vaults: data['vaults'] ?? 0, + DataUsageDonut.misc: data['miscData'] ?? 0, + }; + final flutter = data['flutter'] ?? 0; + if (flutter > 0) { + dataMap[DataUsageDonut.flutter] = flutter; + } + final cacheMap = { + DataUsageDonut.internal: data['internalCache'] ?? 0, + DataUsageDonut.external: data['externalCache'] ?? 0, + }; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DataUsageDonut( + title: l10n.aboutDataUsageData, + byTypes: dataMap, + animationDuration: animationDuration, + ), + DataUsageDonut( + title: l10n.aboutDataUsageCache, + byTypes: cacheMap, + animationDuration: animationDuration, + ), + ], + ); + }, + ), + isExpanded: _isExpanded, + canTapOnHeader: true, + backgroundColor: Colors.transparent, + ), + ], + ); + } +} + +class DataUsageDonut extends StatelessWidget { + final String title; + final Map byTypes; + final Duration animationDuration; + + // data + static const String bin = 'bin'; + static const String database = 'database'; + static const String flutter = 'flutter'; + static const String vaults = 'vaults'; + static const String misc = 'misc'; + + // cache + static const String internal = 'internal'; + static const String external = 'external'; + + const DataUsageDonut({ + super.key, + required this.title, + required this.byTypes, + required this.animationDuration, + }); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final locale = l10n.localeName; + + return AvesDonut( + title: Text(title), + byTypes: byTypes, + animationDuration: animationDuration, + formatKey: (d) { + switch (d.key) { + case bin: + return l10n.binPageTitle; + case database: + return l10n.aboutDataUsageDatabase; + case flutter: + return 'Flutter'; + case vaults: + return l10n.albumTierVaults; + case misc: + return l10n.aboutDataUsageMisc; + case internal: + return l10n.aboutDataUsageInternal; + case external: + return l10n.aboutDataUsageExternal; + default: + return d.key; + } + }, + formatValue: (v) => formatFileSize(locale, v, round: 0), + colorize: (context, d) { + final colors = context.read(); + Color? color; + switch (d.key) { + case flutter: + color = colors.fromBrandColor(BrandColors.flutter); + } + return color ?? colors.fromString(d.key); + }, + ); + } +} diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart index 35bce20a5..f68db515d 100644 --- a/lib/widgets/aves_app.dart +++ b/lib/widgets/aves_app.dart @@ -41,7 +41,6 @@ import 'package:aves_model/aves_model.dart'; import 'package:aves_utils/aves_utils.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:equatable/equatable.dart'; -import 'package:fijkplayer/fijkplayer.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -50,7 +49,6 @@ import 'package:material_color_utilities/material_color_utilities.dart'; import 'package:overlay_support/overlay_support.dart'; import 'package:provider/provider.dart'; import 'package:screen_brightness/screen_brightness.dart'; -import 'package:tuple/tuple.dart'; import 'package:url_launcher/url_launcher.dart' as ul; class AvesApp extends StatefulWidget { @@ -67,9 +65,12 @@ class AvesApp extends StatefulWidget { 'gl', // Galician 'he', // Hebrew 'hi', // Hindi + 'kn', // Kannada 'ml', // Malayalam + 'my', // Burmese 'or', // Odia 'sk', // Slovak + 'sl', // Slovenian 'th', // Thai }.map(Locale.new).toSet(); static final List supportedLocales = AppLocalizations.supportedLocales.where((v) => !_unsupportedLocales.contains(v)).toList(); @@ -154,7 +155,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { late final Future _dynamicColorPaletteLoader; final TvRailController _tvRailController = TvRailController(); final CollectionSource _mediaStoreSource = MediaStoreSource(); - final Debouncer _mediaStoreChangeDebouncer = Debouncer(delay: Durations.mediaContentChangeDebounceDelay); + final Debouncer _mediaStoreChangeDebouncer = Debouncer(delay: ADurations.mediaContentChangeDebounceDelay); final Set _changedUris = {}; Size? _screenSize; @@ -234,16 +235,14 @@ class _AvesAppState extends State with WidgetsBindingObserver { : AvesScaffold( body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(), ); - return Selector>( - selector: (context, s) => Tuple3( + return Selector( + selector: (context, s) => ( s.locale, s.initialized ? s.themeBrightness : SettingsDefaults.themeBrightness, s.initialized ? s.enableDynamicColor : SettingsDefaults.enableDynamicColor, ), builder: (context, s, child) { - final settingsLocale = s.item1; - final themeBrightness = s.item2; - final enableDynamicColor = s.item3; + final (settingsLocale, themeBrightness, enableDynamicColor) = s; AStyles.updateStylesForLocale(settings.appliedLocale); @@ -295,8 +294,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { ...LocalizationsNn.delegates, ], supportedLocales: AvesApp.supportedLocales, - // TODO TLAD remove custom scroll behavior when this is fixed: https://github.com/flutter/flutter/issues/82906 - scrollBehavior: StretchMaterialScrollBehavior(), + scrollBehavior: AvesScrollBehavior(), ), ), ); @@ -389,6 +387,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { case AppMode.pickSingleMediaExternal: case AppMode.pickMultipleMediaExternal: _saveTopEntries(); + break; case AppMode.pickCollectionFiltersExternal: case AppMode.pickMediaInternal: case AppMode.pickFilterInternal: @@ -401,8 +400,8 @@ class _AvesAppState extends State with WidgetsBindingObserver { } case AppLifecycleState.resumed: RecentlyAddedFilter.updateNow(); - case AppLifecycleState.paused: - case AppLifecycleState.detached: + break; + default: break; } } @@ -455,8 +454,8 @@ class _AvesAppState extends State with WidgetsBindingObserver { settings.areAnimationsRemoved = await AccessibilityService.areAnimationsRemoved(); await _onTvLayoutChanged(); _monitorSettings(); + videoControllerFactory.init(); - FijkLog.setLevel(FijkLogLevel.Warn); unawaited(_setupErrorReporting()); debugPrint('App setup in ${stopwatch.elapsed.inMilliseconds}ms'); @@ -549,15 +548,15 @@ class _AvesAppState extends State with WidgetsBindingObserver { final settingStream = settings.updateStream; // app - settingStream.where((event) => event.key == Settings.isInstalledAppAccessAllowedKey).listen((_) => applyIsInstalledAppAccessAllowed()); + settingStream.where((event) => event.key == SettingKeys.isInstalledAppAccessAllowedKey).listen((_) => applyIsInstalledAppAccessAllowed()); // display - settingStream.where((event) => event.key == Settings.displayRefreshRateModeKey).listen((_) => applyDisplayRefreshRateMode()); - settingStream.where((event) => event.key == Settings.maxBrightnessKey).listen((_) => applyMaxBrightness()); - settingStream.where((event) => event.key == Settings.forceTvLayoutKey).listen((_) => applyForceTvLayout()); + settingStream.where((event) => event.key == SettingKeys.displayRefreshRateModeKey).listen((_) => applyDisplayRefreshRateMode()); + settingStream.where((event) => event.key == SettingKeys.maxBrightnessKey).listen((_) => applyMaxBrightness()); + settingStream.where((event) => event.key == SettingKeys.forceTvLayoutKey).listen((_) => applyForceTvLayout()); // navigation - settingStream.where((event) => event.key == Settings.keepScreenOnKey).listen((_) => applyKeepScreenOn()); + settingStream.where((event) => event.key == SettingKeys.keepScreenOnKey).listen((_) => applyKeepScreenOn()); // platform settings - settingStream.where((event) => event.key == Settings.platformAccelerometerRotationKey).listen((_) => applyIsRotationLocked()); + settingStream.where((event) => event.key == SettingKeys.platformAccelerometerRotationKey).listen((_) => applyIsRotationLocked()); applyDisplayRefreshRateMode(); applyMaxBrightness(); @@ -567,7 +566,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { Future _setupErrorReporting() async { await reportService.init(); - settings.updateStream.where((event) => event.key == Settings.isErrorReportingAllowedKey).listen( + settings.updateStream.where((event) => event.key == SettingKeys.isErrorReportingAllowedKey).listen( (_) => reportService.setCollectionEnabled(settings.isErrorReportingAllowed), ); await reportService.setCollectionEnabled(settings.isErrorReportingAllowed); @@ -633,13 +632,20 @@ class _AvesAppState extends State with WidgetsBindingObserver { void _onError(String? error) => reportService.recordError(error, null); } -class StretchMaterialScrollBehavior extends MaterialScrollBehavior { +class AvesScrollBehavior extends MaterialScrollBehavior { @override Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) { - return StretchingOverscrollIndicator( - axisDirection: details.direction, - child: child, - ); + final animate = context.select((v) => v.accessibilityAnimations.animate); + return animate + ? StretchingOverscrollIndicator( + axisDirection: details.direction, + child: child, + ) + : GlowingOverscrollIndicator( + axisDirection: details.direction, + color: Colors.white, + child: child, + ); } } diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index 63a181814..542cdede8 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -37,7 +37,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class CollectionAppBar extends StatefulWidget { final ValueNotifier appBarHeightNotifier; @@ -194,9 +193,9 @@ class _CollectionAppBarState extends State with SingleTickerPr ), ), if (showFilterBar) - NotificationListener( + NotificationListener( onNotification: (notification) { - collection.addFilter(notification.reversedFilter); + collection.addFilter(notification.filter); return true; }, child: FilterBar( @@ -433,7 +432,7 @@ class _CollectionAppBarState extends State with SingleTickerPr }, onSelected: (action) async { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(Durations.popupMenuAnimation * timeDilation); + await Future.delayed(ADurations.popupMenuAnimation * timeDilation); await _onActionSelected(action); }, ), @@ -647,14 +646,14 @@ class _CollectionAppBarState extends State with SingleTickerPr } Future _configureView() async { - final initialValue = Tuple4( + final initialValue = ( settings.collectionSortFactor, settings.collectionSectionFactor, settings.getTileLayout(CollectionPage.routeName), settings.collectionSortReverse, ); final extentController = context.read(); - final value = await showDialog>( + final value = await showDialog<(EntrySortFactor?, EntryGroupFactor?, TileLayout?, bool)>( context: context, builder: (context) { return TileViewDialog( @@ -670,12 +669,12 @@ class _CollectionAppBarState extends State with SingleTickerPr routeSettings: const RouteSettings(name: TileViewDialog.routeName), ); // wait for the dialog to hide as applying the change may block the UI - await Future.delayed(Durations.dialogTransitionAnimation * timeDilation); + await Future.delayed(ADurations.dialogTransitionAnimation * timeDilation); if (value != null && initialValue != value) { - settings.collectionSortFactor = value.item1!; - settings.collectionSectionFactor = value.item2!; - settings.setTileLayout(CollectionPage.routeName, value.item3!); - settings.collectionSortReverse = value.item4; + settings.collectionSortFactor = value.$1!; + settings.collectionSectionFactor = value.$2!; + settings.setTileLayout(CollectionPage.routeName, value.$3!); + settings.collectionSortReverse = value.$4; } } diff --git a/lib/widgets/collection/collection_grid.dart b/lib/widgets/collection/collection_grid.dart index 059bd0a63..974eba8bc 100644 --- a/lib/widgets/collection/collection_grid.dart +++ b/lib/widgets/collection/collection_grid.dart @@ -51,7 +51,6 @@ import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:intl/intl.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class CollectionGrid extends StatefulWidget { final String settingsRouteKey; @@ -132,13 +131,10 @@ class _CollectionGridContentState extends State<_CollectionGridContent> { valueListenable: context.select>((controller) => controller.extentNotifier), builder: (context, thumbnailExtent, child) { assert(thumbnailExtent > 0); - return Selector>( - selector: (context, c) => Tuple4(c.viewportSize.width, c.columnCount, c.spacing, c.horizontalPadding), + return Selector( + selector: (context, c) => (c.viewportSize.width, c.columnCount, c.spacing, c.horizontalPadding), builder: (context, c, child) { - final scrollableWidth = c.item1; - final columnCount = c.item2; - final tileSpacing = c.item3; - final horizontalPadding = c.item4; + final (scrollableWidth, columnCount, tileSpacing, horizontalPadding) = c; final source = collection.source; return GridTheme( extent: thumbnailExtent, @@ -369,9 +365,7 @@ class _CollectionScaler extends StatelessWidget { @override Widget build(BuildContext context) { - final metrics = context.select>((v) => Tuple2(v.spacing, v.horizontalPadding)); - final tileSpacing = metrics.item1; - final horizontalPadding = metrics.item2; + final (tileSpacing, horizontalPadding) = context.select((v) => (v.spacing, v.horizontalPadding)); final brightness = Theme.of(context).brightness; final borderColor = DecoratedThumbnail.borderColor; final borderWidth = DecoratedThumbnail.borderWidth(context); @@ -476,20 +470,13 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge @override void didChangeAppLifecycleState(AppLifecycleState state) { - switch (state) { - case AppLifecycleState.inactive: - case AppLifecycleState.paused: - case AppLifecycleState.detached: - break; - case AppLifecycleState.resumed: - if (_checkingStoragePermission) { - _checkingStoragePermission = false; - _isStoragePermissionGranted.then((granted) { - if (granted) { - widget.collection.source.init(); - } - }); + if (state == AppLifecycleState.resumed && _checkingStoragePermission) { + _checkingStoragePermission = false; + _isStoragePermissionGranted.then((granted) { + if (granted) { + widget.collection.source.init(); } + }); } } @@ -648,7 +635,7 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge void _onScrollChanged() { widget.isScrollingNotifier.value = true; _stopScrollMonitoringTimer(); - _scrollMonitoringTimer = Timer(Durations.collectionScrollMonitoringTimerDelay, () { + _scrollMonitoringTimer = Timer(ADurations.collectionScrollMonitoringTimerDelay, () { widget.isScrollingNotifier.value = false; }); } diff --git a/lib/widgets/collection/collection_page.dart b/lib/widgets/collection/collection_page.dart index 93bc20b49..867d73e73 100644 --- a/lib/widgets/collection/collection_page.dart +++ b/lib/widgets/collection/collection_page.dart @@ -27,6 +27,7 @@ import 'package:aves/widgets/common/providers/selection_provider.dart'; import 'package:aves/widgets/navigation/drawer/app_drawer.dart'; import 'package:aves/widgets/navigation/nav_bar/nav_bar.dart'; import 'package:aves/widgets/navigation/tv_rail.dart'; +import 'package:aves_model/aves_model.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -64,7 +65,7 @@ class _CollectionPageState extends State { filters: widget.filters, ); super.initState(); - _subscriptions.add(settings.updateStream.where((event) => event.key == Settings.enableBinKey).listen((_) { + _subscriptions.add(settings.updateStream.where((event) => event.key == SettingKeys.enableBinKey).listen((_) { if (!settings.enableBin) { _collection.removeFilter(TrashFilter.instance); } @@ -221,7 +222,7 @@ class _CollectionPageState extends State { if (item == null) return; final delayDuration = context.read().staggeredAnimationPageTarget; - await Future.delayed(delayDuration + Durations.highlightScrollInitDelay); + await Future.delayed(delayDuration + ADurations.highlightScrollInitDelay); if (!mounted) return; final animate = context.read().accessibilityAnimations.animate; diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index 2eb7a9bdc..564724969 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -45,7 +45,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMixin, EntryEditorMixin, EntryStorageMixin { bool isVisible( @@ -322,7 +321,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware final successCount = successOps.length; if (successCount < todoCount) { final count = todoCount - successCount; - showFeedback(context, context.l10n.collectionDeleteFailureFeedback(count)); + showFeedback(context, FeedbackType.warn, context.l10n.collectionDeleteFailureFeedback(count)); } // cleanup @@ -439,10 +438,10 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware final successCount = successOps.length; if (successCount < todoCount) { final count = todoCount - successCount; - showFeedback(context, l10n.collectionEditFailureFeedback(count)); + showFeedback(context, FeedbackType.warn, l10n.collectionEditFailureFeedback(count)); } else { final count = editedOps.length; - showFeedback(context, l10n.collectionEditSuccessFeedback(count)); + showFeedback(context, FeedbackType.info, l10n.collectionEditSuccessFeedback(count)); } } }, @@ -487,7 +486,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware if (confirmed == null || !confirmed) return null; // wait for the dialog to hide as applying the change may block the UI - await Future.delayed(Durations.dialogTransitionAnimation * timeDilation); + await Future.delayed(ADurations.dialogTransitionAnimation * timeDilation); return supported; } @@ -709,7 +708,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware final sortedFilters = List.from(filters)..sort(); defaultName = sortedFilters.first.getLabel(context).replaceAll('\n', ' '); } - final result = await showDialog>( + final result = await showDialog<(AvesEntry?, String)>( context: context, builder: (context) => AddShortcutDialog( defaultName: defaultName ?? '', @@ -719,13 +718,12 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware ); if (result == null) return; - final coverEntry = result.item1; - final name = result.item2; + final (coverEntry, name) = result; if (name.isEmpty) return; await appService.pinToHomeScreen(name, coverEntry, filters: filters); if (!device.showPinShortcutFeedback) { - showFeedback(context, context.l10n.genericSuccessFeedback); + showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback); } } } diff --git a/lib/widgets/collection/filter_bar.dart b/lib/widgets/collection/filter_bar.dart index 20f1a47ed..848c8b37a 100644 --- a/lib/widgets/collection/filter_bar.dart +++ b/lib/widgets/collection/filter_bar.dart @@ -62,7 +62,7 @@ class _FilterBarState extends State { ); } : (context, animation) => const SizedBox(), - duration: animate ? Durations.filterBarRemovalAnimation : Duration.zero, + duration: animate ? ADurations.filterBarRemovalAnimation : Duration.zero, ); }); added.forEach((filter) { diff --git a/lib/widgets/common/action_mixins/entry_storage.dart b/lib/widgets/common/action_mixins/entry_storage.dart index c41508ae1..171d3341f 100644 --- a/lib/widgets/common/action_mixins/entry_storage.dart +++ b/lib/widgets/common/action_mixins/entry_storage.dart @@ -116,12 +116,14 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin { final count = selectionCount - successCount; showFeedback( context, + FeedbackType.warn, l10n.collectionExportFailureFeedback(count), showAction, ); } else { showFeedback( context, + FeedbackType.info, l10n.genericSuccessFeedback, showAction, ); @@ -226,7 +228,11 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin { final successCount = successOps.length; if (successCount < todoCount) { final count = todoCount - successCount; - showFeedback(context, copy ? l10n.collectionCopyFailureFeedback(count) : l10n.collectionMoveFailureFeedback(count)); + showFeedback( + context, + FeedbackType.warn, + copy ? l10n.collectionCopyFailureFeedback(count) : l10n.collectionMoveFailureFeedback(count), + ); } else { final count = movedOps.length; final appMode = context.read?>()?.value; @@ -268,6 +274,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin { if (!toBin || (toBin && settings.confirmAfterMoveToBin)) { showFeedback( context, + FeedbackType.info, copy ? l10n.collectionCopySuccessFeedback(count) : l10n.collectionMoveSuccessFeedback(count), action, ); @@ -366,10 +373,10 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin { final successCount = successOps.length; if (successCount < todoCount) { final count = todoCount - successCount; - showFeedback(context, l10n.collectionRenameFailureFeedback(count)); + showFeedback(context, FeedbackType.warn, l10n.collectionRenameFailureFeedback(count)); } else { final count = movedOps.length; - showFeedback(context, l10n.collectionRenameSuccessFeedback(count)); + showFeedback(context, FeedbackType.info, l10n.collectionRenameSuccessFeedback(count)); onSuccess?.call(); } }, @@ -437,7 +444,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin { )); } else { // track in current page, without navigation - await Future.delayed(Durations.highlightScrollInitDelay); + await Future.delayed(ADurations.highlightScrollInitDelay); final targetEntry = collection.sortedEntries.firstWhereOrNull(highlightTest); if (targetEntry != null) { context.read().trackItem(targetEntry, highlightItem: targetEntry); diff --git a/lib/widgets/common/action_mixins/feedback.dart b/lib/widgets/common/action_mixins/feedback.dart index ec3ae653d..30fd47900 100644 --- a/lib/widgets/common/action_mixins/feedback.dart +++ b/lib/widgets/common/action_mixins/feedback.dart @@ -18,10 +18,12 @@ import 'package:overlay_support/overlay_support.dart'; import 'package:percent_indicator/circular_percent_indicator.dart'; import 'package:provider/provider.dart'; +enum FeedbackType { info, warn } + mixin FeedbackMixin { void dismissFeedback(BuildContext context) => ScaffoldMessenger.of(context).hideCurrentSnackBar(); - void showFeedback(BuildContext context, String message, [SnackBarAction? action]) { + void showFeedback(BuildContext context, FeedbackType type, String message, [SnackBarAction? action]) { ScaffoldMessengerState? scaffoldMessenger; try { scaffoldMessenger = ScaffoldMessenger.of(context); @@ -31,18 +33,19 @@ mixin FeedbackMixin { debugPrint('failed to find ScaffoldMessenger in context'); } if (scaffoldMessenger != null) { - showFeedbackWithMessenger(context, scaffoldMessenger, message, action); + showFeedbackWithMessenger(context, scaffoldMessenger, type, message, action); } } // provide the messenger if feedback happens as the widget is disposed - void showFeedbackWithMessenger(BuildContext context, ScaffoldMessengerState messenger, String message, [SnackBarAction? action]) { + void showFeedbackWithMessenger(BuildContext context, ScaffoldMessengerState messenger, FeedbackType type, String message, [SnackBarAction? action]) { settings.timeToTakeAction.getSnackBarDuration(action != null).then((duration) { final start = DateTime.now(); final theme = Theme.of(context); final snackBarTheme = theme.snackBarTheme; final snackBarContent = _FeedbackMessage( + type: type, message: message, progressColor: theme.colorScheme.secondary, start: start, @@ -171,7 +174,7 @@ class _ReportOverlayState extends State> with SingleTickerPr super.initState(); _animationController = AnimationController( - duration: Durations.collectionOpOverlayAnimation, + duration: ADurations.collectionOpOverlayAnimation, vsync: this, ); _animation = CurvedAnimation( @@ -274,11 +277,13 @@ class _ReportOverlayState extends State> with SingleTickerPr } class _FeedbackMessage extends StatefulWidget { + final FeedbackType type; final String message; final DateTime? start, stop; final Color progressColor; const _FeedbackMessage({ + required this.type, required this.message, required this.progressColor, this.start, @@ -326,56 +331,80 @@ class _FeedbackMessageState extends State<_FeedbackMessage> with SingleTickerPro @override Widget build(BuildContext context) { - final text = Text(widget.message); + final textScaleFactor = MediaQuery.textScaleFactorOf(context); final theme = Theme.of(context); final contentTextStyle = theme.snackBarTheme.contentTextStyle ?? ThemeData(brightness: theme.brightness).textTheme.titleMedium!; + final fontSize = theme.snackBarTheme.contentTextStyle?.fontSize ?? theme.textTheme.bodyMedium!.fontSize!; final timerChangeShadowColor = theme.colorScheme.primary; - return _remainingDurationMillis == null - ? text - : Row( - children: [ - Expanded(child: text), - const SizedBox(width: 16), - AnimatedBuilder( - animation: _remainingDurationMillis!, - builder: (context, child) { - final remainingDurationMillis = _remainingDurationMillis!.value; - return CircularIndicator( - radius: 16, - lineWidth: 2, - percent: remainingDurationMillis / _totalDurationMillis!, - background: Colors.grey, - // progress color is provided by the caller, - // because we cannot use the app context theme here - foreground: widget.progressColor, - center: ChangeHighlightText( - '${(remainingDurationMillis / 1000).ceil()}', - style: contentTextStyle.copyWith( - shadows: [ - Shadow( - color: timerChangeShadowColor.withOpacity(0), - blurRadius: 0, - ) - ], - ), - changedStyle: contentTextStyle.copyWith( - shadows: [ - Shadow( - color: timerChangeShadowColor, - blurRadius: 5, - ) - ], - ), - duration: context.read().formTextStyleTransition, - ), - ); - }, - ), - ], - ); + + return Row( + children: [ + if (widget.type == FeedbackType.warn) ...[ + CustomPaint( + painter: _WarnIndicator(), + size: Size(4, fontSize * textScaleFactor), + ), + const SizedBox(width: 8), + ], + Expanded(child: Text(widget.message)), + if (_remainingDurationMillis != null) ...[ + const SizedBox(width: 16), + AnimatedBuilder( + animation: _remainingDurationMillis!, + builder: (context, child) { + final remainingDurationMillis = _remainingDurationMillis!.value; + return CircularIndicator( + radius: 16, + lineWidth: 2, + percent: remainingDurationMillis / _totalDurationMillis!, + background: Colors.grey, + // progress color is provided by the caller, + // because we cannot use the app context theme here + foreground: widget.progressColor, + center: ChangeHighlightText( + '${(remainingDurationMillis / 1000).ceil()}', + style: contentTextStyle.copyWith( + shadows: [ + Shadow( + color: timerChangeShadowColor.withOpacity(0), + blurRadius: 0, + ) + ], + ), + changedStyle: contentTextStyle.copyWith( + shadows: [ + Shadow( + color: timerChangeShadowColor, + blurRadius: 5, + ) + ], + ), + duration: context.read().formTextStyleTransition, + ), + ); + }, + ), + ] + ], + ); } } +class _WarnIndicator extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + canvas.drawRRect( + RRect.fromLTRBR(0, 0, size.width, size.height, Radius.circular(size.shortestSide / 2)), + Paint() + ..style = PaintingStyle.fill + ..color = Colors.amber, + ); + } + + @override + bool shouldRepaint(_WarnIndicator oldDelegate) => false; +} + class ActionFeedback extends StatefulWidget { final Widget? child; @@ -395,7 +424,7 @@ class _ActionFeedbackState extends State with SingleTickerProvid void initState() { super.initState(); _animationController = AnimationController( - duration: Durations.viewerActionFeedbackAnimation, + duration: ADurations.viewerActionFeedbackAnimation, vsync: this, ); } diff --git a/lib/widgets/common/action_mixins/size_aware.dart b/lib/widgets/common/action_mixins/size_aware.dart index 10723ce6f..d8a7c32c5 100644 --- a/lib/widgets/common/action_mixins/size_aware.dart +++ b/lib/widgets/common/action_mixins/size_aware.dart @@ -30,7 +30,7 @@ mixin SizeAwareMixin { if (free == null) return true; late int needed; - int sumSize(sum, entry) => sum + (entry.sizeBytes ?? 0); + int sumSize(int sum, AvesEntry entry) => sum + (entry.sizeBytes ?? 0); switch (moveType) { case MoveType.copy: case MoveType.export: diff --git a/lib/widgets/common/action_mixins/vault_aware.dart b/lib/widgets/common/action_mixins/vault_aware.dart index d900a3382..2da5a7662 100644 --- a/lib/widgets/common/action_mixins/vault_aware.dart +++ b/lib/widgets/common/action_mixins/vault_aware.dart @@ -74,7 +74,7 @@ mixin VaultAwareMixin on FeedbackMixin { Future unlockAlbum(BuildContext context, String dirPath) async { final success = await _tryUnlock(dirPath, context); if (!success) { - showFeedback(context, context.l10n.genericFailureFeedback); + showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback); } return success; } diff --git a/lib/widgets/common/app_bar/app_bar_subtitle.dart b/lib/widgets/common/app_bar/app_bar_subtitle.dart index b0a5c751a..87fc75855 100644 --- a/lib/widgets/common/app_bar/app_bar_subtitle.dart +++ b/lib/widgets/common/app_bar/app_bar_subtitle.dart @@ -29,7 +29,7 @@ class SourceStateAwareAppBarTitle extends StatelessWidget { valueListenable: source.stateNotifier, builder: (context, sourceState, child) { return AnimatedSwitcher( - duration: Durations.appBarTitleAnimation, + duration: ADurations.appBarTitleAnimation, transitionBuilder: (child, animation) => FadeTransition( opacity: animation, child: SizeTransition( diff --git a/lib/widgets/common/basic/popup/expansion_panel.dart b/lib/widgets/common/basic/popup/expansion_panel.dart index 50b887c3f..10308dfd9 100644 --- a/lib/widgets/common/basic/popup/expansion_panel.dart +++ b/lib/widgets/common/basic/popup/expansion_panel.dart @@ -50,7 +50,7 @@ class _PopupMenuExpansionPanelState extends State> builder: (context, expandedValue, child) { return ExpansionPanelList( expansionCallback: (index, isExpanded) { - widget.expandedNotifier.value = isExpanded ? null : widget.value; + widget.expandedNotifier.value = isExpanded ? widget.value : null; }, animationDuration: animationDuration, expandedHeaderPadding: EdgeInsets.zero, diff --git a/lib/widgets/common/basic/query_bar.dart b/lib/widgets/common/basic/query_bar.dart index 2c75c93aa..24dd2637b 100644 --- a/lib/widgets/common/basic/query_bar.dart +++ b/lib/widgets/common/basic/query_bar.dart @@ -30,7 +30,7 @@ class QueryBar extends StatefulWidget { } class _QueryBarState extends State { - final Debouncer _debouncer = Debouncer(delay: Durations.searchDebounceDelay); + final Debouncer _debouncer = Debouncer(delay: ADurations.searchDebounceDelay); late TextEditingController _controller; ValueNotifier get queryNotifier => widget.queryNotifier; @@ -85,7 +85,7 @@ class _QueryBarState extends State { child: ValueListenableBuilder( valueListenable: _controller, builder: (context, value, child) => AnimatedSwitcher( - duration: Durations.appBarActionChangeAnimation, + duration: ADurations.appBarActionChangeAnimation, transitionBuilder: (child, animation) => FadeTransition( opacity: animation, child: SizeTransition( diff --git a/lib/widgets/common/basic/text/animated_diff.dart b/lib/widgets/common/basic/text/animated_diff.dart index 8ac807e14..091804f12 100644 --- a/lib/widgets/common/basic/text/animated_diff.dart +++ b/lib/widgets/common/basic/text/animated_diff.dart @@ -3,7 +3,6 @@ import 'dart:math'; import 'package:aves/utils/diff_match.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; -import 'package:tuple/tuple.dart'; class AnimatedDiffText extends StatefulWidget { final String text; @@ -71,10 +70,7 @@ class _AnimatedDiffTextState extends State with SingleTickerPr return Text.rich( TextSpan( children: _diffs.map((diff) { - final oldText = diff.item1; - final newText = diff.item2; - final oldSize = diff.item3; - final newSize = diff.item4; + final (oldText, newText, oldSize, newSize) = diff; final text = (_animation.value == 0 ? oldText : newText) ?? ''; return WidgetSpan( child: AnimatedSize( @@ -144,26 +140,22 @@ class _AnimatedDiffTextState extends State with SingleTickerPr ..addAll(d.map((diff) { final text = diff.text; final size = textSize(text); - switch (diff.operation) { - case Operation.delete: - return Tuple4(text, null, size, Size.zero); - case Operation.insert: - return Tuple4(null, text, Size.zero, size); - case Operation.equal: - default: - return Tuple4(text, text, size, size); - } + return switch (diff.operation) { + Operation.delete => (text, null, size, Size.zero), + Operation.insert => (null, text, Size.zero, size), + Operation.equal || _ => (text, text, size, size), + }; }).fold>([], (prev, v) { if (prev.isNotEmpty) { final last = prev.last; - final prevNewText = last.item2; + final prevNewText = last.$2; if (prevNewText == null) { // previous diff is a deletion - final thisOldText = v.item1; + final thisOldText = v.$1; if (thisOldText == null) { // this diff is an insertion // merge deletion and insertion as a change operation - final change = Tuple4(last.item1, v.item2, last.item3, v.item4); + final change = (last.$1, v.$2, last.$3, v.$4); return [...prev.take(prev.length - 1), change]; } } @@ -173,4 +165,4 @@ class _AnimatedDiffTextState extends State with SingleTickerPr } } -typedef _TextDiff = Tuple4; +typedef _TextDiff = (String?, String?, Size, Size); diff --git a/lib/widgets/common/behaviour/pop/double_back.dart b/lib/widgets/common/behaviour/pop/double_back.dart index 2956eefb0..e2dd820e9 100644 --- a/lib/widgets/common/behaviour/pop/double_back.dart +++ b/lib/widgets/common/behaviour/pop/double_back.dart @@ -18,10 +18,10 @@ class DoubleBackPopHandler { if (!Navigator.canPop(context) && settings.mustBackTwiceToExit && !_backOnce) { _backOnce = true; _stopBackTimer(); - _backTimer = Timer(Durations.doubleBackTimerDelay, () => _backOnce = false); + _backTimer = Timer(ADurations.doubleBackTimerDelay, () => _backOnce = false); toast( context.l10n.doubleBackExitMessage, - duration: Durations.doubleBackTimerDelay, + duration: ADurations.doubleBackTimerDelay, ); return false; } diff --git a/lib/widgets/common/behaviour/pop/tv_navigation.dart b/lib/widgets/common/behaviour/pop/tv_navigation.dart index e51367139..a8a5250ba 100644 --- a/lib/widgets/common/behaviour/pop/tv_navigation.dart +++ b/lib/widgets/common/behaviour/pop/tv_navigation.dart @@ -30,13 +30,10 @@ class TvNavigationPopHandler { if (currentRoute != homePage.routeName) return false; - switch (homePage) { - case HomePageSetting.collection: - return context.read().filters.isEmpty; - case HomePageSetting.albums: - case HomePageSetting.tags: - return true; - } + return switch (homePage) { + HomePageSetting.collection => context.read().filters.isEmpty, + HomePageSetting.albums || HomePageSetting.tags => true, + }; } static Route _getHomeRoute() { @@ -46,13 +43,10 @@ class TvNavigationPopHandler { builder: builder, ); - switch (homePage) { - case HomePageSetting.collection: - return buildRoute((context) => CollectionPage(source: context.read(), filters: null)); - case HomePageSetting.albums: - return buildRoute((context) => const AlbumListPage()); - case HomePageSetting.tags: - return buildRoute((context) => const TagListPage()); - } + return switch (homePage) { + HomePageSetting.collection => buildRoute((context) => CollectionPage(source: context.read(), filters: null)), + HomePageSetting.albums => buildRoute((context) => const AlbumListPage()), + HomePageSetting.tags => buildRoute((context) => const TagListPage()), + }; } } diff --git a/lib/widgets/common/expandable_filter_row.dart b/lib/widgets/common/expandable_filter_row.dart index a3dccd4ce..f7e3e8e0c 100644 --- a/lib/widgets/common/expandable_filter_row.dart +++ b/lib/widgets/common/expandable_filter_row.dart @@ -119,7 +119,7 @@ class ExpandableFilterRow extends StatelessWidget { Widget build(BuildContext context) { if (filters.isEmpty) return const SizedBox(); return AnimatedSwitcher( - duration: Durations.filterRowExpandAnimation, + duration: ADurations.filterRowExpandAnimation, layoutBuilder: (currentChild, previousChildren) => Stack( children: [ ...previousChildren, diff --git a/lib/widgets/common/fx/blurred.dart b/lib/widgets/common/fx/blurred.dart index 27831fdd4..bb20869b0 100644 --- a/lib/widgets/common/fx/blurred.dart +++ b/lib/widgets/common/fx/blurred.dart @@ -58,7 +58,7 @@ class BlurredRRect extends StatelessWidget { @override Widget build(BuildContext context) { return ClipRRect( - borderRadius: borderRadius, + borderRadius: borderRadius ?? BorderRadius.zero, child: BackdropFilter( // do not modify tree when disabling filter filter: enabled ? _filter : _identity, diff --git a/lib/widgets/common/fx/dashed_path_painter.dart b/lib/widgets/common/fx/dashed_path_painter.dart index 5d0c36d56..847ca422f 100644 --- a/lib/widgets/common/fx/dashed_path_painter.dart +++ b/lib/widgets/common/fx/dashed_path_painter.dart @@ -1,5 +1,5 @@ -import 'dart:ui' as ui; import 'dart:math' as math; +import 'dart:ui' as ui; import 'package:flutter/material.dart'; diff --git a/lib/widgets/common/fx/sweeper.dart b/lib/widgets/common/fx/sweeper.dart index 3b387834f..bb01c693b 100644 --- a/lib/widgets/common/fx/sweeper.dart +++ b/lib/widgets/common/fx/sweeper.dart @@ -40,7 +40,7 @@ class _SweeperState extends State with SingleTickerProviderStateMixin { void initState() { super.initState(); _angleAnimationController = AnimationController( - duration: Durations.sweepingAnimation, + duration: ADurations.sweepingAnimation, vsync: this, ); final startAngle = widget.startAngle; @@ -85,7 +85,7 @@ class _SweeperState extends State with SingleTickerProviderStateMixin { return IgnorePointer( child: AnimatedOpacity( opacity: isToggled && (_isAppearing || _angleAnimationController.status == AnimationStatus.forward) ? 1 : 0, - duration: Durations.sweeperOpacityAnimation, + duration: ADurations.sweeperOpacityAnimation, child: ValueListenableBuilder( valueListenable: _angleAnimationController, builder: (context, value, child) { @@ -112,7 +112,7 @@ class _SweeperState extends State with SingleTickerProviderStateMixin { if (isToggled) { _isAppearing = true; setState(() {}); - await Future.delayed(Durations.sweeperOpacityAnimation * timeDilation); + await Future.delayed(ADurations.sweeperOpacityAnimation * timeDilation); _isAppearing = false; if (mounted) { _angleAnimationController.reset(); diff --git a/lib/widgets/common/grid/header.dart b/lib/widgets/common/grid/header.dart index 16be78103..ce4213cc2 100644 --- a/lib/widgets/common/grid/header.dart +++ b/lib/widgets/common/grid/header.dart @@ -182,7 +182,7 @@ class _SectionSelectableLeading extends StatelessWidget { descendantsAreFocusable: false, descendantsAreTraversable: false, child: AnimatedSwitcher( - duration: Durations.sectionHeaderAnimation, + duration: ADurations.sectionHeaderAnimation, switchInCurve: Curves.easeInOut, switchOutCurve: Curves.easeInOut, transitionBuilder: (child, animation) { @@ -225,7 +225,7 @@ class _SectionSelectingLeading extends StatelessWidget { final selection = context.watch>(); final isSelected = selection.isSelected(sectionEntries); return AnimatedSwitcher( - duration: Durations.sectionHeaderAnimation, + duration: ADurations.sectionHeaderAnimation, switchInCurve: Curves.easeOutBack, switchOutCurve: Curves.easeOutBack, transitionBuilder: (child, animation) => ScaleTransition( diff --git a/lib/widgets/common/grid/item_tracker.dart b/lib/widgets/common/grid/item_tracker.dart index 4cd88a32f..8ef7203fa 100644 --- a/lib/widgets/common/grid/item_tracker.dart +++ b/lib/widgets/common/grid/item_tracker.dart @@ -115,12 +115,12 @@ class _GridItemTrackerState extends State> with WidgetsBin if (event.animate) { await scrollController.animateTo( scrollOffset, - duration: Duration(milliseconds: (scrollOffset / 2).round().clamp(Durations.highlightScrollAnimationMinMillis, Durations.highlightScrollAnimationMaxMillis)), + duration: Duration(milliseconds: (scrollOffset / 2).round().clamp(ADurations.highlightScrollAnimationMinMillis, ADurations.highlightScrollAnimationMaxMillis)), curve: Curves.easeInOutCubic, ); } else { scrollController.jumpTo(scrollOffset); - await Future.delayed(Durations.highlightJumpDelay); + await Future.delayed(ADurations.highlightJumpDelay); } } diff --git a/lib/widgets/common/grid/overlay.dart b/lib/widgets/common/grid/overlay.dart index 2961dff2b..fe7515494 100644 --- a/lib/widgets/common/grid/overlay.dart +++ b/lib/widgets/common/grid/overlay.dart @@ -10,7 +10,7 @@ class GridItemSelectionOverlay extends StatelessWidget { final BorderRadius? borderRadius; final EdgeInsets? padding; - static const duration = Durations.thumbnailOverlayAnimation; + static const duration = ADurations.thumbnailOverlayAnimation; const GridItemSelectionOverlay({ super.key, diff --git a/lib/widgets/common/grid/sections/fixed/scale_overlay.dart b/lib/widgets/common/grid/sections/fixed/scale_overlay.dart index 8d5200a97..05b3b8c7f 100644 --- a/lib/widgets/common/grid/sections/fixed/scale_overlay.dart +++ b/lib/widgets/common/grid/sections/fixed/scale_overlay.dart @@ -1,8 +1,8 @@ import 'package:aves/theme/durations.dart'; -import 'package:aves/utils/colors.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves_model/aves_model.dart'; +import 'package:aves_utils/aves_utils.dart'; import 'package:flutter/material.dart'; class FixedExtentScaleOverlay extends StatelessWidget { @@ -93,7 +93,7 @@ class _OverlayBackgroundState extends State<_OverlayBackground> { Widget build(BuildContext context) { return AnimatedContainer( decoration: _buildBackgroundDecoration(context), - duration: Durations.scalingGridBackgroundAnimation, + duration: ADurations.scalingGridBackgroundAnimation, child: widget.child, ); } diff --git a/lib/widgets/common/grid/sections/fixed/section_layout_builder.dart b/lib/widgets/common/grid/sections/fixed/section_layout_builder.dart index accf3d9d3..e450f2347 100644 --- a/lib/widgets/common/grid/sections/fixed/section_layout_builder.dart +++ b/lib/widgets/common/grid/sections/fixed/section_layout_builder.dart @@ -6,7 +6,6 @@ import 'package:aves/widgets/common/grid/sections/list_layout.dart'; import 'package:aves/widgets/common/grid/sections/section_layout.dart'; import 'package:aves/widgets/common/grid/sections/section_layout_builder.dart'; import 'package:flutter/material.dart'; -import 'package:tuple/tuple.dart'; class FixedExtentSectionLayoutBuilder extends SectionLayoutBuilder { int _currentIndex = 0; @@ -88,7 +87,7 @@ class FixedExtentSectionLayoutBuilder extends SectionLayoutBuilder { section: section, sectionGridIndex: listIndex * columnCount, sectionChildIndex: sectionChildIndex, - itemIndexRange: () => Tuple2( + itemIndexRange: () => ( (sectionChildIndex - 1) * columnCount, sectionChildIndex * columnCount, ), diff --git a/lib/widgets/common/grid/sections/mosaic/scale_grid.dart b/lib/widgets/common/grid/sections/mosaic/scale_grid.dart index f199d80ab..820d5c75b 100644 --- a/lib/widgets/common/grid/sections/mosaic/scale_grid.dart +++ b/lib/widgets/common/grid/sections/mosaic/scale_grid.dart @@ -57,7 +57,7 @@ class MosaicGrid extends StatelessWidget { top: dy, width: itemWidth, height: itemHeight, - duration: Durations.scalingGridPositionAnimation, + duration: ADurations.scalingGridPositionAnimation, child: builder(i, targetExtent), ), ); diff --git a/lib/widgets/common/grid/sections/mosaic/scale_overlay.dart b/lib/widgets/common/grid/sections/mosaic/scale_overlay.dart index ea6298e42..c5df7da96 100644 --- a/lib/widgets/common/grid/sections/mosaic/scale_overlay.dart +++ b/lib/widgets/common/grid/sections/mosaic/scale_overlay.dart @@ -1,7 +1,7 @@ import 'package:aves/theme/durations.dart'; -import 'package:aves/utils/colors.dart'; import 'package:aves/widgets/common/grid/sections/mosaic/scale_grid.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; +import 'package:aves_utils/aves_utils.dart'; import 'package:flutter/material.dart'; typedef MosaicItemBuilder = Widget Function(int index, double targetExtent); @@ -98,7 +98,7 @@ class _OverlayBackgroundState extends State<_OverlayBackground> { Widget build(BuildContext context) { return AnimatedContainer( decoration: _buildBackgroundDecoration(context), - duration: Durations.scalingGridBackgroundAnimation, + duration: ADurations.scalingGridBackgroundAnimation, child: widget.child, ); } diff --git a/lib/widgets/common/grid/sections/mosaic/section_layout_builder.dart b/lib/widgets/common/grid/sections/mosaic/section_layout_builder.dart index 14a7b6bcb..4be25c6e6 100644 --- a/lib/widgets/common/grid/sections/mosaic/section_layout_builder.dart +++ b/lib/widgets/common/grid/sections/mosaic/section_layout_builder.dart @@ -11,7 +11,6 @@ import 'package:aves/widgets/common/grid/sections/section_layout_builder.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:tuple/tuple.dart'; class MosaicSectionLayoutBuilder extends SectionLayoutBuilder { int _currentIndex = 0; @@ -110,7 +109,7 @@ class MosaicSectionLayoutBuilder extends SectionLayoutBuilder { section: section, sectionGridIndex: sectionGridIndex, sectionChildIndex: sectionChildIndex, - itemIndexRange: () => isHeader ? const Tuple2(0, 0) : Tuple2(row.firstIndex, row.lastIndex + 1), + itemIndexRange: () => isHeader ? const (0, 0) : (row.firstIndex, row.lastIndex + 1), sectionKey: sectionKey, headerExtent: headerExtent, itemSizes: row.itemWidths.map((v) => Size(v, row.height)).toList(), diff --git a/lib/widgets/common/grid/sections/section_layout_builder.dart b/lib/widgets/common/grid/sections/section_layout_builder.dart index 27866181d..2be7e970f 100644 --- a/lib/widgets/common/grid/sections/section_layout_builder.dart +++ b/lib/widgets/common/grid/sections/section_layout_builder.dart @@ -6,7 +6,6 @@ import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; typedef TileBuilder = Widget Function(T item, Size tileSize); @@ -54,7 +53,7 @@ abstract class SectionLayoutBuilder { required List section, required int sectionGridIndex, required int sectionChildIndex, - required Tuple2 Function() itemIndexRange, + required (int, int) Function() itemIndexRange, required SectionKey sectionKey, required double headerExtent, required List itemSizes, @@ -68,8 +67,8 @@ abstract class SectionLayoutBuilder { final sectionItemCount = section.length; final itemMinMax = itemIndexRange(); - final minItemIndex = itemMinMax.item1.clamp(0, sectionItemCount); - final maxItemIndex = itemMinMax.item2.clamp(0, sectionItemCount); + final minItemIndex = itemMinMax.$1.clamp(0, sectionItemCount); + final maxItemIndex = itemMinMax.$2.clamp(0, sectionItemCount); final childrenCount = maxItemIndex - minItemIndex; final children = []; for (var i = 0; i < childrenCount; i++) { diff --git a/lib/widgets/common/grid/selector.dart b/lib/widgets/common/grid/selector.dart index ddb4b3e25..675be6834 100644 --- a/lib/widgets/common/grid/selector.dart +++ b/lib/widgets/common/grid/selector.dart @@ -206,7 +206,7 @@ class _GridSelectionGestureDetectorState extends State getRange(int start, int end) => items.getRange(start, end); + Iterable getRange(int start, int end) => start < end && 0 <= start && end <= items.length ? items.getRange(start, end) : {}; final selection = context.read>(); void addRange(int start, int end) => selection.addToSelection(getRange(start, end)); void removeRange(int start, int end) => selection.removeFromSelection(getRange(start, end)); diff --git a/lib/widgets/common/grid/theme.dart b/lib/widgets/common/grid/theme.dart index a7258cfa3..53487f19a 100644 --- a/lib/widgets/common/grid/theme.dart +++ b/lib/widgets/common/grid/theme.dart @@ -89,7 +89,7 @@ class GridThemeData { if (located && showLocated) LocationIcon.located(), if (!located && showUnlocated) LocationIcon.unlocated(), if (entry.rating != 0 && showRating) RatingIcon(entry: entry), - if (entry.isVideo) + if (entry.isPureVideo) VideoIcon(entry: entry) else if (entry.isAnimated) const AnimatedImageIcon() diff --git a/lib/widgets/common/identity/aves_donut.dart b/lib/widgets/common/identity/aves_donut.dart new file mode 100644 index 000000000..ab587a730 --- /dev/null +++ b/lib/widgets/common/identity/aves_donut.dart @@ -0,0 +1,192 @@ +import 'dart:math'; + +import 'package:aves/model/settings/enums/accessibility_animations.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/theme/colors.dart'; +import 'package:aves/theme/icons.dart'; +import 'package:charts_flutter/flutter.dart' as charts; +import 'package:collection/collection.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +typedef DatumKeyFormatter = String Function(AvesDonutDatum d); +typedef DatumValueFormatter = String Function(int d); +typedef DatumColorizer = Color Function(BuildContext context, AvesDonutDatum d); +typedef DatumCallback = void Function(AvesDonutDatum d); + +class AvesDonut extends StatefulWidget { + final Widget title; + final Map byTypes; + final Duration animationDuration; + final DatumKeyFormatter formatKey; + final DatumValueFormatter formatValue; + final DatumColorizer colorize; + final DatumCallback? onTap; + + const AvesDonut({ + super.key, + required this.title, + required this.byTypes, + required this.animationDuration, + required this.formatKey, + required this.formatValue, + required this.colorize, + this.onTap, + }); + + @override + State createState() => _AvesDonutState(); +} + +class _AvesDonutState extends State with AutomaticKeepAliveClientMixin { + Map get byTypes => widget.byTypes; + + DatumKeyFormatter get formatKey => widget.formatKey; + + DatumValueFormatter get formatValue => widget.formatValue; + + DatumColorizer get colorize => widget.colorize; + + static const avesDonutMinWidth = 124.0; + + @override + Widget build(BuildContext context) { + super.build(context); + + if (byTypes.isEmpty) return const SizedBox(); + + final sum = byTypes.values.sum; + + final seriesData = byTypes.entries.map((kv) { + final type = kv.key; + return AvesDonutDatum( + key: type, + value: kv.value, + ); + }).toList(); + seriesData.sort((d1, d2) { + final c = d2.value.compareTo(d1.value); + return c != 0 ? c : compareAsciiUpperCase(formatKey(d1), formatKey(d2)); + }); + + return AvesColorsProvider( + allowMonochrome: false, + child: LayoutBuilder( + builder: (context, constraints) { + final series = [ + charts.Series( + id: 'type', + colorFn: (d, i) => charts.ColorUtil.fromDartColor(colorize(context, d)), + domainFn: (d, i) => formatKey(d), + measureFn: (d, i) => d.value, + data: seriesData, + labelAccessorFn: (d, _) => '${formatKey(d)}: ${d.value}', + ), + ]; + + final textScaleFactor = MediaQuery.textScaleFactorOf(context); + final minWidth = avesDonutMinWidth * textScaleFactor; + final availableWidth = constraints.maxWidth; + final dim = max(minWidth, availableWidth / (availableWidth > 4 * minWidth ? 4 : (availableWidth > 2 * minWidth ? 2 : 1))); + + final donut = SizedBox( + width: dim, + height: dim, + child: Stack( + children: [ + charts.PieChart( + series, + animate: context.select((v) => v.accessibilityAnimations.animate), + animationDuration: widget.animationDuration, + defaultRenderer: charts.ArcRendererConfig( + arcWidth: 16, + ), + ), + Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + widget.title, + Text( + formatValue(sum), + textAlign: TextAlign.center, + ), + ], + ), + ), + ], + ), + ); + final onTap = widget.onTap; + final legend = SizedBox( + width: dim, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: seriesData + .map((d) => InkWell( + onTap: onTap != null ? () => onTap(d) : null, + borderRadius: const BorderRadius.all(Radius.circular(123)), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(AIcons.disc, color: colorize(context, d)), + const SizedBox(width: 8), + Flexible( + child: Text( + formatKey(d), + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1, + ), + ), + const SizedBox(width: 8), + Text( + formatValue(d.value), + style: TextStyle( + color: Theme.of(context).textTheme.bodySmall!.color, + ), + ), + const SizedBox(width: 4), + ], + ), + )) + .toList(), + ), + ); + final children = [ + donut, + legend, + ]; + return availableWidth > minWidth * 2 + ? Row( + mainAxisSize: MainAxisSize.min, + children: children, + ) + : Column( + mainAxisSize: MainAxisSize.min, + children: children, + ); + }, + ), + ); + } + + @override + bool get wantKeepAlive => true; +} + +@immutable +class AvesDonutDatum extends Equatable { + final String key; + final int value; + + @override + List get props => [key, value]; + + const AvesDonutDatum({ + required this.key, + required this.value, + }); +} diff --git a/lib/widgets/common/identity/aves_filter_chip.dart b/lib/widgets/common/identity/aves_filter_chip.dart index 1192c6322..4022b6553 100644 --- a/lib/widgets/common/identity/aves_filter_chip.dart +++ b/lib/widgets/common/identity/aves_filter_chip.dart @@ -6,6 +6,7 @@ import 'package:aves/model/covers.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/location.dart'; +import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; @@ -100,6 +101,10 @@ class AvesFilterChip extends StatefulWidget { if ((filter is LocationFilter && filter.level == LocationLevel.country)) ChipAction.goToCountryPage, if ((filter is LocationFilter && filter.level == LocationLevel.place)) ChipAction.goToPlacePage, if (filter is TagFilter) ChipAction.goToTagPage, + if (filter is RatingFilter && 1 < filter.rating && filter.rating < 5) ...[ + if (filter.op != RatingFilter.opOrGreater) ChipAction.ratingOrGreater, + if (filter.op != RatingFilter.opOrLower) ChipAction.ratingOrLower, + ], ChipAction.reverse, ChipAction.hide, ChipAction.lockVault, @@ -122,10 +127,19 @@ class AvesFilterChip extends StatefulWidget { const PopupMenuDivider(), ...actions.where((action) => actionDelegate.isVisible(action, filter: filter)).map((action) { late String text; - if (action == ChipAction.reverse) { - text = filter.reversed ? context.l10n.chipActionFilterIn : context.l10n.chipActionFilterOut; - } else { - text = action.getText(context); + switch (action) { + case ChipAction.reverse: + text = filter.reversed ? context.l10n.chipActionFilterIn : context.l10n.chipActionFilterOut; + break; + case ChipAction.ratingOrGreater: + text = RatingFilter.formatRatingRange(context, (filter as RatingFilter).rating, RatingFilter.opOrGreater); + break; + case ChipAction.ratingOrLower: + text = RatingFilter.formatRatingRange(context, (filter as RatingFilter).rating, RatingFilter.opOrLower); + break; + default: + text = action.getText(context); + break; } return PopupMenuItem( value: action, @@ -138,7 +152,7 @@ class AvesFilterChip extends StatefulWidget { ); if (selectedAction != null) { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(Durations.popupMenuAnimation * timeDilation); + await Future.delayed(ADurations.popupMenuAnimation * timeDilation); actionDelegate.onActionSelected(context, filter, selectedAction); } } @@ -165,7 +179,7 @@ class _AvesFilterChipState extends State { _tapped = false; _subscriptions.add(covers.packageChangeStream.listen(_onCoverColorChanged)); _subscriptions.add(covers.colorChangeStream.listen(_onCoverColorChanged)); - _subscriptions.add(settings.updateStream.where((event) => event.key == Settings.themeColorModeKey).listen((_) { + _subscriptions.add(settings.updateStream.where((event) => event.key == SettingKeys.themeColorModeKey).listen((_) { // delay so that contextual colors reflect the new settings WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; diff --git a/lib/widgets/common/identity/aves_icons.dart b/lib/widgets/common/identity/aves_icons.dart index 022389b7d..f108ca0fe 100644 --- a/lib/widgets/common/identity/aves_icons.dart +++ b/lib/widgets/common/identity/aves_icons.dart @@ -100,7 +100,7 @@ class TagIcon extends StatelessWidget { factory TagIcon.tagged() => const TagIcon._private(icon: AIcons.tag); - factory TagIcon.untagged() => const TagIcon._private(icon: AIcons.tagUntagged); + factory TagIcon.untagged() => TagIcon._private(icon: AIcons.tagUntagged); static const scale = .9; diff --git a/lib/widgets/common/map/buttons/coordinate_filter.dart b/lib/widgets/common/map/buttons/coordinate_filter.dart index 4146ae893..87c22e446 100644 --- a/lib/widgets/common/map/buttons/coordinate_filter.dart +++ b/lib/widgets/common/map/buttons/coordinate_filter.dart @@ -25,7 +25,7 @@ class OverlayCoordinateFilterChip extends StatefulWidget { } class _OverlayCoordinateFilterChipState extends State { - final Debouncer _debouncer = Debouncer(delay: Durations.mapInfoDebounceDelay); + final Debouncer _debouncer = Debouncer(delay: ADurations.mapInfoDebounceDelay); final ValueNotifier _idleBoundsNotifier = ValueNotifier(null); @override diff --git a/lib/widgets/common/map/geo_map.dart b/lib/widgets/common/map/geo_map.dart index ef12052ea..a69e189ff 100644 --- a/lib/widgets/common/map/geo_map.dart +++ b/lib/widgets/common/map/geo_map.dart @@ -261,7 +261,7 @@ class _GeoMapState extends State { return AnimatedSize( alignment: Alignment.topCenter, curve: Curves.easeInOutCubic, - duration: Durations.mapStyleSwitchAnimation, + duration: ADurations.mapStyleSwitchAnimation, child: ValueListenableBuilder( valueListenable: widget.isAnimatingNotifier, builder: (context, animating, child) { diff --git a/lib/widgets/common/map/leaflet/latlng_utils.dart b/lib/widgets/common/map/leaflet/latlng_utils.dart index 35867b4ec..adec5d192 100644 --- a/lib/widgets/common/map/leaflet/latlng_utils.dart +++ b/lib/widgets/common/map/leaflet/latlng_utils.dart @@ -4,8 +4,8 @@ class LatLngUtils { static LatLng? lerp(LatLng? a, LatLng? b, double t) { if (a == null && b == null) return null; - final _a = a ?? LatLng(0, 0); - final _b = b ?? LatLng(0, 0); + final _a = a ?? const LatLng(0, 0); + final _b = b ?? const LatLng(0, 0); return LatLng( _a.latitude + (_b.latitude - _a.latitude) * t, _a.longitude + (_b.longitude - _a.longitude) * t, diff --git a/lib/widgets/common/map/leaflet/map.dart b/lib/widgets/common/map/leaflet/map.dart index a48ff84e6..0c3fe7d5d 100644 --- a/lib/widgets/common/map/leaflet/map.dart +++ b/lib/widgets/common/map/leaflet/map.dart @@ -62,7 +62,7 @@ class _EntryLeafletMapState extends State> with TickerProv final MapController _leafletMapController = MapController(); final List _subscriptions = []; Map, GeoEntry> _geoEntryByMarkerKey = {}; - final Debouncer _debouncer = Debouncer(delay: Durations.mapIdleDebounceDelay); + final Debouncer _debouncer = Debouncer(delay: ADurations.mapIdleDebounceDelay); ValueNotifier get boundsNotifier => widget.boundsNotifier; diff --git a/lib/widgets/common/search/page.dart b/lib/widgets/common/search/page.dart index 8aee4f8d9..fe1f32f61 100644 --- a/lib/widgets/common/search/page.dart +++ b/lib/widgets/common/search/page.dart @@ -29,7 +29,7 @@ class SearchPage extends StatefulWidget { } class _SearchPageState extends State { - final Debouncer _debouncer = Debouncer(delay: Durations.searchDebounceDelay); + final Debouncer _debouncer = Debouncer(delay: ADurations.searchDebounceDelay); final FocusNode _searchFieldFocusNode = FocusNode(); final DoubleBackPopHandler _doubleBackPopHandler = DoubleBackPopHandler(); @@ -77,7 +77,7 @@ class _SearchPageState extends State { return; } widget.animation.removeStatusListener(_onAnimationStatusChanged); - Future.delayed(Durations.pageTransitionAnimation * timeDilation).then((_) { + Future.delayed(ADurations.pageTransitionAnimation * timeDilation).then((_) { if (!mounted) return; _searchFieldFocusNode.requestFocus(); }); diff --git a/lib/widgets/common/thumbnail/overlay.dart b/lib/widgets/common/thumbnail/overlay.dart index 6326be3d4..e1e2dd518 100644 --- a/lib/widgets/common/thumbnail/overlay.dart +++ b/lib/widgets/common/thumbnail/overlay.dart @@ -83,7 +83,7 @@ class ThumbnailZoomOverlay extends StatelessWidget { }); static const alignment = AlignmentDirectional.bottomEnd; - static const duration = Durations.thumbnailOverlayAnimation; + static const duration = ADurations.thumbnailOverlayAnimation; @override Widget build(BuildContext context) { diff --git a/lib/widgets/common/thumbnail/scroller.dart b/lib/widgets/common/thumbnail/scroller.dart index ab5680dd6..40dd73e6a 100644 --- a/lib/widgets/common/thumbnail/scroller.dart +++ b/lib/widgets/common/thumbnail/scroller.dart @@ -151,7 +151,7 @@ class _ThumbnailScrollerState extends State { color: currentIndex == index ? Colors.transparent : Colors.black45, width: thumbnailExtent, height: thumbnailExtent, - duration: Durations.thumbnailScrollerShadeAnimation, + duration: ADurations.thumbnailScrollerShadeAnimation, ); }, ), @@ -172,7 +172,7 @@ class _ThumbnailScrollerState extends State { _isAnimating = true; await _scrollController.animateTo( targetOffset, - duration: Durations.thumbnailScrollerScrollAnimation, + duration: ADurations.thumbnailScrollerScrollAnimation, curve: Curves.easeOutCubic, ); _isAnimating = false; diff --git a/lib/widgets/common/tile_extent_controller.dart b/lib/widgets/common/tile_extent_controller.dart index 924ecbdf3..baaaefc84 100644 --- a/lib/widgets/common/tile_extent_controller.dart +++ b/lib/widgets/common/tile_extent_controller.dart @@ -1,9 +1,9 @@ +import 'dart:async'; import 'dart:math'; import 'package:aves/model/settings/settings.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; -import 'package:tuple/tuple.dart'; class TileExtentController { final String settingsRouteKey; @@ -13,6 +13,7 @@ class TileExtentController { late double userPreferredExtent; Size _viewportSize = Size.zero; + final List _subscriptions = []; Size get viewportSize => _viewportSize; @@ -28,11 +29,13 @@ class TileExtentController { // initialize extent to 0, so that it will be dynamically sized on first launch extentNotifier = ValueNotifier(0); userPreferredExtent = settings.getTileExtent(settingsRouteKey); - settings.addListener(_onSettingsChanged); + _subscriptions.add(settings.updateTileExtentStream.listen((_) => _onSettingsChanged())); } void dispose() { - settings.removeListener(_onSettingsChanged); + _subscriptions + ..forEach((sub) => sub.cancel()) + ..clear(); } void _onSettingsChanged() { @@ -98,7 +101,7 @@ class TileExtentController { double get effectiveExtentMax => _extentForColumnCount(_effectiveColumnCountMin()); - Tuple2 get effectiveColumnRange => Tuple2(_effectiveColumnCountMin(), _effectiveColumnCountMax()); + (int min, int max) get effectiveColumnRange => (_effectiveColumnCountMin(), _effectiveColumnCountMax()); int get columnCount => _effectiveColumnCountForExtent(extentNotifier.value); diff --git a/lib/widgets/debug/app_debug_page.dart b/lib/widgets/debug/app_debug_page.dart index 9c5506efb..ffc38373d 100644 --- a/lib/widgets/debug/app_debug_page.dart +++ b/lib/widgets/debug/app_debug_page.dart @@ -65,7 +65,7 @@ class _AppDebugPageState extends State { .toList(), onSelected: (action) async { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(Durations.popupMenuAnimation * timeDilation); + await Future.delayed(ADurations.popupMenuAnimation * timeDilation); unawaited(_onActionSelected(action)); }, ), diff --git a/lib/widgets/dialogs/add_shortcut_dialog.dart b/lib/widgets/dialogs/add_shortcut_dialog.dart index 095a836b8..308a2d1a1 100644 --- a/lib/widgets/dialogs/add_shortcut_dialog.dart +++ b/lib/widgets/dialogs/add_shortcut_dialog.dart @@ -8,7 +8,6 @@ import 'package:aves/widgets/dialogs/item_picker.dart'; import 'package:aves/widgets/dialogs/pick_dialogs/item_pick_page.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:tuple/tuple.dart'; import 'aves_dialog.dart'; @@ -40,7 +39,7 @@ class _AddShortcutDialogState extends State { if (_collection != null) { final entries = _collection.sortedEntries; if (entries.isNotEmpty) { - final coverEntries = _collection.filters.map((filter) => covers.of(filter)?.item1).whereNotNull().map((id) => entries.firstWhereOrNull((entry) => entry.id == id)).whereNotNull(); + final coverEntries = _collection.filters.map((filter) => covers.of(filter)?.$1).whereNotNull().map((id) => entries.firstWhereOrNull((entry) => entry.id == id)).whereNotNull(); _coverEntry = coverEntries.firstOrNull ?? entries.first; } } @@ -142,7 +141,7 @@ class _AddShortcutDialogState extends State { void _submit(BuildContext context) { if (_isValidNotifier.value) { - Navigator.maybeOf(context)?.pop(Tuple2(_coverEntry, _nameController.text)); + Navigator.maybeOf(context)?.pop<(AvesEntry?, String)>((_coverEntry, _nameController.text)); } } } diff --git a/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart index 4111ed044..bd40664d6 100644 --- a/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart @@ -260,7 +260,7 @@ class _EditEntryDateDialogState extends State { padding: const EdgeInsets.only(bottom: 1), child: ExpansionPanelList( expansionCallback: (index, isExpanded) { - setState(() => _showOptions = !isExpanded); + setState(() => _showOptions = isExpanded); }, animationDuration: context.read().expansionTileAnimation, expandedHeaderPadding: EdgeInsets.zero, diff --git a/lib/widgets/dialogs/entry_editors/edit_description_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_description_dialog.dart index e60e49cae..239885316 100644 --- a/lib/widgets/dialogs/entry_editors/edit_description_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_description_dialog.dart @@ -92,12 +92,11 @@ class _EditEntryTitleDescriptionDialogState extends State l10n.viewerInfoLabelTitle, + DescriptionField.description => l10n.viewerInfoLabelDescription, + }; } void _submit(BuildContext context) { diff --git a/lib/widgets/dialogs/entry_editors/remove_metadata_dialog.dart b/lib/widgets/dialogs/entry_editors/remove_metadata_dialog.dart index b31de3de8..18bff131c 100644 --- a/lib/widgets/dialogs/entry_editors/remove_metadata_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/remove_metadata_dialog.dart @@ -58,7 +58,7 @@ class _RemoveEntryMetadataDialogState extends State { padding: const EdgeInsets.only(bottom: 1), child: ExpansionPanelList( expansionCallback: (index, isExpanded) { - setState(() => _showMore = !isExpanded); + setState(() => _showMore = isExpanded); }, animationDuration: animationDuration, expandedHeaderPadding: EdgeInsets.zero, diff --git a/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart b/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart index a9bb11799..20be3112c 100644 --- a/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart +++ b/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart @@ -103,7 +103,7 @@ class _RenameEntrySetPageState extends State { }, onSelected: (key) async { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(Durations.popupMenuAnimation * timeDilation); + await Future.delayed(ADurations.popupMenuAnimation * timeDilation); _insertProcessor(key); }, tooltip: l10n.renameEntrySetPageInsertTooltip, diff --git a/lib/widgets/dialogs/entry_editors/tag_editor_page.dart b/lib/widgets/dialogs/entry_editors/tag_editor_page.dart index 18a8ed91a..885bf0fba 100644 --- a/lib/widgets/dialogs/entry_editors/tag_editor_page.dart +++ b/lib/widgets/dialogs/entry_editors/tag_editor_page.dart @@ -171,7 +171,7 @@ class _TagEditorPageState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon(AIcons.tagUntagged, color: untaggedColor), + Icon(AIcons.tagUntagged, color: untaggedColor), const SizedBox(width: 8), Text( l10n.filterNoTagLabel, @@ -204,7 +204,7 @@ class _TagEditorPageState extends State { onLongPress: null, ), crossFadeState: sortedTags.isEmpty ? CrossFadeState.showFirst : CrossFadeState.showSecond, - duration: Durations.tagEditorTransition, + duration: ADurations.tagEditorTransition, ), ), const Divider(height: 0), diff --git a/lib/widgets/dialogs/filter_editors/cover_selection_dialog.dart b/lib/widgets/dialogs/filter_editors/cover_selection_dialog.dart index 3a3c8db7d..57d093628 100644 --- a/lib/widgets/dialogs/filter_editors/cover_selection_dialog.dart +++ b/lib/widgets/dialogs/filter_editors/cover_selection_dialog.dart @@ -20,7 +20,6 @@ import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class CoverSelectionDialog extends StatefulWidget { static const routeName = '/dialog/select_cover'; @@ -81,8 +80,8 @@ class _CoverSelectionDialogState extends State { @override Widget build(BuildContext context) { final l10n = context.l10n; - final tabs = >[ - Tuple2( + final tabs = <(Tab, Widget)>[ + ( _buildTab( context, const Key('tab-entry'), @@ -92,7 +91,7 @@ class _CoverSelectionDialogState extends State { Column(children: _buildEntryOptions()), ), if (showAppTab) - Tuple2( + ( _buildTab( context, const Key('tab-package'), @@ -102,7 +101,7 @@ class _CoverSelectionDialogState extends State { Column(children: _buildAppOptions()), ), if (showColorTab) - Tuple2( + ( _buildTab( context, const Key('tab-color'), @@ -131,7 +130,7 @@ class _CoverSelectionDialogState extends State { clipBehavior: Clip.antiAlias, child: TabBar( indicatorWeight: tabIndicatorWeight, - tabs: tabs.map((t) => t.item1).toList(), + tabs: tabs.map((t) => t.$1).toList(), ), ), ConstrainedBox( @@ -140,7 +139,7 @@ class _CoverSelectionDialogState extends State { physics: const NeverScrollableScrollPhysics(), children: tabs .map((t) => SingleChildScrollView( - child: t.item2, + child: t.$2, )) .toList(), ), @@ -165,7 +164,7 @@ class _CoverSelectionDialogState extends State { final entry = _isCustomEntry ? _customEntry : null; final package = _isCustomPackage ? _customPackage : null; final color = _isCustomColor ? _customColor : null; - return Navigator.maybeOf(context)?.pop(Tuple3(entry, package, color)); + return Navigator.maybeOf(context)?.pop<(AvesEntry?, String?, Color?)>((entry, package, color)); }, child: Text(l10n.applyButtonLabel), ) diff --git a/lib/widgets/dialogs/filter_editors/create_album_dialog.dart b/lib/widgets/dialogs/filter_editors/create_album_dialog.dart index c31895d9a..20bb2a2ea 100644 --- a/lib/widgets/dialogs/filter_editors/create_album_dialog.dart +++ b/lib/widgets/dialogs/filter_editors/create_album_dialog.dart @@ -133,7 +133,7 @@ class _CreateAlbumDialogState extends State { // when the field gets focus, we wait for the soft keyboard to appear // then scroll to the bottom to make sure the field is in view if (_nameFieldFocusNode.hasFocus) { - await Future.delayed(Durations.softKeyboardDisplayDelay + const Duration(milliseconds: 500)); + await Future.delayed(ADurations.softKeyboardDisplayDelay + const Duration(milliseconds: 500)); _scrollToBottom(); } } @@ -141,7 +141,7 @@ class _CreateAlbumDialogState extends State { void _scrollToBottom() { _scrollController.animateTo( _scrollController.position.maxScrollExtent, - duration: Durations.dialogFieldReachAnimation, + duration: ADurations.dialogFieldReachAnimation, curve: Curves.easeInOut, ); } diff --git a/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart b/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart index 13f446aef..c2055cb6d 100644 --- a/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart +++ b/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart @@ -27,7 +27,6 @@ import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; Future pickAlbum({ required BuildContext context, @@ -68,18 +67,13 @@ class _AlbumPickPageState extends State<_AlbumPickPage> { CollectionSource get source => widget.source; String get title { - switch (widget.moveType) { - case MoveType.copy: - return context.l10n.albumPickPageTitleCopy; - case MoveType.move: - return context.l10n.albumPickPageTitleMove; - case MoveType.export: - return context.l10n.albumPickPageTitleExport; - case MoveType.toBin: - case MoveType.fromBin: - case null: - return context.l10n.albumPickPageTitlePick; - } + final l10n = context.l10n; + return switch (widget.moveType) { + MoveType.copy => l10n.albumPickPageTitleCopy, + MoveType.move => l10n.albumPickPageTitleMove, + MoveType.export => l10n.albumPickPageTitleExport, + MoveType.toBin || MoveType.fromBin || null => l10n.albumPickPageTitlePick, + }; } static const _quickActions = [ @@ -99,8 +93,8 @@ class _AlbumPickPageState extends State<_AlbumPickPage> { Widget build(BuildContext context) { return ListenableProvider>.value( value: ValueNotifier(AppMode.pickFilterInternal), - child: Selector>( - selector: (context, s) => Tuple2(s.albumGroupFactor, s.albumSortFactor), + child: Selector( + selector: (context, s) => (s.albumGroupFactor, s.albumSortFactor), builder: (context, s, child) { return StreamBuilder( stream: source.eventBus.on(), @@ -227,7 +221,7 @@ class _AlbumPickPageState extends State<_AlbumPickPage> { FocusManager.instance.primaryFocus?.unfocus(); // wait for the popup menu to hide before proceeding with the action - await Future.delayed(Durations.popupMenuAnimation * timeDilation); + await Future.delayed(ADurations.popupMenuAnimation * timeDilation); onActionSelected(action); }, ), @@ -243,7 +237,7 @@ class _AlbumPickPageState extends State<_AlbumPickPage> { if (directory == null) return; // wait for the dialog to hide as applying the change may block the UI - await Future.delayed(Durations.dialogTransitionAnimation * timeDilation); + await Future.delayed(ADurations.dialogTransitionAnimation * timeDilation); _pickAlbum(directory); } @@ -265,7 +259,7 @@ class _AlbumPickPageState extends State<_AlbumPickPage> { if (details == null) return; // wait for the dialog to hide as applying the change may block the UI - await Future.delayed(Durations.dialogTransitionAnimation * timeDilation); + await Future.delayed(ADurations.dialogTransitionAnimation * timeDilation); await vaults.create(details); _pickAlbum(details.path); diff --git a/lib/widgets/dialogs/pick_dialogs/location_pick_page.dart b/lib/widgets/dialogs/pick_dialogs/location_pick_page.dart index 5c79bc06a..452f3e9d2 100644 --- a/lib/widgets/dialogs/pick_dialogs/location_pick_page.dart +++ b/lib/widgets/dialogs/pick_dialogs/location_pick_page.dart @@ -68,7 +68,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin final AvesMapController _mapController = AvesMapController(); late final ValueNotifier _isPageAnimatingNotifier; final ValueNotifier _dotLocationNotifier = ValueNotifier(null), _infoLocationNotifier = ValueNotifier(null); - final Debouncer _infoDebouncer = Debouncer(delay: Durations.mapInfoDebounceDelay); + final Debouncer _infoDebouncer = Debouncer(delay: ADurations.mapInfoDebounceDelay); CollectionLens? get openingCollection => widget.collection; @@ -78,7 +78,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin if (ExtraEntryMapStyle.isHeavy(settings.mapStyle)) { _isPageAnimatingNotifier = ValueNotifier(true); - Future.delayed(Durations.pageTransitionAnimation * timeDilation).then((_) { + Future.delayed(ADurations.pageTransitionAnimation * timeDilation).then((_) { if (!mounted) return; _isPageAnimatingNotifier.value = false; }); diff --git a/lib/widgets/dialogs/selection_dialogs/common.dart b/lib/widgets/dialogs/selection_dialogs/common.dart index 4a827e56f..3c80ec6a9 100644 --- a/lib/widgets/dialogs/selection_dialogs/common.dart +++ b/lib/widgets/dialogs/selection_dialogs/common.dart @@ -14,7 +14,7 @@ Future showSelectionDialog({ routeSettings: const RouteSettings(name: AvesSingleSelectionDialog.routeName), ); // wait for the dialog to hide as applying the change may block the UI - await Future.delayed(Durations.dialogTransitionAnimation * timeDilation); + await Future.delayed(ADurations.dialogTransitionAnimation * timeDilation); if (value != null) { onSelection(value); } diff --git a/lib/widgets/dialogs/tile_view_dialog.dart b/lib/widgets/dialogs/tile_view_dialog.dart index 941963e98..2c0af6253 100644 --- a/lib/widgets/dialogs/tile_view_dialog.dart +++ b/lib/widgets/dialogs/tile_view_dialog.dart @@ -11,14 +11,13 @@ import 'package:aves/widgets/common/identity/highlight_title.dart'; import 'package:aves/widgets/common/tile_extent_controller.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; import 'aves_dialog.dart'; class TileViewDialog extends StatefulWidget { static const routeName = '/dialog/tile_view'; - final Tuple4 initialValue; + final (S? sort, G? group, L? layout, bool reverse) initialValue; final List> sortOptions; final List> groupOptions; final List> layoutOptions; @@ -63,15 +62,15 @@ class _TileViewDialogState extends State> with void initState() { super.initState(); final initialValue = widget.initialValue; - _selectedSort = initialValue.item1; - _selectedGroup = initialValue.item2; - _selectedLayout = initialValue.item3; - _reverseSort = initialValue.item4; + _selectedSort = initialValue.$1; + _selectedGroup = initialValue.$2; + _selectedLayout = initialValue.$3; + _reverseSort = initialValue.$4; final extentController = tileExtentController; final columnRange = extentController.effectiveColumnRange; - _columnMin = columnRange.item1; - _columnMax = columnRange.item2; + _columnMin = columnRange.$1; + _columnMax = columnRange.$2; } @override @@ -147,7 +146,7 @@ class _TileViewDialogState extends State> with key: const Key('button-apply'), onPressed: () { tileExtentController.setUserPreferredColumnCount(_columnCountNotifier.value); - Navigator.maybeOf(context)?.pop(Tuple4(_selectedSort, _selectedGroup, _selectedLayout, _reverseSort)); + Navigator.maybeOf(context)?.pop<(S?, G?, L?, bool)>((_selectedSort, _selectedGroup, _selectedLayout, _reverseSort)); }, child: Text(l10n.applyButtonLabel), ) diff --git a/lib/widgets/dialogs/video_stream_selection_dialog.dart b/lib/widgets/dialogs/video_stream_selection_dialog.dart index 5420ce3e3..668ac50f9 100644 --- a/lib/widgets/dialogs/video_stream_selection_dialog.dart +++ b/lib/widgets/dialogs/video_stream_selection_dialog.dart @@ -143,8 +143,9 @@ class _VideoStreamSelectionDialogState extends State ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: TextDropdownButton( - values: streams.whereNotNull().toList(), + // allow `null` subtitle stream to disable subtitles + child: TextDropdownButton( + values: streams, valueText: _streamName, value: current, onChanged: streams.length > 1 ? (newValue) => setState(() => setter(newValue)) : null, diff --git a/lib/widgets/dialogs/wallpaper_settings_dialog.dart b/lib/widgets/dialogs/wallpaper_settings_dialog.dart index 9b3b759ff..20cddd718 100644 --- a/lib/widgets/dialogs/wallpaper_settings_dialog.dart +++ b/lib/widgets/dialogs/wallpaper_settings_dialog.dart @@ -5,7 +5,6 @@ import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:aves/widgets/dialogs/selection_dialogs/radio_list_tile.dart'; import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; -import 'package:tuple/tuple.dart'; class WallpaperSettingsDialog extends StatefulWidget { static const routeName = '/dialog/wallpaper_settings'; @@ -43,7 +42,7 @@ class _WallpaperSettingsDialogState extends State { actions: [ const CancelButton(), TextButton( - onPressed: () => Navigator.maybeOf(context)?.pop(Tuple2(_selectedTarget, _useScrollEffect)), + onPressed: () => Navigator.maybeOf(context)?.pop<(WallpaperTarget, bool)>((_selectedTarget, _useScrollEffect)), child: Text(context.l10n.applyButtonLabel), ), ], diff --git a/lib/widgets/editor/transform/control_panel.dart b/lib/widgets/editor/transform/control_panel.dart index 4615563bd..47a5f41ae 100644 --- a/lib/widgets/editor/transform/control_panel.dart +++ b/lib/widgets/editor/transform/control_panel.dart @@ -13,7 +13,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class TransformControlPanel extends StatefulWidget { final AvesEntry entry; @@ -32,7 +31,7 @@ class TransformControlPanel extends StatefulWidget { } class _TransformControlPanelState extends State with TickerProviderStateMixin { - late final List> _tabs; + late final List<(WidgetBuilder, WidgetBuilder)> _tabs; late final TabController _tabController; static const padding = EditorControlPanel.padding; @@ -41,11 +40,11 @@ class _TransformControlPanelState extends State with Tick void initState() { super.initState(); _tabs = [ - Tuple2( + ( (context) => Tab(text: context.l10n.editorTransformCrop), (context) => const CropControlPanel(), ), - Tuple2( + ( (context) => Tab(text: context.l10n.editorTransformRotate), (context) => const RotationControlPanel(), ), @@ -74,7 +73,7 @@ class _TransformControlPanelState extends State with Tick builder: (context, child) { return AnimatedSwitcher( duration: context.select((v) => v.formTransition), - child: _tabs[_tabController.index].item2(context), + child: _tabs[_tabController.index].$2(context), ); }, ), @@ -87,7 +86,7 @@ class _TransformControlPanelState extends State with Tick ), Expanded( child: TabBar( - tabs: _tabs.map((v) => v.item1(context)).toList(), + tabs: _tabs.map((v) => v.$1(context)).toList(), controller: _tabController, padding: const EdgeInsets.symmetric(horizontal: padding), indicatorSize: TabBarIndicatorSize.label, diff --git a/lib/widgets/filter_grids/albums_page.dart b/lib/widgets/filter_grids/albums_page.dart index a40a79a66..fd346316d 100644 --- a/lib/widgets/filter_grids/albums_page.dart +++ b/lib/widgets/filter_grids/albums_page.dart @@ -17,7 +17,6 @@ import 'package:aves_model/aves_model.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class AlbumListPage extends StatelessWidget { static const routeName = '/albums'; @@ -27,12 +26,12 @@ class AlbumListPage extends StatelessWidget { @override Widget build(BuildContext context) { final source = context.read(); - return Selector>>( - selector: (context, s) => Tuple4(s.albumGroupFactor, s.albumSortFactor, s.albumSortReverse, s.pinnedFilters), + return Selector)>( + selector: (context, s) => (s.albumGroupFactor, s.albumSortFactor, s.albumSortReverse, s.pinnedFilters), shouldRebuild: (t1, t2) { - // `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN` + // `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within records const eq = DeepCollectionEquality(); - return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2) && eq.equals(t1.item3, t2.item3) && eq.equals(t1.item4, t2.item4)); + return !(eq.equals(t1.$1, t2.$1) && eq.equals(t1.$2, t2.$2) && eq.equals(t1.$3, t2.$3) && eq.equals(t1.$4, t2.$4)); }, builder: (context, s, child) { return ValueListenableBuilder( diff --git a/lib/widgets/filter_grids/common/action_delegates/album_set.dart b/lib/widgets/filter_grids/common/action_delegates/album_set.dart index 9a46e474d..8f9be79c8 100644 --- a/lib/widgets/filter_grids/common/action_delegates/album_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/album_set.dart @@ -17,6 +17,7 @@ import 'package:aves/theme/durations.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/view/view.dart'; import 'package:aves/widgets/common/action_mixins/entry_storage.dart'; +import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/tile_extent_controller.dart'; import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart'; @@ -32,7 +33,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class AlbumChipSetActionDelegate extends ChipSetActionDelegate with EntryStorageMixin { final Iterable> _items; @@ -168,14 +168,14 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate with @override Future configureView(BuildContext context) async { - final initialValue = Tuple4( + final initialValue = ( sortFactor, settings.albumGroupFactor, tileLayout, sortReverse, ); final extentController = context.read(); - final value = await showDialog>( + final value = await showDialog<(ChipSortFactor?, AlbumChipGroupFactor?, TileLayout?, bool)>( context: context, builder: (context) { return TileViewDialog( @@ -190,12 +190,12 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate with routeSettings: const RouteSettings(name: TileViewDialog.routeName), ); // wait for the dialog to hide as applying the change may block the UI - await Future.delayed(Durations.dialogTransitionAnimation * timeDilation); + await Future.delayed(ADurations.dialogTransitionAnimation * timeDilation); if (value != null && initialValue != value) { - sortFactor = value.item1!; - settings.albumGroupFactor = value.item2!; - tileLayout = value.item3!; - sortReverse = value.item4; + sortFactor = value.$1!; + settings.albumGroupFactor = value.$2!; + tileLayout = value.$3!; + sortReverse = value.$4; } } @@ -256,7 +256,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate with } }, ); - showFeedback(context, l10n.genericSuccessFeedback, showAction); + showFeedback(context, FeedbackType.info, l10n.genericSuccessFeedback, showAction); } Future _delete(BuildContext context, Set filters) async { @@ -364,7 +364,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate with final successCount = successOps.length; if (successCount < todoCount) { final count = todoCount - successCount; - showFeedbackWithMessenger(context, messenger, l10n.collectionDeleteFailureFeedback(count)); + showFeedbackWithMessenger(context, messenger, FeedbackType.warn, l10n.collectionDeleteFailureFeedback(count)); } // cleanup @@ -443,9 +443,9 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate with final successCount = successOps.length; if (successCount < todoCount) { final count = todoCount - successCount; - showFeedbackWithMessenger(context, messenger, l10n.collectionMoveFailureFeedback(count)); + showFeedbackWithMessenger(context, messenger, FeedbackType.warn, l10n.collectionMoveFailureFeedback(count)); } else { - showFeedbackWithMessenger(context, messenger, l10n.genericSuccessFeedback); + showFeedbackWithMessenger(context, messenger, FeedbackType.info, l10n.genericSuccessFeedback); } // cleanup diff --git a/lib/widgets/filter_grids/common/action_delegates/chip.dart b/lib/widgets/filter_grids/common/action_delegates/chip.dart index 5d3760a43..e9bfcff9f 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip.dart @@ -1,5 +1,6 @@ import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/highlight.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/vaults/vaults.dart'; @@ -26,6 +27,8 @@ class ChipActionDelegate with FeedbackMixin, VaultAwareMixin { case ChipAction.goToCountryPage: case ChipAction.goToPlacePage: case ChipAction.goToTagPage: + case ChipAction.ratingOrGreater: + case ChipAction.ratingOrLower: case ChipAction.reverse: return true; case ChipAction.hide: @@ -46,8 +49,12 @@ class ChipActionDelegate with FeedbackMixin, VaultAwareMixin { _goTo(context, filter, PlaceListPage.routeName, (context) => const PlaceListPage()); case ChipAction.goToTagPage: _goTo(context, filter, TagListPage.routeName, (context) => const TagListPage()); + case ChipAction.ratingOrGreater: + FilterNotification((filter as RatingFilter).copyWith(RatingFilter.opOrGreater)).dispatch(context); + case ChipAction.ratingOrLower: + FilterNotification((filter as RatingFilter).copyWith(RatingFilter.opOrLower)).dispatch(context); case ChipAction.reverse: - ReverseFilterNotification(filter).dispatch(context); + FilterNotification(filter.reverse()).dispatch(context); case ChipAction.hide: _hide(context, filter); case ChipAction.lockVault: @@ -95,8 +102,8 @@ class ChipActionDelegate with FeedbackMixin, VaultAwareMixin { } @immutable -class ReverseFilterNotification extends Notification { - final CollectionFilter reversedFilter; +class FilterNotification extends Notification { + final CollectionFilter filter; - ReverseFilterNotification(CollectionFilter filter) : reversedFilter = filter.reverse(); + const FilterNotification(this.filter); } diff --git a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart index 4f4a8b92d..daf10d73d 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart @@ -31,7 +31,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; abstract class ChipSetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMixin, VaultAwareMixin { Iterable> get allItems; @@ -216,14 +215,14 @@ abstract class ChipSetActionDelegate with FeedbackMi } Future configureView(BuildContext context) async { - final initialValue = Tuple4( + final initialValue = ( sortFactor, null, tileLayout, sortReverse, ); final extentController = context.read(); - final value = await showDialog>( + final value = await showDialog<(ChipSortFactor?, void, TileLayout?, bool)>( context: context, builder: (context) { return TileViewDialog( @@ -237,11 +236,11 @@ abstract class ChipSetActionDelegate with FeedbackMi routeSettings: const RouteSettings(name: TileViewDialog.routeName), ); // wait for the dialog to hide as applying the change may block the UI - await Future.delayed(Durations.dialogTransitionAnimation * timeDilation); + await Future.delayed(ADurations.dialogTransitionAnimation * timeDilation); if (value != null && initialValue != value) { - sortFactor = value.item1!; - tileLayout = value.item3!; - sortReverse = value.item4; + sortFactor = value.$1!; + tileLayout = value.$3!; + sortReverse = value.$4; } } @@ -326,15 +325,15 @@ abstract class ChipSetActionDelegate with FeedbackMi if (!await unlockFilter(context, filter)) return; final existingCover = covers.of(filter); - final entryId = existingCover?.item1; + final entryId = existingCover?.$1; final customEntry = entryId != null ? context.read().visibleEntries.firstWhereOrNull((entry) => entry.id == entryId) : null; - final selectedCover = await showDialog>( + final selectedCover = await showDialog<(AvesEntry?, String?, Color?)>( context: context, builder: (context) => CoverSelectionDialog( filter: filter, customEntry: customEntry, - customPackage: existingCover?.item2, - customColor: existingCover?.item3, + customPackage: existingCover?.$2, + customColor: existingCover?.$3, ), routeSettings: const RouteSettings(name: CoverSelectionDialog.routeName), ); @@ -344,9 +343,7 @@ abstract class ChipSetActionDelegate with FeedbackMi context.read().clearAppColor(filter.album); } - final selectedEntry = selectedCover.item1; - final selectedPackage = selectedCover.item2; - final selectedColor = selectedCover.item3; + final (selectedEntry, selectedPackage, selectedColor) = selectedCover; await covers.set( filter: filter, entryId: selectedEntry?.id, diff --git a/lib/widgets/filter_grids/common/app_bar.dart b/lib/widgets/filter_grids/common/app_bar.dart index 435576e71..d68cc0878 100644 --- a/lib/widgets/filter_grids/common/app_bar.dart +++ b/lib/widgets/filter_grids/common/app_bar.dart @@ -366,7 +366,7 @@ class _FilterGridAppBarState extends StatelessWidget { if (pinned) AnimatedPadding( padding: EdgeInsetsDirectional.only(end: padding), - duration: Durations.chipDecorationAnimation, + duration: ADurations.chipDecorationAnimation, child: Icon( AIcons.pin, color: _detailColor(context), @@ -193,7 +193,7 @@ class CoveredFilterChip extends StatelessWidget { if (filter is AlbumFilter && androidFileUtils.isOnRemovableStorage(filter.album)) AnimatedPadding( padding: EdgeInsetsDirectional.only(end: padding), - duration: Durations.chipDecorationAnimation, + duration: ADurations.chipDecorationAnimation, child: Icon( AIcons.removableStorage, color: _detailColor(context), @@ -203,7 +203,7 @@ class CoveredFilterChip extends StatelessWidget { if (filter is AlbumFilter && vaults.isVault(filter.album)) AnimatedPadding( padding: EdgeInsetsDirectional.only(end: padding), - duration: Durations.chipDecorationAnimation, + duration: ADurations.chipDecorationAnimation, child: Icon( AIcons.locked, color: _detailColor(context), diff --git a/lib/widgets/filter_grids/common/enums.dart b/lib/widgets/filter_grids/common/enums.dart index 0113ecfca..e132044ad 100644 --- a/lib/widgets/filter_grids/common/enums.dart +++ b/lib/widgets/filter_grids/common/enums.dart @@ -6,37 +6,26 @@ enum AlbumImportance { newAlbum, pinned, special, apps, vaults, regular } extension ExtraAlbumImportance on AlbumImportance { String getText(BuildContext context) { - switch (this) { - case AlbumImportance.newAlbum: - return context.l10n.albumTierNew; - case AlbumImportance.pinned: - return context.l10n.albumTierPinned; - case AlbumImportance.special: - return context.l10n.albumTierSpecial; - case AlbumImportance.apps: - return context.l10n.albumTierApps; - case AlbumImportance.vaults: - return context.l10n.albumTierVaults; - case AlbumImportance.regular: - return context.l10n.albumTierRegular; - } + final l10n = context.l10n; + return switch (this) { + AlbumImportance.newAlbum => l10n.albumTierNew, + AlbumImportance.pinned => l10n.albumTierPinned, + AlbumImportance.special => l10n.albumTierSpecial, + AlbumImportance.apps => l10n.albumTierApps, + AlbumImportance.vaults => l10n.albumTierVaults, + AlbumImportance.regular => l10n.albumTierRegular, + }; } IconData getIcon() { - switch (this) { - case AlbumImportance.newAlbum: - return AIcons.newTier; - case AlbumImportance.pinned: - return AIcons.pin; - case AlbumImportance.special: - return AIcons.important; - case AlbumImportance.apps: - return AIcons.app; - case AlbumImportance.vaults: - return AIcons.locked; - case AlbumImportance.regular: - return AIcons.album; - } + return switch (this) { + AlbumImportance.newAlbum => AIcons.newTier, + AlbumImportance.pinned => AIcons.pin, + AlbumImportance.special => AIcons.important, + AlbumImportance.apps => AIcons.app, + AlbumImportance.vaults => AIcons.locked, + AlbumImportance.regular => AIcons.album, + }; } } @@ -44,24 +33,19 @@ enum AlbumMimeType { images, videos, mixed } extension ExtraAlbumMimeType on AlbumMimeType { String getText(BuildContext context) { - switch (this) { - case AlbumMimeType.images: - return context.l10n.drawerCollectionImages; - case AlbumMimeType.videos: - return context.l10n.drawerCollectionVideos; - case AlbumMimeType.mixed: - return context.l10n.albumMimeTypeMixed; - } + final l10n = context.l10n; + return switch (this) { + AlbumMimeType.images => l10n.drawerCollectionImages, + AlbumMimeType.videos => l10n.drawerCollectionVideos, + AlbumMimeType.mixed => l10n.albumMimeTypeMixed, + }; } IconData getIcon() { - switch (this) { - case AlbumMimeType.images: - return AIcons.image; - case AlbumMimeType.videos: - return AIcons.video; - case AlbumMimeType.mixed: - return AIcons.mimeType; - } + return switch (this) { + AlbumMimeType.images => AIcons.image, + AlbumMimeType.videos => AIcons.video, + AlbumMimeType.mixed => AIcons.mimeType, + }; } } diff --git a/lib/widgets/filter_grids/common/filter_grid_page.dart b/lib/widgets/filter_grids/common/filter_grid_page.dart index 7daaca92a..abc85db03 100644 --- a/lib/widgets/filter_grids/common/filter_grid_page.dart +++ b/lib/widgets/filter_grids/common/filter_grid_page.dart @@ -46,7 +46,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; typedef QueryTest = List> Function(BuildContext context, List> filters, String query); @@ -317,13 +316,10 @@ class _FilterGridContentState extends State<_FilterG final sectionedListLayoutProvider = ValueListenableBuilder( valueListenable: context.select>((controller) => controller.extentNotifier), builder: (context, thumbnailExtent, child) { - return Selector>( - selector: (context, c) => Tuple4(c.viewportSize.width, c.columnCount, c.spacing, c.horizontalPadding), + return Selector( + selector: (context, c) => (c.viewportSize.width, c.columnCount, c.spacing, c.horizontalPadding), builder: (context, c, child) { - final scrollableWidth = c.item1; - final columnCount = c.item2; - final tileSpacing = c.item3; - final horizontalPadding = c.item4; + final (scrollableWidth, columnCount, tileSpacing, horizontalPadding) = c; // do not listen for animation delay change final target = context.read().staggeredAnimationPageTarget; final tileAnimationDelay = context.read().getTileAnimationDelay(target); @@ -546,7 +542,7 @@ class _FilterSectionedContentState extends State<_Fi final item = visibleSections.values.expand((list) => list).firstWhereOrNull((gridItem) => gridItem.filter == filter); if (item == null) return; - await Future.delayed(Durations.highlightScrollInitDelay); + await Future.delayed(ADurations.highlightScrollInitDelay); final animate = context.read().accessibilityAnimations.animate; highlightInfo.trackItem(item, animate: animate, highlightItem: filter); @@ -571,9 +567,7 @@ class _FilterScaler extends StatelessWidget { @override Widget build(BuildContext context) { final textScaleFactor = MediaQuery.textScaleFactorOf(context); - final metrics = context.select>((v) => Tuple2(v.spacing, v.horizontalPadding)); - final tileSpacing = metrics.item1; - final horizontalPadding = metrics.item2; + final (tileSpacing, horizontalPadding) = context.select((v) => (v.spacing, v.horizontalPadding)); final brightness = Theme.of(context).brightness; return GridScaleGestureDetector>( scrollableKey: scrollableKey, diff --git a/lib/widgets/filter_grids/countries_page.dart b/lib/widgets/filter_grids/countries_page.dart index dcaf5e879..a26858939 100644 --- a/lib/widgets/filter_grids/countries_page.dart +++ b/lib/widgets/filter_grids/countries_page.dart @@ -13,7 +13,6 @@ import 'package:aves_model/aves_model.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class CountryListPage extends StatelessWidget { static const routeName = '/countries'; @@ -23,12 +22,12 @@ class CountryListPage extends StatelessWidget { @override Widget build(BuildContext context) { final source = context.read(); - return Selector>>( - selector: (context, s) => Tuple3(s.countrySortFactor, s.countrySortReverse, s.pinnedFilters), + return Selector)>( + selector: (context, s) => (s.countrySortFactor, s.countrySortReverse, s.pinnedFilters), shouldRebuild: (t1, t2) { - // `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN` + // `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within records const eq = DeepCollectionEquality(); - return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2) && eq.equals(t1.item3, t2.item3)); + return !(eq.equals(t1.$1, t2.$1) && eq.equals(t1.$2, t2.$2) && eq.equals(t1.$3, t2.$3)); }, builder: (context, s, child) { return StreamBuilder( diff --git a/lib/widgets/filter_grids/places_page.dart b/lib/widgets/filter_grids/places_page.dart index e29dc1c97..c0b456a75 100644 --- a/lib/widgets/filter_grids/places_page.dart +++ b/lib/widgets/filter_grids/places_page.dart @@ -13,7 +13,6 @@ import 'package:aves_model/aves_model.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class PlaceListPage extends StatelessWidget { static const routeName = '/places'; @@ -23,12 +22,12 @@ class PlaceListPage extends StatelessWidget { @override Widget build(BuildContext context) { final source = context.read(); - return Selector>>( - selector: (context, s) => Tuple3(s.placeSortFactor, s.placeSortReverse, s.pinnedFilters), + return Selector)>( + selector: (context, s) => (s.placeSortFactor, s.placeSortReverse, s.pinnedFilters), shouldRebuild: (t1, t2) { - // `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN` + // `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within records const eq = DeepCollectionEquality(); - return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2) && eq.equals(t1.item3, t2.item3)); + return !(eq.equals(t1.$1, t2.$1) && eq.equals(t1.$2, t2.$2) && eq.equals(t1.$3, t2.$3)); }, builder: (context, s, child) { return StreamBuilder( diff --git a/lib/widgets/filter_grids/states_page.dart b/lib/widgets/filter_grids/states_page.dart index 03c4a7dd6..b905bf794 100644 --- a/lib/widgets/filter_grids/states_page.dart +++ b/lib/widgets/filter_grids/states_page.dart @@ -14,7 +14,6 @@ import 'package:aves_model/aves_model.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class StateListPage extends StatelessWidget { static const routeName = '/states'; @@ -29,12 +28,12 @@ class StateListPage extends StatelessWidget { @override Widget build(BuildContext context) { final source = context.read(); - return Selector>>( - selector: (context, s) => Tuple3(s.stateSortFactor, s.stateSortReverse, s.pinnedFilters), + return Selector)>( + selector: (context, s) => (s.stateSortFactor, s.stateSortReverse, s.pinnedFilters), shouldRebuild: (t1, t2) { - // `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN` + // `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within records const eq = DeepCollectionEquality(); - return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2) && eq.equals(t1.item3, t2.item3)); + return !(eq.equals(t1.$1, t2.$1) && eq.equals(t1.$2, t2.$2) && eq.equals(t1.$3, t2.$3)); }, builder: (context, s, child) { return StreamBuilder( diff --git a/lib/widgets/filter_grids/tags_page.dart b/lib/widgets/filter_grids/tags_page.dart index f34d44ed1..70deab605 100644 --- a/lib/widgets/filter_grids/tags_page.dart +++ b/lib/widgets/filter_grids/tags_page.dart @@ -13,7 +13,6 @@ import 'package:aves_model/aves_model.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class TagListPage extends StatelessWidget { static const routeName = '/tags'; @@ -23,12 +22,12 @@ class TagListPage extends StatelessWidget { @override Widget build(BuildContext context) { final source = context.read(); - return Selector>>( - selector: (context, s) => Tuple3(s.tagSortFactor, s.tagSortReverse, s.pinnedFilters), + return Selector)>( + selector: (context, s) => (s.tagSortFactor, s.tagSortReverse, s.pinnedFilters), shouldRebuild: (t1, t2) { - // `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN` + // `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within records const eq = DeepCollectionEquality(); - return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2) && eq.equals(t1.item3, t2.item3)); + return !(eq.equals(t1.$1, t2.$1) && eq.equals(t1.$2, t2.$2) && eq.equals(t1.$3, t2.$3)); }, builder: (context, s, child) { return StreamBuilder( diff --git a/lib/widgets/map/map_page.dart b/lib/widgets/map/map_page.dart index 665ccc8ca..59bf3b2f2 100644 --- a/lib/widgets/map/map_page.dart +++ b/lib/widgets/map/map_page.dart @@ -115,7 +115,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin if (ExtraEntryMapStyle.isHeavy(settings.mapStyle)) { _isPageAnimatingNotifier.value = true; - Future.delayed(Durations.pageTransitionAnimation * timeDilation).then((_) { + Future.delayed(ADurations.pageTransitionAnimation * timeDilation).then((_) { if (!mounted) return; _isPageAnimatingNotifier.value = false; }); @@ -139,7 +139,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin _subscriptions.add(openingCollection.source.eventBus.on().listen((e) => _updateRegionCollection())); _selectedIndexNotifier.addListener(_onThumbnailIndexChanged); - Future.delayed(Durations.pageTransitionAnimation * timeDilation + const Duration(seconds: 1), () { + Future.delayed(ADurations.pageTransitionAnimation * timeDilation + const Duration(seconds: 1), () { final regionEntries = regionCollection?.sortedEntries ?? []; final initialEntry = widget.initialEntry ?? regionEntries.firstOrNull; if (initialEntry != null) { @@ -174,8 +174,8 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin onNotification: (notification) { if (notification is FilterSelectedNotification) { _goToCollection(notification.filter); - } else if (notification is ReverseFilterNotification) { - _goToCollection(notification.reversedFilter); + } else if (notification is FilterNotification) { + _goToCollection(notification.filter); } else { return false; } @@ -475,7 +475,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin ); if (selectedAction != null) { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(Durations.popupMenuAnimation * timeDilation); + await Future.delayed(ADurations.popupMenuAnimation * timeDilation); final delegate = EntrySetActionDelegate(); switch (selectedAction) { case MapClusterAction.editLocation: diff --git a/lib/widgets/map/scroller.dart b/lib/widgets/map/scroller.dart index 0d013ae12..5bb89661f 100644 --- a/lib/widgets/map/scroller.dart +++ b/lib/widgets/map/scroller.dart @@ -28,7 +28,7 @@ class MapEntryScroller extends StatefulWidget { class _MapEntryScrollerState extends State { final ValueNotifier _infoEntryNotifier = ValueNotifier(null); - final Debouncer _infoDebouncer = Debouncer(delay: Durations.mapInfoDebounceDelay); + final Debouncer _infoDebouncer = Debouncer(delay: ADurations.mapInfoDebounceDelay); @override void initState() { diff --git a/lib/widgets/navigation/drawer/app_drawer.dart b/lib/widgets/navigation/drawer/app_drawer.dart index 37ac737cf..b499a33fb 100644 --- a/lib/widgets/navigation/drawer/app_drawer.dart +++ b/lib/widgets/navigation/drawer/app_drawer.dart @@ -17,6 +17,7 @@ import 'package:aves/widgets/about/about_page.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/media_query.dart'; import 'package:aves/widgets/common/identity/aves_logo.dart'; +import 'package:aves/widgets/common/search/page.dart'; import 'package:aves/widgets/debug/app_debug_page.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart'; @@ -110,7 +111,7 @@ class _AppDrawerState extends State { Widget _buildHeader(BuildContext context) { Future goTo(String routeName, WidgetBuilder pageBuilder) async { Navigator.maybeOf(context)?.pop(); - await Future.delayed(Durations.drawerTransitionAnimation); + await Future.delayed(ADurations.drawerTransitionAnimation); await Navigator.maybeOf(context)?.push(MaterialPageRoute( settings: RouteSettings(name: routeName), builder: pageBuilder, @@ -260,6 +261,7 @@ class _AppDrawerState extends State { // key is expected by test driver key: Key('drawer-page-$route'), trailing: trailing, + topLevel: route != SearchPage.routeName, routeName: route, ); }), diff --git a/lib/widgets/search/search_delegate.dart b/lib/widgets/search/search_delegate.dart index fb1fd59d4..290514d4d 100644 --- a/lib/widgets/search/search_delegate.dart +++ b/lib/widgets/search/search_delegate.dart @@ -89,9 +89,9 @@ class CollectionSearchDelegate extends AvesSearchDelegate with FeedbackMixin, Va final upQuery = query.trim().toUpperCase(); bool containQuery(String s) => s.toUpperCase().contains(upQuery); return SafeArea( - child: NotificationListener( + child: NotificationListener( onNotification: (notification) { - _select(context, notification.reversedFilter); + _select(context, notification.filter); return true; }, child: ValueListenableBuilder( diff --git a/lib/widgets/settings/app_export/items.dart b/lib/widgets/settings/app_export/items.dart index 5ee21faa5..ac5f20eb3 100644 --- a/lib/widgets/settings/app_export/items.dart +++ b/lib/widgets/settings/app_export/items.dart @@ -9,25 +9,20 @@ enum AppExportItem { covers, favourites, settings } extension ExtraAppExportItem on AppExportItem { String getText(BuildContext context) { - switch (this) { - case AppExportItem.covers: - return context.l10n.appExportCovers; - case AppExportItem.favourites: - return context.l10n.appExportFavourites; - case AppExportItem.settings: - return context.l10n.appExportSettings; - } + final l10n = context.l10n; + return switch (this) { + AppExportItem.covers => l10n.appExportCovers, + AppExportItem.favourites => l10n.appExportFavourites, + AppExportItem.settings => l10n.appExportSettings, + }; } dynamic export(CollectionSource source) { - switch (this) { - case AppExportItem.covers: - return covers.export(source); - case AppExportItem.favourites: - return favourites.export(source); - case AppExportItem.settings: - return settings.export(); - } + return switch (this) { + AppExportItem.covers => covers.export(source), + AppExportItem.favourites => favourites.export(source), + AppExportItem.settings => settings.export(), + }; } Future import(dynamic jsonMap, CollectionSource source) async { diff --git a/lib/widgets/settings/common/quick_actions/action_panel.dart b/lib/widgets/settings/common/quick_actions/action_panel.dart index 2b099bbbd..636b5923d 100644 --- a/lib/widgets/settings/common/quick_actions/action_panel.dart +++ b/lib/widgets/settings/common/quick_actions/action_panel.dart @@ -29,7 +29,7 @@ class ActionPanel extends StatelessWidget { borderRadius: const BorderRadius.all(Radius.circular(8)), ), margin: const EdgeInsets.all(16), - duration: Durations.quickActionHighlightAnimation, + duration: ADurations.quickActionHighlightAnimation, child: ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(8)), child: child, diff --git a/lib/widgets/settings/common/quick_actions/editor_page.dart b/lib/widgets/settings/common/quick_actions/editor_page.dart index 4ba51136c..331dcd49d 100644 --- a/lib/widgets/settings/common/quick_actions/editor_page.dart +++ b/lib/widgets/settings/common/quick_actions/editor_page.dart @@ -111,7 +111,7 @@ class _QuickActionEditorBodyState extends State extends State _reordering = false); + Future.delayed(ADurations.quickActionListAnimation).then((value) => _reordering = false); return true; } @@ -344,7 +344,7 @@ class _QuickActionEditorBodyState extends State DraggedPlaceholder(child: _buildQuickActionButton(action, animation)), - duration: Durations.quickActionListAnimation, + duration: ADurations.quickActionListAnimation, ); _quickActionsChangeNotifier.notify(); return true; diff --git a/lib/widgets/settings/common/tile_leading.dart b/lib/widgets/settings/common/tile_leading.dart index 1fe84bac9..fb96a5063 100644 --- a/lib/widgets/settings/common/tile_leading.dart +++ b/lib/widgets/settings/common/tile_leading.dart @@ -25,7 +25,7 @@ class SettingsTileLeading extends StatelessWidget { )), shape: BoxShape.circle, ), - duration: Durations.themeColorModeAnimation, + duration: ADurations.themeColorModeAnimation, child: DecoratedIcon( icon, size: 18, diff --git a/lib/widgets/settings/common/tiles.dart b/lib/widgets/settings/common/tiles.dart index c9fabc4c7..0422ce3d4 100644 --- a/lib/widgets/settings/common/tiles.dart +++ b/lib/widgets/settings/common/tiles.dart @@ -68,7 +68,7 @@ class SettingsSwitchListTile extends StatelessWidget { Expanded(child: titleWidget), AnimatedOpacity( opacity: current ? 1 : disabledOpacity, - duration: Durations.toggleableTransitionAnimation, + duration: ADurations.toggleableTransitionAnimation, child: trailing, ), ], diff --git a/lib/widgets/settings/language/locale_tile.dart b/lib/widgets/settings/language/locale_tile.dart index e56081eb6..fa0b2fa49 100644 --- a/lib/widgets/settings/language/locale_tile.dart +++ b/lib/widgets/settings/language/locale_tile.dart @@ -32,7 +32,7 @@ class LocaleTile extends StatelessWidget { ), ); // wait for the dialog to hide as applying the change may block the UI - await Future.delayed(Durations.pageTransitionAnimation * timeDilation); + await Future.delayed(ADurations.pageTransitionAnimation * timeDilation); if (value != null) { settings.locale = value == systemLocaleOption ? null : value; } diff --git a/lib/widgets/settings/navigation/drawer.dart b/lib/widgets/settings/navigation/drawer.dart index 20e5a948e..cd82b9a34 100644 --- a/lib/widgets/settings/navigation/drawer.dart +++ b/lib/widgets/settings/navigation/drawer.dart @@ -15,7 +15,6 @@ import 'package:aves/widgets/settings/navigation/drawer_tab_albums.dart'; import 'package:aves/widgets/settings/navigation/drawer_tab_fixed.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:tuple/tuple.dart'; class NavigationDrawerEditorPage extends StatefulWidget { static const routeName = '/settings/navigation_drawer'; @@ -65,8 +64,8 @@ class _NavigationDrawerEditorPageState extends State @override Widget build(BuildContext context) { final l10n = context.l10n; - final tabs = >[ - Tuple2( + final tabs = <(Tab, Widget)>[ + ( Tab(text: l10n.settingsNavigationDrawerTabTypes), DrawerFixedListTab( items: _typeItems, @@ -75,13 +74,13 @@ class _NavigationDrawerEditorPageState extends State title: (item) => DrawerFilterTitle(filter: item), ), ), - Tuple2( + ( Tab(text: l10n.settingsNavigationDrawerTabAlbums), DrawerAlbumTab( items: _albumItems, ), ), - Tuple2( + ( Tab(text: l10n.settingsNavigationDrawerTabPages), DrawerFixedListTab( items: _pageItems, @@ -99,7 +98,7 @@ class _NavigationDrawerEditorPageState extends State automaticallyImplyLeading: !settings.useTvLayout, title: Text(l10n.settingsNavigationDrawerEditorPageTitle), bottom: TabBar( - tabs: tabs.map((t) => t.item1).toList(), + tabs: tabs.map((t) => t.$1).toList(), ), ), body: WillPopScope( @@ -111,7 +110,7 @@ class _NavigationDrawerEditorPageState extends State }, child: SafeArea( child: TabBarView( - children: tabs.map((t) => t.item2).toList(), + children: tabs.map((t) => t.$2).toList(), ), ), ), diff --git a/lib/widgets/settings/privacy/file_picker/file_picker_page.dart b/lib/widgets/settings/privacy/file_picker/file_picker_page.dart index a62d27bed..592411125 100644 --- a/lib/widgets/settings/privacy/file_picker/file_picker_page.dart +++ b/lib/widgets/settings/privacy/file_picker/file_picker_page.dart @@ -83,7 +83,7 @@ class _FilePickerPageState extends State { }, onSelected: (action) async { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(Durations.popupMenuAnimation * timeDilation); + await Future.delayed(ADurations.popupMenuAnimation * timeDilation); switch (action) { case _PickerAction.toggleHiddenView: settings.filePickerShowHiddenFiles = !showHidden; @@ -168,7 +168,7 @@ class _FilePickerPageState extends State { title: Text(v.getDescription(context)), onTap: () async { Navigator.maybeOf(context)?.pop(); - await Future.delayed(Durations.drawerTransitionAnimation); + await Future.delayed(ADurations.drawerTransitionAnimation); _goTo(v.path); setState(() {}); }, diff --git a/lib/widgets/settings/privacy/hidden_items_page.dart b/lib/widgets/settings/privacy/hidden_items_page.dart index 0e94d6372..bd5f602da 100644 --- a/lib/widgets/settings/privacy/hidden_items_page.dart +++ b/lib/widgets/settings/privacy/hidden_items_page.dart @@ -13,7 +13,6 @@ import 'package:aves/widgets/settings/privacy/file_picker/file_picker_page.dart' import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class HiddenItemsPage extends StatelessWidget { static const routeName = '/settings/hidden_items'; @@ -23,12 +22,12 @@ class HiddenItemsPage extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = context.l10n; - final tabs = >[ - Tuple2( + final tabs = <(Tab, Widget)>[ + ( Tab(text: l10n.settingsHiddenItemsTabFilters), const _HiddenFilters(), ), - Tuple2( + ( Tab(text: l10n.settingsHiddenItemsTabPaths), const _HiddenPaths(), ), @@ -41,12 +40,12 @@ class HiddenItemsPage extends StatelessWidget { automaticallyImplyLeading: !settings.useTvLayout, title: Text(l10n.settingsHiddenItemsPageTitle), bottom: TabBar( - tabs: tabs.map((t) => t.item1).toList(), + tabs: tabs.map((t) => t.$1).toList(), ), ), body: SafeArea( child: TabBarView( - children: tabs.map((t) => t.item2).toList(), + children: tabs.map((t) => t.$2).toList(), ), ), ), @@ -155,7 +154,7 @@ class _HiddenPaths extends StatelessWidget { ), ); // wait for the dialog to hide as applying the change may block the UI - await Future.delayed(Durations.pageTransitionAnimation * timeDilation); + await Future.delayed(ADurations.pageTransitionAnimation * timeDilation); if (path != null && path.isNotEmpty) { settings.changeFilterVisibility({PathFilter(path)}, false); } diff --git a/lib/widgets/settings/settings_mobile_page.dart b/lib/widgets/settings/settings_mobile_page.dart index 354e613f0..6dc4cbd2d 100644 --- a/lib/widgets/settings/settings_mobile_page.dart +++ b/lib/widgets/settings/settings_mobile_page.dart @@ -61,17 +61,17 @@ class _SettingsMobilePageState extends State with FeedbackMi return [ PopupMenuItem( value: SettingsAction.export, - child: MenuRow(text: context.l10n.settingsActionExport, icon: const Icon(AIcons.fileExport)), + child: MenuRow(text: context.l10n.settingsActionExport, icon: Icon(AIcons.fileExport)), ), PopupMenuItem( value: SettingsAction.import, - child: MenuRow(text: context.l10n.settingsActionImport, icon: const Icon(AIcons.fileImport)), + child: MenuRow(text: context.l10n.settingsActionImport, icon: Icon(AIcons.fileImport)), ), ]; }, onSelected: (action) async { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(Durations.popupMenuAnimation * timeDilation); + await Future.delayed(ADurations.popupMenuAnimation * timeDilation); _onActionSelected(action); }, ), @@ -119,9 +119,9 @@ class _SettingsMobilePageState extends State with FeedbackMi ); if (success != null) { if (success) { - showFeedback(context, context.l10n.genericSuccessFeedback); + showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback); } else { - showFeedback(context, context.l10n.genericFailureFeedback); + showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback); } } case SettingsAction.import: @@ -141,7 +141,7 @@ class _SettingsMobilePageState extends State with FeedbackMi } else { if (allJsonMap is! Map) { debugPrint('failed to import app json=$allJsonMap'); - showFeedback(context, context.l10n.genericFailureFeedback); + showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback); return; } allJsonMap.keys.where((v) => v != exportVersionKey).forEach((k) { @@ -165,10 +165,10 @@ class _SettingsMobilePageState extends State with FeedbackMi await Future.forEach(toImport, (item) async { return item.import(importable[item], source); }); - showFeedback(context, context.l10n.genericSuccessFeedback); + showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback); } catch (error) { debugPrint('failed to import app json, error=$error'); - showFeedback(context, context.l10n.genericFailureFeedback); + showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback); } } } diff --git a/lib/widgets/settings/thumbnails/collection_actions_editor_page.dart b/lib/widgets/settings/thumbnails/collection_actions_editor_page.dart index 551612110..c16762cd7 100644 --- a/lib/widgets/settings/thumbnails/collection_actions_editor_page.dart +++ b/lib/widgets/settings/thumbnails/collection_actions_editor_page.dart @@ -5,7 +5,6 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/settings/common/quick_actions/editor_page.dart'; import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; -import 'package:tuple/tuple.dart'; class CollectionActionEditorPage extends StatelessWidget { static const routeName = '/settings/collection_actions'; @@ -15,8 +14,8 @@ class CollectionActionEditorPage extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = context.l10n; - final tabs = >[ - Tuple2( + final tabs = <(Tab, Widget)>[ + ( Tab(text: l10n.settingsCollectionQuickActionTabBrowsing), QuickActionEditorBody( bannerText: context.l10n.settingsCollectionBrowsingQuickActionEditorBanner, @@ -27,7 +26,7 @@ class CollectionActionEditorPage extends StatelessWidget { save: (actions) => settings.collectionBrowsingQuickActions = actions, ), ), - Tuple2( + ( Tab(text: l10n.settingsCollectionQuickActionTabSelecting), QuickActionEditorBody( bannerText: context.l10n.settingsCollectionSelectionQuickActionEditorBanner, @@ -49,12 +48,12 @@ class CollectionActionEditorPage extends StatelessWidget { appBar: AppBar( title: Text(context.l10n.settingsCollectionQuickActionEditorPageTitle), bottom: TabBar( - tabs: tabs.map((t) => t.item1).toList(), + tabs: tabs.map((t) => t.$1).toList(), ), ), body: SafeArea( child: TabBarView( - children: tabs.map((t) => t.item2).toList(), + children: tabs.map((t) => t.$2).toList(), ), ), ), diff --git a/lib/widgets/settings/video/subtitle_theme.dart b/lib/widgets/settings/video/subtitle_theme.dart index 4d7857736..6758d9437 100644 --- a/lib/widgets/settings/video/subtitle_theme.dart +++ b/lib/widgets/settings/video/subtitle_theme.dart @@ -97,15 +97,12 @@ class SubtitleThemePage extends StatelessWidget { } String _getTextAlignName(BuildContext context, TextAlign align) { - switch (align) { - case TextAlign.left: - return context.l10n.settingsSubtitleThemeTextAlignmentLeft; - case TextAlign.center: - return context.l10n.settingsSubtitleThemeTextAlignmentCenter; - case TextAlign.right: - return context.l10n.settingsSubtitleThemeTextAlignmentRight; - default: - return ''; - } + final l10n = context.l10n; + return switch (align) { + TextAlign.left => l10n.settingsSubtitleThemeTextAlignmentLeft, + TextAlign.center => l10n.settingsSubtitleThemeTextAlignmentCenter, + TextAlign.right => l10n.settingsSubtitleThemeTextAlignmentRight, + _ => '', + }; } } diff --git a/lib/widgets/settings/viewer/overlay.dart b/lib/widgets/settings/viewer/overlay.dart index 33110575e..df9726ba7 100644 --- a/lib/widgets/settings/viewer/overlay.dart +++ b/lib/widgets/settings/viewer/overlay.dart @@ -1,10 +1,11 @@ import 'package:aves/model/settings/settings.dart'; +import 'package:aves/view/view.dart'; import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/settings/common/tiles.dart'; +import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class ViewerOverlayPage extends StatelessWidget { static const routeName = '/settings/viewer/overlay'; @@ -34,11 +35,10 @@ class ViewerOverlayPage extends StatelessWidget { title: context.l10n.settingsViewerShowInformation, subtitle: context.l10n.settingsViewerShowInformationSubtitle, ), - Selector>( - selector: (context, s) => Tuple2(s.showOverlayInfo, s.showOverlayRatingTags), + Selector( + selector: (context, s) => (s.showOverlayInfo, s.showOverlayRatingTags), builder: (context, s, child) { - final showInfo = s.item1; - final current = s.item2; + final (showInfo, current) = s; return SwitchListTile( value: current, onChanged: showInfo ? (v) => settings.showOverlayRatingTags = v : null, @@ -46,11 +46,10 @@ class ViewerOverlayPage extends StatelessWidget { ); }, ), - Selector>( - selector: (context, s) => Tuple2(s.showOverlayInfo, s.showOverlayShootingDetails), + Selector( + selector: (context, s) => (s.showOverlayInfo, s.showOverlayShootingDetails), builder: (context, s, child) { - final showInfo = s.item1; - final current = s.item2; + final (showInfo, current) = s; return SwitchListTile( value: current, onChanged: showInfo ? (v) => settings.showOverlayShootingDetails = v : null, @@ -58,11 +57,10 @@ class ViewerOverlayPage extends StatelessWidget { ); }, ), - Selector>( - selector: (context, s) => Tuple2(s.showOverlayInfo, s.showOverlayDescription), + Selector( + selector: (context, s) => (s.showOverlayInfo, s.showOverlayDescription), builder: (context, s, child) { - final showInfo = s.item1; - final current = s.item2; + final (showInfo, current) = s; return SwitchListTile( value: current, onChanged: showInfo ? (v) => settings.showOverlayDescription = v : null, @@ -82,6 +80,14 @@ class ViewerOverlayPage extends StatelessWidget { onChanged: (v) => settings.showOverlayThumbnailPreview = v, title: context.l10n.settingsViewerShowOverlayThumbnails, ), + if (!useTvLayout) + SettingsSelectionListTile( + values: OverlayHistogramStyle.values, + getName: (context, v) => v.getName(context), + selector: (context, s) => s.overlayHistogramStyle, + onSelection: (v) => settings.overlayHistogramStyle = v, + tileTitle: context.l10n.settingsViewerShowHistogram, + ), ], ), ), diff --git a/lib/widgets/settings/viewer/viewer_actions_editor.dart b/lib/widgets/settings/viewer/viewer_actions_editor.dart index 912c13173..824d63fac 100644 --- a/lib/widgets/settings/viewer/viewer_actions_editor.dart +++ b/lib/widgets/settings/viewer/viewer_actions_editor.dart @@ -27,7 +27,7 @@ class ViewerActionEditorPage extends StatelessWidget { EntryAction.flip, ], [ - ...EntryActions.exportInternal, + ...EntryActions.export, ...EntryActions.video.whereNot((v) => v == EntryAction.videoSettings), ], EntryActions.commonMetadataActions, diff --git a/lib/widgets/stats/mime_donut.dart b/lib/widgets/stats/mime_donut.dart index 82066c751..4e0fdc4a5 100644 --- a/lib/widgets/stats/mime_donut.dart +++ b/lib/widgets/stats/mime_donut.dart @@ -1,21 +1,14 @@ -import 'dart:math'; - import 'package:aves/model/filters/mime.dart'; -import 'package:aves/model/settings/enums/accessibility_animations.dart'; -import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/colors.dart'; -import 'package:aves/theme/icons.dart'; import 'package:aves/utils/mime_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/identity/aves_donut.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; -import 'package:charts_flutter/flutter.dart' as charts; -import 'package:collection/collection.dart'; -import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; -class MimeDonut extends StatefulWidget { +class MimeDonut extends StatelessWidget { final IconData icon; final Map byMimeTypes; final Duration animationDuration; @@ -29,157 +22,23 @@ class MimeDonut extends StatefulWidget { required this.onFilterSelection, }); - @override - State createState() => _MimeDonutState(); -} - -class _MimeDonutState extends State with AutomaticKeepAliveClientMixin { - Map get byMimeTypes => widget.byMimeTypes; - - static const mimeDonutMinWidth = 124.0; - @override Widget build(BuildContext context) { - super.build(context); - - if (byMimeTypes.isEmpty) return const SizedBox(); - - final l10n = context.l10n; - final locale = l10n.localeName; + final locale = context.l10n.localeName; final numberFormat = NumberFormat.decimalPattern(locale); - final sum = byMimeTypes.values.sum; - - final colors = context.watch(); - final seriesData = byMimeTypes.entries.map((kv) { - final mimeType = kv.key; - final displayText = MimeUtils.displayType(mimeType); - return EntryByMimeDatum( - mimeType: mimeType, - displayText: displayText, - color: colors.fromString(displayText), - entryCount: kv.value, - ); - }).toList(); - seriesData.sort((d1, d2) { - final c = d2.entryCount.compareTo(d1.entryCount); - return c != 0 ? c : compareAsciiUpperCase(d1.displayText, d2.displayText); - }); - - final series = [ - charts.Series( - id: 'mime', - colorFn: (d, i) => charts.ColorUtil.fromDartColor(d.color), - domainFn: (d, i) => d.displayText, - measureFn: (d, i) => d.entryCount, - data: seriesData, - labelAccessorFn: (d, _) => '${d.displayText}: ${d.entryCount}', - ), - ]; - - return LayoutBuilder(builder: (context, constraints) { - final textScaleFactor = MediaQuery.textScaleFactorOf(context); - final minWidth = mimeDonutMinWidth * textScaleFactor; - final availableWidth = constraints.maxWidth; - final dim = max(minWidth, availableWidth / (availableWidth > 4 * minWidth ? 4 : (availableWidth > 2 * minWidth ? 2 : 1))); - - final donut = SizedBox( - width: dim, - height: dim, - child: Stack( - children: [ - charts.PieChart( - series, - animate: context.select((v) => v.accessibilityAnimations.animate), - animationDuration: widget.animationDuration, - defaultRenderer: charts.ArcRendererConfig( - arcWidth: 16, - ), - ), - Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(widget.icon), - Text( - numberFormat.format(sum), - textAlign: TextAlign.center, - ), - ], - ), - ), - ], - ), - ); - final legend = SizedBox( - width: dim, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: seriesData - .map((d) => InkWell( - onTap: () => widget.onFilterSelection(MimeFilter(d.mimeType)), - borderRadius: const BorderRadius.all(Radius.circular(123)), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(AIcons.disc, color: d.color), - const SizedBox(width: 8), - Flexible( - child: Text( - d.displayText, - overflow: TextOverflow.fade, - softWrap: false, - maxLines: 1, - ), - ), - const SizedBox(width: 8), - Text( - numberFormat.format(d.entryCount), - style: TextStyle( - color: Theme.of(context).textTheme.bodySmall!.color, - ), - ), - const SizedBox(width: 4), - ], - ), - )) - .toList(), - ), - ); - final children = [ - donut, - legend, - ]; - return availableWidth > minWidth * 2 - ? Row( - mainAxisSize: MainAxisSize.min, - children: children, - ) - : Column( - mainAxisSize: MainAxisSize.min, - children: children, - ); - }); + String formatKey(d) => MimeUtils.displayType(d.key); + return AvesDonut( + title: Icon(icon), + byTypes: byMimeTypes, + animationDuration: animationDuration, + formatKey: formatKey, + formatValue: numberFormat.format, + colorize: (context, d) { + final colors = context.read(); + return colors.fromString(formatKey(d)); + }, + onTap: (d) => onFilterSelection(MimeFilter(d.key)), + ); } - - @override - bool get wantKeepAlive => true; -} - -@immutable -class EntryByMimeDatum extends Equatable { - final String mimeType, displayText; - final Color color; - final int entryCount; - - @override - List get props => [mimeType, displayText, color, entryCount]; - - const EntryByMimeDatum({ - required this.mimeType, - required this.displayText, - required this.color, - required this.entryCount, - }); } diff --git a/lib/widgets/stats/stats_page.dart b/lib/widgets/stats/stats_page.dart index a7f6cdb77..a0a146bf6 100644 --- a/lib/widgets/stats/stats_page.dart +++ b/lib/widgets/stats/stats_page.dart @@ -13,6 +13,7 @@ import 'package:aves/model/source/collection_source.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/theme/styles.dart'; +import 'package:aves/utils/file_utils.dart'; import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/action_mixins/vault_aware.dart'; @@ -58,6 +59,7 @@ class _StatsPageState extends State with FeedbackMixin, VaultAwareMix final Map _entryCountPerTag = {}, _entryCountPerAlbum = {}; final Map _entryCountPerRating = Map.fromEntries(List.generate(7, (i) => MapEntry(5 - i, 0))); late final ValueNotifier _isPageAnimatingNotifier; + int _totalSizeBytes = 0; Set get entries => widget.entries; @@ -66,12 +68,14 @@ class _StatsPageState extends State with FeedbackMixin, VaultAwareMix super.initState(); _isPageAnimatingNotifier = ValueNotifier(true); - Future.delayed(Durations.pageTransitionAnimation * timeDilation).then((_) { + Future.delayed(ADurations.pageTransitionAnimation * timeDilation).then((_) { if (!mounted) return; _isPageAnimatingNotifier.value = false; }); entries.forEach((entry) { + _totalSizeBytes += entry.sizeBytes ?? 0; + if (entry.hasAddress) { final address = entry.addressDetails!; var country = address.countryName; @@ -123,7 +127,6 @@ class _StatsPageState extends State with FeedbackMixin, VaultAwareMix text: l10n.collectionEmptyImages, ); } else { - final theme = Theme.of(context); final chartAnimationDuration = context.read().chartTransition; final byMimeTypes = groupBy(entries, (entry) => entry.mimeType).map((k, v) => MapEntry(k, v.length)); @@ -148,51 +151,24 @@ class _StatsPageState extends State with FeedbackMixin, VaultAwareMix ], ); - final catalogued = entries.where((entry) => entry.isCatalogued); - final withGps = catalogued.where((entry) => entry.hasGps); - final withGpsCount = withGps.length; - final withGpsPercent = withGpsCount / entries.length; - final textScaleFactor = MediaQuery.textScaleFactorOf(context); - final lineHeight = 16 * textScaleFactor; - final barRadius = Radius.circular(lineHeight / 2); - final locationIndicator = Padding( - padding: const EdgeInsets.all(16), - child: Column( + final showRatings = _entryCountPerRating.entries.any((kv) => kv.key != 0 && kv.value > 0); + final source = widget.source; + final sizeIndicator = Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisSize: MainAxisSize.min, children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(AIcons.location), - Expanded( - child: LinearPercentIndicator( - percent: withGpsPercent, - lineHeight: lineHeight, - backgroundColor: theme.colorScheme.onPrimary.withOpacity(.1), - progressColor: theme.colorScheme.secondary, - animation: context.select((v) => v.accessibilityAnimations.animate), - isRTL: context.isRtl, - barRadius: barRadius, - center: LinearPercentIndicatorText(percent: withGpsPercent), - padding: EdgeInsets.symmetric(horizontal: lineHeight), - ), - ), - // end padding to match leading, so that inside label is aligned with outside label below - const SizedBox(width: 24), - ], - ), - const SizedBox(height: 8), - Text( - l10n.statsWithGps(withGpsCount), - textAlign: TextAlign.center, + const Icon(AIcons.size), + const SizedBox(width: 16), + Expanded( + child: Text(formatFileSize(l10n.localeName, _totalSizeBytes)), ), ], ), ); - final showRatings = _entryCountPerRating.entries.any((kv) => kv.key != 0 && kv.value > 0); - final source = widget.source; - child = NotificationListener( + child = NotificationListener( onNotification: (notification) { - _onFilterSelection(context, notification.reversedFilter); + _onFilterSelection(context, notification.filter); return true; }, child: AnimationLimiter( @@ -214,7 +190,10 @@ class _StatsPageState extends State with FeedbackMixin, VaultAwareMix animationDuration: chartAnimationDuration, onFilterSelection: (filter) => _onFilterSelection(context, filter), ), - locationIndicator, + const SizedBox(height: 16), + sizeIndicator, + const SizedBox(height: 16), + _LocationIndicator(entries: entries), ..._buildFilterSection(context, l10n.statsTopCountriesSectionTitle, _entryCountPerCountry, (v) => LocationFilter(LocationLevel.country, v)), ..._buildFilterSection(context, l10n.statsTopStatesSectionTitle, _entryCountPerState, (v) => LocationFilter(LocationLevel.state, v)), ..._buildFilterSection(context, l10n.statsTopPlacesSectionTitle, _entryCountPerPlace, (v) => LocationFilter(LocationLevel.place, v)), @@ -399,9 +378,9 @@ class StatsTopPage extends StatelessWidget { child: SafeArea( bottom: false, child: Builder(builder: (context) { - return NotificationListener( + return NotificationListener( onNotification: (notification) { - onFilterSelection(notification.reversedFilter); + onFilterSelection(notification.filter); return true; }, child: SingleChildScrollView( @@ -418,3 +397,54 @@ class StatsTopPage extends StatelessWidget { ); } } + +class _LocationIndicator extends StatelessWidget { + final Set entries; + + const _LocationIndicator({required this.entries}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final catalogued = entries.where((entry) => entry.isCatalogued); + final withGps = catalogued.where((entry) => entry.hasGps); + final withGpsCount = withGps.length; + final withGpsPercent = withGpsCount / entries.length; + final textScaleFactor = MediaQuery.textScaleFactorOf(context); + final lineHeight = 16 * textScaleFactor; + final barRadius = Radius.circular(lineHeight / 2); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(AIcons.location), + Expanded( + child: LinearPercentIndicator( + percent: withGpsPercent, + lineHeight: lineHeight, + backgroundColor: theme.colorScheme.onPrimary.withOpacity(.1), + progressColor: theme.colorScheme.secondary, + animation: context.select((v) => v.accessibilityAnimations.animate), + isRTL: context.isRtl, + barRadius: barRadius, + center: LinearPercentIndicatorText(percent: withGpsPercent), + padding: EdgeInsets.symmetric(horizontal: lineHeight), + ), + ), + // end padding to match leading, so that inside label is aligned with outside label below + const SizedBox(width: 24), + ], + ), + const SizedBox(height: 8), + Text( + context.l10n.statsWithGps(withGpsCount), + textAlign: TextAlign.center, + ), + ], + ), + ); + } +} diff --git a/lib/widgets/viewer/action/entry_action_delegate.dart b/lib/widgets/viewer/action/entry_action_delegate.dart index d31760d64..56685c672 100644 --- a/lib/widgets/viewer/action/entry_action_delegate.dart +++ b/lib/widgets/viewer/action/entry_action_delegate.dart @@ -39,7 +39,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMixin, SingleEntryEditorMixin, EntryStorageMixin, VaultAwareMixin { final AvesEntry mainEntry, pageEntry; @@ -80,18 +79,18 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix case EntryAction.flip: return targetEntry.canFlip; case EntryAction.convert: - return canWrite && !targetEntry.isVideo; + return canWrite && !targetEntry.isPureVideo; case EntryAction.print: - return !targetEntry.isVideo; + return !targetEntry.isPureVideo; case EntryAction.openMap: return !settings.useTvLayout && targetEntry.hasGps; case EntryAction.viewSource: return targetEntry.isSvg; case EntryAction.videoCaptureFrame: - return canWrite && targetEntry.isVideo; + return canWrite && targetEntry.isPureVideo; case EntryAction.lockViewer: case EntryAction.videoToggleMute: - return !settings.useTvLayout && targetEntry.isVideo; + return !settings.useTvLayout && targetEntry.isPureVideo; case EntryAction.videoSelectStreams: case EntryAction.videoSetSpeed: case EntryAction.videoSettings: @@ -99,7 +98,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix case EntryAction.videoReplay10: case EntryAction.videoSkip10: case EntryAction.openVideo: - return targetEntry.isVideo; + return targetEntry.isPureVideo; case EntryAction.rotateScreen: return !settings.useTvLayout && settings.isRotationLocked; case EntryAction.addShortcut: @@ -185,7 +184,11 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix _addShortcut(context, targetEntry); case EntryAction.copyToClipboard: appService.copyToClipboard(targetEntry.uri, targetEntry.bestTitle).then((success) { - showFeedback(context, success ? context.l10n.genericSuccessFeedback : context.l10n.genericFailureFeedback); + if (success) { + showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback); + } else { + showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback); + } }); case EntryAction.delete: _delete(context, targetEntry); @@ -325,7 +328,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix } Future _addShortcut(BuildContext context, AvesEntry targetEntry) async { - final result = await showDialog>( + final result = await showDialog<(AvesEntry?, String)>( context: context, builder: (context) => AddShortcutDialog( defaultName: targetEntry.bestTitle ?? '', @@ -334,12 +337,12 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix ); if (result == null) return; - final name = result.item2; + final name = result.$2; if (name.isEmpty) return; await appService.pinToHomeScreen(name, targetEntry, uri: targetEntry.uri); if (!device.showPinShortcutFeedback) { - showFeedback(context, context.l10n.genericSuccessFeedback); + showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback); } } @@ -376,7 +379,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix if (!await checkStoragePermission(context, {targetEntry})) return; if (!await targetEntry.delete()) { - showFeedback(context, l10n.genericFailureFeedback); + showFeedback(context, FeedbackType.warn, l10n.genericFailureFeedback); } else { final source = context.read(); if (source.initState != SourceInitializationState.none) { @@ -401,7 +404,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix if (newName == null || newName.isEmpty || newName == targetEntry.filenameWithoutExtension) return; // wait for the dialog to hide as applying the change may block the UI - await Future.delayed(Durations.dialogTransitionAnimation * timeDilation); + await Future.delayed(ADurations.dialogTransitionAnimation * timeDilation); await rename( context, entriesToNewName: {targetEntry: '$newName${targetEntry.extension}'}, diff --git a/lib/widgets/viewer/action/entry_info_action_delegate.dart b/lib/widgets/viewer/action/entry_info_action_delegate.dart index 0487d219e..bce630316 100644 --- a/lib/widgets/viewer/action/entry_info_action_delegate.dart +++ b/lib/widgets/viewer/action/entry_info_action_delegate.dart @@ -217,9 +217,9 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi ); if (success != null) { if (success) { - showFeedback(context, context.l10n.genericSuccessFeedback); + showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback); } else { - showFeedback(context, context.l10n.genericFailureFeedback); + showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback); } } } diff --git a/lib/widgets/viewer/action/single_entry_editor.dart b/lib/widgets/viewer/action/single_entry_editor.dart index dd8d3e538..85acb841f 100644 --- a/lib/widgets/viewer/action/single_entry_editor.dart +++ b/lib/widgets/viewer/action/single_entry_editor.dart @@ -57,9 +57,9 @@ mixin SingleEntryEditorMixin on FeedbackMixin, PermissionAwareMixin { await targetEntry.catalog(background: background, force: dataTypes.contains(EntryDataType.catalog), persist: persist); await targetEntry.locate(background: background, force: dataTypes.contains(EntryDataType.address), geocoderLocale: settings.appliedLocale); } - showFeedback(context, l10n.genericSuccessFeedback); + showFeedback(context, FeedbackType.info, l10n.genericSuccessFeedback); } else { - showFeedback(context, l10n.genericFailureFeedback); + showFeedback(context, FeedbackType.warn, l10n.genericFailureFeedback); } } catch (error, stack) { await reportService.recordError(error, stack); diff --git a/lib/widgets/viewer/action/video_action_delegate.dart b/lib/widgets/viewer/action/video_action_delegate.dart index e9a9d454f..739e79bb1 100644 --- a/lib/widgets/viewer/action/video_action_delegate.dart +++ b/lib/widgets/viewer/action/video_action_delegate.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:math'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/location.dart'; @@ -56,7 +57,7 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix case EntryAction.videoTogglePlay: await _togglePlayPause(context, controller); case EntryAction.videoReplay10: - await controller.seekTo(controller.currentPosition - 10000); + await controller.seekTo(max(controller.currentPosition - 10000, 0)); case EntryAction.videoSkip10: await controller.seekTo(controller.currentPosition + 10000); case EntryAction.openVideo: @@ -69,34 +70,37 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix } Future _captureFrame(BuildContext context, AvesEntry entry, AvesVideoController controller) async { - final positionMillis = controller.currentPosition; - final bytes = await controller.captureFrame(); - final destinationAlbum = androidFileUtils.avesVideoCapturesPath; - if (!await checkStoragePermissionForAlbums(context, {destinationAlbum})) return; + final positionMillis = controller.currentPosition; + final Map newFields = {}; - if (!await checkFreeSpace(context, bytes.length, destinationAlbum)) return; + final bytes = await controller.captureFrame(); + if (bytes != null) { + if (!await checkStoragePermissionForAlbums(context, {destinationAlbum})) return; - final rotationDegrees = entry.rotationDegrees; - final dateTimeMillis = entry.catalogMetadata?.dateMillis; - final latLng = entry.latLng; - final exif = { - if (rotationDegrees != 0) 'rotationDegrees': rotationDegrees, - if (dateTimeMillis != null && dateTimeMillis != 0) 'dateTimeMillis': dateTimeMillis, - if (latLng != null) ...{ - 'latitude': latLng.latitude, - 'longitude': latLng.longitude, - } - }; + if (!await checkFreeSpace(context, bytes.length, destinationAlbum)) return; - final newFields = await mediaEditService.captureFrame( - entry, - desiredName: '${entry.bestTitle}_${'$positionMillis'.padLeft(8, '0')}', - exif: exif, - bytes: bytes, - destinationAlbum: destinationAlbum, - nameConflictStrategy: NameConflictStrategy.rename, - ); + final rotationDegrees = entry.rotationDegrees; + final dateTimeMillis = entry.catalogMetadata?.dateMillis; + final latLng = entry.latLng; + final exif = { + if (rotationDegrees != 0) 'rotationDegrees': rotationDegrees, + if (dateTimeMillis != null && dateTimeMillis != 0) 'dateTimeMillis': dateTimeMillis, + if (latLng != null) ...{ + 'latitude': latLng.latitude, + 'longitude': latLng.longitude, + } + }; + + newFields.addAll(await mediaEditService.captureFrame( + entry, + desiredName: '${entry.bestTitle}_${'$positionMillis'.padLeft(8, '0')}', + exif: exif, + bytes: bytes, + destinationAlbum: destinationAlbum, + nameConflictStrategy: NameConflictStrategy.rename, + )); + } final success = newFields.isNotEmpty; final l10n = context.l10n; @@ -127,9 +131,9 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix }, ) : null; - showFeedback(context, l10n.genericSuccessFeedback, showAction); + showFeedback(context, FeedbackType.info, l10n.genericSuccessFeedback, showAction); } else { - showFeedback(context, l10n.genericFailureFeedback); + showFeedback(context, FeedbackType.warn, l10n.genericFailureFeedback); } } @@ -189,6 +193,8 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix } Future _togglePlayPause(BuildContext context, AvesVideoController controller) async { + if (!context.mounted) return; + if (controller.isPlaying) { await controller.pause(); } else { @@ -199,7 +205,7 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix await controller.play(); } // hide overlay - _overlayHidingTimer = Timer(context.read().iconAnimation + Durations.videoOverlayHideDelay, () { + _overlayHidingTimer = Timer(context.read().iconAnimation + ADurations.videoOverlayHideDelay, () { const ToggleOverlayNotification(visible: false).dispatch(context); }); } diff --git a/lib/widgets/viewer/controls/controller.dart b/lib/widgets/viewer/controls/controller.dart index 7c087f615..9e5a87f4b 100644 --- a/lib/widgets/viewer/controls/controller.dart +++ b/lib/widgets/viewer/controls/controller.dart @@ -100,7 +100,7 @@ class ViewerController { ), )); _autopilotAnimationControllers[vsync] = animationController; - Future.delayed(Durations.viewerHorizontalPageAnimation).then((_) => _autopilotAnimationControllers[vsync]?.forward()); + Future.delayed(ADurations.viewerHorizontalPageAnimation).then((_) => _autopilotAnimationControllers[vsync]?.forward()); } } diff --git a/lib/widgets/viewer/controls/notifications.dart b/lib/widgets/viewer/controls/notifications.dart index 1d65991e9..a170bb91e 100644 --- a/lib/widgets/viewer/controls/notifications.dart +++ b/lib/widgets/viewer/controls/notifications.dart @@ -108,3 +108,11 @@ class EntryMovedNotification extends Notification with EquatableMixin { const EntryMovedNotification(this.moveType, this.entries); } + +@immutable +class FullImageLoadedNotification extends Notification { + final AvesEntry entry; + final ImageProvider image; + + const FullImageLoadedNotification(this.entry, this.image); +} diff --git a/lib/widgets/viewer/debug/debug_page.dart b/lib/widgets/viewer/debug/debug_page.dart index a987e9a6b..833bda5c2 100644 --- a/lib/widgets/viewer/debug/debug_page.dart +++ b/lib/widgets/viewer/debug/debug_page.dart @@ -12,7 +12,6 @@ import 'package:aves/widgets/viewer/info/common.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class ViewerDebugPage extends StatelessWidget { static const routeName = '/viewer/debug'; @@ -26,11 +25,11 @@ class ViewerDebugPage extends StatelessWidget { @override Widget build(BuildContext context) { - final tabs = >[ - Tuple2(const Tab(text: 'Entry'), _buildEntryTabView()), - if (context.select, bool>((vn) => vn.value != AppMode.view)) Tuple2(const Tab(text: 'DB'), DbTab(entry: entry)), - Tuple2(const Tab(icon: Icon(AIcons.android)), MetadataTab(entry: entry)), - Tuple2(const Tab(icon: Icon(AIcons.image)), _buildThumbnailsTabView()), + final tabs = <(Tab, Widget)>[ + (const Tab(text: 'Entry'), _buildEntryTabView()), + if (context.select, bool>((vn) => vn.value != AppMode.view)) (const Tab(text: 'DB'), DbTab(entry: entry)), + (const Tab(icon: Icon(AIcons.android)), MetadataTab(entry: entry)), + (const Tab(icon: Icon(AIcons.image)), _buildThumbnailsTabView()), ]; return Directionality( textDirection: TextDirection.ltr, @@ -40,12 +39,12 @@ class ViewerDebugPage extends StatelessWidget { appBar: AppBar( title: const Text('Debug'), bottom: TabBar( - tabs: tabs.map((t) => t.item1).toList(), + tabs: tabs.map((t) => t.$1).toList(), ), ), body: SafeArea( child: TabBarView( - children: tabs.map((t) => t.item2).toList(), + children: tabs.map((t) => t.$2).toList(), ), ), ), diff --git a/lib/widgets/viewer/debug/metadata.dart b/lib/widgets/viewer/debug/metadata.dart index ccf4eb7d5..81963246e 100644 --- a/lib/widgets/viewer/debug/metadata.dart +++ b/lib/widgets/viewer/debug/metadata.dart @@ -149,7 +149,7 @@ class _MetadataTabState extends State { padding: const EdgeInsets.symmetric(horizontal: 8), child: SingleChildScrollView( scrollDirection: Axis.horizontal, - child: Text(data), + child: SelectableText(data), ), ) ], diff --git a/lib/widgets/viewer/entry_vertical_pager.dart b/lib/widgets/viewer/entry_vertical_pager.dart index 7e6cd8129..b81cb75fa 100644 --- a/lib/widgets/viewer/entry_vertical_pager.dart +++ b/lib/widgets/viewer/entry_vertical_pager.dart @@ -309,7 +309,7 @@ class _ViewerVerticalPageViewState extends State { if (animate) { pageController.animateToPage( target, - duration: Durations.viewerHorizontalPageAnimation, + duration: ADurations.viewerHorizontalPageAnimation, curve: Curves.easeInOutCubic, ); } else { @@ -333,7 +333,7 @@ class _ViewerVerticalPageViewState extends State { _isVerticallyScrollingNotifier.value = true; _stopScrollMonitoringTimer(); - _verticalScrollMonitoringTimer = Timer(Durations.infoScrollMonitoringTimerDelay, () { + _verticalScrollMonitoringTimer = Timer(ADurations.infoScrollMonitoringTimerDelay, () { _isVerticallyScrollingNotifier.value = false; }); } diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart index 03093cb45..01399c26f 100644 --- a/lib/widgets/viewer/entry_viewer_stack.dart +++ b/lib/widgets/viewer/entry_viewer_stack.dart @@ -33,7 +33,7 @@ import 'package:aves/widgets/viewer/overlay/top.dart'; import 'package:aves/widgets/viewer/overlay/video/video.dart'; import 'package:aves/widgets/viewer/page_entry_builder.dart'; import 'package:aves/widgets/viewer/video/conductor.dart'; -import 'package:aves/widgets/viewer/visual/conductor.dart'; +import 'package:aves/widgets/viewer/view/conductor.dart'; import 'package:aves/widgets/viewer/visual/controller_mixin.dart'; import 'package:aves_model/aves_model.dart'; import 'package:aves_utils/aves_utils.dart'; @@ -286,6 +286,7 @@ class _EntryViewerStackState extends State with EntryViewContr switch (AvesApp.lifecycleStateNotifier.value) { case AppLifecycleState.inactive: _onAppInactive(); + case AppLifecycleState.hidden: case AppLifecycleState.paused: case AppLifecycleState.detached: pauseVideoControllers(); @@ -417,7 +418,7 @@ class _EntryViewerStackState extends State with EntryViewContr final targetEntry = pageEntry ?? mainEntry; Widget? child; // a 360 video is both a video and a panorama but only the video controls are displayed - if (targetEntry.isVideo) { + if (targetEntry.isPureVideo) { child = Selector( selector: (context, vc) => vc.getController(targetEntry), builder: (context, videoController, child) => VideoControlOverlay( @@ -512,6 +513,10 @@ class _EntryViewerStackState extends State with EntryViewContr bool _handleNotification(dynamic notification) { if (notification is FilterSelectedNotification) { _goToCollection(notification.filter); + } else if (notification is FullImageLoadedNotification) { + final viewStateController = context.read().getOrCreateController(notification.entry); + // microtask so that listeners do not trigger during build + scheduleMicrotask(() => viewStateController.fullImageNotifier.value = notification.image); } else if (notification is EntryDeletedNotification) { _onEntryRemoved(context, notification.entries); } else if (notification is EntryMovedNotification) { @@ -579,11 +584,12 @@ class _EntryViewerStackState extends State with EntryViewContr required AvesVideoController controller, required EntryAction action, }) async { - await _videoActionDelegate.onActionSelected(context, entry, controller, action); if (action == EntryAction.videoToggleMute) { - final override = controller.isMuted; + final override = !controller.isMuted; videoMutedOverride = override; await context.read().muteAll(override); + } else { + await _videoActionDelegate.onActionSelected(context, entry, controller, action); } } diff --git a/lib/widgets/viewer/info/basic_section.dart b/lib/widgets/viewer/info/basic_section.dart index 73445653d..ae9726151 100644 --- a/lib/widgets/viewer/info/basic_section.dart +++ b/lib/widgets/viewer/info/basic_section.dart @@ -125,8 +125,8 @@ class _BasicSectionState extends State { if (entry.isMotionPhoto) TypeFilter.motionPhoto, if (entry.isRaw) TypeFilter.raw, if (entry.isImage && entry.is360) TypeFilter.panorama, - if (entry.isVideo && entry.is360) TypeFilter.sphericalVideo, - if (entry.isVideo && !entry.is360) MimeFilter.video, + if (entry.isPureVideo && entry.is360) TypeFilter.sphericalVideo, + if (entry.isPureVideo && !entry.is360) MimeFilter.video, if (date != null) DateFilter(DateLevel.ymd, date), if (album != null) AlbumFilter(album, collection?.source.getAlbumDisplayName(context, album)), if (entry.rating != 0) RatingFilter(entry.rating), diff --git a/lib/widgets/viewer/info/embedded/embedded_data_opener.dart b/lib/widgets/viewer/info/embedded/embedded_data_opener.dart index 54faa81f6..c7dc91b81 100644 --- a/lib/widgets/viewer/info/embedded/embedded_data_opener.dart +++ b/lib/widgets/viewer/info/embedded/embedded_data_opener.dart @@ -50,7 +50,7 @@ class EmbeddedDataOpener extends StatelessWidget with FeedbackMixin { fields = await embeddedDataService.extractXmpDataProp(entry, notification.props, notification.mimeType); } if (!fields.containsKey('mimeType') || !fields.containsKey('uri')) { - showFeedback(context, context.l10n.viewerInfoOpenEmbeddedFailureFeedback); + showFeedback(context, FeedbackType.warn, context.l10n.viewerInfoOpenEmbeddedFailureFeedback); return; } diff --git a/lib/widgets/viewer/info/info_app_bar.dart b/lib/widgets/viewer/info/info_app_bar.dart index 69f2b910e..767a4b1d6 100644 --- a/lib/widgets/viewer/info/info_app_bar.dart +++ b/lib/widgets/viewer/info/info_app_bar.dart @@ -90,7 +90,7 @@ class InfoAppBar extends StatelessWidget { ], onSelected: (action) async { // wait for the popup menu to hide before proceeding with the action - await Future.delayed(Durations.popupMenuAnimation * timeDilation); + await Future.delayed(ADurations.popupMenuAnimation * timeDilation); actionDelegate.onActionSelected(context, entry, collection, action); }, ), diff --git a/lib/widgets/viewer/info/info_page.dart b/lib/widgets/viewer/info/info_page.dart index 1529e11bb..1f3708606 100644 --- a/lib/widgets/viewer/info/info_page.dart +++ b/lib/widgets/viewer/info/info_page.dart @@ -122,7 +122,7 @@ class _InfoPageState extends State { ShowImageNotification().dispatch(context); _scrollController.animateTo( 0, - duration: Durations.pageTransitionAnimation, + duration: ADurations.pageTransitionAnimation, curve: Curves.easeInOut, ); } @@ -238,9 +238,9 @@ class _InfoPageContentState extends State<_InfoPageContent> { metadataNotifier: _metadataNotifier, ); - return NotificationListener( + return NotificationListener( onNotification: (notification) { - _onFilter(notification.reversedFilter); + _onFilter(notification.filter); return true; }, child: CustomScrollView( @@ -271,7 +271,7 @@ class _InfoPageContentState extends State<_InfoPageContent> { } void _onActionDelegateEvent(ActionEvent event) { - Future.delayed(Durations.dialogTransitionAnimation).then((_) { + Future.delayed(ADurations.dialogTransitionAnimation).then((_) { if (event is ActionStartedEvent) { _isEditingMetadataNotifier.value = event.action; } else if (event is ActionEndedEvent) { diff --git a/lib/widgets/viewer/info/metadata/xmp_card.dart b/lib/widgets/viewer/info/metadata/xmp_card.dart index a7185fb1b..ac359fa52 100644 --- a/lib/widgets/viewer/info/metadata/xmp_card.dart +++ b/lib/widgets/viewer/info/metadata/xmp_card.dart @@ -9,9 +9,8 @@ import 'package:aves/widgets/viewer/info/common.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:tuple/tuple.dart'; -typedef XmpExtractedCard = Tuple2, List?>; +typedef XmpExtractedCard = (Map, List?); class XmpCard extends StatefulWidget { final String title; @@ -30,7 +29,7 @@ class XmpCard extends StatefulWidget { directStruct = structByIndex[null]; final length = structByIndex.keys.whereNotNull().fold(0, max); - indexedStructs = length > 0 ? [for (var i = 0; i < length; i++) structByIndex[i + 1] ?? const Tuple2({}, null)] : null; + indexedStructs = length > 0 ? [for (var i = 0; i < length; i++) structByIndex[i + 1] ?? const ({}, null)] : null; } @override @@ -77,8 +76,8 @@ class _XmpCardState extends State { valueListenable: _indexNotifier, builder: (context, index, child) { final data = _isIndexed ? indexedStructs![index] : widget.directStruct!; - final props = data.item1.entries.map((kv) => XmpProp(kv.key, kv.value.value)).toList()..sort(); - final cards = data.item2; + final props = data.$1.entries.map((kv) => XmpProp(kv.key, kv.value.value)).toList()..sort(); + final cards = data.$2; return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -116,7 +115,7 @@ class _XmpCardState extends State { ), ), MultiCrossFader( - duration: Durations.xmpStructArrayCardTransition, + duration: ADurations.xmpStructArrayCardTransition, sizeCurve: Curves.easeOutBack, alignment: AlignmentDirectional.topStart, child: Padding( @@ -139,7 +138,7 @@ class _XmpCardState extends State { title: card.title, structByIndex: card.data, formatValue: widget.formatValue, - spanBuilders: spanBuilders != null ? (index) => spanBuilders(index, card.data[index]!.item1) : null, + spanBuilders: spanBuilders != null ? (index) => spanBuilders(index, card.data[index]!.$1) : null, ), ); }), diff --git a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart index bb99034b6..1a33eb4c0 100644 --- a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart +++ b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart @@ -19,7 +19,6 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; @immutable class XmpNamespace extends Equatable { @@ -114,7 +113,7 @@ class XmpNamespace extends Equatable { title: card.title, structByIndex: card.data, formatValue: formatValue, - spanBuilders: spanBuilders != null ? (index) => spanBuilders(index, card.data[index]!.item1) : null, + spanBuilders: spanBuilders != null ? (index) => spanBuilders(index, card.data[index]!.$1) : null, ), ); }), @@ -206,8 +205,8 @@ class XmpCardData { final match = matches.first; final field = match.group(1)!; - final fields = data.putIfAbsent(null, () => Tuple2({}, cards?.map((v) => v.cloneEmpty()).toList())); - final _cards = fields.item2; + final fields = data.putIfAbsent(null, () => ({}, cards?.map((v) => v.cloneEmpty()).toList())); + final _cards = fields.$2; if (_cards != null) { final fieldProp = XmpProp(field, prop.value); if (_cards.any((v) => v.extract(fieldProp))) { @@ -215,7 +214,7 @@ class XmpCardData { } } - fields.item1[field] = prop; + fields.$1[field] = prop; return true; } @@ -227,8 +226,8 @@ class XmpCardData { final index = int.parse(match.group(1)!); final field = match.group(2)!; - final fields = data.putIfAbsent(index, () => Tuple2({}, cards?.map((v) => v.cloneEmpty()).toList())); - final _cards = fields.item2; + final fields = data.putIfAbsent(index, () => ({}, cards?.map((v) => v.cloneEmpty()).toList())); + final _cards = fields.$2; if (_cards != null) { final fieldProp = XmpProp(field, prop.value); if (_cards.any((v) => v.extract(fieldProp))) { @@ -236,7 +235,7 @@ class XmpCardData { } } - fields.item1[field] = prop; + fields.$1[field] = prop; return true; } } diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/google.dart b/lib/widgets/viewer/info/metadata/xmp_ns/google.dart index fca1e0d8c..e05ab5852 100644 --- a/lib/widgets/viewer/info/metadata/xmp_ns/google.dart +++ b/lib/widgets/viewer/info/metadata/xmp_ns/google.dart @@ -4,7 +4,6 @@ import 'package:aves/widgets/viewer/info/common.dart'; import 'package:aves/widgets/viewer/info/embedded/notifications.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; import 'package:collection/collection.dart'; -import 'package:tuple/tuple.dart'; abstract class XmpGoogleNamespace extends XmpNamespace { XmpGoogleNamespace({ @@ -13,13 +12,12 @@ abstract class XmpGoogleNamespace extends XmpNamespace { required super.rawProps, }); - List> get dataProps; + List<(String, String)> get dataProps; @override Map linkifyValues(List props) { return Map.fromEntries(dataProps.map((t) { - final dataPropPath = t.item1; - final mimePropPath = t.item2; + final (dataPropPath, mimePropPath) = t; final dataProp = props.firstWhereOrNull((prop) => prop.path == dataPropPath); final mimeProp = props.firstWhereOrNull((prop) => prop.path == mimePropPath); return (dataProp != null && mimeProp != null) @@ -60,8 +58,8 @@ class XmpGAudioNamespace extends XmpGoogleNamespace { XmpGAudioNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: XmpNamespaces.gAudio); @override - List> get dataProps => [ - Tuple2('${nsPrefix}Data', '${nsPrefix}Mime'), + List<(String, String)> get dataProps => [ + ('${nsPrefix}Data', '${nsPrefix}Mime'), ]; } @@ -69,8 +67,8 @@ class XmpGCameraNamespace extends XmpGoogleNamespace { XmpGCameraNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: XmpNamespaces.gCamera); @override - List> get dataProps => [ - Tuple2('${nsPrefix}RelitInputImageData', '${nsPrefix}RelitInputImageMime'), + List<(String, String)> get dataProps => [ + ('${nsPrefix}RelitInputImageData', '${nsPrefix}RelitInputImageMime'), ]; } @@ -87,9 +85,9 @@ class XmpGDepthNamespace extends XmpGoogleNamespace { XmpGDepthNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: XmpNamespaces.gDepth); @override - List> get dataProps => [ - Tuple2('${nsPrefix}Data', '${nsPrefix}Mime'), - Tuple2('${nsPrefix}Confidence', '${nsPrefix}ConfidenceMime'), + List<(String, String)> get dataProps => [ + ('${nsPrefix}Data', '${nsPrefix}Mime'), + ('${nsPrefix}Confidence', '${nsPrefix}ConfidenceMime'), ]; } @@ -156,7 +154,7 @@ class XmpGImageNamespace extends XmpGoogleNamespace { XmpGImageNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: XmpNamespaces.gImage); @override - List> get dataProps => [ - Tuple2('${nsPrefix}Data', '${nsPrefix}Mime'), + List<(String, String)> get dataProps => [ + ('${nsPrefix}Data', '${nsPrefix}Mime'), ]; } diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/xmp.dart b/lib/widgets/viewer/info/metadata/xmp_ns/xmp.dart index 6bdf34d26..655a8fe5d 100644 --- a/lib/widgets/viewer/info/metadata/xmp_ns/xmp.dart +++ b/lib/widgets/viewer/info/metadata/xmp_ns/xmp.dart @@ -1,5 +1,5 @@ -import 'package:aves/ref/mime_types.dart'; import 'package:aves/ref/metadata/xmp.dart'; +import 'package:aves/ref/mime_types.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/viewer/info/common.dart'; import 'package:aves/widgets/viewer/info/embedded/notifications.dart'; diff --git a/lib/widgets/viewer/overlay/histogram.dart b/lib/widgets/viewer/overlay/histogram.dart new file mode 100644 index 000000000..0508c1104 --- /dev/null +++ b/lib/widgets/viewer/overlay/histogram.dart @@ -0,0 +1,154 @@ +import 'dart:ui'; + +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/widgets/viewer/overlay/top.dart'; +import 'package:aves/widgets/viewer/view/controller.dart'; +import 'package:aves/widgets/viewer/view/histogram.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; + +class ImageHistogram extends StatefulWidget { + final ViewStateController viewStateController; + final ImageProvider image; + + const ImageHistogram({ + super.key, + required this.viewStateController, + required this.image, + }); + + @override + State createState() => _ImageHistogramState(); +} + +class _ImageHistogramState extends State { + HistogramLevels _levels = {}; + ImageStream? _imageStream; + late ImageStreamListener _imageListener; + + ViewStateController get viewStateController => widget.viewStateController; + + AvesEntry get entry => viewStateController.entry; + + ImageProvider get imageProvider => widget.image; + + @override + void initState() { + super.initState(); + _registerWidget(widget); + } + + @override + void didUpdateWidget(covariant ImageHistogram oldWidget) { + super.didUpdateWidget(oldWidget); + _unregisterWidget(oldWidget); + _registerWidget(widget); + } + + @override + void dispose() { + _unregisterWidget(widget); + super.dispose(); + } + + void _registerWidget(ImageHistogram widget) { + _imageStream = imageProvider.resolve(ImageConfiguration.empty); + _imageListener = ImageStreamListener((image, synchronousCall) { + _updateLevels(image); + }); + _imageStream?.addListener(_imageListener); + } + + void _unregisterWidget(ImageHistogram widget) { + _imageStream?.removeListener(_imageListener); + } + + @override + Widget build(BuildContext context) { + return IgnorePointer( + child: CustomPaint( + painter: _HistogramPainter( + levels: _levels, + borderColor: ViewerTopOverlay.componentBorderColor, + ), + size: const Size(ViewerTopOverlay.componentDimension, ViewerTopOverlay.componentDimension * .6), + ), + ); + } + + Future _updateLevels(ImageInfo info) async { + final targetEntry = entry; + final forceUpdate = targetEntry.isAnimated; + final newLevels = await viewStateController.getHistogramLevels(info, forceUpdate); + if (mounted) { + setState(() => _levels = targetEntry == entry ? newLevels : {}); + } + } +} + +class _HistogramPainter extends CustomPainter { + final HistogramLevels levels; + final Color borderColor; + + late final Paint fill, borderStroke; + + _HistogramPainter({ + required this.levels, + this.borderColor = Colors.white, + }) { + fill = Paint() + ..style = PaintingStyle.fill + ..color = const Color(0x33000000); + borderStroke = Paint() + ..style = PaintingStyle.stroke + ..color = borderColor; + } + + @override + void paint(Canvas canvas, Size size) { + if (levels.isEmpty) return; + + final backgroundRect = Rect.fromPoints(Offset.zero, Offset(size.width, size.height)); + canvas.drawRect(backgroundRect, fill); + levels.forEach((channel, values) { + final color = _getChannelColor(channel); + _drawLevels(canvas, size, color, values); + }); + canvas.drawRect(backgroundRect, borderStroke); + } + + void _drawLevels(Canvas canvas, Size size, Color color, List values) { + if (values.length < 2) return; + + final xFactor = size.width / (values.length - 1); + final yFactor = size.height; + + final polyline = values.mapIndexed((i, v) => Offset(i * xFactor, size.height - v * yFactor)).toList(); + canvas.drawPoints( + PointMode.polygon, + polyline, + Paint() + ..style = PaintingStyle.stroke + ..color = color); + + polyline.add(Offset(size.width, size.height)); + polyline.add(Offset(0, size.height)); + canvas.drawPath( + Path()..addPolygon(polyline, true), + Paint() + ..style = PaintingStyle.fill + ..color = color.withOpacity(.5)); + } + + Color _getChannelColor(HistogramChannel channel) { + return switch (channel) { + HistogramChannel.red => Colors.red, + HistogramChannel.green => Colors.green, + HistogramChannel.blue => Colors.blue, + HistogramChannel.luminance => Colors.white, + }; + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} diff --git a/lib/widgets/viewer/overlay/minimap.dart b/lib/widgets/viewer/overlay/minimap.dart index 59ca726f8..a29be6f86 100644 --- a/lib/widgets/viewer/overlay/minimap.dart +++ b/lib/widgets/viewer/overlay/minimap.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:aves/model/view_state.dart'; import 'package:aves/widgets/editor/transform/controller.dart'; import 'package:aves/widgets/editor/transform/transformation.dart'; +import 'package:aves/widgets/viewer/overlay/top.dart'; import 'package:aves_utils/aves_utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -11,8 +12,6 @@ import 'package:provider/provider.dart'; class Minimap extends StatelessWidget { final ValueNotifier viewStateNotifier; - static const Size minimapSize = Size(96, 96); - const Minimap({ super.key, required this.viewStateNotifier, @@ -28,44 +27,44 @@ class Minimap extends StatelessWidget { final contentSize = viewState.contentSize; if (viewportSize == null || contentSize == null) return const SizedBox(); return StreamBuilder( - stream: context.select>((v) => v?.transformationStream ?? Stream.value(null)), - builder: (context, snapshot) { - final transformation = snapshot.data; - return CustomPaint( - painter: MinimapPainter( - viewportSize: viewportSize, - contentSize: contentSize, - viewCenterOffset: viewState.position, - viewScale: viewState.scale!, - transformation: transformation, - minimapBorderColor: Colors.white30, - ), - size: minimapSize, - ); - }); + stream: context.select>((v) => v?.transformationStream ?? Stream.value(null)), + builder: (context, snapshot) { + final transformation = snapshot.data; + return CustomPaint( + painter: _MinimapPainter( + viewportSize: viewportSize, + contentSize: contentSize, + viewCenterOffset: viewState.position, + viewScale: viewState.scale!, + transformation: transformation, + minimapBorderColor: ViewerTopOverlay.componentBorderColor, + ), + size: const Size.square(ViewerTopOverlay.componentDimension), + ); + }, + ); }, ), ); } } -class MinimapPainter extends CustomPainter { +class _MinimapPainter extends CustomPainter { final Size contentSize, viewportSize; final Offset viewCenterOffset; final double viewScale; final Transformation? transformation; - final Color minimapBorderColor, viewportBorderColor; + final Color minimapBorderColor; late final Paint fill, minimapStroke, viewportStroke; - MinimapPainter({ + _MinimapPainter({ required this.viewportSize, required this.contentSize, required this.viewCenterOffset, required this.viewScale, this.transformation, this.minimapBorderColor = Colors.white, - this.viewportBorderColor = Colors.white, }) { fill = Paint() ..style = PaintingStyle.fill @@ -75,7 +74,7 @@ class MinimapPainter extends CustomPainter { ..color = minimapBorderColor; viewportStroke = Paint() ..style = PaintingStyle.stroke - ..color = viewportBorderColor; + ..color = Colors.white; } @override diff --git a/lib/widgets/viewer/overlay/multipage.dart b/lib/widgets/viewer/overlay/multipage.dart index d1313890b..50c775a96 100644 --- a/lib/widgets/viewer/overlay/multipage.dart +++ b/lib/widgets/viewer/overlay/multipage.dart @@ -1,7 +1,7 @@ import 'package:aves/model/multipage.dart'; import 'package:aves/widgets/common/thumbnail/scroller.dart'; import 'package:aves/widgets/viewer/multipage/controller.dart'; -import 'package:aves/widgets/viewer/visual/conductor.dart'; +import 'package:aves/widgets/viewer/view/conductor.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/widgets/viewer/overlay/selection_button.dart b/lib/widgets/viewer/overlay/selection_button.dart index 5a3c4ea53..643e3da42 100644 --- a/lib/widgets/viewer/overlay/selection_button.dart +++ b/lib/widgets/viewer/overlay/selection_button.dart @@ -14,7 +14,7 @@ class SelectionButton extends StatelessWidget { final Animation scale; static const double padding = 8; - static const duration = Durations.thumbnailOverlayAnimation; + static const duration = ADurations.thumbnailOverlayAnimation; const SelectionButton({ super.key, diff --git a/lib/widgets/viewer/overlay/thumbnail_preview.dart b/lib/widgets/viewer/overlay/thumbnail_preview.dart index 934ad7518..01f9b57c0 100644 --- a/lib/widgets/viewer/overlay/thumbnail_preview.dart +++ b/lib/widgets/viewer/overlay/thumbnail_preview.dart @@ -25,7 +25,7 @@ class ViewerThumbnailPreview extends StatefulWidget { class _ViewerThumbnailPreviewState extends State { final ValueNotifier _entryIndexNotifier = ValueNotifier(0); - final Debouncer _debouncer = Debouncer(delay: Durations.viewerThumbnailScrollDebounceDelay); + final Debouncer _debouncer = Debouncer(delay: ADurations.viewerThumbnailScrollDebounceDelay); List get entries => widget.entries; diff --git a/lib/widgets/viewer/overlay/top.dart b/lib/widgets/viewer/overlay/top.dart index 67cd07c31..99d45c356 100644 --- a/lib/widgets/viewer/overlay/top.dart +++ b/lib/widgets/viewer/overlay/top.dart @@ -5,9 +5,12 @@ import 'package:aves/theme/themes.dart'; import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/widgets/viewer/multipage/conductor.dart'; import 'package:aves/widgets/viewer/overlay/details/details.dart'; +import 'package:aves/widgets/viewer/overlay/histogram.dart'; import 'package:aves/widgets/viewer/overlay/minimap.dart'; import 'package:aves/widgets/viewer/page_entry_builder.dart'; -import 'package:aves/widgets/viewer/visual/conductor.dart'; +import 'package:aves/widgets/viewer/view/conductor.dart'; +import 'package:aves/widgets/viewer/view/controller.dart'; +import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -21,6 +24,9 @@ class ViewerTopOverlay extends StatelessWidget { final Size availableSize; final EdgeInsets? viewInsets, viewPadding; + static const Color componentBorderColor = Colors.white30; + static const double componentDimension = 96; + const ViewerTopOverlay({ super.key, required this.entries, @@ -45,7 +51,7 @@ class ViewerTopOverlay extends StatelessWidget { final showInfo = settings.showOverlayInfo; final viewStateConductor = context.read(); - final viewStateNotifier = viewStateConductor.getOrCreateController(pageEntry); + final viewStateNotifier = viewStateConductor.getOrCreateController(pageEntry).viewStateNotifier; final blurred = settings.enableBlurEffect; final viewInsetsPadding = (viewInsets ?? EdgeInsets.zero) + (viewPadding ?? EdgeInsets.zero); @@ -79,23 +85,58 @@ class ViewerTopOverlay extends StatelessWidget { ), ), ), - if (settings.showOverlayMinimap) - SafeArea( - top: !showInfo, - minimum: EdgeInsets.only( - left: viewInsetsPadding.left, - right: viewInsetsPadding.right, - ), - child: Padding( - padding: const EdgeInsets.all(8), - child: FadeTransition( - opacity: scale, - child: Minimap( - viewStateNotifier: viewStateNotifier, + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (settings.showOverlayMinimap) + SafeArea( + top: !showInfo, + minimum: EdgeInsets.only( + left: viewInsetsPadding.left, + right: viewInsetsPadding.right, + ), + child: Padding( + padding: const EdgeInsets.all(8), + child: FadeTransition( + opacity: scale, + child: Minimap( + viewStateNotifier: viewStateNotifier, + ), + ), ), ), - ), - ) + const Spacer(), + if (settings.overlayHistogramStyle != OverlayHistogramStyle.none) + SafeArea( + top: !showInfo, + minimum: EdgeInsets.only( + left: viewInsetsPadding.left, + right: viewInsetsPadding.right, + ), + child: Padding( + padding: const EdgeInsets.all(8), + child: FadeTransition( + opacity: scale, + child: Selector( + selector: (context, vsc) => vsc.getOrCreateController(pageEntry!), + builder: (context, viewStateController, child) { + return ValueListenableBuilder( + valueListenable: viewStateController.fullImageNotifier, + builder: (context, fullImage, child) { + if (fullImage == null || pageEntry == null) return const SizedBox(); + return ImageHistogram( + viewStateController: viewStateController, + image: fullImage, + ); + }, + ); + }, + ), + ), + ), + ), + ], + ), ], ); }, diff --git a/lib/widgets/viewer/overlay/viewer_buttons.dart b/lib/widgets/viewer/overlay/viewer_buttons.dart index b64a91f6c..bc80e1141 100644 --- a/lib/widgets/viewer/overlay/viewer_buttons.dart +++ b/lib/widgets/viewer/overlay/viewer_buttons.dart @@ -282,7 +282,7 @@ class ViewerButtonRowContent extends StatelessWidget { onSelected: (action) { _popupExpandedNotifier.value = null; // wait for the popup menu to hide before proceeding with the action - Future.delayed(Durations.popupMenuAnimation * timeDilation, () => actionDelegate.onActionSelected(context, action)); + Future.delayed(ADurations.popupMenuAnimation * timeDilation, () => actionDelegate.onActionSelected(context, action)); }, onCanceled: () { _popupExpandedNotifier.value = null; diff --git a/lib/widgets/viewer/overlay/wallpaper_buttons.dart b/lib/widgets/viewer/overlay/wallpaper_buttons.dart index f7e70483a..1d4b2d519 100644 --- a/lib/widgets/viewer/overlay/wallpaper_buttons.dart +++ b/lib/widgets/viewer/overlay/wallpaper_buttons.dart @@ -5,7 +5,6 @@ import 'dart:ui' as ui; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/images.dart'; import 'package:aves/model/entry/extensions/props.dart'; -import 'package:aves_model/aves_model.dart'; import 'package:aves/services/wallpaper_service.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; @@ -13,12 +12,12 @@ import 'package:aves/widgets/common/identity/buttons/overlay_button.dart'; import 'package:aves/widgets/dialogs/wallpaper_settings_dialog.dart'; import 'package:aves/widgets/viewer/overlay/viewer_buttons.dart'; import 'package:aves/widgets/viewer/video/conductor.dart'; -import 'package:aves/widgets/viewer/visual/conductor.dart'; +import 'package:aves/widgets/viewer/view/conductor.dart'; +import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class WallpaperButtons extends StatelessWidget with FeedbackMixin { final AvesEntry entry; @@ -57,16 +56,13 @@ class WallpaperButtons extends StatelessWidget with FeedbackMixin { Future _setWallpaper(BuildContext context) async { final l10n = context.l10n; - final value = await showDialog>( + final value = await showDialog<(WallpaperTarget, bool)>( context: context, builder: (context) => const WallpaperSettingsDialog(), routeSettings: const RouteSettings(name: WallpaperSettingsDialog.routeName), ); if (value == null) return; - final target = value.item1; - final useScrollEffect = value.item2; - final reportController = StreamController.broadcast(); unawaited(showOpReport( context: context, @@ -76,6 +72,7 @@ class WallpaperButtons extends StatelessWidget with FeedbackMixin { var region = _getVisibleRegion(context); if (region == null) return; + final (target, useScrollEffect) = value; if (useScrollEffect) { final deltaX = min(region.left, entry.displaySize.width - region.right); region = Rect.fromLTRB(region.left - deltaX, region.top, region.right + deltaX, region.bottom); @@ -89,12 +86,12 @@ class WallpaperButtons extends StatelessWidget with FeedbackMixin { if (success) { await SystemNavigator.pop(); } else { - showFeedback(context, l10n.genericFailureFeedback); + showFeedback(context, FeedbackType.warn, l10n.genericFailureFeedback); } } Rect? _getVisibleRegion(BuildContext context) { - final viewState = context.read().getOrCreateController(entry).value; + final viewState = context.read().getOrCreateController(entry).viewState; final viewportSize = viewState.viewportSize; final contentSize = viewState.contentSize; final scale = viewState.scale; @@ -107,7 +104,7 @@ class WallpaperButtons extends StatelessWidget with FeedbackMixin { } Future _getBytes(BuildContext context, Rect displayRegion) async { - final viewState = context.read().getOrCreateController(entry).value; + final viewState = context.read().getOrCreateController(entry).viewState; final scale = viewState.scale; final displaySize = entry.displaySize; @@ -134,9 +131,11 @@ class WallpaperButtons extends StatelessWidget with FeedbackMixin { final videoController = context.read().getController(entry); if (videoController != null) { final bytes = await videoController.captureFrame(); - needOrientation = rotationDegrees != 0 || isFlipped; - needCrop = true; - provider = MemoryImage(bytes); + if (bytes != null) { + needOrientation = rotationDegrees != 0 || isFlipped; + needCrop = true; + provider = MemoryImage(bytes); + } } } else if (entry.canDecode) { if (entry.useTiles) { diff --git a/lib/widgets/viewer/providers.dart b/lib/widgets/viewer/providers.dart index 1a76a0a35..e0c04af67 100644 --- a/lib/widgets/viewer/providers.dart +++ b/lib/widgets/viewer/providers.dart @@ -1,7 +1,7 @@ import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/widgets/viewer/multipage/conductor.dart'; import 'package:aves/widgets/viewer/video/conductor.dart'; -import 'package:aves/widgets/viewer/visual/conductor.dart'; +import 'package:aves/widgets/viewer/view/conductor.dart'; import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; diff --git a/lib/widgets/viewer/slideshow_page.dart b/lib/widgets/viewer/slideshow_page.dart index d05186d6f..2d59ac63a 100644 --- a/lib/widgets/viewer/slideshow_page.dart +++ b/lib/widgets/viewer/slideshow_page.dart @@ -19,7 +19,6 @@ import 'package:aves_model/aves_model.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class SlideshowPage extends StatefulWidget { static const routeName = '/collection/slideshow'; @@ -151,7 +150,7 @@ class _SlideshowPageState extends State { ); } - Tuple2 get collectionSettings => Tuple2(settings.slideshowShuffle, settings.slideshowVideoPlayback == SlideshowVideoPlayback.skip); + (bool, bool) get collectionSettings => (settings.slideshowShuffle, settings.slideshowVideoPlayback == SlideshowVideoPlayback.skip); Future _showSettings(BuildContext context) async { final oldCollectionSettings = collectionSettings; diff --git a/lib/widgets/viewer/video/conductor.dart b/lib/widgets/viewer/video/conductor.dart index dfe886e18..8557b20c2 100644 --- a/lib/widgets/viewer/video/conductor.dart +++ b/lib/widgets/viewer/video/conductor.dart @@ -6,7 +6,6 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/widgets/viewer/video/db_playback_state_handler.dart'; -import 'package:aves/widgets/viewer/video/fijkplayer.dart'; import 'package:aves_model/aves_model.dart'; import 'package:aves_video/aves_video.dart'; import 'package:collection/collection.dart'; @@ -37,7 +36,11 @@ class VideoConductor { if (controller != null) { _controllers.remove(controller); } else { - controller = IjkPlayerAvesVideoController(entry, playbackStateHandler: playbackStateHandler); + controller = videoControllerFactory.buildController( + entry, + playbackStateHandler: playbackStateHandler, + settings: settings, + ); _subscriptions.add(controller.statusStream.listen((event) => _onControllerStatusChanged(entry, controller!, event))); } _controllers.insert(0, controller); diff --git a/lib/widgets/viewer/video/flutter_vlc_player.dart b/lib/widgets/viewer/video/flutter_vlc_player.dart deleted file mode 100644 index 6fb16fe06..000000000 --- a/lib/widgets/viewer/video/flutter_vlc_player.dart +++ /dev/null @@ -1,107 +0,0 @@ -// import 'dart:async'; -// import 'dart:io'; -// -// import 'package:aves/model/entry.dart'; -// import 'package:aves_utils/aves_utils.dart'; -// import 'package:aves_video/aves_video.dart'; -// import 'package:flutter/material.dart'; -// import 'package:flutter/src/foundation/change_notifier.dart'; -// import 'package:flutter/src/widgets/framework.dart'; -// import 'package:flutter_vlc_player/flutter_vlc_player.dart'; -// import 'package:provider/provider.dart'; -// -// class VlcAvesVideoController extends AvesVideoController { -// VlcPlayerController _instance; -// final List _subscriptions = []; -// final StreamController _valueStreamController = StreamController.broadcast(); -// final AChangeNotifier _playFinishNotifier = AChangeNotifier(); -// -// Stream get _valueStream => _valueStreamController.stream; -// -// VlcAvesVideoController(); -// -// @override -// Future setDataSource(String uri, {int startMillis = 0}) async { -// _instance = VlcPlayerController.file( -// File(uri), -// ); -// _instance.addListener(_onValueChanged); -// _subscriptions.add(_valueStream.where((value) => value.isEnded).listen((_) => _playFinishNotifier.notifyListeners())); -// -// // update value stream to: -// // 1) trigger playability check -// // 2) show the `VlcPlayer` widget -// // 3) initialize its `PlatformView` -// // 4) complete `VlcPlayerController` initialization -// _valueStreamController.add(_instance.value); -// } -// -// @override -// void dispose() { -// _instance?.removeListener(_onValueChanged); -// _valueStreamController.close(); -// _subscriptions -// ..forEach((sub) => sub.cancel()) -// ..clear(); -// _instance?.dispose(); -// } -// -// void _onValueChanged() => _valueStreamController.add(_instance.value); -// -// @override -// Future play() => _instance.play(); -// -// @override -// Future pause() => _instance?.pause(); -// -// @override -// Future seekTo(int targetMillis) => _instance.seekTo(Duration(milliseconds: targetMillis)); -// -// @override -// Listenable get playCompletedListenable => _playFinishNotifier; -// -// @override -// VideoStatus get status => _instance?.value?.toAves ?? VideoStatus.idle; -// -// @override -// Stream get statusStream => _valueStream.map((value) => value.toAves); -// -// @override -// bool get isPlayable => _instance != null; -// -// @override -// int get duration => _instance?.value?.duration?.inMilliseconds; -// -// @override -// int get currentPosition => _instance?.value?.position?.inMilliseconds; -// -// @override -// Stream get positionStream => _valueStream.map((value) => value.position.inMilliseconds); -// -// @override -// Widget buildPlayerWidget(BuildContext context, AvesEntry entry) { -// // do not use `Magnifier` with `applyScale` enabled when using this widget, -// // as the original video size will be used to create the `PlatformView` -// // and a virtual display larger than the device screen may crash the app -// final mqWidth = MediaQuery.sizeOf(context).width; -// final displaySize = entry.displaySize; -// final ratio = mqWidth / displaySize.width; -// return SizedBox.fromSize( -// size: displaySize * ratio, -// child: VlcPlayer( -// controller: _instance, -// aspectRatio: entry.displayAspectRatio, -// ), -// ); -// } -// } -// -// extension ExtraVlcPlayerValue on VlcPlayerValue { -// VideoStatus get toAves { -// if (hasError) return VideoStatus.error; -// if (!isInitialized) return VideoStatus.idle; -// if (isEnded) return VideoStatus.completed; -// if (isPlaying) return VideoStatus.playing; -// return VideoStatus.paused; -// } -// } diff --git a/lib/widgets/viewer/video/video_player.dart b/lib/widgets/viewer/video/video_player.dart deleted file mode 100644 index 0aa9cf8c1..000000000 --- a/lib/widgets/viewer/video/video_player.dart +++ /dev/null @@ -1,83 +0,0 @@ -// import 'dart:async'; -// -// import 'package:aves/model/entry.dart'; -// import 'package:aves_utils/aves_utils.dart'; -// import 'package:aves_video/aves_video.dart'; -// import 'package:flutter/src/foundation/change_notifier.dart'; -// import 'package:flutter/src/widgets/framework.dart'; -// import 'package:video_player/video_player.dart'; -// -// class VideoPlayerAvesVideoController extends AvesVideoController { -// VideoPlayerController _instance; -// final List _subscriptions = []; -// final StreamController _valueStreamController = StreamController.broadcast(); -// final AChangeNotifier _playFinishNotifier = AChangeNotifier(); -// -// Stream get _valueStream => _valueStreamController.stream; -// -// VideoPlayerAvesVideoController(); -// -// @override -// Future setDataSource(String uri, {int startMillis = 0}) async { -// _instance = VideoPlayerController.network(uri); -// _instance.addListener(_onValueChanged); -// _subscriptions.add(_valueStream.where((value) => value.position > value.duration).listen((_) => _playFinishNotifier.notifyListeners())); -// -// await _instance.initialize(); -// await play(); -// } -// -// @override -// void dispose() { -// _instance?.removeListener(_onValueChanged); -// _valueStreamController.close(); -// _subscriptions -// ..forEach((sub) => sub.cancel()) -// ..clear(); -// _instance?.dispose(); -// } -// -// void _onValueChanged() => _valueStreamController.add(_instance.value); -// -// @override -// Future play() => _instance.play(); -// -// @override -// Future pause() => _instance?.pause(); -// -// @override -// Future seekTo(int targetMillis) => _instance.seekTo(Duration(milliseconds: targetMillis)); -// -// @override -// Listenable get playCompletedListenable => _playFinishNotifier; -// -// @override -// VideoStatus get status => _instance?.value?.toAves ?? VideoStatus.idle; -// -// @override -// Stream get statusStream => _valueStream.map((value) => value.toAves); -// -// @override -// bool get isPlayable => _instance != null && _instance.value.isInitialized && !_instance.value.hasError; -// -// @override -// int get duration => _instance?.value?.duration?.inMilliseconds; -// -// @override -// int get currentPosition => _instance?.value?.position?.inMilliseconds; -// -// @override -// Stream get positionStream => _valueStream.map((value) => value.position.inMilliseconds); -// -// @override -// Widget buildPlayerWidget(BuildContext context, AvesEntry entry) => VideoPlayer(_instance); -// } -// -// extension ExtraVideoPlayerValue on VideoPlayerValue { -// VideoStatus get toAves { -// if (hasError) return VideoStatus.error; -// if (!isInitialized) return VideoStatus.idle; -// if (isPlaying) return VideoStatus.playing; -// return VideoStatus.paused; -// } -// } diff --git a/lib/widgets/viewer/view/conductor.dart b/lib/widgets/viewer/view/conductor.dart new file mode 100644 index 000000000..be2a6a751 --- /dev/null +++ b/lib/widgets/viewer/view/conductor.dart @@ -0,0 +1,65 @@ +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/view_state.dart'; +import 'package:aves/widgets/viewer/view/controller.dart'; +import 'package:aves_magnifier/aves_magnifier.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; + +class ViewStateConductor { + final List _controllers = []; + Size _viewportSize = Size.zero; + + static const maxControllerCount = 3; + + Future dispose() async { + _controllers.forEach((v) => v.dispose()); + _controllers.clear(); + } + + set viewportSize(Size size) => _viewportSize = size; + + ViewStateController getOrCreateController(AvesEntry entry) { + var controller = getController(entry); + if (controller != null) { + _controllers.remove(controller); + } else { + // try to initialize the view state to match magnifier initial state + const initialScale = ScaleLevel(ref: ScaleReference.contained); + final initialValue = ViewState( + position: Offset.zero, + scale: ScaleBoundaries( + allowOriginalScaleBeyondRange: true, + minScale: initialScale, + maxScale: initialScale, + initialScale: initialScale, + viewportSize: _viewportSize, + contentSize: entry.displaySize, + ).initialScale, + viewportSize: _viewportSize, + contentSize: entry.displaySize, + ); + controller = ViewStateController( + entry: entry, + viewStateNotifier: ValueNotifier(initialValue), + ); + } + _controllers.insert(0, controller); + while (_controllers.length > maxControllerCount) { + _controllers.removeLast().dispose(); + } + return controller; + } + + ViewStateController? getController(AvesEntry entry) { + return _controllers.firstWhereOrNull((c) => c.entry.uri == entry.uri && c.entry.pageId == entry.pageId); + } + + void reset(AvesEntry entry) { + final uris = { + entry, + ...?entry.burstEntries, + }.map((v) => v.uri).toSet(); + _controllers.removeWhere((v) => uris.contains(v.entry.uri)); + } +} diff --git a/lib/widgets/viewer/view/controller.dart b/lib/widgets/viewer/view/controller.dart new file mode 100644 index 000000000..481163a6c --- /dev/null +++ b/lib/widgets/viewer/view/controller.dart @@ -0,0 +1,21 @@ +import 'package:aves/model/entry/entry.dart'; +import 'package:aves/model/view_state.dart'; +import 'package:aves/widgets/viewer/view/histogram.dart'; +import 'package:flutter/material.dart'; + +class ViewStateController with HistogramMixin { + final AvesEntry entry; + final ValueNotifier viewStateNotifier; + final ValueNotifier fullImageNotifier = ValueNotifier(null); + + ViewState get viewState => viewStateNotifier.value; + + ViewStateController({ + required this.entry, + required this.viewStateNotifier, + }); + + void dispose() { + viewStateNotifier.dispose(); + } +} diff --git a/lib/widgets/viewer/view/histogram.dart b/lib/widgets/viewer/view/histogram.dart new file mode 100644 index 000000000..c059e159b --- /dev/null +++ b/lib/widgets/viewer/view/histogram.dart @@ -0,0 +1,97 @@ +import 'dart:async'; +import 'dart:ui'; + +import 'package:aves/model/settings/settings.dart'; +import 'package:aves_model/aves_model.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +enum HistogramChannel { red, green, blue, luminance } + +typedef HistogramLevels = Map>; + +mixin HistogramMixin { + HistogramLevels _levels = {}; + Completer? _completer; + + static const int bins = 256; + + Future getHistogramLevels(ImageInfo info, bool forceUpdate) async { + if (_levels.isEmpty || forceUpdate) { + if (_completer == null) { + _completer = Completer(); + final data = (await info.image.toByteData(format: ImageByteFormat.rawStraightRgba))!; + _levels = switch (settings.overlayHistogramStyle) { + OverlayHistogramStyle.rgb => await compute(_computeRgbLevels, data), + OverlayHistogramStyle.luminance => await compute(_computeLuminanceLevels, data), + _ => >{}, + }; + _completer?.complete(); + } else { + await _completer?.future; + _completer = null; + } + } + return _levels; + } + + static HistogramLevels _computeRgbLevels(ByteData data) { + final redLevels = List.filled(bins, 0); + final greenLevels = List.filled(bins, 0); + final blueLevels = List.filled(bins, 0); + + final view = Uint8List.view(data.buffer); + final pixelCount = view.length / 4; + for (var i = 0; i < pixelCount; i += 4) { + final a = view[i + 3]; + if (a > 0) { + final r = view[i + 0]; + final g = view[i + 1]; + final b = view[i + 2]; + redLevels[r]++; + greenLevels[g]++; + blueLevels[b]++; + } + } + + final max = [ + redLevels.max, + greenLevels.max, + blueLevels.max, + ].max; + if (max == 0) return {}; + + final f = 1.0 / max; + return { + HistogramChannel.red: redLevels.map((v) => v * f).toList(), + HistogramChannel.green: greenLevels.map((v) => v * f).toList(), + HistogramChannel.blue: blueLevels.map((v) => v * f).toList(), + }; + } + + static HistogramLevels _computeLuminanceLevels(ByteData data) { + final lumLevels = List.filled(bins, 0); + const normMax = bins - 1; + + final view = Uint8List.view(data.buffer); + final pixelCount = view.length / 4; + for (var i = 0; i < pixelCount; i += 4) { + final a = view[i + 3]; + if (a > 0) { + final r = view[i + 0]; + final g = view[i + 1]; + final b = view[i + 2]; + lumLevels[(Color.fromARGB(a, r, g, b).computeLuminance() * normMax).round()]++; + } + } + + final max = lumLevels.max; + if (max == 0) return {}; + + final f = 1.0 / max; + return { + HistogramChannel.luminance: lumLevels.map((v) => v * f).toList(), + }; + } +} diff --git a/lib/widgets/viewer/visual/conductor.dart b/lib/widgets/viewer/visual/conductor.dart deleted file mode 100644 index 26f6a3b96..000000000 --- a/lib/widgets/viewer/visual/conductor.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:aves/model/entry/entry.dart'; -import 'package:aves/model/view_state.dart'; -import 'package:aves_magnifier/aves_magnifier.dart'; -import 'package:collection/collection.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/rendering.dart'; -import 'package:tuple/tuple.dart'; - -class ViewStateConductor { - final List>> _controllers = []; - Size _viewportSize = Size.zero; - - static const maxControllerCount = 3; - - Future dispose() async { - _controllers.clear(); - } - - set viewportSize(Size size) => _viewportSize = size; - - ValueNotifier getOrCreateController(AvesEntry entry) { - var controller = _controllers.firstOrNull; - if (controller == null || controller.item1 != entry.uri) { - controller = _controllers.firstWhereOrNull((kv) => kv.item1 == entry.uri); - if (controller != null) { - _controllers.remove(controller); - } else { - // try to initialize the view state to match magnifier initial state - const initialScale = ScaleLevel(ref: ScaleReference.contained); - final initialValue = ViewState( - position: Offset.zero, - scale: ScaleBoundaries( - allowOriginalScaleBeyondRange: true, - minScale: initialScale, - maxScale: initialScale, - initialScale: initialScale, - viewportSize: _viewportSize, - contentSize: entry.displaySize, - ).initialScale, - viewportSize: _viewportSize, - contentSize: entry.displaySize, - ); - controller = Tuple2(entry.uri, ValueNotifier(initialValue)); - } - _controllers.insert(0, controller); - while (_controllers.length > maxControllerCount) { - _controllers.removeLast().item2.dispose(); - } - } - return controller.item2; - } - - void reset(AvesEntry entry) { - final uris = { - entry, - ...?entry.burstEntries, - }.map((v) => v.uri).toSet(); - _controllers.removeWhere((kv) => uris.contains(kv.item1)); - } -} diff --git a/lib/widgets/viewer/visual/controller_mixin.dart b/lib/widgets/viewer/visual/controller_mixin.dart index 52235df48..b00c44761 100644 --- a/lib/widgets/viewer/visual/controller_mixin.dart +++ b/lib/widgets/viewer/visual/controller_mixin.dart @@ -126,9 +126,9 @@ mixin EntryViewControllerMixin on State { final controller = context.read().getOrCreateController(entry); setState(() {}); - if (videoAutoPlayEnabled) { + if (videoAutoPlayEnabled || entry.isAnimated) { final resumeTimeMillis = await controller.getResumeTime(context); - await _playVideo(controller, () => entry == entryNotifier.value, resumeTimeMillis: resumeTimeMillis); + await _autoPlayVideo(controller, () => entry == entryNotifier.value, resumeTimeMillis: resumeTimeMillis); } } @@ -163,7 +163,7 @@ mixin EntryViewControllerMixin on State { final pageVideoController = videoConductor.getController(pageEntry); assert(pageVideoController != null); if (pageVideoController != null) { - await _playVideo(pageVideoController, () => entry == entryNotifier.value && page == multiPageController.page); + await _autoPlayVideo(pageVideoController, () => entry == entryNotifier.value && page == multiPageController.page); } } } @@ -174,7 +174,7 @@ mixin EntryViewControllerMixin on State { await _onPageChanged(); if (entry.isMotionPhoto && shouldAutoPlayMotionPhoto) { - await Future.delayed(Durations.motionPhotoAutoPlayDelay); + await Future.delayed(ADurations.motionPhotoAutoPlayDelay); if (entry == entryNotifier.value) { multiPageController.page = 1; } @@ -192,21 +192,20 @@ mixin EntryViewControllerMixin on State { } } - Future _playVideo(AvesVideoController videoController, bool Function() isCurrent, {int? resumeTimeMillis}) async { + Future _autoPlayVideo(AvesVideoController videoController, bool Function() isCurrent, {int? resumeTimeMillis}) async { // video decoding may fail or have initial artifacts when the player initializes // during this widget initialization (because of the page transition and hero animation?) // so we play after a delay for increased stability await Future.delayed(const Duration(milliseconds: 300) * timeDilation); - if (!videoController.isMuted && shouldAutoPlayVideoMuted) { + if (!videoController.isMuted && (videoController.entry.isAnimated || shouldAutoPlayVideoMuted)) { await videoController.mute(true); } if (resumeTimeMillis != null) { await videoController.seekTo(resumeTimeMillis); - } else { - await videoController.play(); } + await videoController.play(); // playing controllers are paused when the entry changes, // but the controller may still be preparing (not yet playing) when this happens diff --git a/lib/widgets/viewer/visual/entry_page_view.dart b/lib/widgets/viewer/visual/entry_page_view.dart index 12b216852..5ab65e474 100644 --- a/lib/widgets/viewer/visual/entry_page_view.dart +++ b/lib/widgets/viewer/visual/entry_page_view.dart @@ -5,6 +5,7 @@ import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; +import 'package:aves/model/view_state.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/services/media/media_session_service.dart'; import 'package:aves/theme/icons.dart'; @@ -15,10 +16,9 @@ import 'package:aves/widgets/viewer/controls/controller.dart'; import 'package:aves/widgets/viewer/controls/notifications.dart'; import 'package:aves/widgets/viewer/hero.dart'; import 'package:aves/widgets/viewer/video/conductor.dart'; -import 'package:aves/widgets/viewer/visual/conductor.dart'; +import 'package:aves/widgets/viewer/view/conductor.dart'; import 'package:aves/widgets/viewer/visual/error.dart'; import 'package:aves/widgets/viewer/visual/raster.dart'; -import 'package:aves/model/view_state.dart'; import 'package:aves/widgets/viewer/visual/vector.dart'; import 'package:aves/widgets/viewer/visual/video/cover.dart'; import 'package:aves/widgets/viewer/visual/video/subtitle/subtitle.dart'; @@ -29,7 +29,6 @@ import 'package:aves_model/aves_model.dart'; import 'package:decorated_icon/decorated_icon.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; class EntryPageView extends StatefulWidget { final AvesEntry mainEntry, pageEntry; @@ -90,7 +89,7 @@ class _EntryPageViewState extends State with SingleTickerProvider void _registerWidget(EntryPageView widget) { final entry = widget.pageEntry; - _viewStateNotifier = context.read().getOrCreateController(entry); + _viewStateNotifier = context.read().getOrCreateController(entry).viewStateNotifier; _magnifierController = AvesMagnifierController(); _subscriptions.add(_magnifierController.stateStream.listen(_onViewStateChanged)); _subscriptions.add(_magnifierController.scaleBoundariesStream.listen(_onViewScaleBoundariesChanged)); @@ -197,21 +196,20 @@ class _EntryPageViewState extends State with SingleTickerProvider final videoController = context.read().getController(entry); if (videoController == null) return const SizedBox(); - return ValueListenableBuilder( + return ValueListenableBuilder( valueListenable: videoController.sarNotifier, builder: (context, sar, child) { final videoDisplaySize = entry.videoDisplaySize(sar); + final isPureVideo = entry.isPureVideo; - return Selector>( - selector: (context, s) => Tuple3( - s.videoGestureDoubleTapTogglePlay, - s.videoGestureSideDoubleTapSeek, - s.videoGestureVerticalDragBrightnessVolume, + return Selector( + selector: (context, s) => ( + isPureVideo && s.videoGestureDoubleTapTogglePlay, + isPureVideo && s.videoGestureSideDoubleTapSeek, + isPureVideo && s.videoGestureVerticalDragBrightnessVolume, ), builder: (context, s, child) { - final playGesture = s.item1; - final seekGesture = s.item2; - final useVerticalDragGesture = s.item3; + final (playGesture, seekGesture, useVerticalDragGesture) = s; final useTapGesture = playGesture || seekGesture; MagnifierDoubleTapCallback? onDoubleTap; diff --git a/lib/widgets/viewer/visual/raster.dart b/lib/widgets/viewer/visual/raster.dart index 0386b9128..c90f8744b 100644 --- a/lib/widgets/viewer/visual/raster.dart +++ b/lib/widgets/viewer/visual/raster.dart @@ -6,14 +6,14 @@ import 'package:aves/model/entry/extensions/images.dart'; import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/entry_background.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/widgets/common/fx/checkered_decoration.dart'; -import 'package:aves/widgets/viewer/visual/entry_page_view.dart'; import 'package:aves/model/view_state.dart'; +import 'package:aves/widgets/common/fx/checkered_decoration.dart'; +import 'package:aves/widgets/viewer/controls/notifications.dart'; +import 'package:aves/widgets/viewer/visual/entry_page_view.dart'; import 'package:aves_model/aves_model.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:latlong2/latlong.dart'; -import 'package:tuple/tuple.dart'; class RasterImageView extends StatefulWidget { final AvesEntry entry; @@ -105,6 +105,7 @@ class _RasterImageViewState extends State { void _onFullImageCompleted(ImageInfo image, bool synchronousCall) { _unregisterFullImage(); _fullImageLoaded.value = true; + FullImageLoadedNotification(entry, fullImageProvider).dispatch(context); } @override @@ -257,10 +258,11 @@ class _RasterImageViewState extends State { viewRect: viewRect, ); if (rects != null) { + final (tileRect, regionRect) = rects; tiles.add(_RegionTile( entry: entry, - tileRect: rects.item1, - regionRect: rects.item2, + tileRect: tileRect, + regionRect: regionRect, sampleSize: sampleSize, )); } @@ -281,7 +283,7 @@ class _RasterImageViewState extends State { return viewOrigin & viewportSize; } - Tuple2>? _getTileRects({ + (Rect tileRect, Rectangle regionRect)? _getTileRects({ required int x, required int y, required int regionSide, @@ -312,7 +314,7 @@ class _RasterImageViewState extends State { } else { regionRect = Rectangle(x, y, thisRegionWidth, thisRegionHeight); } - return Tuple2>(tileRect, regionRect); + return (tileRect, regionRect); } } diff --git a/lib/widgets/viewer/visual/vector.dart b/lib/widgets/viewer/visual/vector.dart index c1502a7d9..fbc535646 100644 --- a/lib/widgets/viewer/visual/vector.dart +++ b/lib/widgets/viewer/visual/vector.dart @@ -5,14 +5,13 @@ import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/images.dart'; import 'package:aves/model/settings/enums/entry_background.dart'; import 'package:aves/model/settings/settings.dart'; +import 'package:aves/model/view_state.dart'; import 'package:aves/utils/math_utils.dart'; import 'package:aves/widgets/common/fx/checkered_decoration.dart'; import 'package:aves/widgets/viewer/visual/entry_page_view.dart'; -import 'package:aves/model/view_state.dart'; import 'package:aves_model/aves_model.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:tuple/tuple.dart'; class VectorImageView extends StatefulWidget { final AvesEntry entry; @@ -233,10 +232,11 @@ class _VectorImageViewState extends State { viewRect: viewRect, ); if (rects != null) { + final (tileRect, regionRect) = rects; tiles.add(_RegionTile( entry: entry, - tileRect: rects.item1, - regionRect: rects.item2, + tileRect: tileRect, + regionRect: regionRect, scale: svgScale, backgroundColor: backgroundColor, backgroundFrameBuilder: backgroundFrameBuilder, @@ -259,7 +259,7 @@ class _VectorImageViewState extends State { return viewOrigin & viewportSize; } - Tuple2>? _getTileRects({ + (Rect tileRect, Rectangle regionRect)? _getTileRects({ required double x, required double y, required double regionSide, @@ -278,7 +278,7 @@ class _VectorImageViewState extends State { if (!viewRect.overlaps(tileRect)) return null; final regionRect = Rectangle(x, y, thisRegionWidth, thisRegionHeight); - return Tuple2>(tileRect, regionRect); + return (tileRect, regionRect); } double _imageScaleForViewScale({ diff --git a/lib/widgets/viewer/visual/video/cover.dart b/lib/widgets/viewer/visual/video/cover.dart index 5017a4a3f..0d6cb9aca 100644 --- a/lib/widgets/viewer/visual/video/cover.dart +++ b/lib/widgets/viewer/visual/video/cover.dart @@ -3,8 +3,8 @@ import 'package:aves/model/entry/extensions/images.dart'; import 'package:aves/model/entry/extensions/multipage.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/thumbnail/image.dart'; -import 'package:aves_video/aves_video.dart'; import 'package:aves_magnifier/aves_magnifier.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -106,7 +106,7 @@ class _VideoCoverState extends State { child: AnimatedOpacity( opacity: showCover ? 1 : 0, curve: Curves.easeInCirc, - duration: Durations.viewerVideoPlayerTransition, + duration: ADurations.viewerVideoPlayerTransition, onEnd: () { // while cover is fading out, the same controller is used for both the cover and the video, // and both fire scale boundaries events, so we make sure that in the end diff --git a/lib/widgets/viewer/visual/video/subtitle/ass_parser.dart b/lib/widgets/viewer/visual/video/subtitle/ass_parser.dart index 06bba15df..42085900b 100644 --- a/lib/widgets/viewer/visual/video/subtitle/ass_parser.dart +++ b/lib/widgets/viewer/visual/video/subtitle/ass_parser.dart @@ -358,7 +358,7 @@ class AssParser { ); } - static String _replaceChars(String text) => text.replaceAll(r'\h', UniChars.noBreakSpace).replaceAll(r'\N', '\n').trim(); + static String _replaceChars(String text) => text.replaceAll(r'\h', UniChars.noBreakSpace).replaceAll(r'\N', '\n'); static int? _parseAlpha(String param) { final match = alphaPattern.firstMatch(param); diff --git a/lib/widgets/viewer/visual/video/subtitle/style.dart b/lib/widgets/viewer/visual/video/subtitle/style.dart index a0b6a1d2d..3700eb137 100644 --- a/lib/widgets/viewer/visual/video/subtitle/style.dart +++ b/lib/widgets/viewer/visual/video/subtitle/style.dart @@ -85,7 +85,7 @@ class SubtitleStyle extends Equatable with Diagnosticable { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(EnumProperty('hAlign', hAlign)); - properties.add(EnumProperty('vAlign', vAlign)); + properties.add(DiagnosticsProperty('vAlign', vAlign)); properties.add(ColorProperty('borderColor', borderColor)); properties.add(DoubleProperty('borderWidth', borderWidth)); properties.add(DoubleProperty('edgeBlur', edgeBlur)); diff --git a/lib/widgets/viewer/visual/video/subtitle/subtitle.dart b/lib/widgets/viewer/visual/video/subtitle/subtitle.dart index 131012944..517c99002 100644 --- a/lib/widgets/viewer/visual/video/subtitle/subtitle.dart +++ b/lib/widgets/viewer/visual/video/subtitle/subtitle.dart @@ -33,244 +33,250 @@ class VideoSubtitles extends StatelessWidget { @override Widget build(BuildContext context) { - final videoDisplaySize = entry.videoDisplaySize(controller.sarNotifier.value); - return IgnorePointer( - child: Consumer( - builder: (context, settings, child) { - final baseTextAlign = settings.subtitleTextAlignment; - final baseTextAlignY = settings.subtitleTextPosition.toTextAlignVertical(); - final baseOutlineWidth = settings.subtitleShowOutline ? 1 : 0; - final baseOutlineColor = Colors.black.withOpacity(settings.subtitleTextColor.opacity); - final baseShadows = [ - Shadow( - color: baseOutlineColor, - offset: baseShadowOffset, - ), - ]; - final baseStyle = TextStyle( - color: settings.subtitleTextColor, - fontSize: settings.subtitleFontSize, - shadows: settings.subtitleShowOutline ? baseShadows : null, - ); + return ValueListenableBuilder( + valueListenable: controller.sarNotifier, + builder: (context, sar, child) { + final videoDisplaySize = entry.videoDisplaySize(sar); - final viewportSize = MediaQuery.sizeOf(context); - final isPortrait = MediaQuery.orientationOf(context) == Orientation.portrait; - final bottom = isPortrait ? .5 : .8; - return ValueListenableBuilder( - valueListenable: viewStateNotifier, - builder: (context, viewState, child) { - final viewPosition = viewState.position; - final viewScale = viewState.scale ?? 1; - final viewSize = videoDisplaySize * viewScale; - final viewOffset = Offset( - (viewportSize.width - viewSize.width) / 2, - (viewportSize.height - viewSize.height) / 2, + return IgnorePointer( + child: Consumer( + builder: (context, settings, child) { + final baseTextAlign = settings.subtitleTextAlignment; + final baseTextAlignY = settings.subtitleTextPosition.toTextAlignVertical(); + final baseOutlineWidth = settings.subtitleShowOutline ? 1 : 0; + final baseOutlineColor = Colors.black.withOpacity(settings.subtitleTextColor.opacity); + final baseShadows = [ + Shadow( + color: baseOutlineColor, + offset: baseShadowOffset, + ), + ]; + final baseStyle = TextStyle( + color: settings.subtitleTextColor, + fontSize: settings.subtitleFontSize, + shadows: settings.subtitleShowOutline ? baseShadows : null, ); - return StreamBuilder( - stream: controller.timedTextStream, - builder: (context, snapshot) { - final text = snapshot.data; - if (text == null) return const SizedBox(); + final viewportSize = MediaQuery.sizeOf(context); + final isPortrait = MediaQuery.orientationOf(context) == Orientation.portrait; + final bottom = isPortrait ? .5 : .8; + return ValueListenableBuilder( + valueListenable: viewStateNotifier, + builder: (context, viewState, child) { + final viewPosition = viewState.position; + final viewScale = viewState.scale ?? 1; + final viewSize = videoDisplaySize * viewScale; + final viewOffset = Offset( + (viewportSize.width - viewSize.width) / 2, + (viewportSize.height - viewSize.height) / 2, + ); - if (debugMode) { - return Padding( - padding: const EdgeInsets.only(top: 100.0), - child: Align( - alignment: Alignment.topLeft, - child: OutlinedText( - textSpans: [ - TextSpan( - text: text, - style: const TextStyle(fontSize: 14), - ) - ], - outlineWidth: 1, - outlineColor: Colors.black, - ), - ), - ); - } + return StreamBuilder( + stream: controller.timedTextStream, + builder: (context, snapshot) { + final text = snapshot.data; + if (text == null) return const SizedBox(); - final styledLine = AssParser.parse(text, baseStyle, viewScale); - final position = styledLine.position; - final clip = styledLine.clip; - final styledSpans = styledLine.spans; - final byExtraStyle = groupBy(styledSpans, (v) => v.extraStyle); - return Stack( - children: byExtraStyle.entries.map((kv) { - final extraStyle = kv.key; - final spans = kv.value.map((v) { - final span = v.textSpan; - final style = span.style; - if (position == null || style == null) return span; - - final letterSpacing = style.letterSpacing; - final shadows = style.shadows; - return TextSpan( - text: span.text, - style: style.copyWith( - letterSpacing: letterSpacing != null ? letterSpacing * viewScale : null, - shadows: shadows - ?.map((v) => Shadow( - color: v.color, - offset: v.offset * viewScale, - blurRadius: v.blurRadius * viewScale, - )) - .toList(), - ), - ); - }).toList(); - final drawingPaths = extraStyle.drawingPaths; - final textHAlign = extraStyle.hAlign ?? (position != null ? TextAlign.center : baseTextAlign); - final textVAlign = extraStyle.vAlign ?? (position != null ? TextAlignVertical.bottom : baseTextAlignY); - - Widget child; - if (drawingPaths != null) { - child = CustomPaint( - painter: SubtitlePathPainter( - paths: drawingPaths, - scale: viewScale, - fillColor: spans.firstOrNull?.style?.color ?? Colors.white, - strokeColor: extraStyle.borderColor, - ), - ); - } else { - final outlineWidth = extraStyle.borderWidth ?? (extraStyle.edgeBlur != null ? 2 : 1); - child = OutlinedText( - textSpans: spans, - outlineWidth: outlineWidth * (position != null ? viewScale : baseOutlineWidth), - outlineColor: extraStyle.borderColor ?? baseOutlineColor, - outlineBlurSigma: extraStyle.edgeBlur ?? 0, - textAlign: textHAlign, - ); - } - - var transform = Matrix4.identity(); - - if (position != null) { - final para = RenderParagraph( - TextSpan(children: spans), - textDirection: TextDirection.ltr, - textScaleFactor: MediaQuery.textScaleFactorOf(context), - )..layout(const BoxConstraints()); - final textWidth = para.getMaxIntrinsicWidth(double.infinity); - final textHeight = para.getMaxIntrinsicHeight(double.infinity); - - late double anchorOffsetX, anchorOffsetY; - switch (textHAlign) { - case TextAlign.left: - anchorOffsetX = 0; - case TextAlign.right: - anchorOffsetX = -textWidth; - case TextAlign.center: - default: - anchorOffsetX = -textWidth / 2; - } - switch (textVAlign) { - case TextAlignVertical.top: - anchorOffsetY = 0; - case TextAlignVertical.center: - anchorOffsetY = -textHeight / 2; - case TextAlignVertical.bottom: - anchorOffsetY = -textHeight; - } - final alignOffset = Offset(anchorOffsetX, anchorOffsetY); - final lineOffset = position * viewScale + viewPosition; - final translateOffset = viewOffset + lineOffset + alignOffset; - transform.translate(translateOffset.dx, translateOffset.dy); - } - - if (extraStyle.rotating) { - // for perspective - transform.setEntry(3, 2, 0.001); - final x = -angles.degToRadian(extraStyle.rotationX ?? 0); - final y = -angles.degToRadian(extraStyle.rotationY ?? 0); - final z = -angles.degToRadian(extraStyle.rotationZ ?? 0); - if (x != 0) transform.rotateX(x); - if (y != 0) transform.rotateY(y); - if (z != 0) transform.rotateZ(z); - } - if (extraStyle.scaling) { - final x = extraStyle.scaleX ?? 1; - final y = extraStyle.scaleY ?? 1; - transform.scale(x, y); - } - if (extraStyle.shearing) { - final x = extraStyle.shearX ?? 0; - final y = extraStyle.shearY ?? 0; - transform.multiply(Matrix4(1, y, 0, 0, x, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)); - } - - if (!transform.isIdentity()) { - child = Transform( - transform: transform, - alignment: Alignment.center, - child: child, - ); - } - - if (position == null) { - late double alignX; - switch (textHAlign) { - case TextAlign.left: - alignX = -1; - case TextAlign.right: - alignX = 1; - case TextAlign.center: - default: - alignX = 0; - } - late double alignY; - switch (textVAlign) { - case TextAlignVertical.top: - alignY = -bottom; - case TextAlignVertical.center: - alignY = 0; - case TextAlignVertical.bottom: - default: - alignY = bottom; - } - child = Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), + if (debugMode) { + return Padding( + padding: const EdgeInsets.only(top: 100.0), child: Align( - alignment: Alignment(alignX, alignY), - child: TextBackgroundPainter( - spans: spans, - style: DefaultTextStyle.of(context).style.merge(spans.first.style!.copyWith( - backgroundColor: settings.subtitleBackgroundColor, - )), - textAlign: textHAlign, - child: child, + alignment: Alignment.topLeft, + child: OutlinedText( + textSpans: [ + TextSpan( + text: text, + style: const TextStyle(fontSize: 14), + ) + ], + outlineWidth: 1, + outlineColor: Colors.black, ), ), ); } - if (clip != null) { - final clipOffset = viewOffset + viewPosition; - final matrix = Matrix4.identity() - ..translate(clipOffset.dx, clipOffset.dy) - ..scale(viewScale, viewScale); - final transform = matrix.storage; - child = ClipPath( - clipper: SubtitlePathClipper( - paths: clip.map((v) => v.transform(transform)).toList(), - scale: viewScale, - ), - child: child, - ); - } + final styledLine = AssParser.parse(text, baseStyle, viewScale); + final position = styledLine.position; + final clip = styledLine.clip; + final styledSpans = styledLine.spans; + final byExtraStyle = groupBy(styledSpans, (v) => v.extraStyle); + return Stack( + children: byExtraStyle.entries.map((kv) { + final extraStyle = kv.key; + final spans = kv.value.map((v) { + final span = v.textSpan; + final style = span.style; + if (position == null || style == null) return span; - return child; - }).toList(), + final letterSpacing = style.letterSpacing; + final shadows = style.shadows; + return TextSpan( + text: span.text, + style: style.copyWith( + letterSpacing: letterSpacing != null ? letterSpacing * viewScale : null, + shadows: shadows + ?.map((v) => Shadow( + color: v.color, + offset: v.offset * viewScale, + blurRadius: v.blurRadius * viewScale, + )) + .toList(), + ), + ); + }).toList(); + final drawingPaths = extraStyle.drawingPaths; + final textHAlign = extraStyle.hAlign ?? (position != null ? TextAlign.center : baseTextAlign); + final textVAlign = extraStyle.vAlign ?? (position != null ? TextAlignVertical.bottom : baseTextAlignY); + + Widget child; + if (drawingPaths != null) { + child = CustomPaint( + painter: SubtitlePathPainter( + paths: drawingPaths, + scale: viewScale, + fillColor: spans.firstOrNull?.style?.color ?? Colors.white, + strokeColor: extraStyle.borderColor, + ), + ); + } else { + final outlineWidth = extraStyle.borderWidth ?? (extraStyle.edgeBlur != null ? 2 : 1); + child = OutlinedText( + textSpans: spans, + outlineWidth: outlineWidth * (position != null ? viewScale : baseOutlineWidth), + outlineColor: extraStyle.borderColor ?? baseOutlineColor, + outlineBlurSigma: extraStyle.edgeBlur ?? 0, + textAlign: textHAlign, + ); + } + + var transform = Matrix4.identity(); + + if (position != null) { + final para = RenderParagraph( + TextSpan(children: spans), + textDirection: TextDirection.ltr, + textScaleFactor: MediaQuery.textScaleFactorOf(context), + )..layout(const BoxConstraints()); + final textWidth = para.getMaxIntrinsicWidth(double.infinity); + final textHeight = para.getMaxIntrinsicHeight(double.infinity); + + late double anchorOffsetX, anchorOffsetY; + switch (textHAlign) { + case TextAlign.left: + anchorOffsetX = 0; + case TextAlign.right: + anchorOffsetX = -textWidth; + case TextAlign.center: + default: + anchorOffsetX = -textWidth / 2; + } + switch (textVAlign) { + case TextAlignVertical.top: + anchorOffsetY = 0; + case TextAlignVertical.center: + anchorOffsetY = -textHeight / 2; + case TextAlignVertical.bottom: + anchorOffsetY = -textHeight; + } + final alignOffset = Offset(anchorOffsetX, anchorOffsetY); + final lineOffset = position * viewScale + viewPosition; + final translateOffset = viewOffset + lineOffset + alignOffset; + transform.translate(translateOffset.dx, translateOffset.dy); + } + + if (extraStyle.rotating) { + // for perspective + transform.setEntry(3, 2, 0.001); + final x = -angles.degToRadian(extraStyle.rotationX ?? 0); + final y = -angles.degToRadian(extraStyle.rotationY ?? 0); + final z = -angles.degToRadian(extraStyle.rotationZ ?? 0); + if (x != 0) transform.rotateX(x); + if (y != 0) transform.rotateY(y); + if (z != 0) transform.rotateZ(z); + } + if (extraStyle.scaling) { + final x = extraStyle.scaleX ?? 1; + final y = extraStyle.scaleY ?? 1; + transform.scale(x, y); + } + if (extraStyle.shearing) { + final x = extraStyle.shearX ?? 0; + final y = extraStyle.shearY ?? 0; + transform.multiply(Matrix4(1, y, 0, 0, x, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)); + } + + if (!transform.isIdentity()) { + child = Transform( + transform: transform, + alignment: Alignment.center, + child: child, + ); + } + + if (position == null) { + late double alignX; + switch (textHAlign) { + case TextAlign.left: + alignX = -1; + case TextAlign.right: + alignX = 1; + case TextAlign.center: + default: + alignX = 0; + } + late double alignY; + switch (textVAlign) { + case TextAlignVertical.top: + alignY = -bottom; + case TextAlignVertical.center: + alignY = 0; + case TextAlignVertical.bottom: + default: + alignY = bottom; + } + child = Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Align( + alignment: Alignment(alignX, alignY), + child: TextBackgroundPainter( + spans: spans, + style: DefaultTextStyle.of(context).style.merge(spans.first.style!.copyWith( + backgroundColor: settings.subtitleBackgroundColor, + )), + textAlign: textHAlign, + child: child, + ), + ), + ); + } + + if (clip != null) { + final clipOffset = viewOffset + viewPosition; + final matrix = Matrix4.identity() + ..translate(clipOffset.dx, clipOffset.dy) + ..scale(viewScale, viewScale); + final transform = matrix.storage; + child = ClipPath( + clipper: SubtitlePathClipper( + paths: clip.map((v) => v.transform(transform)).toList(), + scale: viewScale, + ), + child: child, + ); + } + + return child; + }).toList(), + ); + }, ); }, ); }, - ); - }, - ), + ), + ); + }, ); } } diff --git a/lib/widgets/viewer/visual/video/video_view.dart b/lib/widgets/viewer/visual/video/video_view.dart index 06c3d6819..c06c06067 100644 --- a/lib/widgets/viewer/visual/video/video_view.dart +++ b/lib/widgets/viewer/visual/video/video_view.dart @@ -51,10 +51,9 @@ class _VideoViewState extends State { @override Widget build(BuildContext context) { return StreamBuilder( - stream: controller.statusStream, - builder: (context, snapshot) { - return controller.isReady ? controller.buildPlayerWidget(context) : const SizedBox(); - }); + stream: controller.statusStream, + builder: (context, snapshot) => controller.isReady ? controller.buildPlayerWidget(context) : const SizedBox(), + ); } // not called when looping diff --git a/lib/widgets/wallpaper_page.dart b/lib/widgets/wallpaper_page.dart index 0642b57e1..5d9311ac7 100644 --- a/lib/widgets/wallpaper_page.dart +++ b/lib/widgets/wallpaper_page.dart @@ -173,7 +173,7 @@ class _EntryEditorState extends State with EntryViewControllerMixin final targetEntry = pageEntry ?? mainEntry; Widget? child; // a 360 video is both a video and a panorama but only the video controls are displayed - if (targetEntry.isVideo) { + if (targetEntry.isPureVideo) { child = Selector( selector: (context, vc) => vc.getController(targetEntry), builder: (context, videoController, child) => VideoControlOverlay( diff --git a/lib/widgets/welcome_page.dart b/lib/widgets/welcome_page.dart index 9a03e5a52..1bde23fa2 100644 --- a/lib/widgets/welcome_page.dart +++ b/lib/widgets/welcome_page.dart @@ -77,7 +77,7 @@ class _WelcomePageState extends State { Padding( padding: const EdgeInsets.all(16), child: LinkChip( - leading: const Icon( + leading: Icon( AIcons.privacy, size: 22, ), diff --git a/plugins/aves_magnifier/lib/src/core/core.dart b/plugins/aves_magnifier/lib/src/core/core.dart index 53cb4da9a..1dd776e67 100644 --- a/plugins/aves_magnifier/lib/src/core/core.dart +++ b/plugins/aves_magnifier/lib/src/core/core.dart @@ -12,7 +12,6 @@ import 'package:aves_utils/aves_utils.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; -import 'package:tuple/tuple.dart'; /* adapted from package `photo_view` v0.9.2: @@ -162,7 +161,7 @@ class _AvesMagnifierState extends State with TickerProviderStateM Stopwatch? _scaleStopwatch; VelocityTracker? _velocityTracker; - var _mayFlingLTRB = const Tuple4(false, false, false, false); + var _mayFlingLTRB = const (false, false, false, false); void onScaleStart(ScaleStartDetails details, bool doubleTap) { final boundaries = scaleBoundaries; @@ -172,7 +171,7 @@ class _AvesMagnifierState extends State with TickerProviderStateM _scaleStopwatch = Stopwatch()..start(); _velocityTracker = VelocityTracker.withKind(_flingPointerKind); - _mayFlingLTRB = const Tuple4(true, true, true, true); + _mayFlingLTRB = const (true, true, true, true); _updateMayFling(); _startScale = scale; @@ -246,9 +245,8 @@ class _AvesMagnifierState extends State with TickerProviderStateM final estimate = _velocityTracker?.getVelocityEstimate(); final onFling = widget.onFling; if (estimate != null && onFling != null) { + final (left, up, right, down) = _mayFlingLTRB; if (_isFlingGesture(estimate, _flingPointerKind, Axis.horizontal)) { - final left = _mayFlingLTRB.item1; - final right = _mayFlingLTRB.item3; if (left ^ right) { if (left) { onFling(AxisDirection.left); @@ -257,8 +255,6 @@ class _AvesMagnifierState extends State with TickerProviderStateM } } } else if (_isFlingGesture(estimate, _flingPointerKind, Axis.vertical)) { - final up = _mayFlingLTRB.item2; - final down = _mayFlingLTRB.item4; if (up ^ down) { if (up) { onFling(AxisDirection.up); @@ -320,11 +316,12 @@ class _AvesMagnifierState extends State with TickerProviderStateM void _updateMayFling() { final xHit = getXEdgeHit(); final yHit = getYEdgeHit(); - _mayFlingLTRB = Tuple4( - _mayFlingLTRB.item1 && xHit.hasHitMin, - _mayFlingLTRB.item2 && yHit.hasHitMin, - _mayFlingLTRB.item3 && xHit.hasHitMax, - _mayFlingLTRB.item4 && yHit.hasHitMax, + final (left, up, right, down) = _mayFlingLTRB; + _mayFlingLTRB = ( + xHit.hasHitMin && left, + yHit.hasHitMin && up, + xHit.hasHitMax && right, + yHit.hasHitMax && down, ); } @@ -448,16 +445,6 @@ class _AvesMagnifierState extends State with TickerProviderStateM child: widget.child, ); - // `Matrix4.scale` uses dynamic typing and can throw `UnimplementedError` on wrong types - final double effectiveScale = (applyScale ? scale : null) ?? 1.0; - child = Transform( - transform: Matrix4.identity() - ..translate(position.dx, position.dy) - ..scale(effectiveScale), - alignment: basePosition, - child: child, - ); - return MagnifierGestureDetector( hitDetector: this, onScaleStart: onScaleStart, @@ -467,17 +454,29 @@ class _AvesMagnifierState extends State with TickerProviderStateM onDoubleTap: onDoubleTap, child: Padding( padding: widget.viewportPadding, - child: LayoutBuilder(builder: (context, constraints) { - controller.setScaleBoundaries((controller.scaleBoundaries ?? ScaleBoundaries.zero).copyWith( - allowOriginalScaleBeyondRange: widget.allowOriginalScaleBeyondRange, - minScale: widget.minScale, - maxScale: widget.maxScale, - initialScale: widget.initialScale, - viewportSize: constraints.biggest, - contentSize: widget.contentSize.isEmpty == false ? widget.contentSize : constraints.biggest, - )); - return child; - }), + child: LayoutBuilder( + builder: (context, constraints) { + final boundaries = (controller.scaleBoundaries ?? ScaleBoundaries.zero).copyWith( + allowOriginalScaleBeyondRange: widget.allowOriginalScaleBeyondRange, + minScale: widget.minScale, + maxScale: widget.maxScale, + initialScale: widget.initialScale, + viewportSize: constraints.biggest, + contentSize: widget.contentSize.isEmpty == false ? widget.contentSize : constraints.biggest, + ); + controller.setScaleBoundaries(boundaries); + + // `Matrix4.scale` uses dynamic typing and can throw `UnimplementedError` on wrong types + final double effectiveScale = (applyScale ? scale : null) ?? 1.0; + return Transform( + transform: Matrix4.identity() + ..translate(position.dx, position.dy) + ..scale(effectiveScale), + alignment: basePosition, + child: child, + ); + }, + ), ), ); }, diff --git a/plugins/aves_magnifier/pubspec.lock b/plugins/aves_magnifier/pubspec.lock index 5f7bbc3f6..38e19b5d2 100644 --- a/plugins/aves_magnifier/pubspec.lock +++ b/plugins/aves_magnifier/pubspec.lock @@ -20,10 +20,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" equatable: dependency: "direct main" description: @@ -41,18 +41,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" + version: "2.0.2" lints: dependency: transitive description: @@ -65,10 +57,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -98,14 +90,6 @@ packages: description: flutter source: sdk version: "0.0.99" - tuple: - dependency: "direct main" - description: - name: tuple - sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" - url: "https://pub.dev" - source: hosted - version: "2.0.1" vector_math: dependency: transitive description: @@ -114,6 +98,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=1.16.0" diff --git a/plugins/aves_magnifier/pubspec.yaml b/plugins/aves_magnifier/pubspec.yaml index cb6f1a9c7..d51f0aa56 100644 --- a/plugins/aves_magnifier/pubspec.yaml +++ b/plugins/aves_magnifier/pubspec.yaml @@ -12,7 +12,6 @@ dependencies: path: ../aves_utils equatable: provider: - tuple: dev_dependencies: flutter_lints: diff --git a/plugins/aves_map/lib/src/zoomed_bounds.dart b/plugins/aves_map/lib/src/zoomed_bounds.dart index d0ccc1d73..a66b3c8f9 100644 --- a/plugins/aves_map/lib/src/zoomed_bounds.dart +++ b/plugins/aves_map/lib/src/zoomed_bounds.dart @@ -23,8 +23,7 @@ class ZoomedBounds extends Equatable { final swPoint = _crs.latLngToPoint(sw, zoom); final nePoint = _crs.latLngToPoint(ne, zoom); // assume no padding around bounds - final projectedCenter = _crs.pointToLatLng((swPoint + nePoint) / 2, zoom); - return projectedCenter ?? GeoUtils.getLatLngCenter([sw, ne]); + return _crs.pointToLatLng((swPoint + nePoint) / 2, zoom); } @override diff --git a/plugins/aves_map/pubspec.lock b/plugins/aves_map/pubspec.lock index a3e069de4..90addde74 100644 --- a/plugins/aves_map/pubspec.lock +++ b/plugins/aves_map/pubspec.lock @@ -36,18 +36,18 @@ packages: dependency: "direct main" description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" custom_rounded_rectangle_border: dependency: "direct main" description: name: custom_rounded_rectangle_border - sha256: "57b7af53da4e8bf4afa5a8393c446e953a81c23dd309f4341cfc38d19ff6f062" + sha256: "3e8ca0c26b8d22d5d3842bab59dfd209995f8e42af7c2eef03da70642c040819" url: "https://pub.dev" source: hosted - version: "0.2.0-nullsafety.0" + version: "0.3.0" equatable: dependency: "direct main" description: @@ -73,26 +73,26 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" flutter_map: dependency: "direct main" description: name: flutter_map - sha256: "52c65a977daae42f9aae6748418dd1535eaf27186e9bac9bf431843082bc75a3" + sha256: "5286f72f87deb132daa1489442d6cc46e986fc105cb727d9ae1b602b35b1d1f3" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.0" http: dependency: transitive description: name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" url: "https://pub.dev" source: hosted - version: "0.13.6" + version: "1.1.0" http_parser: dependency: transitive description: @@ -109,22 +109,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.18.1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" latlong2: dependency: "direct main" description: name: latlong2 - sha256: "08ef7282ba9f76e8495e49e2dc4d653015ac929dce5f92b375a415d30b407ea0" + sha256: "18712164760cee655bc790122b0fd8f3d5b3c36da2cb7bf94b68a197fbb0811b" url: "https://pub.dev" source: hosted - version: "0.8.2" + version: "0.9.0" lints: dependency: transitive description: @@ -145,10 +137,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -234,14 +226,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" - tuple: - dependency: transitive - description: - name: tuple - sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" - url: "https://pub.dev" - source: hosted - version: "2.0.1" typed_data: dependency: transitive description: @@ -266,6 +250,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" wkt_parser: dependency: transitive description: @@ -275,5 +267,5 @@ packages: source: hosted version: "2.0.0" sdks: - dart: ">=3.0.0 <4.0.0" - flutter: ">=3.3.0" + dart: ">=3.1.0-185.0.dev <4.0.0" + flutter: ">=3.10.0" diff --git a/plugins/aves_map/pubspec.yaml b/plugins/aves_map/pubspec.yaml index a6cdca279..9509d443f 100644 --- a/plugins/aves_map/pubspec.yaml +++ b/plugins/aves_map/pubspec.yaml @@ -11,8 +11,7 @@ dependencies: aves_ui: path: ../aves_ui collection: - # TODO TLAD as of 2022/02/22, null safe version is pre-release - custom_rounded_rectangle_border: '>=0.2.0-nullsafety.0' + custom_rounded_rectangle_border: equatable: fluster: flutter_map: diff --git a/plugins/aves_model/lib/aves_model.dart b/plugins/aves_model/lib/aves_model.dart index 30d67e558..bef278c0c 100644 --- a/plugins/aves_model/lib/aves_model.dart +++ b/plugins/aves_model/lib/aves_model.dart @@ -14,7 +14,11 @@ export 'src/editor/enums.dart'; export 'src/entry/base.dart'; export 'src/metadata/enums.dart'; export 'src/metadata/fields.dart'; +export 'src/settings/access.dart'; export 'src/settings/enums.dart'; +export 'src/settings/event.dart'; +export 'src/settings/keys.dart'; +export 'src/settings/store.dart'; export 'src/source/album.dart'; export 'src/source/enums.dart'; export 'src/source/vault.dart'; diff --git a/plugins/aves_model/lib/src/actions/chip.dart b/plugins/aves_model/lib/src/actions/chip.dart index e4cd6789b..46fc4cf10 100644 --- a/plugins/aves_model/lib/src/actions/chip.dart +++ b/plugins/aves_model/lib/src/actions/chip.dart @@ -3,6 +3,8 @@ enum ChipAction { goToCountryPage, goToPlacePage, goToTagPage, + ratingOrGreater, + ratingOrLower, reverse, hide, lockVault, diff --git a/plugins/aves_model/lib/src/entry/base.dart b/plugins/aves_model/lib/src/entry/base.dart index 01966327d..68346d1eb 100644 --- a/plugins/aves_model/lib/src/entry/base.dart +++ b/plugins/aves_model/lib/src/entry/base.dart @@ -9,10 +9,14 @@ mixin AvesEntryBase { int? get pageId; + String? get path; + int? get sizeBytes; int? get durationMillis; + bool get isAnimated; + int get rotationDegrees; Size get displaySize; diff --git a/plugins/aves_model/lib/src/settings/access.dart b/plugins/aves_model/lib/src/settings/access.dart new file mode 100644 index 000000000..3dbf406c8 --- /dev/null +++ b/plugins/aves_model/lib/src/settings/access.dart @@ -0,0 +1,105 @@ +import 'package:aves_model/aves_model.dart'; +import 'package:collection/collection.dart'; + +mixin SettingsAccess { + bool get initialized; + + SettingsStore get store; + + Stream get updateStream; + + void notifyKeyChange(String key, dynamic oldValue, dynamic newValue); + + void notifyListeners(); + + void set(String key, dynamic newValue) { + var oldValue = store.get(key); + if (newValue == null) { + store.remove(key); + } else if (newValue is String) { + oldValue = getString(key); + store.setString(key, newValue); + } else if (newValue is List) { + oldValue = getStringList(key); + store.setStringList(key, newValue); + } else if (newValue is int) { + oldValue = getInt(key); + store.setInt(key, newValue); + } else if (newValue is double) { + oldValue = getDouble(key); + store.setDouble(key, newValue); + } else if (newValue is bool) { + oldValue = getBool(key); + store.setBool(key, newValue); + } + if (oldValue != newValue) { + notifyKeyChange(key, oldValue, newValue); + notifyListeners(); + } + } + + // getters + + bool? getBool(String key) { + try { + return store.getBool(key); + } catch (error) { + // ignore, could be obsolete value of different type + return null; + } + } + + int? getInt(String key) { + try { + return store.getInt(key); + } catch (error) { + // ignore, could be obsolete value of different type + return null; + } + } + + double? getDouble(String key) { + try { + return store.getDouble(key); + } catch (error) { + // ignore, could be obsolete value of different type + return null; + } + } + + String? getString(String key) { + try { + return store.getString(key); + } catch (error) { + // ignore, could be obsolete value of different type + return null; + } + } + + List? getStringList(String key) { + try { + return store.getStringList(key); + } catch (error) { + // ignore, could be obsolete value of different type + return null; + } + } + + T getEnumOrDefault(String key, T defaultValue, Iterable values) { + try { + final valueString = store.getString(key); + for (final v in values) { + if (v.toString() == valueString) { + return v; + } + } + } catch (error) { + // ignore, could be obsolete value of different type + } + return defaultValue; + } + + List getEnumListOrDefault(String key, List defaultValue, Iterable values) { + return store.getStringList(key)?.map((s) => values.firstWhereOrNull((v) => v.toString() == s)).whereNotNull().toList() ?? defaultValue; + } +} diff --git a/plugins/aves_model/lib/src/settings/enums.dart b/plugins/aves_model/lib/src/settings/enums.dart index 3f327feca..fecfac6ca 100644 --- a/plugins/aves_model/lib/src/settings/enums.dart +++ b/plugins/aves_model/lib/src/settings/enums.dart @@ -20,6 +20,8 @@ enum KeepScreenOn { never, videoPlayback, viewerOnly, always } enum MaxBrightness { never, viewerOnly, always } +enum OverlayHistogramStyle { none, rgb, luminance } + enum SlideshowVideoPlayback { skip, playMuted, playWithSound } enum SubtitlePosition { top, bottom } diff --git a/plugins/aves_model/lib/src/settings/event.dart b/plugins/aves_model/lib/src/settings/event.dart new file mode 100644 index 000000000..22b3bfa60 --- /dev/null +++ b/plugins/aves_model/lib/src/settings/event.dart @@ -0,0 +1,11 @@ +import 'package:meta/meta.dart'; + +@immutable +class SettingsChangedEvent { + final String key; + final dynamic oldValue; + final dynamic newValue; + + // old and new values as stored, e.g. `List` for collections + const SettingsChangedEvent(this.key, this.oldValue, this.newValue); +} diff --git a/plugins/aves_model/lib/src/settings/keys.dart b/plugins/aves_model/lib/src/settings/keys.dart new file mode 100644 index 000000000..36f03e422 --- /dev/null +++ b/plugins/aves_model/lib/src/settings/keys.dart @@ -0,0 +1,186 @@ +class SettingKeys { + static bool isInternalKey(String key) => _internalKeys.contains(key) || key.startsWith(_widgetKeyPrefix); + + static const Set _internalKeys = { + hasAcceptedTermsKey, + catalogTimeZoneRawOffsetMillisKey, + searchHistoryKey, + platformAccelerometerRotationKey, + platformTransitionAnimationScaleKey, + topEntryIdsKey, + recentDestinationAlbumsKey, + recentTagsKey, + }; + + static const _widgetKeyPrefix = 'widget_'; + + // app + static const hasAcceptedTermsKey = 'has_accepted_terms'; + static const canUseAnalysisServiceKey = 'can_use_analysis_service'; + static const isInstalledAppAccessAllowedKey = 'is_installed_app_access_allowed'; + static const isErrorReportingAllowedKey = 'is_crashlytics_enabled'; + static const localeKey = 'locale'; + static const catalogTimeZoneRawOffsetMillisKey = 'catalog_time_zone_raw_offset_millis'; + static const tileExtentPrefixKey = 'tile_extent_'; + static const tileLayoutPrefixKey = 'tile_layout_'; + static const entryRenamingPatternKey = 'entry_renaming_pattern'; + static const topEntryIdsKey = 'top_entry_ids'; + static const recentDestinationAlbumsKey = 'recent_destination_albums'; + static const recentTagsKey = 'recent_tags'; + + // display + static const displayRefreshRateModeKey = 'display_refresh_rate_mode'; + static const themeBrightnessKey = 'theme_brightness'; + static const themeColorModeKey = 'theme_color_mode'; + static const enableDynamicColorKey = 'dynamic_color'; + static const enableBlurEffectKey = 'enable_overlay_blur_effect'; + static const maxBrightnessKey = 'max_brightness'; + static const forceTvLayoutKey = 'force_tv_layout'; + + // navigation + static const mustBackTwiceToExitKey = 'must_back_twice_to_exit'; + static const keepScreenOnKey = 'keep_screen_on'; + static const homePageKey = 'home_page'; + static const enableBottomNavigationBarKey = 'show_bottom_navigation_bar'; + static const confirmCreateVaultKey = 'confirm_create_vault'; + static const confirmDeleteForeverKey = 'confirm_delete_forever'; + static const confirmMoveToBinKey = 'confirm_move_to_bin'; + static const confirmMoveUndatedItemsKey = 'confirm_move_undated_items'; + static const confirmAfterMoveToBinKey = 'confirm_after_move_to_bin'; + static const setMetadataDateBeforeFileOpKey = 'set_metadata_date_before_file_op'; + static const drawerTypeBookmarksKey = 'drawer_type_bookmarks'; + static const drawerAlbumBookmarksKey = 'drawer_album_bookmarks'; + static const drawerPageBookmarksKey = 'drawer_page_bookmarks'; + + // collection + static const collectionBurstPatternsKey = 'collection_burst_patterns'; + static const collectionGroupFactorKey = 'collection_group_factor'; + static const collectionSortFactorKey = 'collection_sort_factor'; + static const collectionSortReverseKey = 'collection_sort_reverse'; + static const collectionBrowsingQuickActionsKey = 'collection_browsing_quick_actions'; + static const collectionSelectionQuickActionsKey = 'collection_selection_quick_actions'; + static const showThumbnailFavouriteKey = 'show_thumbnail_favourite'; + static const thumbnailLocationIconKey = 'thumbnail_location_icon'; + static const thumbnailTagIconKey = 'thumbnail_tag_icon'; + static const showThumbnailMotionPhotoKey = 'show_thumbnail_motion_photo'; + static const showThumbnailRatingKey = 'show_thumbnail_rating'; + static const showThumbnailRawKey = 'show_thumbnail_raw'; + static const showThumbnailVideoDurationKey = 'show_thumbnail_video_duration'; + + // filter grids + static const albumGroupFactorKey = 'album_group_factor'; + static const albumSortFactorKey = 'album_sort_factor'; + static const countrySortFactorKey = 'country_sort_factor'; + static const stateSortFactorKey = 'state_sort_factor'; + static const placeSortFactorKey = 'place_sort_factor'; + static const tagSortFactorKey = 'tag_sort_factor'; + static const albumSortReverseKey = 'album_sort_reverse'; + static const countrySortReverseKey = 'country_sort_reverse'; + static const stateSortReverseKey = 'state_sort_reverse'; + static const placeSortReverseKey = 'place_sort_reverse'; + static const tagSortReverseKey = 'tag_sort_reverse'; + static const pinnedFiltersKey = 'pinned_filters'; + static const hiddenFiltersKey = 'hidden_filters'; + static const showAlbumPickQueryKey = 'show_album_pick_query'; + + // viewer + static const viewerQuickActionsKey = 'viewer_quick_actions'; + static const showOverlayOnOpeningKey = 'show_overlay_on_opening'; + static const showOverlayMinimapKey = 'show_overlay_minimap'; + static const overlayHistogramStyleKey = 'show_overlay_histogram'; + static const showOverlayInfoKey = 'show_overlay_info'; + static const showOverlayDescriptionKey = 'show_overlay_description'; + static const showOverlayRatingTagsKey = 'show_overlay_rating_tags'; + static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details'; + static const showOverlayThumbnailPreviewKey = 'show_overlay_thumbnail_preview'; + static const viewerGestureSideTapNextKey = 'viewer_gesture_side_tap_next'; + static const viewerUseCutoutKey = 'viewer_use_cutout'; + static const enableMotionPhotoAutoPlayKey = 'motion_photo_auto_play'; + static const imageBackgroundKey = 'image_background'; + + // video + static const enableVideoHardwareAccelerationKey = 'video_hwaccel_mediacodec'; + static const videoBackgroundModeKey = 'video_background_mode'; + static const videoAutoPlayModeKey = 'video_auto_play_mode'; + static const videoLoopModeKey = 'video_loop'; + static const videoResumptionModeKey = 'video_resumption_mode'; + static const videoControlsKey = 'video_controls'; + static const videoGestureDoubleTapTogglePlayKey = 'video_gesture_double_tap_toggle_play'; + static const videoGestureSideDoubleTapSeekKey = 'video_gesture_side_double_tap_skip'; + static const videoGestureVerticalDragBrightnessVolumeKey = 'video_gesture_vertical_drag_brightness_volume'; + + // subtitles + static const subtitleFontSizeKey = 'subtitle_font_size'; + static const subtitleTextAlignmentKey = 'subtitle_text_alignment'; + static const subtitleTextPositionKey = 'subtitle_text_position'; + static const subtitleShowOutlineKey = 'subtitle_show_outline'; + static const subtitleTextColorKey = 'subtitle_text_color'; + static const subtitleBackgroundColorKey = 'subtitle_background_color'; + + // info + static const infoMapZoomKey = 'info_map_zoom'; + static const coordinateFormatKey = 'coordinates_format'; + static const unitSystemKey = 'unit_system'; + + // tag editor + + static const tagEditorCurrentFilterSectionExpandedKey = 'tag_editor_current_filter_section_expanded'; + static const tagEditorExpandedSectionKey = 'tag_editor_expanded_section'; + + // converter + + static const convertMimeTypeKey = 'convert_mime_type'; + static const convertQualityKey = 'convert_quality'; + static const convertWriteMetadataKey = 'convert_write_metadata'; + + // map + static const mapStyleKey = 'info_map_style'; + static const mapDefaultCenterKey = 'map_default_center'; + + // search + static const saveSearchHistoryKey = 'save_search_history'; + static const searchHistoryKey = 'search_history'; + + // bin + static const enableBinKey = 'enable_bin'; + + // accessibility + static const showPinchGestureAlternativesKey = 'show_pinch_gesture_alternatives'; + static const accessibilityAnimationsKey = 'accessibility_animations'; + static const timeToTakeActionKey = 'time_to_take_action'; + + // file picker + static const filePickerShowHiddenFilesKey = 'file_picker_show_hidden_files'; + + // screen saver + static const screenSaverFillScreenKey = 'screen_saver_fill_screen'; + static const screenSaverAnimatedZoomEffectKey = 'screen_saver_animated_zoom_effect'; + static const screenSaverTransitionKey = 'screen_saver_transition'; + static const screenSaverVideoPlaybackKey = 'screen_saver_video_playback'; + static const screenSaverIntervalKey = 'screen_saver_interval'; + static const screenSaverCollectionFiltersKey = 'screen_saver_collection_filters'; + + // slideshow + static const slideshowRepeatKey = 'slideshow_loop'; + static const slideshowShuffleKey = 'slideshow_shuffle'; + static const slideshowFillScreenKey = 'slideshow_fill_screen'; + static const slideshowAnimatedZoomEffectKey = 'slideshow_animated_zoom_effect'; + static const slideshowTransitionKey = 'slideshow_transition'; + static const slideshowVideoPlaybackKey = 'slideshow_video_playback'; + static const slideshowIntervalKey = 'slideshow_interval'; + + // widget + static const widgetOutlinePrefixKey = '${_widgetKeyPrefix}outline_'; + static const widgetShapePrefixKey = '${_widgetKeyPrefix}shape_'; + static const widgetCollectionFiltersPrefixKey = '${_widgetKeyPrefix}collection_filters_'; + static const widgetOpenPagePrefixKey = '${_widgetKeyPrefix}open_page_'; + static const widgetDisplayedItemPrefixKey = '${_widgetKeyPrefix}displayed_item_'; + static const widgetUriPrefixKey = '${_widgetKeyPrefix}uri_'; + + // platform settings + // cf Android `Settings.System.ACCELEROMETER_ROTATION` + static const platformAccelerometerRotationKey = 'accelerometer_rotation'; + + // cf Android `Settings.Global.TRANSITION_ANIMATION_SCALE` + static const platformTransitionAnimationScaleKey = 'transition_animation_scale'; +} \ No newline at end of file diff --git a/lib/model/settings/store/store.dart b/plugins/aves_model/lib/src/settings/store.dart similarity index 100% rename from lib/model/settings/store/store.dart rename to plugins/aves_model/lib/src/settings/store.dart diff --git a/plugins/aves_model/lib/src/video/keys.dart b/plugins/aves_model/lib/src/video/keys.dart index 9cb9e3fbd..40a64c534 100644 --- a/plugins/aves_model/lib/src/video/keys.dart +++ b/plugins/aves_model/lib/src/video/keys.dart @@ -6,41 +6,66 @@ class Keys { static const androidManufacturer = 'com.android.manufacturer'; static const androidModel = 'com.android.model'; static const androidVersion = 'com.android.version'; + static const avgFrameRate = 'avg_frame_rate'; static const bps = 'bps'; static const bitrate = 'bitrate'; + static const bitsPerRawSample = 'bits_per_raw_sample'; static const byteCount = 'number_of_bytes'; static const channelLayout = 'channel_layout'; + static const chromaLocation = 'chroma_location'; static const codecLevel = 'codec_level'; static const codecName = 'codec_name'; static const codecPixelFormat = 'codec_pixel_format'; static const codecProfileId = 'codec_profile_id'; + static const codecTag = 'codec_tag'; + static const codecTagString = 'codec_tag_string'; + static const codedHeight = 'coded_height'; + static const codedWidth = 'coded_width'; + static const colorPrimaries = 'color_primaries'; + static const colorRange = 'color_range'; + static const colorSpace = 'color_space'; + static const colorTransfer = 'color_transfer'; static const compatibleBrands = 'compatible_brands'; static const creationTime = 'creation_time'; + static const dar = 'display_aspect_ratio'; static const date = 'date'; + static const disposition = 'disposition'; static const duration = 'duration'; static const durationMicros = 'duration_us'; + static const durationTs = 'duration_ts'; static const encoder = 'encoder'; + static const extraDataSize = 'extradata_size'; + static const fieldOrder = 'field_order'; static const filename = 'filename'; static const fpsDen = 'fps_den'; static const fpsNum = 'fps_num'; static const frameCount = 'number_of_frames'; static const handlerName = 'handler_name'; + static const hasBFrames = 'has_b_frames'; static const height = 'height'; static const index = 'index'; + static const isAvc = 'is_avc'; static const language = 'language'; static const location = 'location'; static const majorBrand = 'major_brand'; static const mediaFormat = 'format'; static const mediaType = 'media_type'; static const minorVersion = 'minor_version'; + static const nalLengthSize = 'nal_length_size'; + static const probeScore = 'probe_score'; + static const programCount = 'nb_programs'; static const quicktimeCreationDate = 'com.apple.quicktime.creationdate'; static const quicktimeLocationAccuracyHorizontal = 'com.apple.quicktime.location.accuracy.horizontal'; static const quicktimeLocationIso6709 = 'com.apple.quicktime.location.iso6709'; static const quicktimeMake = 'com.apple.quicktime.make'; static const quicktimeModel = 'com.apple.quicktime.model'; static const quicktimeSoftware = 'com.apple.quicktime.software'; + static const refs = 'refs'; + static const rFrameRate = 'r_frame_rate'; static const rotate = 'rotate'; + static const sampleFormat = 'sample_fmt'; static const sampleRate = 'sample_rate'; + static const sar = 'sample_aspect_ratio'; static const sarDen = 'sar_den'; static const sarNum = 'sar_num'; static const selectedAudioStream = 'audio'; @@ -48,15 +73,21 @@ class Keys { static const selectedVideoStream = 'video'; static const sourceOshash = 'source_oshash'; static const startMicros = 'start_us'; + static const startPts = 'start_pts'; + static const startTime = 'start_time'; static const statisticsTags = '_statistics_tags'; static const statisticsWritingApp = '_statistics_writing_app'; static const statisticsWritingDateUtc = '_statistics_writing_date_utc'; + static const streamCount = 'nb_streams'; static const streams = 'streams'; static const tbrDen = 'tbr_den'; static const tbrNum = 'tbr_num'; + static const segmentCount = 'segment_count'; static const streamType = 'type'; static const title = 'title'; + static const timeBase = 'time_base'; static const track = 'track'; + static const vendorId = 'vendor_id'; static const width = 'width'; static const xiaomiSlowMoment = 'com.xiaomi.slow_moment'; } diff --git a/plugins/aves_model/pubspec.lock b/plugins/aves_model/pubspec.lock index 39823942d..00d9f1996 100644 --- a/plugins/aves_model/pubspec.lock +++ b/plugins/aves_model/pubspec.lock @@ -10,13 +10,13 @@ packages: source: hosted version: "1.3.0" collection: - dependency: transitive + dependency: "direct main" description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" equatable: dependency: "direct main" description: @@ -34,18 +34,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" + version: "2.0.2" lints: dependency: transitive description: @@ -58,10 +50,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: "direct main" description: @@ -83,5 +75,13 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" diff --git a/plugins/aves_model/pubspec.yaml b/plugins/aves_model/pubspec.yaml index d24556f14..fe317bde2 100644 --- a/plugins/aves_model/pubspec.yaml +++ b/plugins/aves_model/pubspec.yaml @@ -8,6 +8,7 @@ environment: dependencies: flutter: sdk: flutter + collection: equatable: meta: diff --git a/plugins/aves_platform_meta/pubspec.lock b/plugins/aves_platform_meta/pubspec.lock index f124f2410..9b578aa54 100644 --- a/plugins/aves_platform_meta/pubspec.lock +++ b/plugins/aves_platform_meta/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" flutter: dependency: "direct main" description: flutter @@ -26,18 +26,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" + version: "2.0.2" lints: dependency: transitive description: @@ -50,10 +42,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -66,10 +58,10 @@ packages: dependency: "direct main" description: name: plugin_platform_interface - sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" sky_engine: dependency: transitive description: flutter @@ -83,5 +75,13 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" diff --git a/plugins/aves_report/pubspec.lock b/plugins/aves_report/pubspec.lock index 36b4ccb1b..ea9f19c12 100644 --- a/plugins/aves_report/pubspec.lock +++ b/plugins/aves_report/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: "direct main" description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" flutter: dependency: "direct main" description: flutter @@ -26,18 +26,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" + version: "2.0.2" lints: dependency: transitive description: @@ -50,10 +42,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -79,10 +71,10 @@ packages: dependency: "direct main" description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" vector_math: dependency: transitive description: @@ -91,5 +83,13 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" diff --git a/plugins/aves_report_console/pubspec.lock b/plugins/aves_report_console/pubspec.lock index 7c92c7b0a..8a681d8cd 100644 --- a/plugins/aves_report_console/pubspec.lock +++ b/plugins/aves_report_console/pubspec.lock @@ -20,10 +20,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" flutter: dependency: "direct main" description: flutter @@ -33,18 +33,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" + version: "2.0.2" lints: dependency: transitive description: @@ -57,10 +49,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -86,10 +78,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" vector_math: dependency: transitive description: @@ -98,5 +90,13 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" diff --git a/plugins/aves_report_crashlytics/pubspec.lock b/plugins/aves_report_crashlytics/pubspec.lock index a93d35491..af0cad0d9 100644 --- a/plugins/aves_report_crashlytics/pubspec.lock +++ b/plugins/aves_report_crashlytics/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "9ebe81588e666f7e2b21309f2b5653bd9642d7f27fd0a6894278d2ff40cb9481" + sha256: "1a5e13736d59235ce0139621b4bbe29bc89839e202409081bc667eb3cd20674c" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.5" async: dependency: transitive description: @@ -52,10 +52,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" fake_async: dependency: transitive description: @@ -68,10 +68,10 @@ packages: dependency: "direct main" description: name: firebase_core - sha256: e9b36b391690cf329c6fb1de220045e97c13784c303820cd33962319580a56c6 + sha256: c78132175edda4bc532a71e01a32964e4b4fcf53de7853a422d96dac3725f389 url: "https://pub.dev" source: hosted - version: "2.13.1" + version: "2.15.1" firebase_core_platform_interface: dependency: transitive description: @@ -84,26 +84,26 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: "8c0f4c87d20e2d001a5915df238c1f9c88704231f591324205f5a5d2a7740a45" + sha256: "4cf4d2161530332ddc3c562f19823fb897ff37a9a774090d28df99f47370e973" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.7.0" firebase_crashlytics: dependency: "direct main" description: name: firebase_crashlytics - sha256: "603f23a74995c193cae89a784b8da529b1e6a91c03bc63f885f36456e9e867a0" + sha256: fd9e1a1cb7cce3f9dd2358d8363d235f25f056981e23a333db1e57eca693913f url: "https://pub.dev" source: hosted - version: "3.3.2" + version: "3.3.5" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface - sha256: cefeeeb98abdb9d848581603bd1e33a2a8e6d3ed937586cb84437e606049071b + sha256: "0d19ef23cf7a917a357d2eb1807338ec536ec3232e729ebd769f5bb2aba9e085" url: "https://pub.dev" source: hosted - version: "3.6.2" + version: "3.6.5" flutter: dependency: "direct main" description: flutter @@ -113,10 +113,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" flutter_test: dependency: transitive description: flutter @@ -147,18 +147,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -179,10 +179,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" sky_engine: dependency: transitive description: flutter @@ -192,10 +192,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -232,10 +232,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" vector_math: dependency: transitive description: @@ -244,6 +244,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.3.0" diff --git a/plugins/aves_screen_state/pubspec.lock b/plugins/aves_screen_state/pubspec.lock index f124f2410..9b578aa54 100644 --- a/plugins/aves_screen_state/pubspec.lock +++ b/plugins/aves_screen_state/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" flutter: dependency: "direct main" description: flutter @@ -26,18 +26,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" + version: "2.0.2" lints: dependency: transitive description: @@ -50,10 +42,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -66,10 +58,10 @@ packages: dependency: "direct main" description: name: plugin_platform_interface - sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" sky_engine: dependency: transitive description: flutter @@ -83,5 +75,13 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" diff --git a/plugins/aves_services/pubspec.lock b/plugins/aves_services/pubspec.lock index 2855f37c9..f188d293b 100644 --- a/plugins/aves_services/pubspec.lock +++ b/plugins/aves_services/pubspec.lock @@ -43,18 +43,18 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" custom_rounded_rectangle_border: dependency: transitive description: name: custom_rounded_rectangle_border - sha256: "57b7af53da4e8bf4afa5a8393c446e953a81c23dd309f4341cfc38d19ff6f062" + sha256: "3e8ca0c26b8d22d5d3842bab59dfd209995f8e42af7c2eef03da70642c040819" url: "https://pub.dev" source: hosted - version: "0.2.0-nullsafety.0" + version: "0.3.0" equatable: dependency: transitive description: @@ -80,26 +80,26 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" flutter_map: dependency: transitive description: name: flutter_map - sha256: "52c65a977daae42f9aae6748418dd1535eaf27186e9bac9bf431843082bc75a3" + sha256: "5286f72f87deb132daa1489442d6cc46e986fc105cb727d9ae1b602b35b1d1f3" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.0" http: dependency: transitive description: name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" url: "https://pub.dev" source: hosted - version: "0.13.6" + version: "1.1.0" http_parser: dependency: transitive description: @@ -116,22 +116,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.18.1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" latlong2: dependency: "direct main" description: name: latlong2 - sha256: "08ef7282ba9f76e8495e49e2dc4d653015ac929dce5f92b375a415d30b407ea0" + sha256: "18712164760cee655bc790122b0fd8f3d5b3c36da2cb7bf94b68a197fbb0811b" url: "https://pub.dev" source: hosted - version: "0.8.2" + version: "0.9.0" lints: dependency: transitive description: @@ -152,10 +144,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -241,14 +233,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" - tuple: - dependency: transitive - description: - name: tuple - sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" - url: "https://pub.dev" - source: hosted - version: "2.0.1" typed_data: dependency: transitive description: @@ -273,6 +257,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" wkt_parser: dependency: transitive description: @@ -282,5 +274,5 @@ packages: source: hosted version: "2.0.0" sdks: - dart: ">=3.0.0 <4.0.0" - flutter: ">=3.3.0" + dart: ">=3.1.0-185.0.dev <4.0.0" + flutter: ">=3.10.0" diff --git a/plugins/aves_services_google/lib/src/map.dart b/plugins/aves_services_google/lib/src/map.dart index d80565c08..d78377e4f 100644 --- a/plugins/aves_services_google/lib/src/map.dart +++ b/plugins/aves_services_google/lib/src/map.dart @@ -109,15 +109,10 @@ class _EntryGoogleMapState extends State> with WidgetsBindi @override void didChangeAppLifecycleState(AppLifecycleState state) { - switch (state) { - case AppLifecycleState.inactive: - case AppLifecycleState.paused: - case AppLifecycleState.detached: - break; - case AppLifecycleState.resumed: - // workaround for blank map when resuming app - // cf https://github.com/flutter/flutter/issues/40284 - _serviceMapController?.setMapStyle(null); + if (state == AppLifecycleState.resumed) { + // workaround for blank map when resuming app + // cf https://github.com/flutter/flutter/issues/40284 + _serviceMapController?.setMapStyle(null); } } diff --git a/plugins/aves_services_google/pubspec.lock b/plugins/aves_services_google/pubspec.lock index 0c9a30818..c5d2257bc 100644 --- a/plugins/aves_services_google/pubspec.lock +++ b/plugins/aves_services_google/pubspec.lock @@ -50,26 +50,34 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" + csslib: + dependency: transitive + description: + name: csslib + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + url: "https://pub.dev" + source: hosted + version: "1.0.0" custom_rounded_rectangle_border: dependency: transitive description: name: custom_rounded_rectangle_border - sha256: "57b7af53da4e8bf4afa5a8393c446e953a81c23dd309f4341cfc38d19ff6f062" + sha256: "3e8ca0c26b8d22d5d3842bab59dfd209995f8e42af7c2eef03da70642c040819" url: "https://pub.dev" source: hosted - version: "0.2.0-nullsafety.0" + version: "0.3.0" device_info_plus: dependency: "direct main" description: name: device_info_plus - sha256: "2c35b6d1682b028e42d07b3aee4b98fa62996c10bc12cb651ec856a80d6a761b" + sha256: "86add5ef97215562d2e090535b0a16f197902b10c369c558a100e74ea06e8659" url: "https://pub.dev" source: hosted - version: "9.0.2" + version: "9.0.3" device_info_plus_platform_interface: dependency: transitive description: @@ -90,10 +98,10 @@ packages: dependency: transitive description: name: ffi - sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.1.0" file: dependency: transitive description: @@ -119,18 +127,18 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" flutter_map: dependency: transitive description: name: flutter_map - sha256: "52c65a977daae42f9aae6748418dd1535eaf27186e9bac9bf431843082bc75a3" + sha256: "5286f72f87deb132daa1489442d6cc46e986fc105cb727d9ae1b602b35b1d1f3" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.0" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -168,22 +176,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + google_maps: + dependency: transitive + description: + name: google_maps + sha256: "555d5d736339b0478e821167ac521c810d7b51c3b2734e6802a9f046b64ea37a" + url: "https://pub.dev" + source: hosted + version: "6.3.0" google_maps_flutter: dependency: "direct main" description: name: google_maps_flutter - sha256: abefcb1e5e5c96bdd8084939dda555257af272c7972902ca46d5631092c1df68 + sha256: c290921cd1750b5ede99c82dcaa84740da86278e6ed0f83ad29752b29a8552c6 url: "https://pub.dev" source: hosted - version: "2.2.8" + version: "2.4.0" google_maps_flutter_android: dependency: "direct main" description: name: google_maps_flutter_android - sha256: "9512c862df77c1f0fa5f445513dd3c57f5996f0a809dccb74e54b690ee4e3a0f" + sha256: fb3e52f94d0736f6ee8bcdef290f8eab9325376d676f61303347c10ccf15379c url: "https://pub.dev" source: hosted - version: "2.4.15" + version: "2.4.16" google_maps_flutter_ios: dependency: transitive description: @@ -196,18 +212,34 @@ packages: dependency: "direct main" description: name: google_maps_flutter_platform_interface - sha256: "308f0af138fa78e8224d598d46ca182673874d0ef4d754b7157c073b5b4b8e0d" + sha256: b363e9a1ef7d063fb21ec8eef5a450db4b0500cc39712c9410b5cc64013d6fc6 url: "https://pub.dev" source: hosted - version: "2.2.7" + version: "2.4.0" + google_maps_flutter_web: + dependency: transitive + description: + name: google_maps_flutter_web + sha256: "229391997f216d37c76bfc10d9b5837a1dfeb98c4b4063c605016382e5bcd910" + url: "https://pub.dev" + source: hosted + version: "0.5.3" + html: + dependency: transitive + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.dev" + source: hosted + version: "0.15.4" http: dependency: transitive description: name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" url: "https://pub.dev" source: hosted - version: "0.13.6" + version: "1.1.0" http_parser: dependency: transitive description: @@ -232,14 +264,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + js_wrapping: + dependency: transitive + description: + name: js_wrapping + sha256: e385980f7c76a8c1c9a560dfb623b890975841542471eade630b2871d243851c + url: "https://pub.dev" + source: hosted + version: "0.7.4" latlong2: dependency: "direct main" description: name: latlong2 - sha256: "08ef7282ba9f76e8495e49e2dc4d653015ac929dce5f92b375a415d30b407ea0" + sha256: "18712164760cee655bc790122b0fd8f3d5b3c36da2cb7bf94b68a197fbb0811b" url: "https://pub.dev" source: hosted - version: "0.8.2" + version: "0.9.0" lints: dependency: transitive description: @@ -260,10 +300,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -300,10 +340,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" polylabel: dependency: transitive description: @@ -328,6 +368,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.5" + sanitize_html: + dependency: transitive + description: + name: sanitize_html + sha256: "0a445f19bbaa196f5a4f93461aa066b94e6e025622eb1e9bc77872a5e25233a5" + url: "https://pub.dev" + source: hosted + version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -365,14 +413,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" - tuple: - dependency: transitive - description: - name: tuple - sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" - url: "https://pub.dev" - source: hosted - version: "2.0.1" typed_data: dependency: transitive description: @@ -397,14 +437,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" win32: dependency: transitive description: name: win32 - sha256: "7dacfda1edcca378031db9905ad7d7bd56b29fd1a90b0908b71a52a12c41e36b" + sha256: f2add6fa510d3ae152903412227bda57d0d5a8da61d2c39c1fb022c9429a41c0 url: "https://pub.dev" source: hosted - version: "5.0.3" + version: "5.0.6" win32_registry: dependency: transitive description: @@ -422,5 +470,5 @@ packages: source: hosted version: "2.0.0" sdks: - dart: ">=3.0.0 <4.0.0" - flutter: ">=3.3.0" + dart: ">=3.1.0-185.0.dev <4.0.0" + flutter: ">=3.10.0" diff --git a/plugins/aves_services_huawei/pubspec.lock b/plugins/aves_services_huawei/pubspec.lock index 4460f1bde..89474be4c 100644 --- a/plugins/aves_services_huawei/pubspec.lock +++ b/plugins/aves_services_huawei/pubspec.lock @@ -57,18 +57,18 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" custom_rounded_rectangle_border: dependency: transitive description: name: custom_rounded_rectangle_border - sha256: "57b7af53da4e8bf4afa5a8393c446e953a81c23dd309f4341cfc38d19ff6f062" + sha256: "3e8ca0c26b8d22d5d3842bab59dfd209995f8e42af7c2eef03da70642c040819" url: "https://pub.dev" source: hosted - version: "0.2.0-nullsafety.0" + version: "0.3.0" equatable: dependency: transitive description: @@ -94,26 +94,26 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" flutter_map: dependency: transitive description: name: flutter_map - sha256: "52c65a977daae42f9aae6748418dd1535eaf27186e9bac9bf431843082bc75a3" + sha256: "5286f72f87deb132daa1489442d6cc46e986fc105cb727d9ae1b602b35b1d1f3" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.0" http: dependency: transitive description: name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" url: "https://pub.dev" source: hosted - version: "0.13.6" + version: "1.1.0" http_parser: dependency: transitive description: @@ -127,7 +127,7 @@ packages: description: path: flutter-hms-availability ref: agp8-compat - resolved-ref: d861f98fcfbd770420594f92d099ea3128c840c2 + resolved-ref: "031b7aa2553dfb71c2791c918f535b5d4e6a82be" url: "https://github.com/deckerst/hms-flutter-plugin.git" source: git version: "6.6.0+300" @@ -136,10 +136,10 @@ packages: description: path: flutter-hms-map ref: agp8-compat - resolved-ref: d861f98fcfbd770420594f92d099ea3128c840c2 + resolved-ref: "031b7aa2553dfb71c2791c918f535b5d4e6a82be" url: "https://github.com/deckerst/hms-flutter-plugin.git" source: git - version: "6.9.0+300" + version: "6.11.0+304" intl: dependency: transitive description: @@ -148,22 +148,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.18.1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" latlong2: dependency: "direct main" description: name: latlong2 - sha256: "08ef7282ba9f76e8495e49e2dc4d653015ac929dce5f92b375a415d30b407ea0" + sha256: "18712164760cee655bc790122b0fd8f3d5b3c36da2cb7bf94b68a197fbb0811b" url: "https://pub.dev" source: hosted - version: "0.8.2" + version: "0.9.0" lints: dependency: transitive description: @@ -184,10 +176,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -224,10 +216,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" polylabel: dependency: transitive description: @@ -281,14 +273,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" - tuple: - dependency: transitive - description: - name: tuple - sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" - url: "https://pub.dev" - source: hosted - version: "2.0.1" typed_data: dependency: transitive description: @@ -313,6 +297,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" wkt_parser: dependency: transitive description: @@ -322,5 +314,5 @@ packages: source: hosted version: "2.0.0" sdks: - dart: ">=3.0.0 <4.0.0" - flutter: ">=3.3.0" + dart: ">=3.1.0-185.0.dev <4.0.0" + flutter: ">=3.10.0" diff --git a/plugins/aves_services_none/pubspec.lock b/plugins/aves_services_none/pubspec.lock index c5c70e4c5..ffca3df40 100644 --- a/plugins/aves_services_none/pubspec.lock +++ b/plugins/aves_services_none/pubspec.lock @@ -50,18 +50,18 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" custom_rounded_rectangle_border: dependency: transitive description: name: custom_rounded_rectangle_border - sha256: "57b7af53da4e8bf4afa5a8393c446e953a81c23dd309f4341cfc38d19ff6f062" + sha256: "3e8ca0c26b8d22d5d3842bab59dfd209995f8e42af7c2eef03da70642c040819" url: "https://pub.dev" source: hosted - version: "0.2.0-nullsafety.0" + version: "0.3.0" equatable: dependency: transitive description: @@ -87,26 +87,26 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" flutter_map: dependency: transitive description: name: flutter_map - sha256: "52c65a977daae42f9aae6748418dd1535eaf27186e9bac9bf431843082bc75a3" + sha256: "5286f72f87deb132daa1489442d6cc46e986fc105cb727d9ae1b602b35b1d1f3" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.0" http: dependency: transitive description: name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" url: "https://pub.dev" source: hosted - version: "0.13.6" + version: "1.1.0" http_parser: dependency: transitive description: @@ -123,22 +123,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.18.1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" latlong2: dependency: "direct main" description: name: latlong2 - sha256: "08ef7282ba9f76e8495e49e2dc4d653015ac929dce5f92b375a415d30b407ea0" + sha256: "18712164760cee655bc790122b0fd8f3d5b3c36da2cb7bf94b68a197fbb0811b" url: "https://pub.dev" source: hosted - version: "0.8.2" + version: "0.9.0" lints: dependency: transitive description: @@ -159,10 +151,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -248,14 +240,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" - tuple: - dependency: transitive - description: - name: tuple - sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" - url: "https://pub.dev" - source: hosted - version: "2.0.1" typed_data: dependency: transitive description: @@ -280,6 +264,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" wkt_parser: dependency: transitive description: @@ -289,5 +281,5 @@ packages: source: hosted version: "2.0.0" sdks: - dart: ">=3.0.0 <4.0.0" - flutter: ">=3.3.0" + dart: ">=3.1.0-185.0.dev <4.0.0" + flutter: ">=3.10.0" diff --git a/plugins/aves_ui/pubspec.lock b/plugins/aves_ui/pubspec.lock index e352be627..07f725ddc 100644 --- a/plugins/aves_ui/pubspec.lock +++ b/plugins/aves_ui/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" flutter: dependency: "direct main" description: flutter @@ -26,18 +26,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" + version: "2.0.2" lints: dependency: transitive description: @@ -50,10 +42,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -75,5 +67,13 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" diff --git a/plugins/aves_utils/lib/aves_utils.dart b/plugins/aves_utils/lib/aves_utils.dart index d41bcfd2b..c2f027c4e 100644 --- a/plugins/aves_utils/lib/aves_utils.dart +++ b/plugins/aves_utils/lib/aves_utils.dart @@ -1,5 +1,6 @@ library aves_utils; export 'src/change_notifier.dart'; +export 'src/colors.dart'; export 'src/optional_event_channel.dart'; export 'src/vector_utils.dart'; diff --git a/lib/utils/colors.dart b/plugins/aves_utils/lib/src/colors.dart similarity index 100% rename from lib/utils/colors.dart rename to plugins/aves_utils/lib/src/colors.dart diff --git a/plugins/aves_utils/pubspec.lock b/plugins/aves_utils/pubspec.lock index 71c318804..a532a54bc 100644 --- a/plugins/aves_utils/pubspec.lock +++ b/plugins/aves_utils/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" flutter: dependency: "direct main" description: flutter @@ -26,18 +26,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" + version: "2.0.2" lints: dependency: transitive description: @@ -50,10 +42,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -75,5 +67,13 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" diff --git a/plugins/aves_video/lib/aves_video.dart b/plugins/aves_video/lib/aves_video.dart index 6c308814d..68ff7c235 100644 --- a/plugins/aves_video/lib/aves_video.dart +++ b/plugins/aves_video/lib/aves_video.dart @@ -1,4 +1,8 @@ library aves_video; export 'src/controller.dart'; +export 'src/metadata.dart'; +export 'src/settings/subtitles.dart'; +export 'src/settings/video.dart'; export 'src/stream.dart'; +export 'src/video_loop_mode.dart'; diff --git a/plugins/aves_video/lib/src/controller.dart b/plugins/aves_video/lib/src/controller.dart index 3d658ecb8..7fb227629 100644 --- a/plugins/aves_video/lib/src/controller.dart +++ b/plugins/aves_video/lib/src/controller.dart @@ -1,19 +1,34 @@ import 'dart:async'; import 'package:aves_model/aves_model.dart'; -import 'package:aves_video/src/stream.dart'; +import 'package:aves_video/aves_video.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; +abstract class AvesVideoControllerFactory { + void init(); + + AvesVideoController buildController( + AvesEntryBase entry, { + required PlaybackStateHandler playbackStateHandler, + required VideoSettings settings, + }); +} + abstract class AvesVideoController { final AvesEntryBase _entry; final PlaybackStateHandler playbackStateHandler; + final VideoSettings settings; AvesEntryBase get entry => _entry; static const resumeTimeSaveMinDuration = Duration(minutes: 2); - AvesVideoController(AvesEntryBase entry, {required this.playbackStateHandler}) : _entry = entry { + AvesVideoController( + AvesEntryBase entry, { + required this.playbackStateHandler, + required this.settings, + }) : _entry = entry { entry.visualChangeNotifier.addListener(onVisualChanged); } @@ -38,7 +53,7 @@ abstract class AvesVideoController { Future seekTo(int targetMillis); - Future seekToProgress(double progress) => seekTo((duration * progress).toInt()); + Future seekToProgress(double progress) => seekTo((duration * progress.clamp(0, 1)).toInt()); Listenable get playCompletedListenable; @@ -52,6 +67,18 @@ abstract class AvesVideoController { bool get isReady; + Future get untilReady { + if (isReady) return Future.value(); + + final completer = Completer(); + late StreamSubscription sub; + sub = statusStream.where((_) => isReady).listen((_) { + sub.cancel(); + completer.complete(); + }); + return completer.future; + } + bool get isPlaying => status == VideoStatus.playing; int get duration; @@ -75,7 +102,7 @@ abstract class AvesVideoController { ValueNotifier get canSelectStreamNotifier; - ValueNotifier get sarNotifier; + ValueNotifier get sarNotifier; bool get isMuted; @@ -93,7 +120,7 @@ abstract class AvesVideoController { List get streams; - Future captureFrame(); + Future captureFrame(); Future mute(bool muted); diff --git a/plugins/aves_video/lib/src/metadata.dart b/plugins/aves_video/lib/src/metadata.dart new file mode 100644 index 000000000..21f699059 --- /dev/null +++ b/plugins/aves_video/lib/src/metadata.dart @@ -0,0 +1,9 @@ +import 'dart:async'; + +import 'package:aves_model/aves_model.dart'; + +abstract class AvesVideoMetadataFetcher { + void init(); + + Future getMetadata(AvesEntryBase entry); +} diff --git a/plugins/aves_video/lib/src/settings/defaults.dart b/plugins/aves_video/lib/src/settings/defaults.dart new file mode 100644 index 000000000..fcdc9b0df --- /dev/null +++ b/plugins/aves_video/lib/src/settings/defaults.dart @@ -0,0 +1,26 @@ +import 'dart:ui'; + +import 'package:aves_model/aves_model.dart'; +import 'package:aves_utils/aves_utils.dart'; + +class SettingsDefaults { + // 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; +} diff --git a/plugins/aves_video/lib/src/settings/subtitles.dart b/plugins/aves_video/lib/src/settings/subtitles.dart new file mode 100644 index 000000000..c001055c8 --- /dev/null +++ b/plugins/aves_video/lib/src/settings/subtitles.dart @@ -0,0 +1,30 @@ +import 'dart:ui'; + +import 'package:aves_model/aves_model.dart'; +import 'package:aves_video/src/settings/defaults.dart'; + +mixin SubtitlesSettings on SettingsAccess { + double get subtitleFontSize => getDouble(SettingKeys.subtitleFontSizeKey) ?? SettingsDefaults.subtitleFontSize; + + set subtitleFontSize(double newValue) => set(SettingKeys.subtitleFontSizeKey, newValue); + + TextAlign get subtitleTextAlignment => getEnumOrDefault(SettingKeys.subtitleTextAlignmentKey, SettingsDefaults.subtitleTextAlignment, TextAlign.values); + + set subtitleTextAlignment(TextAlign newValue) => set(SettingKeys.subtitleTextAlignmentKey, newValue.toString()); + + SubtitlePosition get subtitleTextPosition => getEnumOrDefault(SettingKeys.subtitleTextPositionKey, SettingsDefaults.subtitleTextPosition, SubtitlePosition.values); + + set subtitleTextPosition(SubtitlePosition newValue) => set(SettingKeys.subtitleTextPositionKey, newValue.toString()); + + bool get subtitleShowOutline => getBool(SettingKeys.subtitleShowOutlineKey) ?? SettingsDefaults.subtitleShowOutline; + + set subtitleShowOutline(bool newValue) => set(SettingKeys.subtitleShowOutlineKey, newValue); + + Color get subtitleTextColor => Color(getInt(SettingKeys.subtitleTextColorKey) ?? SettingsDefaults.subtitleTextColor.value); + + set subtitleTextColor(Color newValue) => set(SettingKeys.subtitleTextColorKey, newValue.value); + + Color get subtitleBackgroundColor => Color(getInt(SettingKeys.subtitleBackgroundColorKey) ?? SettingsDefaults.subtitleBackgroundColor.value); + + set subtitleBackgroundColor(Color newValue) => set(SettingKeys.subtitleBackgroundColorKey, newValue.value); +} diff --git a/plugins/aves_video/lib/src/settings/video.dart b/plugins/aves_video/lib/src/settings/video.dart new file mode 100644 index 000000000..11aa335ff --- /dev/null +++ b/plugins/aves_video/lib/src/settings/video.dart @@ -0,0 +1,40 @@ +import 'package:aves_model/aves_model.dart'; +import 'package:aves_video/src/settings/defaults.dart'; + +mixin VideoSettings on SettingsAccess { + bool get enableVideoHardwareAcceleration => getBool(SettingKeys.enableVideoHardwareAccelerationKey) ?? SettingsDefaults.enableVideoHardwareAcceleration; + + set enableVideoHardwareAcceleration(bool newValue) => set(SettingKeys.enableVideoHardwareAccelerationKey, newValue); + + VideoAutoPlayMode get videoAutoPlayMode => getEnumOrDefault(SettingKeys.videoAutoPlayModeKey, SettingsDefaults.videoAutoPlayMode, VideoAutoPlayMode.values); + + set videoAutoPlayMode(VideoAutoPlayMode newValue) => set(SettingKeys.videoAutoPlayModeKey, newValue.toString()); + + VideoBackgroundMode get videoBackgroundMode => getEnumOrDefault(SettingKeys.videoBackgroundModeKey, SettingsDefaults.videoBackgroundMode, VideoBackgroundMode.values); + + set videoBackgroundMode(VideoBackgroundMode newValue) => set(SettingKeys.videoBackgroundModeKey, newValue.toString()); + + VideoLoopMode get videoLoopMode => getEnumOrDefault(SettingKeys.videoLoopModeKey, SettingsDefaults.videoLoopMode, VideoLoopMode.values); + + set videoLoopMode(VideoLoopMode newValue) => set(SettingKeys.videoLoopModeKey, newValue.toString()); + + VideoResumptionMode get videoResumptionMode => getEnumOrDefault(SettingKeys.videoResumptionModeKey, SettingsDefaults.videoResumptionMode, VideoResumptionMode.values); + + set videoResumptionMode(VideoResumptionMode newValue) => set(SettingKeys.videoResumptionModeKey, newValue.toString()); + + VideoControls get videoControls => getEnumOrDefault(SettingKeys.videoControlsKey, SettingsDefaults.videoControls, VideoControls.values); + + set videoControls(VideoControls newValue) => set(SettingKeys.videoControlsKey, newValue.toString()); + + bool get videoGestureDoubleTapTogglePlay => getBool(SettingKeys.videoGestureDoubleTapTogglePlayKey) ?? SettingsDefaults.videoGestureDoubleTapTogglePlay; + + set videoGestureDoubleTapTogglePlay(bool newValue) => set(SettingKeys.videoGestureDoubleTapTogglePlayKey, newValue); + + bool get videoGestureSideDoubleTapSeek => getBool(SettingKeys.videoGestureSideDoubleTapSeekKey) ?? SettingsDefaults.videoGestureSideDoubleTapSeek; + + set videoGestureSideDoubleTapSeek(bool newValue) => set(SettingKeys.videoGestureSideDoubleTapSeekKey, newValue); + + bool get videoGestureVerticalDragBrightnessVolume => getBool(SettingKeys.videoGestureVerticalDragBrightnessVolumeKey) ?? SettingsDefaults.videoGestureVerticalDragBrightnessVolume; + + set videoGestureVerticalDragBrightnessVolume(bool newValue) => set(SettingKeys.videoGestureVerticalDragBrightnessVolumeKey, newValue); +} diff --git a/plugins/aves_video/lib/src/stream.dart b/plugins/aves_video/lib/src/stream.dart index 93c4bb05b..1d7548869 100644 --- a/plugins/aves_video/lib/src/stream.dart +++ b/plugins/aves_video/lib/src/stream.dart @@ -18,5 +18,5 @@ class MediaStreamSummary { }); @override - String toString() => '$runtimeType#${shortHash(this)}{type: type, index: $index, codecName: $codecName, language: $language, title: $title, width: $width, height: $height}'; + String toString() => '$runtimeType#${shortHash(this)}{type: $type, index: $index, codecName: $codecName, language: $language, title: $title, width: $width, height: $height}'; } diff --git a/lib/model/settings/enums/video_loop_mode.dart b/plugins/aves_video/lib/src/video_loop_mode.dart similarity index 76% rename from lib/model/settings/enums/video_loop_mode.dart rename to plugins/aves_video/lib/src/video_loop_mode.dart index 587f5f815..196bd017d 100644 --- a/lib/model/settings/enums/video_loop_mode.dart +++ b/plugins/aves_video/lib/src/video_loop_mode.dart @@ -3,11 +3,14 @@ import 'package:aves_model/aves_model.dart'; extension ExtraVideoLoopMode on VideoLoopMode { static const shortVideoThreshold = Duration(seconds: 30); - bool shouldLoop(int? durationMillis) { + bool shouldLoop(AvesEntryBase entry) { + if (entry.isAnimated) return true; + switch (this) { case VideoLoopMode.never: return false; case VideoLoopMode.shortOnly: + final durationMillis = entry.durationMillis; return durationMillis != null ? durationMillis < shortVideoThreshold.inMilliseconds : false; case VideoLoopMode.always: return true; diff --git a/plugins/aves_video/pubspec.lock b/plugins/aves_video/pubspec.lock index 091723a3d..8f02fe123 100644 --- a/plugins/aves_video/pubspec.lock +++ b/plugins/aves_video/pubspec.lock @@ -8,6 +8,13 @@ packages: relative: true source: path version: "0.0.1" + aves_utils: + dependency: "direct main" + description: + path: "../aves_utils" + relative: true + source: path + version: "0.0.1" characters: dependency: transitive description: @@ -20,10 +27,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" equatable: dependency: transitive description: @@ -41,18 +48,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" + version: "2.0.2" lints: dependency: transitive description: @@ -65,10 +64,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -90,5 +89,13 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" diff --git a/plugins/aves_video/pubspec.yaml b/plugins/aves_video/pubspec.yaml index 7c477db6e..258cd6ded 100644 --- a/plugins/aves_video/pubspec.yaml +++ b/plugins/aves_video/pubspec.yaml @@ -10,6 +10,8 @@ dependencies: sdk: flutter aves_model: path: ../aves_model + aves_utils: + path: ../aves_utils dev_dependencies: flutter_lints: diff --git a/plugins/aves_video_ffmpeg/.gitignore b/plugins/aves_video_ffmpeg/.gitignore new file mode 100644 index 000000000..28124a571 --- /dev/null +++ b/plugins/aves_video_ffmpeg/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +#/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/plugins/aves_video_ffmpeg/.metadata b/plugins/aves_video_ffmpeg/.metadata new file mode 100644 index 000000000..fa347fc6a --- /dev/null +++ b/plugins/aves_video_ffmpeg/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 + channel: stable + +project_type: package diff --git a/plugins/aves_video_ffmpeg/analysis_options.yaml b/plugins/aves_video_ffmpeg/analysis_options.yaml new file mode 100644 index 000000000..f04c6cf0f --- /dev/null +++ b/plugins/aves_video_ffmpeg/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../analysis_options.yaml diff --git a/plugins/aves_video_ffmpeg/lib/aves_video_ffmpeg.dart b/plugins/aves_video_ffmpeg/lib/aves_video_ffmpeg.dart new file mode 100644 index 000000000..3ca8aa3ca --- /dev/null +++ b/plugins/aves_video_ffmpeg/lib/aves_video_ffmpeg.dart @@ -0,0 +1,3 @@ +library aves_video_ffmpeg; + +export 'src/metadata.dart'; diff --git a/plugins/aves_video_ffmpeg/lib/src/metadata.dart b/plugins/aves_video_ffmpeg/lib/src/metadata.dart new file mode 100644 index 000000000..1c4b4a39c --- /dev/null +++ b/plugins/aves_video_ffmpeg/lib/src/metadata.dart @@ -0,0 +1,146 @@ +import 'package:aves_model/aves_model.dart'; +import 'package:aves_video/aves_video.dart'; +import 'package:ffmpeg_kit_flutter/ffmpeg_kit_config.dart'; +import 'package:ffmpeg_kit_flutter/ffprobe_kit.dart'; +import 'package:flutter/foundation.dart'; + +class FfmpegVideoMetadataFetcher extends AvesVideoMetadataFetcher { + static const chaptersKey = 'chapters'; + static const formatKey = 'format'; + static const streamsKey = 'streams'; + + @override + void init() {} + + @override + Future getMetadata(AvesEntryBase entry) async { + var uri = entry.uri; + if (uri.startsWith('content://')) { + final safUri = await FFmpegKitConfig.getSafParameterForRead(uri); + if (safUri == null) { + debugPrint('failed to get SAF URI for entry=$entry'); + return {}; + } + uri = safUri; + } + + final session = await FFprobeKit.getMediaInformation(uri); + final information = session.getMediaInformation(); + if (information == null) { + final failStackTrace = await session.getFailStackTrace(); + final output = await session.getOutput(); + debugPrint('failed to get video metadata for entry=$entry, failStackTrace=$failStackTrace, output=$output'); + return {}; + } + + final props = information.getAllProperties(); + if (props == null) return {}; + + final chapters = props[chaptersKey]; + if (chapters is List) { + if (chapters.isEmpty) { + props.remove(chaptersKey); + } + } + + final format = props.remove(formatKey); + if (format is Map) { + format.remove(Keys.filename); + format.remove('size'); + _normalizeGroup(format); + props.addAll(format); + } + + final streams = props[streamsKey]; + if (streams is List) { + streams.forEach((stream) { + if (stream is Map) { + _normalizeGroup(stream); + + final fps = stream[Keys.avgFrameRate]; + if (fps is String) { + final parts = fps.split('/'); + if (parts.length == 2) { + final num = int.tryParse(parts[0]); + final den = int.tryParse(parts[1]); + if (num != null && den != null) { + if (den > 0) { + stream[Keys.fpsNum] = num; + stream[Keys.fpsDen] = den; + } + stream.remove(Keys.avgFrameRate); + } + } + } + + final disposition = stream[Keys.disposition]; + if (disposition is Map) { + disposition.removeWhere((key, value) => value == 0); + stream[Keys.disposition] = disposition.keys.join(', '); + } + + final idValue = stream['id']; + if (idValue is String) { + final id = int.tryParse(idValue); + if (id != null) { + stream[Keys.index] = id - 1; + stream.remove('id'); + } + } + + if (stream[Keys.streamType] == 'data') { + stream[Keys.streamType] = MediaStreamTypes.metadata; + } + } + }); + } + return props; + } + + void _normalizeGroup(Map stream) { + void replaceKey(k1, k2) { + final v = stream.remove(k1); + if (v != null) { + stream[k2] = v; + } + } + + replaceKey('bit_rate', Keys.bitrate); + replaceKey('codec_type', Keys.streamType); + replaceKey('format_name', Keys.mediaFormat); + replaceKey('level', Keys.codecLevel); + replaceKey('nb_frames', Keys.frameCount); + replaceKey('pix_fmt', Keys.codecPixelFormat); + replaceKey('profile', Keys.codecProfileId); + + final tags = stream.remove('tags'); + if (tags is Map) { + stream.addAll(tags); + } + + { + Keys.codecProfileId, + Keys.rFrameRate, + 'bits_per_sample', + 'closed_captions', + 'codec_long_name', + 'film_grain', + 'has_b_frames', + 'start_pts', + 'start_time', + 'vendor_id', + }.forEach((key) { + final value = stream[key]; + switch (value) { + case final num v: + if (v == 0) { + stream.remove(key); + } + case final String v: + if (double.tryParse(v) == 0 || v == '0/0' || v == 'unknown' || v == '[0][0][0][0]') { + stream.remove(key); + } + } + }); + } +} diff --git a/plugins/aves_video_ffmpeg/pubspec.lock b/plugins/aves_video_ffmpeg/pubspec.lock new file mode 100644 index 000000000..c40fb1114 --- /dev/null +++ b/plugins/aves_video_ffmpeg/pubspec.lock @@ -0,0 +1,134 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + aves_model: + dependency: "direct main" + description: + path: "../aves_model" + relative: true + source: path + version: "0.0.1" + aves_utils: + dependency: transitive + description: + path: "../aves_utils" + relative: true + source: path + version: "0.0.1" + aves_video: + dependency: "direct main" + description: + path: "../aves_video" + relative: true + source: path + version: "0.0.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + collection: + dependency: transitive + description: + name: collection + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + url: "https://pub.dev" + source: hosted + version: "1.17.2" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + ffmpeg_kit_flutter: + dependency: "direct main" + description: + path: "flutter/flutter" + ref: background-lts + resolved-ref: "1606719442d8ca04827a94dacd8d3d7a17b5526d" + url: "https://github.com/deckerst/ffmpeg-kit.git" + source: git + version: "5.1.0" + ffmpeg_kit_flutter_platform_interface: + dependency: transitive + description: + name: ffmpeg_kit_flutter_platform_interface + sha256: addf046ae44e190ad0101b2fde2ad909a3cd08a2a109f6106d2f7048b7abedee + url: "https://pub.dev" + source: hosted + version: "0.2.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" +sdks: + dart: ">=3.1.0-185.0.dev <4.0.0" + flutter: ">=2.0.0" diff --git a/plugins/aves_video_ffmpeg/pubspec.yaml b/plugins/aves_video_ffmpeg/pubspec.yaml new file mode 100644 index 000000000..0fc06138e --- /dev/null +++ b/plugins/aves_video_ffmpeg/pubspec.yaml @@ -0,0 +1,30 @@ +name: aves_video_ffmpeg +version: 0.0.1 +publish_to: none + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + aves_model: + path: ../aves_model + aves_video: + path: ../aves_video +# ffmpeg_kit_flutter_min: 5.1.0-LTS +# ffmpeg_kit_flutter: +# git: +# url: https://github.com/arthenica/ffmpeg-kit.git +# ref: development-flutter +# path: flutter/flutter + ffmpeg_kit_flutter: + git: + url: https://github.com/deckerst/ffmpeg-kit.git + ref: background-lts + path: flutter/flutter + +dev_dependencies: + flutter_lints: + +flutter: diff --git a/plugins/aves_video_ijk/.gitignore b/plugins/aves_video_ijk/.gitignore new file mode 100644 index 000000000..28124a571 --- /dev/null +++ b/plugins/aves_video_ijk/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +#/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/plugins/aves_video_ijk/.metadata b/plugins/aves_video_ijk/.metadata new file mode 100644 index 000000000..9596faeed --- /dev/null +++ b/plugins/aves_video_ijk/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 796c8ef79279f9c774545b3771238c3098dbefab + channel: stable + +project_type: package diff --git a/plugins/aves_video_ijk/analysis_options.yaml b/plugins/aves_video_ijk/analysis_options.yaml new file mode 100644 index 000000000..f04c6cf0f --- /dev/null +++ b/plugins/aves_video_ijk/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../analysis_options.yaml diff --git a/plugins/aves_video_ijk/lib/aves_video_ijk.dart b/plugins/aves_video_ijk/lib/aves_video_ijk.dart new file mode 100644 index 000000000..4488c8daa --- /dev/null +++ b/plugins/aves_video_ijk/lib/aves_video_ijk.dart @@ -0,0 +1,5 @@ +library aves_video_ijk; + +export 'src/controller.dart'; +export 'src/factory.dart'; +export 'src/metadata.dart'; diff --git a/lib/widgets/viewer/video/fijkplayer.dart b/plugins/aves_video_ijk/lib/src/controller.dart similarity index 90% rename from lib/widgets/viewer/video/fijkplayer.dart rename to plugins/aves_video_ijk/lib/src/controller.dart index 80d65934e..56915091c 100644 --- a/lib/widgets/viewer/video/fijkplayer.dart +++ b/plugins/aves_video_ijk/lib/src/controller.dart @@ -1,7 +1,5 @@ import 'dart:async'; -import 'package:aves/model/settings/enums/video_loop_mode.dart'; -import 'package:aves/model/settings/settings.dart'; import 'package:aves_model/aves_model.dart'; import 'package:aves_utils/aves_utils.dart'; import 'package:aves_video/aves_video.dart'; @@ -10,7 +8,7 @@ import 'package:fijkplayer/fijkplayer.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -class IjkPlayerAvesVideoController extends AvesVideoController { +class IjkVideoController extends AvesVideoController { final EventChannel _eventChannel = const OptionalEventChannel('befovy.com/fijk/event'); late FijkPlayer _instance; @@ -21,7 +19,6 @@ class IjkPlayerAvesVideoController extends AvesVideoController { final StreamController _speedStreamController = StreamController.broadcast(); final AChangeNotifier _completedNotifier = AChangeNotifier(); Offset _macroBlockCrop = Offset.zero; - final List _streams = []; Timer? _initialPlayTimer; double _speed = 1; double _volume = 1; @@ -49,7 +46,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController { final ValueNotifier canSelectStreamNotifier = ValueNotifier(false); @override - final ValueNotifier sarNotifier = ValueNotifier(1); + final ValueNotifier sarNotifier = ValueNotifier(null); Stream get _valueStream => _valueStreamController.stream; @@ -58,9 +55,10 @@ class IjkPlayerAvesVideoController extends AvesVideoController { static const gifLikeBitRateThreshold = 2 << 18; // 512kB/s (4Mb/s) static const captureFrameEnabled = true; - IjkPlayerAvesVideoController( + IjkVideoController( super.entry, { required super.playbackStateHandler, + required super.settings, }) { _instance = FijkPlayer(); _valueStream.map((value) => value.videoRenderStart).firstWhere((v) => v, orElse: () => false).then( @@ -96,8 +94,8 @@ class IjkPlayerAvesVideoController extends AvesVideoController { _subscriptions.add(_instance.onTimedText.listen(_timedTextStreamController.add)); _subscriptions.add(settings.updateStream .where((event) => { - Settings.enableVideoHardwareAccelerationKey, - Settings.videoLoopModeKey, + SettingKeys.enableVideoHardwareAccelerationKey, + SettingKeys.videoLoopModeKey, }.contains(event.key)) .listen((_) => _instance.reset())); } @@ -117,8 +115,8 @@ class IjkPlayerAvesVideoController extends AvesVideoController { _startListening(); } - sarNotifier.value = 1; - _streams.clear(); + sarNotifier.value = null; + streams.clear(); _applyOptions(startMillis); // calling `setDataSource()` with `autoPlay` starts as soon as possible, but often yields initial artifacts @@ -162,7 +160,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController { _macroBlockCrop = Offset(s.width, s.height); } - final loopEnabled = settings.videoLoopMode.shouldLoop(entry.durationMillis); + final loopEnabled = settings.videoLoopMode.shouldLoop(entry); // `fastseek`: enable fast, but inaccurate seeks for some formats // in practice the flag seems ineffective, but harmless too @@ -242,56 +240,6 @@ class IjkPlayerAvesVideoController extends AvesVideoController { return true; } - void _fetchStreams() async { - final mediaInfo = await _instance.getInfo(); - if (!mediaInfo.containsKey(Keys.streams)) return; - - var videoStreamCount = 0, audioStreamCount = 0, textStreamCount = 0; - - _streams.clear(); - final allStreams = (mediaInfo[Keys.streams] as List).cast(); - allStreams.forEach((stream) { - final type = ExtraStreamType.fromTypeString(stream[Keys.streamType]); - if (type != null) { - final width = stream[Keys.width] as int?; - final height = stream[Keys.height] as int?; - _streams.add(MediaStreamSummary( - type: type, - index: stream[Keys.index], - codecName: stream[Keys.codecName], - language: stream[Keys.language], - title: stream[Keys.title], - width: width, - height: height, - )); - switch (type) { - case MediaStreamType.video: - // check width/height to exclude image streams (that are included among video streams) - if (width != null && height != null) { - videoStreamCount++; - } - case MediaStreamType.audio: - audioStreamCount++; - case MediaStreamType.text: - textStreamCount++; - } - } - }); - - canSelectStreamNotifier.value = videoStreamCount > 1 || audioStreamCount > 1 || textStreamCount > 0; - - final selectedVideo = await getSelectedStream(MediaStreamType.video); - if (selectedVideo != null) { - final streamIndex = selectedVideo.index; - final streamInfo = allStreams.firstWhereOrNull((stream) => stream[Keys.index] == streamIndex); - if (streamInfo != null) { - final num = streamInfo[Keys.sarNum] ?? 0; - final den = streamInfo[Keys.sarDen] ?? 0; - sarNotifier.value = (num != 0 ? num : 1) / (den != 0 ? den : 1); - } - } - } - // cf https://developer.android.com/reference/android/media/AudioManager static const int _audioFocusLoss = -1; static const int _audioFocusRequestFailed = 0; @@ -316,7 +264,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController { } void _onValueChanged() { - if (_instance.state == FijkState.prepared && _streams.isEmpty) { + if (_instance.state == FijkState.prepared && streams.isEmpty) { _fetchStreams(); } _valueStreamController.add(_instance.value); @@ -422,41 +370,8 @@ class IjkPlayerAvesVideoController extends AvesVideoController { // TODO TLAD [video] bug: setting speed fails when there is no audio stream or audio is disabled Future _applySpeed() => _instance.setSpeed(speed); - // When a stream is selected, the video accelerates to catch up with it. - // The duration of this acceleration phase depends on the player `min-frames` parameter. - // Calling `seekTo` after stream de/selection is a workaround to: - // 1) prevent video stream acceleration to catch up with audio - // 2) apply timed text stream @override - Future selectStream(MediaStreamType type, MediaStreamSummary? selected) async { - final current = await getSelectedStream(type); - if (current != selected) { - if (selected != null) { - final newIndex = selected.index; - if (newIndex != null) { - await _instance.selectTrack(newIndex); - } - } else if (current != null) { - await _instance.deselectTrack(current.index!); - } - if (type == MediaStreamType.text) { - _timedTextStreamController.add(null); - } - await seekTo(currentPosition); - } - } - - @override - Future getSelectedStream(MediaStreamType type) async { - final currentIndex = await _instance.getSelectedTrack(type.code); - return currentIndex != -1 ? _streams.firstWhereOrNull((v) => v.index == currentIndex) : null; - } - - @override - List get streams => _streams; - - @override - Future captureFrame() { + Future captureFrame() { if (!_instance.value.videoRenderStart) { return Future.error('cannot capture frame when video is not rendered'); } @@ -465,24 +380,27 @@ class IjkPlayerAvesVideoController extends AvesVideoController { @override Widget buildPlayerWidget(BuildContext context) { - return ValueListenableBuilder( - valueListenable: sarNotifier, - builder: (context, sar, child) { - // derive DAR (Display Aspect Ratio) from SAR (Storage Aspect Ratio), if any - // e.g. 960x536 (~16:9) with SAR 4:3 should be displayed as ~2.39:1 - final dar = entry.displayAspectRatio * sar; - return FijkView( - player: _instance, - fit: FijkFit( - sizeFactor: 1.0, - aspectRatio: dar, - alignment: _alignmentForRotation(entry.rotationDegrees), - macroBlockCrop: _macroBlockCrop, - ), - panelBuilder: (player, data, context, viewSize, texturePos) => const SizedBox(), - color: Colors.transparent, - ); - }); + return ValueListenableBuilder( + valueListenable: sarNotifier, + builder: (context, sar, child) { + if (sar == null) return const SizedBox(); + + // derive DAR (Display Aspect Ratio) from SAR (Storage Aspect Ratio), if any + // e.g. 960x536 (~16:9) with SAR 4:3 should be displayed as ~2.39:1 + final dar = entry.displayAspectRatio * sar; + return FijkView( + player: _instance, + fit: FijkFit( + sizeFactor: 1.0, + aspectRatio: dar, + alignment: _alignmentForRotation(entry.rotationDegrees), + macroBlockCrop: _macroBlockCrop, + ), + panelBuilder: (player, data, context, viewSize, texturePos) => const SizedBox(), + color: Colors.transparent, + ); + }, + ); } Alignment _alignmentForRotation(int rotation) { @@ -498,6 +416,93 @@ class IjkPlayerAvesVideoController extends AvesVideoController { return Alignment.topLeft; } } + + // streams (aka tracks) + + final List _streams = []; + + @override + List get streams => _streams; + + void _fetchStreams() async { + final mediaInfo = await _instance.getInfo(); + if (!mediaInfo.containsKey(Keys.streams)) return; + + var videoStreamCount = 0, audioStreamCount = 0, textStreamCount = 0; + + _streams.clear(); + final allStreams = (mediaInfo[Keys.streams] as List).cast(); + allStreams.forEach((stream) { + final type = ExtraStreamType.fromTypeString(stream[Keys.streamType]); + if (type != null) { + final width = stream[Keys.width] as int?; + final height = stream[Keys.height] as int?; + _streams.add(MediaStreamSummary( + type: type, + index: stream[Keys.index], + codecName: stream[Keys.codecName], + language: stream[Keys.language], + title: stream[Keys.title], + width: width, + height: height, + )); + switch (type) { + case MediaStreamType.video: + // check width/height to exclude image streams (that are included among video streams) + if (width != null && height != null) { + videoStreamCount++; + } + case MediaStreamType.audio: + audioStreamCount++; + case MediaStreamType.text: + textStreamCount++; + } + } + }); + + canSelectStreamNotifier.value = videoStreamCount > 1 || audioStreamCount > 1 || textStreamCount > 0; + + final selectedVideo = await getSelectedStream(MediaStreamType.video); + if (selectedVideo != null) { + final streamIndex = selectedVideo.index; + final streamInfo = allStreams.firstWhereOrNull((stream) => stream[Keys.index] == streamIndex); + if (streamInfo != null) { + final num = streamInfo[Keys.sarNum] ?? 0; + final den = streamInfo[Keys.sarDen] ?? 0; + sarNotifier.value = (num != 0 ? num : 1) / (den != 0 ? den : 1); + } + } + } + + @override + Future getSelectedStream(MediaStreamType type) async { + final currentIndex = await _instance.getSelectedTrack(type.code); + return currentIndex != -1 ? _streams.firstWhereOrNull((v) => v.index == currentIndex) : null; + } + + // When a stream is selected, the video accelerates to catch up with it. + // The duration of this acceleration phase depends on the player `min-frames` parameter. + // Calling `seekTo` after stream de/selection is a workaround to: + // 1) prevent video stream acceleration to catch up with audio + // 2) apply timed text stream + @override + Future selectStream(MediaStreamType type, MediaStreamSummary? selected) async { + final current = await getSelectedStream(type); + if (current == selected) return; + + if (selected != null) { + final newIndex = selected.index; + if (newIndex != null) { + await _instance.selectTrack(newIndex); + } + } else if (current != null) { + await _instance.deselectTrack(current.index!); + } + if (type == MediaStreamType.text) { + _timedTextStreamController.add(null); + } + await seekTo(currentPosition); + } } extension ExtraIjkStatus on FijkState { diff --git a/plugins/aves_video_ijk/lib/src/factory.dart b/plugins/aves_video_ijk/lib/src/factory.dart new file mode 100644 index 000000000..c1fcdfca1 --- /dev/null +++ b/plugins/aves_video_ijk/lib/src/factory.dart @@ -0,0 +1,21 @@ +import 'package:aves_model/aves_model.dart'; +import 'package:aves_video/aves_video.dart'; +import 'package:aves_video_ijk/aves_video_ijk.dart'; +import 'package:fijkplayer/fijkplayer.dart'; + +class IjkVideoControllerFactory extends AvesVideoControllerFactory { + @override + void init() => FijkLog.setLevel(FijkLogLevel.Warn); + + @override + AvesVideoController buildController( + AvesEntryBase entry, { + required PlaybackStateHandler playbackStateHandler, + required VideoSettings settings, + }) => + IjkVideoController( + entry, + playbackStateHandler: playbackStateHandler, + settings: settings, + ); +} diff --git a/plugins/aves_video_ijk/lib/src/metadata.dart b/plugins/aves_video_ijk/lib/src/metadata.dart new file mode 100644 index 000000000..5434030ba --- /dev/null +++ b/plugins/aves_video_ijk/lib/src/metadata.dart @@ -0,0 +1,23 @@ +import 'package:aves_model/aves_model.dart'; +import 'package:aves_video/aves_video.dart'; +import 'package:aves_video_ijk/aves_video_ijk.dart'; +import 'package:fijkplayer/fijkplayer.dart'; +import 'package:flutter/foundation.dart'; + +class IjkVideoMetadataFetcher extends AvesVideoMetadataFetcher { + @override + void init() => FijkLog.setLevel(FijkLogLevel.Warn); + + @override + Future getMetadata(AvesEntryBase 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; + } +} diff --git a/plugins/aves_video_ijk/pubspec.lock b/plugins/aves_video_ijk/pubspec.lock new file mode 100644 index 000000000..4233ed92d --- /dev/null +++ b/plugins/aves_video_ijk/pubspec.lock @@ -0,0 +1,117 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + aves_model: + dependency: "direct main" + description: + path: "../aves_model" + relative: true + source: path + version: "0.0.1" + aves_utils: + dependency: "direct main" + description: + path: "../aves_utils" + relative: true + source: path + version: "0.0.1" + aves_video: + dependency: "direct main" + description: + path: "../aves_video" + relative: true + source: path + version: "0.0.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + collection: + dependency: "direct main" + description: + name: collection + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + url: "https://pub.dev" + source: hosted + version: "1.17.2" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + fijkplayer: + dependency: "direct main" + description: + path: "." + ref: aves + resolved-ref: "935a2d86ebf45fbdbaf8b4a0921d5eaea87410d6" + url: "https://github.com/deckerst/fijkplayer.git" + source: git + version: "0.10.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" +sdks: + dart: ">=3.1.0-185.0.dev <4.0.0" diff --git a/plugins/aves_video_ijk/pubspec.yaml b/plugins/aves_video_ijk/pubspec.yaml new file mode 100644 index 000000000..e1cdc9f77 --- /dev/null +++ b/plugins/aves_video_ijk/pubspec.yaml @@ -0,0 +1,26 @@ +name: aves_video_ijk +version: 0.0.1 +publish_to: none + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + aves_model: + path: ../aves_model + aves_video: + path: ../aves_video + aves_utils: + path: ../aves_utils + collection: + fijkplayer: + git: + url: https://github.com/deckerst/fijkplayer.git + ref: aves + +dev_dependencies: + flutter_lints: + +flutter: diff --git a/plugins/aves_video_mpv/.gitignore b/plugins/aves_video_mpv/.gitignore new file mode 100644 index 000000000..28124a571 --- /dev/null +++ b/plugins/aves_video_mpv/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +#/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/plugins/aves_video_mpv/.metadata b/plugins/aves_video_mpv/.metadata new file mode 100644 index 000000000..9596faeed --- /dev/null +++ b/plugins/aves_video_mpv/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 796c8ef79279f9c774545b3771238c3098dbefab + channel: stable + +project_type: package diff --git a/plugins/aves_video_mpv/analysis_options.yaml b/plugins/aves_video_mpv/analysis_options.yaml new file mode 100644 index 000000000..f04c6cf0f --- /dev/null +++ b/plugins/aves_video_mpv/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../analysis_options.yaml diff --git a/plugins/aves_video_mpv/lib/aves_video_mpv.dart b/plugins/aves_video_mpv/lib/aves_video_mpv.dart new file mode 100644 index 000000000..7fb338e98 --- /dev/null +++ b/plugins/aves_video_mpv/lib/aves_video_mpv.dart @@ -0,0 +1,4 @@ +library aves_video_mpv; + +export 'src/controller.dart'; +export 'src/factory.dart'; diff --git a/plugins/aves_video_mpv/lib/src/controller.dart b/plugins/aves_video_mpv/lib/src/controller.dart new file mode 100644 index 000000000..f1f0cbba6 --- /dev/null +++ b/plugins/aves_video_mpv/lib/src/controller.dart @@ -0,0 +1,390 @@ +import 'dart:async'; + +import 'package:aves_model/aves_model.dart'; +import 'package:aves_utils/aves_utils.dart'; +import 'package:aves_video/aves_video.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:media_kit/media_kit.dart'; +import 'package:media_kit_video/media_kit_video.dart'; + +class MpvVideoController extends AvesVideoController { + late Player _instance; + late VideoStatus _status; + bool _firstFrameRendered = false; + final ValueNotifier _controllerNotifier = ValueNotifier(null); + final List _subscriptions = []; + final StreamController _statusStreamController = StreamController.broadcast(); + final StreamController _timedTextStreamController = StreamController.broadcast(); + final AChangeNotifier _completedNotifier = AChangeNotifier(); + + @override + double get minSpeed => .25; + + @override + double get maxSpeed => 4; + + @override + final ValueNotifier canCaptureFrameNotifier = ValueNotifier(true); + + @override + final ValueNotifier canMuteNotifier = ValueNotifier(true); + + @override + final ValueNotifier canSetSpeedNotifier = ValueNotifier(true); + + @override + final ValueNotifier canSelectStreamNotifier = ValueNotifier(false); + + @override + final ValueNotifier sarNotifier = ValueNotifier(null); + + MpvVideoController( + super.entry, { + required super.playbackStateHandler, + required super.settings, + }) { + _status = VideoStatus.idle; + _statusStreamController.add(_status); + + _instance = Player( + configuration: const PlayerConfiguration( + libass: false, + logLevel: MPVLogLevel.warn, + ), + ); + _initController(); + _init(); + + _startListening(); + } + + @override + Future dispose() async { + await super.dispose(); + _stopListening(); + _stopStreamFetchTimer(); + await _statusStreamController.close(); + await _timedTextStreamController.close(); + await _instance.dispose(); + } + + void _startListening() { + _subscriptions.add(statusStream.listen((v) => _status = v)); + _subscriptions.add(_instance.stream.completed.listen((v) { + if (v) { + _statusStreamController.add(VideoStatus.completed); + _completedNotifier.notify(); + } + })); + _subscriptions.add(_instance.stream.playing.listen((v) { + if (status == VideoStatus.idle) return; + _statusStreamController.add(v ? VideoStatus.playing : VideoStatus.paused); + })); + _subscriptions.add(_instance.stream.subtitle.listen((v) => _timedTextStreamController.add(v.isEmpty ? null : v[0]))); + _subscriptions.add(_instance.stream.videoParams.listen((v) => sarNotifier.value = v.par)); + _subscriptions.add(_instance.stream.log.listen((v) => debugPrint('libmpv log: $v'))); + _subscriptions.add(_instance.stream.error.listen((v) => debugPrint('libmpv error: $v'))); + _subscriptions.add(settings.updateStream.where((event) => event.key == SettingKeys.enableVideoHardwareAccelerationKey).listen((_) => _initController())); + _subscriptions.add(settings.updateStream.where((event) => event.key == SettingKeys.videoLoopModeKey).listen((_) => _applyLoop())); + } + + void _stopListening() { + _subscriptions + ..forEach((sub) => sub.cancel()) + ..clear(); + } + + Future _applyLoop() async { + final loopEnabled = settings.videoLoopMode.shouldLoop(entry); + await _instance.setPlaylistMode(loopEnabled ? PlaylistMode.single : PlaylistMode.none); + } + + Future _init({int startMillis = 0}) async { + final playing = _instance.state.playing; + + await _applyLoop(); + await _instance.open(Media(entry.uri), play: playing); + if (startMillis > 0) { + await seekTo(startMillis); + } + + _fetchStreams(); + _statusStreamController.add(_instance.state.playing ? VideoStatus.playing : VideoStatus.paused); + } + + void _initController() { + _firstFrameRendered = false; + _controllerNotifier.value = VideoController( + _instance, + configuration: VideoControllerConfiguration( + enableHardwareAcceleration: settings.enableVideoHardwareAcceleration, + ), + )..waitUntilFirstFrameRendered.then((v) { + _firstFrameRendered = true; + _statusStreamController.add(_status); + }); + } + + @override + void onVisualChanged() => _init(startMillis: currentPosition); + + @override + Future play() async { + await untilReady; + await _instance.play(); + } + + @override + Future pause() => _instance.pause(); + + @override + Future seekTo(int targetMillis) async { + if (!isReady) { + await untilReady; + // When the player gets ready, it can play from the beginning right away, + // but trying to seek then just plays from the start. + // There is no state or hook identifying readiness to seek on start, + // and `PlayerConfiguration.ready` hook is useless. + await Future.delayed(const Duration(milliseconds: 500)); + } + await _instance.seek(Duration(milliseconds: targetMillis)); + } + + @override + Listenable get playCompletedListenable => _completedNotifier; + + @override + VideoStatus get status => _status; + + @override + Stream get statusStream => _statusStreamController.stream; + + @override + Stream get volumeStream => _instance.stream.volume; + + @override + Stream get speedStream => _instance.stream.rate; + + @override + bool get isReady { + switch (_status) { + case VideoStatus.error: + case VideoStatus.idle: + case VideoStatus.initialized: + return false; + case VideoStatus.paused: + case VideoStatus.playing: + case VideoStatus.completed: + return _firstFrameRendered; + } + } + + @override + int get duration => _instance.state.duration.inMilliseconds; + + @override + int get currentPosition => _instance.state.position.inMilliseconds; + + @override + Stream get positionStream => _instance.stream.position.map((pos) => pos.inMilliseconds); + + @override + Stream get timedTextStream => _timedTextStreamController.stream; + + @override + bool get isMuted => _instance.state.volume == 0; + + @override + Future mute(bool muted) => _instance.setVolume(muted ? 0 : 100); + + @override + double get speed => _instance.state.rate; + + @override + set speed(double speed) => _instance.setRate(speed); + + @override + Future captureFrame() => _instance.screenshot(); + + @override + Widget buildPlayerWidget(BuildContext context) { + return ValueListenableBuilder( + valueListenable: sarNotifier, + builder: (context, sar, child) { + if (sar == null) return const SizedBox(); + + // derive DAR (Display Aspect Ratio) from SAR (Storage Aspect Ratio), if any + // e.g. 960x536 (~16:9) with SAR 4:3 should be displayed as ~2.39:1 + final dar = entry.displayAspectRatio * sar; + return ValueListenableBuilder( + valueListenable: _controllerNotifier, + builder: (context, controller, child) { + if (controller == null) return const SizedBox(); + return Video( + controller: controller, + fill: Colors.transparent, + aspectRatio: dar, + controls: NoVideoControls, + wakelock: false, + subtitleViewConfiguration: const SubtitleViewConfiguration( + visible: false, + ), + ); + }); + }, + ); + } + + // streams (aka tracks) + + // `auto` and `no` are the first 2 tracks in the player state track lists + static const int fakeTrackCount = 2; + + Tracks get _tracks => _instance.state.tracks; + + List get _videoTracks => _tracks.video.skip(fakeTrackCount).toList(); + + List get _audioTracks => _tracks.audio.skip(fakeTrackCount).toList(); + + List get _subtitleTracks => _tracks.subtitle.skip(fakeTrackCount).toList(); + + @override + List get streams { + return { + ..._videoTracks.mapIndexed((i, v) => v.toAves(i)), + ..._audioTracks.mapIndexed((i, v) => v.toAves(i)), + ..._subtitleTracks.mapIndexed((i, v) => v.toAves(i)), + }.toList(); + } + + Timer? _streamFetchTimer; + + void _stopStreamFetchTimer() { + _streamFetchTimer?.cancel(); + _streamFetchTimer = null; + } + + void _fetchStreams() { + _stopStreamFetchTimer(); + _streamFetchTimer = Timer.periodic(const Duration(milliseconds: 100), (_) { + if (status != VideoStatus.error) { + if (_videoTracks.isEmpty && _audioTracks.isEmpty) return; + + final videoStreamCount = _videoTracks.length; + final audioStreamCount = _audioTracks.length; + final textStreamCount = _subtitleTracks.length; + canSelectStreamNotifier.value = videoStreamCount > 1 || audioStreamCount > 1 || textStreamCount > 0; + } + _stopStreamFetchTimer(); + }); + } + + @override + Future getSelectedStream(MediaStreamType type) async { + final track = _instance.state.track; + switch (type) { + case MediaStreamType.video: + final video = track.video; + if (video != VideoTrack.no()) { + final index = video == VideoTrack.auto() ? 0 : _videoTracks.indexOf(video); + return video.toAves(index); + } + case MediaStreamType.audio: + final audio = track.audio; + if (audio != AudioTrack.no()) { + final index = audio == AudioTrack.auto() ? 0 : _audioTracks.indexOf(audio); + return audio.toAves(index); + } + case MediaStreamType.text: + final subtitle = track.subtitle; + if (subtitle != SubtitleTrack.no()) { + final index = subtitle == SubtitleTrack.auto() ? 0 : _subtitleTracks.indexOf(subtitle); + return subtitle.toAves(index); + } + } + return null; + } + + @override + Future selectStream(MediaStreamType type, MediaStreamSummary? selected) async { + final current = await getSelectedStream(type); + if (current == selected) return; + + if (selected != null) { + final newIndex = selected.index; + if (newIndex != null) { + // select track + switch (type) { + case MediaStreamType.video: + await _instance.setVideoTrack(_videoTracks[selected.index ?? 0]); + break; + case MediaStreamType.audio: + await _instance.setAudioTrack(_audioTracks[selected.index ?? 0]); + break; + case MediaStreamType.text: + await _instance.setSubtitleTrack(_subtitleTracks[selected.index ?? 0]); + break; + } + } + } else if (current != null) { + // deselect track + switch (type) { + case MediaStreamType.video: + await _instance.setVideoTrack(VideoTrack.no()); + break; + case MediaStreamType.audio: + await _instance.setAudioTrack(AudioTrack.no()); + break; + case MediaStreamType.text: + await _instance.setSubtitleTrack(SubtitleTrack.no()); + // remove current subtitle, if any + _timedTextStreamController.add(null); + break; + } + } + } +} + +extension ExtraVideoTrack on VideoTrack { + MediaStreamSummary toAves(int index) { + return MediaStreamSummary( + type: MediaStreamType.video, + index: index, + codecName: null, + language: language, + title: title, + width: null, + height: null, + ); + } +} + +extension ExtraAudioTrack on AudioTrack { + MediaStreamSummary toAves(int index) { + return MediaStreamSummary( + type: MediaStreamType.audio, + index: index, + codecName: null, + language: language, + title: title, + width: null, + height: null, + ); + } +} + +extension ExtraSubtitleTrack on SubtitleTrack { + MediaStreamSummary toAves(int index) { + return MediaStreamSummary( + type: MediaStreamType.text, + index: index, + codecName: null, + language: language, + title: title, + width: null, + height: null, + ); + } +} diff --git a/plugins/aves_video_mpv/lib/src/factory.dart b/plugins/aves_video_mpv/lib/src/factory.dart new file mode 100644 index 000000000..2fdbd7485 --- /dev/null +++ b/plugins/aves_video_mpv/lib/src/factory.dart @@ -0,0 +1,21 @@ +import 'package:aves_model/aves_model.dart'; +import 'package:aves_video/aves_video.dart'; +import 'package:aves_video_mpv/aves_video_mpv.dart'; +import 'package:media_kit/media_kit.dart'; + +class MpvVideoControllerFactory extends AvesVideoControllerFactory { + @override + void init() => MediaKit.ensureInitialized(); + + @override + AvesVideoController buildController( + AvesEntryBase entry, { + required PlaybackStateHandler playbackStateHandler, + required VideoSettings settings, + }) => + MpvVideoController( + entry, + playbackStateHandler: playbackStateHandler, + settings: settings, + ); +} diff --git a/plugins/aves_video_mpv/pubspec.lock b/plugins/aves_video_mpv/pubspec.lock new file mode 100644 index 000000000..a7006875d --- /dev/null +++ b/plugins/aves_video_mpv/pubspec.lock @@ -0,0 +1,442 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + archive: + dependency: transitive + description: + name: archive + sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" + url: "https://pub.dev" + source: hosted + version: "3.3.7" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + aves_model: + dependency: "direct main" + description: + path: "../aves_model" + relative: true + source: path + version: "0.0.1" + aves_utils: + dependency: "direct main" + description: + path: "../aves_utils" + relative: true + source: path + version: "0.0.1" + aves_video: + dependency: "direct main" + description: + path: "../aves_video" + relative: true + source: path + version: "0.0.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + collection: + dependency: "direct main" + description: + name: collection + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + url: "https://pub.dev" + source: hosted + version: "1.17.2" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + dbus: + dependency: transitive + description: + name: dbus + sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263" + url: "https://pub.dev" + source: hosted + version: "0.7.8" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + image: + dependency: transitive + description: + name: image + sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf + url: "https://pub.dev" + source: hosted + version: "4.0.17" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + media_kit: + dependency: "direct main" + description: + name: media_kit + sha256: d7a827080fb28f0ba4e8a7ab3f3e3f868fa817f0a94499640466ade84a1c31c9 + url: "https://pub.dev" + source: hosted + version: "1.1.5" + media_kit_libs_android_video: + dependency: "direct main" + description: + name: media_kit_libs_android_video + sha256: "142d389bf3efcf8469594a9c7a06a92fc25843fc6c0c3247f76cdcf70b3b29de" + url: "https://pub.dev" + source: hosted + version: "1.3.2" + media_kit_native_event_loop: + dependency: "direct main" + description: + name: media_kit_native_event_loop + sha256: e37ce6fb5fa71b8cf513c6a6cd591367743a342a385e7da621a047dd8ef6f4a4 + url: "https://pub.dev" + source: hosted + version: "1.0.7" + media_kit_video: + dependency: "direct main" + description: + name: media_kit_video + sha256: d4143a96d97965d025bbb8b88db0ebf301e3c4cfa10c7e2ad7fd47c86a7febae + url: "https://pub.dev" + source: hosted + version: "1.1.6" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "6ff267fcd9d48cb61c8df74a82680e8b82e940231bb5f68356672fde0397334a" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + url: "https://pub.dev" + source: hosted + version: "5.4.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + url: "https://pub.dev" + source: hosted + version: "3.7.3" + safe_local_storage: + dependency: transitive + description: + name: safe_local_storage + sha256: ede4eb6cb7d88a116b3d3bf1df70790b9e2038bc37cb19112e381217c74d9440 + url: "https://pub.dev" + source: hosted + version: "1.0.2" + screen_brightness: + dependency: transitive + description: + name: screen_brightness + sha256: "62fd61a64e68b32b98b840bad7d8b6822bbc40e63c2b569a5f85528484c86b41" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + screen_brightness_android: + dependency: transitive + description: + name: screen_brightness_android + sha256: "3df10961e3a9e968a5e076fe27e7f4741fa8a1d3950bdeb48cf121ed529d0caf" + url: "https://pub.dev" + source: hosted + version: "0.1.0+2" + screen_brightness_ios: + dependency: transitive + description: + name: screen_brightness_ios + sha256: "99adc3ca5490b8294284aad5fcc87f061ad685050e03cf45d3d018fe398fd9a2" + url: "https://pub.dev" + source: hosted + version: "0.1.0" + screen_brightness_macos: + dependency: transitive + description: + name: screen_brightness_macos + sha256: "64b34e7e3f4900d7687c8e8fb514246845a73ecec05ab53483ed025bd4a899fd" + url: "https://pub.dev" + source: hosted + version: "0.1.0+1" + screen_brightness_platform_interface: + dependency: transitive + description: + name: screen_brightness_platform_interface + sha256: b211d07f0c96637a15fb06f6168617e18030d5d74ad03795dd8547a52717c171 + url: "https://pub.dev" + source: hosted + version: "0.1.0" + screen_brightness_windows: + dependency: transitive + description: + name: screen_brightness_windows + sha256: "80d90ecdc63fc0823f2ecb1be323471619287937e14210650d7b25ca181abd05" + url: "https://pub.dev" + source: hosted + version: "0.1.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc + url: "https://pub.dev" + source: hosted + version: "1.0.0+1" + uri_parser: + dependency: transitive + description: + name: uri_parser + sha256: "6543c9fd86d2862fac55d800a43e67c0dcd1a41677cb69c2f8edfe73bbcf1835" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" + source: hosted + version: "3.0.7" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + volume_controller: + dependency: transitive + description: + name: volume_controller + sha256: "189bdc7a554f476b412e4c8b2f474562b09d74bc458c23667356bce3ca1d48c9" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + wakelock_plus: + dependency: transitive + description: + name: wakelock_plus + sha256: aac3f3258f01781ec9212df94eecef1eb9ba9350e106728def405baa096ba413 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + wakelock_plus_platform_interface: + dependency: transitive + description: + name: wakelock_plus_platform_interface + sha256: "40fabed5da06caff0796dc638e1f07ee395fb18801fbff3255a2372db2d80385" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" + win32: + dependency: transitive + description: + name: win32 + sha256: f2add6fa510d3ae152903412227bda57d0d5a8da61d2c39c1fb022c9429a41c0 + url: "https://pub.dev" + source: hosted + version: "5.0.6" + xml: + dependency: transitive + description: + name: xml + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + url: "https://pub.dev" + source: hosted + version: "6.3.0" +sdks: + dart: ">=3.1.0-185.0.dev <4.0.0" + flutter: ">=3.7.0" diff --git a/plugins/aves_video_mpv/pubspec.yaml b/plugins/aves_video_mpv/pubspec.yaml new file mode 100644 index 000000000..4a15a0563 --- /dev/null +++ b/plugins/aves_video_mpv/pubspec.yaml @@ -0,0 +1,26 @@ +name: aves_video_mpv +version: 0.0.1 +publish_to: none + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + aves_model: + path: ../aves_model + aves_video: + path: ../aves_video + aves_utils: + path: ../aves_utils + collection: + media_kit: + media_kit_libs_android_video: + media_kit_native_event_loop: + media_kit_video: + +dev_dependencies: + flutter_lints: + +flutter: diff --git a/pubspec.lock b/pubspec.lock index b541bac71..fb19c76ac 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "9ebe81588e666f7e2b21309f2b5653bd9642d7f27fd0a6894278d2ff40cb9481" + sha256: "1a5e13736d59235ce0139621b4bbe29bc89839e202409081bc667eb3cd20674c" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.5" analyzer: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: args - sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" async: dependency: transitive description: @@ -126,6 +126,20 @@ packages: relative: true source: path version: "0.0.1" + aves_video_ffmpeg: + dependency: "direct main" + description: + path: "plugins/aves_video_ffmpeg" + relative: true + source: path + version: "0.0.1" + aves_video_mpv: + dependency: "direct main" + description: + path: "plugins/aves_video_mpv" + relative: true + source: path + version: "0.0.1" barcode: dependency: transitive description: @@ -138,10 +152,10 @@ packages: dependency: transitive description: name: bidi - sha256: dc00274c7edabae2ab30c676e736ea1eb0b1b7a1b436cb5fe372e431ccb39ab0 + sha256: "6794b226bc939731308b8539c49bb6c2fdbf0e78c3a65e9b9e81e727c256dfe6" url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.0.7" boolean_selector: dependency: transitive description: @@ -188,18 +202,18 @@ packages: dependency: "direct main" description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" connectivity_plus: dependency: "direct main" description: name: connectivity_plus - sha256: "8599ae9edca5ff96163fca3e36f8e481ea917d1e71cdad912c084b5579913f34" + sha256: "77a180d6938f78ca7d2382d2240eb626c0f6a735d0bfdce227d8ffb80f95c48b" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" connectivity_plus_platform_interface: dependency: transitive description: @@ -240,14 +254,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + csslib: + dependency: transitive + description: + name: csslib + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + url: "https://pub.dev" + source: hosted + version: "1.0.0" custom_rounded_rectangle_border: dependency: transitive description: name: custom_rounded_rectangle_border - sha256: "57b7af53da4e8bf4afa5a8393c446e953a81c23dd309f4341cfc38d19ff6f062" + sha256: "3e8ca0c26b8d22d5d3842bab59dfd209995f8e42af7c2eef03da70642c040819" url: "https://pub.dev" source: hosted - version: "0.2.0-nullsafety.0" + version: "0.3.0" dbus: dependency: transitive description: @@ -268,10 +290,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "2c35b6d1682b028e42d07b3aee4b98fa62996c10bc12cb651ec856a80d6a761b" + sha256: "86add5ef97215562d2e090535b0a16f197902b10c369c558a100e74ea06e8659" url: "https://pub.dev" source: hosted - version: "9.0.2" + version: "9.0.3" device_info_plus_platform_interface: dependency: transitive description: @@ -284,10 +306,10 @@ packages: dependency: "direct main" description: name: dynamic_color - sha256: "74dff1435a695887ca64899b8990004f8d1232b0e84bfc4faa1fdda7c6f57cc1" + sha256: de4798a7069121aee12d5895315680258415de9b00e717723a1bd73d58f0126d url: "https://pub.dev" source: hosted - version: "1.6.5" + version: "1.6.6" equatable: dependency: "direct main" description: @@ -309,10 +331,10 @@ packages: description: path: "." ref: HEAD - resolved-ref: b7375db537aabbeb7ccf43bfa65ea502ca586c37 + resolved-ref: cfcb070b627f110d01dff88809dcea20faa13444 url: "https://github.com/deckerst/expansion_tile_card.git" source: git - version: "2.0.0" + version: "3.0.0" fake_async: dependency: transitive description: @@ -325,19 +347,27 @@ packages: dependency: transitive description: name: ffi - sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" url: "https://pub.dev" source: hosted - version: "2.0.2" - fijkplayer: - dependency: "direct main" + version: "2.1.0" + ffmpeg_kit_flutter: + dependency: transitive description: - path: "." - ref: aves - resolved-ref: "935a2d86ebf45fbdbaf8b4a0921d5eaea87410d6" - url: "https://github.com/deckerst/fijkplayer.git" + path: "flutter/flutter" + ref: background-lts + resolved-ref: "1606719442d8ca04827a94dacd8d3d7a17b5526d" + url: "https://github.com/deckerst/ffmpeg-kit.git" source: git - version: "0.10.0" + version: "5.1.0" + ffmpeg_kit_flutter_platform_interface: + dependency: transitive + description: + name: ffmpeg_kit_flutter_platform_interface + sha256: addf046ae44e190ad0101b2fde2ad909a3cd08a2a109f6106d2f7048b7abedee + url: "https://pub.dev" + source: hosted + version: "0.2.1" file: dependency: transitive description: @@ -350,10 +380,10 @@ packages: dependency: transitive description: name: firebase_core - sha256: e9b36b391690cf329c6fb1de220045e97c13784c303820cd33962319580a56c6 + sha256: c78132175edda4bc532a71e01a32964e4b4fcf53de7853a422d96dac3725f389 url: "https://pub.dev" source: hosted - version: "2.13.1" + version: "2.15.1" firebase_core_platform_interface: dependency: transitive description: @@ -366,51 +396,50 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: "8c0f4c87d20e2d001a5915df238c1f9c88704231f591324205f5a5d2a7740a45" + sha256: "4cf4d2161530332ddc3c562f19823fb897ff37a9a774090d28df99f47370e973" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.7.0" firebase_crashlytics: dependency: transitive description: name: firebase_crashlytics - sha256: "603f23a74995c193cae89a784b8da529b1e6a91c03bc63f885f36456e9e867a0" + sha256: fd9e1a1cb7cce3f9dd2358d8363d235f25f056981e23a333db1e57eca693913f url: "https://pub.dev" source: hosted - version: "3.3.2" + version: "3.3.5" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface - sha256: cefeeeb98abdb9d848581603bd1e33a2a8e6d3ed937586cb84437e606049071b + sha256: "0d19ef23cf7a917a357d2eb1807338ec536ec3232e729ebd769f5bb2aba9e085" url: "https://pub.dev" source: hosted - version: "3.6.2" + version: "3.6.5" flex_color_picker: dependency: "direct main" description: name: flex_color_picker - sha256: d8279250820ad279123fa8ee94151dd99400dd9ef4fb096589fcf956765d39a9 + sha256: f37476ab3e80dcaca94e428e159944d465dd16312fda9ff41e07e86f04bfa51c url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.3.0" flex_seed_scheme: dependency: transitive description: name: flex_seed_scheme - sha256: e4168a6fc88a3e5bc3d6b7a748c6a6083eedc193d343ddc26bbad7fb1b258555 + sha256: "29c12aba221eb8a368a119685371381f8035011d18de5ba277ad11d7dfb8657f" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" floating: dependency: "direct main" description: - path: "." - ref: main - resolved-ref: b073419d48f099b5855816a7c6e04d397b1f1c37 - url: "https://github.com/wrbl606/floating.git" - source: git - version: "2.0.0" + name: floating + sha256: d9d563089e34fbd714ffdcdd2df447ec41b40c9226dacae6b4f78847aef8b991 + url: "https://pub.dev" + source: hosted + version: "2.0.1" fluster: dependency: "direct main" description: @@ -457,16 +486,16 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" flutter_localization_nn: dependency: "direct main" description: path: "." ref: HEAD - resolved-ref: "61b18541adb2028798cf246dda93c974120dabb8" + resolved-ref: da1bd8f16cbce3fa60bae8fff478718fe9d6835e url: "https://github.com/deckerst/flutter_localization_nn.git" source: git version: "0.0.1" @@ -479,18 +508,18 @@ packages: dependency: "direct main" description: name: flutter_map - sha256: "52c65a977daae42f9aae6748418dd1535eaf27186e9bac9bf431843082bc75a3" + sha256: "5286f72f87deb132daa1489442d6cc46e986fc105cb727d9ae1b602b35b1d1f3" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.0" flutter_markdown: dependency: "direct main" description: name: flutter_markdown - sha256: dc6d5258653f6857135b32896ccda7f7af0c54dcec832495ad6835154c6c77c0 + sha256: "2b206d397dd7836ea60035b2d43825c8a303a76a5098e66f42d55a753e18d431" url: "https://pub.dev" source: hosted - version: "0.6.15" + version: "0.6.17+1" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -570,22 +599,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + google_maps: + dependency: transitive + description: + name: google_maps + sha256: "555d5d736339b0478e821167ac521c810d7b51c3b2734e6802a9f046b64ea37a" + url: "https://pub.dev" + source: hosted + version: "6.3.0" google_maps_flutter: dependency: transitive description: name: google_maps_flutter - sha256: abefcb1e5e5c96bdd8084939dda555257af272c7972902ca46d5631092c1df68 + sha256: c290921cd1750b5ede99c82dcaa84740da86278e6ed0f83ad29752b29a8552c6 url: "https://pub.dev" source: hosted - version: "2.2.8" + version: "2.4.0" google_maps_flutter_android: dependency: transitive description: name: google_maps_flutter_android - sha256: "9512c862df77c1f0fa5f445513dd3c57f5996f0a809dccb74e54b690ee4e3a0f" + sha256: fb3e52f94d0736f6ee8bcdef290f8eab9325376d676f61303347c10ccf15379c url: "https://pub.dev" source: hosted - version: "2.4.15" + version: "2.4.16" google_maps_flutter_ios: dependency: transitive description: @@ -598,10 +635,18 @@ packages: dependency: transitive description: name: google_maps_flutter_platform_interface - sha256: "308f0af138fa78e8224d598d46ca182673874d0ef4d754b7157c073b5b4b8e0d" + sha256: b363e9a1ef7d063fb21ec8eef5a450db4b0500cc39712c9410b5cc64013d6fc6 url: "https://pub.dev" source: hosted - version: "2.2.7" + version: "2.4.0" + google_maps_flutter_web: + dependency: transitive + description: + name: google_maps_flutter_web + sha256: "229391997f216d37c76bfc10d9b5837a1dfeb98c4b4063c605016382e5bcd910" + url: "https://pub.dev" + source: hosted + version: "0.5.3" highlight: dependency: transitive description: @@ -610,14 +655,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" + html: + dependency: transitive + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.dev" + source: hosted + version: "0.15.4" http: dependency: transitive description: name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" url: "https://pub.dev" source: hosted - version: "0.13.6" + version: "1.1.0" http_multi_server: dependency: transitive description: @@ -646,10 +699,10 @@ packages: dependency: "direct main" description: name: intl - sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" url: "https://pub.dev" source: hosted - version: "0.18.0" + version: "0.18.1" io: dependency: transitive description: @@ -666,14 +719,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + js_wrapping: + dependency: transitive + description: + name: js_wrapping + sha256: e385980f7c76a8c1c9a560dfb623b890975841542471eade630b2871d243851c + url: "https://pub.dev" + source: hosted + version: "0.7.4" latlong2: dependency: "direct main" description: name: latlong2 - sha256: "08ef7282ba9f76e8495e49e2dc4d653015ac929dce5f92b375a415d30b407ea0" + sha256: "18712164760cee655bc790122b0fd8f3d5b3c36da2cb7bf94b68a197fbb0811b" url: "https://pub.dev" source: hosted - version: "0.8.2" + version: "0.9.0" lints: dependency: transitive description: @@ -702,10 +763,10 @@ packages: dependency: transitive description: name: local_auth_android - sha256: "91824b34c013b9a03dfb754ac2b15329e1c553b8fd18f6d1baffebb72ceff226" + sha256: "36a78898198386d36d4e152b8cb46059b18f0e2017f813a0e833e216199f8950" url: "https://pub.dev" source: hosted - version: "1.0.30" + version: "1.0.32" local_auth_ios: dependency: transitive description: @@ -726,10 +787,10 @@ packages: dependency: transitive description: name: local_auth_windows - sha256: "19323b75ab781d5362dbb15dcb7e0916d2431c7a6dbdda016ec9708689877f73" + sha256: "5af808e108c445d0cf702a8c5f8242f1363b7970320334f82e6e1e8ad0b0d7d4" url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.0.9" logging: dependency: transitive description: @@ -742,18 +803,18 @@ packages: dependency: transitive description: name: markdown - sha256: "8e332924094383133cee218b676871f42db2514f1f6ac617b6cf6152a7faab8e" + sha256: acf35edccc0463a9d7384e437c015a3535772e09714cf60e07eeef3a15870dcd url: "https://pub.dev" source: hosted - version: "7.1.0" + version: "7.1.1" matcher: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: "direct main" description: @@ -766,10 +827,42 @@ packages: dependency: "direct main" description: name: material_design_icons_flutter - sha256: "8ef8562d16e747b2d93e5da5c2508931588939c5c00ebc8e2768e803db7dfd3c" + sha256: "6f986b7a51f3ad4c00e33c5c84e8de1bdd140489bbcdc8b66fc1283dad4dea5a" url: "https://pub.dev" source: hosted - version: "6.0.7096" + version: "7.0.7296" + media_kit: + dependency: transitive + description: + name: media_kit + sha256: d7a827080fb28f0ba4e8a7ab3f3e3f868fa817f0a94499640466ade84a1c31c9 + url: "https://pub.dev" + source: hosted + version: "1.1.5" + media_kit_libs_android_video: + dependency: transitive + description: + name: media_kit_libs_android_video + sha256: "142d389bf3efcf8469594a9c7a06a92fc25843fc6c0c3247f76cdcf70b3b29de" + url: "https://pub.dev" + source: hosted + version: "1.3.2" + media_kit_native_event_loop: + dependency: transitive + description: + name: media_kit_native_event_loop + sha256: e37ce6fb5fa71b8cf513c6a6cd591367743a342a385e7da621a047dd8ef6f4a4 + url: "https://pub.dev" + source: hosted + version: "1.0.7" + media_kit_video: + dependency: transitive + description: + name: media_kit_video + sha256: d4143a96d97965d025bbb8b88db0ebf301e3c4cfa10c7e2ad7fd47c86a7febae + url: "https://pub.dev" + source: hosted + version: "1.1.6" meta: dependency: transitive description: @@ -799,7 +892,7 @@ packages: description: path: "." ref: aves - resolved-ref: "45e13ba987b00261d00488d29fa6f97ed0bcb96a" + resolved-ref: fe840a13d0943f5afcc244c3b9f936e77bda2b8d url: "https://github.com/deckerst/aves_panorama_motion_sensors.git" source: git version: "0.1.0" @@ -847,10 +940,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: ceb027f6bc6a60674a233b4a90a7658af1aebdea833da0b5b53c1e9821a78c7b + sha256: "6ff267fcd9d48cb61c8df74a82680e8b82e940231bb5f68356672fde0397334a" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.0" package_info_plus_platform_interface: dependency: transitive description: @@ -872,7 +965,7 @@ packages: description: path: "." ref: aves - resolved-ref: dfde64310edb376a6a2536e5684de0e0a89cc31c + resolved-ref: baada6758628a2ee602e76d0088619ebb52316b4 url: "https://github.com/deckerst/aves_panorama.git" source: git version: "0.4.1" @@ -896,26 +989,26 @@ packages: dependency: transitive description: name: path_provider_linux - sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57 + sha256: ba2b77f0c52a33db09fc8caf85b12df691bf28d983e84cf87ff6d693cfa007b3 url: "https://pub.dev" source: hosted - version: "2.1.11" + version: "2.2.0" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + sha256: bced5679c7df11190e1ddc35f3222c858f328fff85c3942e46e7f5589bf9eb84 url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.1.0" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6 + sha256: ee0e0d164516b90ae1f970bdf29f726f1aa730d7cfc449ecc74c495378b705da url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.2.0" pattern_lock: dependency: "direct main" description: @@ -944,42 +1037,42 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "33c6a1253d1f95fd06fa74b65b7ba907ae9811f9d5c1d3150e51417d04b8d6a8" + sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81" url: "https://pub.dev" source: hosted - version: "10.2.0" + version: "10.4.3" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: d8cc6a62ded6d0f49c6eac337e080b066ee3bce4d405bd9439a61e1f1927bfe8 + sha256: "2ffaf52a21f64ac9b35fe7369bb9533edbd4f698e5604db8645b1064ff4cf221" url: "https://pub.dev" source: hosted - version: "10.2.1" + version: "10.3.3" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: ee96ac32f5a8e6f80756e25b25b9f8e535816c8e6665a96b6d70681f8c4f7e85 + sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" url: "https://pub.dev" source: hosted - version: "9.0.8" + version: "9.1.4" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: "68abbc472002b5e6dfce47fe9898c6b7d8328d58b5d2524f75e277c07a97eb84" + sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9" url: "https://pub.dev" source: hosted - version: "3.9.0" + version: "3.11.3" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b + sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 url: "https://pub.dev" source: hosted - version: "0.1.2" + version: "0.1.3" petitparser: dependency: transitive description: @@ -992,10 +1085,10 @@ packages: dependency: "direct main" description: name: pin_code_fields - sha256: c8652519d14688f3fe2a8288d86910a46aa0b9046d728f292d3bf6067c31b4c7 + sha256: "4c0db7fbc889e622e7c71ea54b9ee624bb70c7365b532abea0271b17ea75b729" url: "https://pub.dev" source: hosted - version: "7.4.0" + version: "8.0.1" platform: dependency: transitive description: @@ -1008,10 +1101,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" pointycastle: dependency: transitive description: @@ -1084,6 +1177,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + safe_local_storage: + dependency: transitive + description: + name: safe_local_storage + sha256: ede4eb6cb7d88a116b3d3bf1df70790b9e2038bc37cb19112e381217c74d9440 + url: "https://pub.dev" + source: hosted + version: "1.0.2" + sanitize_html: + dependency: transitive + description: + name: sanitize_html + sha256: "0a445f19bbaa196f5a4f93461aa066b94e6e025622eb1e9bc77872a5e25233a5" + url: "https://pub.dev" + source: hosted + version: "2.0.0" screen_brightness: dependency: "direct main" description: @@ -1136,58 +1245,58 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022" + sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749" + sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076 url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb + sha256: d29753996d8eb8f7619a1f13df6ce65e34bc107bef6330739ed76f18b22310ef url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.3" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa" + sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" shared_preferences_platform_interface: dependency: "direct dev" description: name: shared_preferences_platform_interface - sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d + sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5" + sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173" + sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" shelf: dependency: transitive description: @@ -1253,26 +1362,26 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" sqflite: dependency: "direct main" description: name: sqflite - sha256: b4d6710e1200e96845747e37338ea8a819a12b51689a3bcf31eff0003b37a0b9 + sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a" url: "https://pub.dev" source: hosted - version: "2.2.8+4" + version: "2.3.0" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: e77abf6ff961d69dfef41daccbb66b51e9983cdd5cb35bf30733598057401555 + sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a" url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "2.5.0" stack_trace: dependency: "direct main" description: @@ -1302,7 +1411,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: "096f964597703830f384d1a336e7752a6531f2a2" + resolved-ref: acaf1351bf7f5f2cf2ea25989b732b9acb56e267 url: "https://github.com/deckerst/aves_streams_channel.git" source: git version: "0.3.0" @@ -1342,26 +1451,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" + sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46" url: "https://pub.dev" source: hosted - version: "1.24.1" + version: "1.24.3" test_api: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" test_core: dependency: transitive description: name: test_core - sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" + sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.5.3" transparent_image: dependency: "direct main" description: @@ -1370,14 +1479,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" - tuple: - dependency: "direct main" - description: - name: tuple - sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" - url: "https://pub.dev" - source: hosted - version: "2.0.1" typed_data: dependency: transitive description: @@ -1394,22 +1495,38 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.1" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc + url: "https://pub.dev" + source: hosted + version: "1.0.0+1" + uri_parser: + dependency: transitive + description: + name: uri_parser + sha256: "6543c9fd86d2862fac55d800a43e67c0dcd1a41677cb69c2f8edfe73bbcf1835" + url: "https://pub.dev" + source: hosted + version: "2.0.2" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3 + sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e" url: "https://pub.dev" source: hosted - version: "6.1.11" + version: "6.1.12" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: eed4e6a1164aa9794409325c3b707ff424d4d1c2a785e7db67f8bbda00e36e51 + sha256: "3dd2388cc0c42912eee04434531a26a82512b9cb1827e0214430c9bcbddfe025" url: "https://pub.dev" source: hosted - version: "6.0.35" + version: "6.0.38" url_launcher_ios: dependency: transitive description: @@ -1430,34 +1547,42 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e" + sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" + sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab" + sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4 url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.0.18" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771" + sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" + uuid: + dependency: transitive + description: + name: uuid + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" + source: hosted + version: "3.0.7" vector_math: dependency: "direct main" description: @@ -1470,10 +1595,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6deed8ed625c52864792459709183da231ebf66ff0cf09e69b573227c377efe + sha256: c620a6f783fa22436da68e42db7ebbf18b8c44b9a46ab911f666ff09ffd9153f url: "https://pub.dev" source: hosted - version: "11.3.0" + version: "11.7.1" volume_controller: dependency: "direct main" description: @@ -1482,6 +1607,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.7" + wakelock_plus: + dependency: transitive + description: + name: wakelock_plus + sha256: aac3f3258f01781ec9212df94eecef1eb9ba9350e106728def405baa096ba413 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + wakelock_plus_platform_interface: + dependency: transitive + description: + name: wakelock_plus_platform_interface + sha256: "40fabed5da06caff0796dc638e1f07ee395fb18801fbff3255a2372db2d80385" + url: "https://pub.dev" + source: hosted + version: "1.1.0" watcher: dependency: transitive description: @@ -1490,6 +1631,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -1518,18 +1667,18 @@ packages: dependency: transitive description: name: win32 - sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + sha256: f2add6fa510d3ae152903412227bda57d0d5a8da61d2c39c1fb022c9429a41c0 url: "https://pub.dev" source: hosted - version: "4.1.4" + version: "5.0.6" win32_registry: dependency: transitive description: name: win32_registry - sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae" + sha256: e4506d60b7244251bc59df15656a3093501c37fb5af02105a944d73eb95be4c9 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" wkt_parser: dependency: transitive description: @@ -1542,10 +1691,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 + sha256: f0c26453a2d47aa4c2570c6a033246a3fc62da2fe23c7ffdd0a7495086dc0247 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.2" xml: dependency: "direct main" description: @@ -1563,5 +1712,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.1 <4.0.0" - flutter: ">=3.10.3" + dart: ">=3.1.0-185.0.dev <4.0.0" + flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index 40e232d06..451bf324a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,13 +7,13 @@ repository: https://github.com/deckerst/aves # - play changelog: /whatsnew/whatsnew-en-US # - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XX01.txt # - libre changelog: /fastlane/metadata/android/en-US/changelogs/XX.txt -version: 1.8.9+100 +version: 1.9.0+101 publish_to: none environment: # this project bundles Flutter SDK via `flutter_wrapper` # cf https://github.com/passsy/flutter_wrapper - flutter: 3.10.3 + flutter: 3.13.0 sdk: ">=3.0.0 <4.0.0" # use `scripts/apply_flavor_{flavor}.sh` to set the right dependencies for the flavor @@ -40,6 +40,12 @@ dependencies: path: plugins/aves_services_google aves_video: path: plugins/aves_video +# aves_video_ijk: +# path: plugins/aves_video_ijk + aves_video_ffmpeg: + path: plugins/aves_video_ffmpeg + aves_video_mpv: + path: plugins/aves_video_mpv aves_ui: path: plugins/aves_ui aves_utils: @@ -63,16 +69,8 @@ dependencies: expansion_tile_card: git: url: https://github.com/deckerst/expansion_tile_card.git - fijkplayer: - git: - url: https://github.com/deckerst/fijkplayer.git - ref: aves flex_color_picker: floating: - git: - url: https://github.com/wrbl606/floating.git - # v2.0.0 is incompatible with AGP8 - ref: main fluster: flutter_displaymode: flutter_highlight: @@ -113,7 +111,6 @@ dependencies: git: url: https://github.com/deckerst/aves_streams_channel.git transparent_image: - tuple: url_launcher: vector_math: volume_controller: @@ -128,12 +125,6 @@ dev_dependencies: shared_preferences_platform_interface: test: -dependency_overrides: - # as of Flutter beta v3.10.0-1.5.pre, `flutter_driver` - # constrains `material_color_utilities` to v0.2.0, which - # constrains `dynamic_color` to v1.6.4, which is incompatible with AGP8 - material_color_utilities: ^0.5.0 - flutter: assets: - assets/ diff --git a/scripts/fix_android_log_levels.bat b/scripts/fix_android_log_levels.bat index ee9d7b46d..b585d2771 100644 --- a/scripts/fix_android_log_levels.bat +++ b/scripts/fix_android_log_levels.bat @@ -29,6 +29,7 @@ adb.exe shell setprop log.tag.J4A INFO adb.exe shell setprop log.tag.MediaCodec WARN adb.exe shell setprop log.tag.MediaMetadataRetriever INFO adb.exe shell setprop log.tag.MediaMetadataRetrieverJNI INFO +adb.exe shell setprop log.tag.NativeTiffDecoder INFO adb.exe shell setprop log.tag.NuMediaExtractor INFO adb.exe shell setprop log.tag.PipelineWatcher INFO adb.exe shell setprop log.tag.ReflectedParamUpdater INFO diff --git a/scripts/fix_android_log_levels.sh b/scripts/fix_android_log_levels.sh index 1e525225e..f179d3624 100755 --- a/scripts/fix_android_log_levels.sh +++ b/scripts/fix_android_log_levels.sh @@ -21,6 +21,7 @@ adb shell setprop log.tag.J4A INFO adb shell setprop log.tag.MediaCodec WARN adb shell setprop log.tag.MediaMetadataRetriever INFO adb shell setprop log.tag.MediaMetadataRetrieverJNI INFO +adb shell setprop log.tag.NativeTiffDecoder INFO adb shell setprop log.tag.NuMediaExtractor INFO adb shell setprop log.tag.PipelineWatcher INFO adb shell setprop log.tag.ReflectedParamUpdater INFO diff --git a/shaders.sksl.json b/shaders.sksl.json index 8ee2d65fb..dc0469668 100644 --- a/shaders.sksl.json +++ b/shaders.sksl.json @@ -1 +1 @@ -{"platform":"android","name":"SM G970N","engineRevision":"2a3401c9bbb5a9a9aec74d4f735d18a9dd3ebf2d","data":{"AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CgAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAADqAQAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQAAAAA=","B2AAQAAABQAAIAABBYAAB7777777777774ABICAAAAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CgAAAExTS1MOAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmIGluQ292ZXJhZ2U7Cm91dCBoYWxmIHZpbkNvdmVyYWdlX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cgl2aW5Db3ZlcmFnZV9TMCA9IGluQ292ZXJhZ2U7Cglza19Qb3NpdGlvbiA9IF90bXBfMV9pblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAATQEAAHVuaWZvcm0gaGFsZjQgdUNvbG9yX1MwOwppbiBoYWxmIHZpbkNvdmVyYWdlX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdUNvbG9yX1MwOwoJaGFsZiBhbHBoYSA9IDEuMDsKCWFscGhhID0gdmluQ292ZXJhZ2VfUzA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACgAAAGluUG9zaXRpb24AAAoAAABpbkNvdmVyYWdlAAAAAAAA","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGACQAGAAAAAQAAAAAAAQQGAAA":"CgAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAAAAACsAQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGZsb2F0MiB2bG9jYWxDb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9IChibGVuZF9tb2R1bGF0ZShzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHRleENvb3JkKSwgaGFsZjQoMSkpKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQBAAAQAAAAGQCBAMQACAAAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CgAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAHADAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBjbGFtcChzdWJzZXRDb29yZC54LCB1Y2xhbXBfUzFfYzAueCwgdWNsYW1wX1MxX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAAAAAA==","AYTRVAADQAAAOAEARAFQJAABBADAAAILBYAACCYUQD777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CgAAAExTS1NyAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7CmluIGhhbGYzIGluQ2xpcFBsYW5lOwppbiBoYWxmMyBpbklzZWN0UGxhbmU7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGYzIHZpbkNsaXBQbGFuZV9TMDsKb3V0IGhhbGYzIHZpbklzZWN0UGxhbmVfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCXZpbkNpcmNsZUVkZ2VfUzAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5DbGlwUGxhbmVfUzAgPSBpbkNsaXBQbGFuZTsKCXZpbklzZWN0UGxhbmVfUzAgPSBpbklzZWN0UGxhbmU7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gdWxvY2FsTWF0cml4X1MwLnh6ICogaW5Qb3NpdGlvbiArIHVsb2NhbE1hdHJpeF9TMC55dzsKCXNrX1Bvc2l0aW9uID0gX3RtcF8wX2luUG9zaXRpb24ueHkwMTsKfQoAAAAAAADdAwAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGYzIHZpbkNsaXBQbGFuZV9TMDsKaW4gaGFsZjMgdmluSXNlY3RQbGFuZV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGYzIGNsaXBQbGFuZTsKCWNsaXBQbGFuZSA9IHZpbkNsaXBQbGFuZV9TMDsKCWhhbGYzIGlzZWN0UGxhbmU7Cglpc2VjdFBsYW5lID0gdmluSXNlY3RQbGFuZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZiBkaXN0YW5jZVRvSW5uZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoZCAtIGNpcmNsZUVkZ2UudykpOwoJaGFsZiBpbm5lckFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb0lubmVyRWRnZSk7CgllZGdlQWxwaGEgKj0gaW5uZXJBbHBoYTsKCWhhbGYgY2xpcCA9IGhhbGYoc2F0dXJhdGUoY2lyY2xlRWRnZS56ICogZG90KGNpcmNsZUVkZ2UueHksIGNsaXBQbGFuZS54eSkgKyBjbGlwUGxhbmUueikpOwoJY2xpcCAqPSBoYWxmKHNhdHVyYXRlKGNpcmNsZUVkZ2UueiAqIGRvdChjaXJjbGVFZGdlLnh5LCBpc2VjdFBsYW5lLnh5KSArIGlzZWN0UGxhbmUueikpOwoJZWRnZUFscGhhICo9IGNsaXA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlCwAAAGluQ2xpcFBsYW5lAAwAAABpbklzZWN0UGxhbmUAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAFBQATAAAAAAFAAMAAAABAAAAAAABBAMAAAAA":"CgAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAABABAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBmbG9hdDIgdWludlJhZGlpWFlfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKaGFsZjQgRWxsaXB0aWNhbFJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJZmxvYXQyIFogPSBkeHkgKiB1aW52UmFkaWlYWV9TMS54eTsKCWhhbGYgaW1wbGljaXQgPSBoYWxmKGRvdChaLCBkeHkpIC0gMS4wKTsKCWhhbGYgZ3JhZF9kb3QgPSBoYWxmKDQuMCAqIGRvdChaLCBaKSk7CglncmFkX2RvdCA9IG1heChncmFkX2RvdCwgMS4wZS00KTsKCWhhbGYgYXBwcm94X2Rpc3QgPSBpbXBsaWNpdCAqIGhhbGYoaW52ZXJzZXNxcnQoZ3JhZF9kb3QpKTsKCWhhbGYgYWxwaGEgPSBjbGFtcCgwLjUgKyBhcHByb3hfZGlzdCwgMC4wLCAxLjApOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gRWxsaXB0aWNhbFJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAAAAAA==","HUQACAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CgAAAExTS1PPAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAMAQAAaW4gaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAAAAAA==","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAGIBIAAABAAAAANAEAAAAAAAAAAAAAABAAOAAAABAAAAAAABBAMAAAAA":"CgAAAExTS1N0AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMl9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAHMCAAB1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwKS5ycnJyOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gTWF0cml4RWZmZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAAKAAAAbG9jYWxDb29yZAAAAAAAAA==","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAFQBQU7BTXIAAAAAAAAAACAAAAAVQEAAQAAAAAQCDAEQQGAAAAAAAAAAAA4IAPAAACAAAAAAAEABYAAAAEAAAAAAAEEBQA":"CgAAAExTS1N6AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfM19TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzFfYzApICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAABAAAArAQAAHVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMjsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzI7CnVuaWZvcm0gc2FtcGxlckV4dGVybmFsT0VTIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoX2lucHV0KTsKfQpoYWxmNCBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKGhhbGY0IF9pbnB1dCkgCnsKCV9pbnB1dCA9IE1hdHJpeEVmZmVjdF9TMV9jMChfaW5wdXQpOwoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CglyZXR1cm4gaGFsZjQoX2lucHV0KTsKfQpoYWxmNCBDaXJjdWxhclJSZWN0X1MyKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMi5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMi5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MyLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gRGlzYWJsZUNvdmVyYWdlQXNBbHBoYV9TMShvdXRwdXRDb2xvcl9TMCk7CgloYWxmNCBvdXRwdXRfUzI7CglvdXRwdXRfUzIgPSBDaXJjdWxhclJSZWN0X1MyKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfUzEgKiBvdXRwdXRfUzI7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAAKAAAAbG9jYWxDb29yZAAAAAAAAA==","HVIACAAAABQAAGAAAQ4AAAAAGQQAARC4GAAAIOCAAD6P7777777777YDAAAAAAAAAAAFIBQU7BTXIAAAAAACAAAAAAQFV5W6JEAAAAAYAAAABQEZ2AKAWAQAABAL6SYKDYAAAACAAAAAAQEGIAAAAACAWTWL3EYAAAADAAAAACADHIJJCYCAAAEAP2LRIPAAAAAIAAAAAAABTALI3F5SOAIAABQAAAAAABTUEUZMBAAAAAH5FYUXQAAAAAAAEAAAAAZMRGOQCQFQEAAAAAAAAAGARL2LXJHAAEAAAAAEAAAABSCQX5FQUHQAAAAAAAAAACAA4AAAABAACAAAACCAYAAAAA":"CgAAAExTS1PrAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdCBjb3ZlcmFnZTsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdCB2Y292ZXJhZ2VfUzA7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzZfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwoJewoJCXZUcmFuc2Zvcm1lZENvb3Jkc182X1MwID0gZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMF9jMSkgKiBsb2NhbENvb3JkLnh5MTsKCX0KfQoAAAAAAMMHAAB1bmlmb3JtIGhhbGY0IHVzdGFydF9TMV9jMF9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1ZW5kX1MxX2MwX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzBfYzE7CnVuaWZvcm0gaGFsZjQgdWxlZnRCb3JkZXJDb2xvcl9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TMV9jMF9jMDsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0IHZjb3ZlcmFnZV9TMDsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc182X1MwOwpoYWxmNCBTaW5nbGVJbnRlcnZhbENvbG9yaXplcl9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CglmbG9hdDIgX3RtcF8xX2Nvb3JkcyA9IF9jb29yZHM7CglyZXR1cm4gaGFsZjQobWl4KHVzdGFydF9TMV9jMF9jMF9jMCwgdWVuZF9TMV9jMF9jMF9jMCwgaGFsZihfdG1wXzFfY29vcmRzLngpKSk7Cn0KaGFsZjQgTGluZWFyTGF5b3V0X1MxX2MwX2MwX2MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMl9pbkNvbG9yID0gX2lucHV0OwoJZmxvYXQyIF90bXBfM19jb29yZHMgPSB2VHJhbnNmb3JtZWRDb29yZHNfNl9TMDsKCXJldHVybiBoYWxmNChoYWxmNChoYWxmKF90bXBfM19jb29yZHMueCkgKyAxZS0wNSwgMS4wLCAwLjAsIDAuMCkpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMF9jMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyTGF5b3V0X1MxX2MwX2MwX2MxX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQ2xhbXBlZEdyYWRpZW50X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfNF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjQgdCA9IE1hdHJpeEVmZmVjdF9TMV9jMF9jMF9jMShfdG1wXzRfaW5Db2xvcik7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghYm9vbChpbnQoMSkpICYmIHQueSA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSBoYWxmNCgwLjApOwoJfQoJZWxzZSBpZiAodC54IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IHVsZWZ0Qm9yZGVyQ29sb3JfUzFfYzBfYzA7Cgl9CgllbHNlIGlmICh0LnggPiAxLjApIAoJewoJCW91dENvbG9yID0gdXJpZ2h0Qm9yZGVyQ29sb3JfUzFfYzBfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzBfYzAoX3RtcF80X2luQ29sb3IsIGZsb2F0MihoYWxmMih0LngsIDAuMCkpKTsKCX0KCXJldHVybiBoYWxmNChvdXRDb2xvcik7Cn0KaGFsZjQgY29sb3JfeGZvcm1fUzFfYzAoZmxvYXQ0IGNvbG9yKSAKewoJY29sb3IucmdiICo9IGNvbG9yLmE7CglyZXR1cm4gaGFsZjQoY29sb3IpOwp9CmhhbGY0IENvbG9yU3BhY2VYZm9ybV9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gY29sb3JfeGZvcm1fUzFfYzAoQ2xhbXBlZEdyYWRpZW50X1MxX2MwX2MwKF9pbnB1dCkpOwp9CmhhbGY0IERpc2FibGVDb3ZlcmFnZUFzQWxwaGFfUzEoaGFsZjQgX2lucHV0KSAKewoJX2lucHV0ID0gQ29sb3JTcGFjZVhmb3JtX1MxX2MwKF9pbnB1dCk7CgloYWxmNCBfdG1wXzVfaW5Db2xvciA9IF9pbnB1dDsKCXJldHVybiBoYWxmNChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCBjb3ZlcmFnZSA9IHZjb3ZlcmFnZV9TMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoaGFsZihjb3ZlcmFnZSkpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gRGlzYWJsZUNvdmVyYWdlQXNBbHBoYV9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gKGhhbGY0KDEuMCkgLSBvdXRwdXRfUzEpICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAAHBvc2l0aW9uCAAAAGNvdmVyYWdlBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQBAEAQAAAAGQCBAMQACAIAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CgAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAE0DAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkID0gY2xhbXAoc3Vic2V0Q29vcmQsIHVjbGFtcF9TMV9jMC54eSwgdWNsYW1wX1MxX2MwLnp3KTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAAAAAA=","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAIAD2AAAAAAQAVSWGRIBAAADAAAAACAAAAAAQCGEIQOZLBIQAAAABQAAAAAAAAAAAAFAAMAAAABAAAAAAABBAMAAA":"CgAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAABAAAARQUAAGNvbnN0IGludCBrRmlsbEJXX1MxX2MwID0gMDsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEJXX1MxX2MwID0gMjsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEFBX1MxX2MwID0gMzsKdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1MxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1MwOwpmbGF0IGluIGZsb2F0IHZUZXhJbmRleF9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7CmhhbGY0IFJlY3RfUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGNvdmVyYWdlOwoJaWYgKGludCgxKSA9PSBrRmlsbEJXX1MxX2MwIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMV9jMCkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKGFsbChncmVhdGVyVGhhbihmbG9hdDQoc2tfRnJhZ0Nvb3JkLnh5LCB1cmVjdFVuaWZvcm1fUzFfYzAuencpLCBmbG9hdDQodXJlY3RVbmlmb3JtX1MxX2MwLnh5LCBza19GcmFnQ29vcmQueHkpKSkpOwoJfQoJZWxzZSAKCXsKCQloYWxmNCBkaXN0czQgPSBzYXR1cmF0ZShoYWxmNCgxLjAsIDEuMCwgLTEuMCwgLTEuMCkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIHVyZWN0VW5pZm9ybV9TMV9jMCkpOwoJCWhhbGYyIGRpc3RzMiA9IChkaXN0czQueHkgKyBkaXN0czQuencpIC0gMS4wOwoJCWNvdmVyYWdlID0gZGlzdHMyLnggKiBkaXN0czIueTsKCX0KCWlmIChpbnQoMSkgPT0ga0ludmVyc2VGaWxsQldfUzFfYzAgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEFBX1MxX2MwKSAKCXsKCQljb3ZlcmFnZSA9IDEuMCAtIGNvdmVyYWdlOwoJfQoJcmV0dXJuIGhhbGY0KGhhbGY0KGNvdmVyYWdlKSk7Cn0KaGFsZjQgQmxlbmRfUzEoaGFsZjQgX3NyYywgaGFsZjQgX2RzdCkgCnsKCXJldHVybiBibGVuZF9tb2R1bGF0ZShSZWN0X1MxX2MwKF9zcmMpLCBfc3JjKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZjQgdGV4Q29sb3I7Cgl7CgkJdGV4Q29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHZUZXh0dXJlQ29vcmRzX1MwKS5ycnJyOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSB0ZXhDb2xvcjsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IEJsZW5kX1MxKG91dHB1dENvdmVyYWdlX1MwLCBoYWxmNCgxKSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADwAAAGluVGV4dHVyZUNvb3JkcwAAAAAA","DASAAAAAQAAWAABAYAAQBYH7777Z6QQBAEAAAAAAEAAAAAAAEBSAAAB2AAAAAAACAAAAAEBSAAAAA":"CgAAAExTS1PVAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBCaXRtYXBUZXh0CglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkcyAqIHVBdGxhc1NpemVJbnZfUzA7Cgl2VGV4SW5kZXhfUzAgPSBmbG9hdCh0ZXhJZHgpOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAAAADgAQAAdW5pZm9ybSBoYWxmNCB1Q29sb3JfUzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdlRleHR1cmVDb29yZHNfUzA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHVDb2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCk7Cgl9CglvdXRwdXRDb2xvcl9TMCA9IG91dHB1dENvbG9yX1MwICogdGV4Q29sb3I7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAADwAAAGluVGV4dHVyZUNvb3JkcwAAAAAA","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CgAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAsQEAAHVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdlRleHR1cmVDb29yZHNfUzA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gdGV4Q29sb3I7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAPAAAAaW5UZXh0dXJlQ29vcmRzAAAAAAA=","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAAA5AAAAAAABAAAAACAZAAAAA":"CgAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgAAAABFAgAAZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2YXJjY29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1MwLngsIHk9dmFyY2Nvb3JkX1MwLnk7CgloYWxmIGNvdmVyYWdlOwoJaWYgKDAgPT0geF9wbHVzXzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZih5KTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQgZm4gPSB4X3BsdXNfMSAqICh4X3BsdXNfMSAtIDIpOwoJCWZuID0gZm1hKHkseSwgZm4pOwoJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABAAAAHNrZXcZAAAAdHJhbnNsYXRlX2FuZF9sb2NhbHJvdGF0ZQAAAAUAAABjb2xvcgAAAAAAAAA=","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CgAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoAAAAAEQEAAGZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAAAAAA==","CMRQCIAABBYAAAEIXBAAACDQMAABRAFAAAAAAAAAAAAAAAEABYAAAAEAAAAAAAEEBQAAAAA":"CgAAAExTS1MyAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0MiBpbkVsbGlwc2VPZmZzZXQ7CmluIGZsb2F0NCBpbkVsbGlwc2VSYWRpaTsKb3V0IGZsb2F0MiB2RWxsaXBzZU9mZnNldHNfUzA7Cm91dCBmbG9hdDQgdkVsbGlwc2VSYWRpaV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBFbGxpcHNlR2VvbWV0cnlQcm9jZXNzb3IKCXZFbGxpcHNlT2Zmc2V0c19TMCA9IGluRWxsaXBzZU9mZnNldDsKCXZFbGxpcHNlUmFkaWlfUzAgPSBpbkVsbGlwc2VSYWRpaTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAAAHIDAABpbiBmbG9hdDIgdkVsbGlwc2VPZmZzZXRzX1MwOwppbiBmbG9hdDQgdkVsbGlwc2VSYWRpaV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBFbGxpcHNlR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0MiBvZmZzZXQgPSB2RWxsaXBzZU9mZnNldHNfUzAueHk7CglvZmZzZXQgKj0gdkVsbGlwc2VSYWRpaV9TMC54eTsKCWZsb2F0IHRlc3QgPSBkb3Qob2Zmc2V0LCBvZmZzZXQpIC0gMS4wOwoJZmxvYXQyIGdyYWQgPSAyLjAqb2Zmc2V0KnZFbGxpcHNlUmFkaWlfUzAueHk7CglmbG9hdCBncmFkX2RvdCA9IGRvdChncmFkLCBncmFkKTsKCWdyYWRfZG90ID0gbWF4KGdyYWRfZG90LCAxLjE3NTVlLTM4KTsKCWZsb2F0IGludmxlbiA9IGludmVyc2VzcXJ0KGdyYWRfZG90KTsKCWZsb2F0IGVkZ2VBbHBoYSA9IHNhdHVyYXRlKDAuNS10ZXN0Kmludmxlbik7CglvZmZzZXQgPSB2RWxsaXBzZU9mZnNldHNfUzAueHkqdkVsbGlwc2VSYWRpaV9TMC56dzsKCXRlc3QgPSBkb3Qob2Zmc2V0LCBvZmZzZXQpIC0gMS4wOwoJZ3JhZCA9IDIuMCpvZmZzZXQqdkVsbGlwc2VSYWRpaV9TMC56dzsKCWdyYWRfZG90ID0gZG90KGdyYWQsIGdyYWQpOwoJaW52bGVuID0gaW52ZXJzZXNxcnQoZ3JhZF9kb3QpOwoJZWRnZUFscGhhICo9IHNhdHVyYXRlKDAuNSt0ZXN0Kmludmxlbik7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGhhbGYoZWRnZUFscGhhKSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA8AAABpbkVsbGlwc2VPZmZzZXQADgAAAGluRWxsaXBzZVJhZGlpAAAAAAAA","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGABZAA6IAAAAACAAAAAADUAAAAAAAEAAAAAIDEAAAAAAA":"CgAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAAA7AwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9IChibGVuZF9tb2R1bGF0ZShzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHRleENvb3JkKSwgaGFsZjQoMSkpKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAIAAEAAAABJYQAAAAAQAAIAAAAAWCBACAABAAAAANAECAZAAEAAAAAAAAFAAMAAAABAAAAAAABBAM":"CgAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAC8GAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1SW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGYyIHVPZmZzZXRzQW5kS2VybmVsX1MxX2MwWzEzXTsKdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglmbG9hdDIgaW5Db29yZCA9IF9jb29yZHM7CglmbG9hdDIgc3Vic2V0Q29vcmQ7CglzdWJzZXRDb29yZC54ID0gaW5Db29yZC54OwoJc3Vic2V0Q29vcmQueSA9IGluQ29vcmQueTsKCWZsb2F0MiBjbGFtcGVkQ29vcmQ7CgljbGFtcGVkQ29vcmQueCA9IGNsYW1wKHN1YnNldENvb3JkLngsIHVjbGFtcF9TMV9jMF9jMF9jMC54LCB1Y2xhbXBfUzFfYzBfYzBfYzAueik7CgljbGFtcGVkQ29vcmQueSA9IHN1YnNldENvb3JkLnk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBTbW9vdGhfUzFfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgY29vcmQsIGhhbGYyIG9mZnNldEFuZEtlcm5lbCkgCnsKCXJldHVybiBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCAoY29vcmQgKyBvZmZzZXRBbmRLZXJuZWwueCAqIHVJbmNyZW1lbnRfUzFfYzApKSAqIG9mZnNldEFuZEtlcm5lbC55Owp9CmhhbGY0IEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgY29sb3IgPSBoYWxmNCgwKTsKCWZsb2F0MiBjb29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZm9yIChpbnQgaT0wOyBpPDEzOyArK2kpIAoJewoJCWNvbG9yICs9IFNtb290aF9TMV9jMChfaW5wdXQsIGNvb3JkLCB1T2Zmc2V0c0FuZEtlcm5lbF9TMV9jMFtpXSk7Cgl9CglyZXR1cm4gY29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBHYXVzc2lhbkNvbnZvbHV0aW9uX1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAB3QA6AAAEAAAAAAAMAAPEAEAAABAAAAAAB2AAAAAAACAAAAAEBSAAAA":"CgAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAAAeBQAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzI7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MyOwppbiBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglhbHBoYSA9IDEuMCAtIGFscGhhOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CmhhbGY0IENpcmN1bGFyUlJlY3RfUzIoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MyLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MyLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzIueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDQgY2lyY2xlRWRnZTsKCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCWhhbGY0IG91dHB1dF9TMjsKCW91dHB1dF9TMiA9IENpcmN1bGFyUlJlY3RfUzIob3V0cHV0X1MxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMjsKCX0KfQoAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2UAAAAA","HTQAAGAABBYAAAEIXBAAAGEAMAAAAAAAAAAAAAAAQAHAAAAAQAAAAAAAQQGAAAAA":"CgAAAExTS1M/AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5RdWFkRWRnZTsKb3V0IGZsb2F0NCB2UXVhZEVkZ2VfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZEVkZ2UKCXZRdWFkRWRnZV9TMCA9IGluUXVhZEVkZ2U7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgABAAAABQMAAGluIGZsb2F0NCB2UXVhZEVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZEVkZ2UKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGYgZWRnZUFscGhhOwoJaGFsZjIgZHV2ZHggPSBoYWxmMihkRmR4KHZRdWFkRWRnZV9TMC54eSkpOwoJaGFsZjIgZHV2ZHkgPSBoYWxmMihkRmR5KHZRdWFkRWRnZV9TMC54eSkpOwoJaWYgKHZRdWFkRWRnZV9TMC56ID4gMC4wICYmIHZRdWFkRWRnZV9TMC53ID4gMC4wKSAKCXsKCQllZGdlQWxwaGEgPSBoYWxmKG1pbihtaW4odlF1YWRFZGdlX1MwLnosIHZRdWFkRWRnZV9TMC53KSArIDAuNSwgMS4wKSk7Cgl9CgllbHNlIAoJewoJCWhhbGYyIGdGID0gaGFsZjIoaGFsZigyLjAqdlF1YWRFZGdlX1MwLngqZHV2ZHgueCAtIGR1dmR4LnkpLCAgICAgICAgICAgICAgICAgaGFsZigyLjAqdlF1YWRFZGdlX1MwLngqZHV2ZHkueCAtIGR1dmR5LnkpKTsKCQllZGdlQWxwaGEgPSBoYWxmKHZRdWFkRWRnZV9TMC54KnZRdWFkRWRnZV9TMC54IC0gdlF1YWRFZGdlX1MwLnkpOwoJCWVkZ2VBbHBoYSA9IHNhdHVyYXRlKDAuNSAtIGVkZ2VBbHBoYSAvIGxlbmd0aChnRikpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IACgAAAGluUXVhZEVkZ2UAAAAAAAA=","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAEQQGABZAA6IAAAAACAAAAAADUAAAAAAAEAAAAAIDEAAAAAAA":"CgAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABEAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlckV4dGVybmFsT0VTIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9IChibGVuZF9tb2R1bGF0ZShzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHRleENvb3JkKSwgaGFsZjQoMSkpKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAAAAAAA","B2ABSAAABQAAIAABBYAAB7777777777774ABICAAAAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CgAAAExTS1N4AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gaGFsZjQgdUNvbG9yX1MwOwppbiBmbG9hdDIgaW5Qb3NpdGlvbjsKaW4gaGFsZiBpbkNvdmVyYWdlOwpvdXQgaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IGNvbG9yID0gdUNvbG9yX1MwOwoJY29sb3IgPSBjb2xvciAqIGluQ292ZXJhZ2U7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8zX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzFfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAGAQAAaW4gaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAACgAAAGluQ292ZXJhZ2UAAAAAAAA=","HVJAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAABAAAAAABBAMABAAOAAAABAAAAAAABBAMAAA":"CgAAAExTS1MjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cgl2bG9jYWxDb29yZF9TMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAAAAADdAQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdDIgdGV4Q29vcmQ7Cgl0ZXhDb29yZCA9IHZsb2NhbENvb3JkX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSAoYmxlbmRfbW9kdWxhdGUoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCksIG91dHB1dENvbG9yX1MwKSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAAKAAAAbG9jYWxDb29yZAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAAAAEAAAABJYQAAAAAAAAIAAAAAWCBAAAABAAAAANAECAZAAAAAAAAAAAFAAMAAAABAAAAAAABBAM":"CgAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAPAEAAB1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzA7CnVuaWZvcm0gaGFsZjIgdUluY3JlbWVudF9TMV9jMDsKdW5pZm9ybSBoYWxmMiB1T2Zmc2V0c0FuZEtlcm5lbF9TMV9jMFsxM107CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgX2Nvb3Jkcyk7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChfaW5wdXQsIGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzApICogX2Nvb3Jkcy54eTEpOwp9CmhhbGY0IFNtb290aF9TMV9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBjb29yZCwgaGFsZjIgb2Zmc2V0QW5kS2VybmVsKSAKewoJcmV0dXJuIE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfaW5wdXQsIChjb29yZCArIG9mZnNldEFuZEtlcm5lbC54ICogdUluY3JlbWVudF9TMV9jMCkpICogb2Zmc2V0QW5kS2VybmVsLnk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBjb2xvciA9IGhhbGY0KDApOwoJZmxvYXQyIGNvb3JkID0gdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzA7Cglmb3IgKGludCBpPTA7IGk8MTM7ICsraSkgCgl7CgkJY29sb3IgKz0gU21vb3RoX1MxX2MwKF9pbnB1dCwgY29vcmQsIHVPZmZzZXRzQW5kS2VybmVsX1MxX2MwW2ldKTsKCX0KCXJldHVybiBjb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAAAAAA==","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABYQA6AAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CgAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAAB5AwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2UAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAIAAIAAAABLCIABAAAAABAEGABBAMAACAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CgAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAADsAwAAdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZC54ID0gY2xhbXAoc3Vic2V0Q29vcmQueCwgdWNsYW1wX1MxX2MwX2MwLngsIHVjbGFtcF9TMV9jMF9jMC56KTsKCWNsYW1wZWRDb29yZC55ID0gc3Vic2V0Q29vcmQueTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQmxlbmRfUzEoaGFsZjQgX3NyYywgaGFsZjQgX2RzdCkgCnsKCXJldHVybiBibGVuZF9tb2R1bGF0ZShNYXRyaXhFZmZlY3RfUzFfYzAoX3NyYyksIF9zcmMpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwID0gaGFsZjQoMSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQmxlbmRfUzEob3V0cHV0Q29sb3JfUzAsIGhhbGY0KDEpKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfUzEgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAAAAAAA","HUQACAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CgAAAExTS1PPAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAEAAACbAgAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGhhbGY0IHZjb2xvcl9TMDsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAAAAAA=","AYAA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CgAAAExTS1OCAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCXZpbkNpcmNsZUVkZ2VfUzAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMl9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gX3RtcF8wX2luUG9zaXRpb24ueHkwMTsKfQoAAAAAAADqAQAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQAAEAQAAAAGQCBAMQAAAIAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CgAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAHADAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBzdWJzZXRDb29yZC54OwoJY2xhbXBlZENvb3JkLnkgPSBjbGFtcChzdWJzZXRDb29yZC55LCB1Y2xhbXBfUzFfYzAueSwgdWNsYW1wX1MxX2MwLncpOwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAAAAAA==","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAFIBQU7BTXIAAAAAACAAAAAAQFV5W6JEAAAAAYAAAABQEZ2AKAWAQAABAL6SYKDYAAAACAAAAAAQEGIAAAAACAWTWL3EYAAAADAAAAACADHIJJCYCAAAEAP2LRIPAAAAAIAAAAAAABTALI3F5SOAIAABQAAAAAABTUEUZMBAAAAAH5FYUXQAAAAAAAEAAAAAZMRGOQCQFQEAAAAAAAAAGARL2LXJHAAEAAAAAEAAAABSCQX5FQUHQAAAAAAAAAACAA4AAAACAAAAAAACCAYAAAAA":"CgAAAExTS1OGAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfNl9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzBfYzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAAAAdgcAAHVuaWZvcm0gaGFsZjQgdXN0YXJ0X1MxX2MwX2MwX2MwOwp1bmlmb3JtIGhhbGY0IHVlbmRfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMF9jMTsKdW5pZm9ybSBoYWxmNCB1bGVmdEJvcmRlckNvbG9yX1MxX2MwX2MwOwp1bmlmb3JtIGhhbGY0IHVyaWdodEJvcmRlckNvbG9yX1MxX2MwX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc182X1MwOwpoYWxmNCBTaW5nbGVJbnRlcnZhbENvbG9yaXplcl9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CglmbG9hdDIgX3RtcF8xX2Nvb3JkcyA9IF9jb29yZHM7CglyZXR1cm4gaGFsZjQobWl4KHVzdGFydF9TMV9jMF9jMF9jMCwgdWVuZF9TMV9jMF9jMF9jMCwgaGFsZihfdG1wXzFfY29vcmRzLngpKSk7Cn0KaGFsZjQgTGluZWFyTGF5b3V0X1MxX2MwX2MwX2MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMl9pbkNvbG9yID0gX2lucHV0OwoJZmxvYXQyIF90bXBfM19jb29yZHMgPSB2VHJhbnNmb3JtZWRDb29yZHNfNl9TMDsKCXJldHVybiBoYWxmNChoYWxmNChoYWxmKF90bXBfM19jb29yZHMueCkgKyAxZS0wNSwgMS4wLCAwLjAsIDAuMCkpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMF9jMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyTGF5b3V0X1MxX2MwX2MwX2MxX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQ2xhbXBlZEdyYWRpZW50X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfNF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjQgdCA9IE1hdHJpeEVmZmVjdF9TMV9jMF9jMF9jMShfdG1wXzRfaW5Db2xvcik7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghYm9vbChpbnQoMSkpICYmIHQueSA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSBoYWxmNCgwLjApOwoJfQoJZWxzZSBpZiAodC54IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IHVsZWZ0Qm9yZGVyQ29sb3JfUzFfYzBfYzA7Cgl9CgllbHNlIGlmICh0LnggPiAxLjApIAoJewoJCW91dENvbG9yID0gdXJpZ2h0Qm9yZGVyQ29sb3JfUzFfYzBfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzBfYzAoX3RtcF80X2luQ29sb3IsIGZsb2F0MihoYWxmMih0LngsIDAuMCkpKTsKCX0KCXJldHVybiBoYWxmNChvdXRDb2xvcik7Cn0KaGFsZjQgY29sb3JfeGZvcm1fUzFfYzAoZmxvYXQ0IGNvbG9yKSAKewoJY29sb3IucmdiICo9IGNvbG9yLmE7CglyZXR1cm4gaGFsZjQoY29sb3IpOwp9CmhhbGY0IENvbG9yU3BhY2VYZm9ybV9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gY29sb3JfeGZvcm1fUzFfYzAoQ2xhbXBlZEdyYWRpZW50X1MxX2MwX2MwKF9pbnB1dCkpOwp9CmhhbGY0IERpc2FibGVDb3ZlcmFnZUFzQWxwaGFfUzEoaGFsZjQgX2lucHV0KSAKewoJX2lucHV0ID0gQ29sb3JTcGFjZVhmb3JtX1MxX2MwKF9pbnB1dCk7CgloYWxmNCBfdG1wXzVfaW5Db2xvciA9IF9pbnB1dDsKCXJldHVybiBoYWxmNChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gRGlzYWJsZUNvdmVyYWdlQXNBbHBoYV9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAoAAABsb2NhbENvb3JkAAAAAAAA","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEAB5AAAAACQHEB4XIQAQAADQAAAABAAAAAAABAEMVDOMCJKRAAAAAHAAAAAAAAAAACQAGAAAAAQAAAAAAAQQGAAA":"CgAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAABRBQAAY29uc3QgaW50IGtGaWxsQUFfUzFfYzAgPSAxOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzFfYzAgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzFfYzAgPSAzOwp1bmlmb3JtIGZsb2F0NCB1Y2lyY2xlX1MxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZhcmNjb29yZF9TMDsKaGFsZjQgQ2lyY2xlX1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZiBkOwoJaWYgKGludCgzKSA9PSBrSW52ZXJzZUZpbGxCV19TMV9jMCB8fCBpbnQoMykgPT0ga0ludmVyc2VGaWxsQUFfUzFfYzApIAoJewoJCWQgPSBoYWxmKChsZW5ndGgoKHVjaXJjbGVfUzFfYzAueHkgLSBza19GcmFnQ29vcmQueHkpICogdWNpcmNsZV9TMV9jMC53KSAtIDEuMCkgKiB1Y2lyY2xlX1MxX2MwLnopOwoJfQoJZWxzZSAKCXsKCQlkID0gaGFsZigoMS4wIC0gbGVuZ3RoKCh1Y2lyY2xlX1MxX2MwLnh5IC0gc2tfRnJhZ0Nvb3JkLnh5KSAqIHVjaXJjbGVfUzFfYzAudykpICogdWNpcmNsZV9TMV9jMC56KTsKCX0KCXJldHVybiBoYWxmNChoYWxmNChpbnQoMykgPT0ga0ZpbGxBQV9TMV9jMCB8fCBpbnQoMykgPT0ga0ludmVyc2VGaWxsQUFfUzFfYzAgPyBzYXR1cmF0ZShkKSA6IGhhbGYoZCA+IDAuNSA/IDEgOiAwKSkpOwp9CmhhbGY0IEJsZW5kX1MxKGhhbGY0IF9zcmMsIGhhbGY0IF9kc3QpIAp7CglyZXR1cm4gYmxlbmRfbW9kdWxhdGUoX3NyYywgQ2lyY2xlX1MxX2MwKF9zcmMpKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWZsb2F0IHhfcGx1c18xPXZhcmNjb29yZF9TMC54LCB5PXZhcmNjb29yZF9TMC55OwoJaGFsZiBjb3ZlcmFnZTsKCWlmICgwID09IHhfcGx1c18xKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoeSk7Cgl9CgllbHNlIAoJewoJCWZsb2F0IGZuID0geF9wbHVzXzEgKiAoeF9wbHVzXzEgLSAyKTsKCQlmbiA9IGZtYSh5LHksIGZuKTsKCQlmbG9hdCBmbndpZHRoID0gZndpZHRoKGZuKTsKCQljb3ZlcmFnZSA9IC41IC0gaGFsZihmbi9mbndpZHRoKTsKCQljb3ZlcmFnZSA9IGNsYW1wKGNvdmVyYWdlLCAwLCAxKTsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoY292ZXJhZ2UpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQmxlbmRfUzEob3V0cHV0Q292ZXJhZ2VfUzAsIGhhbGY0KDEpKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABAAAAHNrZXcZAAAAdHJhbnNsYXRlX2FuZF9sb2NhbHJvdGF0ZQAAAAUAAABjb2xvcgAAAAAAAAA=","GEMAAAYAAEHAAAARC4EAAAQWBQAAAAAAAAAQAAAAIBCAAAGQAEAAAAAQAAAABAEQAEAAAAA":"CgAAAExTS1NUAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBoYWxmMyBpblNoYWRvd1BhcmFtczsKb3V0IGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBSUmVjdFNoYWRvdwoJdmluU2hhZG93UGFyYW1zX1MwID0gaW5TaGFkb3dQYXJhbXM7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAALAgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBSUmVjdFNoYWRvdwoJaGFsZjMgc2hhZG93UGFyYW1zOwoJc2hhZG93UGFyYW1zID0gdmluU2hhZG93UGFyYW1zX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZiBkID0gbGVuZ3RoKHNoYWRvd1BhcmFtcy54eSk7CglmbG9hdDIgdXYgPSBmbG9hdDIoc2hhZG93UGFyYW1zLnogKiAoMS4wIC0gZCksIDAuNSk7CgloYWxmIGZhY3RvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdXYpLjAwMHIuYTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZmFjdG9yKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAOAAAAaW5TaGFkb3dQYXJhbXMAAAAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABZQA6AAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CgAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACPAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCWFscGhhID0gMS4wIC0gYWxwaGE7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDQgY2lyY2xlRWRnZTsKCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAIBAIAAAABLCIIBAAAAABAEGABBAMAACAIAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CgAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAADJAwAAdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZCA9IGNsYW1wKHN1YnNldENvb3JkLCB1Y2xhbXBfUzFfYzBfYzAueHksIHVjbGFtcF9TMV9jMF9jMC56dyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMF9jMChfaW5wdXQpOwp9CmhhbGY0IEJsZW5kX1MxKGhhbGY0IF9zcmMsIGhhbGY0IF9kc3QpIAp7CglyZXR1cm4gYmxlbmRfbW9kdWxhdGUoTWF0cml4RWZmZWN0X1MxX2MwKF9zcmMpLCBfc3JjKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IEJsZW5kX1MxKG91dHB1dENvbG9yX1MwLCBoYWxmNCgxKSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAAAAAA==","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEADZABYAAAIAAAAAACQAGAAAAAQAAAAAAAQQG":"CgAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAADUAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpoYWxmNCBDaXJjdWxhclJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1MwLngsIHk9dmFyY2Nvb3JkX1MwLnk7CgloYWxmIGNvdmVyYWdlOwoJaWYgKDAgPT0geF9wbHVzXzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZih5KTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQgZm4gPSB4X3BsdXNfMSAqICh4X3BsdXNfMSAtIDIpOwoJCWZuID0gZm1hKHkseSwgZm4pOwoJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABAAAAHNrZXcZAAAAdHJhbnNsYXRlX2FuZF9sb2NhbHJvdGF0ZQAAAAUAAABjb2xvcgAAAAAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAAYQADAAAEAFEURUKQKAAAYAAAAAAAAIAAAABSCICWKY2FAEAAAMAAAAAAAAAAAAAIADQAAAAIAAAAAAAIIDAAAA":"CgAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAAB+BQAAY29uc3QgaW50IGtGaWxsQldfUzFfYzAgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzFfYzAgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzFfYzAgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzFfYzA7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKaGFsZjQgUmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgY292ZXJhZ2U7CglpZiAoaW50KDEpID09IGtGaWxsQldfUzFfYzAgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxX2MwKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TMV9jMC56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fUzFfYzAueHksIHNrX0ZyYWdDb29yZC54eSkpKSk7Cgl9CgllbHNlIAoJewoJCWhhbGY0IGRpc3RzNCA9IHNhdHVyYXRlKGhhbGY0KDEuMCwgMS4wLCAtMS4wLCAtMS4wKSAqIGhhbGY0KHNrX0ZyYWdDb29yZC54eXh5IC0gdXJlY3RVbmlmb3JtX1MxX2MwKSk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJaWYgKGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMV9jMCB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQUFfUzFfYzApIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gaGFsZjQoaGFsZjQoY292ZXJhZ2UpKTsKfQpoYWxmNCBCbGVuZF9TMShoYWxmNCBfc3JjLCBoYWxmNCBfZHN0KSAKewoJcmV0dXJuIGJsZW5kX21vZHVsYXRlKFJlY3RfUzFfYzAoX3NyYyksIF9zcmMpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQmxlbmRfUzEob3V0cHV0Q292ZXJhZ2VfUzAsIGhhbGY0KDEpKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2UAAAAA","AYQQ5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CgAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAB7AgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGYgZGlzdGFuY2VUb0lubmVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKGQgLSBjaXJjbGVFZGdlLncpKTsKCWhhbGYgaW5uZXJBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9Jbm5lckVkZ2UpOwoJZWRnZUFscGhhICo9IGlubmVyQWxwaGE7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAABAEAAAABJYQAAAAAACAIAAAAAWCBAAAIBAAAAANAECAZAAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"CgAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAC8GAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1SW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGYyIHVPZmZzZXRzQW5kS2VybmVsX1MxX2MwWzEzXTsKdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglmbG9hdDIgaW5Db29yZCA9IF9jb29yZHM7CglmbG9hdDIgc3Vic2V0Q29vcmQ7CglzdWJzZXRDb29yZC54ID0gaW5Db29yZC54OwoJc3Vic2V0Q29vcmQueSA9IGluQ29vcmQueTsKCWZsb2F0MiBjbGFtcGVkQ29vcmQ7CgljbGFtcGVkQ29vcmQueCA9IHN1YnNldENvb3JkLng7CgljbGFtcGVkQ29vcmQueSA9IGNsYW1wKHN1YnNldENvb3JkLnksIHVjbGFtcF9TMV9jMF9jMF9jMC55LCB1Y2xhbXBfUzFfYzBfYzBfYzAudyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBTbW9vdGhfUzFfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgY29vcmQsIGhhbGYyIG9mZnNldEFuZEtlcm5lbCkgCnsKCXJldHVybiBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCAoY29vcmQgKyBvZmZzZXRBbmRLZXJuZWwueCAqIHVJbmNyZW1lbnRfUzFfYzApKSAqIG9mZnNldEFuZEtlcm5lbC55Owp9CmhhbGY0IEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgY29sb3IgPSBoYWxmNCgwKTsKCWZsb2F0MiBjb29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZm9yIChpbnQgaT0wOyBpPDEzOyArK2kpIAoJewoJCWNvbG9yICs9IFNtb290aF9TMV9jMChfaW5wdXQsIGNvb3JkLCB1T2Zmc2V0c0FuZEtlcm5lbF9TMV9jMFtpXSk7Cgl9CglyZXR1cm4gY29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBHYXVzc2lhbkNvbnZvbHV0aW9uX1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAAAAAAA","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAADEAANAAAAALHCKLMRAAAAAAAAABAAAAAGJBCFLQVBWAQAAAAAAQAAAAAMACQCAACAAAAA2AIBAEIAAAAAAAAAAAAIADQAAAAIAAAAAAAIIDAAAAAA":"CgAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoBAAAAeAQAAHVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1Y2lyY2xlRGF0YV9TMV9jMDsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBfY29vcmRzKS4wMDByOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBDaXJjbGVCbHVyX1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjIgdmVjID0gaGFsZjIoKHNrX0ZyYWdDb29yZC54eSAtIGZsb2F0Mih1Y2lyY2xlRGF0YV9TMV9jMC54eSkpICogZmxvYXQodWNpcmNsZURhdGFfUzFfYzAudykpOwoJaGFsZiBkaXN0ID0gbGVuZ3RoKHZlYykgKyAoMC41IC0gdWNpcmNsZURhdGFfUzFfYzAueikgKiB1Y2lyY2xlRGF0YV9TMV9jMC53OwoJcmV0dXJuIGhhbGY0KE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfdG1wXzBfaW5Db2xvciwgZmxvYXQyKGhhbGYyKGRpc3QsIDAuNSkpKS53d3d3KTsKfQpoYWxmNCBCbGVuZF9TMShoYWxmNCBfc3JjLCBoYWxmNCBfZHN0KSAKewoJcmV0dXJuIGJsZW5kX21vZHVsYXRlKENpcmNsZUJsdXJfUzFfYzAoX3NyYyksIF9zcmMpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQmxlbmRfUzEob3V0cHV0Q292ZXJhZ2VfUzAsIGhhbGY0KDEpKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAAAAAA=","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CgAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoBAAAAoAIAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzE7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAAAAAA==","BYIBQAAABQAAIAABBYAAAEIXBAAP777777777777AAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CgAAAExTS1M+AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwpvdXQgaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IGNvbG9yID0gaW5Db2xvcjsKCXZjb2xvcl9TMCA9IGNvbG9yOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzNfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IF90bXBfMV9pblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAABgEAAGluIGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIERlZmF1bHRHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAAAAAA=","HWQACAAAABAAADAAAIOAAAAADIIAAIRODAAP577774DSAIAA737777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CgAAAExTS1ONAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQgY292ZXJhZ2U7CmluIGhhbGY0IGNvbG9yOwppbiBmbG9hdDQgZ2VvbVN1YnNldDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQgdmNvdmVyYWdlX1MwOwpmbGF0IG91dCBmbG9hdDQgdmdlb21TdWJzZXRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJdmdlb21TdWJzZXRfUzAgPSBnZW9tU3Vic2V0OwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAAB5AgAAZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0IHZjb3ZlcmFnZV9TMDsKZmxhdCBpbiBmbG9hdDQgdmdlb21TdWJzZXRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCBjb3ZlcmFnZSA9IHZjb3ZlcmFnZV9TMDsKCWZsb2F0NCBnZW9TdWJzZXQ7CglnZW9TdWJzZXQgPSB2Z2VvbVN1YnNldF9TMDsKCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEsIDEsIC0xLCAtMSkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIGdlb1N1YnNldCksIDAsIDEpOwoJaGFsZjIgZGlzdHMyID0gZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3IC0gMTsKCWhhbGYgc3Vic2V0Q292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJY292ZXJhZ2UgPSBtaW4oY292ZXJhZ2UsIHN1YnNldENvdmVyYWdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoaGFsZihjb3ZlcmFnZSkpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAIAAAAcG9zaXRpb24IAAAAY292ZXJhZ2UFAAAAY29sb3IAAAAKAAAAZ2VvbVN1YnNldAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAAAAIAAAABLAIABAAAAABAEGABBAMAAAAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CgAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAC2AgAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoX2lucHV0KTsKfQpoYWxmNCBCbGVuZF9TMShoYWxmNCBfc3JjLCBoYWxmNCBfZHN0KSAKewoJcmV0dXJuIGJsZW5kX21vZHVsYXRlKE1hdHJpeEVmZmVjdF9TMV9jMChfc3JjKSwgX3NyYyk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBCbGVuZF9TMShvdXRwdXRDb2xvcl9TMCwgaGFsZjQoMSkpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAAAAAA==","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEAKPABAAAAAAB2AAAAAAACAAAAAEBSAAAAAAA":"CgAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAACbBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBmbG9hdDIgdWludlJhZGlpWFlfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpoYWxmNCBFbGxpcHRpY2FsUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CglmbG9hdDIgWiA9IGR4eSAqIHVpbnZSYWRpaVhZX1MxLnh5OwoJaGFsZiBpbXBsaWNpdCA9IGhhbGYoZG90KFosIGR4eSkgLSAxLjApOwoJaGFsZiBncmFkX2RvdCA9IGhhbGYoNC4wICogZG90KFosIFopKTsKCWdyYWRfZG90ID0gbWF4KGdyYWRfZG90LCAxLjBlLTQpOwoJaGFsZiBhcHByb3hfZGlzdCA9IGltcGxpY2l0ICogaGFsZihpbnZlcnNlc3FydChncmFkX2RvdCkpOwoJaGFsZiBhbHBoYSA9IGNsYW1wKDAuNSArIGFwcHJveF9kaXN0LCAwLjAsIDEuMCk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCB4X3BsdXNfMT12YXJjY29vcmRfUzAueCwgeT12YXJjY29vcmRfUzAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGNvdmVyYWdlKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IEVsbGlwdGljYWxSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAEAAAAc2tldxkAAAB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlAAAABQAAAGNvbG9yAAAAAAAAAA==","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGAAZAADIAAAACU53QJEKAAAAAAMAAAAAIAAAAAAGIRDFB2XASAUAABQAAAAAAAAAAAAADUAAAAAAAEAAAAAIDEAAA":"CgAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAAC4BAAAY29uc3QgaW50IGtGaWxsQUFfUzFfYzAgPSAxOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzFfYzAgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzFfYzAgPSAzOwp1bmlmb3JtIGZsb2F0NCB1Y2lyY2xlX1MxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZsb2NhbENvb3JkX1MwOwpoYWxmNCBDaXJjbGVfUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGQ7CglpZiAoaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxX2MwIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMV9jMCkgCgl7CgkJZCA9IGhhbGYoKGxlbmd0aCgodWNpcmNsZV9TMV9jMC54eSAtIHNrX0ZyYWdDb29yZC54eSkgKiB1Y2lyY2xlX1MxX2MwLncpIC0gMS4wKSAqIHVjaXJjbGVfUzFfYzAueik7Cgl9CgllbHNlIAoJewoJCWQgPSBoYWxmKCgxLjAgLSBsZW5ndGgoKHVjaXJjbGVfUzFfYzAueHkgLSBza19GcmFnQ29vcmQueHkpICogdWNpcmNsZV9TMV9jMC53KSkgKiB1Y2lyY2xlX1MxX2MwLnopOwoJfQoJcmV0dXJuIGhhbGY0KGhhbGY0KGludCgxKSA9PSBrRmlsbEFBX1MxX2MwIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMV9jMCA/IHNhdHVyYXRlKGQpIDogaGFsZihkID4gMC41ID8gMSA6IDApKSk7Cn0KaGFsZjQgQmxlbmRfUzEoaGFsZjQgX3NyYywgaGFsZjQgX2RzdCkgCnsKCXJldHVybiBibGVuZF9tb2R1bGF0ZShfc3JjLCBDaXJjbGVfUzFfYzAoX3NyYykpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwID0gaGFsZjQoMSk7CglmbG9hdDIgdGV4Q29vcmQ7Cgl0ZXhDb29yZCA9IHZsb2NhbENvb3JkX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSAoYmxlbmRfbW9kdWxhdGUoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCksIGhhbGY0KDEpKSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQmxlbmRfUzEob3V0cHV0Q292ZXJhZ2VfUzAsIGhhbGY0KDEpKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAAAAAAA","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAFIBQU7BTXIAAAAAACAWXW3ZEQAAAADAAAAAGATHIBICYCAAAEBP2LBIPAAAAAIAAAAAEARRALJ3F5SMAAAABQAAAABABTUEURMBAAACAH5FYUHQAAAAAAAEAAAAAZ4RGGRCQFAEAAAAAAAAAGARP2LVJPAAAAAAAAEAAAABSKRXZFAUHQAAAAAAAAAACAA4AAAACAAAAAAACCAYAA":"CgAAAExTS1OAAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfNV9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAIIGAAB1bmlmb3JtIGhhbGY0IHVzdGFydF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1ZW5kX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzE7CnVuaWZvcm0gaGFsZjQgdWxlZnRCb3JkZXJDb2xvcl9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TMV9jMDsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKaGFsZjQgU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJZmxvYXQyIF90bXBfMV9jb29yZHMgPSBfY29vcmRzOwoJcmV0dXJuIGhhbGY0KG1peCh1c3RhcnRfUzFfYzBfYzAsIHVlbmRfUzFfYzBfYzAsIGhhbGYoX3RtcF8xX2Nvb3Jkcy54KSkpOwp9CmhhbGY0IExpbmVhckxheW91dF9TMV9jMF9jMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzJfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzNfY29vcmRzID0gdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CglyZXR1cm4gaGFsZjQoaGFsZjQoaGFsZihfdG1wXzNfY29vcmRzLngpICsgMWUtMDUsIDEuMCwgMC4wLCAwLjApKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIExpbmVhckxheW91dF9TMV9jMF9jMV9jMChfaW5wdXQpOwp9CmhhbGY0IENsYW1wZWRHcmFkaWVudF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzRfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGY0IHQgPSBNYXRyaXhFZmZlY3RfUzFfYzBfYzEoX3RtcF80X2luQ29sb3IpOwoJaGFsZjQgb3V0Q29sb3I7CglpZiAoIWJvb2woaW50KDEpKSAmJiB0LnkgPCAwLjApIAoJewoJCW91dENvbG9yID0gaGFsZjQoMC4wKTsKCX0KCWVsc2UgaWYgKHQueCA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSB1bGVmdEJvcmRlckNvbG9yX1MxX2MwOwoJfQoJZWxzZSBpZiAodC54ID4gMS4wKSAKCXsKCQlvdXRDb2xvciA9IHVyaWdodEJvcmRlckNvbG9yX1MxX2MwOwoJfQoJZWxzZSAKCXsKCQlvdXRDb2xvciA9IFNpbmdsZUludGVydmFsQ29sb3JpemVyX1MxX2MwX2MwKF90bXBfNF9pbkNvbG9yLCBmbG9hdDIoaGFsZjIodC54LCAwLjApKSk7Cgl9CglyZXR1cm4gaGFsZjQob3V0Q29sb3IpOwp9CmhhbGY0IERpc2FibGVDb3ZlcmFnZUFzQWxwaGFfUzEoaGFsZjQgX2lucHV0KSAKewoJX2lucHV0ID0gQ2xhbXBlZEdyYWRpZW50X1MxX2MwKF9pbnB1dCk7CgloYWxmNCBfdG1wXzVfaW5Db2xvciA9IF9pbnB1dDsKCXJldHVybiBoYWxmNChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gRGlzYWJsZUNvdmVyYWdlQXNBbHBoYV9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAoAAABsb2NhbENvb3JkAAAAAAAA","HWQACAAAABAAADAAAIOAAAAADIIAAIRODAAP577774DSAIAA737777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CgAAAExTS1ONAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQgY292ZXJhZ2U7CmluIGhhbGY0IGNvbG9yOwppbiBmbG9hdDQgZ2VvbVN1YnNldDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQgdmNvdmVyYWdlX1MwOwpmbGF0IG91dCBmbG9hdDQgdmdlb21TdWJzZXRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJdmdlb21TdWJzZXRfUzAgPSBnZW9tU3Vic2V0OwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAAAIBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdCB2Y292ZXJhZ2VfUzA7CmZsYXQgaW4gZmxvYXQ0IHZnZW9tU3Vic2V0X1MwOwpoYWxmNCBDaXJjdWxhclJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCBjb3ZlcmFnZSA9IHZjb3ZlcmFnZV9TMDsKCWZsb2F0NCBnZW9TdWJzZXQ7CglnZW9TdWJzZXQgPSB2Z2VvbVN1YnNldF9TMDsKCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEsIDEsIC0xLCAtMSkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIGdlb1N1YnNldCksIDAsIDEpOwoJaGFsZjIgZGlzdHMyID0gZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3IC0gMTsKCWhhbGYgc3Vic2V0Q292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJY292ZXJhZ2UgPSBtaW4oY292ZXJhZ2UsIHN1YnNldENvdmVyYWdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoaGFsZihjb3ZlcmFnZSkpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY3VsYXJSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAIAAAAcG9zaXRpb24IAAAAY292ZXJhZ2UFAAAAY29sb3IAAAAKAAAAZ2VvbVN1YnNldAAAAAAAAA==","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAIAHSADQAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"CgAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAABAAAAQAMAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzE7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1MwOwpmbGF0IGluIGZsb2F0IHZUZXhJbmRleF9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gdGV4Q29sb3I7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAPAAAAaW5UZXh0dXJlQ29vcmRzAAAAAAA=","EADQAAAAAEAAAAAUAABQAAQPAAABCFYMAAKAUEAAAAAAAAABAAAAAAAAAAANAAIAAAABAAAAACAJAAIAAAAA":"CgAAAExTS1NyAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc0RpbWVuc2lvbnNJbnZfUzA7CmluIGZsb2F0MyBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgZmxvYXQyIHZJbnRUZXh0dXJlQ29vcmRzX1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIERpc3RhbmNlRmllbGRQYXRoCglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkcyAqIHVBdGxhc0RpbWVuc2lvbnNJbnZfUzA7Cgl2VGV4SW5kZXhfUzAgPSBmbG9hdCh0ZXhJZHgpOwoJdkludFRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkczsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MyBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IGluUG9zaXRpb24ueHkwejsKfQoAAAAAAACfAgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBpbiBmbG9hdCB2VGV4SW5kZXhfUzA7CmluIGZsb2F0MiB2SW50VGV4dHVyZUNvb3Jkc19TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBEaXN0YW5jZUZpZWxkUGF0aAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQyIHV2ID0gdlRleHR1cmVDb29yZHNfUzA7CgloYWxmNCB0ZXhDb2xvcjsKCXsKCQl0ZXhDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdXYpLnJycnI7Cgl9CgloYWxmIGRpc3RhbmNlID0gNy45Njg3NSoodGV4Q29sb3IuciAtIDAuNTAxOTYwNzg0MzEpOwoJaGFsZiBhZndpZHRoOwoJYWZ3aWR0aCA9IGFicygwLjY1KmhhbGYoZEZkeCh2SW50VGV4dHVyZUNvb3Jkc19TMC54KSkpOwoJaGFsZiB2YWwgPSBzbW9vdGhzdGVwKC1hZndpZHRoLCBhZndpZHRoLCBkaXN0YW5jZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KHZhbCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADwAAAGluVGV4dHVyZUNvb3JkcwAAAAAA"}} \ No newline at end of file +{"platform":"android","name":"SM G970N","engineRevision":"1ac611c64eadbd93c5f5aba5494b8fc3b35ee952","data":{"GEMAAAYAAEHAAAARC4EAAAQWBQAAAAAAAAAQAAAAIBCAAAGQAEAAAAAQAAAABAEQAEAAAAA":"DAAAAExTS1NUAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBoYWxmMyBpblNoYWRvd1BhcmFtczsKb3V0IGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBSUmVjdFNoYWRvdwoJdmluU2hhZG93UGFyYW1zX1MwID0gaW5TaGFkb3dQYXJhbXM7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAALAgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBSUmVjdFNoYWRvdwoJaGFsZjMgc2hhZG93UGFyYW1zOwoJc2hhZG93UGFyYW1zID0gdmluU2hhZG93UGFyYW1zX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZiBkID0gbGVuZ3RoKHNoYWRvd1BhcmFtcy54eSk7CglmbG9hdDIgdXYgPSBmbG9hdDIoc2hhZG93UGFyYW1zLnogKiAoMS4wIC0gZCksIDAuNSk7CgloYWxmIGZhY3RvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdXYpLjAwMHIuYTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZmFjdG9yKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAOAAAAaW5TaGFkb3dQYXJhbXMAAAAAAAA=","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAIAD2AAAAAGQMH4A6TYBAAADAAAAACAAAAAAQCGHIGH6YNJQAAAABQAAAAAAAAAAAAFAAMAAAABAAAAAAABBAMAAA":"DAAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAABAAAARQUAAGNvbnN0IGludCBrRmlsbEJXX1MxX2MwID0gMDsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEJXX1MxX2MwID0gMjsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEFBX1MxX2MwID0gMzsKdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1MxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1MwOwpmbGF0IGluIGZsb2F0IHZUZXhJbmRleF9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7CmhhbGY0IFJlY3RfUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGNvdmVyYWdlOwoJaWYgKGludCgxKSA9PSBrRmlsbEJXX1MxX2MwIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMV9jMCkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKGFsbChncmVhdGVyVGhhbihmbG9hdDQoc2tfRnJhZ0Nvb3JkLnh5LCB1cmVjdFVuaWZvcm1fUzFfYzAuencpLCBmbG9hdDQodXJlY3RVbmlmb3JtX1MxX2MwLnh5LCBza19GcmFnQ29vcmQueHkpKSkpOwoJfQoJZWxzZSAKCXsKCQloYWxmNCBkaXN0czQgPSBzYXR1cmF0ZShoYWxmNCgxLjAsIDEuMCwgLTEuMCwgLTEuMCkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIHVyZWN0VW5pZm9ybV9TMV9jMCkpOwoJCWhhbGYyIGRpc3RzMiA9IChkaXN0czQueHkgKyBkaXN0czQuencpIC0gMS4wOwoJCWNvdmVyYWdlID0gZGlzdHMyLnggKiBkaXN0czIueTsKCX0KCWlmIChpbnQoMSkgPT0ga0ludmVyc2VGaWxsQldfUzFfYzAgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEFBX1MxX2MwKSAKCXsKCQljb3ZlcmFnZSA9IDEuMCAtIGNvdmVyYWdlOwoJfQoJcmV0dXJuIGhhbGY0KGhhbGY0KGNvdmVyYWdlKSk7Cn0KaGFsZjQgQmxlbmRfUzEoaGFsZjQgX3NyYywgaGFsZjQgX2RzdCkgCnsKCXJldHVybiBibGVuZF9tb2R1bGF0ZShSZWN0X1MxX2MwKF9zcmMpLCBfc3JjKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZjQgdGV4Q29sb3I7Cgl7CgkJdGV4Q29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHZUZXh0dXJlQ29vcmRzX1MwKS5ycnJyOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSB0ZXhDb2xvcjsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IEJsZW5kX1MxKG91dHB1dENvdmVyYWdlX1MwLCBoYWxmNCgxKSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADwAAAGluVGV4dHVyZUNvb3JkcwAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABYQA6AAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"DAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAAB5AwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2UAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQJAAAAAAABAEAAAABJSQAAAAAACAIAAAAAWCBAAAIBAAAAANAECAZAAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"DAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAC8GAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1SW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGYyIHVPZmZzZXRzQW5kS2VybmVsX1MxX2MwWzEwXTsKdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglmbG9hdDIgaW5Db29yZCA9IF9jb29yZHM7CglmbG9hdDIgc3Vic2V0Q29vcmQ7CglzdWJzZXRDb29yZC54ID0gaW5Db29yZC54OwoJc3Vic2V0Q29vcmQueSA9IGluQ29vcmQueTsKCWZsb2F0MiBjbGFtcGVkQ29vcmQ7CgljbGFtcGVkQ29vcmQueCA9IHN1YnNldENvb3JkLng7CgljbGFtcGVkQ29vcmQueSA9IGNsYW1wKHN1YnNldENvb3JkLnksIHVjbGFtcF9TMV9jMF9jMF9jMC55LCB1Y2xhbXBfUzFfYzBfYzBfYzAudyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBTbW9vdGhfUzFfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgY29vcmQsIGhhbGYyIG9mZnNldEFuZEtlcm5lbCkgCnsKCXJldHVybiBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCAoY29vcmQgKyBvZmZzZXRBbmRLZXJuZWwueCAqIHVJbmNyZW1lbnRfUzFfYzApKSAqIG9mZnNldEFuZEtlcm5lbC55Owp9CmhhbGY0IEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgY29sb3IgPSBoYWxmNCgwKTsKCWZsb2F0MiBjb29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZm9yIChpbnQgaT0wOyBpPDEwOyArK2kpIAoJewoJCWNvbG9yICs9IFNtb290aF9TMV9jMChfaW5wdXQsIGNvb3JkLCB1T2Zmc2V0c0FuZEtlcm5lbF9TMV9jMFtpXSk7Cgl9CglyZXR1cm4gY29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBHYXVzc2lhbkNvbnZvbHV0aW9uX1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAAAAAAA","HVJAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAABAAAAAABBAMABAAOAAAABAAAAAAABBAMAAA":"DAAAAExTS1MjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cgl2bG9jYWxDb29yZF9TMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAAAAADdAQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdDIgdGV4Q29vcmQ7Cgl0ZXhDb29yZCA9IHZsb2NhbENvb3JkX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSAoYmxlbmRfbW9kdWxhdGUoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCksIG91dHB1dENvbG9yX1MwKSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAAKAAAAbG9jYWxDb29yZAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQJAAAAAAIAAEAAAABJSQAAAAAQAAIAAAAAWCBACAABAAAAANAECAZAAEAAAAAAAAFAAMAAAABAAAAAAABBAM":"DAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAC8GAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1SW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGYyIHVPZmZzZXRzQW5kS2VybmVsX1MxX2MwWzEwXTsKdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglmbG9hdDIgaW5Db29yZCA9IF9jb29yZHM7CglmbG9hdDIgc3Vic2V0Q29vcmQ7CglzdWJzZXRDb29yZC54ID0gaW5Db29yZC54OwoJc3Vic2V0Q29vcmQueSA9IGluQ29vcmQueTsKCWZsb2F0MiBjbGFtcGVkQ29vcmQ7CgljbGFtcGVkQ29vcmQueCA9IGNsYW1wKHN1YnNldENvb3JkLngsIHVjbGFtcF9TMV9jMF9jMF9jMC54LCB1Y2xhbXBfUzFfYzBfYzBfYzAueik7CgljbGFtcGVkQ29vcmQueSA9IHN1YnNldENvb3JkLnk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBTbW9vdGhfUzFfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgY29vcmQsIGhhbGYyIG9mZnNldEFuZEtlcm5lbCkgCnsKCXJldHVybiBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCAoY29vcmQgKyBvZmZzZXRBbmRLZXJuZWwueCAqIHVJbmNyZW1lbnRfUzFfYzApKSAqIG9mZnNldEFuZEtlcm5lbC55Owp9CmhhbGY0IEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgY29sb3IgPSBoYWxmNCgwKTsKCWZsb2F0MiBjb29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZm9yIChpbnQgaT0wOyBpPDEwOyArK2kpIAoJewoJCWNvbG9yICs9IFNtb290aF9TMV9jMChfaW5wdXQsIGNvb3JkLCB1T2Zmc2V0c0FuZEtlcm5lbF9TMV9jMFtpXSk7Cgl9CglyZXR1cm4gY29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBHYXVzc2lhbkNvbnZvbHV0aW9uX1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQBAAAQAAAAGQCBAMQACAAAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"DAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAHADAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBjbGFtcChzdWJzZXRDb29yZC54LCB1Y2xhbXBfUzFfYzAueCwgdWNsYW1wX1MxX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAAAAAA==","HTQAAGAABBYAAAEIXBAAAGEAMAAAAAAAAAAAAAAAQAHAAAAAQAAAAAAAQQGAAAAA":"DAAAAExTS1M/AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5RdWFkRWRnZTsKb3V0IGZsb2F0NCB2UXVhZEVkZ2VfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZEVkZ2UKCXZRdWFkRWRnZV9TMCA9IGluUXVhZEVkZ2U7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgABAAAABQMAAGluIGZsb2F0NCB2UXVhZEVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZEVkZ2UKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGYgZWRnZUFscGhhOwoJaGFsZjIgZHV2ZHggPSBoYWxmMihkRmR4KHZRdWFkRWRnZV9TMC54eSkpOwoJaGFsZjIgZHV2ZHkgPSBoYWxmMihkRmR5KHZRdWFkRWRnZV9TMC54eSkpOwoJaWYgKHZRdWFkRWRnZV9TMC56ID4gMC4wICYmIHZRdWFkRWRnZV9TMC53ID4gMC4wKSAKCXsKCQllZGdlQWxwaGEgPSBoYWxmKG1pbihtaW4odlF1YWRFZGdlX1MwLnosIHZRdWFkRWRnZV9TMC53KSArIDAuNSwgMS4wKSk7Cgl9CgllbHNlIAoJewoJCWhhbGYyIGdGID0gaGFsZjIoaGFsZigyLjAqdlF1YWRFZGdlX1MwLngqZHV2ZHgueCAtIGR1dmR4LnkpLCAgICAgICAgICAgICAgICAgaGFsZigyLjAqdlF1YWRFZGdlX1MwLngqZHV2ZHkueCAtIGR1dmR5LnkpKTsKCQllZGdlQWxwaGEgPSBoYWxmKHZRdWFkRWRnZV9TMC54KnZRdWFkRWRnZV9TMC54IC0gdlF1YWRFZGdlX1MwLnkpOwoJCWVkZ2VBbHBoYSA9IHNhdHVyYXRlKDAuNSAtIGVkZ2VBbHBoYSAvIGxlbmd0aChnRikpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IACgAAAGluUXVhZEVkZ2UAAAAAAAA=","DASAAAAAQAAWAABAYAAQBYH7777Z6QQBAEAAAAAAEAAAAAAAEBSAAAB2AAAAAAACAAAAAEBSAAAAA":"DAAAAExTS1PVAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBCaXRtYXBUZXh0CglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkcyAqIHVBdGxhc1NpemVJbnZfUzA7Cgl2VGV4SW5kZXhfUzAgPSBmbG9hdCh0ZXhJZHgpOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAAAADgAQAAdW5pZm9ybSBoYWxmNCB1Q29sb3JfUzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdlRleHR1cmVDb29yZHNfUzA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHVDb2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCk7Cgl9CglvdXRwdXRDb2xvcl9TMCA9IG91dHB1dENvbG9yX1MwICogdGV4Q29sb3I7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAADwAAAGluVGV4dHVyZUNvb3JkcwAAAAAA","AYTRVAADQAAAOAEARAFQJAABBADAAAILBYAACCYUQD777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"DAAAAExTS1NyAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7CmluIGhhbGYzIGluQ2xpcFBsYW5lOwppbiBoYWxmMyBpbklzZWN0UGxhbmU7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGYzIHZpbkNsaXBQbGFuZV9TMDsKb3V0IGhhbGYzIHZpbklzZWN0UGxhbmVfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCXZpbkNpcmNsZUVkZ2VfUzAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5DbGlwUGxhbmVfUzAgPSBpbkNsaXBQbGFuZTsKCXZpbklzZWN0UGxhbmVfUzAgPSBpbklzZWN0UGxhbmU7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gdWxvY2FsTWF0cml4X1MwLnh6ICogaW5Qb3NpdGlvbiArIHVsb2NhbE1hdHJpeF9TMC55dzsKCXNrX1Bvc2l0aW9uID0gX3RtcF8wX2luUG9zaXRpb24ueHkwMTsKfQoAAAAAAADdAwAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGYzIHZpbkNsaXBQbGFuZV9TMDsKaW4gaGFsZjMgdmluSXNlY3RQbGFuZV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGYzIGNsaXBQbGFuZTsKCWNsaXBQbGFuZSA9IHZpbkNsaXBQbGFuZV9TMDsKCWhhbGYzIGlzZWN0UGxhbmU7Cglpc2VjdFBsYW5lID0gdmluSXNlY3RQbGFuZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZiBkaXN0YW5jZVRvSW5uZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoZCAtIGNpcmNsZUVkZ2UudykpOwoJaGFsZiBpbm5lckFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb0lubmVyRWRnZSk7CgllZGdlQWxwaGEgKj0gaW5uZXJBbHBoYTsKCWhhbGYgY2xpcCA9IGhhbGYoc2F0dXJhdGUoY2lyY2xlRWRnZS56ICogZG90KGNpcmNsZUVkZ2UueHksIGNsaXBQbGFuZS54eSkgKyBjbGlwUGxhbmUueikpOwoJY2xpcCAqPSBoYWxmKHNhdHVyYXRlKGNpcmNsZUVkZ2UueiAqIGRvdChjaXJjbGVFZGdlLnh5LCBpc2VjdFBsYW5lLnh5KSArIGlzZWN0UGxhbmUueikpOwoJZWRnZUFscGhhICo9IGNsaXA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlCwAAAGluQ2xpcFBsYW5lAAwAAABpbklzZWN0UGxhbmUAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAIBAIAAAABLCIIBAAAAABAEGABBAMAACAIAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"DAAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAADJAwAAdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZCA9IGNsYW1wKHN1YnNldENvb3JkLCB1Y2xhbXBfUzFfYzBfYzAueHksIHVjbGFtcF9TMV9jMF9jMC56dyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMF9jMChfaW5wdXQpOwp9CmhhbGY0IEJsZW5kX1MxKGhhbGY0IF9zcmMsIGhhbGY0IF9kc3QpIAp7CglyZXR1cm4gYmxlbmRfbW9kdWxhdGUoTWF0cml4RWZmZWN0X1MxX2MwKF9zcmMpLCBfc3JjKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IEJsZW5kX1MxKG91dHB1dENvbG9yX1MwLCBoYWxmNCgxKSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAAAAAA==","AYAA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"DAAAAExTS1OCAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCXZpbkNpcmNsZUVkZ2VfUzAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMl9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gX3RtcF8wX2luUG9zaXRpb24ueHkwMTsKfQoAAAAAAADqAQAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAABAEAAAABJYQAAAAAACAIAAAAAWCBAAAIBAAAAANAECAZAAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"DAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAC8GAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1SW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGYyIHVPZmZzZXRzQW5kS2VybmVsX1MxX2MwWzEzXTsKdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglmbG9hdDIgaW5Db29yZCA9IF9jb29yZHM7CglmbG9hdDIgc3Vic2V0Q29vcmQ7CglzdWJzZXRDb29yZC54ID0gaW5Db29yZC54OwoJc3Vic2V0Q29vcmQueSA9IGluQ29vcmQueTsKCWZsb2F0MiBjbGFtcGVkQ29vcmQ7CgljbGFtcGVkQ29vcmQueCA9IHN1YnNldENvb3JkLng7CgljbGFtcGVkQ29vcmQueSA9IGNsYW1wKHN1YnNldENvb3JkLnksIHVjbGFtcF9TMV9jMF9jMF9jMC55LCB1Y2xhbXBfUzFfYzBfYzBfYzAudyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBTbW9vdGhfUzFfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgY29vcmQsIGhhbGYyIG9mZnNldEFuZEtlcm5lbCkgCnsKCXJldHVybiBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCAoY29vcmQgKyBvZmZzZXRBbmRLZXJuZWwueCAqIHVJbmNyZW1lbnRfUzFfYzApKSAqIG9mZnNldEFuZEtlcm5lbC55Owp9CmhhbGY0IEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgY29sb3IgPSBoYWxmNCgwKTsKCWZsb2F0MiBjb29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZm9yIChpbnQgaT0wOyBpPDEzOyArK2kpIAoJewoJCWNvbG9yICs9IFNtb290aF9TMV9jMChfaW5wdXQsIGNvb3JkLCB1T2Zmc2V0c0FuZEtlcm5lbF9TMV9jMFtpXSk7Cgl9CglyZXR1cm4gY29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBHYXVzc2lhbkNvbnZvbHV0aW9uX1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAAAAAAA","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAFIBVWKMG7QAAAAAAGVXOVKVEAAAAADAAAAAFQ454NUDACAAAIAQZUOAMQAAAAIAAAAAEARTKLVK5LSAAAAABQAAAAAYGP6G2BSBAAAAAIO2HAGIAAAAAAAEAAAAAZ3RZTY3IGAEAAAAAAAAAGARI4UKA4WAAAAAAAEAAAABSMQII2XAGAAAAAAAAAAACAA4AAAACAAAAAAACCAYAA":"DAAAAExTS1OAAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfNV9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAIIGAAB1bmlmb3JtIGhhbGY0IHVzdGFydF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1ZW5kX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzE7CnVuaWZvcm0gaGFsZjQgdWxlZnRCb3JkZXJDb2xvcl9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TMV9jMDsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKaGFsZjQgU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJZmxvYXQyIF90bXBfMV9jb29yZHMgPSBfY29vcmRzOwoJcmV0dXJuIGhhbGY0KG1peCh1c3RhcnRfUzFfYzBfYzAsIHVlbmRfUzFfYzBfYzAsIGhhbGYoX3RtcF8xX2Nvb3Jkcy54KSkpOwp9CmhhbGY0IExpbmVhckxheW91dF9TMV9jMF9jMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzJfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzNfY29vcmRzID0gdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CglyZXR1cm4gaGFsZjQoaGFsZjQoaGFsZihfdG1wXzNfY29vcmRzLngpICsgMWUtMDUsIDEuMCwgMC4wLCAwLjApKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIExpbmVhckxheW91dF9TMV9jMF9jMV9jMChfaW5wdXQpOwp9CmhhbGY0IENsYW1wZWRHcmFkaWVudF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzRfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGY0IHQgPSBNYXRyaXhFZmZlY3RfUzFfYzBfYzEoX3RtcF80X2luQ29sb3IpOwoJaGFsZjQgb3V0Q29sb3I7CglpZiAoIWJvb2woaW50KDEpKSAmJiB0LnkgPCAwLjApIAoJewoJCW91dENvbG9yID0gaGFsZjQoMC4wKTsKCX0KCWVsc2UgaWYgKHQueCA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSB1bGVmdEJvcmRlckNvbG9yX1MxX2MwOwoJfQoJZWxzZSBpZiAodC54ID4gMS4wKSAKCXsKCQlvdXRDb2xvciA9IHVyaWdodEJvcmRlckNvbG9yX1MxX2MwOwoJfQoJZWxzZSAKCXsKCQlvdXRDb2xvciA9IFNpbmdsZUludGVydmFsQ29sb3JpemVyX1MxX2MwX2MwKF90bXBfNF9pbkNvbG9yLCBmbG9hdDIoaGFsZjIodC54LCAwLjApKSk7Cgl9CglyZXR1cm4gaGFsZjQob3V0Q29sb3IpOwp9CmhhbGY0IERpc2FibGVDb3ZlcmFnZUFzQWxwaGFfUzEoaGFsZjQgX2lucHV0KSAKewoJX2lucHV0ID0gQ2xhbXBlZEdyYWRpZW50X1MxX2MwKF9pbnB1dCk7CgloYWxmNCBfdG1wXzVfaW5Db2xvciA9IF9pbnB1dDsKCXJldHVybiBoYWxmNChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gRGlzYWJsZUNvdmVyYWdlQXNBbHBoYV9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAoAAABsb2NhbENvb3JkAAAAAAAA","HUQACAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"DAAAAExTS1PPAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAEAAACbAgAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGhhbGY0IHZjb2xvcl9TMDsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAAAAAA=","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAAA5AAAAAAABAAAAACAZAAAAA":"DAAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgAAAABFAgAAZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2YXJjY29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1MwLngsIHk9dmFyY2Nvb3JkX1MwLnk7CgloYWxmIGNvdmVyYWdlOwoJaWYgKDAgPT0geF9wbHVzXzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZih5KTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQgZm4gPSB4X3BsdXNfMSAqICh4X3BsdXNfMSAtIDIpOwoJCWZuID0gZm1hKHkseSwgZm4pOwoJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABAAAAHNrZXcZAAAAdHJhbnNsYXRlX2FuZF9sb2NhbHJvdGF0ZQAAAAUAAABjb2xvcgAAAAAAAAA=","HUQACAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"DAAAAExTS1PPAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAMAQAAaW4gaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAAAAAA==","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGACQAGAAAAAQAAAAAAAQQGAAA":"DAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAAAAACsAQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGZsb2F0MiB2bG9jYWxDb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9IChibGVuZF9tb2R1bGF0ZShzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHRleENvb3JkKSwgaGFsZjQoMSkpKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAAAAAAA","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAFQBVWKMG7QAAAAAAAAAACAAAAAVQEAAQAAAAAQCDAEQQGAAAAAAAAAAAA4IAPAAACAAAAAAAEABYAAAAEAAAAAAAEEBQA":"DAAAAExTS1N6AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfM19TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzFfYzApICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAABAAAArAQAAHVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMjsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzI7CnVuaWZvcm0gc2FtcGxlckV4dGVybmFsT0VTIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoX2lucHV0KTsKfQpoYWxmNCBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKGhhbGY0IF9pbnB1dCkgCnsKCV9pbnB1dCA9IE1hdHJpeEVmZmVjdF9TMV9jMChfaW5wdXQpOwoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CglyZXR1cm4gaGFsZjQoX2lucHV0KTsKfQpoYWxmNCBDaXJjdWxhclJSZWN0X1MyKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMi5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMi5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MyLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gRGlzYWJsZUNvdmVyYWdlQXNBbHBoYV9TMShvdXRwdXRDb2xvcl9TMCk7CgloYWxmNCBvdXRwdXRfUzI7CglvdXRwdXRfUzIgPSBDaXJjdWxhclJSZWN0X1MyKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfUzEgKiBvdXRwdXRfUzI7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAAKAAAAbG9jYWxDb29yZAAAAAAAAA==","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"DAAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAsQEAAHVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdlRleHR1cmVDb29yZHNfUzA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gdGV4Q29sb3I7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAPAAAAaW5UZXh0dXJlQ29vcmRzAAAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAB3QA6AAAEAAAAAAAMAAPEAEAAABAAAAAAB2AAAAAAACAAAAAEBSAAAA":"DAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAAAeBQAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzI7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MyOwppbiBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglhbHBoYSA9IDEuMCAtIGFscGhhOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CmhhbGY0IENpcmN1bGFyUlJlY3RfUzIoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MyLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MyLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzIueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDQgY2lyY2xlRWRnZTsKCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCWhhbGY0IG91dHB1dF9TMjsKCW91dHB1dF9TMiA9IENpcmN1bGFyUlJlY3RfUzIob3V0cHV0X1MxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMjsKCX0KfQoAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2UAAAAA","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGAAZAADIAAAAGULKMMQKAAAAAAMAAAAAIAAAAAAGIRBNAWEYZAUAABQAAAAAAAAAAAAADUAAAAAAAEAAAAAIDEAAA":"DAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAAC4BAAAY29uc3QgaW50IGtGaWxsQUFfUzFfYzAgPSAxOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzFfYzAgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzFfYzAgPSAzOwp1bmlmb3JtIGZsb2F0NCB1Y2lyY2xlX1MxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZsb2NhbENvb3JkX1MwOwpoYWxmNCBDaXJjbGVfUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGQ7CglpZiAoaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxX2MwIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMV9jMCkgCgl7CgkJZCA9IGhhbGYoKGxlbmd0aCgodWNpcmNsZV9TMV9jMC54eSAtIHNrX0ZyYWdDb29yZC54eSkgKiB1Y2lyY2xlX1MxX2MwLncpIC0gMS4wKSAqIHVjaXJjbGVfUzFfYzAueik7Cgl9CgllbHNlIAoJewoJCWQgPSBoYWxmKCgxLjAgLSBsZW5ndGgoKHVjaXJjbGVfUzFfYzAueHkgLSBza19GcmFnQ29vcmQueHkpICogdWNpcmNsZV9TMV9jMC53KSkgKiB1Y2lyY2xlX1MxX2MwLnopOwoJfQoJcmV0dXJuIGhhbGY0KGhhbGY0KGludCgxKSA9PSBrRmlsbEFBX1MxX2MwIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMV9jMCA/IHNhdHVyYXRlKGQpIDogaGFsZihkID4gMC41ID8gMSA6IDApKSk7Cn0KaGFsZjQgQmxlbmRfUzEoaGFsZjQgX3NyYywgaGFsZjQgX2RzdCkgCnsKCXJldHVybiBibGVuZF9tb2R1bGF0ZShfc3JjLCBDaXJjbGVfUzFfYzAoX3NyYykpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwID0gaGFsZjQoMSk7CglmbG9hdDIgdGV4Q29vcmQ7Cgl0ZXhDb29yZCA9IHZsb2NhbENvb3JkX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSAoYmxlbmRfbW9kdWxhdGUoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCksIGhhbGY0KDEpKSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQmxlbmRfUzEob3V0cHV0Q292ZXJhZ2VfUzAsIGhhbGY0KDEpKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAAAAAAA","HVIACAAAABQAAGAAAQ4AAAAAGQQAARC4GAAAIOCAAD6P7777777777YDAAAAAAAAAAAFIBVWKMG7QAAAAAAGVXOVKVEAAAAADAAAAAAAAEAABMHHPDNAYAQAACAEGNDQDEAAAACAAAAABAEM2S5KXK4QAAAAAMAAAAAAAAQAABQM74NUDECAAAAAQ5UOAMQAAAAAAAIAACAAZBAAAAANQ4Z4NUDACAAAEAAAAACAI33BYT43IEAAAAAAAAAAAWARQ5UOAMQAAAAIAAAAAAABS4UIAYXAGAAAAAAAAAAA2AAQAAAAFAAAAAEASAAQAAAA":"DAAAAExTS1PlAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdCBjb3ZlcmFnZTsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdCB2Y292ZXJhZ2VfUzA7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzZfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwoJewoJCXZUcmFuc2Zvcm1lZENvb3Jkc182X1MwID0gZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMSkgKiBsb2NhbENvb3JkLnh5MTsKCX0KfQoAAAAAAAAAxwcAAHVuaWZvcm0gaGFsZjQgdXN0YXJ0X1MxX2MwX2MwX2MwOwp1bmlmb3JtIGhhbGY0IHVlbmRfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKdW5pZm9ybSBoYWxmNCB1bGVmdEJvcmRlckNvbG9yX1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVyaWdodEJvcmRlckNvbG9yX1MxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQgdmNvdmVyYWdlX1MwOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzZfUzA7CmhhbGY0IFNpbmdsZUludGVydmFsQ29sb3JpemVyX1MxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzFfY29vcmRzID0gX2Nvb3JkczsKCXJldHVybiBoYWxmNChtaXgodXN0YXJ0X1MxX2MwX2MwX2MwLCB1ZW5kX1MxX2MwX2MwX2MwLCBoYWxmKF90bXBfMV9jb29yZHMueCkpKTsKfQpoYWxmNCBjb2xvcl94Zm9ybV9TMV9jMF9jMChmbG9hdDQgY29sb3IpIAp7Cgljb2xvci5yZ2IgKj0gY29sb3IuYTsKCXJldHVybiBoYWxmNChjb2xvcik7Cn0KaGFsZjQgQ29sb3JTcGFjZVhmb3JtX1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gY29sb3JfeGZvcm1fUzFfYzBfYzAoU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzBfYzAoX2lucHV0LCBfY29vcmRzKSk7Cn0KaGFsZjQgTGluZWFyTGF5b3V0X1MxX2MwX2MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMl9pbkNvbG9yID0gX2lucHV0OwoJZmxvYXQyIF90bXBfM19jb29yZHMgPSB2VHJhbnNmb3JtZWRDb29yZHNfNl9TMDsKCXJldHVybiBoYWxmNChoYWxmNChoYWxmKF90bXBfM19jb29yZHMueCkgKyAxZS0wNSwgMS4wLCAwLjAsIDAuMCkpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyTGF5b3V0X1MxX2MwX2MxX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQ2xhbXBlZEdyYWRpZW50X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfNF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjQgdCA9IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShfdG1wXzRfaW5Db2xvcik7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghYm9vbChpbnQoMSkpICYmIHQueSA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSBoYWxmNCgwLjApOwoJfQoJZWxzZSBpZiAodC54IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IHVsZWZ0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIGlmICh0LnggPiAxLjApIAoJewoJCW91dENvbG9yID0gdXJpZ2h0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gQ29sb3JTcGFjZVhmb3JtX1MxX2MwX2MwKF90bXBfNF9pbkNvbG9yLCBmbG9hdDIoaGFsZjIodC54LCAwLjApKSk7Cgl9CglyZXR1cm4gaGFsZjQob3V0Q29sb3IpOwp9CmhhbGY0IERpc2FibGVDb3ZlcmFnZUFzQWxwaGFfUzEoaGFsZjQgX2lucHV0KSAKewoJX2lucHV0ID0gQ2xhbXBlZEdyYWRpZW50X1MxX2MwKF9pbnB1dCk7CgloYWxmNCBfdG1wXzVfaW5Db2xvciA9IF9pbnB1dDsKCXJldHVybiBoYWxmNChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCBjb3ZlcmFnZSA9IHZjb3ZlcmFnZV9TMDsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoaGFsZihjb3ZlcmFnZSkpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gRGlzYWJsZUNvdmVyYWdlQXNBbHBoYV9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gKGhhbGY0KDEuMCkgLSBvdXRwdXRfUzEpICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAAHBvc2l0aW9uCAAAAGNvdmVyYWdlBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAAAAAA=","HWQACAAAABAAADAAAIOAAAAADIIAAIRODAAP577774DSAIAA737777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"DAAAAExTS1ONAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQgY292ZXJhZ2U7CmluIGhhbGY0IGNvbG9yOwppbiBmbG9hdDQgZ2VvbVN1YnNldDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQgdmNvdmVyYWdlX1MwOwpmbGF0IG91dCBmbG9hdDQgdmdlb21TdWJzZXRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJdmdlb21TdWJzZXRfUzAgPSBnZW9tU3Vic2V0OwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAAB5AgAAZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0IHZjb3ZlcmFnZV9TMDsKZmxhdCBpbiBmbG9hdDQgdmdlb21TdWJzZXRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCBjb3ZlcmFnZSA9IHZjb3ZlcmFnZV9TMDsKCWZsb2F0NCBnZW9TdWJzZXQ7CglnZW9TdWJzZXQgPSB2Z2VvbVN1YnNldF9TMDsKCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEsIDEsIC0xLCAtMSkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIGdlb1N1YnNldCksIDAsIDEpOwoJaGFsZjIgZGlzdHMyID0gZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3IC0gMTsKCWhhbGYgc3Vic2V0Q292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJY292ZXJhZ2UgPSBtaW4oY292ZXJhZ2UsIHN1YnNldENvdmVyYWdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoaGFsZihjb3ZlcmFnZSkpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAIAAAAcG9zaXRpb24IAAAAY292ZXJhZ2UFAAAAY29sb3IAAAAKAAAAZ2VvbVN1YnNldAAAAAAAAA==","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEAB5AAAAACQNNQSEIYAQAADQAAAABAAAAAAABAEMVC2TBEKRAAAAAHAAAAAAAAAAACQAGAAAAAQAAAAAAAQQGAAA":"DAAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAABRBQAAY29uc3QgaW50IGtGaWxsQUFfUzFfYzAgPSAxOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzFfYzAgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzFfYzAgPSAzOwp1bmlmb3JtIGZsb2F0NCB1Y2lyY2xlX1MxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZhcmNjb29yZF9TMDsKaGFsZjQgQ2lyY2xlX1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZiBkOwoJaWYgKGludCgzKSA9PSBrSW52ZXJzZUZpbGxCV19TMV9jMCB8fCBpbnQoMykgPT0ga0ludmVyc2VGaWxsQUFfUzFfYzApIAoJewoJCWQgPSBoYWxmKChsZW5ndGgoKHVjaXJjbGVfUzFfYzAueHkgLSBza19GcmFnQ29vcmQueHkpICogdWNpcmNsZV9TMV9jMC53KSAtIDEuMCkgKiB1Y2lyY2xlX1MxX2MwLnopOwoJfQoJZWxzZSAKCXsKCQlkID0gaGFsZigoMS4wIC0gbGVuZ3RoKCh1Y2lyY2xlX1MxX2MwLnh5IC0gc2tfRnJhZ0Nvb3JkLnh5KSAqIHVjaXJjbGVfUzFfYzAudykpICogdWNpcmNsZV9TMV9jMC56KTsKCX0KCXJldHVybiBoYWxmNChoYWxmNChpbnQoMykgPT0ga0ZpbGxBQV9TMV9jMCB8fCBpbnQoMykgPT0ga0ludmVyc2VGaWxsQUFfUzFfYzAgPyBzYXR1cmF0ZShkKSA6IGhhbGYoZCA+IDAuNSA/IDEgOiAwKSkpOwp9CmhhbGY0IEJsZW5kX1MxKGhhbGY0IF9zcmMsIGhhbGY0IF9kc3QpIAp7CglyZXR1cm4gYmxlbmRfbW9kdWxhdGUoX3NyYywgQ2lyY2xlX1MxX2MwKF9zcmMpKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWZsb2F0IHhfcGx1c18xPXZhcmNjb29yZF9TMC54LCB5PXZhcmNjb29yZF9TMC55OwoJaGFsZiBjb3ZlcmFnZTsKCWlmICgwID09IHhfcGx1c18xKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoeSk7Cgl9CgllbHNlIAoJewoJCWZsb2F0IGZuID0geF9wbHVzXzEgKiAoeF9wbHVzXzEgLSAyKTsKCQlmbiA9IGZtYSh5LHksIGZuKTsKCQlmbG9hdCBmbndpZHRoID0gZndpZHRoKGZuKTsKCQljb3ZlcmFnZSA9IC41IC0gaGFsZihmbi9mbndpZHRoKTsKCQljb3ZlcmFnZSA9IGNsYW1wKGNvdmVyYWdlLCAwLCAxKTsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoY292ZXJhZ2UpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQmxlbmRfUzEob3V0cHV0Q292ZXJhZ2VfUzAsIGhhbGY0KDEpKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABAAAAHNrZXcZAAAAdHJhbnNsYXRlX2FuZF9sb2NhbHJvdGF0ZQAAAAUAAABjb2xvcgAAAAAAAAA=","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEADZABYAAAIAAAAAACQAGAAAAAQAAAAAAAQQG":"DAAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAADUAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpoYWxmNCBDaXJjdWxhclJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1MwLngsIHk9dmFyY2Nvb3JkX1MwLnk7CgloYWxmIGNvdmVyYWdlOwoJaWYgKDAgPT0geF9wbHVzXzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZih5KTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQgZm4gPSB4X3BsdXNfMSAqICh4X3BsdXNfMSAtIDIpOwoJCWZuID0gZm1hKHkseSwgZm4pOwoJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABAAAAHNrZXcZAAAAdHJhbnNsYXRlX2FuZF9sb2NhbHJvdGF0ZQAAAAUAAABjb2xvcgAAAAAAAAA=","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAEQQGABZAA6IAAAAACAAAAAADUAAAAAAAEAAAAAIDEAAAAAAA":"DAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABEAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlckV4dGVybmFsT0VTIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9IChibGVuZF9tb2R1bGF0ZShzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHRleENvb3JkKSwgaGFsZjQoMSkpKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAAAAAAA","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEAKPABAAAAAAB2AAAAAAACAAAAAEBSAAAAAAA":"DAAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAACbBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBmbG9hdDIgdWludlJhZGlpWFlfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpoYWxmNCBFbGxpcHRpY2FsUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CglmbG9hdDIgWiA9IGR4eSAqIHVpbnZSYWRpaVhZX1MxLnh5OwoJaGFsZiBpbXBsaWNpdCA9IGhhbGYoZG90KFosIGR4eSkgLSAxLjApOwoJaGFsZiBncmFkX2RvdCA9IGhhbGYoNC4wICogZG90KFosIFopKTsKCWdyYWRfZG90ID0gbWF4KGdyYWRfZG90LCAxLjBlLTQpOwoJaGFsZiBhcHByb3hfZGlzdCA9IGltcGxpY2l0ICogaGFsZihpbnZlcnNlc3FydChncmFkX2RvdCkpOwoJaGFsZiBhbHBoYSA9IGNsYW1wKDAuNSArIGFwcHJveF9kaXN0LCAwLjAsIDEuMCk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCB4X3BsdXNfMT12YXJjY29vcmRfUzAueCwgeT12YXJjY29vcmRfUzAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGNvdmVyYWdlKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IEVsbGlwdGljYWxSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAEAAAAc2tldxkAAAB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlAAAABQAAAGNvbG9yAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAIAAIAAAABLCIABAAAAABAEGABBAMAACAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"DAAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAADsAwAAdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZC54ID0gY2xhbXAoc3Vic2V0Q29vcmQueCwgdWNsYW1wX1MxX2MwX2MwLngsIHVjbGFtcF9TMV9jMF9jMC56KTsKCWNsYW1wZWRDb29yZC55ID0gc3Vic2V0Q29vcmQueTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQmxlbmRfUzEoaGFsZjQgX3NyYywgaGFsZjQgX2RzdCkgCnsKCXJldHVybiBibGVuZF9tb2R1bGF0ZShNYXRyaXhFZmZlY3RfUzFfYzAoX3NyYyksIF9zcmMpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwID0gaGFsZjQoMSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQmxlbmRfUzEob3V0cHV0Q29sb3JfUzAsIGhhbGY0KDEpKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfUzEgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAAAAAAA","CMRQCIAABBYAAAEIXBAAACDQMAABRAFAAAAAAAAAAAAAAAEABYAAAAEAAAAAAAEEBQAAAAA":"DAAAAExTS1MyAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0MiBpbkVsbGlwc2VPZmZzZXQ7CmluIGZsb2F0NCBpbkVsbGlwc2VSYWRpaTsKb3V0IGZsb2F0MiB2RWxsaXBzZU9mZnNldHNfUzA7Cm91dCBmbG9hdDQgdkVsbGlwc2VSYWRpaV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBFbGxpcHNlR2VvbWV0cnlQcm9jZXNzb3IKCXZFbGxpcHNlT2Zmc2V0c19TMCA9IGluRWxsaXBzZU9mZnNldDsKCXZFbGxpcHNlUmFkaWlfUzAgPSBpbkVsbGlwc2VSYWRpaTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAAAHIDAABpbiBmbG9hdDIgdkVsbGlwc2VPZmZzZXRzX1MwOwppbiBmbG9hdDQgdkVsbGlwc2VSYWRpaV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBFbGxpcHNlR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0MiBvZmZzZXQgPSB2RWxsaXBzZU9mZnNldHNfUzAueHk7CglvZmZzZXQgKj0gdkVsbGlwc2VSYWRpaV9TMC54eTsKCWZsb2F0IHRlc3QgPSBkb3Qob2Zmc2V0LCBvZmZzZXQpIC0gMS4wOwoJZmxvYXQyIGdyYWQgPSAyLjAqb2Zmc2V0KnZFbGxpcHNlUmFkaWlfUzAueHk7CglmbG9hdCBncmFkX2RvdCA9IGRvdChncmFkLCBncmFkKTsKCWdyYWRfZG90ID0gbWF4KGdyYWRfZG90LCAxLjE3NTVlLTM4KTsKCWZsb2F0IGludmxlbiA9IGludmVyc2VzcXJ0KGdyYWRfZG90KTsKCWZsb2F0IGVkZ2VBbHBoYSA9IHNhdHVyYXRlKDAuNS10ZXN0Kmludmxlbik7CglvZmZzZXQgPSB2RWxsaXBzZU9mZnNldHNfUzAueHkqdkVsbGlwc2VSYWRpaV9TMC56dzsKCXRlc3QgPSBkb3Qob2Zmc2V0LCBvZmZzZXQpIC0gMS4wOwoJZ3JhZCA9IDIuMCpvZmZzZXQqdkVsbGlwc2VSYWRpaV9TMC56dzsKCWdyYWRfZG90ID0gZG90KGdyYWQsIGdyYWQpOwoJaW52bGVuID0gaW52ZXJzZXNxcnQoZ3JhZF9kb3QpOwoJZWRnZUFscGhhICo9IHNhdHVyYXRlKDAuNSt0ZXN0Kmludmxlbik7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGhhbGYoZWRnZUFscGhhKSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA8AAABpbkVsbGlwc2VPZmZzZXQADgAAAGluRWxsaXBzZVJhZGlpAAAAAAAA","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAGIBIAAABAAAAANAEAAAAAAAAAAAAAABAAOAAAABAAAAAAABBAMAAAAA":"DAAAAExTS1N0AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMl9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAHMCAAB1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwKS5ycnJyOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gTWF0cml4RWZmZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAAKAAAAbG9jYWxDb29yZAAAAAAAAA==","B2ABSAAABQAAIAABBYAAB7777777777774ABICAAAAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"DAAAAExTS1N4AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gaGFsZjQgdUNvbG9yX1MwOwppbiBmbG9hdDIgaW5Qb3NpdGlvbjsKaW4gaGFsZiBpbkNvdmVyYWdlOwpvdXQgaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IGNvbG9yID0gdUNvbG9yX1MwOwoJY29sb3IgPSBjb2xvciAqIGluQ292ZXJhZ2U7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8zX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzFfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAGAQAAaW4gaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAACgAAAGluQ292ZXJhZ2UAAAAAAAA=","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAADEAANAAAAAZ3N3DJRAAAAAAAAABAAAAAGJZFMBV5RUAQAAAAAAQAAAAAMACQCAACAAAAA2AIBAEIAAAAAAAAAAAAIADQAAAAIAAAAAAAIIDAAAAAA":"DAAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoBAAAAeAQAAHVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1Y2lyY2xlRGF0YV9TMV9jMDsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBfY29vcmRzKS4wMDByOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBDaXJjbGVCbHVyX1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjIgdmVjID0gaGFsZjIoKHNrX0ZyYWdDb29yZC54eSAtIGZsb2F0Mih1Y2lyY2xlRGF0YV9TMV9jMC54eSkpICogZmxvYXQodWNpcmNsZURhdGFfUzFfYzAudykpOwoJaGFsZiBkaXN0ID0gbGVuZ3RoKHZlYykgKyAoMC41IC0gdWNpcmNsZURhdGFfUzFfYzAueikgKiB1Y2lyY2xlRGF0YV9TMV9jMC53OwoJcmV0dXJuIGhhbGY0KE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfdG1wXzBfaW5Db2xvciwgZmxvYXQyKGhhbGYyKGRpc3QsIDAuNSkpKS53d3d3KTsKfQpoYWxmNCBCbGVuZF9TMShoYWxmNCBfc3JjLCBoYWxmNCBfZHN0KSAKewoJcmV0dXJuIGJsZW5kX21vZHVsYXRlKENpcmNsZUJsdXJfUzFfYzAoX3NyYyksIF9zcmMpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQmxlbmRfUzEob3V0cHV0Q292ZXJhZ2VfUzAsIGhhbGY0KDEpKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"DAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAADqAQAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABZQA6AAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"DAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACPAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCWFscGhhID0gMS4wIC0gYWxwaGE7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDQgY2lyY2xlRWRnZTsKCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQAAEAQAAAAGQCBAMQAAAIAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"DAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAHADAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBzdWJzZXRDb29yZC54OwoJY2xhbXBlZENvb3JkLnkgPSBjbGFtcChzdWJzZXRDb29yZC55LCB1Y2xhbXBfUzFfYzAueSwgdWNsYW1wX1MxX2MwLncpOwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAAAAAA==","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAIAHSADQAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"DAAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAABAAAAQAMAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzE7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1MwOwpmbGF0IGluIGZsb2F0IHZUZXhJbmRleF9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gdGV4Q29sb3I7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAPAAAAaW5UZXh0dXJlQ29vcmRzAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAAAAIAAAABLAIABAAAAABAEGABBAMAAAAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"DAAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAC2AgAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoX2lucHV0KTsKfQpoYWxmNCBCbGVuZF9TMShoYWxmNCBfc3JjLCBoYWxmNCBfZHN0KSAKewoJcmV0dXJuIGJsZW5kX21vZHVsYXRlKE1hdHJpeEVmZmVjdF9TMV9jMChfc3JjKSwgX3NyYyk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBCbGVuZF9TMShvdXRwdXRDb2xvcl9TMCwgaGFsZjQoMSkpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAAAAEAAAABJYQAAAAAAAAIAAAAAWCBAAAABAAAAANAECAZAAAAAAAAAAAFAAMAAAABAAAAAAABBAM":"DAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAPAEAAB1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzA7CnVuaWZvcm0gaGFsZjIgdUluY3JlbWVudF9TMV9jMDsKdW5pZm9ybSBoYWxmMiB1T2Zmc2V0c0FuZEtlcm5lbF9TMV9jMFsxM107CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgX2Nvb3Jkcyk7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChfaW5wdXQsIGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzApICogX2Nvb3Jkcy54eTEpOwp9CmhhbGY0IFNtb290aF9TMV9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBjb29yZCwgaGFsZjIgb2Zmc2V0QW5kS2VybmVsKSAKewoJcmV0dXJuIE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfaW5wdXQsIChjb29yZCArIG9mZnNldEFuZEtlcm5lbC54ICogdUluY3JlbWVudF9TMV9jMCkpICogb2Zmc2V0QW5kS2VybmVsLnk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBjb2xvciA9IGhhbGY0KDApOwoJZmxvYXQyIGNvb3JkID0gdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzA7Cglmb3IgKGludCBpPTA7IGk8MTM7ICsraSkgCgl7CgkJY29sb3IgKz0gU21vb3RoX1MxX2MwKF9pbnB1dCwgY29vcmQsIHVPZmZzZXRzQW5kS2VybmVsX1MxX2MwW2ldKTsKCX0KCXJldHVybiBjb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAAAAAA==","AYQQ5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"DAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAB7AgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGYgZGlzdGFuY2VUb0lubmVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKGQgLSBjaXJjbGVFZGdlLncpKTsKCWhhbGYgaW5uZXJBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9Jbm5lckVkZ2UpOwoJZWRnZUFscGhhICo9IGlubmVyQWxwaGE7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQAAAAA=","HWQACAAAABAAADAAAIOAAAAADIIAAIRODAAP577774DSAIAA737777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"DAAAAExTS1ONAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQgY292ZXJhZ2U7CmluIGhhbGY0IGNvbG9yOwppbiBmbG9hdDQgZ2VvbVN1YnNldDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQgdmNvdmVyYWdlX1MwOwpmbGF0IG91dCBmbG9hdDQgdmdlb21TdWJzZXRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJdmdlb21TdWJzZXRfUzAgPSBnZW9tU3Vic2V0OwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAAAIBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdCB2Y292ZXJhZ2VfUzA7CmZsYXQgaW4gZmxvYXQ0IHZnZW9tU3Vic2V0X1MwOwpoYWxmNCBDaXJjdWxhclJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCBjb3ZlcmFnZSA9IHZjb3ZlcmFnZV9TMDsKCWZsb2F0NCBnZW9TdWJzZXQ7CglnZW9TdWJzZXQgPSB2Z2VvbVN1YnNldF9TMDsKCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEsIDEsIC0xLCAtMSkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIGdlb1N1YnNldCksIDAsIDEpOwoJaGFsZjIgZGlzdHMyID0gZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3IC0gMTsKCWhhbGYgc3Vic2V0Q292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJY292ZXJhZ2UgPSBtaW4oY292ZXJhZ2UsIHN1YnNldENvdmVyYWdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoaGFsZihjb3ZlcmFnZSkpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY3VsYXJSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAIAAAAcG9zaXRpb24IAAAAY292ZXJhZ2UFAAAAY29sb3IAAAAKAAAAZ2VvbVN1YnNldAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQBAEAQAAAAGQCBAMQACAIAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"DAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAE0DAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkID0gY2xhbXAoc3Vic2V0Q29vcmQsIHVjbGFtcF9TMV9jMC54eSwgdWNsYW1wX1MxX2MwLnp3KTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAAAAAA=","EADQAAAAAEAAAAAUAABQAAQPAAABCFYMAAKAUEAAAAAAAAABAAAAAAAAAAANAAIAAAABAAAAACAJAAIAAAAA":"DAAAAExTS1NyAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc0RpbWVuc2lvbnNJbnZfUzA7CmluIGZsb2F0MyBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgZmxvYXQyIHZJbnRUZXh0dXJlQ29vcmRzX1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIERpc3RhbmNlRmllbGRQYXRoCglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkcyAqIHVBdGxhc0RpbWVuc2lvbnNJbnZfUzA7Cgl2VGV4SW5kZXhfUzAgPSBmbG9hdCh0ZXhJZHgpOwoJdkludFRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkczsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MyBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IGluUG9zaXRpb24ueHkwejsKfQoAAAAAAACfAgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBpbiBmbG9hdCB2VGV4SW5kZXhfUzA7CmluIGZsb2F0MiB2SW50VGV4dHVyZUNvb3Jkc19TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBEaXN0YW5jZUZpZWxkUGF0aAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQyIHV2ID0gdlRleHR1cmVDb29yZHNfUzA7CgloYWxmNCB0ZXhDb2xvcjsKCXsKCQl0ZXhDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdXYpLnJycnI7Cgl9CgloYWxmIGRpc3RhbmNlID0gNy45Njg3NSoodGV4Q29sb3IuciAtIDAuNTAxOTYwNzg0MzEpOwoJaGFsZiBhZndpZHRoOwoJYWZ3aWR0aCA9IGFicygwLjY1KmhhbGYoZEZkeCh2SW50VGV4dHVyZUNvb3Jkc19TMC54KSkpOwoJaGFsZiB2YWwgPSBzbW9vdGhzdGVwKC1hZndpZHRoLCBhZndpZHRoLCBkaXN0YW5jZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KHZhbCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADwAAAGluVGV4dHVyZUNvb3JkcwAAAAAA","BYIBQAAABQAAIAABBYAAAEIXBAAP777777777777AAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"DAAAAExTS1M+AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwpvdXQgaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IGNvbG9yID0gaW5Db2xvcjsKCXZjb2xvcl9TMCA9IGNvbG9yOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzNfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IF90bXBfMV9pblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAABgEAAGluIGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIERlZmF1bHRHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAAAAAA=","B2AAQAAABQAAIAABBYAAB7777777777774ABICAAAAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"DAAAAExTS1MOAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmIGluQ292ZXJhZ2U7Cm91dCBoYWxmIHZpbkNvdmVyYWdlX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cgl2aW5Db3ZlcmFnZV9TMCA9IGluQ292ZXJhZ2U7Cglza19Qb3NpdGlvbiA9IF90bXBfMV9pblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAATQEAAHVuaWZvcm0gaGFsZjQgdUNvbG9yX1MwOwppbiBoYWxmIHZpbkNvdmVyYWdlX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdUNvbG9yX1MwOwoJaGFsZiBhbHBoYSA9IDEuMDsKCWFscGhhID0gdmluQ292ZXJhZ2VfUzA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACgAAAGluUG9zaXRpb24AAAoAAABpbkNvdmVyYWdlAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAAYQADAAAEBB7BH46AKAAAYAAAAAAAAIAAAABS2JQ7QD2PAEAAAMAAAAAAAAAAAAAIADQAAAAIAAAAAAAIIDAAAA":"DAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAAB+BQAAY29uc3QgaW50IGtGaWxsQldfUzFfYzAgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzFfYzAgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzFfYzAgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzFfYzA7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKaGFsZjQgUmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgY292ZXJhZ2U7CglpZiAoaW50KDEpID09IGtGaWxsQldfUzFfYzAgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxX2MwKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TMV9jMC56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fUzFfYzAueHksIHNrX0ZyYWdDb29yZC54eSkpKSk7Cgl9CgllbHNlIAoJewoJCWhhbGY0IGRpc3RzNCA9IHNhdHVyYXRlKGhhbGY0KDEuMCwgMS4wLCAtMS4wLCAtMS4wKSAqIGhhbGY0KHNrX0ZyYWdDb29yZC54eXh5IC0gdXJlY3RVbmlmb3JtX1MxX2MwKSk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJaWYgKGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMV9jMCB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQUFfUzFfYzApIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gaGFsZjQoaGFsZjQoY292ZXJhZ2UpKTsKfQpoYWxmNCBCbGVuZF9TMShoYWxmNCBfc3JjLCBoYWxmNCBfZHN0KSAKewoJcmV0dXJuIGJsZW5kX21vZHVsYXRlKFJlY3RfUzFfYzAoX3NyYyksIF9zcmMpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQmxlbmRfUzEob3V0cHV0Q292ZXJhZ2VfUzAsIGhhbGY0KDEpKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2UAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAIAAEAAAABJYQAAAAAQAAIAAAAAWCBACAABAAAAANAECAZAAEAAAAAAAAFAAMAAAABAAAAAAABBAM":"DAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAC8GAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1SW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGYyIHVPZmZzZXRzQW5kS2VybmVsX1MxX2MwWzEzXTsKdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglmbG9hdDIgaW5Db29yZCA9IF9jb29yZHM7CglmbG9hdDIgc3Vic2V0Q29vcmQ7CglzdWJzZXRDb29yZC54ID0gaW5Db29yZC54OwoJc3Vic2V0Q29vcmQueSA9IGluQ29vcmQueTsKCWZsb2F0MiBjbGFtcGVkQ29vcmQ7CgljbGFtcGVkQ29vcmQueCA9IGNsYW1wKHN1YnNldENvb3JkLngsIHVjbGFtcF9TMV9jMF9jMF9jMC54LCB1Y2xhbXBfUzFfYzBfYzBfYzAueik7CgljbGFtcGVkQ29vcmQueSA9IHN1YnNldENvb3JkLnk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBTbW9vdGhfUzFfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgY29vcmQsIGhhbGYyIG9mZnNldEFuZEtlcm5lbCkgCnsKCXJldHVybiBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCAoY29vcmQgKyBvZmZzZXRBbmRLZXJuZWwueCAqIHVJbmNyZW1lbnRfUzFfYzApKSAqIG9mZnNldEFuZEtlcm5lbC55Owp9CmhhbGY0IEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgY29sb3IgPSBoYWxmNCgwKTsKCWZsb2F0MiBjb29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZm9yIChpbnQgaT0wOyBpPDEzOyArK2kpIAoJewoJCWNvbG9yICs9IFNtb290aF9TMV9jMChfaW5wdXQsIGNvb3JkLCB1T2Zmc2V0c0FuZEtlcm5lbF9TMV9jMFtpXSk7Cgl9CglyZXR1cm4gY29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBHYXVzc2lhbkNvbnZvbHV0aW9uX1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAAAAAAA","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGABZAA6IAAAAACAAAAAADUAAAAAAAEAAAAAIDEAAAAAAA":"DAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAAA7AwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9IChibGVuZF9tb2R1bGF0ZShzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHRleENvb3JkKSwgaGFsZjQoMSkpKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAAAAAA==","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"DAAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoAAAAAEQEAAGZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAAAAAA=="}} \ No newline at end of file diff --git a/test/model/collection_source_test.dart b/test/model/collection_source_test.dart index a8249b240..f21fcc516 100644 --- a/test/model/collection_source_test.dart +++ b/test/model/collection_source_test.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:aves/l10n/l10n.dart'; import 'package:aves/model/availability.dart'; import 'package:aves/model/covers.dart'; import 'package:aves/model/db/db_metadata.dart'; @@ -46,7 +47,7 @@ void main() { const destinationAlbum = '${FakeStorageService.primaryPath}Pictures/destination'; const aTag = 'sometag'; - final australiaLatLng = LatLng(-26, 141); + const australiaLatLng = LatLng(-26, 141); const australiaAddress = AddressDetails( id: 0, countryCode: 'AU', @@ -201,7 +202,7 @@ void main() { await covers.set(filter: albumFilter, entryId: image1.id, packageName: null, color: null); expect(covers.count, 1); - expect(covers.of(albumFilter)?.item1, image1.id); + expect(covers.of(albumFilter)?.$1, image1.id); await covers.set(filter: albumFilter, entryId: null, packageName: null, color: null); expect(covers.count, 0); @@ -229,7 +230,7 @@ void main() { expect(favourites.count, 1); expect(image1.isFavourite, true); expect(covers.count, 1); - expect(covers.of(albumFilter)?.item1, image1.id); + expect(covers.of(albumFilter)?.$1, image1.id); }); test('favourites and covers are cleared when removing entries', () async { @@ -348,7 +349,7 @@ void main() { expect(favourites.count, 1); expect(image1.isFavourite, true); expect(covers.count, 1); - expect(covers.of(albumFilter)?.item1, image1.id); + expect(covers.of(albumFilter)?.$1, image1.id); }); testWidgets('unique album names', (tester) async { @@ -369,22 +370,26 @@ void main() { final source = await _initSource(); await tester.pumpWidget( - Builder( - builder: (context) { - expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Elea/Zeno'), 'Elea/Zeno'); - expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Citium/Zeno'), 'Citium/Zeno'); - expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Cleanthes'), 'Cleanthes'); - expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Chrysippus'), 'Chrysippus'); - expect(source.getAlbumDisplayName(context, '${FakeStorageService.removablePath}Pictures/Chrysippus'), 'Chrysippus (${FakeStorageService.removableDescription})'); - expect(source.getAlbumDisplayName(context, FakeStorageService.primaryRootAlbum), FakeStorageService.primaryDescription); - expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Seneca'), 'Pictures/Seneca'); - expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Seneca'), 'Seneca'); - expect(source.getAlbumDisplayName(context, '${FakeStorageService.removablePath}Pictures/Cicero'), 'Cicero'); - expect(source.getAlbumDisplayName(context, '${FakeStorageService.removablePath}Marcus Aurelius'), 'Marcus Aurelius'); - expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Hannah Arendt'), 'Hannah Arendt'); - expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Arendt'), 'Arendt'); - return const Placeholder(); - }, + Localizations( + locale: AppLocalizations.supportedLocales.first, + delegates: AppLocalizations.localizationsDelegates, + child: Builder( + builder: (context) { + expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Elea/Zeno'), 'Elea/Zeno'); + expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Citium/Zeno'), 'Citium/Zeno'); + expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Cleanthes'), 'Cleanthes'); + expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Chrysippus'), 'Chrysippus'); + expect(source.getAlbumDisplayName(context, '${FakeStorageService.removablePath}Pictures/Chrysippus'), 'Chrysippus (${FakeStorageService.removableDescription})'); + expect(source.getAlbumDisplayName(context, FakeStorageService.primaryRootAlbum), FakeStorageService.primaryDescription); + expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Seneca'), 'Pictures/Seneca'); + expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Seneca'), 'Seneca'); + expect(source.getAlbumDisplayName(context, '${FakeStorageService.removablePath}Pictures/Cicero'), 'Cicero'); + expect(source.getAlbumDisplayName(context, '${FakeStorageService.removablePath}Marcus Aurelius'), 'Marcus Aurelius'); + expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Hannah Arendt'), 'Hannah Arendt'); + expect(source.getAlbumDisplayName(context, '${FakeStorageService.primaryPath}Pictures/Arendt'), 'Arendt'); + return const Placeholder(); + }, + ), ), ); }); diff --git a/test/model/filters_test.dart b/test/model/filters_test.dart index fe50208fe..44781b96c 100644 --- a/test/model/filters_test.dart +++ b/test/model/filters_test.dart @@ -41,7 +41,7 @@ void main() { final aspectRatio = AspectRatioFilter.landscape; expect(aspectRatio, jsonRoundTrip(aspectRatio)); - final bounds = CoordinateFilter(LatLng(29.979167, 28.223615), LatLng(36.451000, 31.134167)); + final bounds = CoordinateFilter(const LatLng(29.979167, 28.223615), const LatLng(36.451000, 31.134167)); expect(bounds, jsonRoundTrip(bounds)); final date = DateFilter(DateLevel.ym, DateTime(1969, 7)); diff --git a/test/utils/geo_utils_test.dart b/test/utils/geo_utils_test.dart index 8fe3fef49..11f862a32 100644 --- a/test/utils/geo_utils_test.dart +++ b/test/utils/geo_utils_test.dart @@ -8,18 +8,18 @@ import 'package:test/test.dart'; void main() { test('Decimal degrees to DMS (sexagesimal)', () { final l10n = lookupAppLocalizations(AvesApp.supportedLocales.first); - expect(ExtraCoordinateFormat.toDMS(l10n, LatLng(37.496667, 127.0275)), ['37° 29′ 48.00″ N', '127° 1′ 39.00″ E']); // Gangnam - expect(ExtraCoordinateFormat.toDMS(l10n, LatLng(78.9243503, 11.9230465)), ['78° 55′ 27.66″ N', '11° 55′ 22.97″ E']); // Ny-Ålesund - expect(ExtraCoordinateFormat.toDMS(l10n, LatLng(-38.6965891, 175.9830047)), ['38° 41′ 47.72″ S', '175° 58′ 58.82″ E']); // Taupo - expect(ExtraCoordinateFormat.toDMS(l10n, LatLng(-64.249391, -56.6556145)), ['64° 14′ 57.81″ S', '56° 39′ 20.21″ W']); // Marambio - expect(ExtraCoordinateFormat.toDMS(l10n, LatLng(0, 0)), ['0° 0′ 0.00″ N', '0° 0′ 0.00″ E']); - expect(ExtraCoordinateFormat.toDMS(l10n, LatLng(0, 0), minuteSecondPadding: true), ['0° 00′ 00.00″ N', '0° 00′ 00.00″ E']); - expect(ExtraCoordinateFormat.toDMS(l10n, LatLng(0, 0), secondDecimals: 0), ['0° 0′ 0″ N', '0° 0′ 0″ E']); - expect(ExtraCoordinateFormat.toDMS(l10n, LatLng(0, 0), secondDecimals: 4), ['0° 0′ 0.0000″ N', '0° 0′ 0.0000″ E']); + expect(ExtraCoordinateFormat.toDMS(l10n, const LatLng(37.496667, 127.0275)), ['37° 29′ 48.00″ N', '127° 1′ 39.00″ E']); // Gangnam + expect(ExtraCoordinateFormat.toDMS(l10n, const LatLng(78.9243503, 11.9230465)), ['78° 55′ 27.66″ N', '11° 55′ 22.97″ E']); // Ny-Ålesund + expect(ExtraCoordinateFormat.toDMS(l10n, const LatLng(-38.6965891, 175.9830047)), ['38° 41′ 47.72″ S', '175° 58′ 58.82″ E']); // Taupo + expect(ExtraCoordinateFormat.toDMS(l10n, const LatLng(-64.249391, -56.6556145)), ['64° 14′ 57.81″ S', '56° 39′ 20.21″ W']); // Marambio + expect(ExtraCoordinateFormat.toDMS(l10n, const LatLng(0, 0)), ['0° 0′ 0.00″ N', '0° 0′ 0.00″ E']); + expect(ExtraCoordinateFormat.toDMS(l10n, const LatLng(0, 0), minuteSecondPadding: true), ['0° 00′ 00.00″ N', '0° 00′ 00.00″ E']); + expect(ExtraCoordinateFormat.toDMS(l10n, const LatLng(0, 0), secondDecimals: 0), ['0° 0′ 0″ N', '0° 0′ 0″ E']); + expect(ExtraCoordinateFormat.toDMS(l10n, const LatLng(0, 0), secondDecimals: 4), ['0° 0′ 0.0000″ N', '0° 0′ 0.0000″ E']); }); test('bounds center', () { - expect(GeoUtils.getLatLngCenter([LatLng(10, 30), LatLng(30, 50)]), LatLng(20.28236664671092, 39.351653000319956)); - expect(GeoUtils.getLatLngCenter([LatLng(10, -179), LatLng(30, 179)]), LatLng(20.00279344048298, -179.9358157370226)); + expect(GeoUtils.getLatLngCenter(const [LatLng(10, 30), LatLng(30, 50)]), const LatLng(20.28236664671092, 39.351653000319956)); + expect(GeoUtils.getLatLngCenter(const [LatLng(10, -179), LatLng(30, 179)]), const LatLng(20.00279344048298, -179.9358157370226)); }); } diff --git a/test/utils/math_utils_test.dart b/test/utils/math_utils_test.dart index 8032d07c9..0d067bcd6 100644 --- a/test/utils/math_utils_test.dart +++ b/test/utils/math_utils_test.dart @@ -2,7 +2,6 @@ import 'dart:ui'; import 'package:aves/utils/math_utils.dart'; import 'package:test/test.dart'; -import 'package:tuple/tuple.dart'; void main() { test('highest power of 2 that is smaller than or equal to the number', () { @@ -29,8 +28,8 @@ void main() { }); test('segment intersection', () { - const s1 = Tuple2(Offset(1, 1), Offset(3, 2)); - const s2 = Tuple2(Offset(1, 4), Offset(2, -1)); + const s1 = (Offset(1, 1), Offset(3, 2)); + const s2 = (Offset(1, 4), Offset(2, -1)); expect(segmentIntersection(s1, s2), const Offset(17 / 11, 14 / 11)); }); } diff --git a/test_driver/driver_screenshots.dart b/test_driver/driver_screenshots.dart index 12a9c1939..dd2dd22bf 100644 --- a/test_driver/driver_screenshots.dart +++ b/test_driver/driver_screenshots.dart @@ -45,6 +45,7 @@ Future configureAndLaunch() async { ..viewerQuickActions = SettingsDefaults.viewerQuickActions ..showOverlayOnOpening = true ..showOverlayMinimap = false + ..overlayHistogramStyle = OverlayHistogramStyle.none ..showOverlayInfo = true ..showOverlayDescription = false ..showOverlayRatingTags = false diff --git a/test_driver/driver_shaders.dart b/test_driver/driver_shaders.dart index 5c84a6e0b..6c26d1986 100644 --- a/test_driver/driver_shaders.dart +++ b/test_driver/driver_shaders.dart @@ -35,6 +35,7 @@ Future configureAndLaunch() async { // viewer ..showOverlayOnOpening = true ..showOverlayMinimap = true + ..overlayHistogramStyle = OverlayHistogramStyle.rgb ..showOverlayInfo = true ..showOverlayShootingDetails = true ..showOverlayThumbnailPreview = true diff --git a/untranslated.json b/untranslated.json index 125ecde2f..56bea6c8e 100644 --- a/untranslated.json +++ b/untranslated.json @@ -142,6 +142,9 @@ "nameConflictStrategyRename", "nameConflictStrategyReplace", "nameConflictStrategySkip", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "subtitlePositionTop", "subtitlePositionBottom", "themeBrightnessLight", @@ -298,6 +301,13 @@ "aboutBugCopyInfoButton", "aboutBugReportInstruction", "aboutBugReportButton", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "aboutCreditsSectionTitle", "aboutCreditsWorldAtlas1", "aboutCreditsWorldAtlas2", @@ -480,6 +490,7 @@ "settingsViewerOverlayTile", "settingsViewerOverlayPageTitle", "settingsViewerShowOverlayOnOpening", + "settingsViewerShowHistogram", "settingsViewerShowMinimap", "settingsViewerShowInformation", "settingsViewerShowInformationSubtitle", @@ -633,198 +644,22 @@ ], "be": [ - "columnCount", - "timeSeconds", - "timeMinutes", - "timeDays", - "entryActionExport", - "entryActionInfo", - "entryActionRename", - "entryActionRestore", - "entryActionRotateCCW", - "entryActionRotateCW", - "entryActionFlip", - "entryActionPrint", - "entryActionShare", - "entryActionShareImageOnly", - "entryActionShareVideoOnly", - "entryActionViewSource", - "entryActionShowGeoTiffOnMap", - "entryActionConvertMotionPhotoToStillImage", - "entryActionViewMotionPhotoVideo", - "entryActionEdit", - "entryActionOpen", - "entryActionSetAs", - "entryActionOpenMap", - "entryActionRotateScreen", - "entryActionAddFavourite", - "entryActionRemoveFavourite", - "videoActionCaptureFrame", - "videoActionMute", - "videoActionUnmute", - "videoActionPause", - "videoActionPlay", - "videoActionReplay10", - "videoActionSkip10", - "videoActionSelectStreams", - "videoActionSetSpeed", - "viewerActionSettings", - "viewerActionLock", - "viewerActionUnlock", - "slideshowActionResume", - "slideshowActionShowInCollection", - "entryInfoActionEditDate", - "entryInfoActionEditLocation", - "entryInfoActionEditTitleDescription", - "entryInfoActionEditRating", - "entryInfoActionEditTags", - "entryInfoActionRemoveMetadata", - "entryInfoActionExportMetadata", - "entryInfoActionRemoveLocation", - "editorActionTransform", - "editorTransformCrop", - "editorTransformRotate", - "cropAspectRatioFree", - "cropAspectRatioOriginal", - "cropAspectRatioSquare", - "filterAspectRatioLandscapeLabel", - "filterAspectRatioPortraitLabel", - "filterBinLabel", - "filterFavouriteLabel", - "filterNoDateLabel", - "filterNoAddressLabel", - "filterLocatedLabel", - "filterNoLocationLabel", - "filterNoRatingLabel", - "filterTaggedLabel", - "filterNoTagLabel", - "filterNoTitleLabel", - "filterOnThisDayLabel", - "filterRecentlyAddedLabel", - "filterRatingRejectedLabel", - "filterTypeAnimatedLabel", - "filterTypeMotionPhotoLabel", - "filterTypePanoramaLabel", - "filterTypeRawLabel", - "filterTypeSphericalVideoLabel", - "filterTypeGeotiffLabel", - "filterMimeImageLabel", - "filterMimeVideoLabel", - "accessibilityAnimationsRemove", - "accessibilityAnimationsKeep", - "albumTierNew", - "albumTierPinned", - "albumTierSpecial", - "albumTierApps", - "albumTierVaults", - "albumTierRegular", - "coordinateFormatDms", - "coordinateFormatDecimal", - "coordinateDms", - "coordinateDmsNorth", - "coordinateDmsSouth", - "coordinateDmsEast", - "coordinateDmsWest", - "displayRefreshRatePreferHighest", - "displayRefreshRatePreferLowest", - "keepScreenOnNever", - "keepScreenOnVideoPlayback", - "keepScreenOnViewerOnly", - "keepScreenOnAlways", - "lengthUnitPixel", - "lengthUnitPercent", - "mapStyleGoogleNormal", - "mapStyleGoogleHybrid", - "mapStyleGoogleTerrain", - "mapStyleHuaweiNormal", - "mapStyleHuaweiTerrain", - "mapStyleOsmHot", - "mapStyleStamenToner", - "mapStyleStamenWatercolor", - "maxBrightnessNever", - "maxBrightnessAlways", - "nameConflictStrategyRename", - "nameConflictStrategyReplace", - "nameConflictStrategySkip", - "subtitlePositionTop", - "subtitlePositionBottom", - "themeBrightnessLight", - "themeBrightnessDark", - "themeBrightnessBlack", - "unitSystemMetric", - "unitSystemImperial", - "vaultLockTypePattern", - "vaultLockTypePin", - "vaultLockTypePassword", - "settingsVideoEnablePip", - "videoControlsPlay", - "videoControlsPlaySeek", - "videoControlsPlayOutside", - "videoControlsNone", - "videoLoopModeNever", - "videoLoopModeShortOnly", - "videoLoopModeAlways", - "videoPlaybackSkip", - "videoPlaybackMuted", - "videoPlaybackWithSound", - "videoResumptionModeNever", - "videoResumptionModeAlways", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "viewerTransitionSlide", - "viewerTransitionParallax", "viewerTransitionFade", "viewerTransitionZoomIn", "viewerTransitionNone", - "wallpaperTargetHome", - "wallpaperTargetLock", - "wallpaperTargetHomeLock", "widgetDisplayedItemRandom", - "widgetDisplayedItemMostRecent", "widgetOpenPageHome", "widgetOpenPageCollection", "widgetOpenPageViewer", - "widgetTapUpdateWidget", - "storageVolumeDescriptionFallbackPrimary", - "storageVolumeDescriptionFallbackNonPrimary", - "rootDirectoryDescription", - "otherDirectoryDescription", - "storageAccessDialogMessage", - "restrictedAccessDialogMessage", - "notEnoughSpaceDialogMessage", - "missingSystemFilePickerDialogMessage", - "unsupportedTypeDialogMessage", - "nameConflictDialogSingleSourceMessage", - "nameConflictDialogMultipleSourceMessage", - "addShortcutDialogLabel", - "addShortcutButtonLabel", - "noMatchingAppDialogMessage", "binEntriesConfirmationDialogMessage", "deleteEntriesConfirmationDialogMessage", - "moveUndatedConfirmationDialogMessage", - "moveUndatedConfirmationDialogSetDate", - "videoResumeDialogMessage", - "videoStartOverButtonLabel", - "videoResumeButtonLabel", - "setCoverDialogLatest", - "setCoverDialogAuto", "setCoverDialogCustom", "hideFilterConfirmationDialogMessage", - "newAlbumDialogTitle", - "newAlbumDialogNameLabel", - "newAlbumDialogNameLabelAlreadyExistsHelper", - "newAlbumDialogStorageLabel", "newVaultWarningDialogMessage", - "newVaultDialogTitle", - "configureVaultDialogTitle", - "vaultDialogLockModeWhenScreenOff", - "vaultDialogLockTypeLabel", - "patternDialogEnter", - "patternDialogConfirm", - "pinDialogEnter", - "pinDialogConfirm", - "passwordDialogEnter", - "passwordDialogConfirm", - "authenticateToConfigureVault", - "authenticateToUnlockVault", "vaultBinUsageDialogMessage", "renameAlbumDialogLabel", "renameAlbumDialogLabelAlreadyExistsHelper", @@ -902,6 +737,13 @@ "aboutBugCopyInfoButton", "aboutBugReportInstruction", "aboutBugReportButton", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "aboutCreditsSectionTitle", "aboutCreditsWorldAtlas1", "aboutCreditsWorldAtlas2", @@ -1085,6 +927,7 @@ "settingsViewerOverlayTile", "settingsViewerOverlayPageTitle", "settingsViewerShowOverlayOnOpening", + "settingsViewerShowHistogram", "settingsViewerShowMinimap", "settingsViewerShowInformation", "settingsViewerShowInformationSubtitle", @@ -1190,64 +1033,9 @@ "statsWithGps", "statsTopCountriesSectionTitle", "statsTopStatesSectionTitle", - "statsTopPlacesSectionTitle", - "statsTopTagsSectionTitle", - "statsTopAlbumsSectionTitle", "viewerOpenPanoramaButtonLabel", - "viewerSetWallpaperButtonLabel", - "viewerErrorUnknown", - "viewerErrorDoesNotExist", - "viewerInfoPageTitle", - "viewerInfoBackToViewerTooltip", - "viewerInfoUnknown", - "viewerInfoLabelDescription", - "viewerInfoLabelTitle", - "viewerInfoLabelDate", - "viewerInfoLabelResolution", - "viewerInfoLabelSize", "viewerInfoLabelUri", - "viewerInfoLabelPath", - "viewerInfoLabelDuration", - "viewerInfoLabelOwner", - "viewerInfoLabelCoordinates", - "viewerInfoLabelAddress", - "mapStyleDialogTitle", - "mapStyleTooltip", - "mapZoomInTooltip", - "mapZoomOutTooltip", - "mapPointNorthUpTooltip", - "mapAttributionOsmHot", - "mapAttributionStamen", - "openMapPageTooltip", - "mapEmptyRegion", - "viewerInfoOpenEmbeddedFailureFeedback", - "viewerInfoOpenLinkText", - "viewerInfoViewXmlLinkText", - "viewerInfoSearchFieldLabel", - "viewerInfoSearchEmpty", - "viewerInfoSearchSuggestionDate", - "viewerInfoSearchSuggestionDescription", - "viewerInfoSearchSuggestionDimensions", - "viewerInfoSearchSuggestionResolution", - "viewerInfoSearchSuggestionRights", - "wallpaperUseScrollEffect", - "tagEditorPageTitle", - "tagEditorPageNewTagFieldLabel", - "tagEditorPageAddTagTooltip", - "tagEditorSectionRecent", - "tagEditorSectionPlaceholders", - "tagEditorDiscardDialogMessage", - "tagPlaceholderCountry", - "tagPlaceholderState", - "tagPlaceholderPlace", - "panoramaEnableSensorControl", - "panoramaDisableSensorControl", - "sourceViewerPageTitle", - "filePickerShowHiddenFiles", - "filePickerDoNotShowHiddenFiles", - "filePickerOpenFrom", - "filePickerNoItems", - "filePickerUseThisFolder" + "viewerInfoSearchSuggestionRights" ], "ckb": [ @@ -1347,6 +1135,9 @@ "nameConflictStrategyRename", "nameConflictStrategyReplace", "nameConflictStrategySkip", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "subtitlePositionTop", "subtitlePositionBottom", "themeBrightnessLight", @@ -1503,6 +1294,13 @@ "aboutBugCopyInfoButton", "aboutBugReportInstruction", "aboutBugReportButton", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "aboutCreditsSectionTitle", "aboutCreditsWorldAtlas1", "aboutCreditsWorldAtlas2", @@ -1686,6 +1484,7 @@ "settingsViewerOverlayTile", "settingsViewerOverlayPageTitle", "settingsViewerShowOverlayOnOpening", + "settingsViewerShowHistogram", "settingsViewerShowMinimap", "settingsViewerShowInformation", "settingsViewerShowInformationSubtitle", @@ -1852,9 +1651,31 @@ ], "cs": [ - "editorActionTransform", - "cropAspectRatioFree", - "cropAspectRatioSquare" + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", + "settingsViewerShowHistogram" + ], + + "de": [ + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", + "settingsViewerShowHistogram" + ], + + "el": [ + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", + "settingsViewerShowHistogram" ], "fa": [ @@ -1909,6 +1730,9 @@ "maxBrightnessNever", "maxBrightnessAlways", "nameConflictStrategySkip", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "vaultLockTypePattern", "vaultLockTypePin", "vaultLockTypePassword", @@ -2030,6 +1854,13 @@ "aboutBugSaveLogInstruction", "aboutBugCopyInfoInstruction", "aboutBugReportInstruction", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "aboutCreditsSectionTitle", "aboutCreditsWorldAtlas1", "aboutCreditsWorldAtlas2", @@ -2198,6 +2029,7 @@ "settingsViewerOverlayTile", "settingsViewerOverlayPageTitle", "settingsViewerShowOverlayOnOpening", + "settingsViewerShowHistogram", "settingsViewerShowMinimap", "settingsViewerShowInformation", "settingsViewerShowInformationSubtitle", @@ -2402,6 +2234,9 @@ "lengthUnitPercent", "maxBrightnessNever", "maxBrightnessAlways", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "subtitlePositionTop", "subtitlePositionBottom", "themeBrightnessLight", @@ -2549,6 +2384,13 @@ "aboutBugCopyInfoButton", "aboutBugReportInstruction", "aboutBugReportButton", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "aboutCreditsSectionTitle", "aboutCreditsWorldAtlas1", "aboutCreditsWorldAtlas2", @@ -2732,6 +2574,7 @@ "settingsViewerOverlayTile", "settingsViewerOverlayPageTitle", "settingsViewerShowOverlayOnOpening", + "settingsViewerShowHistogram", "settingsViewerShowMinimap", "settingsViewerShowInformation", "settingsViewerShowInformationSubtitle", @@ -3058,6 +2901,9 @@ "nameConflictStrategyRename", "nameConflictStrategyReplace", "nameConflictStrategySkip", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "subtitlePositionTop", "subtitlePositionBottom", "themeBrightnessLight", @@ -3214,6 +3060,13 @@ "aboutBugCopyInfoButton", "aboutBugReportInstruction", "aboutBugReportButton", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "aboutCreditsSectionTitle", "aboutCreditsWorldAtlas1", "aboutCreditsWorldAtlas2", @@ -3397,6 +3250,7 @@ "settingsViewerOverlayTile", "settingsViewerOverlayPageTitle", "settingsViewerShowOverlayOnOpening", + "settingsViewerShowHistogram", "settingsViewerShowMinimap", "settingsViewerShowInformation", "settingsViewerShowInformationSubtitle", @@ -3703,6 +3557,9 @@ "nameConflictStrategyRename", "nameConflictStrategyReplace", "nameConflictStrategySkip", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "subtitlePositionTop", "subtitlePositionBottom", "themeBrightnessLight", @@ -3859,6 +3716,13 @@ "aboutBugCopyInfoButton", "aboutBugReportInstruction", "aboutBugReportButton", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "aboutCreditsSectionTitle", "aboutCreditsWorldAtlas1", "aboutCreditsWorldAtlas2", @@ -4042,6 +3906,7 @@ "settingsViewerOverlayTile", "settingsViewerOverlayPageTitle", "settingsViewerShowOverlayOnOpening", + "settingsViewerShowHistogram", "settingsViewerShowMinimap", "settingsViewerShowInformation", "settingsViewerShowInformationSubtitle", @@ -4207,16 +4072,18 @@ "filePickerUseThisFolder" ], + "hu": [ + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", + "settingsViewerShowHistogram" + ], + "it": [ - "saveCopyButtonLabel", - "applyTooltip", - "editorActionTransform", - "editorTransformCrop", - "editorTransformRotate", - "cropAspectRatioFree", - "cropAspectRatioOriginal", - "cropAspectRatioSquare", - "widgetTapUpdateWidget" + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", + "settingsViewerShowHistogram" ], "ja": [ @@ -4239,6 +4106,9 @@ "albumTierVaults", "maxBrightnessNever", "maxBrightnessAlways", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "subtitlePositionBottom", "videoResumptionModeNever", "videoResumptionModeAlways", @@ -4246,6 +4116,13 @@ "vaultBinUsageDialogMessage", "exportEntryDialogQuality", "exportEntryDialogWriteMetadata", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "stateEmpty", "placeEmpty", "searchStatesSectionTitle", @@ -4254,6 +4131,7 @@ "settingsConfirmationVaultDataLoss", "settingsCollectionBurstPatternsTile", "settingsCollectionBurstPatternsNone", + "settingsViewerShowHistogram", "settingsViewerShowDescription", "settingsVideoPlaybackTile", "settingsVideoPlaybackPageTitle", @@ -4270,6 +4148,655 @@ "tagEditorDiscardDialogMessage" ], + "kn": [ + "itemCount", + "columnCount", + "timeSeconds", + "timeMinutes", + "timeDays", + "focalLength", + "chipActionGoToCountryPage", + "chipActionGoToPlacePage", + "chipActionGoToTagPage", + "chipActionFilterOut", + "chipActionFilterIn", + "chipActionHide", + "chipActionLock", + "chipActionPin", + "chipActionUnpin", + "chipActionRename", + "chipActionSetCover", + "chipActionShowCountryStates", + "chipActionCreateAlbum", + "chipActionCreateVault", + "chipActionConfigureVault", + "entryActionCopyToClipboard", + "entryActionDelete", + "entryActionConvert", + "entryActionExport", + "entryActionInfo", + "entryActionRename", + "entryActionRestore", + "entryActionRotateCCW", + "entryActionRotateCW", + "entryActionFlip", + "entryActionPrint", + "entryActionShare", + "entryActionShareImageOnly", + "entryActionShareVideoOnly", + "entryActionViewSource", + "entryActionShowGeoTiffOnMap", + "entryActionConvertMotionPhotoToStillImage", + "entryActionViewMotionPhotoVideo", + "entryActionEdit", + "entryActionOpen", + "entryActionSetAs", + "entryActionOpenMap", + "entryActionRotateScreen", + "entryActionAddFavourite", + "entryActionRemoveFavourite", + "videoActionCaptureFrame", + "videoActionMute", + "videoActionUnmute", + "videoActionPause", + "videoActionPlay", + "videoActionReplay10", + "videoActionSkip10", + "videoActionSelectStreams", + "videoActionSetSpeed", + "viewerActionSettings", + "viewerActionLock", + "viewerActionUnlock", + "slideshowActionResume", + "slideshowActionShowInCollection", + "entryInfoActionEditDate", + "entryInfoActionEditLocation", + "entryInfoActionEditTitleDescription", + "entryInfoActionEditRating", + "entryInfoActionEditTags", + "entryInfoActionRemoveMetadata", + "entryInfoActionExportMetadata", + "entryInfoActionRemoveLocation", + "editorActionTransform", + "editorTransformCrop", + "editorTransformRotate", + "cropAspectRatioFree", + "cropAspectRatioOriginal", + "cropAspectRatioSquare", + "filterAspectRatioLandscapeLabel", + "filterAspectRatioPortraitLabel", + "filterBinLabel", + "filterFavouriteLabel", + "filterNoDateLabel", + "filterNoAddressLabel", + "filterLocatedLabel", + "filterNoLocationLabel", + "filterNoRatingLabel", + "filterTaggedLabel", + "filterNoTagLabel", + "filterNoTitleLabel", + "filterOnThisDayLabel", + "filterRecentlyAddedLabel", + "filterRatingRejectedLabel", + "filterTypeAnimatedLabel", + "filterTypeMotionPhotoLabel", + "filterTypePanoramaLabel", + "filterTypeRawLabel", + "filterTypeSphericalVideoLabel", + "filterTypeGeotiffLabel", + "filterMimeImageLabel", + "filterMimeVideoLabel", + "accessibilityAnimationsRemove", + "accessibilityAnimationsKeep", + "albumTierNew", + "albumTierPinned", + "albumTierSpecial", + "albumTierApps", + "albumTierVaults", + "albumTierRegular", + "coordinateFormatDms", + "coordinateFormatDecimal", + "coordinateDms", + "coordinateDmsNorth", + "coordinateDmsSouth", + "coordinateDmsEast", + "coordinateDmsWest", + "displayRefreshRatePreferHighest", + "displayRefreshRatePreferLowest", + "keepScreenOnNever", + "keepScreenOnVideoPlayback", + "keepScreenOnViewerOnly", + "keepScreenOnAlways", + "lengthUnitPixel", + "lengthUnitPercent", + "mapStyleGoogleNormal", + "mapStyleGoogleHybrid", + "mapStyleGoogleTerrain", + "mapStyleHuaweiNormal", + "mapStyleHuaweiTerrain", + "mapStyleOsmHot", + "mapStyleStamenToner", + "mapStyleStamenWatercolor", + "maxBrightnessNever", + "maxBrightnessAlways", + "nameConflictStrategyRename", + "nameConflictStrategyReplace", + "nameConflictStrategySkip", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", + "subtitlePositionTop", + "subtitlePositionBottom", + "themeBrightnessLight", + "themeBrightnessDark", + "themeBrightnessBlack", + "unitSystemMetric", + "unitSystemImperial", + "vaultLockTypePattern", + "vaultLockTypePin", + "vaultLockTypePassword", + "settingsVideoEnablePip", + "videoControlsPlay", + "videoControlsPlaySeek", + "videoControlsPlayOutside", + "videoControlsNone", + "videoLoopModeNever", + "videoLoopModeShortOnly", + "videoLoopModeAlways", + "videoPlaybackSkip", + "videoPlaybackMuted", + "videoPlaybackWithSound", + "videoResumptionModeNever", + "videoResumptionModeAlways", + "viewerTransitionSlide", + "viewerTransitionParallax", + "viewerTransitionFade", + "viewerTransitionZoomIn", + "viewerTransitionNone", + "wallpaperTargetHome", + "wallpaperTargetLock", + "wallpaperTargetHomeLock", + "widgetDisplayedItemRandom", + "widgetDisplayedItemMostRecent", + "widgetOpenPageHome", + "widgetOpenPageCollection", + "widgetOpenPageViewer", + "widgetTapUpdateWidget", + "storageVolumeDescriptionFallbackPrimary", + "storageVolumeDescriptionFallbackNonPrimary", + "rootDirectoryDescription", + "otherDirectoryDescription", + "storageAccessDialogMessage", + "restrictedAccessDialogMessage", + "notEnoughSpaceDialogMessage", + "missingSystemFilePickerDialogMessage", + "unsupportedTypeDialogMessage", + "nameConflictDialogSingleSourceMessage", + "nameConflictDialogMultipleSourceMessage", + "addShortcutDialogLabel", + "addShortcutButtonLabel", + "noMatchingAppDialogMessage", + "binEntriesConfirmationDialogMessage", + "deleteEntriesConfirmationDialogMessage", + "moveUndatedConfirmationDialogMessage", + "moveUndatedConfirmationDialogSetDate", + "videoResumeDialogMessage", + "videoStartOverButtonLabel", + "videoResumeButtonLabel", + "setCoverDialogLatest", + "setCoverDialogAuto", + "setCoverDialogCustom", + "hideFilterConfirmationDialogMessage", + "newAlbumDialogTitle", + "newAlbumDialogNameLabel", + "newAlbumDialogNameLabelAlreadyExistsHelper", + "newAlbumDialogStorageLabel", + "newVaultWarningDialogMessage", + "newVaultDialogTitle", + "configureVaultDialogTitle", + "vaultDialogLockModeWhenScreenOff", + "vaultDialogLockTypeLabel", + "patternDialogEnter", + "patternDialogConfirm", + "pinDialogEnter", + "pinDialogConfirm", + "passwordDialogEnter", + "passwordDialogConfirm", + "authenticateToConfigureVault", + "authenticateToUnlockVault", + "vaultBinUsageDialogMessage", + "renameAlbumDialogLabel", + "renameAlbumDialogLabelAlreadyExistsHelper", + "renameEntrySetPageTitle", + "renameEntrySetPagePatternFieldLabel", + "renameEntrySetPageInsertTooltip", + "renameEntrySetPagePreviewSectionTitle", + "renameProcessorCounter", + "renameProcessorName", + "deleteSingleAlbumConfirmationDialogMessage", + "deleteMultiAlbumConfirmationDialogMessage", + "exportEntryDialogFormat", + "exportEntryDialogWidth", + "exportEntryDialogHeight", + "exportEntryDialogQuality", + "exportEntryDialogWriteMetadata", + "renameEntryDialogLabel", + "editEntryDialogCopyFromItem", + "editEntryDialogTargetFieldsHeader", + "editEntryDateDialogTitle", + "editEntryDateDialogSetCustom", + "editEntryDateDialogCopyField", + "editEntryDateDialogExtractFromTitle", + "editEntryDateDialogShift", + "editEntryDateDialogSourceFileModifiedDate", + "durationDialogHours", + "durationDialogMinutes", + "durationDialogSeconds", + "editEntryLocationDialogTitle", + "editEntryLocationDialogSetCustom", + "editEntryLocationDialogChooseOnMap", + "editEntryLocationDialogLatitude", + "editEntryLocationDialogLongitude", + "locationPickerUseThisLocationButton", + "editEntryRatingDialogTitle", + "removeEntryMetadataDialogTitle", + "removeEntryMetadataDialogMore", + "removeEntryMetadataMotionPhotoXmpWarningDialogMessage", + "videoSpeedDialogLabel", + "videoStreamSelectionDialogVideo", + "videoStreamSelectionDialogAudio", + "videoStreamSelectionDialogText", + "videoStreamSelectionDialogOff", + "videoStreamSelectionDialogTrack", + "videoStreamSelectionDialogNoSelection", + "genericSuccessFeedback", + "genericFailureFeedback", + "genericDangerWarningDialogMessage", + "tooManyItemsErrorDialogMessage", + "menuActionConfigureView", + "menuActionSelect", + "menuActionSelectAll", + "menuActionSelectNone", + "menuActionMap", + "menuActionSlideshow", + "menuActionStats", + "viewDialogSortSectionTitle", + "viewDialogGroupSectionTitle", + "viewDialogLayoutSectionTitle", + "viewDialogReverseSortOrder", + "tileLayoutMosaic", + "tileLayoutGrid", + "tileLayoutList", + "coverDialogTabCover", + "coverDialogTabApp", + "coverDialogTabColor", + "appPickDialogTitle", + "appPickDialogNone", + "aboutPageTitle", + "aboutLinkLicense", + "aboutLinkPolicy", + "aboutBugSectionTitle", + "aboutBugSaveLogInstruction", + "aboutBugCopyInfoInstruction", + "aboutBugCopyInfoButton", + "aboutBugReportInstruction", + "aboutBugReportButton", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", + "aboutCreditsSectionTitle", + "aboutCreditsWorldAtlas1", + "aboutCreditsWorldAtlas2", + "aboutTranslatorsSectionTitle", + "aboutLicensesSectionTitle", + "aboutLicensesBanner", + "aboutLicensesAndroidLibrariesSectionTitle", + "aboutLicensesFlutterPluginsSectionTitle", + "aboutLicensesFlutterPackagesSectionTitle", + "aboutLicensesDartPackagesSectionTitle", + "aboutLicensesShowAllButtonLabel", + "policyPageTitle", + "collectionPageTitle", + "collectionPickPageTitle", + "collectionSelectPageTitle", + "collectionActionShowTitleSearch", + "collectionActionHideTitleSearch", + "collectionActionAddShortcut", + "collectionActionEmptyBin", + "collectionActionCopy", + "collectionActionMove", + "collectionActionRescan", + "collectionActionEdit", + "collectionSearchTitlesHintText", + "collectionGroupAlbum", + "collectionGroupMonth", + "collectionGroupDay", + "collectionGroupNone", + "sectionUnknown", + "dateToday", + "dateYesterday", + "dateThisMonth", + "collectionDeleteFailureFeedback", + "collectionCopyFailureFeedback", + "collectionMoveFailureFeedback", + "collectionRenameFailureFeedback", + "collectionEditFailureFeedback", + "collectionExportFailureFeedback", + "collectionCopySuccessFeedback", + "collectionMoveSuccessFeedback", + "collectionRenameSuccessFeedback", + "collectionEditSuccessFeedback", + "collectionEmptyFavourites", + "collectionEmptyVideos", + "collectionEmptyImages", + "collectionEmptyGrantAccessButtonLabel", + "collectionSelectSectionTooltip", + "collectionDeselectSectionTooltip", + "drawerAboutButton", + "drawerSettingsButton", + "drawerCollectionAll", + "drawerCollectionFavourites", + "drawerCollectionImages", + "drawerCollectionVideos", + "drawerCollectionAnimated", + "drawerCollectionMotionPhotos", + "drawerCollectionPanoramas", + "drawerCollectionRaws", + "drawerCollectionSphericalVideos", + "drawerAlbumPage", + "drawerCountryPage", + "drawerPlacePage", + "drawerTagPage", + "sortByDate", + "sortByName", + "sortByItemCount", + "sortBySize", + "sortByAlbumFileName", + "sortByRating", + "sortOrderNewestFirst", + "sortOrderOldestFirst", + "sortOrderAtoZ", + "sortOrderZtoA", + "sortOrderHighestFirst", + "sortOrderLowestFirst", + "sortOrderLargestFirst", + "sortOrderSmallestFirst", + "albumGroupTier", + "albumGroupType", + "albumGroupVolume", + "albumGroupNone", + "albumMimeTypeMixed", + "albumPickPageTitleCopy", + "albumPickPageTitleExport", + "albumPickPageTitleMove", + "albumPickPageTitlePick", + "albumCamera", + "albumDownload", + "albumScreenshots", + "albumScreenRecordings", + "albumVideoCaptures", + "albumPageTitle", + "albumEmpty", + "createAlbumButtonLabel", + "newFilterBanner", + "countryPageTitle", + "countryEmpty", + "statePageTitle", + "stateEmpty", + "placePageTitle", + "placeEmpty", + "tagPageTitle", + "tagEmpty", + "binPageTitle", + "searchCollectionFieldHint", + "searchRecentSectionTitle", + "searchDateSectionTitle", + "searchAlbumsSectionTitle", + "searchCountriesSectionTitle", + "searchStatesSectionTitle", + "searchPlacesSectionTitle", + "searchTagsSectionTitle", + "searchRatingSectionTitle", + "searchMetadataSectionTitle", + "settingsPageTitle", + "settingsSystemDefault", + "settingsDefault", + "settingsDisabled", + "settingsAskEverytime", + "settingsModificationWarningDialogMessage", + "settingsSearchFieldLabel", + "settingsSearchEmpty", + "settingsActionExport", + "settingsActionExportDialogTitle", + "settingsActionImport", + "settingsActionImportDialogTitle", + "appExportCovers", + "appExportFavourites", + "appExportSettings", + "settingsNavigationSectionTitle", + "settingsHomeTile", + "settingsHomeDialogTitle", + "settingsShowBottomNavigationBar", + "settingsKeepScreenOnTile", + "settingsKeepScreenOnDialogTitle", + "settingsDoubleBackExit", + "settingsConfirmationTile", + "settingsConfirmationDialogTitle", + "settingsConfirmationBeforeDeleteItems", + "settingsConfirmationBeforeMoveToBinItems", + "settingsConfirmationBeforeMoveUndatedItems", + "settingsConfirmationAfterMoveToBinItems", + "settingsConfirmationVaultDataLoss", + "settingsNavigationDrawerTile", + "settingsNavigationDrawerEditorPageTitle", + "settingsNavigationDrawerBanner", + "settingsNavigationDrawerTabTypes", + "settingsNavigationDrawerTabAlbums", + "settingsNavigationDrawerTabPages", + "settingsNavigationDrawerAddAlbum", + "settingsThumbnailSectionTitle", + "settingsThumbnailOverlayTile", + "settingsThumbnailOverlayPageTitle", + "settingsThumbnailShowFavouriteIcon", + "settingsThumbnailShowTagIcon", + "settingsThumbnailShowLocationIcon", + "settingsThumbnailShowMotionPhotoIcon", + "settingsThumbnailShowRating", + "settingsThumbnailShowRawIcon", + "settingsThumbnailShowVideoDuration", + "settingsCollectionQuickActionsTile", + "settingsCollectionQuickActionEditorPageTitle", + "settingsCollectionQuickActionTabBrowsing", + "settingsCollectionQuickActionTabSelecting", + "settingsCollectionBrowsingQuickActionEditorBanner", + "settingsCollectionSelectionQuickActionEditorBanner", + "settingsCollectionBurstPatternsTile", + "settingsCollectionBurstPatternsNone", + "settingsViewerSectionTitle", + "settingsViewerGestureSideTapNext", + "settingsViewerUseCutout", + "settingsViewerMaximumBrightness", + "settingsMotionPhotoAutoPlay", + "settingsImageBackground", + "settingsViewerQuickActionsTile", + "settingsViewerQuickActionEditorPageTitle", + "settingsViewerQuickActionEditorBanner", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle", + "settingsViewerQuickActionEmpty", + "settingsViewerOverlayTile", + "settingsViewerOverlayPageTitle", + "settingsViewerShowOverlayOnOpening", + "settingsViewerShowHistogram", + "settingsViewerShowMinimap", + "settingsViewerShowInformation", + "settingsViewerShowInformationSubtitle", + "settingsViewerShowRatingTags", + "settingsViewerShowShootingDetails", + "settingsViewerShowDescription", + "settingsViewerShowOverlayThumbnails", + "settingsViewerEnableOverlayBlurEffect", + "settingsViewerSlideshowTile", + "settingsViewerSlideshowPageTitle", + "settingsSlideshowRepeat", + "settingsSlideshowShuffle", + "settingsSlideshowFillScreen", + "settingsSlideshowAnimatedZoomEffect", + "settingsSlideshowTransitionTile", + "settingsSlideshowIntervalTile", + "settingsSlideshowVideoPlaybackTile", + "settingsSlideshowVideoPlaybackDialogTitle", + "settingsVideoPageTitle", + "settingsVideoSectionTitle", + "settingsVideoShowVideos", + "settingsVideoPlaybackTile", + "settingsVideoPlaybackPageTitle", + "settingsVideoEnableHardwareAcceleration", + "settingsVideoAutoPlay", + "settingsVideoLoopModeTile", + "settingsVideoLoopModeDialogTitle", + "settingsVideoResumptionModeTile", + "settingsVideoResumptionModeDialogTitle", + "settingsVideoBackgroundMode", + "settingsVideoBackgroundModeDialogTitle", + "settingsVideoControlsTile", + "settingsVideoControlsPageTitle", + "settingsVideoButtonsTile", + "settingsVideoGestureDoubleTapTogglePlay", + "settingsVideoGestureSideDoubleTapSeek", + "settingsVideoGestureVerticalDragBrightnessVolume", + "settingsSubtitleThemeTile", + "settingsSubtitleThemePageTitle", + "settingsSubtitleThemeSample", + "settingsSubtitleThemeTextAlignmentTile", + "settingsSubtitleThemeTextAlignmentDialogTitle", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle", + "settingsSubtitleThemeTextSize", + "settingsSubtitleThemeShowOutline", + "settingsSubtitleThemeTextColor", + "settingsSubtitleThemeTextOpacity", + "settingsSubtitleThemeBackgroundColor", + "settingsSubtitleThemeBackgroundOpacity", + "settingsSubtitleThemeTextAlignmentLeft", + "settingsSubtitleThemeTextAlignmentCenter", + "settingsSubtitleThemeTextAlignmentRight", + "settingsPrivacySectionTitle", + "settingsAllowInstalledAppAccess", + "settingsAllowInstalledAppAccessSubtitle", + "settingsAllowErrorReporting", + "settingsSaveSearchHistory", + "settingsEnableBin", + "settingsEnableBinSubtitle", + "settingsDisablingBinWarningDialogMessage", + "settingsAllowMediaManagement", + "settingsHiddenItemsTile", + "settingsHiddenItemsPageTitle", + "settingsHiddenItemsTabFilters", + "settingsHiddenFiltersBanner", + "settingsHiddenFiltersEmpty", + "settingsHiddenItemsTabPaths", + "settingsHiddenPathsBanner", + "addPathTooltip", + "settingsStorageAccessTile", + "settingsStorageAccessPageTitle", + "settingsStorageAccessBanner", + "settingsStorageAccessEmpty", + "settingsStorageAccessRevokeTooltip", + "settingsAccessibilitySectionTitle", + "settingsRemoveAnimationsTile", + "settingsRemoveAnimationsDialogTitle", + "settingsTimeToTakeActionTile", + "settingsAccessibilityShowPinchGestureAlternatives", + "settingsDisplaySectionTitle", + "settingsThemeBrightnessTile", + "settingsThemeBrightnessDialogTitle", + "settingsThemeColorHighlights", + "settingsThemeEnableDynamicColor", + "settingsDisplayRefreshRateModeTile", + "settingsDisplayRefreshRateModeDialogTitle", + "settingsDisplayUseTvInterface", + "settingsLanguageSectionTitle", + "settingsLanguageTile", + "settingsLanguagePageTitle", + "settingsCoordinateFormatTile", + "settingsCoordinateFormatDialogTitle", + "settingsUnitSystemTile", + "settingsUnitSystemDialogTitle", + "settingsScreenSaverPageTitle", + "settingsWidgetPageTitle", + "settingsWidgetShowOutline", + "settingsWidgetOpenPage", + "settingsWidgetDisplayedItem", + "settingsCollectionTile", + "statsPageTitle", + "statsWithGps", + "statsTopCountriesSectionTitle", + "statsTopStatesSectionTitle", + "statsTopPlacesSectionTitle", + "statsTopTagsSectionTitle", + "statsTopAlbumsSectionTitle", + "viewerOpenPanoramaButtonLabel", + "viewerSetWallpaperButtonLabel", + "viewerErrorUnknown", + "viewerErrorDoesNotExist", + "viewerInfoPageTitle", + "viewerInfoBackToViewerTooltip", + "viewerInfoUnknown", + "viewerInfoLabelDescription", + "viewerInfoLabelTitle", + "viewerInfoLabelDate", + "viewerInfoLabelResolution", + "viewerInfoLabelSize", + "viewerInfoLabelUri", + "viewerInfoLabelPath", + "viewerInfoLabelDuration", + "viewerInfoLabelOwner", + "viewerInfoLabelCoordinates", + "viewerInfoLabelAddress", + "mapStyleDialogTitle", + "mapStyleTooltip", + "mapZoomInTooltip", + "mapZoomOutTooltip", + "mapPointNorthUpTooltip", + "mapAttributionOsmHot", + "mapAttributionStamen", + "openMapPageTooltip", + "mapEmptyRegion", + "viewerInfoOpenEmbeddedFailureFeedback", + "viewerInfoOpenLinkText", + "viewerInfoViewXmlLinkText", + "viewerInfoSearchFieldLabel", + "viewerInfoSearchEmpty", + "viewerInfoSearchSuggestionDate", + "viewerInfoSearchSuggestionDescription", + "viewerInfoSearchSuggestionDimensions", + "viewerInfoSearchSuggestionResolution", + "viewerInfoSearchSuggestionRights", + "wallpaperUseScrollEffect", + "tagEditorPageTitle", + "tagEditorPageNewTagFieldLabel", + "tagEditorPageAddTagTooltip", + "tagEditorSectionRecent", + "tagEditorSectionPlaceholders", + "tagEditorDiscardDialogMessage", + "tagPlaceholderCountry", + "tagPlaceholderState", + "tagPlaceholderPlace", + "panoramaEnableSensorControl", + "panoramaDisableSensorControl", + "sourceViewerPageTitle", + "filePickerShowHiddenFiles", + "filePickerDoNotShowHiddenFiles", + "filePickerOpenFrom", + "filePickerNoItems", + "filePickerUseThisFolder" + ], + "lt": [ "columnCount", "saveCopyButtonLabel", @@ -4295,6 +4822,9 @@ "lengthUnitPercent", "maxBrightnessNever", "maxBrightnessAlways", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "vaultLockTypePattern", "vaultLockTypePin", "vaultLockTypePassword", @@ -4319,6 +4849,13 @@ "exportEntryDialogQuality", "exportEntryDialogWriteMetadata", "tooManyItemsErrorDialogMessage", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "drawerPlacePage", "statePageTitle", "stateEmpty", @@ -4330,6 +4867,7 @@ "settingsConfirmationVaultDataLoss", "settingsCollectionBurstPatternsTile", "settingsCollectionBurstPatternsNone", + "settingsViewerShowHistogram", "settingsViewerShowDescription", "settingsVideoPlaybackTile", "settingsVideoPlaybackPageTitle", @@ -4511,6 +5049,9 @@ "nameConflictStrategyRename", "nameConflictStrategyReplace", "nameConflictStrategySkip", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "subtitlePositionTop", "subtitlePositionBottom", "themeBrightnessLight", @@ -4667,6 +5208,13 @@ "aboutBugCopyInfoButton", "aboutBugReportInstruction", "aboutBugReportButton", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "aboutCreditsSectionTitle", "aboutCreditsWorldAtlas1", "aboutCreditsWorldAtlas2", @@ -4850,6 +5398,642 @@ "settingsViewerOverlayTile", "settingsViewerOverlayPageTitle", "settingsViewerShowOverlayOnOpening", + "settingsViewerShowHistogram", + "settingsViewerShowMinimap", + "settingsViewerShowInformation", + "settingsViewerShowInformationSubtitle", + "settingsViewerShowRatingTags", + "settingsViewerShowShootingDetails", + "settingsViewerShowDescription", + "settingsViewerShowOverlayThumbnails", + "settingsViewerEnableOverlayBlurEffect", + "settingsViewerSlideshowTile", + "settingsViewerSlideshowPageTitle", + "settingsSlideshowRepeat", + "settingsSlideshowShuffle", + "settingsSlideshowFillScreen", + "settingsSlideshowAnimatedZoomEffect", + "settingsSlideshowTransitionTile", + "settingsSlideshowIntervalTile", + "settingsSlideshowVideoPlaybackTile", + "settingsSlideshowVideoPlaybackDialogTitle", + "settingsVideoPageTitle", + "settingsVideoSectionTitle", + "settingsVideoShowVideos", + "settingsVideoPlaybackTile", + "settingsVideoPlaybackPageTitle", + "settingsVideoEnableHardwareAcceleration", + "settingsVideoAutoPlay", + "settingsVideoLoopModeTile", + "settingsVideoLoopModeDialogTitle", + "settingsVideoResumptionModeTile", + "settingsVideoResumptionModeDialogTitle", + "settingsVideoBackgroundMode", + "settingsVideoBackgroundModeDialogTitle", + "settingsVideoControlsTile", + "settingsVideoControlsPageTitle", + "settingsVideoButtonsTile", + "settingsVideoGestureDoubleTapTogglePlay", + "settingsVideoGestureSideDoubleTapSeek", + "settingsVideoGestureVerticalDragBrightnessVolume", + "settingsSubtitleThemeTile", + "settingsSubtitleThemePageTitle", + "settingsSubtitleThemeSample", + "settingsSubtitleThemeTextAlignmentTile", + "settingsSubtitleThemeTextAlignmentDialogTitle", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle", + "settingsSubtitleThemeTextSize", + "settingsSubtitleThemeShowOutline", + "settingsSubtitleThemeTextColor", + "settingsSubtitleThemeTextOpacity", + "settingsSubtitleThemeBackgroundColor", + "settingsSubtitleThemeBackgroundOpacity", + "settingsSubtitleThemeTextAlignmentLeft", + "settingsSubtitleThemeTextAlignmentCenter", + "settingsSubtitleThemeTextAlignmentRight", + "settingsPrivacySectionTitle", + "settingsAllowInstalledAppAccess", + "settingsAllowInstalledAppAccessSubtitle", + "settingsAllowErrorReporting", + "settingsSaveSearchHistory", + "settingsEnableBin", + "settingsEnableBinSubtitle", + "settingsDisablingBinWarningDialogMessage", + "settingsAllowMediaManagement", + "settingsHiddenItemsTile", + "settingsHiddenItemsPageTitle", + "settingsHiddenItemsTabFilters", + "settingsHiddenFiltersBanner", + "settingsHiddenFiltersEmpty", + "settingsHiddenItemsTabPaths", + "settingsHiddenPathsBanner", + "addPathTooltip", + "settingsStorageAccessTile", + "settingsStorageAccessPageTitle", + "settingsStorageAccessBanner", + "settingsStorageAccessEmpty", + "settingsStorageAccessRevokeTooltip", + "settingsAccessibilitySectionTitle", + "settingsRemoveAnimationsTile", + "settingsRemoveAnimationsDialogTitle", + "settingsTimeToTakeActionTile", + "settingsAccessibilityShowPinchGestureAlternatives", + "settingsDisplaySectionTitle", + "settingsThemeBrightnessTile", + "settingsThemeBrightnessDialogTitle", + "settingsThemeColorHighlights", + "settingsThemeEnableDynamicColor", + "settingsDisplayRefreshRateModeTile", + "settingsDisplayRefreshRateModeDialogTitle", + "settingsDisplayUseTvInterface", + "settingsLanguageSectionTitle", + "settingsLanguageTile", + "settingsLanguagePageTitle", + "settingsCoordinateFormatTile", + "settingsCoordinateFormatDialogTitle", + "settingsUnitSystemTile", + "settingsUnitSystemDialogTitle", + "settingsScreenSaverPageTitle", + "settingsWidgetPageTitle", + "settingsWidgetShowOutline", + "settingsWidgetOpenPage", + "settingsWidgetDisplayedItem", + "settingsCollectionTile", + "statsPageTitle", + "statsWithGps", + "statsTopCountriesSectionTitle", + "statsTopStatesSectionTitle", + "statsTopPlacesSectionTitle", + "statsTopTagsSectionTitle", + "statsTopAlbumsSectionTitle", + "viewerOpenPanoramaButtonLabel", + "viewerSetWallpaperButtonLabel", + "viewerErrorUnknown", + "viewerErrorDoesNotExist", + "viewerInfoPageTitle", + "viewerInfoBackToViewerTooltip", + "viewerInfoUnknown", + "viewerInfoLabelDescription", + "viewerInfoLabelTitle", + "viewerInfoLabelDate", + "viewerInfoLabelResolution", + "viewerInfoLabelSize", + "viewerInfoLabelUri", + "viewerInfoLabelPath", + "viewerInfoLabelDuration", + "viewerInfoLabelOwner", + "viewerInfoLabelCoordinates", + "viewerInfoLabelAddress", + "mapStyleDialogTitle", + "mapStyleTooltip", + "mapZoomInTooltip", + "mapZoomOutTooltip", + "mapPointNorthUpTooltip", + "mapAttributionOsmHot", + "mapAttributionStamen", + "openMapPageTooltip", + "mapEmptyRegion", + "viewerInfoOpenEmbeddedFailureFeedback", + "viewerInfoOpenLinkText", + "viewerInfoViewXmlLinkText", + "viewerInfoSearchFieldLabel", + "viewerInfoSearchEmpty", + "viewerInfoSearchSuggestionDate", + "viewerInfoSearchSuggestionDescription", + "viewerInfoSearchSuggestionDimensions", + "viewerInfoSearchSuggestionResolution", + "viewerInfoSearchSuggestionRights", + "wallpaperUseScrollEffect", + "tagEditorPageTitle", + "tagEditorPageNewTagFieldLabel", + "tagEditorPageAddTagTooltip", + "tagEditorSectionRecent", + "tagEditorSectionPlaceholders", + "tagEditorDiscardDialogMessage", + "tagPlaceholderCountry", + "tagPlaceholderState", + "tagPlaceholderPlace", + "panoramaEnableSensorControl", + "panoramaDisableSensorControl", + "sourceViewerPageTitle", + "filePickerShowHiddenFiles", + "filePickerDoNotShowHiddenFiles", + "filePickerOpenFrom", + "filePickerNoItems", + "filePickerUseThisFolder" + ], + + "my": [ + "sourceStateCataloguing", + "chipActionRename", + "chipActionSetCover", + "chipActionShowCountryStates", + "chipActionCreateAlbum", + "chipActionCreateVault", + "chipActionConfigureVault", + "entryActionCopyToClipboard", + "entryActionDelete", + "entryActionConvert", + "entryActionExport", + "entryActionInfo", + "entryActionRename", + "entryActionRestore", + "entryActionRotateCCW", + "entryActionRotateCW", + "entryActionFlip", + "entryActionPrint", + "entryActionShare", + "entryActionShareImageOnly", + "entryActionShareVideoOnly", + "entryActionViewSource", + "entryActionShowGeoTiffOnMap", + "entryActionConvertMotionPhotoToStillImage", + "entryActionViewMotionPhotoVideo", + "entryActionEdit", + "entryActionOpen", + "entryActionSetAs", + "entryActionOpenMap", + "entryActionRotateScreen", + "entryActionAddFavourite", + "entryActionRemoveFavourite", + "videoActionCaptureFrame", + "videoActionMute", + "videoActionUnmute", + "videoActionPause", + "videoActionPlay", + "videoActionReplay10", + "videoActionSkip10", + "videoActionSelectStreams", + "videoActionSetSpeed", + "viewerActionSettings", + "viewerActionLock", + "viewerActionUnlock", + "slideshowActionResume", + "slideshowActionShowInCollection", + "entryInfoActionEditDate", + "entryInfoActionEditLocation", + "entryInfoActionEditTitleDescription", + "entryInfoActionEditRating", + "entryInfoActionEditTags", + "entryInfoActionRemoveMetadata", + "entryInfoActionExportMetadata", + "entryInfoActionRemoveLocation", + "editorActionTransform", + "editorTransformCrop", + "editorTransformRotate", + "cropAspectRatioFree", + "cropAspectRatioOriginal", + "cropAspectRatioSquare", + "filterAspectRatioLandscapeLabel", + "filterAspectRatioPortraitLabel", + "filterBinLabel", + "filterFavouriteLabel", + "filterNoDateLabel", + "filterNoAddressLabel", + "filterLocatedLabel", + "filterNoLocationLabel", + "filterNoRatingLabel", + "filterTaggedLabel", + "filterNoTagLabel", + "filterNoTitleLabel", + "filterOnThisDayLabel", + "filterRecentlyAddedLabel", + "filterRatingRejectedLabel", + "filterTypeAnimatedLabel", + "filterTypeMotionPhotoLabel", + "filterTypePanoramaLabel", + "filterTypeRawLabel", + "filterTypeSphericalVideoLabel", + "filterTypeGeotiffLabel", + "filterMimeImageLabel", + "filterMimeVideoLabel", + "accessibilityAnimationsRemove", + "accessibilityAnimationsKeep", + "albumTierNew", + "albumTierPinned", + "albumTierSpecial", + "albumTierApps", + "albumTierVaults", + "albumTierRegular", + "coordinateFormatDms", + "coordinateFormatDecimal", + "coordinateDms", + "coordinateDmsNorth", + "coordinateDmsSouth", + "coordinateDmsEast", + "coordinateDmsWest", + "displayRefreshRatePreferHighest", + "displayRefreshRatePreferLowest", + "keepScreenOnNever", + "keepScreenOnVideoPlayback", + "keepScreenOnViewerOnly", + "keepScreenOnAlways", + "lengthUnitPixel", + "lengthUnitPercent", + "mapStyleGoogleNormal", + "mapStyleGoogleHybrid", + "mapStyleGoogleTerrain", + "mapStyleHuaweiNormal", + "mapStyleHuaweiTerrain", + "mapStyleOsmHot", + "mapStyleStamenToner", + "mapStyleStamenWatercolor", + "maxBrightnessNever", + "maxBrightnessAlways", + "nameConflictStrategyRename", + "nameConflictStrategyReplace", + "nameConflictStrategySkip", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", + "subtitlePositionTop", + "subtitlePositionBottom", + "themeBrightnessLight", + "themeBrightnessDark", + "themeBrightnessBlack", + "unitSystemMetric", + "unitSystemImperial", + "vaultLockTypePattern", + "vaultLockTypePin", + "vaultLockTypePassword", + "settingsVideoEnablePip", + "videoControlsPlay", + "videoControlsPlaySeek", + "videoControlsPlayOutside", + "videoControlsNone", + "videoLoopModeNever", + "videoLoopModeShortOnly", + "videoLoopModeAlways", + "videoPlaybackSkip", + "videoPlaybackMuted", + "videoPlaybackWithSound", + "videoResumptionModeNever", + "videoResumptionModeAlways", + "viewerTransitionSlide", + "viewerTransitionParallax", + "viewerTransitionFade", + "viewerTransitionZoomIn", + "viewerTransitionNone", + "wallpaperTargetHome", + "wallpaperTargetLock", + "wallpaperTargetHomeLock", + "widgetDisplayedItemRandom", + "widgetDisplayedItemMostRecent", + "widgetOpenPageHome", + "widgetOpenPageCollection", + "widgetOpenPageViewer", + "widgetTapUpdateWidget", + "storageVolumeDescriptionFallbackPrimary", + "storageVolumeDescriptionFallbackNonPrimary", + "rootDirectoryDescription", + "otherDirectoryDescription", + "storageAccessDialogMessage", + "restrictedAccessDialogMessage", + "notEnoughSpaceDialogMessage", + "missingSystemFilePickerDialogMessage", + "unsupportedTypeDialogMessage", + "nameConflictDialogSingleSourceMessage", + "nameConflictDialogMultipleSourceMessage", + "addShortcutDialogLabel", + "addShortcutButtonLabel", + "noMatchingAppDialogMessage", + "binEntriesConfirmationDialogMessage", + "deleteEntriesConfirmationDialogMessage", + "moveUndatedConfirmationDialogMessage", + "moveUndatedConfirmationDialogSetDate", + "videoResumeDialogMessage", + "videoStartOverButtonLabel", + "videoResumeButtonLabel", + "setCoverDialogLatest", + "setCoverDialogAuto", + "setCoverDialogCustom", + "hideFilterConfirmationDialogMessage", + "newAlbumDialogTitle", + "newAlbumDialogNameLabel", + "newAlbumDialogNameLabelAlreadyExistsHelper", + "newAlbumDialogStorageLabel", + "newVaultWarningDialogMessage", + "newVaultDialogTitle", + "configureVaultDialogTitle", + "vaultDialogLockModeWhenScreenOff", + "vaultDialogLockTypeLabel", + "patternDialogEnter", + "patternDialogConfirm", + "pinDialogEnter", + "pinDialogConfirm", + "passwordDialogEnter", + "passwordDialogConfirm", + "authenticateToConfigureVault", + "authenticateToUnlockVault", + "vaultBinUsageDialogMessage", + "renameAlbumDialogLabel", + "renameAlbumDialogLabelAlreadyExistsHelper", + "renameEntrySetPageTitle", + "renameEntrySetPagePatternFieldLabel", + "renameEntrySetPageInsertTooltip", + "renameEntrySetPagePreviewSectionTitle", + "renameProcessorCounter", + "renameProcessorName", + "deleteSingleAlbumConfirmationDialogMessage", + "deleteMultiAlbumConfirmationDialogMessage", + "exportEntryDialogFormat", + "exportEntryDialogWidth", + "exportEntryDialogHeight", + "exportEntryDialogQuality", + "exportEntryDialogWriteMetadata", + "renameEntryDialogLabel", + "editEntryDialogCopyFromItem", + "editEntryDialogTargetFieldsHeader", + "editEntryDateDialogTitle", + "editEntryDateDialogSetCustom", + "editEntryDateDialogCopyField", + "editEntryDateDialogExtractFromTitle", + "editEntryDateDialogShift", + "editEntryDateDialogSourceFileModifiedDate", + "durationDialogHours", + "durationDialogMinutes", + "durationDialogSeconds", + "editEntryLocationDialogTitle", + "editEntryLocationDialogSetCustom", + "editEntryLocationDialogChooseOnMap", + "editEntryLocationDialogLatitude", + "editEntryLocationDialogLongitude", + "locationPickerUseThisLocationButton", + "editEntryRatingDialogTitle", + "removeEntryMetadataDialogTitle", + "removeEntryMetadataDialogMore", + "removeEntryMetadataMotionPhotoXmpWarningDialogMessage", + "videoSpeedDialogLabel", + "videoStreamSelectionDialogVideo", + "videoStreamSelectionDialogAudio", + "videoStreamSelectionDialogText", + "videoStreamSelectionDialogOff", + "videoStreamSelectionDialogTrack", + "videoStreamSelectionDialogNoSelection", + "genericSuccessFeedback", + "genericFailureFeedback", + "genericDangerWarningDialogMessage", + "tooManyItemsErrorDialogMessage", + "menuActionConfigureView", + "menuActionSelect", + "menuActionSelectAll", + "menuActionSelectNone", + "menuActionMap", + "menuActionSlideshow", + "menuActionStats", + "viewDialogSortSectionTitle", + "viewDialogGroupSectionTitle", + "viewDialogLayoutSectionTitle", + "viewDialogReverseSortOrder", + "tileLayoutMosaic", + "tileLayoutGrid", + "tileLayoutList", + "coverDialogTabCover", + "coverDialogTabApp", + "coverDialogTabColor", + "appPickDialogTitle", + "appPickDialogNone", + "aboutPageTitle", + "aboutLinkLicense", + "aboutLinkPolicy", + "aboutBugSectionTitle", + "aboutBugSaveLogInstruction", + "aboutBugCopyInfoInstruction", + "aboutBugCopyInfoButton", + "aboutBugReportInstruction", + "aboutBugReportButton", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", + "aboutCreditsSectionTitle", + "aboutCreditsWorldAtlas1", + "aboutCreditsWorldAtlas2", + "aboutTranslatorsSectionTitle", + "aboutLicensesSectionTitle", + "aboutLicensesBanner", + "aboutLicensesAndroidLibrariesSectionTitle", + "aboutLicensesFlutterPluginsSectionTitle", + "aboutLicensesFlutterPackagesSectionTitle", + "aboutLicensesDartPackagesSectionTitle", + "aboutLicensesShowAllButtonLabel", + "policyPageTitle", + "collectionPageTitle", + "collectionPickPageTitle", + "collectionSelectPageTitle", + "collectionActionShowTitleSearch", + "collectionActionHideTitleSearch", + "collectionActionAddShortcut", + "collectionActionEmptyBin", + "collectionActionCopy", + "collectionActionMove", + "collectionActionRescan", + "collectionActionEdit", + "collectionSearchTitlesHintText", + "collectionGroupAlbum", + "collectionGroupMonth", + "collectionGroupDay", + "collectionGroupNone", + "sectionUnknown", + "dateToday", + "dateYesterday", + "dateThisMonth", + "collectionDeleteFailureFeedback", + "collectionCopyFailureFeedback", + "collectionMoveFailureFeedback", + "collectionRenameFailureFeedback", + "collectionEditFailureFeedback", + "collectionExportFailureFeedback", + "collectionCopySuccessFeedback", + "collectionMoveSuccessFeedback", + "collectionRenameSuccessFeedback", + "collectionEditSuccessFeedback", + "collectionEmptyFavourites", + "collectionEmptyVideos", + "collectionEmptyImages", + "collectionEmptyGrantAccessButtonLabel", + "collectionSelectSectionTooltip", + "collectionDeselectSectionTooltip", + "drawerAboutButton", + "drawerSettingsButton", + "drawerCollectionAll", + "drawerCollectionFavourites", + "drawerCollectionImages", + "drawerCollectionVideos", + "drawerCollectionAnimated", + "drawerCollectionMotionPhotos", + "drawerCollectionPanoramas", + "drawerCollectionRaws", + "drawerCollectionSphericalVideos", + "drawerAlbumPage", + "drawerCountryPage", + "drawerPlacePage", + "drawerTagPage", + "sortByDate", + "sortByName", + "sortByItemCount", + "sortBySize", + "sortByAlbumFileName", + "sortByRating", + "sortOrderNewestFirst", + "sortOrderOldestFirst", + "sortOrderAtoZ", + "sortOrderZtoA", + "sortOrderHighestFirst", + "sortOrderLowestFirst", + "sortOrderLargestFirst", + "sortOrderSmallestFirst", + "albumGroupTier", + "albumGroupType", + "albumGroupVolume", + "albumGroupNone", + "albumMimeTypeMixed", + "albumPickPageTitleCopy", + "albumPickPageTitleExport", + "albumPickPageTitleMove", + "albumPickPageTitlePick", + "albumCamera", + "albumDownload", + "albumScreenshots", + "albumScreenRecordings", + "albumVideoCaptures", + "albumPageTitle", + "albumEmpty", + "createAlbumButtonLabel", + "newFilterBanner", + "countryPageTitle", + "countryEmpty", + "statePageTitle", + "stateEmpty", + "placePageTitle", + "placeEmpty", + "tagPageTitle", + "tagEmpty", + "binPageTitle", + "searchCollectionFieldHint", + "searchRecentSectionTitle", + "searchDateSectionTitle", + "searchAlbumsSectionTitle", + "searchCountriesSectionTitle", + "searchStatesSectionTitle", + "searchPlacesSectionTitle", + "searchTagsSectionTitle", + "searchRatingSectionTitle", + "searchMetadataSectionTitle", + "settingsPageTitle", + "settingsSystemDefault", + "settingsDefault", + "settingsDisabled", + "settingsAskEverytime", + "settingsModificationWarningDialogMessage", + "settingsSearchFieldLabel", + "settingsSearchEmpty", + "settingsActionExport", + "settingsActionExportDialogTitle", + "settingsActionImport", + "settingsActionImportDialogTitle", + "appExportCovers", + "appExportFavourites", + "appExportSettings", + "settingsNavigationSectionTitle", + "settingsHomeTile", + "settingsHomeDialogTitle", + "settingsShowBottomNavigationBar", + "settingsKeepScreenOnTile", + "settingsKeepScreenOnDialogTitle", + "settingsDoubleBackExit", + "settingsConfirmationTile", + "settingsConfirmationDialogTitle", + "settingsConfirmationBeforeDeleteItems", + "settingsConfirmationBeforeMoveToBinItems", + "settingsConfirmationBeforeMoveUndatedItems", + "settingsConfirmationAfterMoveToBinItems", + "settingsConfirmationVaultDataLoss", + "settingsNavigationDrawerTile", + "settingsNavigationDrawerEditorPageTitle", + "settingsNavigationDrawerBanner", + "settingsNavigationDrawerTabTypes", + "settingsNavigationDrawerTabAlbums", + "settingsNavigationDrawerTabPages", + "settingsNavigationDrawerAddAlbum", + "settingsThumbnailSectionTitle", + "settingsThumbnailOverlayTile", + "settingsThumbnailOverlayPageTitle", + "settingsThumbnailShowFavouriteIcon", + "settingsThumbnailShowTagIcon", + "settingsThumbnailShowLocationIcon", + "settingsThumbnailShowMotionPhotoIcon", + "settingsThumbnailShowRating", + "settingsThumbnailShowRawIcon", + "settingsThumbnailShowVideoDuration", + "settingsCollectionQuickActionsTile", + "settingsCollectionQuickActionEditorPageTitle", + "settingsCollectionQuickActionTabBrowsing", + "settingsCollectionQuickActionTabSelecting", + "settingsCollectionBrowsingQuickActionEditorBanner", + "settingsCollectionSelectionQuickActionEditorBanner", + "settingsCollectionBurstPatternsTile", + "settingsCollectionBurstPatternsNone", + "settingsViewerSectionTitle", + "settingsViewerGestureSideTapNext", + "settingsViewerUseCutout", + "settingsViewerMaximumBrightness", + "settingsMotionPhotoAutoPlay", + "settingsImageBackground", + "settingsViewerQuickActionsTile", + "settingsViewerQuickActionEditorPageTitle", + "settingsViewerQuickActionEditorBanner", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle", + "settingsViewerQuickActionEmpty", + "settingsViewerOverlayTile", + "settingsViewerOverlayPageTitle", + "settingsViewerShowOverlayOnOpening", + "settingsViewerShowHistogram", "settingsViewerShowMinimap", "settingsViewerShowInformation", "settingsViewerShowInformationSubtitle", @@ -5016,42 +6200,23 @@ ], "nb": [ - "saveCopyButtonLabel", - "applyTooltip", - "chipActionShowCountryStates", "viewerActionLock", "viewerActionUnlock", "editorActionTransform", - "editorTransformCrop", - "editorTransformRotate", "cropAspectRatioFree", "cropAspectRatioOriginal", "cropAspectRatioSquare", - "maxBrightnessNever", - "maxBrightnessAlways", - "vaultLockTypePattern", + "overlayHistogramNone", + "overlayHistogramLuminance", "settingsVideoEnablePip", - "videoResumptionModeNever", - "videoResumptionModeAlways", "widgetTapUpdateWidget", "patternDialogEnter", - "patternDialogConfirm", - "exportEntryDialogQuality", - "statePageTitle", - "stateEmpty", - "searchStatesSectionTitle", - "settingsAskEverytime", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "settingsCollectionBurstPatternsTile", "settingsCollectionBurstPatternsNone", - "settingsVideoPlaybackTile", - "settingsVideoPlaybackPageTitle", - "settingsVideoResumptionModeTile", - "settingsVideoResumptionModeDialogTitle", - "settingsVideoBackgroundMode", - "settingsVideoBackgroundModeDialogTitle", - "statsTopStatesSectionTitle", - "tagEditorDiscardDialogMessage", - "tagPlaceholderState" + "settingsViewerShowHistogram", + "statsTopStatesSectionTitle" ], "nl": [ @@ -5073,6 +6238,9 @@ "albumTierVaults", "maxBrightnessNever", "maxBrightnessAlways", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "subtitlePositionTop", "subtitlePositionBottom", "vaultLockTypePattern", @@ -5096,6 +6264,13 @@ "exportEntryDialogQuality", "exportEntryDialogWriteMetadata", "tooManyItemsErrorDialogMessage", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "drawerPlacePage", "statePageTitle", "stateEmpty", @@ -5107,6 +6282,7 @@ "settingsConfirmationVaultDataLoss", "settingsCollectionBurstPatternsTile", "settingsCollectionBurstPatternsNone", + "settingsViewerShowHistogram", "settingsViewerShowRatingTags", "settingsViewerShowDescription", "settingsVideoPlaybackTile", @@ -5129,18 +6305,29 @@ "nn": [ "sourceStateCataloguing", "accessibilityAnimationsKeep", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "settingsVideoEnablePip", "widgetTapUpdateWidget", "authenticateToConfigureVault", "authenticateToUnlockVault", "viewDialogSortSectionTitle", "viewDialogReverseSortOrder", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "aboutCreditsSectionTitle", "aboutLicensesBanner", "aboutLicensesAndroidLibrariesSectionTitle", "collectionActionShowTitleSearch", "collectionActionHideTitleSearch", "drawerCollectionAnimated", + "settingsViewerShowHistogram", "settingsSlideshowAnimatedZoomEffect", "settingsHiddenItemsTabFilters", "settingsHiddenFiltersBanner", @@ -5302,6 +6489,9 @@ "maxBrightnessNever", "maxBrightnessAlways", "nameConflictStrategyReplace", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "subtitlePositionTop", "subtitlePositionBottom", "unitSystemMetric", @@ -5432,6 +6622,13 @@ "aboutBugCopyInfoButton", "aboutBugReportInstruction", "aboutBugReportButton", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "aboutCreditsWorldAtlas1", "aboutCreditsWorldAtlas2", "aboutLicensesSectionTitle", @@ -5589,6 +6786,7 @@ "settingsViewerOverlayTile", "settingsViewerOverlayPageTitle", "settingsViewerShowOverlayOnOpening", + "settingsViewerShowHistogram", "settingsViewerShowMinimap", "settingsViewerShowInformationSubtitle", "settingsViewerShowRatingTags", @@ -5752,15 +6950,17 @@ ], "pt": [ - "saveCopyButtonLabel", - "applyTooltip", - "editorActionTransform", - "editorTransformCrop", - "editorTransformRotate", - "cropAspectRatioFree", - "cropAspectRatioOriginal", - "cropAspectRatioSquare", - "widgetTapUpdateWidget" + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", + "settingsViewerShowHistogram" ], "ro": [ @@ -5774,11 +6974,22 @@ "cropAspectRatioSquare", "maxBrightnessNever", "maxBrightnessAlways", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "videoResumptionModeNever", "videoResumptionModeAlways", "widgetTapUpdateWidget", "exportEntryDialogQuality", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "settingsAskEverytime", + "settingsViewerShowHistogram", "settingsVideoPlaybackTile", "settingsVideoPlaybackPageTitle", "settingsVideoResumptionModeTile", @@ -5813,6 +7024,9 @@ "lengthUnitPercent", "maxBrightnessNever", "maxBrightnessAlways", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "vaultLockTypePattern", "vaultLockTypePin", "vaultLockTypePassword", @@ -5891,6 +7105,13 @@ "aboutBugCopyInfoButton", "aboutBugReportInstruction", "aboutBugReportButton", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "aboutCreditsSectionTitle", "aboutCreditsWorldAtlas1", "aboutCreditsWorldAtlas2", @@ -6074,6 +7295,683 @@ "settingsViewerOverlayTile", "settingsViewerOverlayPageTitle", "settingsViewerShowOverlayOnOpening", + "settingsViewerShowHistogram", + "settingsViewerShowMinimap", + "settingsViewerShowInformation", + "settingsViewerShowInformationSubtitle", + "settingsViewerShowRatingTags", + "settingsViewerShowShootingDetails", + "settingsViewerShowDescription", + "settingsViewerShowOverlayThumbnails", + "settingsViewerEnableOverlayBlurEffect", + "settingsViewerSlideshowTile", + "settingsViewerSlideshowPageTitle", + "settingsSlideshowRepeat", + "settingsSlideshowShuffle", + "settingsSlideshowFillScreen", + "settingsSlideshowAnimatedZoomEffect", + "settingsSlideshowTransitionTile", + "settingsSlideshowIntervalTile", + "settingsSlideshowVideoPlaybackTile", + "settingsSlideshowVideoPlaybackDialogTitle", + "settingsVideoPageTitle", + "settingsVideoSectionTitle", + "settingsVideoShowVideos", + "settingsVideoPlaybackTile", + "settingsVideoPlaybackPageTitle", + "settingsVideoEnableHardwareAcceleration", + "settingsVideoAutoPlay", + "settingsVideoLoopModeTile", + "settingsVideoLoopModeDialogTitle", + "settingsVideoResumptionModeTile", + "settingsVideoResumptionModeDialogTitle", + "settingsVideoBackgroundMode", + "settingsVideoBackgroundModeDialogTitle", + "settingsVideoControlsTile", + "settingsVideoControlsPageTitle", + "settingsVideoButtonsTile", + "settingsVideoGestureDoubleTapTogglePlay", + "settingsVideoGestureSideDoubleTapSeek", + "settingsVideoGestureVerticalDragBrightnessVolume", + "settingsSubtitleThemeTile", + "settingsSubtitleThemePageTitle", + "settingsSubtitleThemeSample", + "settingsSubtitleThemeTextAlignmentTile", + "settingsSubtitleThemeTextAlignmentDialogTitle", + "settingsSubtitleThemeTextPositionTile", + "settingsSubtitleThemeTextPositionDialogTitle", + "settingsSubtitleThemeTextSize", + "settingsSubtitleThemeShowOutline", + "settingsSubtitleThemeTextColor", + "settingsSubtitleThemeTextOpacity", + "settingsSubtitleThemeBackgroundColor", + "settingsSubtitleThemeBackgroundOpacity", + "settingsSubtitleThemeTextAlignmentLeft", + "settingsSubtitleThemeTextAlignmentCenter", + "settingsSubtitleThemeTextAlignmentRight", + "settingsPrivacySectionTitle", + "settingsAllowInstalledAppAccess", + "settingsAllowInstalledAppAccessSubtitle", + "settingsAllowErrorReporting", + "settingsSaveSearchHistory", + "settingsEnableBin", + "settingsEnableBinSubtitle", + "settingsDisablingBinWarningDialogMessage", + "settingsAllowMediaManagement", + "settingsHiddenItemsTile", + "settingsHiddenItemsPageTitle", + "settingsHiddenItemsTabFilters", + "settingsHiddenFiltersBanner", + "settingsHiddenFiltersEmpty", + "settingsHiddenItemsTabPaths", + "settingsHiddenPathsBanner", + "addPathTooltip", + "settingsStorageAccessTile", + "settingsStorageAccessPageTitle", + "settingsStorageAccessBanner", + "settingsStorageAccessEmpty", + "settingsStorageAccessRevokeTooltip", + "settingsAccessibilitySectionTitle", + "settingsRemoveAnimationsTile", + "settingsRemoveAnimationsDialogTitle", + "settingsTimeToTakeActionTile", + "settingsAccessibilityShowPinchGestureAlternatives", + "settingsDisplaySectionTitle", + "settingsThemeBrightnessTile", + "settingsThemeBrightnessDialogTitle", + "settingsThemeColorHighlights", + "settingsThemeEnableDynamicColor", + "settingsDisplayRefreshRateModeTile", + "settingsDisplayRefreshRateModeDialogTitle", + "settingsDisplayUseTvInterface", + "settingsLanguageSectionTitle", + "settingsLanguageTile", + "settingsLanguagePageTitle", + "settingsCoordinateFormatTile", + "settingsCoordinateFormatDialogTitle", + "settingsUnitSystemTile", + "settingsUnitSystemDialogTitle", + "settingsScreenSaverPageTitle", + "settingsWidgetPageTitle", + "settingsWidgetShowOutline", + "settingsWidgetOpenPage", + "settingsWidgetDisplayedItem", + "settingsCollectionTile", + "statsPageTitle", + "statsWithGps", + "statsTopCountriesSectionTitle", + "statsTopStatesSectionTitle", + "statsTopPlacesSectionTitle", + "statsTopTagsSectionTitle", + "statsTopAlbumsSectionTitle", + "viewerOpenPanoramaButtonLabel", + "viewerSetWallpaperButtonLabel", + "viewerErrorUnknown", + "viewerErrorDoesNotExist", + "viewerInfoPageTitle", + "viewerInfoBackToViewerTooltip", + "viewerInfoUnknown", + "viewerInfoLabelDescription", + "viewerInfoLabelTitle", + "viewerInfoLabelDate", + "viewerInfoLabelResolution", + "viewerInfoLabelSize", + "viewerInfoLabelUri", + "viewerInfoLabelPath", + "viewerInfoLabelDuration", + "viewerInfoLabelOwner", + "viewerInfoLabelCoordinates", + "viewerInfoLabelAddress", + "mapStyleDialogTitle", + "mapStyleTooltip", + "mapZoomInTooltip", + "mapZoomOutTooltip", + "mapPointNorthUpTooltip", + "mapAttributionOsmHot", + "mapAttributionStamen", + "openMapPageTooltip", + "mapEmptyRegion", + "viewerInfoOpenEmbeddedFailureFeedback", + "viewerInfoOpenLinkText", + "viewerInfoViewXmlLinkText", + "viewerInfoSearchFieldLabel", + "viewerInfoSearchEmpty", + "viewerInfoSearchSuggestionDate", + "viewerInfoSearchSuggestionDescription", + "viewerInfoSearchSuggestionDimensions", + "viewerInfoSearchSuggestionResolution", + "viewerInfoSearchSuggestionRights", + "wallpaperUseScrollEffect", + "tagEditorPageTitle", + "tagEditorPageNewTagFieldLabel", + "tagEditorPageAddTagTooltip", + "tagEditorSectionRecent", + "tagEditorSectionPlaceholders", + "tagEditorDiscardDialogMessage", + "tagPlaceholderCountry", + "tagPlaceholderState", + "tagPlaceholderPlace", + "panoramaEnableSensorControl", + "panoramaDisableSensorControl", + "sourceViewerPageTitle", + "filePickerShowHiddenFiles", + "filePickerDoNotShowHiddenFiles", + "filePickerOpenFrom", + "filePickerNoItems", + "filePickerUseThisFolder" + ], + + "sl": [ + "itemCount", + "columnCount", + "timeSeconds", + "timeMinutes", + "timeDays", + "focalLength", + "applyButtonLabel", + "deleteButtonLabel", + "nextButtonLabel", + "showButtonLabel", + "hideButtonLabel", + "continueButtonLabel", + "saveCopyButtonLabel", + "applyTooltip", + "cancelTooltip", + "changeTooltip", + "clearTooltip", + "previousTooltip", + "nextTooltip", + "showTooltip", + "hideTooltip", + "actionRemove", + "resetTooltip", + "saveTooltip", + "pickTooltip", + "doubleBackExitMessage", + "doNotAskAgain", + "sourceStateLoading", + "sourceStateCataloguing", + "sourceStateLocatingCountries", + "sourceStateLocatingPlaces", + "chipActionDelete", + "chipActionGoToAlbumPage", + "chipActionGoToCountryPage", + "chipActionGoToPlacePage", + "chipActionGoToTagPage", + "chipActionFilterOut", + "chipActionFilterIn", + "chipActionHide", + "chipActionLock", + "chipActionPin", + "chipActionUnpin", + "chipActionRename", + "chipActionSetCover", + "chipActionShowCountryStates", + "chipActionCreateAlbum", + "chipActionCreateVault", + "chipActionConfigureVault", + "entryActionCopyToClipboard", + "entryActionDelete", + "entryActionConvert", + "entryActionExport", + "entryActionInfo", + "entryActionRename", + "entryActionRestore", + "entryActionRotateCCW", + "entryActionRotateCW", + "entryActionFlip", + "entryActionPrint", + "entryActionShare", + "entryActionShareImageOnly", + "entryActionShareVideoOnly", + "entryActionViewSource", + "entryActionShowGeoTiffOnMap", + "entryActionConvertMotionPhotoToStillImage", + "entryActionViewMotionPhotoVideo", + "entryActionEdit", + "entryActionOpen", + "entryActionSetAs", + "entryActionOpenMap", + "entryActionRotateScreen", + "entryActionAddFavourite", + "entryActionRemoveFavourite", + "videoActionCaptureFrame", + "videoActionMute", + "videoActionUnmute", + "videoActionPause", + "videoActionPlay", + "videoActionReplay10", + "videoActionSkip10", + "videoActionSelectStreams", + "videoActionSetSpeed", + "viewerActionSettings", + "viewerActionLock", + "viewerActionUnlock", + "slideshowActionResume", + "slideshowActionShowInCollection", + "entryInfoActionEditDate", + "entryInfoActionEditLocation", + "entryInfoActionEditTitleDescription", + "entryInfoActionEditRating", + "entryInfoActionEditTags", + "entryInfoActionRemoveMetadata", + "entryInfoActionExportMetadata", + "entryInfoActionRemoveLocation", + "editorActionTransform", + "editorTransformCrop", + "editorTransformRotate", + "cropAspectRatioFree", + "cropAspectRatioOriginal", + "cropAspectRatioSquare", + "filterAspectRatioLandscapeLabel", + "filterAspectRatioPortraitLabel", + "filterBinLabel", + "filterFavouriteLabel", + "filterNoDateLabel", + "filterNoAddressLabel", + "filterLocatedLabel", + "filterNoLocationLabel", + "filterNoRatingLabel", + "filterTaggedLabel", + "filterNoTagLabel", + "filterNoTitleLabel", + "filterOnThisDayLabel", + "filterRecentlyAddedLabel", + "filterRatingRejectedLabel", + "filterTypeAnimatedLabel", + "filterTypeMotionPhotoLabel", + "filterTypePanoramaLabel", + "filterTypeRawLabel", + "filterTypeSphericalVideoLabel", + "filterTypeGeotiffLabel", + "filterMimeImageLabel", + "filterMimeVideoLabel", + "accessibilityAnimationsRemove", + "accessibilityAnimationsKeep", + "albumTierNew", + "albumTierPinned", + "albumTierSpecial", + "albumTierApps", + "albumTierVaults", + "albumTierRegular", + "coordinateFormatDms", + "coordinateFormatDecimal", + "coordinateDms", + "coordinateDmsNorth", + "coordinateDmsSouth", + "coordinateDmsEast", + "coordinateDmsWest", + "displayRefreshRatePreferHighest", + "displayRefreshRatePreferLowest", + "keepScreenOnNever", + "keepScreenOnVideoPlayback", + "keepScreenOnViewerOnly", + "keepScreenOnAlways", + "lengthUnitPixel", + "lengthUnitPercent", + "mapStyleGoogleNormal", + "mapStyleGoogleHybrid", + "mapStyleGoogleTerrain", + "mapStyleHuaweiNormal", + "mapStyleHuaweiTerrain", + "mapStyleOsmHot", + "mapStyleStamenToner", + "mapStyleStamenWatercolor", + "maxBrightnessNever", + "maxBrightnessAlways", + "nameConflictStrategyRename", + "nameConflictStrategyReplace", + "nameConflictStrategySkip", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", + "subtitlePositionTop", + "subtitlePositionBottom", + "themeBrightnessLight", + "themeBrightnessDark", + "themeBrightnessBlack", + "unitSystemMetric", + "unitSystemImperial", + "vaultLockTypePattern", + "vaultLockTypePin", + "vaultLockTypePassword", + "settingsVideoEnablePip", + "videoControlsPlay", + "videoControlsPlaySeek", + "videoControlsPlayOutside", + "videoControlsNone", + "videoLoopModeNever", + "videoLoopModeShortOnly", + "videoLoopModeAlways", + "videoPlaybackSkip", + "videoPlaybackMuted", + "videoPlaybackWithSound", + "videoResumptionModeNever", + "videoResumptionModeAlways", + "viewerTransitionSlide", + "viewerTransitionParallax", + "viewerTransitionFade", + "viewerTransitionZoomIn", + "viewerTransitionNone", + "wallpaperTargetHome", + "wallpaperTargetLock", + "wallpaperTargetHomeLock", + "widgetDisplayedItemRandom", + "widgetDisplayedItemMostRecent", + "widgetOpenPageHome", + "widgetOpenPageCollection", + "widgetOpenPageViewer", + "widgetTapUpdateWidget", + "storageVolumeDescriptionFallbackPrimary", + "storageVolumeDescriptionFallbackNonPrimary", + "rootDirectoryDescription", + "otherDirectoryDescription", + "storageAccessDialogMessage", + "restrictedAccessDialogMessage", + "notEnoughSpaceDialogMessage", + "missingSystemFilePickerDialogMessage", + "unsupportedTypeDialogMessage", + "nameConflictDialogSingleSourceMessage", + "nameConflictDialogMultipleSourceMessage", + "addShortcutDialogLabel", + "addShortcutButtonLabel", + "noMatchingAppDialogMessage", + "binEntriesConfirmationDialogMessage", + "deleteEntriesConfirmationDialogMessage", + "moveUndatedConfirmationDialogMessage", + "moveUndatedConfirmationDialogSetDate", + "videoResumeDialogMessage", + "videoStartOverButtonLabel", + "videoResumeButtonLabel", + "setCoverDialogLatest", + "setCoverDialogAuto", + "setCoverDialogCustom", + "hideFilterConfirmationDialogMessage", + "newAlbumDialogTitle", + "newAlbumDialogNameLabel", + "newAlbumDialogNameLabelAlreadyExistsHelper", + "newAlbumDialogStorageLabel", + "newVaultWarningDialogMessage", + "newVaultDialogTitle", + "configureVaultDialogTitle", + "vaultDialogLockModeWhenScreenOff", + "vaultDialogLockTypeLabel", + "patternDialogEnter", + "patternDialogConfirm", + "pinDialogEnter", + "pinDialogConfirm", + "passwordDialogEnter", + "passwordDialogConfirm", + "authenticateToConfigureVault", + "authenticateToUnlockVault", + "vaultBinUsageDialogMessage", + "renameAlbumDialogLabel", + "renameAlbumDialogLabelAlreadyExistsHelper", + "renameEntrySetPageTitle", + "renameEntrySetPagePatternFieldLabel", + "renameEntrySetPageInsertTooltip", + "renameEntrySetPagePreviewSectionTitle", + "renameProcessorCounter", + "renameProcessorName", + "deleteSingleAlbumConfirmationDialogMessage", + "deleteMultiAlbumConfirmationDialogMessage", + "exportEntryDialogFormat", + "exportEntryDialogWidth", + "exportEntryDialogHeight", + "exportEntryDialogQuality", + "exportEntryDialogWriteMetadata", + "renameEntryDialogLabel", + "editEntryDialogCopyFromItem", + "editEntryDialogTargetFieldsHeader", + "editEntryDateDialogTitle", + "editEntryDateDialogSetCustom", + "editEntryDateDialogCopyField", + "editEntryDateDialogExtractFromTitle", + "editEntryDateDialogShift", + "editEntryDateDialogSourceFileModifiedDate", + "durationDialogHours", + "durationDialogMinutes", + "durationDialogSeconds", + "editEntryLocationDialogTitle", + "editEntryLocationDialogSetCustom", + "editEntryLocationDialogChooseOnMap", + "editEntryLocationDialogLatitude", + "editEntryLocationDialogLongitude", + "locationPickerUseThisLocationButton", + "editEntryRatingDialogTitle", + "removeEntryMetadataDialogTitle", + "removeEntryMetadataDialogMore", + "removeEntryMetadataMotionPhotoXmpWarningDialogMessage", + "videoSpeedDialogLabel", + "videoStreamSelectionDialogVideo", + "videoStreamSelectionDialogAudio", + "videoStreamSelectionDialogText", + "videoStreamSelectionDialogOff", + "videoStreamSelectionDialogTrack", + "videoStreamSelectionDialogNoSelection", + "genericSuccessFeedback", + "genericFailureFeedback", + "genericDangerWarningDialogMessage", + "tooManyItemsErrorDialogMessage", + "menuActionConfigureView", + "menuActionSelect", + "menuActionSelectAll", + "menuActionSelectNone", + "menuActionMap", + "menuActionSlideshow", + "menuActionStats", + "viewDialogSortSectionTitle", + "viewDialogGroupSectionTitle", + "viewDialogLayoutSectionTitle", + "viewDialogReverseSortOrder", + "tileLayoutMosaic", + "tileLayoutGrid", + "tileLayoutList", + "coverDialogTabCover", + "coverDialogTabApp", + "coverDialogTabColor", + "appPickDialogTitle", + "appPickDialogNone", + "aboutPageTitle", + "aboutLinkLicense", + "aboutLinkPolicy", + "aboutBugSectionTitle", + "aboutBugSaveLogInstruction", + "aboutBugCopyInfoInstruction", + "aboutBugCopyInfoButton", + "aboutBugReportInstruction", + "aboutBugReportButton", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", + "aboutCreditsSectionTitle", + "aboutCreditsWorldAtlas1", + "aboutCreditsWorldAtlas2", + "aboutTranslatorsSectionTitle", + "aboutLicensesSectionTitle", + "aboutLicensesBanner", + "aboutLicensesAndroidLibrariesSectionTitle", + "aboutLicensesFlutterPluginsSectionTitle", + "aboutLicensesFlutterPackagesSectionTitle", + "aboutLicensesDartPackagesSectionTitle", + "aboutLicensesShowAllButtonLabel", + "policyPageTitle", + "collectionPageTitle", + "collectionPickPageTitle", + "collectionSelectPageTitle", + "collectionActionShowTitleSearch", + "collectionActionHideTitleSearch", + "collectionActionAddShortcut", + "collectionActionEmptyBin", + "collectionActionCopy", + "collectionActionMove", + "collectionActionRescan", + "collectionActionEdit", + "collectionSearchTitlesHintText", + "collectionGroupAlbum", + "collectionGroupMonth", + "collectionGroupDay", + "collectionGroupNone", + "sectionUnknown", + "dateToday", + "dateYesterday", + "dateThisMonth", + "collectionDeleteFailureFeedback", + "collectionCopyFailureFeedback", + "collectionMoveFailureFeedback", + "collectionRenameFailureFeedback", + "collectionEditFailureFeedback", + "collectionExportFailureFeedback", + "collectionCopySuccessFeedback", + "collectionMoveSuccessFeedback", + "collectionRenameSuccessFeedback", + "collectionEditSuccessFeedback", + "collectionEmptyFavourites", + "collectionEmptyVideos", + "collectionEmptyImages", + "collectionEmptyGrantAccessButtonLabel", + "collectionSelectSectionTooltip", + "collectionDeselectSectionTooltip", + "drawerAboutButton", + "drawerSettingsButton", + "drawerCollectionAll", + "drawerCollectionFavourites", + "drawerCollectionImages", + "drawerCollectionVideos", + "drawerCollectionAnimated", + "drawerCollectionMotionPhotos", + "drawerCollectionPanoramas", + "drawerCollectionRaws", + "drawerCollectionSphericalVideos", + "drawerAlbumPage", + "drawerCountryPage", + "drawerPlacePage", + "drawerTagPage", + "sortByDate", + "sortByName", + "sortByItemCount", + "sortBySize", + "sortByAlbumFileName", + "sortByRating", + "sortOrderNewestFirst", + "sortOrderOldestFirst", + "sortOrderAtoZ", + "sortOrderZtoA", + "sortOrderHighestFirst", + "sortOrderLowestFirst", + "sortOrderLargestFirst", + "sortOrderSmallestFirst", + "albumGroupTier", + "albumGroupType", + "albumGroupVolume", + "albumGroupNone", + "albumMimeTypeMixed", + "albumPickPageTitleCopy", + "albumPickPageTitleExport", + "albumPickPageTitleMove", + "albumPickPageTitlePick", + "albumCamera", + "albumDownload", + "albumScreenshots", + "albumScreenRecordings", + "albumVideoCaptures", + "albumPageTitle", + "albumEmpty", + "createAlbumButtonLabel", + "newFilterBanner", + "countryPageTitle", + "countryEmpty", + "statePageTitle", + "stateEmpty", + "placePageTitle", + "placeEmpty", + "tagPageTitle", + "tagEmpty", + "binPageTitle", + "searchCollectionFieldHint", + "searchRecentSectionTitle", + "searchDateSectionTitle", + "searchAlbumsSectionTitle", + "searchCountriesSectionTitle", + "searchStatesSectionTitle", + "searchPlacesSectionTitle", + "searchTagsSectionTitle", + "searchRatingSectionTitle", + "searchMetadataSectionTitle", + "settingsPageTitle", + "settingsSystemDefault", + "settingsDefault", + "settingsDisabled", + "settingsAskEverytime", + "settingsModificationWarningDialogMessage", + "settingsSearchFieldLabel", + "settingsSearchEmpty", + "settingsActionExport", + "settingsActionExportDialogTitle", + "settingsActionImport", + "settingsActionImportDialogTitle", + "appExportCovers", + "appExportFavourites", + "appExportSettings", + "settingsNavigationSectionTitle", + "settingsHomeTile", + "settingsHomeDialogTitle", + "settingsShowBottomNavigationBar", + "settingsKeepScreenOnTile", + "settingsKeepScreenOnDialogTitle", + "settingsDoubleBackExit", + "settingsConfirmationTile", + "settingsConfirmationDialogTitle", + "settingsConfirmationBeforeDeleteItems", + "settingsConfirmationBeforeMoveToBinItems", + "settingsConfirmationBeforeMoveUndatedItems", + "settingsConfirmationAfterMoveToBinItems", + "settingsConfirmationVaultDataLoss", + "settingsNavigationDrawerTile", + "settingsNavigationDrawerEditorPageTitle", + "settingsNavigationDrawerBanner", + "settingsNavigationDrawerTabTypes", + "settingsNavigationDrawerTabAlbums", + "settingsNavigationDrawerTabPages", + "settingsNavigationDrawerAddAlbum", + "settingsThumbnailSectionTitle", + "settingsThumbnailOverlayTile", + "settingsThumbnailOverlayPageTitle", + "settingsThumbnailShowFavouriteIcon", + "settingsThumbnailShowTagIcon", + "settingsThumbnailShowLocationIcon", + "settingsThumbnailShowMotionPhotoIcon", + "settingsThumbnailShowRating", + "settingsThumbnailShowRawIcon", + "settingsThumbnailShowVideoDuration", + "settingsCollectionQuickActionsTile", + "settingsCollectionQuickActionEditorPageTitle", + "settingsCollectionQuickActionTabBrowsing", + "settingsCollectionQuickActionTabSelecting", + "settingsCollectionBrowsingQuickActionEditorBanner", + "settingsCollectionSelectionQuickActionEditorBanner", + "settingsCollectionBurstPatternsTile", + "settingsCollectionBurstPatternsNone", + "settingsViewerSectionTitle", + "settingsViewerGestureSideTapNext", + "settingsViewerUseCutout", + "settingsViewerMaximumBrightness", + "settingsMotionPhotoAutoPlay", + "settingsImageBackground", + "settingsViewerQuickActionsTile", + "settingsViewerQuickActionEditorPageTitle", + "settingsViewerQuickActionEditorBanner", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle", + "settingsViewerQuickActionEmpty", + "settingsViewerOverlayTile", + "settingsViewerOverlayPageTitle", + "settingsViewerShowOverlayOnOpening", + "settingsViewerShowHistogram", "settingsViewerShowMinimap", "settingsViewerShowInformation", "settingsViewerShowInformationSubtitle", @@ -6267,6 +8165,9 @@ "lengthUnitPercent", "maxBrightnessNever", "maxBrightnessAlways", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "vaultLockTypePattern", "vaultLockTypePin", "vaultLockTypePassword", @@ -6294,6 +8195,13 @@ "editEntryDateDialogShift", "removeEntryMetadataDialogTitle", "tooManyItemsErrorDialogMessage", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "collectionActionShowTitleSearch", "collectionActionHideTitleSearch", "collectionActionAddShortcut", @@ -6462,6 +8370,7 @@ "settingsViewerOverlayTile", "settingsViewerOverlayPageTitle", "settingsViewerShowOverlayOnOpening", + "settingsViewerShowHistogram", "settingsViewerShowMinimap", "settingsViewerShowInformation", "settingsViewerShowInformationSubtitle", @@ -6648,6 +8557,9 @@ "lengthUnitPercent", "maxBrightnessNever", "maxBrightnessAlways", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "vaultLockTypePattern", "vaultLockTypePin", "vaultLockTypePassword", @@ -6671,6 +8583,13 @@ "vaultBinUsageDialogMessage", "exportEntryDialogQuality", "exportEntryDialogWriteMetadata", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "drawerPlacePage", "statePageTitle", "stateEmpty", @@ -6681,6 +8600,7 @@ "settingsConfirmationVaultDataLoss", "settingsCollectionBurstPatternsTile", "settingsCollectionBurstPatternsNone", + "settingsViewerShowHistogram", "settingsVideoPlaybackTile", "settingsVideoPlaybackPageTitle", "settingsVideoResumptionModeTile", @@ -6707,6 +8627,9 @@ "lengthUnitPercent", "maxBrightnessNever", "maxBrightnessAlways", + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", "vaultLockTypePattern", "vaultLockTypePin", "settingsVideoEnablePip", @@ -6725,6 +8648,13 @@ "exportEntryDialogQuality", "exportEntryDialogWriteMetadata", "tooManyItemsErrorDialogMessage", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", "drawerPlacePage", "statePageTitle", "stateEmpty", @@ -6736,6 +8666,7 @@ "settingsConfirmationVaultDataLoss", "settingsCollectionBurstPatternsTile", "settingsCollectionBurstPatternsNone", + "settingsViewerShowHistogram", "settingsViewerShowDescription", "settingsVideoPlaybackTile", "settingsVideoPlaybackPageTitle", @@ -6748,5 +8679,19 @@ "settingsAccessibilityShowPinchGestureAlternatives", "statsTopStatesSectionTitle", "tagPlaceholderState" + ], + + "zh_Hant": [ + "overlayHistogramNone", + "overlayHistogramRGB", + "overlayHistogramLuminance", + "aboutDataUsageSectionTitle", + "aboutDataUsageData", + "aboutDataUsageCache", + "aboutDataUsageDatabase", + "aboutDataUsageMisc", + "aboutDataUsageInternal", + "aboutDataUsageExternal", + "settingsViewerShowHistogram" ] } diff --git a/whatsnew/whatsnew-en-US b/whatsnew/whatsnew-en-US index e126e5ad6..ac542c6e0 100644 --- a/whatsnew/whatsnew-en-US +++ b/whatsnew/whatsnew-en-US @@ -1,5 +1,5 @@ -In v1.8.9: -- play your animated PNGs -- set your home to the Tags page -- enjoy the app in Norwegian (Nynorsk) +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 \ No newline at end of file