diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index feee323cd..bb6136f7e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -15,7 +15,7 @@ jobs: - uses: subosito/flutter-action@v1 with: channel: stable - flutter-version: '2.10.2' + flutter-version: '2.10.3' - name: Clone the repository. uses: actions/checkout@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 813ec0b82..8172cb051 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: - uses: subosito/flutter-action@v1 with: channel: stable - flutter-version: '2.10.2' + flutter-version: '2.10.3' # Workaround for this Android Gradle Plugin issue (supposedly fixed in AGP 4.1): # https://issuetracker.google.com/issues/144111441 @@ -52,12 +52,12 @@ jobs: rm release.keystore.asc mkdir outputs (cd scripts/; ./apply_flavor_play.sh) - flutter build appbundle -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_2.10.2.sksl.json + flutter build appbundle -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_2.10.3.sksl.json cp build/app/outputs/bundle/playRelease/*.aab outputs - flutter build apk -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_2.10.2.sksl.json + flutter build apk -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_2.10.3.sksl.json cp build/app/outputs/apk/play/release/*.apk outputs (cd scripts/; ./apply_flavor_izzy.sh) - flutter build apk -t lib/main_izzy.dart --flavor izzy --split-per-abi --bundle-sksl-path shaders_2.10.2.sksl.json + flutter build apk -t lib/main_izzy.dart --flavor izzy --split-per-abi --bundle-sksl-path shaders_2.10.3.sksl.json cp build/app/outputs/apk/izzy/release/*.apk outputs rm $AVES_STORE_FILE env: diff --git a/CHANGELOG.md b/CHANGELOG.md index d17e6f445..3c51c196f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [v1.6.2] - 2022-03-07 + +### Added + +- Viewer: optional thumbnail preview +- Video: optional gestures to play/seek +- Video: mute action +- Japanese translation (thanks Maki) + +### Changed + +- Viewer: overlay reorganization +- upgraded Flutter to stable v2.10.3 + +### Fixed + +- storage write access for Android <11 +- various bin related fixes +- Viewer: apply video settings change without leaving the viewer + ## [v1.6.1] - 2022-02-23 ### Added diff --git a/README.md b/README.md index 4f17a28bb..3c6d54aa9 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ At this stage this project does *not* accept PRs, except for translations. ### Translations -If you want to translate this app in your language and share the result, [there is a guide](https://github.com/deckerst/aves/wiki/Contributing-to-Translations). English, Korean and French are already handled by me. Russian, German, Spanish, Portuguese & Indonesian are handled by generous volunteers. +If you want to translate this app in your language and share the result, [there is a guide](https://github.com/deckerst/aves/wiki/Contributing-to-Translations). English, Korean and French are already handled by me. Russian, German, Spanish, Portuguese, Indonesian & Japanese are handled by generous volunteers. ### Donations diff --git a/android/app/build.gradle b/android/app/build.gradle index fed4ca45f..ccdb492b5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -152,7 +152,7 @@ dependencies { implementation 'com.github.deckerst:Android-TiffBitmapFactory:876e53870a' // forked, built by JitPack, cf https://jitpack.io/p/deckerst/pixymeta-android implementation 'com.github.deckerst:pixymeta-android:706bd73d6e' - implementation 'com.github.bumptech.glide:glide:4.13.0' + implementation 'com.github.bumptech.glide:glide:4.13.1' kapt 'androidx.annotation:annotation:1.3.0' kapt 'com.github.bumptech.glide:compiler:4.13.0' diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt index 5e99cee7e..a6b54573d 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt @@ -59,7 +59,11 @@ class ThumbnailFetcher internal constructor( // As of Android R, the Media Store content resolver may return a thumbnail // that is automatically rotated according to EXIF orientation, but not flipped, // so we skip this step for flipped entries. - bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) getByResolver() else getByMediaStore() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + bitmap = getByResolver() + } else if (StorageUtils.isMediaStoreContentUri(uri)) { + bitmap = getByMediaStore() + } } } catch (e: Exception) { exception = e diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt index 43e648b34..82bbdcf2e 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt @@ -74,10 +74,6 @@ abstract class ImageProvider { throw UnsupportedOperationException("`scanPostMetadataEdit` is not supported by this image provider") } - open fun scanObsoletePath(context: Context, path: String, mimeType: String) { - throw UnsupportedOperationException("`scanObsoletePath` is not supported by this image provider") - } - suspend fun exportMultiple( activity: Activity, imageExportMimeType: String, diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt index 79f3f4a21..4708e909d 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt @@ -30,6 +30,8 @@ import deckers.thibault.aves.utils.StorageUtils.PathSegments import deckers.thibault.aves.utils.StorageUtils.ensureTrailingSeparator import deckers.thibault.aves.utils.StorageUtils.removeTrailingSeparator import deckers.thibault.aves.utils.UriUtils.tryParseId +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking import java.io.File import java.io.OutputStream import java.util.* @@ -293,17 +295,17 @@ class MediaStoreImageProvider : ImageProvider() { if (fileExists) { if (StorageUtils.canEditByFile(activity, path)) { if (hasEntry(activity, uri)) { - Log.d(LOG_TAG, "delete content at uri=$uri path=$path") + Log.d(LOG_TAG, "delete [permission:file, file exists, content exists] content at uri=$uri path=$path") activity.contentResolver.delete(uri, null, null) } // in theory, deleting via content resolver should remove the file on storage // in practice, the file may still be there afterwards if (file.exists()) { - Log.d(LOG_TAG, "delete file at uri=$uri path=$path") + Log.d(LOG_TAG, "delete [permission:file, file exists after content delete] file at uri=$uri path=$path") if (file.delete()) { // in theory, scanning an obsolete path should remove the entry from the Media Store // in practice, the entry may still be there afterwards - scanObsoletePath(activity, path, mimeType) + scanObsoletePath(activity, uri, path, mimeType) return } } else { @@ -312,18 +314,28 @@ class MediaStoreImageProvider : ImageProvider() { } else if (!isMediaUriPermissionGranted(activity, uri, mimeType) && StorageUtils.requireAccessPermission(activity, path) ) { - // if the file is on SD card, calling the content resolver `delete()` - // removes the entry from the Media Store but it doesn't delete the file, - // even when the app has the permission, so we manually delete the document file - Log.d(LOG_TAG, "delete document (fileExists=$fileExists) at uri=$uri path=$path") - val df = StorageUtils.getDocumentFile(activity, path, uri) + // the delete request may yield a `RecoverableSecurityException` when using scoped storage, + // even if we have permissions on the tree document via SAF + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q && hasEntry(activity, uri)) { + Log.d(LOG_TAG, "delete [permission:doc, file exists, content exists] content at uri=$uri path=$path") + activity.contentResolver.delete(uri, null, null) + } - @Suppress("BlockingMethodInNonBlockingContext") - if (df != null && df.delete()) { - scanObsoletePath(activity, path, mimeType) + // in theory, deleting via content resolver should remove the file on storage + // in practice, the file may still be there afterwards + if (file.exists()) { + Log.d(LOG_TAG, "delete [permission:doc, file exists after content delete] document at uri=$uri path=$path") + val df = StorageUtils.getDocumentFile(activity, path, uri) + + @Suppress("BlockingMethodInNonBlockingContext") + if (df != null && df.delete()) { + scanObsoletePath(activity, uri, path, mimeType) + return + } + throw Exception("failed to delete document with df=$df") + } else { return } - throw Exception("failed to delete document with df=$df") } } else if (uri.scheme?.lowercase(Locale.ROOT) == ContentResolver.SCHEME_FILE) { val uriFilePath = File(uri.path!!).path @@ -332,7 +344,7 @@ class MediaStoreImageProvider : ImageProvider() { } try { - Log.d(LOG_TAG, "delete content (fileExists=$fileExists) at uri=$uri path=$path") + Log.d(LOG_TAG, "delete [file exists=$fileExists] content at uri=$uri path=$path") if (activity.contentResolver.delete(uri, null, null) > 0) return if (hasEntry(activity, uri) || file.exists()) { @@ -620,7 +632,7 @@ class MediaStoreImageProvider : ImageProvider() { val newFile = File(oldFile.parent, newFileName) return when { oldFile == newFile -> skippedFieldMap - StorageUtils.canEditByFile(activity, oldPath) -> renameSingleByFile(activity, mimeType, oldPath, newFile) + StorageUtils.canEditByFile(activity, oldPath) -> renameSingleByFile(activity, mimeType, oldMediaUri, oldPath, newFile) isMediaUriPermissionGranted(activity, oldMediaUri, mimeType) -> renameSingleByMediaStore(activity, mimeType, oldMediaUri, newFile) else -> renameSingleByTreeDoc(activity, mimeType, oldMediaUri, oldPath, newFile) } @@ -674,13 +686,14 @@ class MediaStoreImageProvider : ImageProvider() { if (!renamed) { throw Exception("failed to rename document at path=$oldPath") } - scanObsoletePath(activity, oldPath, mimeType) + scanObsoletePath(activity, oldMediaUri, oldPath, mimeType) return scanNewPath(activity, newFile.path, mimeType) } private suspend fun renameSingleByFile( activity: Activity, mimeType: String, + oldMediaUri: Uri, oldPath: String, newFile: File ): FieldMap { @@ -689,7 +702,7 @@ class MediaStoreImageProvider : ImageProvider() { if (!renamed) { throw Exception("failed to rename file at path=$oldPath") } - scanObsoletePath(activity, oldPath, mimeType) + scanObsoletePath(activity, oldMediaUri, oldPath, mimeType) return scanNewPath(activity, newFile.path, mimeType) } @@ -714,8 +727,31 @@ class MediaStoreImageProvider : ImageProvider() { } } - override fun scanObsoletePath(context: Context, path: String, mimeType: String) { - MediaScannerConnection.scanFile(context, arrayOf(path), arrayOf(mimeType), null) + private fun scanObsoletePath(context: Context, uri: Uri, path: String, mimeType: String) { + val file = File(path) + val delayMillis = 500L + val maxDelayMillis = 10000L + var totalDelayMillis = 0L + while (file.exists()) { + if (!hasEntry(context, uri)) return + if (totalDelayMillis < maxDelayMillis) { + Log.d(LOG_TAG, "Trying to scan obsolete path but file exists at path=$path. Will retry in $delayMillis ms (total: $totalDelayMillis ms)") + runBlocking { delay(delayMillis) } + totalDelayMillis += delayMillis + } else { + throw Exception("Timeout ($maxDelayMillis ms) to clear MediaStore entry for file at path=$path") + } + } + + if (hasEntry(context, uri)) { + MediaScannerConnection.scanFile(context, arrayOf(path), arrayOf(mimeType)) { _, newUri: Uri? -> + if (newUri != null && hasEntry(context, newUri)) { + Log.w(LOG_TAG, "Failed to clear Media Store entry at uri=$newUri path=$path") + } else { + Log.w(LOG_TAG, "Cleared Media Store entry at uri=$newUri path=$path") + } + } + } } suspend fun scanNewPath(context: Context, path: String, mimeType: String): FieldMap = diff --git a/android/app/src/main/res/values-ja/strings.xml b/android/app/src/main/res/values-ja/strings.xml new file mode 100644 index 000000000..3c2f5d93e --- /dev/null +++ b/android/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,10 @@ + + +アヴェス +検索 +動画 +メディアスキャン +画像と動画をスキャン +メディアをスキャン中 +停止 + \ No newline at end of file diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/1.png b/fastlane/metadata/android/de/images/phoneScreenshots/1.png index 7bb979df1..0ef48db3d 100644 Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/1.png and b/fastlane/metadata/android/de/images/phoneScreenshots/1.png differ diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/2.png b/fastlane/metadata/android/de/images/phoneScreenshots/2.png index b770a7c9e..fb5080556 100644 Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/2.png and b/fastlane/metadata/android/de/images/phoneScreenshots/2.png differ diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/3.png b/fastlane/metadata/android/de/images/phoneScreenshots/3.png index 56ef9ded6..540e6c430 100644 Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/3.png and b/fastlane/metadata/android/de/images/phoneScreenshots/3.png differ diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/4.png b/fastlane/metadata/android/de/images/phoneScreenshots/4.png index 0a8c2deb7..674ee85e7 100644 Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/4.png and b/fastlane/metadata/android/de/images/phoneScreenshots/4.png differ diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/5.png b/fastlane/metadata/android/de/images/phoneScreenshots/5.png index a048d09d4..19bfe9bcd 100644 Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/5.png and b/fastlane/metadata/android/de/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/6.png b/fastlane/metadata/android/de/images/phoneScreenshots/6.png index e42e4d034..2e8d3034d 100644 Binary files a/fastlane/metadata/android/de/images/phoneScreenshots/6.png and b/fastlane/metadata/android/de/images/phoneScreenshots/6.png differ diff --git a/fastlane/metadata/android/en-US/changelogs/1068.txt b/fastlane/metadata/android/en-US/changelogs/1068.txt new file mode 100644 index 000000000..decd4dce4 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/1068.txt @@ -0,0 +1,5 @@ +In v1.6.2: +- revisited viewer: new layout, thumbnail previews, video gestures +- storage related fixes for Android 10 and older +- enjoy the app in Japanese +Full changelog available on GitHub \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png index 4674df71c..aef538c60 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png index d699af62a..a6d84ce73 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png index 20457a45f..039a7a267 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png index 92fec2632..42a897ce9 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png index 95ef69497..7fc82a805 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png index c2383bd70..e93411a46 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png differ diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/1.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/1.png index a4ca6bdf7..aedd59b04 100644 Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/1.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/1.png differ diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/2.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/2.png index bc1654404..907ea242f 100644 Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/2.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/2.png differ diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/3.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/3.png index f86f0c4a2..fff50ac57 100644 Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/3.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/3.png differ diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/4.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/4.png index b5eb49bcc..69a09e0b3 100644 Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/4.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/4.png differ diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/5.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/5.png index 7021c0374..77cbdf3b5 100644 Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/5.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/es-MX/images/phoneScreenshots/6.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/6.png index 7735c2cb3..1cb5582b6 100644 Binary files a/fastlane/metadata/android/es-MX/images/phoneScreenshots/6.png and b/fastlane/metadata/android/es-MX/images/phoneScreenshots/6.png differ diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/1.png b/fastlane/metadata/android/fr/images/phoneScreenshots/1.png index eb62e1b14..4e9d3a559 100644 Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/1.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/1.png differ diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/2.png b/fastlane/metadata/android/fr/images/phoneScreenshots/2.png index 6aa5def2a..d6d30754d 100644 Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/2.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/2.png differ diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/3.png b/fastlane/metadata/android/fr/images/phoneScreenshots/3.png index ad50c09d1..d6d1136a2 100644 Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/3.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/3.png differ diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/4.png b/fastlane/metadata/android/fr/images/phoneScreenshots/4.png index 070238313..1e1b71624 100644 Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/4.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/4.png differ diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/5.png b/fastlane/metadata/android/fr/images/phoneScreenshots/5.png index cc70a692c..82e97daf0 100644 Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/5.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/fr/images/phoneScreenshots/6.png b/fastlane/metadata/android/fr/images/phoneScreenshots/6.png index 7961778de..dcc9e4eff 100644 Binary files a/fastlane/metadata/android/fr/images/phoneScreenshots/6.png and b/fastlane/metadata/android/fr/images/phoneScreenshots/6.png differ diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/1.png b/fastlane/metadata/android/id/images/phoneScreenshots/1.png index 9bca9f58b..1148786fd 100644 Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/1.png and b/fastlane/metadata/android/id/images/phoneScreenshots/1.png differ diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/2.png b/fastlane/metadata/android/id/images/phoneScreenshots/2.png index 85100c774..f1a6c95bb 100644 Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/2.png and b/fastlane/metadata/android/id/images/phoneScreenshots/2.png differ diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/3.png b/fastlane/metadata/android/id/images/phoneScreenshots/3.png index cfdeeec8f..646a107e9 100644 Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/3.png and b/fastlane/metadata/android/id/images/phoneScreenshots/3.png differ diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/4.png b/fastlane/metadata/android/id/images/phoneScreenshots/4.png index d5240f2b0..887f67554 100644 Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/4.png and b/fastlane/metadata/android/id/images/phoneScreenshots/4.png differ diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/5.png b/fastlane/metadata/android/id/images/phoneScreenshots/5.png index 00baaa099..5684e687b 100644 Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/5.png and b/fastlane/metadata/android/id/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/id/images/phoneScreenshots/6.png b/fastlane/metadata/android/id/images/phoneScreenshots/6.png index be711d86a..43ed58d3e 100644 Binary files a/fastlane/metadata/android/id/images/phoneScreenshots/6.png and b/fastlane/metadata/android/id/images/phoneScreenshots/6.png differ diff --git a/fastlane/metadata/android/ja/images/featureGraphics.png b/fastlane/metadata/android/ja/images/featureGraphics.png new file mode 100644 index 000000000..db3be126f Binary files /dev/null and b/fastlane/metadata/android/ja/images/featureGraphics.png differ diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/1.png b/fastlane/metadata/android/ja/images/phoneScreenshots/1.png new file mode 100644 index 000000000..8a2eb8f99 Binary files /dev/null and b/fastlane/metadata/android/ja/images/phoneScreenshots/1.png differ diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/2.png b/fastlane/metadata/android/ja/images/phoneScreenshots/2.png new file mode 100644 index 000000000..e840f74b2 Binary files /dev/null and b/fastlane/metadata/android/ja/images/phoneScreenshots/2.png differ diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/3.png b/fastlane/metadata/android/ja/images/phoneScreenshots/3.png new file mode 100644 index 000000000..a72c431f3 Binary files /dev/null and b/fastlane/metadata/android/ja/images/phoneScreenshots/3.png differ diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/4.png b/fastlane/metadata/android/ja/images/phoneScreenshots/4.png new file mode 100644 index 000000000..e47324358 Binary files /dev/null and b/fastlane/metadata/android/ja/images/phoneScreenshots/4.png differ diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/5.png b/fastlane/metadata/android/ja/images/phoneScreenshots/5.png new file mode 100644 index 000000000..2c0a91d4f Binary files /dev/null and b/fastlane/metadata/android/ja/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/6.png b/fastlane/metadata/android/ja/images/phoneScreenshots/6.png new file mode 100644 index 000000000..392691f0e Binary files /dev/null and b/fastlane/metadata/android/ja/images/phoneScreenshots/6.png differ diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/1.png b/fastlane/metadata/android/ko/images/phoneScreenshots/1.png index d4fcc34c3..d8e080ffc 100644 Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/1.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/1.png differ diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/2.png b/fastlane/metadata/android/ko/images/phoneScreenshots/2.png index 13a2135ee..d6928ee42 100644 Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/2.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/2.png differ diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/3.png b/fastlane/metadata/android/ko/images/phoneScreenshots/3.png index b9593c029..7906b78cf 100644 Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/3.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/3.png differ diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/4.png b/fastlane/metadata/android/ko/images/phoneScreenshots/4.png index 3bfa8781a..5ef843272 100644 Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/4.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/4.png differ diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/5.png b/fastlane/metadata/android/ko/images/phoneScreenshots/5.png index ad6518250..b1c3362c9 100644 Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/5.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/6.png b/fastlane/metadata/android/ko/images/phoneScreenshots/6.png index 6aaa08e7e..a733c7331 100644 Binary files a/fastlane/metadata/android/ko/images/phoneScreenshots/6.png and b/fastlane/metadata/android/ko/images/phoneScreenshots/6.png differ diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/1.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/1.png index 8763df15d..5721e71de 100644 Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/1.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/1.png differ diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/2.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/2.png index 8c6c9597f..41555870c 100644 Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/2.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/2.png differ diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/3.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/3.png index 200da6361..b8bd3594f 100644 Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/3.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/3.png differ diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/4.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/4.png index d72ae6282..364cc6fbc 100644 Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/4.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/4.png differ diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/5.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/5.png index 30a9ebd14..c75e08755 100644 Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/5.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/6.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/6.png index 02b87189c..5244a840b 100644 Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/6.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/6.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/1.png b/fastlane/metadata/android/ru/images/phoneScreenshots/1.png index 80af968b3..f1315be49 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/1.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/1.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/2.png b/fastlane/metadata/android/ru/images/phoneScreenshots/2.png index 1d42a1cb0..7a310ca4f 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/2.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/2.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/3.png b/fastlane/metadata/android/ru/images/phoneScreenshots/3.png index 811a42242..30ad8972d 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/3.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/3.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/4.png b/fastlane/metadata/android/ru/images/phoneScreenshots/4.png index daa6be34e..e91ec53c5 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/4.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/4.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/5.png b/fastlane/metadata/android/ru/images/phoneScreenshots/5.png index b22e9d202..e7b6dc26b 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/5.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/ru/images/phoneScreenshots/6.png b/fastlane/metadata/android/ru/images/phoneScreenshots/6.png index 0a4a2630a..b69e24c5c 100644 Binary files a/fastlane/metadata/android/ru/images/phoneScreenshots/6.png and b/fastlane/metadata/android/ru/images/phoneScreenshots/6.png differ diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index a74c025cd..633b13c3b 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -48,6 +48,7 @@ "entryActionCopyToClipboard": "In die Zwischenablage kopieren", "entryActionDelete": "Löschen", + "entryActionConvert": "Konvertieren", "entryActionExport": "Exportieren", "entryActionRename": "Umbenennen", "entryActionRestore": "Wiederherstellen", @@ -67,6 +68,8 @@ "entryActionRemoveFavourite": "Aus Favoriten entfernen", "videoActionCaptureFrame": "Frame aufnehmen", + "videoActionMute": "Audio deaktivieren", + "videoActionUnmute": "Audio aktiveren", "videoActionPause": "Pause", "videoActionPlay": "Spielen", "videoActionReplay10": "10 Sekunden rückwärts springen", @@ -111,6 +114,11 @@ "videoLoopModeShortOnly": "Nur kurze Videos", "videoLoopModeAlways": "Immer", + "videoControlsPlay": "Abspielen/Pausieren", + "videoControlsPlaySeek": "Abspielen/Pausieren & Sprung-Schaltflächen", + "videoControlsPlayOutside": "Mit anderem Video-Player öffnen", + "videoControlsNone": "Keine Schaltflächen", + "mapStyleGoogleNormal": "Google Maps", "mapStyleGoogleHybrid": "Google Maps (Hybrid)", "mapStyleGoogleTerrain": "Google Maps (Gelände)", @@ -256,8 +264,7 @@ "aboutCredits": "Credits", "aboutCreditsWorldAtlas1": "Diese Anwendung verwendet eine TopoJSON-Datei von", "aboutCreditsWorldAtlas2": "unter ISC-Lizenz.", - "aboutCreditsTranslators": "Übersetzer:", - "aboutCreditsTranslatorLine": "{language}: {names}", + "aboutCreditsTranslators": "Übersetzer", "aboutLicenses": "Open-Source-Lizenzen", "aboutLicensesBanner": "Diese Anwendung verwendet die folgenden Open-Source-Pakete und -Bibliotheken.", @@ -430,6 +437,7 @@ "settingsViewerShowInformation": "Informationen anzeigen", "settingsViewerShowInformationSubtitle": "Titel, Datum, Ort, etc. anzeigen.", "settingsViewerShowShootingDetails": "Aufnahmedetails anzeigen", + "settingsViewerShowOverlayThumbnails": "Vorschaubilder anzeigen", "settingsViewerEnableOverlayBlurEffect": "Unschärfe-Effekt", "settingsVideoPageTitle": "Video-Einstellungen", @@ -439,8 +447,6 @@ "settingsVideoEnableAutoPlay": "Automatische Wiedergabe", "settingsVideoLoopModeTile": "Schleifen-Modus", "settingsVideoLoopModeTitle": "Schleifen-Modus", - "settingsVideoQuickActionsTile": "Schnelle Aktionen für Videos", - "settingsVideoQuickActionEditorTitle": "Schnelle Aktionen", "settingsSubtitleThemeTile": "Untertitel", "settingsSubtitleThemeTitle": "Untertitel", @@ -457,6 +463,13 @@ "settingsSubtitleThemeTextAlignmentCenter": "Zentrum", "settingsSubtitleThemeTextAlignmentRight": "Rechts", + "settingsVideoControlsTile": "Steuerung", + "settingsVideoControlsTitle": "Steuerung", + "settingsVideoButtonsTile": "Schaltflächen", + "settingsVideoButtonsTitle": "Schaltflächen", + "settingsVideoGestureDoubleTapTogglePlay": "Doppeltippen zum Abspielen/Pausieren", + "settingsVideoGestureSideDoubleTapSeek": "Doppeltippen auf die Bildschirmränder zum Rückwärts-/Vorwärtsspringen", + "settingsSectionPrivacy": "Datenschutz", "settingsAllowInstalledAppAccess": "Zugriff auf die Liste der installierten Apps", "settingsAllowInstalledAppAccessSubtitle": "zur Gruppierung von Bildern nach Apps", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 37598546e..bfcd0e5f5 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -92,10 +92,12 @@ "entryActionSetAs": "Set as", "entryActionOpenMap": "Show in map app", "entryActionRotateScreen": "Rotate screen", - "entryActionAddFavourite": "Add to favourites", - "entryActionRemoveFavourite": "Remove from favourites", + "entryActionAddFavourite": "Add to favorites", + "entryActionRemoveFavourite": "Remove from favorites", "videoActionCaptureFrame": "Capture frame", + "videoActionMute": "Mute", + "videoActionUnmute": "Unmute", "videoActionPause": "Pause", "videoActionPlay": "Play", "videoActionReplay10": "Seek backward 10 seconds", @@ -111,7 +113,7 @@ "entryInfoActionRemoveMetadata": "Remove metadata", "filterBinLabel": "Recycle bin", - "filterFavouriteLabel": "Favourite", + "filterFavouriteLabel": "Favorite", "filterLocationEmptyLabel": "Unlocated", "filterTagEmptyLabel": "Untagged", "filterRatingUnratedLabel": "Unrated", @@ -152,6 +154,11 @@ "videoLoopModeShortOnly": "Short videos only", "videoLoopModeAlways": "Always", + "videoControlsPlay": "Play", + "videoControlsPlaySeek": "Play & seek backward/forward", + "videoControlsPlayOutside": "Open with other player", + "videoControlsNone": "None", + "mapStyleGoogleNormal": "Google Maps", "mapStyleGoogleHybrid": "Google Maps (Hybrid)", "mapStyleGoogleTerrain": "Google Maps (Terrain)", @@ -387,20 +394,7 @@ "aboutCredits": "Credits", "aboutCreditsWorldAtlas1": "This app uses a TopoJSON file from", "aboutCreditsWorldAtlas2": "under ISC License.", - "aboutCreditsTranslators": "Translators:", - "aboutCreditsTranslatorLine": "{language}: {names}", - "@aboutCreditsTranslatorLine": { - "placeholders": { - "language": { - "type": "String", - "example": "Sumerian" - }, - "names": { - "type": "String", - "example": "Reggie Lampert" - } - } - }, + "aboutCreditsTranslators": "Translators", "aboutLicenses": "Open-Source Licenses", "aboutLicensesBanner": "This app uses the following open-source packages and libraries.", @@ -490,7 +484,7 @@ } }, - "collectionEmptyFavourites": "No favourites", + "collectionEmptyFavourites": "No favorites", "collectionEmptyVideos": "No videos", "collectionEmptyImages": "No images", @@ -498,7 +492,7 @@ "collectionDeselectSectionTooltip": "Deselect section", "drawerCollectionAll": "All collection", - "drawerCollectionFavourites": "Favourites", + "drawerCollectionFavourites": "Favorites", "drawerCollectionImages": "Images", "drawerCollectionVideos": "Videos", "drawerCollectionAnimated": "Animated", @@ -556,7 +550,7 @@ "settingsActionImport": "Import", "appExportCovers": "Covers", - "appExportFavourites": "Favourites", + "appExportFavourites": "Favorites", "appExportSettings": "Settings", "settingsSectionNavigation": "Navigation", @@ -579,7 +573,7 @@ "settingsNavigationDrawerAddAlbum": "Add album", "settingsSectionThumbnails": "Thumbnails", - "settingsThumbnailShowFavouriteIcon": "Show favourite icon", + "settingsThumbnailShowFavouriteIcon": "Show favorite icon", "settingsThumbnailShowLocationIcon": "Show location icon", "settingsThumbnailShowMotionPhotoIcon": "Show motion photo icon", "settingsThumbnailShowRating": "Show rating", @@ -613,6 +607,7 @@ "settingsViewerShowInformation": "Show information", "settingsViewerShowInformationSubtitle": "Show title, date, location, etc.", "settingsViewerShowShootingDetails": "Show shooting details", + "settingsViewerShowOverlayThumbnails": "Show thumbnails", "settingsViewerEnableOverlayBlurEffect": "Blur effect", "settingsVideoPageTitle": "Video Settings", @@ -622,8 +617,6 @@ "settingsVideoEnableAutoPlay": "Auto play", "settingsVideoLoopModeTile": "Loop mode", "settingsVideoLoopModeTitle": "Loop Mode", - "settingsVideoQuickActionsTile": "Quick actions for videos", - "settingsVideoQuickActionEditorTitle": "Quick Actions", "settingsSubtitleThemeTile": "Subtitles", "settingsSubtitleThemeTitle": "Subtitles", @@ -640,6 +633,13 @@ "settingsSubtitleThemeTextAlignmentCenter": "Center", "settingsSubtitleThemeTextAlignmentRight": "Right", + "settingsVideoControlsTile": "Controls", + "settingsVideoControlsTitle": "Controls", + "settingsVideoButtonsTile": "Buttons", + "settingsVideoButtonsTitle": "Buttons", + "settingsVideoGestureDoubleTapTogglePlay": "Double tap to play/pause", + "settingsVideoGestureSideDoubleTapSeek": "Double tap on screen edges to seek backward/forward", + "settingsSectionPrivacy": "Privacy", "settingsAllowInstalledAppAccess": "Allow access to app inventory", "settingsAllowInstalledAppAccessSubtitle": "Used to improve album display", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 39ac08f98..99e1ce283 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -112,6 +112,11 @@ "videoLoopModeShortOnly": "Sólo videos cortos", "videoLoopModeAlways": "Siempre", + "videoControlsPlay": "Reproducir", + "videoControlsPlaySeek": "Reproducir y buscar", + "videoControlsPlayOutside": "Reproducir externamente", + "videoControlsNone": "Ninguno", + "mapStyleGoogleNormal": "Mapas de Google", "mapStyleGoogleHybrid": "Mapas de Google (Híbrido)", "mapStyleGoogleTerrain": "Mapas de Google (Superficie)", @@ -259,8 +264,7 @@ "aboutCredits": "Créditos", "aboutCreditsWorldAtlas1": "Esta aplicación usa un archivo TopoJSON de", "aboutCreditsWorldAtlas2": "bajo licencia ISC.", - "aboutCreditsTranslators": "Traductores:", - "aboutCreditsTranslatorLine": "{language}: {names}", + "aboutCreditsTranslators": "Traductores", "aboutLicenses": "Licencias de código abierto", "aboutLicensesBanner": "Esta aplicación usa los siguientes paquetes y librerías de código abierto.", @@ -433,6 +437,7 @@ "settingsViewerShowInformation": "Mostrar información", "settingsViewerShowInformationSubtitle": "Mostrar título, fecha, ubicación, etc.", "settingsViewerShowShootingDetails": "Mostrar detalles de toma", + "settingsViewerShowOverlayThumbnails": "Mostrar miniaturas", "settingsViewerEnableOverlayBlurEffect": "Efecto de difuminado", "settingsVideoPageTitle": "Ajustes de video", @@ -442,8 +447,6 @@ "settingsVideoEnableAutoPlay": "Reproducción automática", "settingsVideoLoopModeTile": "Modo bucle", "settingsVideoLoopModeTitle": "Modo bucle", - "settingsVideoQuickActionsTile": "Acciones rápidas para videos", - "settingsVideoQuickActionEditorTitle": "Acciones rápidas", "settingsSubtitleThemeTile": "Subtítulos", "settingsSubtitleThemeTitle": "Subtítulos", @@ -460,6 +463,13 @@ "settingsSubtitleThemeTextAlignmentCenter": "Centro", "settingsSubtitleThemeTextAlignmentRight": "Derecha", + "settingsVideoControlsTile": "Controles", + "settingsVideoControlsTitle": "Controles", + "settingsVideoButtonsTile": "Botones", + "settingsVideoButtonsTitle": "Botones", + "settingsVideoGestureDoubleTapTogglePlay": "Doble toque para reproducir/pausar", + "settingsVideoGestureSideDoubleTapSeek": "Doble toque en los bordes de la pantalla para buscar atrás/adelante", + "settingsSectionPrivacy": "Privacidad", "settingsAllowInstalledAppAccess": "Permita el acceso a lista de aplicaciones", "settingsAllowInstalledAppAccessSubtitle": "Usado para mejorar los álbumes mostrados", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 65759fb4e..1c9be0f58 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -68,6 +68,8 @@ "entryActionRemoveFavourite": "Retirer des favoris", "videoActionCaptureFrame": "Capturer l’image", + "videoActionMute": "Couper le son", + "videoActionUnmute": "Rétablir le son", "videoActionPause": "Pause", "videoActionPlay": "Lire", "videoActionReplay10": "Reculer de 10 secondes", @@ -112,6 +114,11 @@ "videoLoopModeShortOnly": "Courtes vidéos seulement", "videoLoopModeAlways": "Toujours", + "videoControlsPlay": "Lecture", + "videoControlsPlaySeek": "Lecture & déplacement", + "videoControlsPlayOutside": "Ouvrir avec un autre lecteur", + "videoControlsNone": "Aucun", + "mapStyleGoogleNormal": "Google Maps", "mapStyleGoogleHybrid": "Google Maps (Satellite)", "mapStyleGoogleTerrain": "Google Maps (Relief)", @@ -213,7 +220,7 @@ "removeEntryMetadataDialogTitle": "Retrait de métadonnées", "removeEntryMetadataDialogMore": "Voir plus", - "removeEntryMetadataMotionPhotoXmpWarningDialogMessage": "Les métadonnées XMP sont nécessaires pour jouer la vidéo d’une photo animée.\n\nVoulez-vous vraiment les supprimer ?", + "removeEntryMetadataMotionPhotoXmpWarningDialogMessage": "Les métadonnées XMP sont nécessaires pour lire la vidéo d’une photo animée.\n\nVoulez-vous vraiment les supprimer ?", "videoSpeedDialogLabel": "Vitesse de lecture", @@ -257,8 +264,7 @@ "aboutCredits": "Remerciements", "aboutCreditsWorldAtlas1": "Cette app utilise un fichier TopoJSON de ", "aboutCreditsWorldAtlas2": "sous licence ISC.", - "aboutCreditsTranslators": "Traducteurs :", - "aboutCreditsTranslatorLine": "{language} : {names}", + "aboutCreditsTranslators": "Traducteurs", "aboutLicenses": "Licences open-source", "aboutLicensesBanner": "Cette app utilise ces librairies et packages open-source.", @@ -431,6 +437,7 @@ "settingsViewerShowInformation": "Afficher les détails", "settingsViewerShowInformationSubtitle": "Afficher les titre, date, lieu, etc.", "settingsViewerShowShootingDetails": "Afficher les détails de prise de vue", + "settingsViewerShowOverlayThumbnails": "Afficher les vignettes", "settingsViewerEnableOverlayBlurEffect": "Effets de flou", "settingsVideoPageTitle": "Réglages vidéo", @@ -440,8 +447,6 @@ "settingsVideoEnableAutoPlay": "Lecture automatique", "settingsVideoLoopModeTile": "Lecture répétée", "settingsVideoLoopModeTitle": "Lecture répétée", - "settingsVideoQuickActionsTile": "Actions rapides pour les vidéos", - "settingsVideoQuickActionEditorTitle": "Actions rapides", "settingsSubtitleThemeTile": "Sous-titres", "settingsSubtitleThemeTitle": "Sous-titres", @@ -458,6 +463,13 @@ "settingsSubtitleThemeTextAlignmentCenter": "centré", "settingsSubtitleThemeTextAlignmentRight": "droite", + "settingsVideoControlsTile": "Contrôles", + "settingsVideoControlsTitle": "Contrôles", + "settingsVideoButtonsTile": "Boutons", + "settingsVideoButtonsTitle": "Boutons", + "settingsVideoGestureDoubleTapTogglePlay": "Appuyer deux fois pour lire ou mettre en pause", + "settingsVideoGestureSideDoubleTapSeek": "Appuyer deux fois sur les bords de l’écran pour reculer ou avancer", + "settingsSectionPrivacy": "Confidentialité", "settingsAllowInstalledAppAccess": "Autoriser l’accès à l’inventaire des apps", "settingsAllowInstalledAppAccessSubtitle": "Pour un affichage amélioré des albums", diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index 468e6b2ba..93a384738 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -44,9 +44,9 @@ "chipActionUnpin": "Lepas sematan dari atas", "chipActionRename": "Ganti nama", "chipActionSetCover": "Setel sampul", - "chipActionCreateAlbum": "Membuat album", + "chipActionCreateAlbum": "Buat album", - "entryActionCopyToClipboard": "Salinan ke papan", + "entryActionCopyToClipboard": "Salin ke papan klip", "entryActionDelete": "Hapus", "entryActionConvert": "Ubah", "entryActionExport": "Ekspor", @@ -70,7 +70,7 @@ "videoActionCaptureFrame": "Tangkap bingkai", "videoActionPause": "Henti", "videoActionPlay": "Mainkan", - "videoActionReplay10": "Mundur 10 detik", + "videoActionReplay10": "Mundurkan 10 detik", "videoActionSkip10": "Majukan 10 detik", "videoActionSelectStreams": "Pilih trek", "videoActionSetSpeed": "Kecepatan pemutaran", @@ -84,7 +84,7 @@ "filterBinLabel": "Tong sampah", "filterFavouriteLabel": "Favorit", - "filterLocationEmptyLabel": "Lokasi Tidak ditemukan", + "filterLocationEmptyLabel": "Lokasi yang tidak ditemukan", "filterTagEmptyLabel": "Tidak ditag", "filterRatingUnratedLabel": "Belum diberi peringkat", "filterRatingRejectedLabel": "Ditolak", @@ -161,7 +161,7 @@ "noMatchingAppDialogTitle": "Tidak Ada Aplikasi Yang Cocok", "noMatchingAppDialogMessage": "Tidak ada aplikasi yang cocok untuk menangani ini.", - "binEntriesConfirmationDialogMessage": "{count, plural, =1{Pindah benda ini ke tong sampah?} other{Pindah {count} benda ke tempat sampah?}}", + "binEntriesConfirmationDialogMessage": "{count, plural, =1{Pindahkan benda ini ke tong sampah?} other{Pindahkan {count} benda ke tempat sampah?}}", "deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Anda yakin ingin menghapus benda ini?} other{Apakah Anda yakin ingin menghapus {count} benda?}}", "videoResumeDialogMessage": "Apakah Anda ingin melanjutkan di {time}?", @@ -224,10 +224,10 @@ "videoStreamSelectionDialogTrack": "Trek", "videoStreamSelectionDialogNoSelection": "Tidak ada Trek yang lain.", - "genericSuccessFeedback": "Sukses!", + "genericSuccessFeedback": "Selesai!", "genericFailureFeedback": "Gagal", - "menuActionConfigureView": "Lihat", + "menuActionConfigureView": "Tata letak", "menuActionSelect": "Pilih", "menuActionSelectAll": "Pilih semua", "menuActionSelectNone": "Pilih tidak ada", @@ -257,11 +257,10 @@ "aboutCredits": "Kredit", "aboutCreditsWorldAtlas1": "Aplikasi ini menggunakan file TopoJSON dari", "aboutCreditsWorldAtlas2": "dibawah Lisensi ISC.", - "aboutCreditsTranslators": "Penerjemah:", - "aboutCreditsTranslatorLine": "{language}: {names}", + "aboutCreditsTranslators": "Penerjemah", "aboutLicenses": "Lisensi Sumber Terbuka", - "aboutLicensesBanner": "Aplikasi ini menggunakan paket dan pustaka sumber terbuka berikut.", + "aboutLicensesBanner": "Aplikasi ini menggunakan paket dan perpustakaan sumber terbuka berikut.", "aboutLicensesAndroidLibraries": "Perpustakaan Android", "aboutLicensesFlutterPlugins": "Plugin Flutter", "aboutLicensesFlutterPackages": "Paket Flutter", @@ -305,7 +304,7 @@ "collectionEditFailureFeedback": "{count, plural, other{Gagal untuk mengubah {count} benda}}", "collectionExportFailureFeedback": "{count, plural, other{Gagal untuk mengekspor {count} halaman}}", "collectionCopySuccessFeedback": "{count, plural, other{Menyalin {count} benda}}", - "collectionMoveSuccessFeedback": "{count, plural, other{Menggerakkan {count} benda}}", + "collectionMoveSuccessFeedback": "{count, plural, other{{count} benda terpindah}}", "collectionEditSuccessFeedback": "{count, plural, other{Mengubah {count} benda}}", "collectionEmptyFavourites": "Tidak ada favorit", @@ -386,7 +385,7 @@ "settingsConfirmationDialogTile": "Dialog konfirmasi", "settingsConfirmationDialogTitle": "Dialog Konfirmasi", "settingsConfirmationDialogDeleteItems": "Tanya sebelum menghapus benda selamanya", - "settingsConfirmationDialogMoveToBinItems": "Tanya sebelum memindahkan benda ke tempat sampah", + "settingsConfirmationDialogMoveToBinItems": "Tanya sebelum memindahkan benda ke tong sampah", "settingsNavigationDrawerTile": "Menu navigasi", "settingsNavigationDrawerEditorTitle": "Menu Navigasi", "settingsNavigationDrawerBanner": "Sentuh dan tahan untuk memindahkan dan menyusun ulang benda menu.", @@ -439,8 +438,6 @@ "settingsVideoEnableAutoPlay": "Putar otomatis", "settingsVideoLoopModeTile": "Putar ulang", "settingsVideoLoopModeTitle": "Putar Ulang", - "settingsVideoQuickActionsTile": "Aksi cepat untuk video", - "settingsVideoQuickActionEditorTitle": "Aksi Cepat", "settingsSubtitleThemeTile": "Subtitle", "settingsSubtitleThemeTitle": "Subtitle", @@ -460,7 +457,7 @@ "settingsSectionPrivacy": "Privasi", "settingsAllowInstalledAppAccess": "Izinkan akses ke inventori aplikasi", "settingsAllowInstalledAppAccessSubtitle": "Digunakan untuk meningkatkan tampilan album", - "settingsAllowErrorReporting": "Izinkan pelaporan kesalahan anonim", + "settingsAllowErrorReporting": "Izinkan pelaporan kesalahan secara anonim", "settingsSaveSearchHistory": "Simpan riwayat pencarian", "settingsEnableBin": "Gunakan tong sampah", "settingsEnableBinSubtitle": "Simpan benda yang dihapus selama 30 hari", @@ -478,14 +475,14 @@ "settingsStorageAccessTile": "Akses penyimpanan", "settingsStorageAccessTitle": "Akses Penyimpanan", - "settingsStorageAccessBanner": "Beberapa direktori memerlukan pemberian akses eksplisit untuk memodifikasi file di dalamnya. Anda dapat meninjau di sini direktori yang sebelumnya Anda beri akses.", + "settingsStorageAccessBanner": "Beberapa direktori memerlukan pemberian akses eksplisit untuk memodifikasi file di dalamnya. Anda dapat meninjau direktori yang anda beri akses sebelumnya di sini.", "settingsStorageAccessEmpty": "Tidak ada akses", "settingsStorageAccessRevokeTooltip": "Tarik kembali", "settingsSectionAccessibility": "Aksesibilitas", "settingsRemoveAnimationsTile": "Hapus animasi", "settingsRemoveAnimationsTitle": "Hapus Animasi", - "settingsTimeToTakeActionTile": "Saatnya untuk mengambil tindakan", + "settingsTimeToTakeActionTile": "Waktu untuk mengambil tindakan", "settingsTimeToTakeActionTitle": "Saatnya Bertindak", "settingsSectionLanguage": "Bahasa & Format", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb new file mode 100644 index 000000000..acc36340d --- /dev/null +++ b/lib/l10n/app_ja.arb @@ -0,0 +1,575 @@ +{ + "appName": "アヴェス", + "welcomeMessage": "アヴェスへようこそ", + "welcomeOptional": "オプション", + "welcomeTermsToggle": "利用規約に同意する", + "itemCount": "{count, plural, other{{count} 件のアイテム}}", + + "timeSeconds": "{seconds, plural, other{{seconds} 秒}}", + "timeMinutes": "{minutes, plural, other{{minutes} 分}}", + "timeDays": "{days, plural, other{{days} 日}}", + "focalLength": "{length} mm", + + "applyButtonLabel": "適用", + "deleteButtonLabel": "削除", + "nextButtonLabel": "次へ", + "showButtonLabel": "表示", + "hideButtonLabel": "非表示", + "continueButtonLabel": "続ける", + + "cancelTooltip": "キャンセル", + "changeTooltip": "変更", + "clearTooltip": "クリア", + "previousTooltip": "前へ", + "nextTooltip": "次へ", + "showTooltip": "表示する", + "hideTooltip": "非表示にする", + "actionRemove": "削除", + "resetButtonTooltip": "リセット", + + "doubleBackExitMessage": "終了するには「戻る」をもう一度タップしてください。", + "doNotAskAgain": "今後このメッセージを表示しない", + + "sourceStateLoading": "読み込み中", + "sourceStateCataloguing": "分類中", + "sourceStateLocatingCountries": "国、地域を確認中", + "sourceStateLocatingPlaces": "場所を確認中", + + "chipActionDelete": "削除", + "chipActionGoToAlbumPage": "アルバム別に表示", + "chipActionGoToCountryPage": "国地域別に表示", + "chipActionGoToTagPage": "タグ別に表示", + "chipActionHide": "非表示", + "chipActionPin": "一番上に固定", + "chipActionUnpin": "一番上への固定を解除", + "chipActionRename": "名前を変更", + "chipActionSetCover": "カバーを設定", + "chipActionCreateAlbum": "アルバムを作成", + + "entryActionCopyToClipboard": "クリップボードにコピー", + "entryActionDelete": "削除", + "entryActionConvert": "変換", + "entryActionExport": "エクスポート", + "entryActionRename": "名前を変更", + "entryActionRestore": "元に戻す", + "entryActionRotateCCW": "反時計回りに回転", + "entryActionRotateCW": "時計回りに回転", + "entryActionFlip": "水平方向に反転", + "entryActionPrint": "印刷", + "entryActionShare": "共有", + "entryActionViewSource": "ソースを表示", + "entryActionViewMotionPhotoVideo": "モーションフォトを開く", + "entryActionEdit": "編集", + "entryActionOpen": "アプリで開く", + "entryActionSetAs": "登録", + "entryActionOpenMap": "地図アプリで表示", + "entryActionRotateScreen": "画面を回転", + "entryActionAddFavourite": "お気に入りに追加", + "entryActionRemoveFavourite": "お気に入りから削除", + + "videoActionCaptureFrame": "フレームをキャプチャ", + "videoActionMute": "ミュート", + "videoActionUnmute": "ミュート解除", + "videoActionPause": "一時停止", + "videoActionPlay": "再生", + "videoActionReplay10": "10 秒前に戻る", + "videoActionSkip10": "10 秒前に進む", + "videoActionSelectStreams": "トラックを選択", + "videoActionSetSpeed": "再生速度", + "videoActionSettings": "設定", + + "entryInfoActionEditDate": "日時を編集", + "entryInfoActionEditLocation": "位置情報を編集", + "entryInfoActionEditRating": "評価を編集", + "entryInfoActionEditTags": "タグを編集", + "entryInfoActionRemoveMetadata": "メタデータを削除", + + "filterBinLabel": "ごみ箱", + "filterFavouriteLabel": "お気に入り", + "filterLocationEmptyLabel": "位置情報なし", + "filterTagEmptyLabel": "タグ情報なし", + "filterRatingUnratedLabel": "評価情報なし", + "filterRatingRejectedLabel": "拒否されました", + "filterTypeAnimatedLabel": "アニメーション", + "filterTypeMotionPhotoLabel": "モーションフォト", + "filterTypePanoramaLabel": "パノラマ", + "filterTypeRawLabel": "Raw", + "filterTypeSphericalVideoLabel": "360° 動画", + "filterTypeGeotiffLabel": "GeoTIFF", + "filterMimeImageLabel": "画像", + "filterMimeVideoLabel": "動画", + + "coordinateFormatDms": "度分秒", + "coordinateFormatDecimal": "十進角", + "coordinateDms": "{direction}{coordinate}", + "coordinateDmsNorth": "北緯", + "coordinateDmsSouth": "南緯", + "coordinateDmsEast": "東経", + "coordinateDmsWest": "西経", + + "unitSystemMetric": "メートル法", + "unitSystemImperial": "ヤード・ポンド法", + + "videoLoopModeNever": "ループ再生しない", + "videoLoopModeShortOnly": "短い動画のみループ再生", + "videoLoopModeAlways": "常にループ再生", + + "videoControlsPlay": "再生", + "videoControlsPlaySeek": "再生&早送り/早戻し", + "videoControlsPlayOutside": "他のプレイヤーで開く", + "videoControlsNone": "なし", + + "mapStyleGoogleNormal": "Google マップ", + "mapStyleGoogleHybrid": "Google マップ(ハイブリッド)", + "mapStyleGoogleTerrain": "Google マップ(地形)", + "mapStyleOsmHot": "人道支援 OSM", + "mapStyleStamenToner": "Stamen Toner", + "mapStyleStamenWatercolor": "Stamen Watercolor", + + "nameConflictStrategyRename": "名前を変更", + "nameConflictStrategyReplace": "置換", + "nameConflictStrategySkip": "スキップ", + + "keepScreenOnNever": "自動オフ", + "keepScreenOnViewerOnly": "ビューアー使用時のみオン", + "keepScreenOnAlways": "常にオン", + + "accessibilityAnimationsRemove": "画面エフェクトを利用しない", + "accessibilityAnimationsKeep": "画面エフェクトを利用", + + "albumTierNew": "新規", + "albumTierPinned": "固定", + "albumTierSpecial": "全体", + "albumTierApps": "アプリ", + "albumTierRegular": "その他", + + "storageVolumeDescriptionFallbackPrimary": "内蔵ストレージ", + "storageVolumeDescriptionFallbackNonPrimary": "SD カード", + "rootDirectoryDescription": "ルート ディレクトリ", + "otherDirectoryDescription": "“{name}” ディレクトリ", + "storageAccessDialogTitle": "ストレージ アクセス", + "storageAccessDialogMessage": "このアプリにアクセスを許可するために次の画面で {directory} の “{volume}” を選択してください。", + "restrictedAccessDialogTitle": "アクセス制限", + "restrictedAccessDialogMessage": "このアプリは {directory} の “{volume}” にあるファイルを変更できません。\n\nプリインストールされているファイル マネージャまたはギャラリー アプリを使用して他のディレクトリに移動してください。", + "notEnoughSpaceDialogTitle": "容量不足", + "notEnoughSpaceDialogMessage": "この操作には “{volume}” に {neededSize} の容量が必要ですが、 残り {freeSize} のみ利用可能です。", + "missingSystemFilePickerDialogTitle": "システム ファイル ピッカーが見つかりません", + "missingSystemFilePickerDialogMessage": "システム ファイル ピッカーが見つからないか、利用できません。利用可能にしてから再度お試しください。", + + "unsupportedTypeDialogTitle": "対応していないタイプ", + "unsupportedTypeDialogMessage": "{count, plural, other{この操作は次のタイプのアイテムには対応していません: {types}.}}", + + "nameConflictDialogSingleSourceMessage": "目的フォルダに同じ名前のファイルが存在しています。", + "nameConflictDialogMultipleSourceMessage": "同じ名前のファイルが存在しています。", + + "addShortcutDialogLabel": "ショートカット ラベル", + "addShortcutButtonLabel": "追加", + + "noMatchingAppDialogTitle": "一致するアプリなし", + "noMatchingAppDialogMessage": "処理できるアプリが見つかりません。", + + "binEntriesConfirmationDialogMessage": "{count, plural, =1{このアイテムをごみ箱に移動しますか?} other{{count} 件のアイテムをごみ箱に移動しますか?}}", + "deleteEntriesConfirmationDialogMessage": "{count, plural, =1{このアイテムを削除しますか?} other{{count} 件のアイテムを削除しますか?}}", + + "videoResumeDialogMessage": " {time} の時点から再生を再開しますか?", + "videoStartOverButtonLabel": "最初から再生", + "videoResumeButtonLabel": "再開", + + "setCoverDialogTitle": "カバーを設定", + "setCoverDialogLatest": "最新のアイテム", + "setCoverDialogCustom": "カスタム", + + "hideFilterConfirmationDialogMessage": "一致する写真と動画はコレクションに表示されなくなります。「プライバシー」設定からいつでもアイテムを表示できます。\n\nこれらのアイテムを非表示にしますか?", + + "newAlbumDialogTitle": "新規アルバム", + "newAlbumDialogNameLabel": "アルバム名", + "newAlbumDialogNameLabelAlreadyExistsHelper": "ディレクトリが既に存在します", + "newAlbumDialogStorageLabel": "ストレージ:", + + "renameAlbumDialogLabel": "新しい名前", + "renameAlbumDialogLabelAlreadyExistsHelper": "ディレクトリが既に存在します", + + "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{このアルバムとアルバム内のアイテムを削除しますか?} other{このアルバムとアルバム内の {count} 件のアイテムを削除しますか?}}", + "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{これらのアルバムとアルバム内のアイテムを削除しますか?} other{これらのアルバムとアルバム内の {count} 件のアイテムを削除しますか?}}", + + "exportEntryDialogFormat": "形式:", + "exportEntryDialogWidth": "幅", + "exportEntryDialogHeight": "高さ", + + "renameEntryDialogLabel": "新しい名前", + + "editEntryDateDialogTitle": "日時", + "editEntryDateDialogSetCustom": "日を設定する", + "editEntryDateDialogCopyField": "他の日からコピーする", + "editEntryDateDialogExtractFromTitle": "タイトルから抽出する", + "editEntryDateDialogShift": "シフト", + "editEntryDateDialogSourceFileModifiedDate": "ファイル更新日", + "editEntryDateDialogTargetFieldsHeader": "更新するフィールド", + "editEntryDateDialogHours": "時", + "editEntryDateDialogMinutes": "分", + + "editEntryLocationDialogTitle": "位置情報", + "editEntryLocationDialogChooseOnMapTooltip": "地図上で選択", + "editEntryLocationDialogLatitude": "緯度", + "editEntryLocationDialogLongitude": "経度", + + "locationPickerUseThisLocationButton": "この位置情報を使用", + + "editEntryRatingDialogTitle": "評価", + + "removeEntryMetadataDialogTitle": "メタデータの削除", + "removeEntryMetadataDialogMore": "もっと見る", + + "removeEntryMetadataMotionPhotoXmpWarningDialogMessage": "モーションフォト内の動画を再生するには XMP が必要です。\n\n削除しますか?", + + "videoSpeedDialogLabel": "再生速度", + + "videoStreamSelectionDialogVideo": "動画", + "videoStreamSelectionDialogAudio": "音声", + "videoStreamSelectionDialogText": "字幕", + "videoStreamSelectionDialogOff": "オフ", + "videoStreamSelectionDialogTrack": "トラック", + "videoStreamSelectionDialogNoSelection": "他にトラックはありません。", + + "genericSuccessFeedback": "完了しました!", + "genericFailureFeedback": "エラー", + + "menuActionConfigureView": "表示", + "menuActionSelect": "選択", + "menuActionSelectAll": "すべて選択", + "menuActionSelectNone": "選択を解除", + "menuActionMap": "地図", + "menuActionStats": "統計", + + "viewDialogTabSort": "並べ替え", + "viewDialogTabGroup": "グループ", + "viewDialogTabLayout": "レイアウト", + + "tileLayoutGrid": "グリッド表示", + "tileLayoutList": "リスト表示", + + "aboutPageTitle": "アプリについて", + "aboutLinkSources": "ソース", + "aboutLinkLicense": "ライセンス", + "aboutLinkPolicy": "プライバシー ポリシー", + + "aboutBug": "バグの報告", + "aboutBugSaveLogInstruction": "アプリのログをファイルに保存", + "aboutBugSaveLogButton": "保存", + "aboutBugCopyInfoInstruction": "システム情報をコピー", + "aboutBugCopyInfoButton": "コピー", + "aboutBugReportInstruction": "ログとシステム情報とともに GitHub で報告", + "aboutBugReportButton": "報告", + + "aboutCredits": "クレジット", + "aboutCreditsWorldAtlas1": "このアプリは TopoJSON ファイルを", + "aboutCreditsWorldAtlas2": "ISC License の下使用しています。", + "aboutCreditsTranslators": "翻訳", + + "aboutLicenses": "オープンソース ライセンス", + "aboutLicensesBanner": "このアプリは下記のオープンソース パッケージおよびライブラリを使用しています。", + "aboutLicensesAndroidLibraries": "Android ライブラリ", + "aboutLicensesFlutterPlugins": "Flutter プラグイン", + "aboutLicensesFlutterPackages": "Flutter パッケージ", + "aboutLicensesDartPackages": "Dart パッケージ", + "aboutLicensesShowAllButtonLabel": "すべてのライセンスを表示", + + "policyPageTitle": "プライバシー ポリシー", + + "collectionPageTitle": "コレクション", + "collectionPickPageTitle": "ピック", + "collectionSelectPageTitle": "アイテムを選択", + + "collectionActionShowTitleSearch": "タイトル名フィルターを表示", + "collectionActionHideTitleSearch": "タイトル名フィルターを非表示", + "collectionActionAddShortcut": "ショートカットを追加", + "collectionActionEmptyBin": "ごみ箱を空にする", + "collectionActionCopy": "アルバムにコピー", + "collectionActionMove": "アルバムに移動", + "collectionActionRescan": "再スキャン", + "collectionActionEdit": "編集", + + "collectionSearchTitlesHintText": "タイトルを検索", + + "collectionSortDate": "日付", + "collectionSortSize": "サイズ", + "collectionSortName": "アルバムとファイル名", + "collectionSortRating": "評価", + + "collectionGroupAlbum": "アルバム別", + "collectionGroupMonth": "月別", + "collectionGroupDay": "日別", + "collectionGroupNone": "グループ化しない", + + "sectionUnknown": "不明", + "dateToday": "今日", + "dateYesterday": "昨日", + "dateThisMonth": "今月", + "collectionDeleteFailureFeedback": "{count, plural, other{{count} 件のアイテムを削除できませんでした}}", + "collectionCopyFailureFeedback": "{count, plural, other{{count} 件のアイテムをコピーできませんでした}}", + "collectionMoveFailureFeedback": "{count, plural, other{{count} 件のアイテムを移動できませんでした}}", + "collectionEditFailureFeedback": "{count, plural, other{{count} 件のアイテムを編集できませんでした}}", + "collectionExportFailureFeedback": "{count, plural, other{{count} ページをエクスポートできませんでした}}", + "collectionCopySuccessFeedback": "{count, plural, other{{count} 件のアイテムをコピーしました}}", + "collectionMoveSuccessFeedback": "{count, plural, other{{count} 件のアイテムを移動しました}}", + "collectionEditSuccessFeedback": "{count, plural, other{{count} 件のアイテムを編集しました}}", + + "collectionEmptyFavourites": "お気に入りはありません", + "collectionEmptyVideos": "動画はありません", + "collectionEmptyImages": "画像はありません", + + "collectionSelectSectionTooltip": "セクションを選択", + "collectionDeselectSectionTooltip": "セクションの選択を解除", + + "drawerCollectionAll": "すべてのコレクション", + "drawerCollectionFavourites": "お気に入り", + "drawerCollectionImages": "画像", + "drawerCollectionVideos": "動画", + "drawerCollectionAnimated": "アニメーション", + "drawerCollectionMotionPhotos": "モーションフォト", + "drawerCollectionPanoramas": "パノラマ", + "drawerCollectionRaws": "Raw 写真", + "drawerCollectionSphericalVideos": "360° 動画", + + "chipSortDate": "日付", + "chipSortName": "名前", + "chipSortCount": "アイテム件数", + + "albumGroupTier": "階層別", + "albumGroupVolume": "ストレージ ボリューム別", + "albumGroupNone": "グループ化しない", + + "albumPickPageTitleCopy": "アルバムにコピー", + "albumPickPageTitleExport": "アルバムにエクスポート", + "albumPickPageTitleMove": "アルバムに移動", + "albumPickPageTitlePick": "アルバムを選択", + + "albumCamera": "カメラ", + "albumDownload": "ダウンロード", + "albumScreenshots": "スクリーンショット", + "albumScreenRecordings": "画面録画", + "albumVideoCaptures": "動画キャプチャ", + + "albumPageTitle": "アルバム", + "albumEmpty": "アルバムはありません", + "createAlbumTooltip": "アルバムを作成", + "createAlbumButtonLabel": "作成", + "newFilterBanner": "新規", + + "countryPageTitle": "国", + "countryEmpty": "国はありません", + + "tagPageTitle": "タグ", + "tagEmpty": "タグはありません", + + "binPageTitle": "ごみ箱", + + "searchCollectionFieldHint": "コレクションを検索", + "searchSectionRecent": "最近", + "searchSectionAlbums": "アルバム", + "searchSectionCountries": "国", + "searchSectionPlaces": "場所", + "searchSectionTags": "タグ", + "searchSectionRating": "評価", + + "settingsPageTitle": "設定", + "settingsSystemDefault": "システム", + "settingsDefault": "デフォルト", + + "settingsActionExport": "エクスポート", + "settingsActionImport": "インポート", + + "appExportCovers": "カバー", + "appExportFavourites": "お気に入り", + "appExportSettings": "設定", + + "settingsSectionNavigation": "ナビゲーション", + "settingsHome": "ホーム", + "settingsKeepScreenOnTile": "画面表示をオンのままにする", + "settingsKeepScreenOnTitle": "画面表示をオンのままにする", + "settingsDoubleBackExit": "「戻る」を2回タップして終了", + + "settingsConfirmationDialogTile": "確認メッセージ", + "settingsConfirmationDialogTitle": "確認メッセージ", + "settingsConfirmationDialogDeleteItems": "アイテムを完全に削除する前に確認", + "settingsConfirmationDialogMoveToBinItems": "アイテムをごみ箱に移動する前に確認", + + "settingsNavigationDrawerTile": "ナビゲーション メニュー", + "settingsNavigationDrawerEditorTitle": "ナビゲーション メニュー", + "settingsNavigationDrawerBanner": "メニュー項目を長押しして、移動および並べ替え", + "settingsNavigationDrawerTabTypes": "タイプ", + "settingsNavigationDrawerTabAlbums": "アルバム", + "settingsNavigationDrawerTabPages": "ページ", + "settingsNavigationDrawerAddAlbum": "アルバムを追加", + + "settingsSectionThumbnails": "サムネイル", + "settingsThumbnailShowFavouriteIcon": "お気に入りアイコンを表示", + "settingsThumbnailShowLocationIcon": "位置情報アイコンを表示", + "settingsThumbnailShowMotionPhotoIcon": "モーションフォトアイコンを表示", + "settingsThumbnailShowRating": "評価情報を表示", + "settingsThumbnailShowRawIcon": "Raw アイコンを表示", + "settingsThumbnailShowVideoDuration": "動画の再生時間を表示", + + "settingsCollectionQuickActionsTile": "クイック アクション", + "settingsCollectionQuickActionEditorTitle": "クイック アクション", + "settingsCollectionQuickActionTabBrowsing": "ブラウズ中", + "settingsCollectionQuickActionTabSelecting": "選択中", + "settingsCollectionBrowsingQuickActionEditorBanner": "長押ししてボタンを移動しアイテムを閲覧中に表示されるアクションを選択します。", + "settingsCollectionSelectionQuickActionEditorBanner": "長押ししてボタンを移動しアイテムを選択中に表示されるアクションを選択します。", + + "settingsSectionViewer": "ビューアー", + "settingsViewerUseCutout": "切り取り領域を使用", + "settingsViewerMaximumBrightness": "明るさ最大", + "settingsMotionPhotoAutoPlay": "モーションフォト自動再生", + "settingsImageBackground": "画像背景", + + "settingsViewerQuickActionsTile": "クイックアクション", + "settingsViewerQuickActionEditorTitle": "クイックアクション", + "settingsViewerQuickActionEditorBanner": "長押ししてボタンを移動しビューアーで表示されるアクションを選択します。", + "settingsViewerQuickActionEditorDisplayedButtons": "表示ボタン", + "settingsViewerQuickActionEditorAvailableButtons": "利用可能なボタン", + "settingsViewerQuickActionEmpty": "ボタンなし", + + "settingsViewerOverlayTile": "オーバーレイ", + "settingsViewerOverlayTitle": "オーバーレイ", + "settingsViewerShowOverlayOnOpening": "最初に表示", + "settingsViewerShowMinimap": "小さな地図を表示", + "settingsViewerShowInformation": "情報を表示", + "settingsViewerShowInformationSubtitle": "タイトル、日付、位置情報、その他を表示", + "settingsViewerShowShootingDetails": "撮影詳細を表示", + "settingsViewerShowOverlayThumbnails": "サムネイルを表示", + "settingsViewerEnableOverlayBlurEffect": "ぼかし効果", + + "settingsVideoPageTitle": "動画設定", + "settingsSectionVideo": "動画", + "settingsVideoShowVideos": "動画を表示", + "settingsVideoEnableHardwareAcceleration": "ハードウェア アクセラレーション", + "settingsVideoEnableAutoPlay": "自動再生", + "settingsVideoLoopModeTile": "ループ モード", + "settingsVideoLoopModeTitle": "ループ モード", + + "settingsVideoQuickActionsTile": "動画のクイック アクション", + "settingsVideoQuickActionEditorTitle": "クイック アクション", + "settingsSubtitleThemeTile": "字幕", + "settingsSubtitleThemeTitle": "字幕", + "settingsSubtitleThemeSample": "これはサンプルです。", + "settingsSubtitleThemeTextAlignmentTile": "テキストの配置", + "settingsSubtitleThemeTextAlignmentTitle": "テキストの配置", + "settingsSubtitleThemeTextSize": "テキストのサイズ", + "settingsSubtitleThemeShowOutline": "アウトラインと影を表示", + "settingsSubtitleThemeTextColor": "テキストの色", + "settingsSubtitleThemeTextOpacity": "テキストの不透明度", + "settingsSubtitleThemeBackgroundColor": "背景色", + "settingsSubtitleThemeBackgroundOpacity": "背景の不透明度", + "settingsSubtitleThemeTextAlignmentLeft": "左揃え", + "settingsSubtitleThemeTextAlignmentCenter": "中央揃え", + "settingsSubtitleThemeTextAlignmentRight": "右揃え", + + "settingsVideoControlsTile": "操作", + "settingsVideoControlsTitle": "操作", + "settingsVideoButtonsTile": "ボタン", + "settingsVideoButtonsTitle": "ボタン", + "settingsVideoGestureDoubleTapTogglePlay": "2回タップして再生/一時停止", + "settingsVideoGestureSideDoubleTapSeek": "画面の角を2回タップして早送り/早戻し", + + "settingsSectionPrivacy": "プライバシー", + "settingsAllowInstalledAppAccess": "アプリのインベントリへのアクセスを許可する", + "settingsAllowInstalledAppAccessSubtitle": "アルバム表示の改善に使用されます", + "settingsAllowErrorReporting": "匿名でのエラー報告を許可する", + "settingsSaveSearchHistory": "検索履歴を保存", + "settingsEnableBin": "ごみ箱を使用", + "settingsEnableBinSubtitle": "削除したアイテムを30日間保持します", + + "settingsHiddenItemsTile": "非表示アイテム", + "settingsHiddenItemsTitle": "非表示アイテム", + + "settingsHiddenFiltersTitle": "非表示フィルター", + "settingsHiddenFiltersBanner": "非表示のフィルターに一致する写真と動画は、コレクションに表示されません。", + "settingsHiddenFiltersEmpty": "非表示フィルターがありません", + + "settingsHiddenPathsTitle": "非表示パス", + "settingsHiddenPathsBanner": "これらのフォルダまたはそのサブフォルダにある写真と動画は、コレクションに表示されません。", + "addPathTooltip": "パスを追加", + + "settingsStorageAccessTile": "ストレージへのアクセス", + "settingsStorageAccessTitle": "ストレージへのアクセス", + "settingsStorageAccessBanner": "ディレクトリによっては保存されているファイルの編集のためにアクセスを許可する必要があります。これまでにアクセスを許可したディレクトりはこちらで確認できます。", + "settingsStorageAccessEmpty": "許可したアクセスはありません", + "settingsStorageAccessRevokeTooltip": "許可を取り消し", + + "settingsSectionAccessibility": "アクセシビリティ", + "settingsRemoveAnimationsTile": "アニメーションの削除", + "settingsRemoveAnimationsTitle": "アニメーションの削除", + "settingsTimeToTakeActionTile": "操作までの時間", + "settingsTimeToTakeActionTitle": "操作までの時間", + + "settingsSectionLanguage": "言語と形式", + "settingsLanguage": "言語", + "settingsCoordinateFormatTile": "座標形式", + "settingsCoordinateFormatTitle": "座標形式", + "settingsUnitSystemTile": "単位", + "settingsUnitSystemTitle": "単位", + + "statsPageTitle": "統計", + "statsWithGps": "{count, plural, other{位置情報のあるアイテム {count} 件}}", + "statsTopCountries": "上位の国", + "statsTopPlaces": "上位の場所", + "statsTopTags": "上位のタグ", + + "viewerOpenPanoramaButtonLabel": "パノラマを開く", + "viewerErrorUnknown": "エラー", + "viewerErrorDoesNotExist": "ファイルが存在しません。", + + "viewerInfoPageTitle": "情報", + "viewerInfoBackToViewerTooltip": "ビューアーに戻る", + + "viewerInfoUnknown": "不明", + "viewerInfoLabelTitle": "タイトル", + "viewerInfoLabelDate": "日付", + "viewerInfoLabelResolution": "解像度", + "viewerInfoLabelSize": "サイズ", + "viewerInfoLabelUri": "URI", + "viewerInfoLabelPath": "パス", + "viewerInfoLabelDuration": "再生時間", + "viewerInfoLabelOwner": "所有者", + "viewerInfoLabelCoordinates": "座標", + "viewerInfoLabelAddress": "アドレス", + + "mapStyleTitle": "地図のスタイル", + "mapStyleTooltip": "地図のスタイルを選択", + "mapZoomInTooltip": "ズームイン", + "mapZoomOutTooltip": "ズームアウト", + "mapPointNorthUpTooltip": "北が上になるように表示", + "mapAttributionOsmHot": "地図データ © [OpenStreetMap](https://www.openstreetmap.org/copyright) contributors • Tiles by [HOT](https://www.hotosm.org/) • Hosted by [OSM France](https://openstreetmap.fr/)", + "mapAttributionStamen": "地図データ © [OpenStreetMap](https://www.openstreetmap.org/copyright) contributors • Tiles by [Stamen Design](http://stamen.com), [CC BY 3.0](http://creativecommons.org/licenses/by/3.0)", + "openMapPageTooltip": "地図ページで表示", + "mapEmptyRegion": "この地域の画像はありません", + + "viewerInfoOpenEmbeddedFailureFeedback": "埋め込みデータを抽出できませんでした", + "viewerInfoOpenLinkText": "開く", + "viewerInfoViewXmlLinkText": "XML を表示", + + "viewerInfoSearchFieldLabel": "メタデータを検索", + "viewerInfoSearchEmpty": "一致するキーはありません", + "viewerInfoSearchSuggestionDate": "日時", + "viewerInfoSearchSuggestionDescription": "説明", + "viewerInfoSearchSuggestionDimensions": "寸法", + "viewerInfoSearchSuggestionResolution": "解像度", + "viewerInfoSearchSuggestionRights": "権限", + + "tagEditorPageTitle": "タグの編集", + "tagEditorPageNewTagFieldLabel": "新しいタグ", + "tagEditorPageAddTagTooltip": "タグを追加", + "tagEditorSectionRecent": "最近", + + "panoramaEnableSensorControl": "センサー制御を有効にする", + "panoramaDisableSensorControl": "センサー制御を無効にする", + + "sourceViewerPageTitle": "ソース", + + "filePickerShowHiddenFiles": "非表示のファイルを表示する", + "filePickerDoNotShowHiddenFiles": "非表示のファイルを表示しない", + "filePickerOpenFrom": "次から開く", + "filePickerNoItems": "アイテムはありません", + "filePickerUseThisFolder": "このフォルダを使用" +} diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index abe37bbcc..b5a4fd993 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -68,6 +68,8 @@ "entryActionRemoveFavourite": "즐겨찾기에서 삭제", "videoActionCaptureFrame": "프레임 캡처", + "videoActionMute": "음소거", + "videoActionUnmute": "음소거 해제", "videoActionPause": "일시정지", "videoActionPlay": "재생", "videoActionReplay10": "10초 뒤로 탐색", @@ -112,6 +114,11 @@ "videoLoopModeShortOnly": "짧은 동영상만 반복", "videoLoopModeAlways": "항상 반복", + "videoControlsPlay": "재생", + "videoControlsPlaySeek": "재생 및 앞뒤로 가기", + "videoControlsPlayOutside": "다른 앱에서 열기", + "videoControlsNone": "없음", + "mapStyleGoogleNormal": "구글 지도", "mapStyleGoogleHybrid": "구글 지도 (위성)", "mapStyleGoogleTerrain": "구글 지도 (지형)", @@ -257,8 +264,7 @@ "aboutCredits": "크레딧", "aboutCreditsWorldAtlas1": "이 앱은", "aboutCreditsWorldAtlas2": "의 TopoJSON 파일(ISC 라이선스)을 이용합니다.", - "aboutCreditsTranslators": "번역가:", - "aboutCreditsTranslatorLine": "{language}: {names}", + "aboutCreditsTranslators": "번역가", "aboutLicenses": "오픈 소스 라이선스", "aboutLicensesBanner": "이 앱은 다음의 오픈 소스 패키지와 라이브러리를 이용합니다.", @@ -431,6 +437,7 @@ "settingsViewerShowInformation": "상세 정보 표시", "settingsViewerShowInformationSubtitle": "제목, 날짜, 장소 등 표시", "settingsViewerShowShootingDetails": "촬영 정보 표시", + "settingsViewerShowOverlayThumbnails": "섬네일 표시", "settingsViewerEnableOverlayBlurEffect": "흐림 효과", "settingsVideoPageTitle": "동영상 설정", @@ -440,8 +447,6 @@ "settingsVideoEnableAutoPlay": "자동 재생", "settingsVideoLoopModeTile": "반복 모드", "settingsVideoLoopModeTitle": "반복 모드", - "settingsVideoQuickActionsTile": "동영상의 빠른 작업", - "settingsVideoQuickActionEditorTitle": "빠른 작업", "settingsSubtitleThemeTile": "자막", "settingsSubtitleThemeTitle": "자막", @@ -458,6 +463,13 @@ "settingsSubtitleThemeTextAlignmentCenter": "가운데", "settingsSubtitleThemeTextAlignmentRight": "오른쪽", + "settingsVideoControlsTile": "제어", + "settingsVideoControlsTitle": "제어", + "settingsVideoButtonsTile": "버튼", + "settingsVideoButtonsTitle": "버튼", + "settingsVideoGestureDoubleTapTogglePlay": "두 번 탭해서 재생이나 일시정지하기", + "settingsVideoGestureSideDoubleTapSeek": "화면 측면에서 두 번 탭해서 앞뒤로 가기", + "settingsSectionPrivacy": "개인정보 보호", "settingsAllowInstalledAppAccess": "설치된 앱의 목록 접근 허용", "settingsAllowInstalledAppAccessSubtitle": "앨범 표시 개선을 위해", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 948e20c12..55b03d590 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -68,6 +68,8 @@ "entryActionRemoveFavourite": "Remova dos favoritos", "videoActionCaptureFrame": "Capturar quadro", + "videoActionMute": "Mudo", + "videoActionUnmute": "Ativar mudo", "videoActionPause": "Pausa", "videoActionPlay": "Toque", "videoActionReplay10": "Retroceda 10 segundos", @@ -112,6 +114,11 @@ "videoLoopModeShortOnly": "Apenas vídeos curtos", "videoLoopModeAlways": "Sempre", + "videoControlsPlay": "Começar", + "videoControlsPlaySeek": "Começar e procurar", + "videoControlsPlayOutside": "Abrir com outro player", + "videoControlsNone": "Nenhum", + "mapStyleGoogleNormal": "Google Maps", "mapStyleGoogleHybrid": "Google Maps (Híbrido)", "mapStyleGoogleTerrain": "Google Maps (Terreno)", @@ -257,8 +264,7 @@ "aboutCredits": "Créditos", "aboutCreditsWorldAtlas1": "Este aplicativo usa um arquivo de TopoJSON", "aboutCreditsWorldAtlas2": "sob licença ISC.", - "aboutCreditsTranslators": "Tradutores:", - "aboutCreditsTranslatorLine": "{language}: {names}", + "aboutCreditsTranslators": "Tradutores", "aboutLicenses": "Licenças de código aberto", "aboutLicensesBanner": "Este aplicativo usa os seguintes pacotes e bibliotecas de código aberto.", @@ -431,6 +437,7 @@ "settingsViewerShowInformation": "Mostrar informações", "settingsViewerShowInformationSubtitle": "Mostrar título, data, local, etc.", "settingsViewerShowShootingDetails": "Mostrar detalhes de disparo", + "settingsViewerShowOverlayThumbnails": "Mostrar miniaturas", "settingsViewerEnableOverlayBlurEffect": "Efeito de desfoque", "settingsVideoPageTitle": "Configurações de vídeo", @@ -440,8 +447,6 @@ "settingsVideoEnableAutoPlay": "Reprodução automática", "settingsVideoLoopModeTile": "Modo de loop", "settingsVideoLoopModeTitle": "Modo de loop", - "settingsVideoQuickActionsTile": "Ações rápidas para vídeos", - "settingsVideoQuickActionEditorTitle": "Ações rápidas", "settingsSubtitleThemeTile": "Legendas", "settingsSubtitleThemeTitle": "Legendas", @@ -458,6 +463,13 @@ "settingsSubtitleThemeTextAlignmentCenter": "Centro", "settingsSubtitleThemeTextAlignmentRight": "Direita", + "settingsVideoControlsTile": "Controles", + "settingsVideoControlsTitle": "Controles", + "settingsVideoButtonsTile": "Botões", + "settingsVideoButtonsTitle": "Botões", + "settingsVideoGestureDoubleTapTogglePlay": "Toque duas vezes para reproduzir/pausar", + "settingsVideoGestureSideDoubleTapSeek": "Toque duas vezes nas bordas da tela buscar para trás/para frente", + "settingsSectionPrivacy": "Privacidade", "settingsAllowInstalledAppAccess": "Permitir acesso ao inventário de aplicativos", "settingsAllowInstalledAppAccessSubtitle": "Usado para melhorar a exibição do álbum", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 86b70ebe5..633ca3a4e 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -48,6 +48,7 @@ "entryActionCopyToClipboard": "Скопировать в буфер обмена", "entryActionDelete": "Удалить", + "entryActionConvert": "Конвертировать", "entryActionExport": "Экспорт", "entryActionRename": "Переименовать", "entryActionRestore": "Восстановить", @@ -67,6 +68,8 @@ "entryActionRemoveFavourite": "Удалить из избранного", "videoActionCaptureFrame": "Сохранить кадр", + "videoActionMute": "Выключить звук", + "videoActionUnmute": "Включить звук", "videoActionPause": "Стоп", "videoActionPlay": "Играть", "videoActionReplay10": "Перемотка на 10 секунд назад", @@ -111,6 +114,11 @@ "videoLoopModeShortOnly": "Только для коротких видео", "videoLoopModeAlways": "Всегда", + "videoControlsPlay": "Воспроизведение", + "videoControlsPlaySeek": "Воспроизведение и переход на позицию", + "videoControlsPlayOutside": "Открыть в другом видеоплеере", + "videoControlsNone": "Ничего", + "mapStyleGoogleNormal": "Google Карты", "mapStyleGoogleHybrid": "Google Карты (Гибридный)", "mapStyleGoogleTerrain": "Google Карты (Местность)", @@ -256,8 +264,7 @@ "aboutCredits": "Благодарности", "aboutCreditsWorldAtlas1": "Это приложение использует файл TopoJSON из", "aboutCreditsWorldAtlas2": "под лицензией ISC.", - "aboutCreditsTranslators": "Переводчики:", - "aboutCreditsTranslatorLine": "{language}: {names}", + "aboutCreditsTranslators": "Переводчики", "aboutLicenses": "Лицензии с открытым исходным кодом", "aboutLicensesBanner": "Это приложение использует следующие пакеты и библиотеки с открытым исходным кодом.", @@ -430,6 +437,7 @@ "settingsViewerShowInformation": "Показать информацию", "settingsViewerShowInformationSubtitle": "Показать название, дату, местоположение и т.д.", "settingsViewerShowShootingDetails": "Показать детали съёмки", + "settingsViewerShowOverlayThumbnails": "Показать эскизы", "settingsViewerEnableOverlayBlurEffect": "Наложение эффекта размытия", "settingsVideoPageTitle": "Настройки видео", @@ -439,8 +447,6 @@ "settingsVideoEnableAutoPlay": "Автозапуск воспроизведения", "settingsVideoLoopModeTile": "Циклический режим", "settingsVideoLoopModeTitle": "Цикличный режим", - "settingsVideoQuickActionsTile": "Быстрые действия для видео", - "settingsVideoQuickActionEditorTitle": "Быстрые действия", "settingsSubtitleThemeTile": "Субтитры", "settingsSubtitleThemeTitle": "Субтитры", @@ -457,6 +463,13 @@ "settingsSubtitleThemeTextAlignmentCenter": "По центру", "settingsSubtitleThemeTextAlignmentRight": "По правой стороне", + "settingsVideoControlsTile": "Элементы управления", + "settingsVideoControlsTitle": "Элементы управления", + "settingsVideoButtonsTile": "Кнопки", + "settingsVideoButtonsTitle": "Кнопки", + "settingsVideoGestureDoubleTapTogglePlay": "Двойное нажатие для воспроизведения/паузы", + "settingsVideoGestureSideDoubleTapSeek": "Двойное нажатие на края экрана для перехода назад/вперёд", + "settingsSectionPrivacy": "Конфиденциальность", "settingsAllowInstalledAppAccess": "Разрешить доступ к библиотеке приложения", "settingsAllowInstalledAppAccessSubtitle": "Используется для улучшения отображения альбомов", diff --git a/lib/model/actions/chip_actions.dart b/lib/model/actions/chip_actions.dart index 046850c81..efae273d7 100644 --- a/lib/model/actions/chip_actions.dart +++ b/lib/model/actions/chip_actions.dart @@ -23,9 +23,7 @@ extension ExtraChipAction on ChipAction { } } - Widget getIcon() { - return Icon(_getIconData()); - } + Widget getIcon() => Icon(_getIconData()); IconData _getIconData() { switch (this) { diff --git a/lib/model/actions/chip_set_actions.dart b/lib/model/actions/chip_set_actions.dart index 7a73a8186..65ce55e5a 100644 --- a/lib/model/actions/chip_set_actions.dart +++ b/lib/model/actions/chip_set_actions.dart @@ -90,9 +90,7 @@ extension ExtraChipSetAction on ChipSetAction { } } - Widget getIcon() { - return Icon(_getIconData()); - } + Widget getIcon() => Icon(_getIconData()); IconData _getIconData() { switch (this) { diff --git a/lib/model/actions/entry_actions.dart b/lib/model/actions/entry_actions.dart index 2528b62d7..b9282b429 100644 --- a/lib/model/actions/entry_actions.dart +++ b/lib/model/actions/entry_actions.dart @@ -21,6 +21,15 @@ enum EntryAction { flip, // vector viewSource, + // video + videoCaptureFrame, + videoSelectStreams, + videoSetSpeed, + videoToggleMute, + videoSettings, + videoTogglePlay, + videoReplay10, + videoSkip10, // external edit, open, @@ -41,8 +50,8 @@ class EntryActions { EntryAction.copy, EntryAction.move, EntryAction.toggleFavourite, - EntryAction.viewSource, EntryAction.rotateScreen, + EntryAction.viewSource, ]; static const export = [ @@ -62,6 +71,14 @@ class EntryActions { ]; static const pageActions = [ + EntryAction.videoCaptureFrame, + EntryAction.videoSelectStreams, + EntryAction.videoSetSpeed, + EntryAction.videoToggleMute, + EntryAction.videoSettings, + EntryAction.videoTogglePlay, + EntryAction.videoReplay10, + EntryAction.videoSkip10, EntryAction.rotateCCW, EntryAction.rotateCW, EntryAction.flip, @@ -72,6 +89,14 @@ class EntryActions { EntryAction.restore, EntryAction.debug, ]; + + static const video = [ + EntryAction.videoCaptureFrame, + EntryAction.videoToggleMute, + EntryAction.videoSetSpeed, + EntryAction.videoSelectStreams, + EntryAction.videoSettings, + ]; } extension ExtraEntryAction on EntryAction { @@ -110,6 +135,25 @@ extension ExtraEntryAction on EntryAction { // vector case EntryAction.viewSource: return context.l10n.entryActionViewSource; + // video + case EntryAction.videoCaptureFrame: + return context.l10n.videoActionCaptureFrame; + case 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.videoActionSettings; + case 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; // external case EntryAction.edit: return context.l10n.entryActionEdit; @@ -176,6 +220,25 @@ extension ExtraEntryAction on EntryAction { // vector case EntryAction.viewSource: return AIcons.vector; + // video + case EntryAction.videoCaptureFrame: + return AIcons.captureFrame; + case 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: + // different data depending on toggle state + return AIcons.play; + case EntryAction.videoReplay10: + return AIcons.replay10; + case EntryAction.videoSkip10: + return AIcons.skip10; // external case EntryAction.edit: return AIcons.edit; diff --git a/lib/model/actions/entry_set_actions.dart b/lib/model/actions/entry_set_actions.dart index 8df63b991..7446ff8e4 100644 --- a/lib/model/actions/entry_set_actions.dart +++ b/lib/model/actions/entry_set_actions.dart @@ -159,9 +159,7 @@ extension ExtraEntrySetAction on EntrySetAction { } } - Widget getIcon() { - return Icon(_getIconData()); - } + Widget getIcon() => Icon(_getIconData()); IconData _getIconData() { switch (this) { diff --git a/lib/model/actions/video_actions.dart b/lib/model/actions/video_actions.dart deleted file mode 100644 index 627640bb5..000000000 --- a/lib/model/actions/video_actions.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:aves/theme/icons.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:flutter/widgets.dart'; - -enum VideoAction { - captureFrame, - playOutside, - replay10, - skip10, - selectStreams, - setSpeed, - settings, - togglePlay, - // TODO TLAD [video] toggle mute -} - -class VideoActions { - static const all = [ - VideoAction.togglePlay, - VideoAction.captureFrame, - VideoAction.setSpeed, - VideoAction.selectStreams, - VideoAction.replay10, - VideoAction.skip10, - VideoAction.playOutside, - VideoAction.settings, - ]; -} - -extension ExtraVideoAction on VideoAction { - String getText(BuildContext context) { - switch (this) { - case VideoAction.captureFrame: - return context.l10n.videoActionCaptureFrame; - case VideoAction.playOutside: - return context.l10n.entryActionOpen; - case VideoAction.replay10: - return context.l10n.videoActionReplay10; - case VideoAction.skip10: - return context.l10n.videoActionSkip10; - case VideoAction.selectStreams: - return context.l10n.videoActionSelectStreams; - case VideoAction.setSpeed: - return context.l10n.videoActionSetSpeed; - case VideoAction.settings: - return context.l10n.videoActionSettings; - case VideoAction.togglePlay: - // different data depending on toggle state - return context.l10n.videoActionPlay; - } - } - - Widget getIcon() { - return Icon(_getIconData()); - } - - IconData _getIconData() { - switch (this) { - case VideoAction.captureFrame: - return AIcons.captureFrame; - case VideoAction.playOutside: - return AIcons.openOutside; - case VideoAction.replay10: - return AIcons.replay10; - case VideoAction.skip10: - return AIcons.skip10; - case VideoAction.selectStreams: - return AIcons.streams; - case VideoAction.setSpeed: - return AIcons.speed; - case VideoAction.settings: - return AIcons.videoSettings; - case VideoAction.togglePlay: - // different data depending on toggle state - return AIcons.play; - } - } -} diff --git a/lib/model/db/db_metadata.dart b/lib/model/db/db_metadata.dart index df051677a..cc23707bf 100644 --- a/lib/model/db/db_metadata.dart +++ b/lib/model/db/db_metadata.dart @@ -30,7 +30,7 @@ abstract class MetadataDb { Future updateEntry(int id, AvesEntry entry); - Future> searchEntries(String query, {int? limit}); + Future> searchLiveEntries(String query, {int? limit}); // date taken diff --git a/lib/model/db/db_metadata_sqflite.dart b/lib/model/db/db_metadata_sqflite.dart index e5133647c..994a7d643 100644 --- a/lib/model/db/db_metadata_sqflite.dart +++ b/lib/model/db/db_metadata_sqflite.dart @@ -215,11 +215,11 @@ class SqfliteMetadataDb implements MetadataDb { } @override - Future> searchEntries(String query, {int? limit}) async { + Future> searchLiveEntries(String query, {int? limit}) async { final rows = await _db.query( entryTable, - where: 'title LIKE ?', - whereArgs: ['%$query%'], + where: 'title LIKE ? AND trashed = ?', + whereArgs: ['%$query%', 0], orderBy: 'sourceDateTakenMillis DESC', limit: limit, ); diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart index e3c2de204..afa1f219f 100644 --- a/lib/model/settings/defaults.dart +++ b/lib/model/settings/defaults.dart @@ -1,6 +1,5 @@ import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/entry_set_actions.dart'; -import 'package:aves/model/actions/video_actions.dart'; import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/settings/enums/enums.dart'; @@ -67,20 +66,20 @@ class SettingsDefaults { static const showOverlayMinimap = false; static const showOverlayInfo = true; static const showOverlayShootingDetails = false; + static const showOverlayThumbnailPreview = false; static const enableOverlayBlurEffect = true; // `enableOverlayBlurEffect` has a contextual default value static const viewerUseCutout = true; static const viewerMaxBrightness = false; static const enableMotionPhotoAutoPlay = false; // video - static const videoQuickActions = [ - VideoAction.replay10, - VideoAction.togglePlay, - ]; static const enableVideoHardwareAcceleration = true; static const enableVideoAutoPlay = false; static const videoLoopMode = VideoLoopMode.shortOnly; static const videoShowRawTimedText = false; + static const videoControls = VideoControls.play; + static const videoGestureDoubleTapTogglePlay = false; + static const videoGestureSideDoubleTapSeek = true; // subtitles static const subtitleFontSize = 20.0; diff --git a/lib/model/settings/enums/enums.dart b/lib/model/settings/enums/enums.dart index 8957d0141..f5100f413 100644 --- a/lib/model/settings/enums/enums.dart +++ b/lib/model/settings/enums/enums.dart @@ -18,3 +18,5 @@ enum KeepScreenOn { never, viewerOnly, always } enum UnitSystem { metric, imperial } enum VideoLoopMode { never, shortOnly, always } + +enum VideoControls { play, playSeek, playOutside, none } diff --git a/lib/model/settings/enums/video_controls.dart b/lib/model/settings/enums/video_controls.dart new file mode 100644 index 000000000..6f9979f71 --- /dev/null +++ b/lib/model/settings/enums/video_controls.dart @@ -0,0 +1,19 @@ +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:flutter/widgets.dart'; + +import 'enums.dart'; + +extension ExtraVideoControls 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; + } + } +} diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index 4d32bff5a..67b8f7e0f 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -4,7 +4,6 @@ import 'dart:math'; import 'package:aves/l10n/l10n.dart'; import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/entry_set_actions.dart'; -import 'package:aves/model/actions/video_actions.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/defaults.dart'; import 'package:aves/model/settings/enums/enums.dart'; @@ -82,6 +81,7 @@ class Settings extends ChangeNotifier { static const showOverlayMinimapKey = 'show_overlay_minimap'; static const showOverlayInfoKey = 'show_overlay_info'; static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details'; + static const showOverlayThumbnailPreviewKey = 'show_overlay_thumbnail_preview'; static const enableOverlayBlurEffectKey = 'enable_overlay_blur_effect'; static const viewerUseCutoutKey = 'viewer_use_cutout'; static const viewerMaxBrightnessKey = 'viewer_max_brightness'; @@ -89,11 +89,13 @@ class Settings extends ChangeNotifier { static const imageBackgroundKey = 'image_background'; // video - static const videoQuickActionsKey = 'video_quick_actions'; static const enableVideoHardwareAccelerationKey = 'video_hwaccel_mediacodec'; static const enableVideoAutoPlayKey = 'video_auto_play'; static const videoLoopModeKey = 'video_loop'; static const videoShowRawTimedTextKey = 'video_show_raw_timed_text'; + static const videoControlsKey = 'video_controls'; + static const videoGestureDoubleTapTogglePlayKey = 'video_gesture_double_tap_toggle_play'; + static const videoGestureSideDoubleTapSeekKey = 'video_gesture_side_double_tap_skip'; // subtitles static const subtitleFontSizeKey = 'subtitle_font_size'; @@ -389,6 +391,10 @@ class Settings extends ChangeNotifier { set showOverlayShootingDetails(bool newValue) => setAndNotify(showOverlayShootingDetailsKey, newValue); + bool get showOverlayThumbnailPreview => getBoolOrDefault(showOverlayThumbnailPreviewKey, SettingsDefaults.showOverlayThumbnailPreview); + + set showOverlayThumbnailPreview(bool newValue) => setAndNotify(showOverlayThumbnailPreviewKey, newValue); + bool get enableOverlayBlurEffect => getBoolOrDefault(enableOverlayBlurEffectKey, SettingsDefaults.enableOverlayBlurEffect); set enableOverlayBlurEffect(bool newValue) => setAndNotify(enableOverlayBlurEffectKey, newValue); @@ -411,10 +417,6 @@ class Settings extends ChangeNotifier { // video - List get videoQuickActions => getEnumListOrDefault(videoQuickActionsKey, SettingsDefaults.videoQuickActions, VideoAction.values); - - set videoQuickActions(List newValue) => setAndNotify(videoQuickActionsKey, newValue.map((v) => v.toString()).toList()); - bool get enableVideoHardwareAcceleration => getBoolOrDefault(enableVideoHardwareAccelerationKey, SettingsDefaults.enableVideoHardwareAcceleration); set enableVideoHardwareAcceleration(bool newValue) => setAndNotify(enableVideoHardwareAccelerationKey, newValue); @@ -431,6 +433,18 @@ class Settings extends ChangeNotifier { set videoShowRawTimedText(bool newValue) => setAndNotify(videoShowRawTimedTextKey, newValue); + VideoControls get videoControls => getEnumOrDefault(videoControlsKey, SettingsDefaults.videoControls, VideoControls.values); + + set videoControls(VideoControls newValue) => setAndNotify(videoControlsKey, newValue.toString()); + + bool get videoGestureDoubleTapTogglePlay => getBoolOrDefault(videoGestureDoubleTapTogglePlayKey, SettingsDefaults.videoGestureDoubleTapTogglePlay); + + set videoGestureDoubleTapTogglePlay(bool newValue) => setAndNotify(videoGestureDoubleTapTogglePlayKey, newValue); + + bool get videoGestureSideDoubleTapSeek => getBoolOrDefault(videoGestureSideDoubleTapSeekKey, SettingsDefaults.videoGestureSideDoubleTapSeek); + + set videoGestureSideDoubleTapSeek(bool newValue) => setAndNotify(videoGestureSideDoubleTapSeekKey, newValue); + // subtitles double get subtitleFontSize => getDouble(subtitleFontSizeKey) ?? SettingsDefaults.subtitleFontSize; @@ -642,12 +656,15 @@ class Settings extends ChangeNotifier { case showOverlayMinimapKey: case showOverlayInfoKey: case showOverlayShootingDetailsKey: + case showOverlayThumbnailPreviewKey: case enableOverlayBlurEffectKey: case viewerUseCutoutKey: case viewerMaxBrightnessKey: case enableMotionPhotoAutoPlayKey: case enableVideoHardwareAccelerationKey: case enableVideoAutoPlayKey: + case videoGestureDoubleTapTogglePlayKey: + case videoGestureSideDoubleTapSeekKey: case subtitleShowOutlineKey: case saveSearchHistoryKey: case filePickerShowHiddenFilesKey: @@ -668,6 +685,7 @@ class Settings extends ChangeNotifier { case tagSortFactorKey: case imageBackgroundKey: case videoLoopModeKey: + case videoControlsKey: case subtitleTextAlignmentKey: case infoMapStyleKey: case coordinateFormatKey: @@ -689,7 +707,6 @@ class Settings extends ChangeNotifier { case collectionBrowsingQuickActionsKey: case collectionSelectionQuickActionsKey: case viewerQuickActionsKey: - case videoQuickActionsKey: if (newValue is List) { settingsStore.setStringList(key, newValue.cast()); } else { diff --git a/lib/model/source/collection_source.dart b/lib/model/source/collection_source.dart index eade5b709..cec81045a 100644 --- a/lib/model/source/collection_source.dart +++ b/lib/model/source/collection_source.dart @@ -338,13 +338,19 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM }); } - if (copy) { - addEntries(movedEntries); - } else { - cleanEmptyAlbums(fromAlbums); - if (moveType != MoveType.toBin) { + switch(moveType) { + case MoveType.copy: + addEntries(movedEntries); + break; + case MoveType.move: + case MoveType.export: + cleanEmptyAlbums(fromAlbums); addDirectories(destinationAlbums); - } + break; + case MoveType.toBin: + case MoveType.fromBin: + updateDerivedFilters(movedEntries); + break; } invalidateAlbumFilterSummary(directories: fromAlbums); _invalidate(movedEntries); diff --git a/lib/services/android_app_service.dart b/lib/services/android_app_service.dart index ee0366e31..425d13483 100644 --- a/lib/services/android_app_service.dart +++ b/lib/services/android_app_service.dart @@ -34,20 +34,24 @@ abstract class AndroidAppService { class PlatformAndroidAppService implements AndroidAppService { static const platform = MethodChannel('deckers.thibault/aves/app'); + static final knownAppDirs = { + 'com.kakao.talk': {'KakaoTalkDownload'}, + 'com.sony.playmemories.mobile': {'Imaging Edge Mobile'}, + 'nekox.messenger': {'NekoX'}, + }; + @override Future> getPackages() async { try { final result = await platform.invokeMethod('getPackages'); final packages = (result as List).cast().map(Package.fromMap).toSet(); // additional info for known directories - final kakaoTalk = packages.firstWhereOrNull((package) => package.packageName == 'com.kakao.talk'); - if (kakaoTalk != null) { - kakaoTalk.ownedDirs.add('KakaoTalkDownload'); - } - final imagingEdge = packages.firstWhereOrNull((package) => package.packageName == 'com.sony.playmemories.mobile'); - if (imagingEdge != null) { - imagingEdge.ownedDirs.add('Imaging Edge Mobile'); - } + knownAppDirs.forEach((packageName, dirs) { + final package = packages.firstWhereOrNull((package) => package.packageName == packageName); + if (package != null) { + package.ownedDirs.addAll(dirs); + } + }); return packages; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); diff --git a/lib/services/global_search.dart b/lib/services/global_search.dart index 2edc86528..19415e4e2 100644 --- a/lib/services/global_search.dart +++ b/lib/services/global_search.dart @@ -51,7 +51,7 @@ Future>> _getSuggestions(dynamic args) async { debugPrint('getSuggestions query=$query, locale=$locale use24hour=$use24hour'); if (query is String && locale is String) { - final entries = await metadataDb.searchEntries(query, limit: 9); + final entries = await metadataDb.searchLiveEntries(query, limit: 9); suggestions.addAll(entries.map((entry) { final date = entry.bestDate; return { diff --git a/lib/theme/durations.dart b/lib/theme/durations.dart index 4c91a1685..da4010ca9 100644 --- a/lib/theme/durations.dart +++ b/lib/theme/durations.dart @@ -39,6 +39,7 @@ class Durations { static const thumbnailScrollerScrollAnimation = Duration(milliseconds: 200); static const thumbnailScrollerShadeAnimation = Duration(milliseconds: 150); static const viewerVideoPlayerTransition = Duration(milliseconds: 500); + static const viewerActionFeedbackAnimation = Duration(milliseconds: 600); // info animations static const mapStyleSwitchAnimation = Duration(milliseconds: 300); @@ -62,7 +63,8 @@ class Durations { static const doubleBackTimerDelay = Duration(milliseconds: 1000); static const softKeyboardDisplayDelay = Duration(milliseconds: 300); static const searchDebounceDelay = Duration(milliseconds: 250); - static const contentChangeDebounceDelay = Duration(milliseconds: 1000); + static const mediaContentChangeDebounceDelay = Duration(milliseconds: 1000); + static const viewerThumbnailScrollDebounceDelay = Duration(milliseconds: 1000); static const mapInfoDebounceDelay = Duration(milliseconds: 150); static const mapIdleDebounceDelay = Duration(milliseconds: 100); } diff --git a/lib/theme/icons.dart b/lib/theme/icons.dart index c147d43d9..8c02d5913 100644 --- a/lib/theme/icons.dart +++ b/lib/theme/icons.dart @@ -72,6 +72,8 @@ class AIcons { static const IconData layers = Icons.layers_outlined; static const IconData map = Icons.map_outlined; static const IconData move = MdiIcons.fileMoveOutline; + static const IconData mute = Icons.volume_off_outlined; + static const IconData unmute = Icons.volume_up_outlined; static const IconData newTier = Icons.fiber_new_outlined; static const IconData openOutside = Icons.open_in_new_outlined; static const IconData pin = Icons.push_pin_outlined; diff --git a/lib/widgets/about/app_ref.dart b/lib/widgets/about/app_ref.dart index cf632b400..2f401185e 100644 --- a/lib/widgets/about/app_ref.dart +++ b/lib/widgets/about/app_ref.dart @@ -13,7 +13,7 @@ class AppReference extends StatefulWidget { const AppReference({Key? key}) : super(key: key); @override - _AppReferenceState createState() => _AppReferenceState(); + State createState() => _AppReferenceState(); } class _AppReferenceState extends State { diff --git a/lib/widgets/about/bug_report.dart b/lib/widgets/about/bug_report.dart index 978123c10..592486d78 100644 --- a/lib/widgets/about/bug_report.dart +++ b/lib/widgets/about/bug_report.dart @@ -25,7 +25,7 @@ class BugReport extends StatefulWidget { const BugReport({Key? key}) : super(key: key); @override - _BugReportState createState() => _BugReportState(); + State createState() => _BugReportState(); } class _BugReportState extends State with FeedbackMixin { diff --git a/lib/widgets/about/credits.dart b/lib/widgets/about/credits.dart index 83b89e9a2..9008c2fd1 100644 --- a/lib/widgets/about/credits.dart +++ b/lib/widgets/about/credits.dart @@ -1,6 +1,7 @@ import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/common/basic/link_chip.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/viewer/info/common.dart'; import 'package:flutter/material.dart'; class AboutCredits extends StatelessWidget { @@ -12,6 +13,7 @@ class AboutCredits extends StatelessWidget { 'Español (México)': 'n-berenice', 'Português (Brasil)': 'Jonatas De Almeida Barros', 'Русский': 'D3ZOXY', + '日本語': 'Maki', }; @override @@ -45,16 +47,14 @@ class AboutCredits extends StatelessWidget { ], ), ), - const SizedBox(height: 16), - Text(l10n.aboutCreditsTranslators), - ...translators.entries.map( - (kv) { - return Padding( - padding: const EdgeInsetsDirectional.only(start: 8, top: 8), - child: Text(l10n.aboutCreditsTranslatorLine(kv.key, kv.value)), - ); - }, + ConstrainedBox( + constraints: const BoxConstraints(minHeight: 48), + child: Align( + alignment: AlignmentDirectional.centerStart, + child: Text(l10n.aboutCreditsTranslators, style: Constants.titleTextStyle), + ), ), + const InfoRowGroup(info: translators), const SizedBox(height: 16), ], ), diff --git a/lib/widgets/about/licenses.dart b/lib/widgets/about/licenses.dart index 3de3ae949..18e593d42 100644 --- a/lib/widgets/about/licenses.dart +++ b/lib/widgets/about/licenses.dart @@ -13,7 +13,7 @@ class Licenses extends StatefulWidget { const Licenses({Key? key}) : super(key: key); @override - _LicensesState createState() => _LicensesState(); + State createState() => _LicensesState(); } class _LicensesState extends State { diff --git a/lib/widgets/about/policy_page.dart b/lib/widgets/about/policy_page.dart index 67c12597c..534f3f7dc 100644 --- a/lib/widgets/about/policy_page.dart +++ b/lib/widgets/about/policy_page.dart @@ -11,7 +11,7 @@ class PolicyPage extends StatefulWidget { }) : super(key: key); @override - _PolicyPageState createState() => _PolicyPageState(); + State createState() => _PolicyPageState(); } class _PolicyPageState extends State { diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart index db11d41c1..a1fcd40d9 100644 --- a/lib/widgets/aves_app.dart +++ b/lib/widgets/aves_app.dart @@ -44,14 +44,14 @@ class AvesApp extends StatefulWidget { }) : super(key: key); @override - _AvesAppState createState() => _AvesAppState(); + State createState() => _AvesAppState(); } class _AvesAppState extends State with WidgetsBindingObserver { final ValueNotifier appModeNotifier = ValueNotifier(AppMode.main); late Future _appSetup; final _mediaStoreSource = MediaStoreSource(); - final Debouncer _mediaStoreChangeDebouncer = Debouncer(delay: Durations.contentChangeDebounceDelay); + final Debouncer _mediaStoreChangeDebouncer = Debouncer(delay: Durations.mediaContentChangeDebounceDelay); final Set changedUris = {}; // observers are not registered when using the same list object with different items diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index bb810f95d..773b54a4f 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -43,7 +43,7 @@ class CollectionAppBar extends StatefulWidget { }) : super(key: key); @override - _CollectionAppBarState createState() => _CollectionAppBarState(); + State createState() => _CollectionAppBarState(); } class _CollectionAppBarState extends State with SingleTickerProviderStateMixin { diff --git a/lib/widgets/collection/collection_grid.dart b/lib/widgets/collection/collection_grid.dart index 8f85f6995..629b52c27 100644 --- a/lib/widgets/collection/collection_grid.dart +++ b/lib/widgets/collection/collection_grid.dart @@ -50,7 +50,7 @@ class CollectionGrid extends StatefulWidget { }) : super(key: key); @override - _CollectionGridState createState() => _CollectionGridState(); + State createState() => _CollectionGridState(); } class _CollectionGridState extends State { @@ -170,7 +170,7 @@ class _CollectionSectionedContent extends StatefulWidget { }); @override - _CollectionSectionedContentState createState() => _CollectionSectionedContentState(); + State<_CollectionSectionedContent> createState() => _CollectionSectionedContentState(); } class _CollectionSectionedContentState extends State<_CollectionSectionedContent> { @@ -291,7 +291,7 @@ class _CollectionScrollView extends StatefulWidget { }); @override - _CollectionScrollViewState createState() => _CollectionScrollViewState(); + State<_CollectionScrollView> createState() => _CollectionScrollViewState(); } class _CollectionScrollViewState extends State<_CollectionScrollView> { diff --git a/lib/widgets/collection/collection_page.dart b/lib/widgets/collection/collection_page.dart index 2f3b4cdca..463053562 100644 --- a/lib/widgets/collection/collection_page.dart +++ b/lib/widgets/collection/collection_page.dart @@ -29,7 +29,7 @@ class CollectionPage extends StatefulWidget { }) : super(key: key); @override - _CollectionPageState createState() => _CollectionPageState(); + State createState() => _CollectionPageState(); } class _CollectionPageState extends State { diff --git a/lib/widgets/collection/filter_bar.dart b/lib/widgets/collection/filter_bar.dart index 76f265e2a..7d67a60ec 100644 --- a/lib/widgets/collection/filter_bar.dart +++ b/lib/widgets/collection/filter_bar.dart @@ -20,7 +20,7 @@ class FilterBar extends StatefulWidget { super(key: key); @override - _FilterBarState createState() => _FilterBarState(); + State createState() => _FilterBarState(); } class _FilterBarState extends State { diff --git a/lib/widgets/collection/query_bar.dart b/lib/widgets/collection/query_bar.dart index f12c18b16..2cc898ade 100644 --- a/lib/widgets/collection/query_bar.dart +++ b/lib/widgets/collection/query_bar.dart @@ -19,7 +19,7 @@ class EntryQueryBar extends StatefulWidget { }) : super(key: key); @override - _EntryQueryBarState createState() => _EntryQueryBarState(); + State createState() => _EntryQueryBarState(); } class _EntryQueryBarState extends State { diff --git a/lib/widgets/common/action_mixins/entry_storage.dart b/lib/widgets/common/action_mixins/entry_storage.dart index 4c458f894..702a2d9c6 100644 --- a/lib/widgets/common/action_mixins/entry_storage.dart +++ b/lib/widgets/common/action_mixins/entry_storage.dart @@ -50,12 +50,6 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin { ))) return; } - final source = context.read(); - if (source.initState != SourceInitializationState.full) { - // source may be uninitialized in viewer mode - await source.init(); - } - final entriesByDestination = >{}; switch (moveType) { case MoveType.copy: @@ -102,20 +96,19 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin { if (uniqueNames.length < names.length) { final value = await showDialog( context: context, - builder: (context) { - return AvesSelectionDialog( - initialValue: nameConflictStrategy, - options: Map.fromEntries(NameConflictStrategy.values.map((v) => MapEntry(v, v.getName(context)))), - message: originAlbums.length == 1 ? l10n.nameConflictDialogSingleSourceMessage : l10n.nameConflictDialogMultipleSourceMessage, - confirmationButtonLabel: l10n.continueButtonLabel, - ); - }, + builder: (context) => AvesSelectionDialog( + initialValue: nameConflictStrategy, + options: Map.fromEntries(NameConflictStrategy.values.map((v) => MapEntry(v, v.getName(context)))), + message: originAlbums.length == 1 ? l10n.nameConflictDialogSingleSourceMessage : l10n.nameConflictDialogMultipleSourceMessage, + confirmationButtonLabel: l10n.continueButtonLabel, + ), ); if (value == null) return; nameConflictStrategy = value; } } + final source = context.read(); source.pauseMonitoring(); final opId = mediaFileService.newOpId; await showOpReport( diff --git a/lib/widgets/common/action_mixins/feedback.dart b/lib/widgets/common/action_mixins/feedback.dart index b3ab4343d..d1c9f9e54 100644 --- a/lib/widgets/common/action_mixins/feedback.dart +++ b/lib/widgets/common/action_mixins/feedback.dart @@ -84,7 +84,7 @@ mixin FeedbackMixin { itemCount: itemCount, onCancel: onCancel, onDone: (processed) { - Navigator.of(context).pop(); + Navigator.pop(context); onDone?.call(processed); }, ), @@ -107,7 +107,7 @@ class ReportOverlay extends StatefulWidget { }) : super(key: key); @override - _ReportOverlayState createState() => _ReportOverlayState(); + State> createState() => _ReportOverlayState(); } class _ReportOverlayState extends State> with SingleTickerProviderStateMixin { @@ -239,7 +239,7 @@ class _FeedbackMessage extends StatefulWidget { }) : super(key: key); @override - _FeedbackMessageState createState() => _FeedbackMessageState(); + State<_FeedbackMessage> createState() => _FeedbackMessageState(); } class _FeedbackMessageState extends State<_FeedbackMessage> { @@ -294,3 +294,70 @@ class _FeedbackMessageState extends State<_FeedbackMessage> { ); } } + +class ActionFeedback extends StatefulWidget { + final Widget? child; + + const ActionFeedback({ + Key? key, + required this.child, + }) : super(key: key); + + @override + State createState() => _ActionFeedbackState(); +} + +class _ActionFeedbackState extends State with SingleTickerProviderStateMixin { + late final AnimationController _animationController; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: Durations.viewerActionFeedbackAnimation, + vsync: this, + ); + } + + @override + void didUpdateWidget(covariant ActionFeedback oldWidget) { + super.didUpdateWidget(oldWidget); + + if (oldWidget.child != widget.child) { + _animationController.reset(); + if (widget.child != null) { + _animationController.forward(); + } + } + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return IgnorePointer( + child: Center( + child: AnimatedBuilder( + animation: _animationController, + builder: (context, child) { + final t = _animationController.value; + final opacity = Curves.easeOutQuad.transform(t > .5 ? (1 - t) * 2 : t * 2); + final scale = Curves.slowMiddle.transform(t) * 2; + return Opacity( + opacity: opacity, + child: Transform.scale( + scale: scale, + child: child, + ), + ); + }, + child: widget.child, + ), + ), + ); + } +} diff --git a/lib/widgets/common/basic/color_list_tile.dart b/lib/widgets/common/basic/color_list_tile.dart index 4ee3a65ba..92216f957 100644 --- a/lib/widgets/common/basic/color_list_tile.dart +++ b/lib/widgets/common/basic/color_list_tile.dart @@ -56,7 +56,7 @@ class ColorPickerDialog extends StatefulWidget { }) : super(key: key); @override - _ColorPickerDialogState createState() => _ColorPickerDialogState(); + State createState() => _ColorPickerDialogState(); } class _ColorPickerDialogState extends State { diff --git a/lib/widgets/common/basic/draggable_scrollbar.dart b/lib/widgets/common/basic/draggable_scrollbar.dart index fb7bd7b11..b3b0ea687 100644 --- a/lib/widgets/common/basic/draggable_scrollbar.dart +++ b/lib/widgets/common/basic/draggable_scrollbar.dart @@ -71,7 +71,7 @@ class DraggableScrollbar extends StatefulWidget { super(key: key); @override - _DraggableScrollbarState createState() => _DraggableScrollbarState(); + State createState() => _DraggableScrollbarState(); static Widget buildScrollThumbAndLabel({ required Widget scrollThumb, diff --git a/lib/widgets/common/basic/menu.dart b/lib/widgets/common/basic/menu.dart index 8bb4db14a..a4984026f 100644 --- a/lib/widgets/common/basic/menu.dart +++ b/lib/widgets/common/basic/menu.dart @@ -63,7 +63,7 @@ class PopupMenuItemExpansionPanel extends StatefulWidget { }) : super(key: key); @override - _PopupMenuItemExpansionPanelState createState() => _PopupMenuItemExpansionPanelState(); + State> createState() => _PopupMenuItemExpansionPanelState(); } class _PopupMenuItemExpansionPanelState extends State> { diff --git a/lib/widgets/common/basic/multi_cross_fader.dart b/lib/widgets/common/basic/multi_cross_fader.dart index ebf7a7e99..95be7ff94 100644 --- a/lib/widgets/common/basic/multi_cross_fader.dart +++ b/lib/widgets/common/basic/multi_cross_fader.dart @@ -16,7 +16,7 @@ class MultiCrossFader extends StatefulWidget { }) : super(key: key); @override - _MultiCrossFaderState createState() => _MultiCrossFaderState(); + State createState() => _MultiCrossFaderState(); } class _MultiCrossFaderState extends State { diff --git a/lib/widgets/common/basic/popup_menu_button.dart b/lib/widgets/common/basic/popup_menu_button.dart index ede8487ee..a6b224f78 100644 --- a/lib/widgets/common/basic/popup_menu_button.dart +++ b/lib/widgets/common/basic/popup_menu_button.dart @@ -41,7 +41,7 @@ class AvesPopupMenuButton extends PopupMenuButton { ); @override - _AvesPopupMenuButtonState createState() => _AvesPopupMenuButtonState(); + PopupMenuButtonState createState() => _AvesPopupMenuButtonState(); } class _AvesPopupMenuButtonState extends PopupMenuButtonState { diff --git a/lib/widgets/common/basic/query_bar.dart b/lib/widgets/common/basic/query_bar.dart index c4b97f3ad..909b5fe05 100644 --- a/lib/widgets/common/basic/query_bar.dart +++ b/lib/widgets/common/basic/query_bar.dart @@ -21,7 +21,7 @@ class QueryBar extends StatefulWidget { }) : super(key: key); @override - _QueryBarState createState() => _QueryBarState(); + State createState() => _QueryBarState(); } class _QueryBarState extends State { diff --git a/lib/widgets/common/basic/wheel.dart b/lib/widgets/common/basic/wheel.dart index 0c258668f..f1e9cbf34 100644 --- a/lib/widgets/common/basic/wheel.dart +++ b/lib/widgets/common/basic/wheel.dart @@ -15,7 +15,7 @@ class WheelSelector extends StatefulWidget { }) : super(key: key); @override - _WheelSelectorState createState() => _WheelSelectorState(); + State> createState() => _WheelSelectorState(); } class _WheelSelectorState extends State> { diff --git a/lib/widgets/common/behaviour/double_back_pop.dart b/lib/widgets/common/behaviour/double_back_pop.dart index 6b198c293..d1bbfc8c3 100644 --- a/lib/widgets/common/behaviour/double_back_pop.dart +++ b/lib/widgets/common/behaviour/double_back_pop.dart @@ -17,7 +17,7 @@ class DoubleBackPopScope extends StatefulWidget { }) : super(key: key); @override - _DoubleBackPopScopeState createState() => _DoubleBackPopScopeState(); + State createState() => _DoubleBackPopScopeState(); } class _DoubleBackPopScopeState extends State with FeedbackMixin { diff --git a/lib/widgets/common/behaviour/known_extent_scroll_physics.dart b/lib/widgets/common/behaviour/known_extent_scroll_physics.dart new file mode 100644 index 000000000..0dce20b84 --- /dev/null +++ b/lib/widgets/common/behaviour/known_extent_scroll_physics.dart @@ -0,0 +1,83 @@ +import 'package:flutter/physics.dart'; +import 'package:flutter/widgets.dart'; + +// adapted from Flutter `FixedExtentScrollPhysics` in `/widgets/list_wheel_scroll_view.dart` +class KnownExtentScrollPhysics extends ScrollPhysics { + final double Function(int index) indexToScrollOffset; + final int Function(double offset) scrollOffsetToIndex; + + const KnownExtentScrollPhysics({ + required this.indexToScrollOffset, + required this.scrollOffsetToIndex, + ScrollPhysics? parent, + }) : super(parent: parent); + + @override + KnownExtentScrollPhysics applyTo(ScrollPhysics? ancestor) { + return KnownExtentScrollPhysics( + indexToScrollOffset: indexToScrollOffset, + scrollOffsetToIndex: scrollOffsetToIndex, + parent: buildParent(ancestor), + ); + } + + @override + Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) { + final ScrollMetrics metrics = position; + + // Scenario 1: + // If we're out of range and not headed back in range, defer to the parent + // ballistics, which should put us back in range at the scrollable's boundary. + if ((velocity <= 0.0 && metrics.pixels <= metrics.minScrollExtent) || (velocity >= 0.0 && metrics.pixels >= metrics.maxScrollExtent)) { + return super.createBallisticSimulation(metrics, velocity); + } + + // Create a test simulation to see where it would have ballistically fallen + // naturally without settling onto items. + final Simulation? testFrictionSimulation = super.createBallisticSimulation(metrics, velocity); + + // Scenario 2: + // If it was going to end up past the scroll extent, defer back to the + // parent physics' ballistics again which should put us on the scrollable's + // boundary. + if (testFrictionSimulation != null && (testFrictionSimulation.x(double.infinity) == metrics.minScrollExtent || testFrictionSimulation.x(double.infinity) == metrics.maxScrollExtent)) { + return super.createBallisticSimulation(metrics, velocity); + } + + // From the natural final position, find the nearest item it should have + // settled to. + final offset = (testFrictionSimulation?.x(double.infinity) ?? metrics.pixels).clamp(metrics.minScrollExtent, metrics.maxScrollExtent); + final int settlingItemIndex = scrollOffsetToIndex(offset); + final double settlingPixels = indexToScrollOffset(settlingItemIndex); + + // Scenario 3: + // If there's no velocity and we're already at where we intend to land, + // do nothing. + if (velocity.abs() < tolerance.velocity && (settlingPixels - metrics.pixels).abs() < tolerance.distance) { + return null; + } + + // Scenario 4: + // If we're going to end back at the same item because initial velocity + // is too low to break past it, use a spring simulation to get back. + if (settlingItemIndex == scrollOffsetToIndex(metrics.pixels)) { + return SpringSimulation( + spring, + metrics.pixels, + settlingPixels, + velocity, + tolerance: tolerance, + ); + } + + // Scenario 5: + // Create a new friction simulation except the drag will be tweaked to land + // exactly on the item closest to the natural stopping point. + return FrictionSimulation.through( + metrics.pixels, + settlingPixels, + velocity, + tolerance.velocity * velocity.sign, + ); + } +} diff --git a/lib/widgets/common/favourite_toggler.dart b/lib/widgets/common/favourite_toggler.dart index 319c1d8ac..d3a2e563b 100644 --- a/lib/widgets/common/favourite_toggler.dart +++ b/lib/widgets/common/favourite_toggler.dart @@ -20,7 +20,7 @@ class FavouriteToggler extends StatefulWidget { }) : super(key: key); @override - _FavouriteTogglerState createState() => _FavouriteTogglerState(); + State createState() => _FavouriteTogglerState(); } class _FavouriteTogglerState extends State { diff --git a/lib/widgets/common/fx/blurred.dart b/lib/widgets/common/fx/blurred.dart index f98b7c63e..91131ac39 100644 --- a/lib/widgets/common/fx/blurred.dart +++ b/lib/widgets/common/fx/blurred.dart @@ -29,7 +29,7 @@ class BlurredRect extends StatelessWidget { class BlurredRRect extends StatelessWidget { final bool enabled; - final double borderRadius; + final BorderRadius? borderRadius; final Widget child; const BlurredRRect({ @@ -39,17 +39,31 @@ class BlurredRRect extends StatelessWidget { required this.child, }) : super(key: key); + factory BlurredRRect.all({ + Key? key, + bool enabled = true, + required double borderRadius, + required Widget child, + }) { + return BlurredRRect( + key: key, + enabled: enabled, + borderRadius: BorderRadius.all(Radius.circular(borderRadius)), + child: child, + ); + } + @override Widget build(BuildContext context) { - return enabled - ? ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(borderRadius)), - child: BackdropFilter( + return ClipRRect( + borderRadius: borderRadius, + child: enabled + ? BackdropFilter( filter: _filter, child: child, - ), - ) - : child; + ) + : child, + ); } } diff --git a/lib/widgets/common/fx/sweeper.dart b/lib/widgets/common/fx/sweeper.dart index 2227508d5..6062016cb 100644 --- a/lib/widgets/common/fx/sweeper.dart +++ b/lib/widgets/common/fx/sweeper.dart @@ -26,7 +26,7 @@ class Sweeper extends StatefulWidget { }) : super(key: key); @override - _SweeperState createState() => _SweeperState(); + State createState() => _SweeperState(); } class _SweeperState extends State with SingleTickerProviderStateMixin { diff --git a/lib/widgets/common/fx/transition_image.dart b/lib/widgets/common/fx/transition_image.dart index e611fa921..33d5b2472 100644 --- a/lib/widgets/common/fx/transition_image.dart +++ b/lib/widgets/common/fx/transition_image.dart @@ -25,7 +25,7 @@ class TransitionImage extends StatefulWidget { }) : super(key: key); @override - _TransitionImageState createState() => _TransitionImageState(); + State createState() => _TransitionImageState(); } class _TransitionImageState extends State { diff --git a/lib/widgets/common/grid/item_tracker.dart b/lib/widgets/common/grid/item_tracker.dart index 3bb375a9e..bceaf8dd8 100644 --- a/lib/widgets/common/grid/item_tracker.dart +++ b/lib/widgets/common/grid/item_tracker.dart @@ -26,7 +26,7 @@ class GridItemTracker extends StatefulWidget { }) : super(key: key); @override - _GridItemTrackerState createState() => _GridItemTrackerState(); + State> createState() => _GridItemTrackerState(); } class _GridItemTrackerState extends State> with WidgetsBindingObserver { diff --git a/lib/widgets/common/grid/scaling.dart b/lib/widgets/common/grid/scaling.dart index 15097ba46..7d99d31f2 100644 --- a/lib/widgets/common/grid/scaling.dart +++ b/lib/widgets/common/grid/scaling.dart @@ -42,7 +42,7 @@ class GridScaleGestureDetector extends StatefulWidget { }) : super(key: key); @override - _GridScaleGestureDetectorState createState() => _GridScaleGestureDetectorState(); + State> createState() => _GridScaleGestureDetectorState(); } class _GridScaleGestureDetectorState extends State> { @@ -235,7 +235,7 @@ class _ScaleOverlay extends StatefulWidget { }) : super(key: key); @override - _ScaleOverlayState createState() => _ScaleOverlayState(); + State<_ScaleOverlay> createState() => _ScaleOverlayState(); } class _ScaleOverlayState extends State<_ScaleOverlay> { diff --git a/lib/widgets/common/grid/selector.dart b/lib/widgets/common/grid/selector.dart index 05f41d58c..907166958 100644 --- a/lib/widgets/common/grid/selector.dart +++ b/lib/widgets/common/grid/selector.dart @@ -28,7 +28,7 @@ class GridSelectionGestureDetector extends StatefulWidget { }) : super(key: key); @override - _GridSelectionGestureDetectorState createState() => _GridSelectionGestureDetectorState(); + State> createState() => _GridSelectionGestureDetectorState(); } class _GridSelectionGestureDetectorState extends State> { diff --git a/lib/widgets/common/identity/aves_filter_chip.dart b/lib/widgets/common/identity/aves_filter_chip.dart index 7061b9f99..e362d4ce7 100644 --- a/lib/widgets/common/identity/aves_filter_chip.dart +++ b/lib/widgets/common/identity/aves_filter_chip.dart @@ -110,7 +110,7 @@ class AvesFilterChip extends StatefulWidget { } @override - _AvesFilterChipState createState() => _AvesFilterChipState(); + State createState() => _AvesFilterChipState(); } class _AvesFilterChipState extends State { diff --git a/lib/widgets/common/magnifier/core/core.dart b/lib/widgets/common/magnifier/core/core.dart index da4ad3c5e..495a857f6 100644 --- a/lib/widgets/common/magnifier/core/core.dart +++ b/lib/widgets/common/magnifier/core/core.dart @@ -19,6 +19,7 @@ class MagnifierCore extends StatefulWidget { final bool applyScale; final double panInertia; final MagnifierTapCallback? onTap; + final MagnifierDoubleTapCallback? onDoubleTap; final Widget child; const MagnifierCore({ @@ -27,7 +28,8 @@ class MagnifierCore extends StatefulWidget { required this.scaleStateCycle, required this.applyScale, this.panInertia = .2, - required this.onTap, + this.onTap, + this.onDoubleTap, required this.child, }) : super(key: key); @@ -204,6 +206,12 @@ class _MagnifierCoreState extends State with TickerProviderStateM void onDoubleTap(TapDownDetails details) { final viewportTapPosition = details.localPosition; + if (widget.onDoubleTap != null) { + final viewportSize = scaleBoundaries.viewportSize; + final alignment = Alignment(viewportTapPosition.dx / viewportSize.width, viewportTapPosition.dy / viewportSize.height); + if (widget.onDoubleTap?.call(alignment) == true) return; + } + final childTapPosition = scaleBoundaries.viewportToChildPosition(controller, viewportTapPosition); nextScaleState(ChangeSource.gesture, childFocalPoint: childTapPosition); } diff --git a/lib/widgets/common/magnifier/core/gesture_detector.dart b/lib/widgets/common/magnifier/core/gesture_detector.dart index 52794be27..507ae5252 100644 --- a/lib/widgets/common/magnifier/core/gesture_detector.dart +++ b/lib/widgets/common/magnifier/core/gesture_detector.dart @@ -31,7 +31,7 @@ class MagnifierGestureDetector extends StatefulWidget { final Widget? child; @override - _MagnifierGestureDetectorState createState() => _MagnifierGestureDetectorState(); + State createState() => _MagnifierGestureDetectorState(); } class _MagnifierGestureDetectorState extends State { diff --git a/lib/widgets/common/magnifier/magnifier.dart b/lib/widgets/common/magnifier/magnifier.dart index 36ab155cd..0661d557b 100644 --- a/lib/widgets/common/magnifier/magnifier.dart +++ b/lib/widgets/common/magnifier/magnifier.dart @@ -29,6 +29,7 @@ class Magnifier extends StatelessWidget { this.scaleStateCycle = defaultScaleStateCycle, this.applyScale = true, this.onTap, + this.onDoubleTap, required this.child, }) : super(key: key); @@ -49,6 +50,7 @@ class Magnifier extends StatelessWidget { final ScaleStateCycle scaleStateCycle; final bool applyScale; final MagnifierTapCallback? onTap; + final MagnifierDoubleTapCallback? onDoubleTap; final Widget child; @override @@ -68,6 +70,7 @@ class Magnifier extends StatelessWidget { scaleStateCycle: scaleStateCycle, applyScale: applyScale, onTap: onTap, + onDoubleTap: onDoubleTap, child: child, ); }, @@ -81,3 +84,7 @@ typedef MagnifierTapCallback = Function( MagnifierState state, Offset childTapPosition, ); + +typedef MagnifierDoubleTapCallback = bool Function( + Alignment alignment, +); diff --git a/lib/widgets/common/map/buttons.dart b/lib/widgets/common/map/buttons.dart index 48223c7cb..e470a0b96 100644 --- a/lib/widgets/common/map/buttons.dart +++ b/lib/widgets/common/map/buttons.dart @@ -17,7 +17,6 @@ import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:aves/widgets/viewer/info/notifications.dart'; import 'package:aves/widgets/viewer/overlay/common.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; @@ -139,21 +138,15 @@ class MapButtonPanel extends StatelessWidget { final availableStyles = EntryMapStyle.values.where((style) => !style.isGoogleMaps || canUseGoogleMaps); final preferredStyle = settings.infoMapStyle; final initialStyle = availableStyles.contains(preferredStyle) ? preferredStyle : availableStyles.first; - final style = await showDialog( + await showSelectionDialog( context: context, - builder: (context) { - return AvesSelectionDialog( - initialValue: initialStyle, - options: Map.fromEntries(availableStyles.map((v) => MapEntry(v, v.getName(context)))), - title: context.l10n.mapStyleTitle, - ); - }, + builder: (context) => AvesSelectionDialog( + initialValue: initialStyle, + options: Map.fromEntries(availableStyles.map((v) => MapEntry(v, v.getName(context)))), + title: context.l10n.mapStyleTitle, + ), + onSelection: (v) => settings.infoMapStyle = v, ); - // wait for the dialog to hide as applying the change may block the UI - await Future.delayed(Durations.dialogTransitionAnimation * timeDilation); - if (style != null && style != settings.infoMapStyle) { - settings.infoMapStyle = style; - } }, tooltip: context.l10n.mapStyleTooltip, ), @@ -248,7 +241,7 @@ class _OverlayCoordinateFilterChip extends StatefulWidget { }) : super(key: key); @override - _OverlayCoordinateFilterChipState createState() => _OverlayCoordinateFilterChipState(); + State<_OverlayCoordinateFilterChip> createState() => _OverlayCoordinateFilterChipState(); } class _OverlayCoordinateFilterChipState extends State<_OverlayCoordinateFilterChip> { @@ -313,7 +306,7 @@ class _OverlayCoordinateFilterChipState extends State<_OverlayCoordinateFilterCh ); return Padding( padding: EdgeInsets.all(widget.padding), - child: BlurredRRect( + child: BlurredRRect.all( enabled: blurred, borderRadius: AvesFilterChip.defaultRadius, child: AvesFilterChip( diff --git a/lib/widgets/common/map/geo_map.dart b/lib/widgets/common/map/geo_map.dart index b9862fdee..93202ab46 100644 --- a/lib/widgets/common/map/geo_map.dart +++ b/lib/widgets/common/map/geo_map.dart @@ -56,7 +56,7 @@ class GeoMap extends StatefulWidget { }) : super(key: key); @override - _GeoMapState createState() => _GeoMapState(); + State createState() => _GeoMapState(); } class _GeoMapState extends State { diff --git a/lib/widgets/common/map/google/marker_generator.dart b/lib/widgets/common/map/google/marker_generator.dart index ded1b979c..55f093b2f 100644 --- a/lib/widgets/common/map/google/marker_generator.dart +++ b/lib/widgets/common/map/google/marker_generator.dart @@ -21,7 +21,7 @@ class MarkerGeneratorWidget extends StatefulWidget { }) : super(key: key); @override - _MarkerGeneratorWidgetState createState() => _MarkerGeneratorWidgetState(); + State> createState() => _MarkerGeneratorWidgetState(); } class _MarkerGeneratorWidgetState extends State> { diff --git a/lib/widgets/common/thumbnail/error.dart b/lib/widgets/common/thumbnail/error.dart index 6ca741e0d..b00266454 100644 --- a/lib/widgets/common/thumbnail/error.dart +++ b/lib/widgets/common/thumbnail/error.dart @@ -18,7 +18,7 @@ class ErrorThumbnail extends StatefulWidget { }) : super(key: key); @override - _ErrorThumbnailState createState() => _ErrorThumbnailState(); + State createState() => _ErrorThumbnailState(); } class _ErrorThumbnailState extends State { diff --git a/lib/widgets/common/thumbnail/image.dart b/lib/widgets/common/thumbnail/image.dart index 0a6961070..f3ae1a39a 100644 --- a/lib/widgets/common/thumbnail/image.dart +++ b/lib/widgets/common/thumbnail/image.dart @@ -37,7 +37,7 @@ class ThumbnailImage extends StatefulWidget { }) : super(key: key); @override - _ThumbnailImageState createState() => _ThumbnailImageState(); + State createState() => _ThumbnailImageState(); } class _ThumbnailImageState extends State { diff --git a/lib/widgets/common/thumbnail/overlay.dart b/lib/widgets/common/thumbnail/overlay.dart index 39a4f60b9..7b1d2912e 100644 --- a/lib/widgets/common/thumbnail/overlay.dart +++ b/lib/widgets/common/thumbnail/overlay.dart @@ -58,7 +58,7 @@ class ThumbnailHighlightOverlay extends StatefulWidget { }) : super(key: key); @override - _ThumbnailHighlightOverlayState createState() => _ThumbnailHighlightOverlayState(); + State createState() => _ThumbnailHighlightOverlayState(); } class _ThumbnailHighlightOverlayState extends State { diff --git a/lib/widgets/common/thumbnail/scroller.dart b/lib/widgets/common/thumbnail/scroller.dart index 6b8e14a74..c0bf6e344 100644 --- a/lib/widgets/common/thumbnail/scroller.dart +++ b/lib/widgets/common/thumbnail/scroller.dart @@ -1,7 +1,9 @@ import 'dart:math'; import 'package:aves/model/entry.dart'; +import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/durations.dart'; +import 'package:aves/widgets/common/behaviour/known_extent_scroll_physics.dart'; import 'package:aves/widgets/common/grid/theme.dart'; import 'package:aves/widgets/common/thumbnail/decorated.dart'; import 'package:flutter/material.dart'; @@ -13,7 +15,7 @@ class ThumbnailScroller extends StatefulWidget { final ValueNotifier indexNotifier; final void Function(int index)? onTap; final Object? Function(AvesEntry entry)? heroTagger; - final bool highlightable; + final bool scrollable, highlightable, showLocation; const ThumbnailScroller({ Key? key, @@ -24,10 +26,12 @@ class ThumbnailScroller extends StatefulWidget { this.onTap, this.heroTagger, this.highlightable = false, + this.showLocation = true, + this.scrollable = true, }) : super(key: key); @override - _ThumbnailScrollerState createState() => _ThumbnailScrollerState(); + State createState() => _ThumbnailScrollerState(); } class _ThumbnailScrollerState extends State { @@ -35,13 +39,18 @@ class _ThumbnailScrollerState extends State { late ScrollController _scrollController; bool _isAnimating = false, _isScrolling = false; - static const double extent = 48; + static const double thumbnailExtent = 48; static const double separatorWidth = 2; + static const double itemExtent = thumbnailExtent + separatorWidth; int get entryCount => widget.entryCount; ValueNotifier get indexNotifier => widget.indexNotifier; + bool get scrollable => widget.scrollable; + + static double widthFor(int pageCount) => pageCount == 0 ? 0 : pageCount * thumbnailExtent + (pageCount - 1) * separatorWidth; + @override void initState() { super.initState(); @@ -79,71 +88,80 @@ class _ThumbnailScrollerState extends State { @override Widget build(BuildContext context) { - final marginWidth = max(0.0, (widget.availableWidth - extent) / 2 - separatorWidth); - final horizontalMargin = SizedBox(width: marginWidth); - const separator = SizedBox(width: separatorWidth); + final marginWidth = max(0.0, (widget.availableWidth - thumbnailExtent) / 2 - separatorWidth); + final padding = scrollable ? EdgeInsets.only(left: marginWidth + separatorWidth, right: marginWidth) : EdgeInsets.zero; return GridTheme( - extent: extent, - showLocation: false, + extent: thumbnailExtent, + showLocation: widget.showLocation && settings.showThumbnailLocation, showTrash: false, child: SizedBox( - height: extent, - child: ListView.separated( + width: scrollable ? null : widthFor(entryCount), + height: thumbnailExtent, + child: ListView.builder( scrollDirection: Axis.horizontal, controller: _scrollController, - // default padding in scroll direction matches `MediaQuery.viewPadding`, - // but we already accommodate for it, so make sure horizontal padding is 0 - padding: EdgeInsets.zero, - itemBuilder: (context, index) { - if (index == 0 || index == entryCount + 1) return horizontalMargin; - final page = index - 1; - final pageEntry = widget.entryBuilder(page); - if (pageEntry == null) return const SizedBox(); - - return Stack( - children: [ - GestureDetector( - onTap: () { - indexNotifier.value = page; - widget.onTap?.call(page); - }, - child: DecoratedThumbnail( - entry: pageEntry, - tileExtent: extent, - // the retrieval task queue can pile up for thumbnails of heavy pages - // (e.g. thumbnails of 15MP HEIF images inside 100MB+ HEIC containers) - // so we cancel these requests when possible - cancellableNotifier: _cancellableNotifier, - selectable: false, - highlightable: widget.highlightable, - heroTagger: () => widget.heroTagger?.call(pageEntry), - ), - ), - IgnorePointer( - child: ValueListenableBuilder( - valueListenable: indexNotifier, - builder: (context, currentIndex, child) { - return AnimatedContainer( - color: currentIndex == page ? Colors.transparent : Colors.black45, - width: extent, - height: extent, - duration: Durations.thumbnailScrollerShadeAnimation, - ); - }, - ), - ), - ], - ); - }, - separatorBuilder: (context, index) => separator, - itemCount: entryCount + 2, + // as of Flutter v2.10.2, `FixedExtentScrollController` can only be used with `ListWheelScrollView` + // and `FixedExtentScrollPhysics` can only be used with Scrollables that uses the `FixedExtentScrollController` + // so we use `KnownExtentScrollPhysics`, adapted from `FixedExtentScrollPhysics` without the constraints + physics: scrollable + ? KnownExtentScrollPhysics( + indexToScrollOffset: indexToScrollOffset, + scrollOffsetToIndex: scrollOffsetToIndex, + ) + : const NeverScrollableScrollPhysics(), + padding: padding, + itemExtent: itemExtent, + itemBuilder: (context, index) => _buildThumbnail(index), + itemCount: entryCount, ), ), ); } + Widget _buildThumbnail(int index) { + final pageEntry = widget.entryBuilder(index); + if (pageEntry == null) return const SizedBox(); + + return Stack( + children: [ + GestureDetector( + onTap: () { + indexNotifier.value = index; + widget.onTap?.call(index); + }, + child: DecoratedThumbnail( + entry: pageEntry, + tileExtent: thumbnailExtent, + // the retrieval task queue can pile up for thumbnails of heavy pages + // (e.g. thumbnails of 15MP HEIF images inside 100MB+ HEIC containers) + // so we cancel these requests when possible + cancellableNotifier: _cancellableNotifier, + selectable: false, + highlightable: widget.highlightable, + heroTagger: () => widget.heroTagger?.call(pageEntry), + ), + ), + IgnorePointer( + child: ValueListenableBuilder( + valueListenable: indexNotifier, + builder: (context, currentIndex, child) { + return AnimatedContainer( + color: currentIndex == index ? Colors.transparent : Colors.black45, + width: thumbnailExtent, + height: thumbnailExtent, + duration: Durations.thumbnailScrollerShadeAnimation, + ); + }, + ), + ), + ], + ); + } + Future _goTo(int index) async { + if (!scrollable) return; + final targetOffset = indexToScrollOffset(index); final offsetDelta = (targetOffset - _scrollController.offset).abs(); @@ -180,7 +198,7 @@ class _ThumbnailScrollerState extends State { _isScrolling = false; } - double indexToScrollOffset(int index) => index * (extent + separatorWidth); + double indexToScrollOffset(int index) => index * itemExtent; - int scrollOffsetToIndex(double offset) => (offset / (extent + separatorWidth)).round(); + int scrollOffsetToIndex(double offset) => (offset / itemExtent).round(); } diff --git a/lib/widgets/debug/android_apps.dart b/lib/widgets/debug/android_apps.dart index 25a89ef64..1aa035e91 100644 --- a/lib/widgets/debug/android_apps.dart +++ b/lib/widgets/debug/android_apps.dart @@ -11,7 +11,7 @@ class DebugAndroidAppSection extends StatefulWidget { const DebugAndroidAppSection({Key? key}) : super(key: key); @override - _DebugAndroidAppSectionState createState() => _DebugAndroidAppSectionState(); + State createState() => _DebugAndroidAppSectionState(); } class _DebugAndroidAppSectionState extends State with AutomaticKeepAliveClientMixin { diff --git a/lib/widgets/debug/android_codecs.dart b/lib/widgets/debug/android_codecs.dart index f9b1d8289..dc2c47c12 100644 --- a/lib/widgets/debug/android_codecs.dart +++ b/lib/widgets/debug/android_codecs.dart @@ -10,7 +10,7 @@ class DebugAndroidCodecSection extends StatefulWidget { const DebugAndroidCodecSection({Key? key}) : super(key: key); @override - _DebugAndroidCodecSectionState createState() => _DebugAndroidCodecSectionState(); + State createState() => _DebugAndroidCodecSectionState(); } class _DebugAndroidCodecSectionState extends State with AutomaticKeepAliveClientMixin { diff --git a/lib/widgets/debug/android_dirs.dart b/lib/widgets/debug/android_dirs.dart index ddf426495..5b5bd1a36 100644 --- a/lib/widgets/debug/android_dirs.dart +++ b/lib/widgets/debug/android_dirs.dart @@ -9,7 +9,7 @@ class DebugAndroidDirSection extends StatefulWidget { const DebugAndroidDirSection({Key? key}) : super(key: key); @override - _DebugAndroidDirSectionState createState() => _DebugAndroidDirSectionState(); + State createState() => _DebugAndroidDirSectionState(); } class _DebugAndroidDirSectionState extends State with AutomaticKeepAliveClientMixin { diff --git a/lib/widgets/debug/cache.dart b/lib/widgets/debug/cache.dart index 9dca26878..28bbcc23a 100644 --- a/lib/widgets/debug/cache.dart +++ b/lib/widgets/debug/cache.dart @@ -7,7 +7,7 @@ class DebugCacheSection extends StatefulWidget { const DebugCacheSection({Key? key}) : super(key: key); @override - _DebugCacheSectionState createState() => _DebugCacheSectionState(); + State createState() => _DebugCacheSectionState(); } class _DebugCacheSectionState extends State with AutomaticKeepAliveClientMixin { diff --git a/lib/widgets/debug/database.dart b/lib/widgets/debug/database.dart index f2bf3386b..60eaad3b6 100644 --- a/lib/widgets/debug/database.dart +++ b/lib/widgets/debug/database.dart @@ -14,7 +14,7 @@ class DebugAppDatabaseSection extends StatefulWidget { const DebugAppDatabaseSection({Key? key}) : super(key: key); @override - _DebugAppDatabaseSectionState createState() => _DebugAppDatabaseSectionState(); + State createState() => _DebugAppDatabaseSectionState(); } class _DebugAppDatabaseSectionState extends State with AutomaticKeepAliveClientMixin { diff --git a/lib/widgets/debug/media_store_scan_dialog.dart b/lib/widgets/debug/media_store_scan_dialog.dart index f211c652e..a63d07202 100644 --- a/lib/widgets/debug/media_store_scan_dialog.dart +++ b/lib/widgets/debug/media_store_scan_dialog.dart @@ -9,7 +9,7 @@ class MediaStoreScanDirDialog extends StatefulWidget { const MediaStoreScanDirDialog({Key? key}) : super(key: key); @override - _MediaStoreScanDirDialogState createState() => _MediaStoreScanDirDialogState(); + State createState() => _MediaStoreScanDirDialogState(); } class _MediaStoreScanDirDialogState extends State { diff --git a/lib/widgets/debug/settings.dart b/lib/widgets/debug/settings.dart index 81bded031..cb2c286a3 100644 --- a/lib/widgets/debug/settings.dart +++ b/lib/widgets/debug/settings.dart @@ -59,7 +59,6 @@ class DebugSettingsSection extends StatelessWidget { 'infoMapZoom': '${settings.infoMapZoom}', 'collectionSelectionQuickActions': '${settings.collectionSelectionQuickActions}', 'viewerQuickActions': '${settings.viewerQuickActions}', - 'videoQuickActions': '${settings.videoQuickActions}', 'drawerTypeBookmarks': toMultiline(settings.drawerTypeBookmarks), 'drawerAlbumBookmarks': toMultiline(settings.drawerAlbumBookmarks), 'drawerPageBookmarks': toMultiline(settings.drawerPageBookmarks), diff --git a/lib/widgets/debug/storage.dart b/lib/widgets/debug/storage.dart index 2dccf852d..e7c697590 100644 --- a/lib/widgets/debug/storage.dart +++ b/lib/widgets/debug/storage.dart @@ -9,7 +9,7 @@ class DebugStorageSection extends StatefulWidget { const DebugStorageSection({Key? key}) : super(key: key); @override - _DebugStorageSectionState createState() => _DebugStorageSectionState(); + State createState() => _DebugStorageSectionState(); } class _DebugStorageSectionState extends State with AutomaticKeepAliveClientMixin { diff --git a/lib/widgets/dialogs/add_shortcut_dialog.dart b/lib/widgets/dialogs/add_shortcut_dialog.dart index 42ce29bb7..e14416b85 100644 --- a/lib/widgets/dialogs/add_shortcut_dialog.dart +++ b/lib/widgets/dialogs/add_shortcut_dialog.dart @@ -23,7 +23,7 @@ class AddShortcutDialog extends StatefulWidget { }) : super(key: key); @override - _AddShortcutDialogState createState() => _AddShortcutDialogState(); + State createState() => _AddShortcutDialogState(); } class _AddShortcutDialogState extends State { diff --git a/lib/widgets/dialogs/aves_confirmation_dialog.dart b/lib/widgets/dialogs/aves_confirmation_dialog.dart index 6d51c812e..de287290a 100644 --- a/lib/widgets/dialogs/aves_confirmation_dialog.dart +++ b/lib/widgets/dialogs/aves_confirmation_dialog.dart @@ -36,7 +36,7 @@ class AvesConfirmationDialog extends StatefulWidget { }) : super(key: key); @override - _AvesConfirmationDialogState createState() => _AvesConfirmationDialogState(); + State createState() => _AvesConfirmationDialogState(); } class _AvesConfirmationDialogState extends State { diff --git a/lib/widgets/dialogs/aves_selection_dialog.dart b/lib/widgets/dialogs/aves_selection_dialog.dart index e6f70dda0..63bb05c34 100644 --- a/lib/widgets/dialogs/aves_selection_dialog.dart +++ b/lib/widgets/dialogs/aves_selection_dialog.dart @@ -1,8 +1,26 @@ +import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/basic/reselectable_radio_list_tile.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'aves_dialog.dart'; +Future showSelectionDialog({ + required BuildContext context, + required WidgetBuilder builder, + required void Function(T value) onSelection, +}) async { + final value = await showDialog( + context: context, + builder: builder, + ); + // wait for the dialog to hide as applying the change may block the UI + await Future.delayed(Durations.dialogTransitionAnimation * timeDilation); + if (value != null) { + onSelection(value); + } +} + typedef TextBuilder = String Function(T value); class AvesSelectionDialog extends StatefulWidget { @@ -10,6 +28,7 @@ class AvesSelectionDialog extends StatefulWidget { final Map options; final TextBuilder? optionSubtitleBuilder; final String? title, message, confirmationButtonLabel; + final bool? dense; const AvesSelectionDialog({ Key? key, @@ -19,10 +38,11 @@ class AvesSelectionDialog extends StatefulWidget { this.title, this.message, this.confirmationButtonLabel, + this.dense, }) : super(key: key); @override - _AvesSelectionDialogState createState() => _AvesSelectionDialogState(); + State> createState() => _AvesSelectionDialogState(); } class _AvesSelectionDialogState extends State> { @@ -92,6 +112,7 @@ class _AvesSelectionDialogState extends State> { maxLines: 1, ) : null, + dense: widget.dense, ); } } diff --git a/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart index 95145b337..28112801c 100644 --- a/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart @@ -21,7 +21,7 @@ class EditEntryDateDialog extends StatefulWidget { }) : super(key: key); @override - _EditEntryDateDialogState createState() => _EditEntryDateDialogState(); + State createState() => _EditEntryDateDialogState(); } class _EditEntryDateDialogState extends State { diff --git a/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart index 3f3e67c95..20520155e 100644 --- a/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart @@ -21,7 +21,7 @@ class EditEntryLocationDialog extends StatefulWidget { }) : super(key: key); @override - _EditEntryLocationDialogState createState() => _EditEntryLocationDialogState(); + State createState() => _EditEntryLocationDialogState(); } class _EditEntryLocationDialogState extends State { diff --git a/lib/widgets/dialogs/entry_editors/edit_rating_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_rating_dialog.dart index 5ab85c56f..275aab264 100644 --- a/lib/widgets/dialogs/entry_editors/edit_rating_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_rating_dialog.dart @@ -14,7 +14,7 @@ class EditEntryRatingDialog extends StatefulWidget { }) : super(key: key); @override - _EditEntryRatingDialogState createState() => _EditEntryRatingDialogState(); + State createState() => _EditEntryRatingDialogState(); } class _EditEntryRatingDialogState extends State { diff --git a/lib/widgets/dialogs/entry_editors/edit_tags_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_tags_dialog.dart index 49dbe5af8..09ee22b97 100644 --- a/lib/widgets/dialogs/entry_editors/edit_tags_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_tags_dialog.dart @@ -24,7 +24,7 @@ class TagEditorPage extends StatefulWidget { }) : super(key: key); @override - _TagEditorPageState createState() => _TagEditorPageState(); + State createState() => _TagEditorPageState(); } class _TagEditorPageState extends State { diff --git a/lib/widgets/dialogs/entry_editors/remove_metadata_dialog.dart b/lib/widgets/dialogs/entry_editors/remove_metadata_dialog.dart index 4967af2e4..c7aeb4e52 100644 --- a/lib/widgets/dialogs/entry_editors/remove_metadata_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/remove_metadata_dialog.dart @@ -20,7 +20,7 @@ class RemoveEntryMetadataDialog extends StatefulWidget { }) : super(key: key); @override - _RemoveEntryMetadataDialogState createState() => _RemoveEntryMetadataDialogState(); + State createState() => _RemoveEntryMetadataDialogState(); } class _RemoveEntryMetadataDialogState extends State { diff --git a/lib/widgets/dialogs/entry_editors/rename_dialog.dart b/lib/widgets/dialogs/entry_editors/rename_dialog.dart index f50b32e13..768d9f11a 100644 --- a/lib/widgets/dialogs/entry_editors/rename_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/rename_dialog.dart @@ -17,7 +17,7 @@ class RenameEntryDialog extends StatefulWidget { }) : super(key: key); @override - _RenameEntryDialogState createState() => _RenameEntryDialogState(); + State createState() => _RenameEntryDialogState(); } class _RenameEntryDialogState extends State { diff --git a/lib/widgets/dialogs/export_entry_dialog.dart b/lib/widgets/dialogs/export_entry_dialog.dart index d84b6c10d..8f5ded8e9 100644 --- a/lib/widgets/dialogs/export_entry_dialog.dart +++ b/lib/widgets/dialogs/export_entry_dialog.dart @@ -16,7 +16,7 @@ class ExportEntryDialog extends StatefulWidget { }) : super(key: key); @override - _ExportEntryDialogState createState() => _ExportEntryDialogState(); + State createState() => _ExportEntryDialogState(); } class _ExportEntryDialogState extends State { diff --git a/lib/widgets/dialogs/filter_editors/cover_selection_dialog.dart b/lib/widgets/dialogs/filter_editors/cover_selection_dialog.dart index 72f463e92..2b861a791 100644 --- a/lib/widgets/dialogs/filter_editors/cover_selection_dialog.dart +++ b/lib/widgets/dialogs/filter_editors/cover_selection_dialog.dart @@ -22,7 +22,7 @@ class CoverSelectionDialog extends StatefulWidget { }) : super(key: key); @override - _CoverSelectionDialogState createState() => _CoverSelectionDialogState(); + State createState() => _CoverSelectionDialogState(); } class _CoverSelectionDialogState extends State { diff --git a/lib/widgets/dialogs/filter_editors/create_album_dialog.dart b/lib/widgets/dialogs/filter_editors/create_album_dialog.dart index dba5b396e..e32a949c0 100644 --- a/lib/widgets/dialogs/filter_editors/create_album_dialog.dart +++ b/lib/widgets/dialogs/filter_editors/create_album_dialog.dart @@ -13,7 +13,7 @@ class CreateAlbumDialog extends StatefulWidget { const CreateAlbumDialog({Key? key}) : super(key: key); @override - _CreateAlbumDialogState createState() => _CreateAlbumDialogState(); + State createState() => _CreateAlbumDialogState(); } class _CreateAlbumDialogState extends State { diff --git a/lib/widgets/dialogs/filter_editors/rename_album_dialog.dart b/lib/widgets/dialogs/filter_editors/rename_album_dialog.dart index d13ff04f6..288cdaed5 100644 --- a/lib/widgets/dialogs/filter_editors/rename_album_dialog.dart +++ b/lib/widgets/dialogs/filter_editors/rename_album_dialog.dart @@ -14,7 +14,7 @@ class RenameAlbumDialog extends StatefulWidget { }) : super(key: key); @override - _RenameAlbumDialogState createState() => _RenameAlbumDialogState(); + State createState() => _RenameAlbumDialogState(); } class _RenameAlbumDialogState extends State { diff --git a/lib/widgets/dialogs/item_pick_dialog.dart b/lib/widgets/dialogs/item_pick_dialog.dart index 7623c05e1..184187a04 100644 --- a/lib/widgets/dialogs/item_pick_dialog.dart +++ b/lib/widgets/dialogs/item_pick_dialog.dart @@ -23,7 +23,7 @@ class ItemPickDialog extends StatefulWidget { }) : super(key: key); @override - _ItemPickDialogState createState() => _ItemPickDialogState(); + State createState() => _ItemPickDialogState(); } class _ItemPickDialogState extends State { diff --git a/lib/widgets/dialogs/location_pick_dialog.dart b/lib/widgets/dialogs/location_pick_dialog.dart index ce0cf1e65..00b3ede5d 100644 --- a/lib/widgets/dialogs/location_pick_dialog.dart +++ b/lib/widgets/dialogs/location_pick_dialog.dart @@ -66,7 +66,7 @@ class _Content extends StatefulWidget { }) : super(key: key); @override - _ContentState createState() => _ContentState(); + State<_Content> createState() => _ContentState(); } class _ContentState extends State<_Content> with SingleTickerProviderStateMixin { @@ -237,7 +237,7 @@ class _AddressRow extends StatefulWidget { }) : super(key: key); @override - _AddressRowState createState() => _AddressRowState(); + State<_AddressRow> createState() => _AddressRowState(); } class _AddressRowState extends State<_AddressRow> { diff --git a/lib/widgets/dialogs/tile_view_dialog.dart b/lib/widgets/dialogs/tile_view_dialog.dart index de3a26288..61c1a03c1 100644 --- a/lib/widgets/dialogs/tile_view_dialog.dart +++ b/lib/widgets/dialogs/tile_view_dialog.dart @@ -24,7 +24,7 @@ class TileViewDialog extends StatefulWidget { }) : super(key: key); @override - _TileViewDialogState createState() => _TileViewDialogState(); + State createState() => _TileViewDialogState(); } class _TileViewDialogState extends State> with SingleTickerProviderStateMixin { diff --git a/lib/widgets/dialogs/video_speed_dialog.dart b/lib/widgets/dialogs/video_speed_dialog.dart index 7aad16f24..a61a53d86 100644 --- a/lib/widgets/dialogs/video_speed_dialog.dart +++ b/lib/widgets/dialogs/video_speed_dialog.dart @@ -14,7 +14,7 @@ class VideoSpeedDialog extends StatefulWidget { }) : super(key: key); @override - _VideoSpeedDialogState createState() => _VideoSpeedDialogState(); + State createState() => _VideoSpeedDialogState(); } class _VideoSpeedDialogState extends State { diff --git a/lib/widgets/dialogs/video_stream_selection_dialog.dart b/lib/widgets/dialogs/video_stream_selection_dialog.dart index a841051d8..e9fa19a0f 100644 --- a/lib/widgets/dialogs/video_stream_selection_dialog.dart +++ b/lib/widgets/dialogs/video_stream_selection_dialog.dart @@ -17,7 +17,7 @@ class VideoStreamSelectionDialog extends StatefulWidget { }) : super(key: key); @override - _VideoStreamSelectionDialogState createState() => _VideoStreamSelectionDialogState(); + State createState() => _VideoStreamSelectionDialogState(); } class _VideoStreamSelectionDialogState extends State { diff --git a/lib/widgets/filter_grids/album_pick.dart b/lib/widgets/filter_grids/album_pick.dart index 00c404f5c..ccdcee07c 100644 --- a/lib/widgets/filter_grids/album_pick.dart +++ b/lib/widgets/filter_grids/album_pick.dart @@ -31,6 +31,10 @@ Future pickAlbum({ required MoveType? moveType, }) async { final source = context.read(); + if (source.initState != SourceInitializationState.full) { + // source may not be fully initialized in view mode + await source.init(); + } final filter = await Navigator.push( context, MaterialPageRoute( @@ -54,7 +58,7 @@ class _AlbumPickPage extends StatefulWidget { }) : super(key: key); @override - _AlbumPickPageState createState() => _AlbumPickPageState(); + State<_AlbumPickPage> createState() => _AlbumPickPageState(); } class _AlbumPickPageState extends State<_AlbumPickPage> { diff --git a/lib/widgets/filter_grids/common/app_bar.dart b/lib/widgets/filter_grids/common/app_bar.dart index 5023a70ac..858b0c7ac 100644 --- a/lib/widgets/filter_grids/common/app_bar.dart +++ b/lib/widgets/filter_grids/common/app_bar.dart @@ -31,7 +31,7 @@ class FilterGridAppBar extends StatefulWidget { }) : super(key: key); @override - _FilterGridAppBarState createState() => _FilterGridAppBarState(); + State> createState() => _FilterGridAppBarState(); } class _FilterGridAppBarState extends State> with SingleTickerProviderStateMixin { diff --git a/lib/widgets/filter_grids/common/filter_grid_page.dart b/lib/widgets/filter_grids/common/filter_grid_page.dart index 387513474..50679bac9 100644 --- a/lib/widgets/filter_grids/common/filter_grid_page.dart +++ b/lib/widgets/filter_grids/common/filter_grid_page.dart @@ -146,7 +146,7 @@ class FilterGrid extends StatefulWidget { }) : super(key: key); @override - _FilterGridState createState() => _FilterGridState(); + State> createState() => _FilterGridState(); } class _FilterGridState extends State> { @@ -338,7 +338,7 @@ class _FilterSectionedContent extends StatefulWidget }); @override - _FilterSectionedContentState createState() => _FilterSectionedContentState(); + State<_FilterSectionedContent> createState() => _FilterSectionedContentState(); } class _FilterSectionedContentState extends State<_FilterSectionedContent> { diff --git a/lib/widgets/filter_grids/common/filter_tile.dart b/lib/widgets/filter_grids/common/filter_tile.dart index f175a3101..b7850d69a 100644 --- a/lib/widgets/filter_grids/common/filter_tile.dart +++ b/lib/widgets/filter_grids/common/filter_tile.dart @@ -33,7 +33,7 @@ class InteractiveFilterTile extends StatefulWidget { }) : super(key: key); @override - _InteractiveFilterTileState createState() => _InteractiveFilterTileState(); + State> createState() => _InteractiveFilterTileState(); } class _InteractiveFilterTileState extends State> { diff --git a/lib/widgets/filter_grids/common/overlay.dart b/lib/widgets/filter_grids/common/overlay.dart index c72362c8e..48651e9a7 100644 --- a/lib/widgets/filter_grids/common/overlay.dart +++ b/lib/widgets/filter_grids/common/overlay.dart @@ -19,7 +19,7 @@ class ChipHighlightOverlay extends StatefulWidget { }) : super(key: key); @override - _ChipHighlightOverlayState createState() => _ChipHighlightOverlayState(); + State createState() => _ChipHighlightOverlayState(); } class _ChipHighlightOverlayState extends State { diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index a94900d38..f1ccbce77 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -39,7 +39,7 @@ class HomePage extends StatefulWidget { }) : super(key: key); @override - _HomePageState createState() => _HomePageState(); + State createState() => _HomePageState(); } class _HomePageState extends State { diff --git a/lib/widgets/map/map_info_row.dart b/lib/widgets/map/map_info_row.dart index 093acf11a..9670d26b9 100644 --- a/lib/widgets/map/map_info_row.dart +++ b/lib/widgets/map/map_info_row.dart @@ -77,7 +77,7 @@ class _AddressRow extends StatefulWidget { }) : super(key: key); @override - _AddressRowState createState() => _AddressRowState(); + State<_AddressRow> createState() => _AddressRowState(); } class _AddressRowState extends State<_AddressRow> { diff --git a/lib/widgets/map/map_page.dart b/lib/widgets/map/map_page.dart index 29a2db70d..4dfeb4a2f 100644 --- a/lib/widgets/map/map_page.dart +++ b/lib/widgets/map/map_page.dart @@ -78,7 +78,7 @@ class _Content extends StatefulWidget { }) : super(key: key); @override - _ContentState createState() => _ContentState(); + State<_Content> createState() => _ContentState(); } class _ContentState extends State<_Content> with SingleTickerProviderStateMixin { @@ -270,6 +270,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin onTap: _onThumbnailTap, heroTagger: (entry) => Object.hashAll([regionCollection?.id, entry.id]), highlightable: true, + showLocation: false, ); }, ); @@ -301,7 +302,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin if (regionCollection != null) { final regionEntries = regionCollection!.sortedEntries; final selectedIndex = _selectedIndexNotifier.value; - selectedEntry = selectedIndex != null && selectedIndex < regionEntries.length ? regionEntries[selectedIndex] : null; + selectedEntry = selectedIndex != null && 0 <= selectedIndex && selectedIndex < regionEntries.length ? regionEntries[selectedIndex] : null; } _regionCollectionNotifier.value = openingCollection.copyWith( diff --git a/lib/widgets/search/search_page.dart b/lib/widgets/search/search_page.dart index 26029a30a..fc730e171 100644 --- a/lib/widgets/search/search_page.dart +++ b/lib/widgets/search/search_page.dart @@ -17,7 +17,7 @@ class SearchPage extends StatefulWidget { }) : super(key: key); @override - _SearchPageState createState() => _SearchPageState(); + State createState() => _SearchPageState(); } class _SearchPageState extends State { diff --git a/lib/widgets/settings/accessibility/remove_animations.dart b/lib/widgets/settings/accessibility/remove_animations.dart index bf3095cef..62ea40095 100644 --- a/lib/widgets/settings/accessibility/remove_animations.dart +++ b/lib/widgets/settings/accessibility/remove_animations.dart @@ -1,11 +1,9 @@ import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; class RemoveAnimationsTile extends StatelessWidget { @@ -18,21 +16,15 @@ class RemoveAnimationsTile extends StatelessWidget { return ListTile( title: Text(context.l10n.settingsRemoveAnimationsTile), subtitle: Text(currentAnimations.getName(context)), - onTap: () async { - final value = await showDialog( - context: context, - builder: (context) => AvesSelectionDialog( - initialValue: currentAnimations, - options: Map.fromEntries(AccessibilityAnimations.values.map((v) => MapEntry(v, v.getName(context)))), - title: context.l10n.settingsRemoveAnimationsTitle, - ), - ); - // wait for the dialog to hide as applying the change may block the UI - await Future.delayed(Durations.dialogTransitionAnimation * timeDilation); - if (value != null) { - settings.accessibilityAnimations = value; - } - }, + onTap: () => showSelectionDialog( + context: context, + builder: (context) => AvesSelectionDialog( + initialValue: currentAnimations, + options: Map.fromEntries(AccessibilityAnimations.values.map((v) => MapEntry(v, v.getName(context)))), + title: context.l10n.settingsRemoveAnimationsTitle, + ), + onSelection: (v) => settings.accessibilityAnimations = v, + ), ); } } diff --git a/lib/widgets/settings/accessibility/time_to_take_action.dart b/lib/widgets/settings/accessibility/time_to_take_action.dart index ced0e082d..d1777b134 100644 --- a/lib/widgets/settings/accessibility/time_to_take_action.dart +++ b/lib/widgets/settings/accessibility/time_to_take_action.dart @@ -11,7 +11,7 @@ class TimeToTakeActionTile extends StatefulWidget { const TimeToTakeActionTile({Key? key}) : super(key: key); @override - _TimeToTakeActionTileState createState() => _TimeToTakeActionTileState(); + State createState() => _TimeToTakeActionTileState(); } class _TimeToTakeActionTileState extends State { @@ -36,19 +36,15 @@ class _TimeToTakeActionTileState extends State { return ListTile( title: Text(context.l10n.settingsTimeToTakeActionTile), subtitle: Text(currentTimeToTakeAction.getName(context)), - onTap: () async { - final value = await showDialog( - context: context, - builder: (context) => AvesSelectionDialog( - initialValue: currentTimeToTakeAction, - options: Map.fromEntries(optionValues.map((v) => MapEntry(v, v.getName(context)))), - title: context.l10n.settingsTimeToTakeActionTitle, - ), - ); - if (value != null) { - settings.timeToTakeAction = value; - } - }, + onTap: () => showSelectionDialog( + context: context, + builder: (context) => AvesSelectionDialog( + initialValue: currentTimeToTakeAction, + options: Map.fromEntries(optionValues.map((v) => MapEntry(v, v.getName(context)))), + title: context.l10n.settingsTimeToTakeActionTitle, + ), + onSelection: (v) => settings.timeToTakeAction = v, + ), ); }, ); diff --git a/lib/widgets/settings/app_export/selection_dialog.dart b/lib/widgets/settings/app_export/selection_dialog.dart index a02d0a311..34cea2020 100644 --- a/lib/widgets/settings/app_export/selection_dialog.dart +++ b/lib/widgets/settings/app_export/selection_dialog.dart @@ -15,7 +15,7 @@ class AppExportItemSelectionDialog extends StatefulWidget { }) : super(key: key); @override - _AppExportItemSelectionDialogState createState() => _AppExportItemSelectionDialogState(); + State createState() => _AppExportItemSelectionDialogState(); } class _AppExportItemSelectionDialogState extends State { diff --git a/lib/widgets/settings/common/quick_actions/editor_page.dart b/lib/widgets/settings/common/quick_actions/editor_page.dart index 874441321..a5cf517ac 100644 --- a/lib/widgets/settings/common/quick_actions/editor_page.dart +++ b/lib/widgets/settings/common/quick_actions/editor_page.dart @@ -75,7 +75,7 @@ class QuickActionEditorBody extends StatefulWidget { }) : super(key: key); @override - _QuickActionEditorBodyState createState() => _QuickActionEditorBodyState(); + State> createState() => _QuickActionEditorBodyState(); } class _QuickActionEditorBodyState extends State> with AutomaticKeepAliveClientMixin { diff --git a/lib/widgets/settings/language/language.dart b/lib/widgets/settings/language/language.dart index b6f72e5e4..adcd05e2d 100644 --- a/lib/widgets/settings/language/language.dart +++ b/lib/widgets/settings/language/language.dart @@ -45,37 +45,29 @@ class LanguageSection extends StatelessWidget { ListTile( title: Text(l10n.settingsCoordinateFormatTile), subtitle: Text(currentCoordinateFormat.getName(context)), - onTap: () async { - final value = await showDialog( - context: context, - builder: (context) => AvesSelectionDialog( - initialValue: currentCoordinateFormat, - options: Map.fromEntries(CoordinateFormat.values.map((v) => MapEntry(v, v.getName(context)))), - optionSubtitleBuilder: (value) => value.format(l10n, Constants.pointNemo), - title: l10n.settingsCoordinateFormatTitle, - ), - ); - if (value != null) { - settings.coordinateFormat = value; - } - }, + onTap: () => showSelectionDialog( + context: context, + builder: (context) => AvesSelectionDialog( + initialValue: currentCoordinateFormat, + options: Map.fromEntries(CoordinateFormat.values.map((v) => MapEntry(v, v.getName(context)))), + optionSubtitleBuilder: (value) => value.format(l10n, Constants.pointNemo), + title: l10n.settingsCoordinateFormatTitle, + ), + onSelection: (v) => settings.coordinateFormat = v, + ), ), ListTile( title: Text(l10n.settingsUnitSystemTile), subtitle: Text(currentUnitSystem.getName(context)), - onTap: () async { - final value = await showDialog( - context: context, - builder: (context) => AvesSelectionDialog( - initialValue: currentUnitSystem, - options: Map.fromEntries(UnitSystem.values.map((v) => MapEntry(v, v.getName(context)))), - title: l10n.settingsUnitSystemTitle, - ), - ); - if (value != null) { - settings.unitSystem = value; - } - }, + onTap: () => showSelectionDialog( + context: context, + builder: (context) => AvesSelectionDialog( + initialValue: currentUnitSystem, + options: Map.fromEntries(UnitSystem.values.map((v) => MapEntry(v, v.getName(context)))), + title: l10n.settingsUnitSystemTitle, + ), + onSelection: (v) => settings.unitSystem = v, + ), ), ], ); diff --git a/lib/widgets/settings/language/locale.dart b/lib/widgets/settings/language/locale.dart index 1279b1b24..0123e9323 100644 --- a/lib/widgets/settings/language/locale.dart +++ b/lib/widgets/settings/language/locale.dart @@ -2,13 +2,11 @@ import 'dart:collection'; import 'package:aves/l10n/l10n.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:aves/widgets/settings/language/locales.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; class LocaleTile extends StatelessWidget { @@ -28,21 +26,16 @@ class LocaleTile extends StatelessWidget { return Text(locale == null ? context.l10n.settingsSystemDefault : _getLocaleName(locale)); }, ), - onTap: () async { - final value = await showDialog( - context: context, - builder: (context) => AvesSelectionDialog( - initialValue: settings.locale ?? _systemLocaleOption, - options: _getLocaleOptions(context), - title: context.l10n.settingsLanguage, - ), - ); - // wait for the dialog to hide as applying the change may block the UI - await Future.delayed(Durations.dialogTransitionAnimation * timeDilation); - if (value != null) { - settings.locale = value == _systemLocaleOption ? null : value; - } - }, + onTap: () => showSelectionDialog( + context: context, + builder: (context) => AvesSelectionDialog( + initialValue: settings.locale ?? _systemLocaleOption, + options: _getLocaleOptions(context), + title: context.l10n.settingsLanguage, + dense: true, + ), + onSelection: (v) => settings.locale = v == _systemLocaleOption ? null : v, + ), ); } diff --git a/lib/widgets/settings/language/locales.dart b/lib/widgets/settings/language/locales.dart index 29d60600a..9018db893 100644 --- a/lib/widgets/settings/language/locales.dart +++ b/lib/widgets/settings/language/locales.dart @@ -7,6 +7,7 @@ class SupportedLocales { 'es': 'Español (México)', 'fr': 'Français', 'id': 'Bahasa Indonesia', + 'ja': '日本語', 'ko': '한국어', 'pt': 'Português (Brasil)', 'ru': 'Русский', diff --git a/lib/widgets/settings/navigation/drawer.dart b/lib/widgets/settings/navigation/drawer.dart index 870852196..bd7adb4e2 100644 --- a/lib/widgets/settings/navigation/drawer.dart +++ b/lib/widgets/settings/navigation/drawer.dart @@ -39,7 +39,7 @@ class NavigationDrawerEditorPage extends StatefulWidget { const NavigationDrawerEditorPage({Key? key}) : super(key: key); @override - _NavigationDrawerEditorPageState createState() => _NavigationDrawerEditorPageState(); + State createState() => _NavigationDrawerEditorPageState(); } class _NavigationDrawerEditorPageState extends State { diff --git a/lib/widgets/settings/navigation/drawer_tab_albums.dart b/lib/widgets/settings/navigation/drawer_tab_albums.dart index d76bd2aa4..187d542f8 100644 --- a/lib/widgets/settings/navigation/drawer_tab_albums.dart +++ b/lib/widgets/settings/navigation/drawer_tab_albums.dart @@ -18,7 +18,7 @@ class DrawerAlbumTab extends StatefulWidget { }) : super(key: key); @override - _DrawerAlbumTabState createState() => _DrawerAlbumTabState(); + State createState() => _DrawerAlbumTabState(); } class _DrawerAlbumTabState extends State { diff --git a/lib/widgets/settings/navigation/drawer_tab_fixed.dart b/lib/widgets/settings/navigation/drawer_tab_fixed.dart index 3bef027dd..abc9f5110 100644 --- a/lib/widgets/settings/navigation/drawer_tab_fixed.dart +++ b/lib/widgets/settings/navigation/drawer_tab_fixed.dart @@ -20,7 +20,7 @@ class DrawerFixedListTab extends StatefulWidget { }) : super(key: key); @override - _DrawerFixedListTabState createState() => _DrawerFixedListTabState(); + State> createState() => _DrawerFixedListTabState(); } class _DrawerFixedListTabState extends State> { diff --git a/lib/widgets/settings/navigation/navigation.dart b/lib/widgets/settings/navigation/navigation.dart index a7466c80c..0737957ca 100644 --- a/lib/widgets/settings/navigation/navigation.dart +++ b/lib/widgets/settings/navigation/navigation.dart @@ -39,38 +39,30 @@ class NavigationSection extends StatelessWidget { ListTile( title: Text(context.l10n.settingsHome), subtitle: Text(currentHomePage.getName(context)), - onTap: () async { - final value = await showDialog( - context: context, - builder: (context) => AvesSelectionDialog( - initialValue: currentHomePage, - options: Map.fromEntries(HomePageSetting.values.map((v) => MapEntry(v, v.getName(context)))), - title: context.l10n.settingsHome, - ), - ); - if (value != null) { - settings.homePage = value; - } - }, + onTap: () => showSelectionDialog( + context: context, + builder: (context) => AvesSelectionDialog( + initialValue: currentHomePage, + options: Map.fromEntries(HomePageSetting.values.map((v) => MapEntry(v, v.getName(context)))), + title: context.l10n.settingsHome, + ), + onSelection: (v) => settings.homePage = v, + ), ), const NavigationDrawerTile(), const ConfirmationDialogTile(), ListTile( title: Text(context.l10n.settingsKeepScreenOnTile), subtitle: Text(currentKeepScreenOn.getName(context)), - onTap: () async { - final value = await showDialog( - context: context, - builder: (context) => AvesSelectionDialog( - initialValue: currentKeepScreenOn, - options: Map.fromEntries(KeepScreenOn.values.map((v) => MapEntry(v, v.getName(context)))), - title: context.l10n.settingsKeepScreenOnTitle, - ), - ); - if (value != null) { - settings.keepScreenOn = value; - } - }, + onTap: () => showSelectionDialog( + context: context, + builder: (context) => AvesSelectionDialog( + initialValue: currentKeepScreenOn, + options: Map.fromEntries(KeepScreenOn.values.map((v) => MapEntry(v, v.getName(context)))), + title: context.l10n.settingsKeepScreenOnTitle, + ), + onSelection: (v) => settings.keepScreenOn = v, + ), ), SwitchListTile( value: currentMustBackTwiceToExit, diff --git a/lib/widgets/settings/privacy/access_grants.dart b/lib/widgets/settings/privacy/access_grants.dart index 2c08f9ada..7355c4175 100644 --- a/lib/widgets/settings/privacy/access_grants.dart +++ b/lib/widgets/settings/privacy/access_grants.dart @@ -31,7 +31,7 @@ class StorageAccessPage extends StatefulWidget { const StorageAccessPage({Key? key}) : super(key: key); @override - _StorageAccessPageState createState() => _StorageAccessPageState(); + State createState() => _StorageAccessPageState(); } class _StorageAccessPageState extends State { diff --git a/lib/widgets/settings/privacy/file_picker/crumb_line.dart b/lib/widgets/settings/privacy/file_picker/crumb_line.dart index 83da70732..6a1c79e8a 100644 --- a/lib/widgets/settings/privacy/file_picker/crumb_line.dart +++ b/lib/widgets/settings/privacy/file_picker/crumb_line.dart @@ -14,7 +14,7 @@ class CrumbLine extends StatefulWidget { }) : super(key: key); @override - _CrumbLineState createState() => _CrumbLineState(); + State createState() => _CrumbLineState(); } class _CrumbLineState extends State { diff --git a/lib/widgets/settings/privacy/file_picker/file_picker.dart b/lib/widgets/settings/privacy/file_picker/file_picker.dart index f8695b482..dd2ef7347 100644 --- a/lib/widgets/settings/privacy/file_picker/file_picker.dart +++ b/lib/widgets/settings/privacy/file_picker/file_picker.dart @@ -23,7 +23,7 @@ class FilePicker extends StatefulWidget { const FilePicker({Key? key}) : super(key: key); @override - _FilePickerState createState() => _FilePickerState(); + State createState() => _FilePickerState(); } class _FilePickerState extends State { diff --git a/lib/widgets/settings/settings_page.dart b/lib/widgets/settings/settings_page.dart index e5d1b610b..0e217194d 100644 --- a/lib/widgets/settings/settings_page.dart +++ b/lib/widgets/settings/settings_page.dart @@ -33,7 +33,7 @@ class SettingsPage extends StatefulWidget { const SettingsPage({Key? key}) : super(key: key); @override - _SettingsPageState createState() => _SettingsPageState(); + State createState() => _SettingsPageState(); } class _SettingsPageState extends State with FeedbackMixin { diff --git a/lib/widgets/settings/video/controls.dart b/lib/widgets/settings/video/controls.dart new file mode 100644 index 000000000..895148960 --- /dev/null +++ b/lib/widgets/settings/video/controls.dart @@ -0,0 +1,80 @@ +import 'package:aves/model/settings/enums/enums.dart'; +import 'package:aves/model/settings/enums/video_controls.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class VideoControlsTile extends StatelessWidget { + const VideoControlsTile({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(context.l10n.settingsVideoControlsTile), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + settings: const RouteSettings(name: VideoControlsPage.routeName), + builder: (context) => const VideoControlsPage(), + ), + ); + }, + ); + } +} + +class VideoControlsPage extends StatelessWidget { + static const routeName = '/settings/video/controls'; + + const VideoControlsPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(context.l10n.settingsVideoControlsTitle), + ), + body: SafeArea( + child: ListView( + children: [ + Selector( + selector: (context, s) => s.videoControls, + builder: (context, current, child) => ListTile( + title: Text(context.l10n.settingsVideoButtonsTile), + subtitle: Text(current.getName(context)), + onTap: () => showSelectionDialog( + context: context, + builder: (context) => AvesSelectionDialog( + initialValue: current, + options: Map.fromEntries(VideoControls.values.map((v) => MapEntry(v, v.getName(context)))), + title: context.l10n.settingsVideoButtonsTitle, + ), + onSelection: (v) => settings.videoControls = v, + ), + ), + ), + Selector( + selector: (context, s) => s.videoGestureDoubleTapTogglePlay, + builder: (context, current, child) => SwitchListTile( + value: current, + onChanged: (v) => settings.videoGestureDoubleTapTogglePlay = v, + title: Text(context.l10n.settingsVideoGestureDoubleTapTogglePlay), + ), + ), + Selector( + selector: (context, s) => s.videoGestureSideDoubleTapSeek, + builder: (context, current, child) => SwitchListTile( + value: current, + onChanged: (v) => settings.videoGestureSideDoubleTapSeek = v, + title: Text(context.l10n.settingsVideoGestureSideDoubleTapSeek), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/settings/video/subtitle_theme.dart b/lib/widgets/settings/video/subtitle_theme.dart index 072e14447..31f2b6ba7 100644 --- a/lib/widgets/settings/video/subtitle_theme.dart +++ b/lib/widgets/settings/video/subtitle_theme.dart @@ -28,7 +28,7 @@ class SubtitleThemeTile extends StatelessWidget { } class SubtitleThemePage extends StatelessWidget { - static const routeName = '/settings/subtitle_theme'; + static const routeName = '/settings/video/subtitle_theme'; static const textAlignOptions = [TextAlign.left, TextAlign.center, TextAlign.right]; @@ -57,19 +57,15 @@ class SubtitleThemePage extends StatelessWidget { ListTile( title: Text(context.l10n.settingsSubtitleThemeTextAlignmentTile), subtitle: Text(_getTextAlignName(context, settings.subtitleTextAlignment)), - onTap: () async { - final value = await showDialog( - context: context, - builder: (context) => AvesSelectionDialog( - initialValue: settings.subtitleTextAlignment, - options: Map.fromEntries(textAlignOptions.map((v) => MapEntry(v, _getTextAlignName(context, v)))), - title: context.l10n.settingsSubtitleThemeTextAlignmentTitle, - ), - ); - if (value != null) { - settings.subtitleTextAlignment = value; - } - }, + onTap: () => showSelectionDialog( + context: context, + builder: (context) => AvesSelectionDialog( + initialValue: settings.subtitleTextAlignment, + options: Map.fromEntries(textAlignOptions.map((v) => MapEntry(v, _getTextAlignName(context, v)))), + title: context.l10n.settingsSubtitleThemeTextAlignmentTitle, + ), + onSelection: (v) => settings.subtitleTextAlignment = v, + ), ), SliderListTile( title: context.l10n.settingsSubtitleThemeTextSize, diff --git a/lib/widgets/settings/video/video.dart b/lib/widgets/settings/video/video.dart index 800ac3def..9162582e3 100644 --- a/lib/widgets/settings/video/video.dart +++ b/lib/widgets/settings/video/video.dart @@ -9,8 +9,8 @@ import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:aves/widgets/settings/common/tile_leading.dart'; +import 'package:aves/widgets/settings/video/controls.dart'; import 'package:aves/widgets/settings/video/subtitle_theme.dart'; -import 'package:aves/widgets/settings/video/video_actions_editor.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -26,46 +26,49 @@ class VideoSection extends StatelessWidget { @override Widget build(BuildContext context) { - final currentShowVideos = context.select((s) => !s.hiddenFilters.contains(MimeFilter.video)); - final currentEnableVideoHardwareAcceleration = context.select((s) => s.enableVideoHardwareAcceleration); - final currentEnableVideoAutoPlay = context.select((s) => s.enableVideoAutoPlay); - final currentVideoLoopMode = context.select((s) => s.videoLoopMode); - final children = [ if (!standalonePage) - SwitchListTile( - value: currentShowVideos, - onChanged: (v) => settings.changeFilterVisibility({MimeFilter.video}, v), - title: Text(context.l10n.settingsVideoShowVideos), + Selector( + selector: (context, s) => !s.hiddenFilters.contains(MimeFilter.video), + builder: (context, current, child) => SwitchListTile( + value: current, + onChanged: (v) => settings.changeFilterVisibility({MimeFilter.video}, v), + title: Text(context.l10n.settingsVideoShowVideos), + ), + ), + Selector( + selector: (context, s) => s.enableVideoHardwareAcceleration, + builder: (context, current, child) => SwitchListTile( + value: current, + onChanged: (v) => settings.enableVideoHardwareAcceleration = v, + title: Text(context.l10n.settingsVideoEnableHardwareAcceleration), ), - const VideoActionsTile(), - SwitchListTile( - value: currentEnableVideoHardwareAcceleration, - onChanged: (v) => settings.enableVideoHardwareAcceleration = v, - title: Text(context.l10n.settingsVideoEnableHardwareAcceleration), ), - SwitchListTile( - value: currentEnableVideoAutoPlay, - onChanged: (v) => settings.enableVideoAutoPlay = v, - title: Text(context.l10n.settingsVideoEnableAutoPlay), + Selector( + selector: (context, s) => s.enableVideoAutoPlay, + builder: (context, current, child) => SwitchListTile( + value: current, + onChanged: (v) => settings.enableVideoAutoPlay = v, + title: Text(context.l10n.settingsVideoEnableAutoPlay), + ), ), - ListTile( - title: Text(context.l10n.settingsVideoLoopModeTile), - subtitle: Text(currentVideoLoopMode.getName(context)), - onTap: () async { - final value = await showDialog( + Selector( + selector: (context, s) => s.videoLoopMode, + builder: (context, current, child) => ListTile( + title: Text(context.l10n.settingsVideoLoopModeTile), + subtitle: Text(current.getName(context)), + onTap: () => showSelectionDialog( context: context, builder: (context) => AvesSelectionDialog( - initialValue: currentVideoLoopMode, + initialValue: current, options: Map.fromEntries(VideoLoopMode.values.map((v) => MapEntry(v, v.getName(context)))), title: context.l10n.settingsVideoLoopModeTitle, ), - ); - if (value != null) { - settings.videoLoopMode = value; - } - }, + onSelection: (v) => settings.videoLoopMode = v, + ), + ), ), + const VideoControlsTile(), const SubtitleThemeTile(), ]; diff --git a/lib/widgets/settings/video/video_actions_editor.dart b/lib/widgets/settings/video/video_actions_editor.dart deleted file mode 100644 index f52cd5ab4..000000000 --- a/lib/widgets/settings/video/video_actions_editor.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:aves/model/actions/video_actions.dart'; -import 'package:aves/model/settings/settings.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:aves/widgets/settings/common/quick_actions/editor_page.dart'; -import 'package:flutter/material.dart'; - -class VideoActionsTile extends StatelessWidget { - const VideoActionsTile({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ListTile( - title: Text(context.l10n.settingsVideoQuickActionsTile), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - settings: const RouteSettings(name: VideoActionEditorPage.routeName), - builder: (context) => const VideoActionEditorPage(), - ), - ); - }, - ); - } -} - -class VideoActionEditorPage extends StatelessWidget { - static const routeName = '/settings/video_actions'; - - const VideoActionEditorPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return QuickActionEditorPage( - title: context.l10n.settingsVideoQuickActionEditorTitle, - bannerText: context.l10n.settingsViewerQuickActionEditorBanner, - allAvailableActions: VideoActions.all, - actionIcon: (action) => action.getIcon(), - actionText: (context, action) => action.getText(context), - load: () => settings.videoQuickActions, - save: (actions) => settings.videoQuickActions = actions, - ); - } -} diff --git a/lib/widgets/settings/viewer/entry_background.dart b/lib/widgets/settings/viewer/entry_background.dart index 2786129bd..64fdf6b28 100644 --- a/lib/widgets/settings/viewer/entry_background.dart +++ b/lib/widgets/settings/viewer/entry_background.dart @@ -15,7 +15,7 @@ class EntryBackgroundSelector extends StatefulWidget { }) : super(key: key); @override - _EntryBackgroundSelectorState createState() => _EntryBackgroundSelectorState(); + State createState() => _EntryBackgroundSelectorState(); } class _EntryBackgroundSelectorState extends State { diff --git a/lib/widgets/settings/viewer/overlay.dart b/lib/widgets/settings/viewer/overlay.dart index 38c85808f..d85832c83 100644 --- a/lib/widgets/settings/viewer/overlay.dart +++ b/lib/widgets/settings/viewer/overlay.dart @@ -46,14 +46,6 @@ class ViewerOverlayPage extends StatelessWidget { title: Text(context.l10n.settingsViewerShowOverlayOnOpening), ), ), - Selector( - selector: (context, s) => s.showOverlayMinimap, - builder: (context, current, child) => SwitchListTile( - value: current, - onChanged: (v) => settings.showOverlayMinimap = v, - title: Text(context.l10n.settingsViewerShowMinimap), - ), - ), Selector( selector: (context, s) => s.showOverlayInfo, builder: (context, current, child) => SwitchListTile( @@ -75,6 +67,22 @@ class ViewerOverlayPage extends StatelessWidget { ); }, ), + Selector( + selector: (context, s) => s.showOverlayMinimap, + builder: (context, current, child) => SwitchListTile( + value: current, + onChanged: (v) => settings.showOverlayMinimap = v, + title: Text(context.l10n.settingsViewerShowMinimap), + ), + ), + Selector( + selector: (context, s) => s.showOverlayThumbnailPreview, + builder: (context, current, child) => SwitchListTile( + value: current, + onChanged: (v) => settings.showOverlayThumbnailPreview = v, + title: Text(context.l10n.settingsViewerShowOverlayThumbnails), + ), + ), Selector( selector: (context, s) => s.enableOverlayBlurEffect, builder: (context, current, child) => SwitchListTile( diff --git a/lib/widgets/settings/viewer/viewer.dart b/lib/widgets/settings/viewer/viewer.dart index 3b684621a..794d64bd5 100644 --- a/lib/widgets/settings/viewer/viewer.dart +++ b/lib/widgets/settings/viewer/viewer.dart @@ -69,7 +69,7 @@ class _CutoutModeSwitch extends StatefulWidget { const _CutoutModeSwitch({Key? key}) : super(key: key); @override - _CutoutModeSwitchState createState() => _CutoutModeSwitchState(); + State<_CutoutModeSwitch> createState() => _CutoutModeSwitchState(); } class _CutoutModeSwitchState extends State<_CutoutModeSwitch> { diff --git a/lib/widgets/settings/viewer/viewer_actions_editor.dart b/lib/widgets/settings/viewer/viewer_actions_editor.dart index 88b1859d6..453a58f96 100644 --- a/lib/widgets/settings/viewer/viewer_actions_editor.dart +++ b/lib/widgets/settings/viewer/viewer_actions_editor.dart @@ -30,7 +30,19 @@ class ViewerActionEditorPage extends StatelessWidget { const ViewerActionEditorPage({Key? key}) : super(key: key); static const allAvailableActions = [ - ...EntryActions.topLevel, + EntryAction.share, + EntryAction.edit, + EntryAction.rename, + EntryAction.delete, + EntryAction.copy, + EntryAction.move, + EntryAction.toggleFavourite, + EntryAction.rotateScreen, + EntryAction.videoCaptureFrame, + EntryAction.videoToggleMute, + EntryAction.videoSetSpeed, + EntryAction.videoSelectStreams, + EntryAction.viewSource, EntryAction.rotateCCW, EntryAction.rotateCW, EntryAction.flip, diff --git a/lib/widgets/viewer/action/entry_action_delegate.dart b/lib/widgets/viewer/action/entry_action_delegate.dart index ab151bec2..8f093ba5b 100644 --- a/lib/widgets/viewer/action/entry_action_delegate.dart +++ b/lib/widgets/viewer/action/entry_action_delegate.dart @@ -34,7 +34,9 @@ import 'package:aves/widgets/viewer/action/printer.dart'; import 'package:aves/widgets/viewer/action/single_entry_editor.dart'; import 'package:aves/widgets/viewer/debug/debug_page.dart'; import 'package:aves/widgets/viewer/info/notifications.dart'; +import 'package:aves/widgets/viewer/overlay/notifications.dart'; import 'package:aves/widgets/viewer/source_viewer_page.dart'; +import 'package:aves/widgets/viewer/video/conductor.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -99,7 +101,20 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix case EntryAction.viewSource: _goToSourceViewer(context); break; - // external + // video + case EntryAction.videoCaptureFrame: + case EntryAction.videoToggleMute: + case EntryAction.videoSelectStreams: + case EntryAction.videoSetSpeed: + case EntryAction.videoSettings: + case EntryAction.videoTogglePlay: + case EntryAction.videoReplay10: + case EntryAction.videoSkip10: + final controller = context.read().getController(entry); + if (controller != null) { + VideoActionNotification(controller: controller, action: action).dispatch(context); + } + break; case EntryAction.edit: androidAppService.edit(entry.uri, entry.mimeType).then((success) { if (!success) showNoMatchingAppDialog(context); @@ -202,10 +217,6 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix ); if (options == null) return; - final source = context.read(); - if (source.initState != SourceInitializationState.full) { - await source.init(); - } final destinationAlbum = await pickAlbum(context: context, moveType: MoveType.export); if (destinationAlbum == null) return; if (!await checkStoragePermissionForAlbums(context, {destinationAlbum})) return; @@ -228,6 +239,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix } final selectionCount = selection.length; + final source = context.read(); source.pauseMonitoring(); await showOpReport( context: context, diff --git a/lib/widgets/viewer/debug/db.dart b/lib/widgets/viewer/debug/db.dart index d94af86ee..6575c1389 100644 --- a/lib/widgets/viewer/debug/db.dart +++ b/lib/widgets/viewer/debug/db.dart @@ -17,7 +17,7 @@ class DbTab extends StatefulWidget { }) : super(key: key); @override - _DbTabState createState() => _DbTabState(); + State createState() => _DbTabState(); } class _DbTabState extends State { diff --git a/lib/widgets/viewer/debug/metadata.dart b/lib/widgets/viewer/debug/metadata.dart index 780f7c1a8..45c525d02 100644 --- a/lib/widgets/viewer/debug/metadata.dart +++ b/lib/widgets/viewer/debug/metadata.dart @@ -18,7 +18,7 @@ class MetadataTab extends StatefulWidget { }) : super(key: key); @override - _MetadataTabState createState() => _MetadataTabState(); + State createState() => _MetadataTabState(); } class _MetadataTabState extends State { diff --git a/lib/widgets/viewer/embedded/notifications.dart b/lib/widgets/viewer/embedded/notifications.dart index d58aba26b..d320e6d85 100644 --- a/lib/widgets/viewer/embedded/notifications.dart +++ b/lib/widgets/viewer/embedded/notifications.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; enum EmbeddedDataSource { motionPhotoVideo, videoCover, xmp } +@immutable class OpenEmbeddedDataNotification extends Notification { final EmbeddedDataSource source; final String? propPath; diff --git a/lib/widgets/viewer/entry_vertical_pager.dart b/lib/widgets/viewer/entry_vertical_pager.dart index 19897bfab..ed28f6d7a 100644 --- a/lib/widgets/viewer/entry_vertical_pager.dart +++ b/lib/widgets/viewer/entry_vertical_pager.dart @@ -11,6 +11,7 @@ import 'package:aves/widgets/viewer/entry_horizontal_pager.dart'; import 'package:aves/widgets/viewer/info/info_page.dart'; import 'package:aves/widgets/viewer/info/notifications.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:screen_brightness/screen_brightness.dart'; class ViewerVerticalPageView extends StatefulWidget { @@ -34,7 +35,7 @@ class ViewerVerticalPageView extends StatefulWidget { }) : super(key: key); @override - _ViewerVerticalPageViewState createState() => _ViewerVerticalPageViewState(); + State createState() => _ViewerVerticalPageViewState(); } class _ViewerVerticalPageViewState extends State { @@ -93,18 +94,7 @@ class _ViewerVerticalPageViewState extends State { // fake page for opacity transition between collection and viewer const transitionPage = SizedBox(); - final imagePage = hasCollection - ? MultiEntryScroller( - collection: collection!, - pageController: widget.horizontalPager, - onPageChanged: widget.onHorizontalPageChanged, - onViewDisposed: widget.onViewDisposed, - ) - : entry != null - ? SingleEntryScroller( - entry: entry!, - ) - : const SizedBox(); + final imagePage = _buildImagePage(); final infoPage = NotificationListener( onNotification: (notification) { @@ -150,6 +140,58 @@ class _ViewerVerticalPageViewState extends State { ); } + Widget _buildImagePage() { + Widget? child; + Map? shortcuts; + + if (hasCollection) { + child = MultiEntryScroller( + collection: collection!, + pageController: widget.horizontalPager, + onPageChanged: widget.onHorizontalPageChanged, + onViewDisposed: widget.onViewDisposed, + ); + shortcuts = const { + SingleActivator(LogicalKeyboardKey.arrowLeft): ShowPreviousIntent(), + SingleActivator(LogicalKeyboardKey.arrowRight): ShowNextIntent(), + SingleActivator(LogicalKeyboardKey.arrowUp): LeaveIntent(), + SingleActivator(LogicalKeyboardKey.arrowDown): ShowInfoIntent(), + }; + } else if (entry != null) { + child = SingleEntryScroller( + entry: entry!, + ); + shortcuts = const { + SingleActivator(LogicalKeyboardKey.arrowUp): LeaveIntent(), + SingleActivator(LogicalKeyboardKey.arrowDown): ShowInfoIntent(), + }; + } + if (child != null) { + return FocusableActionDetector( + autofocus: true, + shortcuts: shortcuts, + actions: { + ShowPreviousIntent: CallbackAction(onInvoke: (intent) => _jumpHorizontalPage(-1)), + ShowNextIntent: CallbackAction(onInvoke: (intent) => _jumpHorizontalPage(1)), + LeaveIntent: CallbackAction(onInvoke: (intent) => Navigator.pop(context)), + ShowInfoIntent: CallbackAction(onInvoke: (intent) => ShowInfoNotification().dispatch(context)), + }, + child: child, + ); + } + return const SizedBox(); + } + + void _jumpHorizontalPage(int delta) { + final pageController = widget.horizontalPager; + final page = pageController.page?.round(); + final _collection = collection; + if (page != null && _collection != null) { + final target = (page + delta).clamp(0, _collection.entryCount - 1); + pageController.jumpToPage(target); + } + } + void _onVerticalPageControllerChanged() { final page = widget.verticalPager.page!; @@ -205,3 +247,21 @@ class _ViewerVerticalPageViewState extends State { } } } + +// keyboard shortcut intents + +class ShowPreviousIntent extends Intent { + const ShowPreviousIntent(); +} + +class ShowNextIntent extends Intent { + const ShowNextIntent(); +} + +class LeaveIntent extends Intent { + const LeaveIntent(); +} + +class ShowInfoIntent extends Intent { + const ShowInfoIntent(); +} diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart index d1d4e28f6..3307af7b7 100644 --- a/lib/widgets/viewer/entry_viewer_stack.dart +++ b/lib/widgets/viewer/entry_viewer_stack.dart @@ -14,17 +14,16 @@ import 'package:aves/utils/change_notifier.dart'; import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/basic/insets.dart'; -import 'package:aves/widgets/viewer/embedded/embedded_data_opener.dart'; import 'package:aves/widgets/viewer/entry_vertical_pager.dart'; import 'package:aves/widgets/viewer/hero.dart'; import 'package:aves/widgets/viewer/info/notifications.dart'; import 'package:aves/widgets/viewer/multipage/conductor.dart'; import 'package:aves/widgets/viewer/multipage/controller.dart'; -import 'package:aves/widgets/viewer/overlay/bottom/common.dart'; -import 'package:aves/widgets/viewer/overlay/bottom/panorama.dart'; -import 'package:aves/widgets/viewer/overlay/bottom/video.dart'; +import 'package:aves/widgets/viewer/overlay/bottom.dart'; import 'package:aves/widgets/viewer/overlay/notifications.dart'; +import 'package:aves/widgets/viewer/overlay/panorama.dart'; 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/video/controller.dart'; @@ -49,7 +48,7 @@ class EntryViewerStack extends StatefulWidget { }) : super(key: key); @override - _EntryViewerStackState createState() => _EntryViewerStackState(); + State createState() => _EntryViewerStackState(); } class _EntryViewerStackState extends State with FeedbackMixin, SingleTickerProviderStateMixin, WidgetsBindingObserver { @@ -60,8 +59,8 @@ class _EntryViewerStackState extends State with FeedbackMixin, final AChangeNotifier _verticalScrollNotifier = AChangeNotifier(); final ValueNotifier _overlayVisible = ValueNotifier(true); late AnimationController _overlayAnimationController; - late Animation _topOverlayScale, _bottomOverlayScale; - late Animation _bottomOverlayOffset; + late Animation _overlayButtonScale, _overlayVideoControlScale; + late Animation _overlayTopOffset; EdgeInsets? _frozenViewInsets, _frozenViewPadding; late VideoActionDelegate _videoActionDelegate; final Map Function()> _multiPageControllerPageListeners = {}; @@ -110,17 +109,17 @@ class _EntryViewerStackState extends State with FeedbackMixin, duration: context.read().viewerOverlayAnimation, vsync: this, ); - _topOverlayScale = CurvedAnimation( + _overlayButtonScale = CurvedAnimation( parent: _overlayAnimationController, // a little bounce at the top curve: Curves.easeOutBack, ); - _bottomOverlayScale = CurvedAnimation( + _overlayVideoControlScale = CurvedAnimation( parent: _overlayAnimationController, // no bounce at the bottom, to avoid video controller displacement curve: Curves.easeOutQuad, ); - _bottomOverlayOffset = Tween(begin: const Offset(0, 1), end: const Offset(0, 0)).animate(CurvedAnimation( + _overlayTopOffset = Tween(begin: const Offset(0, -1), end: const Offset(0, 0)).animate(CurvedAnimation( parent: _overlayAnimationController, curve: Curves.easeOutQuad, )); @@ -198,32 +197,43 @@ class _EntryViewerStackState extends State with FeedbackMixin, _goToCollection(notification.filter); } else if (notification is EntryRemovedNotification) { _onEntryRemoved(context, notification.entry); - } - return false; - }, - child: NotificationListener( - onNotification: (notification) { + } else if (notification is ToggleOverlayNotification) { _overlayVisible.value = notification.visible ?? !_overlayVisible.value; - return true; - }, - child: Stack( - children: [ - ViewerVerticalPageView( - collection: collection, - entryNotifier: _entryNotifier, - verticalPager: _verticalPager, - horizontalPager: _horizontalPager, - onVerticalPageChanged: _onVerticalPageChanged, - onHorizontalPageChanged: _onHorizontalPageChanged, - onImagePageRequested: () => _goToVerticalPage(imagePage), - onViewDisposed: (mainEntry, pageEntry) => viewStateConductor.reset(pageEntry ?? mainEntry), - ), - _buildTopOverlay(), - _buildBottomOverlay(), - const SideGestureAreaProtector(), - const BottomGestureAreaProtector(), - ], - ), + } else if (notification is ShowInfoNotification) { + // remove focus, if any, to prevent viewer shortcuts activation from the Info page + FocusManager.instance.primaryFocus?.unfocus(); + _goToVerticalPage(infoPage); + } else if (notification is ViewEntryNotification) { + final index = notification.index; + if (_currentHorizontalPage != index) { + _horizontalPager.jumpToPage(index); + } + } else if (notification is VideoActionNotification) { + final controller = notification.controller; + final action = notification.action; + _videoActionDelegate.onActionSelected(context, controller, action); + } else { + return false; + } + return true; + }, + child: Stack( + children: [ + ViewerVerticalPageView( + collection: collection, + entryNotifier: _entryNotifier, + verticalPager: _verticalPager, + horizontalPager: _horizontalPager, + onVerticalPageChanged: _onVerticalPageChanged, + onHorizontalPageChanged: _onHorizontalPageChanged, + onImagePageRequested: () => _goToVerticalPage(imagePage), + onViewDisposed: (mainEntry, pageEntry) => viewStateConductor.reset(pageEntry ?? mainEntry), + ), + _buildTopOverlay(), + _buildBottomOverlay(), + const SideGestureAreaProtector(), + const BottomGestureAreaProtector(), + ], ), ), ), @@ -234,32 +244,19 @@ class _EntryViewerStackState extends State with FeedbackMixin, Widget child = ValueListenableBuilder( valueListenable: _entryNotifier, builder: (context, mainEntry, child) { - if (mainEntry == null) return const SizedBox.shrink(); + if (mainEntry == null) return const SizedBox(); - Widget _buildContent({AvesEntry? pageEntry}) { - return EmbeddedDataOpener( - entry: mainEntry, - child: ViewerTopOverlay( - mainEntry: mainEntry, - scale: _topOverlayScale, - canToggleFavourite: hasCollection, - viewInsets: _frozenViewInsets, - viewPadding: _frozenViewPadding, - ), - ); - } - - return NotificationListener( - onNotification: (notification) { - _goToVerticalPage(infoPage); - return true; - }, - child: mainEntry.isMultiPage - ? PageEntryBuilder( - multiPageController: context.read().getController(mainEntry), - builder: (pageEntry) => _buildContent(pageEntry: pageEntry), - ) - : _buildContent(), + return SlideTransition( + position: _overlayTopOffset, + child: ViewerTopOverlay( + entries: entries, + index: _currentHorizontalPage, + hasCollection: hasCollection, + mainEntry: mainEntry, + scale: _overlayButtonScale, + viewInsets: _frozenViewInsets, + viewPadding: _frozenViewPadding, + ), ); }, ); @@ -293,7 +290,8 @@ class _EntryViewerStackState extends State with FeedbackMixin, Widget child = ValueListenableBuilder( valueListenable: _entryNotifier, builder: (context, mainEntry, child) { - if (mainEntry == null) return const SizedBox.shrink(); + if (mainEntry == null) return const SizedBox(); + final multiPageController = mainEntry.isMultiPage ? context.read().getController(mainEntry) : null; Widget? _buildExtraBottomOverlay({AvesEntry? pageEntry}) { @@ -306,7 +304,7 @@ class _EntryViewerStackState extends State with FeedbackMixin, builder: (context, videoController, child) => VideoControlOverlay( entry: targetEntry, controller: videoController, - scale: _bottomOverlayScale, + scale: _overlayVideoControlScale, onActionSelected: (action) { if (videoController != null) { _videoActionDelegate.onActionSelected(context, videoController, action); @@ -324,7 +322,7 @@ class _EntryViewerStackState extends State with FeedbackMixin, } else if (targetEntry.is360) { child = PanoramaOverlay( entry: targetEntry, - scale: _bottomOverlayScale, + scale: _overlayButtonScale, ); } return child != null @@ -343,21 +341,24 @@ class _EntryViewerStackState extends State with FeedbackMixin, ) : _buildExtraBottomOverlay(); - return Column( - children: [ - if (extraBottomOverlay != null) extraBottomOverlay, - SlideTransition( - position: _bottomOverlayOffset, - child: ViewerBottomOverlay( + return TooltipTheme( + data: TooltipTheme.of(context).copyWith( + preferBelow: false, + ), + child: Column( + children: [ + if (extraBottomOverlay != null) extraBottomOverlay, + ViewerBottomOverlay( entries: entries, index: _currentHorizontalPage, - showPosition: hasCollection, + hasCollection: hasCollection, + animationController: _overlayAnimationController, viewInsets: _frozenViewInsets, viewPadding: _frozenViewPadding, multiPageController: multiPageController, ), - ), - ], + ], + ), ); }, ); @@ -463,11 +464,14 @@ class _EntryViewerStackState extends State with FeedbackMixin, if (hasCollection) { final entries = collection!.sortedEntries; entries.remove(entry); - if (entries.isEmpty) { - Navigator.pop(context); - } else { + if (entries.isNotEmpty) { _onCollectionChange(); + return; } + } + + if (Navigator.canPop(context)) { + Navigator.pop(context); } else { // leave viewer SystemNavigator.pop(); diff --git a/lib/widgets/viewer/info/common.dart b/lib/widgets/viewer/info/common.dart index bfb4461c5..f1c568c5c 100644 --- a/lib/widgets/viewer/info/common.dart +++ b/lib/widgets/viewer/info/common.dart @@ -61,7 +61,7 @@ class InfoRowGroup extends StatefulWidget { }) : super(key: key); @override - _InfoRowGroupState createState() => _InfoRowGroupState(); + State createState() => _InfoRowGroupState(); } class _InfoRowGroupState extends State { diff --git a/lib/widgets/viewer/info/info_page.dart b/lib/widgets/viewer/info/info_page.dart index d6036f936..79f789b8a 100644 --- a/lib/widgets/viewer/info/info_page.dart +++ b/lib/widgets/viewer/info/info_page.dart @@ -143,7 +143,7 @@ class _InfoPageContent extends StatefulWidget { }) : super(key: key); @override - _InfoPageContentState createState() => _InfoPageContentState(); + State<_InfoPageContent> createState() => _InfoPageContentState(); } class _InfoPageContentState extends State<_InfoPageContent> { diff --git a/lib/widgets/viewer/info/location_section.dart b/lib/widgets/viewer/info/location_section.dart index 767e0dd8b..cb6062516 100644 --- a/lib/widgets/viewer/info/location_section.dart +++ b/lib/widgets/viewer/info/location_section.dart @@ -31,7 +31,7 @@ class LocationSection extends StatefulWidget { }) : super(key: key); @override - _LocationSectionState createState() => _LocationSectionState(); + State createState() => _LocationSectionState(); } class _LocationSectionState extends State { @@ -165,7 +165,7 @@ class _AddressInfoGroup extends StatefulWidget { const _AddressInfoGroup({required this.entry}); @override - _AddressInfoGroupState createState() => _AddressInfoGroupState(); + State<_AddressInfoGroup> createState() => _AddressInfoGroupState(); } class _AddressInfoGroupState extends State<_AddressInfoGroup> { diff --git a/lib/widgets/viewer/info/metadata/metadata_thumbnail.dart b/lib/widgets/viewer/info/metadata/metadata_thumbnail.dart index 7d2864e32..477f7ec34 100644 --- a/lib/widgets/viewer/info/metadata/metadata_thumbnail.dart +++ b/lib/widgets/viewer/info/metadata/metadata_thumbnail.dart @@ -15,7 +15,7 @@ class MetadataThumbnails extends StatefulWidget { }) : super(key: key); @override - _MetadataThumbnailsState createState() => _MetadataThumbnailsState(); + State createState() => _MetadataThumbnailsState(); } class _MetadataThumbnailsState extends State { diff --git a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart index d4154c64e..4bf210b72 100644 --- a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart +++ b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart @@ -10,6 +10,7 @@ import 'package:aves/widgets/viewer/info/metadata/xmp_ns/dwc.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/exif.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/google.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/iptc.dart'; +import 'package:aves/widgets/viewer/info/metadata/xmp_ns/iptc4xmpext.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/mwg.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/photoshop.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/tiff.dart'; @@ -51,6 +52,8 @@ class XmpNamespace extends Equatable { return XmpGImageNamespace(rawProps); case XmpIptcCoreNamespace.ns: return XmpIptcCoreNamespace(rawProps); + case XmpIptc4xmpExtNamespace.ns: + return XmpIptc4xmpExtNamespace(rawProps); case XmpMgwRegionsNamespace.ns: return XmpMgwRegionsNamespace(rawProps); case XmpMMNamespace.ns: @@ -97,6 +100,7 @@ class XmpNamespace extends Equatable { 'lr': 'Lightroom', 'MicrosoftPhoto': 'Microsoft Photo', 'mwg-rs': 'Regions', + 'nga': 'National Gallery of Art', 'panorama': 'Panorama', 'PanoStudioXMP': 'PanoramaStudio', 'pdf': 'PDF', diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/iptc4xmpext.dart b/lib/widgets/viewer/info/metadata/xmp_ns/iptc4xmpext.dart new file mode 100644 index 000000000..147cb48fe --- /dev/null +++ b/lib/widgets/viewer/info/metadata/xmp_ns/iptc4xmpext.dart @@ -0,0 +1,25 @@ +import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; +import 'package:aves/widgets/viewer/info/metadata/xmp_structs.dart'; +import 'package:flutter/material.dart'; + +class XmpIptc4xmpExtNamespace extends XmpNamespace { + static const ns = 'Iptc4xmpExt'; + + static final aooPattern = RegExp(ns + r':ArtworkOrObject\[(\d+)\]/(.*)'); + + final aoo = >{}; + + XmpIptc4xmpExtNamespace(Map rawProps) : super(ns, rawProps); + + @override + bool extractData(XmpProp prop) => extractIndexedStruct(prop, aooPattern, aoo); + + @override + List buildFromExtractedData() => [ + if (aoo.isNotEmpty) + XmpStructArrayCard( + title: 'Artwork or Object', + structByIndex: aoo, + ), + ]; +} diff --git a/lib/widgets/viewer/info/metadata/xmp_structs.dart b/lib/widgets/viewer/info/metadata/xmp_structs.dart index 9b81e4723..e447e45cc 100644 --- a/lib/widgets/viewer/info/metadata/xmp_structs.dart +++ b/lib/widgets/viewer/info/metadata/xmp_structs.dart @@ -25,7 +25,7 @@ class XmpStructArrayCard extends StatefulWidget { } @override - _XmpStructArrayCardState createState() => _XmpStructArrayCardState(); + State createState() => _XmpStructArrayCardState(); } class _XmpStructArrayCardState extends State { diff --git a/lib/widgets/viewer/info/metadata/xmp_tile.dart b/lib/widgets/viewer/info/metadata/xmp_tile.dart index 44b4ae3e9..74cc82ce2 100644 --- a/lib/widgets/viewer/info/metadata/xmp_tile.dart +++ b/lib/widgets/viewer/info/metadata/xmp_tile.dart @@ -25,7 +25,7 @@ class XmpDirTile extends StatefulWidget { }) : super(key: key); @override - _XmpDirTileState createState() => _XmpDirTileState(); + State createState() => _XmpDirTileState(); } class _XmpDirTileState extends State { diff --git a/lib/widgets/viewer/info/notifications.dart b/lib/widgets/viewer/info/notifications.dart index e46ced611..38e96d70e 100644 --- a/lib/widgets/viewer/info/notifications.dart +++ b/lib/widgets/viewer/info/notifications.dart @@ -2,10 +2,13 @@ import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:flutter/widgets.dart'; +@immutable class ShowImageNotification extends Notification {} +@immutable class ShowInfoNotification extends Notification {} +@immutable class FilterSelectedNotification extends Notification { final CollectionFilter filter; @@ -13,6 +16,7 @@ class FilterSelectedNotification extends Notification { } // deleted or moved to another album +@immutable class EntryRemovedNotification extends Notification { final AvesEntry entry; diff --git a/lib/widgets/viewer/info/owner.dart b/lib/widgets/viewer/info/owner.dart index 07449ec14..fe01690cd 100644 --- a/lib/widgets/viewer/info/owner.dart +++ b/lib/widgets/viewer/info/owner.dart @@ -19,7 +19,7 @@ class OwnerProp extends StatefulWidget { }) : super(key: key); @override - _OwnerPropState createState() => _OwnerPropState(); + State createState() => _OwnerPropState(); } class _OwnerPropState extends State { diff --git a/lib/widgets/viewer/overlay/bottom.dart b/lib/widgets/viewer/overlay/bottom.dart new file mode 100644 index 000000000..6fc54c51f --- /dev/null +++ b/lib/widgets/viewer/overlay/bottom.dart @@ -0,0 +1,235 @@ +import 'dart:math'; + +import 'package:aves/model/entry.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/widgets/common/extensions/media_query.dart'; +import 'package:aves/widgets/viewer/multipage/controller.dart'; +import 'package:aves/widgets/viewer/overlay/multipage.dart'; +import 'package:aves/widgets/viewer/overlay/thumbnail_preview.dart'; +import 'package:aves/widgets/viewer/overlay/viewer_button_row.dart'; +import 'package:aves/widgets/viewer/page_entry_builder.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; + +class ViewerBottomOverlay extends StatefulWidget { + final List entries; + final int index; + final bool hasCollection; + final AnimationController animationController; + final EdgeInsets? viewInsets, viewPadding; + final MultiPageController? multiPageController; + + const ViewerBottomOverlay({ + Key? key, + required this.entries, + required this.index, + required this.hasCollection, + required this.animationController, + this.viewInsets, + this.viewPadding, + required this.multiPageController, + }) : super(key: key); + + @override + State createState() => _ViewerBottomOverlayState(); +} + +class _ViewerBottomOverlayState extends State { + List get entries => widget.entries; + + AvesEntry? get entry { + final index = widget.index; + return index < entries.length ? entries[index] : null; + } + + MultiPageController? get multiPageController => widget.multiPageController; + + @override + Widget build(BuildContext context) { + final mainEntry = entry; + if (mainEntry == null) return const SizedBox(); + + Widget _buildContent({AvesEntry? pageEntry}) => _BottomOverlayContent( + entries: entries, + index: widget.index, + mainEntry: mainEntry, + pageEntry: pageEntry ?? mainEntry, + hasCollection: widget.hasCollection, + multiPageController: multiPageController, + animationController: widget.animationController, + ); + + Widget child = multiPageController != null + ? PageEntryBuilder( + multiPageController: multiPageController!, + builder: (pageEntry) => _buildContent(pageEntry: pageEntry), + ) + : _buildContent(); + + return Selector( + selector: (context, mq) => max(mq.effectiveBottomPadding, mq.systemGestureInsets.bottom), + builder: (context, mqPaddingBottom, child) { + return Padding( + padding: EdgeInsets.only(bottom: mqPaddingBottom), + child: child, + ); + }, + child: child, + ); + } +} + +class _BottomOverlayContent extends StatefulWidget { + final List entries; + final int index; + final AvesEntry mainEntry, pageEntry; + final bool hasCollection; + final MultiPageController? multiPageController; + final AnimationController animationController; + + const _BottomOverlayContent({ + Key? key, + required this.entries, + required this.index, + required this.mainEntry, + required this.pageEntry, + required this.hasCollection, + required this.multiPageController, + required this.animationController, + }) : super(key: key); + + @override + State<_BottomOverlayContent> createState() => _BottomOverlayContentState(); +} + +class _BottomOverlayContentState extends State<_BottomOverlayContent> { + late Animation _buttonScale, _thumbnailOpacity; + + @override + void initState() { + super.initState(); + final animationController = widget.animationController; + _buttonScale = CurvedAnimation( + parent: animationController, + // a little bounce at the top + curve: Curves.easeOutBack, + ); + _thumbnailOpacity = CurvedAnimation( + parent: animationController, + curve: Curves.easeOutQuad, + ); + } + + @override + Widget build(BuildContext context) { + final mainEntry = widget.mainEntry; + final pageEntry = widget.pageEntry; + final multiPageController = widget.multiPageController; + + return AnimatedBuilder( + animation: Listenable.merge([ + mainEntry.metadataChangeNotifier, + pageEntry.metadataChangeNotifier, + ]), + builder: (context, child) { + return Selector( + selector: (context, mq) => mq.size.width, + builder: (context, mqWidth, child) { + final viewerButtonRow = ViewerButtonRow( + mainEntry: mainEntry, + pageEntry: pageEntry, + scale: _buttonScale, + canToggleFavourite: widget.hasCollection, + ); + + final showMultiPageOverlay = mainEntry.isMultiPage && multiPageController != null; + final collapsedPageScroller = mainEntry.isMotionPhoto; + + return SizedBox( + width: mqWidth, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (showMultiPageOverlay && !collapsedPageScroller) + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: FadeTransition( + opacity: _thumbnailOpacity, + child: MultiPageOverlay( + controller: multiPageController, + availableWidth: mqWidth, + scrollable: true, + ), + ), + ), + (showMultiPageOverlay && collapsedPageScroller) + ? Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SafeArea( + top: false, + bottom: false, + child: Padding( + padding: const EdgeInsets.only(bottom: 8), + child: MultiPageOverlay( + controller: multiPageController, + availableWidth: mqWidth, + scrollable: false, + ), + ), + ), + Expanded(child: viewerButtonRow), + ], + ) + : viewerButtonRow, + if (settings.showOverlayThumbnailPreview) + FadeTransition( + opacity: _thumbnailOpacity, + child: ViewerThumbnailPreview( + availableWidth: mqWidth, + displayedIndex: widget.index, + entries: widget.entries, + ), + ), + ], + ), + ); + }, + ); + }); + } +} + +class ExtraBottomOverlay extends StatelessWidget { + final EdgeInsets? viewInsets, viewPadding; + final Widget child; + + const ExtraBottomOverlay({ + Key? key, + this.viewInsets, + this.viewPadding, + required this.child, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final mq = context.select>((mq) => Tuple3(mq.size.width, mq.viewInsets, mq.viewPadding)); + final mqWidth = mq.item1; + final mqViewInsets = mq.item2; + final mqViewPadding = mq.item3; + + final viewInsets = this.viewInsets ?? mqViewInsets; + final viewPadding = this.viewPadding ?? mqViewPadding; + final safePadding = (viewInsets + viewPadding).copyWith(bottom: 8) + const EdgeInsets.symmetric(horizontal: 8.0); + + return Padding( + padding: safePadding, + child: SizedBox( + width: mqWidth - safePadding.horizontal, + child: child, + ), + ); + } +} diff --git a/lib/widgets/viewer/overlay/bottom/video.dart b/lib/widgets/viewer/overlay/bottom/video.dart deleted file mode 100644 index 564325bd5..000000000 --- a/lib/widgets/viewer/overlay/bottom/video.dart +++ /dev/null @@ -1,461 +0,0 @@ -import 'dart:async'; - -import 'package:aves/model/actions/video_actions.dart'; -import 'package:aves/model/entry.dart'; -import 'package:aves/model/settings/settings.dart'; -import 'package:aves/theme/durations.dart'; -import 'package:aves/theme/format.dart'; -import 'package:aves/theme/icons.dart'; -import 'package:aves/utils/constants.dart'; -import 'package:aves/widgets/common/basic/menu.dart'; -import 'package:aves/widgets/common/basic/popup_menu_button.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:aves/widgets/common/fx/blurred.dart'; -import 'package:aves/widgets/common/fx/borders.dart'; -import 'package:aves/widgets/viewer/overlay/common.dart'; -import 'package:aves/widgets/viewer/video/controller.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:provider/provider.dart'; - -class VideoControlOverlay extends StatefulWidget { - final AvesEntry entry; - final AvesVideoController? controller; - final Animation scale; - final Function(VideoAction value) onActionSelected; - final VoidCallback onActionMenuOpened; - - const VideoControlOverlay({ - Key? key, - required this.entry, - required this.controller, - required this.scale, - required this.onActionSelected, - required this.onActionMenuOpened, - }) : super(key: key); - - @override - State createState() => _VideoControlOverlayState(); -} - -class _VideoControlOverlayState extends State with SingleTickerProviderStateMixin { - final GlobalKey _progressBarKey = GlobalKey(debugLabel: 'video-progress-bar'); - bool _playingOnDragStart = false; - - AvesEntry get entry => widget.entry; - - Animation get scale => widget.scale; - - AvesVideoController? get controller => widget.controller; - - Stream get statusStream => controller?.statusStream ?? Stream.value(VideoStatus.idle); - - Stream get positionStream => controller?.positionStream ?? Stream.value(0); - - bool get isPlaying => controller?.isPlaying ?? false; - - static const double outerPadding = 8; - static const double innerPadding = 8; - - @override - Widget build(BuildContext context) { - return StreamBuilder( - stream: statusStream, - builder: (context, snapshot) { - // do not use stream snapshot because it is obsolete when switching between videos - final status = controller?.status ?? VideoStatus.idle; - Widget child; - if (status == VideoStatus.error) { - const action = VideoAction.playOutside; - child = Align( - alignment: AlignmentDirectional.centerEnd, - child: OverlayButton( - scale: scale, - child: IconButton( - icon: action.getIcon(), - onPressed: entry.trashed ? null : () => widget.onActionSelected(action), - tooltip: action.getText(context), - ), - ), - ); - } else { - child = Selector( - selector: (context, mq) => mq.size.width - mq.padding.horizontal, - builder: (context, mqWidth, child) { - final buttonWidth = OverlayButton.getSize(context); - final availableCount = ((mqWidth - outerPadding * 2) / (buttonWidth + innerPadding)).floor(); - final quickActions = settings.videoQuickActions.take(availableCount - 1).toList(); - final menuActions = VideoActions.all.where((action) => !quickActions.contains(action)).toList(); - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - _ButtonRow( - quickActions: quickActions, - menuActions: menuActions, - scale: scale, - controller: controller, - onActionSelected: widget.onActionSelected, - onActionMenuOpened: widget.onActionMenuOpened, - ), - const SizedBox(height: 8), - _buildProgressBar(), - ], - ); - }, - ); - } - - return TooltipTheme( - data: TooltipTheme.of(context).copyWith( - preferBelow: false, - ), - child: child, - ); - }); - } - - Widget _buildProgressBar() { - const progressBarBorderRadius = 123.0; - final blurred = settings.enableOverlayBlurEffect; - const textStyle = TextStyle(shadows: Constants.embossShadows); - return SizeTransition( - sizeFactor: scale, - child: BlurredRRect( - enabled: blurred, - borderRadius: progressBarBorderRadius, - child: GestureDetector( - onTapDown: (details) { - _seekFromTap(details.globalPosition); - }, - onHorizontalDragStart: (details) { - _playingOnDragStart = isPlaying; - if (_playingOnDragStart) controller!.pause(); - }, - onHorizontalDragUpdate: (details) { - _seekFromTap(details.globalPosition); - }, - onHorizontalDragEnd: (details) { - if (_playingOnDragStart) controller!.play(); - }, - child: ConstrainedBox( - constraints: const BoxConstraints( - minHeight: kMinInteractiveDimension, - ), - child: Container( - alignment: Alignment.center, - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16), - decoration: BoxDecoration( - color: overlayBackgroundColor(blurred: blurred), - border: AvesBorder.border, - borderRadius: const BorderRadius.all(Radius.circular(progressBarBorderRadius)), - ), - child: Column( - key: _progressBarKey, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - children: [ - StreamBuilder( - stream: positionStream, - builder: (context, snapshot) { - // do not use stream snapshot because it is obsolete when switching between videos - final position = controller?.currentPosition.floor() ?? 0; - return Text( - formatFriendlyDuration(Duration(milliseconds: position)), - style: textStyle, - ); - }), - const Spacer(), - Text( - entry.durationText, - style: textStyle, - ), - ], - ), - ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(4)), - child: Directionality( - // force directionality for `LinearProgressIndicator` - textDirection: TextDirection.ltr, - child: StreamBuilder( - stream: positionStream, - builder: (context, snapshot) { - // do not use stream snapshot because it is obsolete when switching between videos - var progress = controller?.progress ?? 0.0; - if (!progress.isFinite) progress = 0.0; - return LinearProgressIndicator( - value: progress, - backgroundColor: Colors.grey.shade700, - ); - }), - ), - ), - const Text( - // fake text below to match the height of the text above and center the whole thing - '', - style: textStyle, - ), - ], - ), - ), - ), - ), - ), - ); - } - - void _seekFromTap(Offset globalPosition) async { - if (controller == null) return; - final keyContext = _progressBarKey.currentContext!; - final box = keyContext.findRenderObject() as RenderBox; - final localPosition = box.globalToLocal(globalPosition); - await controller!.seekToProgress(localPosition.dx / box.size.width); - } -} - -class _ButtonRow extends StatelessWidget { - final List quickActions, menuActions; - final Animation scale; - final AvesVideoController? controller; - final Function(VideoAction value) onActionSelected; - final VoidCallback onActionMenuOpened; - - const _ButtonRow({ - Key? key, - required this.quickActions, - required this.menuActions, - required this.scale, - required this.controller, - required this.onActionSelected, - required this.onActionMenuOpened, - }) : super(key: key); - - static const double padding = 8; - - bool get isPlaying => controller?.isPlaying ?? false; - - @override - Widget build(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - ...quickActions.map((action) => _buildOverlayButton(context, action)), - if (menuActions.isNotEmpty) - Padding( - padding: const EdgeInsetsDirectional.only(start: padding), - child: OverlayButton( - scale: scale, - child: MenuIconTheme( - child: AvesPopupMenuButton( - itemBuilder: (context) => menuActions.map((action) => _buildPopupMenuItem(context, action)).toList(), - onSelected: (action) async { - // wait for the popup menu to hide before proceeding with the action - await Future.delayed(Durations.popupMenuAnimation * timeDilation); - onActionSelected(action); - }, - onMenuOpened: onActionMenuOpened, - ), - ), - ), - ), - ], - ); - } - - Widget _buildOverlayButton(BuildContext context, VideoAction action) { - late Widget child; - void onPressed() => onActionSelected(action); - - ValueListenableBuilder _buildFromListenable(ValueListenable? enabledNotifier) { - return ValueListenableBuilder( - valueListenable: enabledNotifier ?? ValueNotifier(false), - builder: (context, canDo, child) => IconButton( - icon: child!, - onPressed: canDo ? onPressed : null, - tooltip: action.getText(context), - ), - child: action.getIcon(), - ); - } - - switch (action) { - case VideoAction.captureFrame: - child = _buildFromListenable(controller?.canCaptureFrameNotifier); - break; - case VideoAction.selectStreams: - child = _buildFromListenable(controller?.canSelectStreamNotifier); - break; - case VideoAction.setSpeed: - child = _buildFromListenable(controller?.canSetSpeedNotifier); - break; - case VideoAction.togglePlay: - child = _PlayToggler( - controller: controller, - onPressed: onPressed, - ); - break; - case VideoAction.playOutside: - case VideoAction.replay10: - case VideoAction.skip10: - case VideoAction.settings: - child = IconButton( - icon: action.getIcon(), - onPressed: onPressed, - tooltip: action.getText(context), - ); - break; - } - return Padding( - padding: const EdgeInsetsDirectional.only(start: padding), - child: OverlayButton( - scale: scale, - child: child, - ), - ); - } - - PopupMenuEntry _buildPopupMenuItem(BuildContext context, VideoAction action) { - late final bool enabled; - switch (action) { - case VideoAction.captureFrame: - enabled = controller?.canCaptureFrameNotifier.value ?? false; - break; - case VideoAction.selectStreams: - enabled = controller?.canSelectStreamNotifier.value ?? false; - break; - case VideoAction.setSpeed: - enabled = controller?.canSetSpeedNotifier.value ?? false; - break; - case VideoAction.replay10: - case VideoAction.skip10: - case VideoAction.settings: - case VideoAction.togglePlay: - enabled = true; - break; - case VideoAction.playOutside: - enabled = !(controller?.entry.trashed ?? true); - break; - } - - Widget? child; - switch (action) { - case VideoAction.togglePlay: - child = _PlayToggler( - controller: controller, - isMenuItem: true, - ); - break; - case VideoAction.captureFrame: - case VideoAction.playOutside: - case VideoAction.replay10: - case VideoAction.skip10: - case VideoAction.selectStreams: - case VideoAction.setSpeed: - case VideoAction.settings: - child = MenuRow(text: action.getText(context), icon: action.getIcon()); - break; - } - - return PopupMenuItem( - value: action, - enabled: enabled, - child: child, - ); - } -} - -class _PlayToggler extends StatefulWidget { - final AvesVideoController? controller; - final bool isMenuItem; - final VoidCallback? onPressed; - - const _PlayToggler({ - required this.controller, - this.isMenuItem = false, - this.onPressed, - }); - - @override - _PlayTogglerState createState() => _PlayTogglerState(); -} - -class _PlayTogglerState extends State<_PlayToggler> with SingleTickerProviderStateMixin { - final List _subscriptions = []; - late AnimationController _playPauseAnimation; - - AvesVideoController? get controller => widget.controller; - - bool get isPlaying => controller?.isPlaying ?? false; - - @override - void initState() { - super.initState(); - _playPauseAnimation = AnimationController( - duration: context.read().iconAnimation, - vsync: this, - ); - _registerWidget(widget); - } - - @override - void didUpdateWidget(covariant _PlayToggler oldWidget) { - super.didUpdateWidget(oldWidget); - _unregisterWidget(oldWidget); - _registerWidget(widget); - } - - @override - void dispose() { - _unregisterWidget(widget); - _playPauseAnimation.dispose(); - super.dispose(); - } - - void _registerWidget(_PlayToggler widget) { - final controller = widget.controller; - if (controller != null) { - _subscriptions.add(controller.statusStream.listen(_onStatusChange)); - _onStatusChange(controller.status); - } - } - - void _unregisterWidget(_PlayToggler widget) { - _subscriptions - ..forEach((sub) => sub.cancel()) - ..clear(); - } - - @override - Widget build(BuildContext context) { - if (widget.isMenuItem) { - return isPlaying - ? MenuRow( - text: context.l10n.videoActionPause, - icon: const Icon(AIcons.pause), - ) - : MenuRow( - text: context.l10n.videoActionPlay, - icon: const Icon(AIcons.play), - ); - } - return IconButton( - icon: AnimatedIcon( - icon: AnimatedIcons.play_pause, - progress: _playPauseAnimation, - ), - onPressed: widget.onPressed, - tooltip: isPlaying ? context.l10n.videoActionPause : context.l10n.videoActionPlay, - ); - } - - void _onStatusChange(VideoStatus status) { - final status = _playPauseAnimation.status; - if (isPlaying && status != AnimationStatus.forward && status != AnimationStatus.completed) { - _playPauseAnimation.forward(); - } else if (!isPlaying && status != AnimationStatus.reverse && status != AnimationStatus.dismissed) { - _playPauseAnimation.reverse(); - } - } -} diff --git a/lib/widgets/viewer/overlay/common.dart b/lib/widgets/viewer/overlay/common.dart index 7caf6446d..4aa7331d1 100644 --- a/lib/widgets/viewer/overlay/common.dart +++ b/lib/widgets/viewer/overlay/common.dart @@ -7,11 +7,13 @@ Color overlayBackgroundColor({required bool blurred}) => blurred ? Colors.black2 class OverlayButton extends StatelessWidget { final Animation scale; + final BorderRadius? borderRadius; final Widget child; const OverlayButton({ Key? key, this.scale = kAlwaysCompleteAnimation, + this.borderRadius, required this.child, }) : super(key: key); @@ -20,20 +22,37 @@ class OverlayButton extends StatelessWidget { final blurred = settings.enableOverlayBlurEffect; return ScaleTransition( scale: scale, - child: BlurredOval( - enabled: blurred, - child: Material( - type: MaterialType.circle, - color: overlayBackgroundColor(blurred: blurred), - child: Ink( - decoration: BoxDecoration( - border: AvesBorder.border, - shape: BoxShape.circle, + child: borderRadius != null + ? BlurredRRect( + enabled: blurred, + borderRadius: borderRadius, + child: Material( + type: MaterialType.button, + borderRadius: borderRadius, + color: overlayBackgroundColor(blurred: blurred), + child: Ink( + decoration: BoxDecoration( + border: AvesBorder.border, + borderRadius: borderRadius, + ), + child: child, + ), + ), + ) + : BlurredOval( + enabled: blurred, + child: Material( + type: MaterialType.circle, + color: overlayBackgroundColor(blurred: blurred), + child: Ink( + decoration: BoxDecoration( + border: AvesBorder.border, + shape: BoxShape.circle, + ), + child: child, + ), + ), ), - child: child, - ), - ), - ), ); } @@ -61,7 +80,7 @@ class OverlayTextButton extends StatelessWidget { final blurred = settings.enableOverlayBlurEffect; return SizeTransition( sizeFactor: scale, - child: BlurredRRect( + child: BlurredRRect.all( enabled: blurred, borderRadius: _borderRadius, child: OutlinedButton( diff --git a/lib/widgets/viewer/overlay/bottom/common.dart b/lib/widgets/viewer/overlay/details.dart similarity index 58% rename from lib/widgets/viewer/overlay/bottom/common.dart rename to lib/widgets/viewer/overlay/details.dart index 775c418dd..e0c8bb44b 100644 --- a/lib/widgets/viewer/overlay/bottom/common.dart +++ b/lib/widgets/viewer/overlay/details.dart @@ -11,52 +11,48 @@ import 'package:aves/theme/format.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:aves/widgets/common/extensions/media_query.dart'; -import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/widgets/viewer/multipage/controller.dart'; -import 'package:aves/widgets/viewer/overlay/bottom/multipage.dart'; -import 'package:aves/widgets/viewer/overlay/common.dart'; import 'package:aves/widgets/viewer/page_entry_builder.dart'; import 'package:decorated_icon/decorated_icon.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; -class ViewerBottomOverlay extends StatefulWidget { +const double _iconPadding = 8.0; +const double _iconSize = 16.0; +const double _interRowPadding = 2.0; +const double _subRowMinWidth = 300.0; + +class ViewerDetailOverlay extends StatefulWidget { final List entries; final int index; - final bool showPosition; - final EdgeInsets? viewInsets, viewPadding; + final bool hasCollection; final MultiPageController? multiPageController; - const ViewerBottomOverlay({ + const ViewerDetailOverlay({ Key? key, required this.entries, required this.index, - required this.showPosition, - this.viewInsets, - this.viewPadding, + required this.hasCollection, required this.multiPageController, }) : super(key: key); @override - State createState() => _ViewerBottomOverlayState(); + State createState() => _ViewerDetailOverlayState(); } -class _ViewerBottomOverlayState extends State { - late Future _detailLoader; - AvesEntry? _lastEntry; - OverlayMetadata? _lastDetails; +class _ViewerDetailOverlayState extends State { + List get entries => widget.entries; AvesEntry? get entry { - final entries = widget.entries; final index = widget.index; return index < entries.length ? entries[index] : null; } - MultiPageController? get multiPageController => widget.multiPageController; + late Future _detailLoader; + AvesEntry? _lastEntry; + OverlayMetadata? _lastDetails; @override void initState() { @@ -65,7 +61,7 @@ class _ViewerBottomOverlayState extends State { } @override - void didUpdateWidget(covariant ViewerBottomOverlay oldWidget) { + void didUpdateWidget(covariant ViewerDetailOverlay oldWidget) { super.didUpdateWidget(oldWidget); if (entry != _lastEntry) { _initDetailLoader(); @@ -79,63 +75,39 @@ class _ViewerBottomOverlayState extends State { @override Widget build(BuildContext context) { - final showOverlayInfo = settings.showOverlayInfo; - final hasEdgeContent = showOverlayInfo || multiPageController != null; - final blurred = settings.enableOverlayBlurEffect; - return BlurredRect( - enabled: hasEdgeContent && blurred, - child: Selector>( - selector: (context, mq) => Tuple3(mq.size.width, mq.viewInsets, mq.viewPadding), - builder: (context, mq, child) { - final mqWidth = mq.item1; - final mqViewInsets = mq.item2; - final mqViewPadding = mq.item3; + return SafeArea( + top: false, + bottom: false, + child: LayoutBuilder( + builder: (context, constraints) { + final availableWidth = constraints.maxWidth; - final viewInsets = widget.viewInsets ?? mqViewInsets; - final viewPadding = widget.viewPadding ?? mqViewPadding; - final availableWidth = mqWidth - viewPadding.horizontal; + return FutureBuilder( + future: _detailLoader, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) { + _lastDetails = snapshot.data; + _lastEntry = entry; + } + if (_lastEntry == null) return const SizedBox(); + final mainEntry = _lastEntry!; - return Selector( - selector: (context, mq) => max(mq.effectiveBottomPadding, showOverlayInfo ? 0 : mq.systemGestureInsets.bottom), - builder: (context, mqPaddingBottom, child) { - return Container( - color: hasEdgeContent ? overlayBackgroundColor(blurred: blurred) : Colors.transparent, - padding: EdgeInsets.only( - left: max(viewInsets.left, viewPadding.left), - top: 0, - right: max(viewInsets.right, viewPadding.right), - bottom: mqPaddingBottom, - ), - child: child, - ); - }, - child: FutureBuilder( - future: _detailLoader, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) { - _lastDetails = snapshot.data; - _lastEntry = entry; - } - if (_lastEntry == null) return const SizedBox(); - final mainEntry = _lastEntry!; + final multiPageController = widget.multiPageController; + Widget _buildContent({AvesEntry? pageEntry}) => ViewerDetailOverlayContent( + pageEntry: pageEntry ?? mainEntry, + details: _lastDetails, + position: widget.hasCollection ? '${widget.index + 1}/${entries.length}' : null, + availableWidth: availableWidth, + multiPageController: multiPageController, + ); - Widget _buildContent({AvesEntry? pageEntry}) => _BottomOverlayContent( - mainEntry: mainEntry, - pageEntry: pageEntry ?? mainEntry, - details: _lastDetails, - position: widget.showPosition ? '${widget.index + 1}/${widget.entries.length}' : null, - availableWidth: availableWidth, + return multiPageController != null + ? PageEntryBuilder( multiPageController: multiPageController, - ); - - return multiPageController != null - ? PageEntryBuilder( - multiPageController: multiPageController!, - builder: (pageEntry) => _buildContent(pageEntry: pageEntry), - ) - : _buildContent(); - }, - ), + builder: (pageEntry) => _buildContent(pageEntry: pageEntry), + ) + : _buildContent(); + }, ); }, ), @@ -143,38 +115,31 @@ class _ViewerBottomOverlayState extends State { } } -const double _iconPadding = 8.0; -const double _iconSize = 16.0; -const double _interRowPadding = 2.0; -const double _subRowMinWidth = 300.0; - -class _BottomOverlayContent extends AnimatedWidget { - final AvesEntry mainEntry, pageEntry; +class ViewerDetailOverlayContent extends StatelessWidget { + final AvesEntry pageEntry; final OverlayMetadata? details; final String? position; final double availableWidth; final MultiPageController? multiPageController; - static const infoPadding = EdgeInsets.symmetric(vertical: 4, horizontal: 8); + static const padding = EdgeInsets.symmetric(vertical: 4, horizontal: 8); - _BottomOverlayContent({ + const ViewerDetailOverlayContent({ Key? key, - required this.mainEntry, required this.pageEntry, - this.details, - this.position, + required this.details, + required this.position, required this.availableWidth, - this.multiPageController, - }) : super( - key: key, - listenable: Listenable.merge([ - mainEntry.metadataChangeNotifier, - pageEntry.metadataChangeNotifier, - ]), - ); + required this.multiPageController, + }) : super(key: key); @override Widget build(BuildContext context) { + final infoMaxWidth = availableWidth - padding.horizontal; + final positionTitle = _PositionTitleRow(entry: pageEntry, collectionPosition: position, multiPageController: multiPageController); + final hasShootingDetails = details != null && !details!.isEmpty && settings.showOverlayShootingDetails; + final animationDuration = context.select((v) => v.viewerOverlayChangeAnimation); + return DefaultTextStyle( style: Theme.of(context).textTheme.bodyText2!.copyWith( shadows: Constants.embossShadows, @@ -182,85 +147,54 @@ class _BottomOverlayContent extends AnimatedWidget { softWrap: false, overflow: TextOverflow.fade, maxLines: 1, - child: SizedBox( - width: availableWidth, + child: Padding( + padding: padding, child: Selector( selector: (context, mq) => mq.orientation, builder: (context, orientation, child) { - Widget? infoColumn; + final twoColumns = orientation == Orientation.landscape && infoMaxWidth / 2 > _subRowMinWidth; + final subRowWidth = twoColumns ? min(_subRowMinWidth, infoMaxWidth / 2) : infoMaxWidth; - if (settings.showOverlayInfo) { - infoColumn = _buildInfoColumn(context, orientation); - } - - if (mainEntry.isMultiPage && multiPageController != null) { - infoColumn = Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MultiPageOverlay( - controller: multiPageController!, - availableWidth: availableWidth, + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (positionTitle.isNotEmpty) positionTitle, + if (twoColumns) + Padding( + padding: const EdgeInsets.only(top: _interRowPadding), + child: Row( + children: [ + SizedBox( + width: subRowWidth, + child: _DateRow( + entry: pageEntry, + multiPageController: multiPageController, + )), + _buildDuoShootingRow(subRowWidth, hasShootingDetails, animationDuration), + ], + ), + ) + else ...[ + Container( + padding: const EdgeInsets.only(top: _interRowPadding), + width: subRowWidth, + child: _DateRow( + entry: pageEntry, + multiPageController: multiPageController, + ), ), - if (infoColumn != null) infoColumn, + _buildSoloShootingRow(subRowWidth, hasShootingDetails, animationDuration), ], - ); - } - - return infoColumn ?? const SizedBox(); + _buildSoloLocationRow(animationDuration), + ], + ); }, ), ), ); } - Widget _buildInfoColumn(BuildContext context, Orientation orientation) { - final infoMaxWidth = availableWidth - infoPadding.horizontal; - final twoColumns = orientation == Orientation.landscape && infoMaxWidth / 2 > _subRowMinWidth; - final subRowWidth = twoColumns ? min(_subRowMinWidth, infoMaxWidth / 2) : infoMaxWidth; - final positionTitle = _PositionTitleRow(entry: pageEntry, collectionPosition: position, multiPageController: multiPageController); - final hasShootingDetails = details != null && !details!.isEmpty && settings.showOverlayShootingDetails; - final animationDuration = context.select((v) => v.viewerOverlayChangeAnimation); - - return Padding( - padding: infoPadding, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (positionTitle.isNotEmpty) positionTitle, - _buildSoloLocationRow(animationDuration), - if (twoColumns) - Padding( - padding: const EdgeInsets.only(top: _interRowPadding), - child: Row( - children: [ - SizedBox( - width: subRowWidth, - child: _DateRow( - entry: pageEntry, - multiPageController: multiPageController, - )), - _buildDuoShootingRow(subRowWidth, hasShootingDetails, animationDuration), - ], - ), - ) - else ...[ - Container( - padding: const EdgeInsets.only(top: _interRowPadding), - width: subRowWidth, - child: _DateRow( - entry: pageEntry, - multiPageController: multiPageController, - ), - ), - _buildSoloShootingRow(subRowWidth, hasShootingDetails, animationDuration), - ], - ], - ), - ); - } - Widget _buildSoloLocationRow(Duration animationDuration) => AnimatedSwitcher( duration: animationDuration, switchInCurve: Curves.easeInOutCubic, @@ -457,35 +391,3 @@ class _ShootingRow extends StatelessWidget { ); } } - -class ExtraBottomOverlay extends StatelessWidget { - final EdgeInsets? viewInsets, viewPadding; - final Widget child; - - const ExtraBottomOverlay({ - Key? key, - this.viewInsets, - this.viewPadding, - required this.child, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final mq = context.select>((mq) => Tuple3(mq.size.width, mq.viewInsets, mq.viewPadding)); - final mqWidth = mq.item1; - final mqViewInsets = mq.item2; - final mqViewPadding = mq.item3; - - final viewInsets = this.viewInsets ?? mqViewInsets; - final viewPadding = this.viewPadding ?? mqViewPadding; - final safePadding = (viewInsets + viewPadding).copyWith(bottom: 8) + const EdgeInsets.symmetric(horizontal: 8.0); - - return Padding( - padding: safePadding, - child: SizedBox( - width: mqWidth - safePadding.horizontal, - child: child, - ), - ); - } -} diff --git a/lib/widgets/viewer/overlay/minimap.dart b/lib/widgets/viewer/overlay/minimap.dart index 42a5617ab..dad6232ae 100644 --- a/lib/widgets/viewer/overlay/minimap.dart +++ b/lib/widgets/viewer/overlay/minimap.dart @@ -1,75 +1,53 @@ import 'dart:math'; -import 'package:aves/model/entry.dart'; -import 'package:aves/widgets/viewer/video/conductor.dart'; import 'package:aves/widgets/viewer/visual/state.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; class Minimap extends StatelessWidget { - final AvesEntry entry; final ValueNotifier viewStateNotifier; - final Size size; - static const defaultSize = Size(96, 96); + static const Size minimapSize = Size(96, 96); const Minimap({ Key? key, - required this.entry, required this.viewStateNotifier, - this.size = defaultSize, }) : super(key: key); @override Widget build(BuildContext context) { return IgnorePointer( child: ValueListenableBuilder( - valueListenable: viewStateNotifier, - builder: (context, viewState, child) { - final viewportSize = viewState.viewportSize; - if (viewportSize == null) return const SizedBox.shrink(); - return AnimatedBuilder( - animation: entry.imageChangeNotifier, - builder: (context, child) { - Widget _builder(Size displaySize) => CustomPaint( - painter: MinimapPainter( - viewportSize: viewportSize, - entrySize: displaySize, - viewCenterOffset: viewState.position, - viewScale: viewState.scale!, - minimapBorderColor: Colors.white30, - ), - size: size, - ); - - if (entry.isVideo) { - final videoController = context.read().getController(entry); - if (videoController == null) return const SizedBox(); - return ValueListenableBuilder( - valueListenable: videoController.sarNotifier, - builder: (context, sar, child) { - return _builder(entry.videoDisplaySize(sar)); - }, - ); - } - return _builder(entry.displaySize); - }, - ); - }), + valueListenable: viewStateNotifier, + builder: (context, viewState, child) { + final viewportSize = viewState.viewportSize; + final contentSize = viewState.contentSize; + if (viewportSize == null || contentSize == null) return const SizedBox(); + return CustomPaint( + painter: MinimapPainter( + viewportSize: viewportSize, + contentSize: contentSize, + viewCenterOffset: viewState.position, + viewScale: viewState.scale!, + minimapBorderColor: Colors.white30, + ), + size: minimapSize, + ); + }, + ), ); } } class MinimapPainter extends CustomPainter { - final Size entrySize, viewportSize; + final Size contentSize, viewportSize; final Offset viewCenterOffset; final double viewScale; final Color minimapBorderColor, viewportBorderColor; const MinimapPainter({ required this.viewportSize, - required this.entrySize, + required this.contentSize, required this.viewCenterOffset, required this.viewScale, this.minimapBorderColor = Colors.white, @@ -78,30 +56,30 @@ class MinimapPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { - if (entrySize.width <= 0 || entrySize.height <= 0) return; + if (contentSize.width <= 0 || contentSize.height <= 0) return; - final viewSize = entrySize * viewScale; + final viewSize = contentSize * viewScale; if (viewSize.isEmpty) return; // hide minimap when image is in full view if (viewportSize + const Offset(precisionErrorTolerance, precisionErrorTolerance) >= viewSize) return; final canvasScale = size.longestSide / viewSize.longestSide; - final scaledEntrySize = viewSize * canvasScale; + final scaledContentSize = viewSize * canvasScale; final scaledViewportSize = viewportSize * canvasScale; - final entryRect = Rect.fromCenter( + final contentRect = Rect.fromCenter( center: size.center(Offset.zero), - width: scaledEntrySize.width, - height: scaledEntrySize.height, + width: scaledContentSize.width, + height: scaledContentSize.height, ); final viewportRect = Rect.fromCenter( center: size.center(Offset.zero) - viewCenterOffset * canvasScale, - width: min(scaledEntrySize.width, scaledViewportSize.width), - height: min(scaledEntrySize.height, scaledViewportSize.height), + width: min(scaledContentSize.width, scaledViewportSize.width), + height: min(scaledContentSize.height, scaledViewportSize.height), ); - canvas.translate((entryRect.width - size.width) / 2, (entryRect.height - size.height) / 2); + canvas.translate((contentRect.width - size.width) / 2, (contentRect.height - size.height) / 2); final fill = Paint() ..style = PaintingStyle.fill @@ -114,8 +92,8 @@ class MinimapPainter extends CustomPainter { ..color = viewportBorderColor; canvas.drawRect(viewportRect, fill); - canvas.drawRect(entryRect, fill); - canvas.drawRect(entryRect, minimapStroke); + canvas.drawRect(contentRect, fill); + canvas.drawRect(contentRect, minimapStroke); canvas.drawRect(viewportRect, viewportStroke); } diff --git a/lib/widgets/viewer/overlay/bottom/multipage.dart b/lib/widgets/viewer/overlay/multipage.dart similarity index 93% rename from lib/widgets/viewer/overlay/bottom/multipage.dart rename to lib/widgets/viewer/overlay/multipage.dart index 5630a7fe1..44507cd47 100644 --- a/lib/widgets/viewer/overlay/bottom/multipage.dart +++ b/lib/widgets/viewer/overlay/multipage.dart @@ -8,15 +8,17 @@ import 'package:provider/provider.dart'; class MultiPageOverlay extends StatefulWidget { final MultiPageController controller; final double availableWidth; + final bool scrollable; const MultiPageOverlay({ Key? key, required this.controller, required this.availableWidth, + required this.scrollable, }) : super(key: key); @override - _MultiPageOverlayState createState() => _MultiPageOverlayState(); + State createState() => _MultiPageOverlayState(); } class _MultiPageOverlayState extends State { @@ -66,6 +68,7 @@ class _MultiPageOverlayState extends State { entryCount: multiPageInfo?.pageCount ?? 0, entryBuilder: (page) => multiPageInfo?.getPageEntryByIndex(page), indexNotifier: controller.pageNotifier, + scrollable: widget.scrollable, ); }, ); diff --git a/lib/widgets/viewer/overlay/notifications.dart b/lib/widgets/viewer/overlay/notifications.dart index 4b7e26dc3..f4dc5893d 100644 --- a/lib/widgets/viewer/overlay/notifications.dart +++ b/lib/widgets/viewer/overlay/notifications.dart @@ -1,7 +1,28 @@ +import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/widgets/viewer/video/controller.dart'; import 'package:flutter/material.dart'; +@immutable class ToggleOverlayNotification extends Notification { final bool? visible; const ToggleOverlayNotification({this.visible}); } + +@immutable +class ViewEntryNotification extends Notification { + final int index; + + const ViewEntryNotification({required this.index}); +} + +@immutable +class VideoActionNotification extends Notification { + final AvesVideoController controller; + final EntryAction action; + + const VideoActionNotification({ + required this.controller, + required this.action, + }); +} diff --git a/lib/widgets/viewer/overlay/bottom/panorama.dart b/lib/widgets/viewer/overlay/panorama.dart similarity index 100% rename from lib/widgets/viewer/overlay/bottom/panorama.dart rename to lib/widgets/viewer/overlay/panorama.dart diff --git a/lib/widgets/viewer/overlay/thumbnail_preview.dart b/lib/widgets/viewer/overlay/thumbnail_preview.dart new file mode 100644 index 000000000..18f736556 --- /dev/null +++ b/lib/widgets/viewer/overlay/thumbnail_preview.dart @@ -0,0 +1,66 @@ +import 'package:aves/model/entry.dart'; +import 'package:aves/theme/durations.dart'; +import 'package:aves/utils/debouncer.dart'; +import 'package:aves/widgets/common/thumbnail/scroller.dart'; +import 'package:aves/widgets/viewer/overlay/notifications.dart'; +import 'package:flutter/material.dart'; + +class ViewerThumbnailPreview extends StatefulWidget { + final List entries; + final int displayedIndex; + final double availableWidth; + + const ViewerThumbnailPreview({ + Key? key, + required this.entries, + required this.displayedIndex, + required this.availableWidth, + }) : super(key: key); + + @override + State createState() => _ViewerThumbnailPreviewState(); +} + +class _ViewerThumbnailPreviewState extends State { + final ValueNotifier _entryIndexNotifier = ValueNotifier(0); + final Debouncer _debouncer = Debouncer(delay: Durations.viewerThumbnailScrollDebounceDelay); + + List get entries => widget.entries; + + int get entryCount => entries.length; + + @override + void initState() { + super.initState(); + _entryIndexNotifier.value = widget.displayedIndex; + _entryIndexNotifier.addListener(_onScrollerIndexChange); + } + + @override + void didUpdateWidget(covariant ViewerThumbnailPreview oldWidget) { + super.didUpdateWidget(oldWidget); + + if (oldWidget.displayedIndex != widget.displayedIndex) { + _entryIndexNotifier.value = widget.displayedIndex; + } + } + + @override + void dispose() { + _entryIndexNotifier.removeListener(_onScrollerIndexChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ThumbnailScroller( + availableWidth: widget.availableWidth, + entryCount: entryCount, + entryBuilder: (index) => 0 <= index && index < entryCount ? entries[index] : null, + indexNotifier: _entryIndexNotifier, + onTap: (index) => ViewEntryNotification(index: index).dispatch(context), + ); + } + + void _onScrollerIndexChange() => _debouncer(() => ViewEntryNotification(index: _entryIndexNotifier.value).dispatch(context)); +} diff --git a/lib/widgets/viewer/overlay/top.dart b/lib/widgets/viewer/overlay/top.dart index dcd2d774a..fdebc1f4b 100644 --- a/lib/widgets/viewer/overlay/top.dart +++ b/lib/widgets/viewer/overlay/top.dart @@ -1,330 +1,106 @@ -import 'package:aves/model/actions/entry_actions.dart'; -import 'package:aves/model/device.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/theme/durations.dart'; -import 'package:aves/theme/icons.dart'; -import 'package:aves/widgets/common/basic/menu.dart'; -import 'package:aves/widgets/common/basic/popup_menu_button.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:aves/widgets/common/favourite_toggler.dart'; -import 'package:aves/widgets/viewer/action/entry_action_delegate.dart'; +import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/widgets/viewer/multipage/conductor.dart'; +import 'package:aves/widgets/viewer/multipage/controller.dart'; import 'package:aves/widgets/viewer/overlay/common.dart'; +import 'package:aves/widgets/viewer/overlay/details.dart'; import 'package:aves/widgets/viewer/overlay/minimap.dart'; -import 'package:aves/widgets/viewer/overlay/notifications.dart'; import 'package:aves/widgets/viewer/page_entry_builder.dart'; import 'package:aves/widgets/viewer/visual/conductor.dart'; -import 'package:collection/collection.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; class ViewerTopOverlay extends StatelessWidget { + final List entries; + final int index; final AvesEntry mainEntry; final Animation scale; final EdgeInsets? viewInsets, viewPadding; - final bool canToggleFavourite; - - static const double outerPadding = 8; - static const double innerPadding = 8; + final bool hasCollection; const ViewerTopOverlay({ Key? key, + required this.entries, + required this.index, required this.mainEntry, required this.scale, - required this.canToggleFavourite, + required this.hasCollection, required this.viewInsets, required this.viewPadding, }) : super(key: key); @override Widget build(BuildContext context) { - return SafeArea( - minimum: (viewInsets ?? EdgeInsets.zero) + (viewPadding ?? EdgeInsets.zero), - child: Padding( - padding: const EdgeInsets.all(outerPadding), - child: Selector( - selector: (context, mq) => mq.size.width - mq.padding.horizontal, - builder: (context, mqWidth, child) { - final buttonWidth = OverlayButton.getSize(context); - final availableCount = ((mqWidth - outerPadding * 2 - buttonWidth) / (buttonWidth + innerPadding)).floor(); + late Widget child; - return mainEntry.isMultiPage - ? PageEntryBuilder( - multiPageController: context.read().getController(mainEntry), - builder: (pageEntry) => _buildOverlay(context, availableCount, mainEntry, pageEntry: pageEntry), - ) - : _buildOverlay(context, availableCount, mainEntry); - }, + if (mainEntry.isMultiPage) { + final multiPageController = context.read().getController(mainEntry); + child = PageEntryBuilder( + multiPageController: multiPageController, + builder: (pageEntry) => _buildOverlay( + context, + mainEntry, + pageEntry: pageEntry, + multiPageController: multiPageController, ), - ), - ); - } - - Widget _buildOverlay(BuildContext context, int availableCount, AvesEntry mainEntry, {AvesEntry? pageEntry}) { - pageEntry ??= mainEntry; - final trashed = mainEntry.trashed; - - bool _isVisible(EntryAction action) { - if (trashed) { - switch (action) { - case EntryAction.delete: - case EntryAction.restore: - return true; - case EntryAction.debug: - return kDebugMode; - default: - return false; - } - } else { - final targetEntry = EntryActions.pageActions.contains(action) ? pageEntry! : mainEntry; - switch (action) { - case EntryAction.toggleFavourite: - return canToggleFavourite; - case EntryAction.delete: - case EntryAction.rename: - case EntryAction.copy: - case EntryAction.move: - return targetEntry.canEdit; - case EntryAction.rotateCCW: - case EntryAction.rotateCW: - case EntryAction.flip: - return targetEntry.canRotateAndFlip; - case EntryAction.convert: - case EntryAction.print: - return !targetEntry.isVideo && device.canPrint; - case EntryAction.openMap: - return targetEntry.hasGps; - case EntryAction.viewSource: - return targetEntry.isSvg; - case EntryAction.rotateScreen: - return settings.isRotationLocked; - case EntryAction.addShortcut: - return device.canPinShortcut; - case EntryAction.copyToClipboard: - case EntryAction.edit: - case EntryAction.open: - case EntryAction.setAs: - case EntryAction.share: - return true; - case EntryAction.restore: - return false; - case EntryAction.debug: - return kDebugMode; - } - } - } - - final buttonRow = Selector( - selector: (context, s) => s.isRotationLocked, - builder: (context, s, child) { - final quickActions = (trashed ? EntryActions.trashed : settings.viewerQuickActions).where(_isVisible).take(availableCount - 1).toList(); - final topLevelActions = EntryActions.topLevel.where((action) => !quickActions.contains(action)).where(_isVisible).toList(); - final exportActions = EntryActions.export.where((action) => !quickActions.contains(action)).where(_isVisible).toList(); - return _TopOverlayRow( - quickActions: quickActions, - topLevelActions: topLevelActions, - exportActions: exportActions, - scale: scale, - mainEntry: mainEntry, - pageEntry: pageEntry!, - ); - }, - ); - - if (settings.showOverlayMinimap) { - final viewStateConductor = context.read(); - final viewStateNotifier = viewStateConductor.getOrCreateController(pageEntry); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buttonRow, - const SizedBox(height: 8), - FadeTransition( - opacity: scale, - child: Minimap( - entry: pageEntry, - viewStateNotifier: viewStateNotifier, - ), - ) - ], ); + } else { + child = _buildOverlay(context, mainEntry); } - return buttonRow; + + return child; } -} -class _TopOverlayRow extends StatelessWidget { - final List quickActions, topLevelActions, exportActions; - final Animation scale; - final AvesEntry mainEntry, pageEntry; + Widget _buildOverlay( + BuildContext context, + AvesEntry mainEntry, { + AvesEntry? pageEntry, + MultiPageController? multiPageController, + }) { + pageEntry ??= mainEntry; - AvesEntry get favouriteTargetEntry => mainEntry.isBurst ? pageEntry : mainEntry; + final showInfo = settings.showOverlayInfo; - const _TopOverlayRow({ - Key? key, - required this.quickActions, - required this.topLevelActions, - required this.exportActions, - required this.scale, - required this.mainEntry, - required this.pageEntry, - }) : super(key: key); + final viewStateConductor = context.read(); + final viewStateNotifier = viewStateConductor.getOrCreateController(pageEntry); - @override - Widget build(BuildContext context) { - final hasOverflowMenu = pageEntry.canRotateAndFlip || topLevelActions.isNotEmpty || exportActions.isNotEmpty; - return Row( + final blurred = settings.enableOverlayBlurEffect; + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - OverlayButton( - scale: scale, - child: Navigator.canPop(context) ? const BackButton() : const CloseButton(), - ), - const Spacer(), - ...quickActions.map((action) => _buildOverlayButton(context, action)), - if (hasOverflowMenu) - OverlayButton( - scale: scale, - child: MenuIconTheme( - child: AvesPopupMenuButton( - key: const Key('entry-menu-button'), - itemBuilder: (context) { - final exportInternalActions = exportActions.whereNot(EntryActions.exportExternal.contains).toList(); - final exportExternalActions = exportActions.where(EntryActions.exportExternal.contains).toList(); - return [ - if (pageEntry.canRotateAndFlip) _buildRotateAndFlipMenuItems(context), - ...topLevelActions.map((action) => _buildPopupMenuItem(context, action)), - if (exportActions.isNotEmpty) - PopupMenuItem( - padding: EdgeInsets.zero, - child: PopupMenuItemExpansionPanel( - icon: AIcons.export, - title: context.l10n.entryActionExport, - items: [ - ...exportInternalActions.map((action) => _buildPopupMenuItem(context, action)).toList(), - if (exportInternalActions.isNotEmpty && exportExternalActions.isNotEmpty) const PopupMenuDivider(height: 0), - ...exportExternalActions.map((action) => _buildPopupMenuItem(context, action)).toList(), - ], - ), - ), - if (!kReleaseMode) ...[ - const PopupMenuDivider(), - _buildPopupMenuItem(context, EntryAction.debug), - ] - ]; - }, - onSelected: (action) { - // wait for the popup menu to hide before proceeding with the action - Future.delayed(Durations.popupMenuAnimation * timeDilation, () => _onActionSelected(context, action)); - }, - onMenuOpened: () { - // if the menu is opened while overlay is hiding, - // the popup menu button is disposed and menu items are ineffective, - // so we make sure overlay stays visible - const ToggleOverlayNotification(visible: true).dispatch(context); - }, + if (showInfo) + BlurredRect( + enabled: blurred, + child: Container( + color: overlayBackgroundColor(blurred: blurred), + child: SafeArea( + minimum: EdgeInsets.only(top: (viewInsets?.top ?? 0) + (viewPadding?.top ?? 0)), + bottom: false, + child: ViewerDetailOverlay( + index: index, + entries: entries, + hasCollection: hasCollection, + multiPageController: multiPageController, + ), ), ), ), + if (settings.showOverlayMinimap) + SafeArea( + top: !showInfo, + child: Padding( + padding: const EdgeInsets.all(8), + child: FadeTransition( + opacity: scale, + child: Minimap( + viewStateNotifier: viewStateNotifier, + ), + ), + ), + ) ], ); } - - Widget _buildOverlayButton(BuildContext context, EntryAction action) { - Widget? child; - void onPressed() => _onActionSelected(context, action); - switch (action) { - case EntryAction.toggleFavourite: - child = FavouriteToggler( - entries: {favouriteTargetEntry}, - onPressed: onPressed, - ); - break; - default: - child = IconButton( - icon: action.getIcon(), - onPressed: onPressed, - tooltip: action.getText(context), - ); - break; - } - return Padding( - padding: const EdgeInsetsDirectional.only(end: ViewerTopOverlay.innerPadding), - child: OverlayButton( - scale: scale, - child: child, - ), - ); - } - - PopupMenuItem _buildPopupMenuItem(BuildContext context, EntryAction action) { - Widget? child; - switch (action) { - // in app actions - case EntryAction.toggleFavourite: - child = FavouriteToggler( - entries: {favouriteTargetEntry}, - isMenuItem: true, - ); - break; - default: - child = MenuRow(text: action.getText(context), icon: action.getIcon()); - break; - } - return PopupMenuItem( - value: action, - child: child, - ); - } - - PopupMenuItem _buildRotateAndFlipMenuItems(BuildContext context) { - Widget buildDivider() => const SizedBox( - height: 16, - child: VerticalDivider( - width: 1, - thickness: 1, - ), - ); - - Widget buildItem(EntryAction action) => Expanded( - child: PopupMenuItem( - value: action, - child: Tooltip( - message: action.getText(context), - child: Center(child: action.getIcon()), - ), - ), - ); - - return PopupMenuItem( - child: Row( - children: [ - buildDivider(), - buildItem(EntryAction.rotateCCW), - buildDivider(), - buildItem(EntryAction.rotateCW), - buildDivider(), - buildItem(EntryAction.flip), - buildDivider(), - ], - ), - ); - } - - void _onActionSelected(BuildContext context, EntryAction action) { - var targetEntry = mainEntry; - if (mainEntry.isMultiPage && (mainEntry.isBurst || EntryActions.pageActions.contains(action))) { - final multiPageController = context.read().getController(mainEntry); - if (multiPageController != null) { - final multiPageInfo = multiPageController.info; - final pageEntry = multiPageInfo?.getPageEntryByIndex(multiPageController.page); - if (pageEntry != null) { - targetEntry = pageEntry; - } - } - } - EntryActionDelegate(targetEntry).onActionSelected(context, action); - } } diff --git a/lib/widgets/viewer/overlay/video/controls.dart b/lib/widgets/viewer/overlay/video/controls.dart new file mode 100644 index 000000000..eab6a7be9 --- /dev/null +++ b/lib/widgets/viewer/overlay/video/controls.dart @@ -0,0 +1,102 @@ +import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/settings/enums/enums.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/widgets/viewer/overlay/common.dart'; +import 'package:aves/widgets/viewer/overlay/video/play_toggler.dart'; +import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class VideoControlRow extends StatelessWidget { + final AvesVideoController? controller; + final Animation scale; + final Function(EntryAction value) onActionSelected; + + static const double padding = 8; + static const Radius radius = Radius.circular(123); + + const VideoControlRow({ + Key? key, + required this.controller, + required this.scale, + required this.onActionSelected, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Selector( + selector: (context, s) => s.videoControls, + builder: (context, videoControls, child) { + switch (videoControls) { + case VideoControls.play: + return Padding( + padding: const EdgeInsetsDirectional.only(start: padding), + child: _buildOverlayButton( + child: PlayToggler( + controller: controller, + onPressed: () => onActionSelected(EntryAction.videoTogglePlay), + ), + ), + ); + case VideoControls.playSeek: + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(width: padding), + _buildIconButton( + context, + EntryAction.videoReplay10, + borderRadius: const BorderRadius.only(topLeft: radius, bottomLeft: radius), + ), + _buildOverlayButton( + child: PlayToggler( + controller: controller, + onPressed: () => onActionSelected(EntryAction.videoTogglePlay), + ), + borderRadius: const BorderRadius.all(Radius.zero), + ), + _buildIconButton( + context, + EntryAction.videoSkip10, + borderRadius: const BorderRadius.only(topRight: radius, bottomRight: radius), + ), + ], + ); + case VideoControls.playOutside: + final trashed = controller?.entry.trashed ?? false; + return Padding( + padding: const EdgeInsetsDirectional.only(start: padding), + child: _buildIconButton(context, EntryAction.open, enabled: !trashed), + ); + case VideoControls.none: + return const SizedBox(); + } + }, + ); + } + + Widget _buildOverlayButton({ + BorderRadius? borderRadius, + required Widget child, + }) => + OverlayButton( + scale: scale, + borderRadius: borderRadius, + child: child, + ); + + Widget _buildIconButton( + BuildContext context, + EntryAction action, { + bool enabled = true, + BorderRadius? borderRadius, + }) => + _buildOverlayButton( + borderRadius: borderRadius, + child: IconButton( + icon: action.getIcon(), + onPressed: enabled ? () => onActionSelected(action) : null, + tooltip: action.getText(context), + ), + ); +} diff --git a/lib/widgets/viewer/overlay/video/mute_toggler.dart b/lib/widgets/viewer/overlay/video/mute_toggler.dart new file mode 100644 index 000000000..8ab3089c7 --- /dev/null +++ b/lib/widgets/viewer/overlay/video/mute_toggler.dart @@ -0,0 +1,49 @@ +import 'dart:async'; + +import 'package:aves/theme/icons.dart'; +import 'package:aves/widgets/common/basic/menu.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:flutter/material.dart'; + +class MuteToggler extends StatelessWidget { + final AvesVideoController? controller; + final bool isMenuItem; + final VoidCallback? onPressed; + + const MuteToggler({ + Key? key, + required this.controller, + this.isMenuItem = false, + this.onPressed, + }) : super(key: key); + + bool get isMuted => controller?.isMuted ?? false; + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: controller?.canMuteNotifier ?? ValueNotifier(false), + builder: (context, canDo, child) { + return StreamBuilder( + stream: controller?.volumeStream ?? Stream.value(1.0), + builder: (context, snapshot) { + final icon = Icon(isMuted ? AIcons.unmute : AIcons.mute); + final text = isMuted ? context.l10n.videoActionUnmute : context.l10n.videoActionMute; + + return isMenuItem + ? MenuRow( + text: text, + icon: icon, + ) + : IconButton( + icon: icon, + onPressed: canDo ? onPressed : null, + tooltip: text, + ); + }, + ); + }, + ); + } +} diff --git a/lib/widgets/viewer/overlay/video/play_toggler.dart b/lib/widgets/viewer/overlay/video/play_toggler.dart new file mode 100644 index 000000000..b7e781b29 --- /dev/null +++ b/lib/widgets/viewer/overlay/video/play_toggler.dart @@ -0,0 +1,100 @@ +import 'dart:async'; + +import 'package:aves/theme/durations.dart'; +import 'package:aves/theme/icons.dart'; +import 'package:aves/widgets/common/basic/menu.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class PlayToggler extends StatefulWidget { + final AvesVideoController? controller; + final bool isMenuItem; + final VoidCallback? onPressed; + + const PlayToggler({ + Key? key, + required this.controller, + this.isMenuItem = false, + this.onPressed, + }) : super(key: key); + + @override + State createState() => _PlayTogglerState(); +} + +class _PlayTogglerState extends State with SingleTickerProviderStateMixin { + final List _subscriptions = []; + late AnimationController _playPauseAnimation; + + AvesVideoController? get controller => widget.controller; + + bool get isPlaying => controller?.isPlaying ?? false; + + @override + void initState() { + super.initState(); + _playPauseAnimation = AnimationController( + duration: context.read().iconAnimation, + vsync: this, + ); + _registerWidget(widget); + } + + @override + void didUpdateWidget(covariant PlayToggler oldWidget) { + super.didUpdateWidget(oldWidget); + _unregisterWidget(oldWidget); + _registerWidget(widget); + } + + @override + void dispose() { + _unregisterWidget(widget); + _playPauseAnimation.dispose(); + super.dispose(); + } + + void _registerWidget(PlayToggler widget) { + final controller = widget.controller; + if (controller != null) { + _subscriptions.add(controller.statusStream.listen(_onStatusChange)); + _onStatusChange(controller.status); + } + } + + void _unregisterWidget(PlayToggler widget) { + _subscriptions + ..forEach((sub) => sub.cancel()) + ..clear(); + } + + @override + Widget build(BuildContext context) { + final text = isPlaying ? context.l10n.videoActionPause : context.l10n.videoActionPlay; + + return widget.isMenuItem + ? MenuRow( + text: text, + icon: Icon(isPlaying ? AIcons.pause : AIcons.play), + ) + : IconButton( + icon: AnimatedIcon( + icon: AnimatedIcons.play_pause, + progress: _playPauseAnimation, + ), + onPressed: widget.onPressed, + tooltip: text, + ); + } + + void _onStatusChange(VideoStatus status) { + final status = _playPauseAnimation.status; + if (isPlaying && status != AnimationStatus.forward && status != AnimationStatus.completed) { + _playPauseAnimation.forward(); + } else if (!isPlaying && status != AnimationStatus.reverse && status != AnimationStatus.dismissed) { + _playPauseAnimation.reverse(); + } + } +} diff --git a/lib/widgets/viewer/overlay/video/progress_bar.dart b/lib/widgets/viewer/overlay/video/progress_bar.dart new file mode 100644 index 000000000..6138b37f2 --- /dev/null +++ b/lib/widgets/viewer/overlay/video/progress_bar.dart @@ -0,0 +1,135 @@ +import 'dart:async'; + +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/theme/format.dart'; +import 'package:aves/utils/constants.dart'; +import 'package:aves/widgets/common/fx/blurred.dart'; +import 'package:aves/widgets/common/fx/borders.dart'; +import 'package:aves/widgets/viewer/overlay/common.dart'; +import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:flutter/material.dart'; + +class VideoProgressBar extends StatefulWidget { + final AvesVideoController? controller; + final Animation scale; + + const VideoProgressBar({ + Key? key, + required this.controller, + required this.scale, + }) : super(key: key); + + @override + State createState() => _VideoProgressBarState(); +} + +class _VideoProgressBarState extends State { + final GlobalKey _progressBarKey = GlobalKey(debugLabel: 'video-progress-bar'); + bool _playingOnDragStart = false; + + static const double radius = 123; + + AvesVideoController? get controller => widget.controller; + + Stream get positionStream => controller?.positionStream ?? Stream.value(0); + + bool get isPlaying => controller?.isPlaying ?? false; + + @override + Widget build(BuildContext context) { + final blurred = settings.enableOverlayBlurEffect; + const textStyle = TextStyle(shadows: Constants.embossShadows); + return SizeTransition( + sizeFactor: widget.scale, + child: BlurredRRect.all( + enabled: blurred, + borderRadius: radius, + child: GestureDetector( + onTapDown: (details) { + _seekFromTap(details.globalPosition); + }, + onHorizontalDragStart: (details) { + _playingOnDragStart = isPlaying; + if (_playingOnDragStart) controller!.pause(); + }, + onHorizontalDragUpdate: (details) { + _seekFromTap(details.globalPosition); + }, + onHorizontalDragEnd: (details) { + if (_playingOnDragStart) controller!.play(); + }, + child: ConstrainedBox( + constraints: const BoxConstraints( + minHeight: kMinInteractiveDimension, + ), + child: Container( + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16), + decoration: BoxDecoration( + color: overlayBackgroundColor(blurred: blurred), + border: AvesBorder.border, + borderRadius: const BorderRadius.all(Radius.circular(radius)), + ), + child: Column( + key: _progressBarKey, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + StreamBuilder( + stream: positionStream, + builder: (context, snapshot) { + // do not use stream snapshot because it is obsolete when switching between videos + final position = controller?.currentPosition.floor() ?? 0; + return Text( + formatFriendlyDuration(Duration(milliseconds: position)), + style: textStyle, + ); + }), + const Spacer(), + Text( + formatFriendlyDuration(Duration(milliseconds: controller?.duration ?? 0)), + style: textStyle, + ), + ], + ), + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(4)), + child: Directionality( + // force directionality for `LinearProgressIndicator` + textDirection: TextDirection.ltr, + child: StreamBuilder( + stream: positionStream, + builder: (context, snapshot) { + // do not use stream snapshot because it is obsolete when switching between videos + var progress = controller?.progress ?? 0.0; + if (!progress.isFinite) progress = 0.0; + return LinearProgressIndicator( + value: progress, + backgroundColor: Colors.grey.shade700, + ); + }), + ), + ), + const Text( + // fake text below to match the height of the text above and center the whole thing + '', + style: textStyle, + ), + ], + ), + ), + ), + ), + ), + ); + } + + void _seekFromTap(Offset globalPosition) async { + if (controller == null) return; + final keyContext = _progressBarKey.currentContext!; + final box = keyContext.findRenderObject() as RenderBox; + final localPosition = box.globalToLocal(globalPosition); + await controller!.seekToProgress(localPosition.dx / box.size.width); + } +} diff --git a/lib/widgets/viewer/overlay/video/video.dart b/lib/widgets/viewer/overlay/video/video.dart new file mode 100644 index 000000000..4243cb05c --- /dev/null +++ b/lib/widgets/viewer/overlay/video/video.dart @@ -0,0 +1,81 @@ +import 'dart:async'; + +import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/entry.dart'; +import 'package:aves/widgets/viewer/overlay/common.dart'; +import 'package:aves/widgets/viewer/overlay/video/controls.dart'; +import 'package:aves/widgets/viewer/overlay/video/progress_bar.dart'; +import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:flutter/material.dart'; + +class VideoControlOverlay extends StatefulWidget { + final AvesEntry entry; + final AvesVideoController? controller; + final Animation scale; + final Function(EntryAction value) onActionSelected; + final VoidCallback onActionMenuOpened; + + const VideoControlOverlay({ + Key? key, + required this.entry, + required this.controller, + required this.scale, + required this.onActionSelected, + required this.onActionMenuOpened, + }) : super(key: key); + + @override + State createState() => _VideoControlOverlayState(); +} + +class _VideoControlOverlayState extends State with SingleTickerProviderStateMixin { + AvesEntry get entry => widget.entry; + + Animation get scale => widget.scale; + + AvesVideoController? get controller => widget.controller; + + Stream get statusStream => controller?.statusStream ?? Stream.value(VideoStatus.idle); + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: statusStream, + builder: (context, snapshot) { + // do not use stream snapshot because it is obsolete when switching between videos + final status = controller?.status ?? VideoStatus.idle; + + if (status == VideoStatus.error) { + const action = EntryAction.open; + return Align( + alignment: AlignmentDirectional.centerEnd, + child: OverlayButton( + scale: scale, + child: IconButton( + icon: action.getIcon(), + onPressed: entry.trashed ? null : () => widget.onActionSelected(action), + tooltip: action.getText(context), + ), + ), + ); + } + + return Row( + children: [ + Expanded( + child: VideoProgressBar( + controller: controller, + scale: scale, + ), + ), + VideoControlRow( + controller: controller, + scale: scale, + onActionSelected: widget.onActionSelected, + ), + ], + ); + }, + ); + } +} diff --git a/lib/widgets/viewer/overlay/viewer_button_row.dart b/lib/widgets/viewer/overlay/viewer_button_row.dart new file mode 100644 index 000000000..3b8585b77 --- /dev/null +++ b/lib/widgets/viewer/overlay/viewer_button_row.dart @@ -0,0 +1,393 @@ +import 'package:aves/model/actions/entry_actions.dart'; +import 'package:aves/model/device.dart'; +import 'package:aves/model/entry.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/theme/durations.dart'; +import 'package:aves/theme/icons.dart'; +import 'package:aves/widgets/common/basic/menu.dart'; +import 'package:aves/widgets/common/basic/popup_menu_button.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/favourite_toggler.dart'; +import 'package:aves/widgets/viewer/action/entry_action_delegate.dart'; +import 'package:aves/widgets/viewer/multipage/conductor.dart'; +import 'package:aves/widgets/viewer/overlay/common.dart'; +import 'package:aves/widgets/viewer/overlay/notifications.dart'; +import 'package:aves/widgets/viewer/overlay/video/mute_toggler.dart'; +import 'package:aves/widgets/viewer/overlay/video/play_toggler.dart'; +import 'package:aves/widgets/viewer/video/conductor.dart'; +import 'package:aves/widgets/viewer/video/controller.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:provider/provider.dart'; + +class ViewerButtonRow extends StatelessWidget { + final AvesEntry mainEntry; + final AvesEntry pageEntry; + final Animation scale; + final bool canToggleFavourite; + + static const double outerPadding = 8; + static const double innerPadding = 8; + + const ViewerButtonRow({ + Key? key, + required this.mainEntry, + required this.pageEntry, + required this.scale, + required this.canToggleFavourite, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final trashed = mainEntry.trashed; + + bool _isVisible(EntryAction action) { + if (trashed) { + switch (action) { + case EntryAction.delete: + case EntryAction.restore: + return true; + case EntryAction.debug: + return kDebugMode; + default: + return false; + } + } else { + final targetEntry = EntryActions.pageActions.contains(action) ? pageEntry : mainEntry; + switch (action) { + case EntryAction.toggleFavourite: + return canToggleFavourite; + case EntryAction.delete: + case EntryAction.rename: + case EntryAction.copy: + case EntryAction.move: + return targetEntry.canEdit; + case EntryAction.rotateCCW: + case EntryAction.rotateCW: + case EntryAction.flip: + return targetEntry.canRotateAndFlip; + case EntryAction.convert: + case EntryAction.print: + return !targetEntry.isVideo && device.canPrint; + case EntryAction.openMap: + return targetEntry.hasGps; + case EntryAction.viewSource: + return targetEntry.isSvg; + case EntryAction.videoCaptureFrame: + case EntryAction.videoToggleMute: + case EntryAction.videoSelectStreams: + case EntryAction.videoSetSpeed: + case EntryAction.videoSettings: + case EntryAction.videoTogglePlay: + case EntryAction.videoReplay10: + case EntryAction.videoSkip10: + return targetEntry.isVideo; + case EntryAction.rotateScreen: + return settings.isRotationLocked; + case EntryAction.addShortcut: + return device.canPinShortcut; + case EntryAction.copyToClipboard: + case EntryAction.edit: + case EntryAction.open: + case EntryAction.setAs: + case EntryAction.share: + return true; + case EntryAction.restore: + return false; + case EntryAction.debug: + return kDebugMode; + } + } + } + + return SafeArea( + top: false, + bottom: false, + child: LayoutBuilder( + builder: (context, constraints) { + final buttonWidth = OverlayButton.getSize(context); + final availableCount = ((constraints.maxWidth - outerPadding * 2) / (buttonWidth + innerPadding)).floor(); + return Selector( + selector: (context, s) => s.isRotationLocked, + builder: (context, s, child) { + final quickActions = (trashed ? EntryActions.trashed : settings.viewerQuickActions).where(_isVisible).take(availableCount - 1).toList(); + final topLevelActions = EntryActions.topLevel.where((action) => !quickActions.contains(action)).where(_isVisible).toList(); + final exportActions = EntryActions.export.where((action) => !quickActions.contains(action)).where(_isVisible).toList(); + final videoActions = EntryActions.video.where((action) => !quickActions.contains(action)).where(_isVisible).toList(); + return ViewerButtonRowContent( + quickActions: quickActions, + topLevelActions: topLevelActions, + exportActions: exportActions, + videoActions: videoActions, + scale: scale, + mainEntry: mainEntry, + pageEntry: pageEntry, + ); + }, + ); + }, + ), + ); + } +} + +class ViewerButtonRowContent extends StatelessWidget { + final List quickActions, topLevelActions, exportActions, videoActions; + final Animation scale; + final AvesEntry mainEntry, pageEntry; + + AvesEntry get favouriteTargetEntry => mainEntry.isBurst ? pageEntry : mainEntry; + + static const double padding = 8; + + const ViewerButtonRowContent({ + Key? key, + required this.quickActions, + required this.topLevelActions, + required this.exportActions, + required this.videoActions, + required this.scale, + required this.mainEntry, + required this.pageEntry, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final hasOverflowMenu = pageEntry.canRotateAndFlip || topLevelActions.isNotEmpty || exportActions.isNotEmpty || videoActions.isNotEmpty; + return Selector( + selector: (context, vc) => vc.getController(pageEntry), + builder: (context, videoController, child) { + return Padding( + padding: const EdgeInsets.only(left: padding / 2, right: padding / 2, bottom: padding), + child: Row( + children: [ + const Spacer(), + ...quickActions.map((action) => _buildOverlayButton(context, action, videoController)), + if (hasOverflowMenu) + Padding( + padding: const EdgeInsets.symmetric(horizontal: padding / 2), + child: OverlayButton( + scale: scale, + child: MenuIconTheme( + child: AvesPopupMenuButton( + key: const Key('entry-menu-button'), + itemBuilder: (context) { + final exportInternalActions = exportActions.whereNot(EntryActions.exportExternal.contains).toList(); + final exportExternalActions = exportActions.where(EntryActions.exportExternal.contains).toList(); + return [ + if (pageEntry.canRotateAndFlip) _buildRotateAndFlipMenuItems(context), + ...topLevelActions.map((action) => _buildPopupMenuItem(context, action, videoController)), + if (exportActions.isNotEmpty) + PopupMenuItem( + padding: EdgeInsets.zero, + child: PopupMenuItemExpansionPanel( + icon: AIcons.export, + title: context.l10n.entryActionExport, + items: [ + ...exportInternalActions.map((action) => _buildPopupMenuItem(context, action, videoController)).toList(), + if (exportInternalActions.isNotEmpty && exportExternalActions.isNotEmpty) const PopupMenuDivider(height: 0), + ...exportExternalActions.map((action) => _buildPopupMenuItem(context, action, videoController)).toList(), + ], + ), + ), + if (videoActions.isNotEmpty) + PopupMenuItem( + padding: EdgeInsets.zero, + child: PopupMenuItemExpansionPanel( + icon: AIcons.video, + title: context.l10n.settingsSectionVideo, + items: [ + ...videoActions.map((action) => _buildPopupMenuItem(context, action, videoController)).toList(), + ], + ), + ), + if (!kReleaseMode) ...[ + const PopupMenuDivider(), + _buildPopupMenuItem(context, EntryAction.debug, videoController), + ] + ]; + }, + onSelected: (action) { + // wait for the popup menu to hide before proceeding with the action + Future.delayed(Durations.popupMenuAnimation * timeDilation, () => _onActionSelected(context, action)); + }, + onMenuOpened: () { + // if the menu is opened while overlay is hiding, + // the popup menu button is disposed and menu items are ineffective, + // so we make sure overlay stays visible + const ToggleOverlayNotification(visible: true).dispatch(context); + }, + ), + ), + ), + ), + ], + ), + ); + }, + ); + } + + Widget _buildOverlayButton(BuildContext context, EntryAction action, AvesVideoController? videoController) { + Widget? child; + void onPressed() => _onActionSelected(context, action); + + ValueListenableBuilder _buildFromListenable(ValueListenable? enabledNotifier) { + return ValueListenableBuilder( + valueListenable: enabledNotifier ?? ValueNotifier(false), + builder: (context, canDo, child) => IconButton( + icon: child!, + onPressed: canDo ? onPressed : null, + tooltip: action.getText(context), + ), + child: action.getIcon(), + ); + } + + switch (action) { + case EntryAction.toggleFavourite: + child = FavouriteToggler( + entries: {favouriteTargetEntry}, + onPressed: onPressed, + ); + break; + case EntryAction.videoToggleMute: + child = MuteToggler( + controller: videoController, + onPressed: onPressed, + ); + break; + case EntryAction.videoTogglePlay: + child = PlayToggler( + controller: videoController, + onPressed: onPressed, + ); + break; + case EntryAction.videoCaptureFrame: + child = _buildFromListenable(videoController?.canCaptureFrameNotifier); + break; + case EntryAction.videoSelectStreams: + child = _buildFromListenable(videoController?.canSelectStreamNotifier); + break; + case EntryAction.videoSetSpeed: + child = _buildFromListenable(videoController?.canSetSpeedNotifier); + break; + default: + child = IconButton( + icon: action.getIcon(), + onPressed: onPressed, + tooltip: action.getText(context), + ); + break; + } + return Padding( + padding: const EdgeInsets.symmetric(horizontal: padding / 2), + child: OverlayButton( + scale: scale, + child: child, + ), + ); + } + + PopupMenuItem _buildPopupMenuItem(BuildContext context, EntryAction action, AvesVideoController? videoController) { + late final bool enabled; + switch (action) { + case EntryAction.videoCaptureFrame: + enabled = videoController?.canCaptureFrameNotifier.value ?? false; + break; + case EntryAction.videoToggleMute: + enabled = videoController?.canMuteNotifier.value ?? false; + break; + case EntryAction.videoSelectStreams: + enabled = videoController?.canSelectStreamNotifier.value ?? false; + break; + case EntryAction.videoSetSpeed: + enabled = videoController?.canSetSpeedNotifier.value ?? false; + break; + default: + enabled = true; + break; + } + + Widget? child; + switch (action) { + case EntryAction.toggleFavourite: + child = FavouriteToggler( + entries: {favouriteTargetEntry}, + isMenuItem: true, + ); + break; + case EntryAction.videoToggleMute: + child = MuteToggler( + controller: videoController, + isMenuItem: true, + ); + break; + case EntryAction.videoTogglePlay: + child = PlayToggler( + controller: videoController, + isMenuItem: true, + ); + break; + default: + child = MenuRow(text: action.getText(context), icon: action.getIcon()); + break; + } + return PopupMenuItem( + value: action, + enabled: enabled, + child: child, + ); + } + + PopupMenuItem _buildRotateAndFlipMenuItems(BuildContext context) { + Widget buildDivider() => const SizedBox( + height: 16, + child: VerticalDivider( + width: 1, + thickness: 1, + ), + ); + + Widget buildItem(EntryAction action) => Expanded( + child: PopupMenuItem( + value: action, + child: Tooltip( + message: action.getText(context), + child: Center(child: action.getIcon()), + ), + ), + ); + + return PopupMenuItem( + child: Row( + children: [ + buildDivider(), + buildItem(EntryAction.rotateCCW), + buildDivider(), + buildItem(EntryAction.rotateCW), + buildDivider(), + buildItem(EntryAction.flip), + buildDivider(), + ], + ), + ); + } + + void _onActionSelected(BuildContext context, EntryAction action) { + var targetEntry = mainEntry; + if (mainEntry.isMultiPage && (mainEntry.isBurst || EntryActions.pageActions.contains(action))) { + final multiPageController = context.read().getController(mainEntry); + if (multiPageController != null) { + final multiPageInfo = multiPageController.info; + final pageEntry = multiPageInfo?.getPageEntryByIndex(multiPageController.page); + if (pageEntry != null) { + targetEntry = pageEntry; + } + } + } + EntryActionDelegate(targetEntry).onActionSelected(context, action); + } +} diff --git a/lib/widgets/viewer/panorama_page.dart b/lib/widgets/viewer/panorama_page.dart index 2ca1be7a2..b1de75d75 100644 --- a/lib/widgets/viewer/panorama_page.dart +++ b/lib/widgets/viewer/panorama_page.dart @@ -30,7 +30,7 @@ class PanoramaPage extends StatefulWidget { }) : super(key: key); @override - _PanoramaPageState createState() => _PanoramaPageState(); + State createState() => _PanoramaPageState(); } class _PanoramaPageState extends State { diff --git a/lib/widgets/viewer/source_viewer_page.dart b/lib/widgets/viewer/source_viewer_page.dart index 36267056c..f486011ea 100644 --- a/lib/widgets/viewer/source_viewer_page.dart +++ b/lib/widgets/viewer/source_viewer_page.dart @@ -14,7 +14,7 @@ class SourceViewerPage extends StatefulWidget { }) : super(key: key); @override - _SourceViewerPageState createState() => _SourceViewerPageState(); + State createState() => _SourceViewerPageState(); } class _SourceViewerPageState extends State { diff --git a/lib/widgets/viewer/video/controller.dart b/lib/widgets/viewer/video/controller.dart index 58048fefa..6507d5e58 100644 --- a/lib/widgets/viewer/video/controller.dart +++ b/lib/widgets/viewer/video/controller.dart @@ -92,6 +92,8 @@ abstract class AvesVideoController { Stream get statusStream; + Stream get volumeStream; + bool get isReady; bool get isPlaying => status == VideoStatus.playing; @@ -111,12 +113,16 @@ abstract class AvesVideoController { ValueNotifier get canCaptureFrameNotifier; + ValueNotifier get canMuteNotifier; + ValueNotifier get canSetSpeedNotifier; ValueNotifier get canSelectStreamNotifier; ValueNotifier get sarNotifier; + bool get isMuted; + double get speed; double get minSpeed; @@ -133,6 +139,8 @@ abstract class AvesVideoController { Future captureFrame(); + Future toggleMute(); + Widget buildPlayerWidget(BuildContext context); } diff --git a/lib/widgets/viewer/video/fijkplayer.dart b/lib/widgets/viewer/video/fijkplayer.dart index 24c5e7bf2..253a946b8 100644 --- a/lib/widgets/viewer/video/fijkplayer.dart +++ b/lib/widgets/viewer/video/fijkplayer.dart @@ -1,10 +1,9 @@ import 'dart:async'; -import 'dart:math'; import 'dart:typed_data'; import 'package:aves/model/entry.dart'; -import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/enums/video_loop_mode.dart'; +import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/video/keys.dart'; import 'package:aves/model/video/metadata.dart'; import 'package:aves/utils/change_notifier.dart'; @@ -18,11 +17,13 @@ class IjkPlayerAvesVideoController extends AvesVideoController { final List _subscriptions = []; final StreamController _valueStreamController = StreamController.broadcast(); final StreamController _timedTextStreamController = StreamController.broadcast(); + final StreamController _volumeStreamController = StreamController.broadcast(); final AChangeNotifier _completedNotifier = AChangeNotifier(); Offset _macroBlockCrop = Offset.zero; final List _streams = []; Timer? _initialPlayTimer; double _speed = 1; + double _volume = 1; // audio/video get out of sync with speed < .5 // the video stream plays at .5 but the audio is slowed as requested @@ -37,6 +38,9 @@ class IjkPlayerAvesVideoController extends AvesVideoController { @override final ValueNotifier canCaptureFrameNotifier = ValueNotifier(false); + @override + final ValueNotifier canMuteNotifier = ValueNotifier(false); + @override final ValueNotifier canSetSpeedNotifier = ValueNotifier(false); @@ -62,13 +66,18 @@ class IjkPlayerAvesVideoController extends AvesVideoController { ) { _instance = FijkPlayer(); _valueStream.map((value) => value.videoRenderStart).firstWhere((v) => v, orElse: () => false).then( - (started) => canCaptureFrameNotifier.value = captureFrameEnabled && started, - onError: (error) {}, - ); + (started) { + canCaptureFrameNotifier.value = captureFrameEnabled && started; + }, + onError: (error) {}, + ); _valueStream.map((value) => value.audioRenderStart).firstWhere((v) => v, orElse: () => false).then( - (started) => canSetSpeedNotifier.value = started, - onError: (error) {}, - ); + (started) { + canMuteNotifier.value = started; + canSetSpeedNotifier.value = started; + }, + onError: (error) {}, + ); _startListening(); } @@ -86,6 +95,12 @@ class IjkPlayerAvesVideoController extends AvesVideoController { _instance.addListener(_onValueChanged); _subscriptions.add(_valueStream.where((value) => value.state == FijkState.completed).listen((_) => _completedNotifier.notify())); _subscriptions.add(_instance.onTimedText.listen(_timedTextStreamController.add)); + _subscriptions.add(settings.updateStream + .where((event) => { + Settings.enableVideoHardwareAccelerationKey, + Settings.videoLoopModeKey, + }.contains(event.key)) + .listen((_) => _instance.reset())); } void _stopListening() { @@ -104,13 +119,15 @@ class IjkPlayerAvesVideoController extends AvesVideoController { } sarNotifier.value = 1; + _streams.clear(); _applyOptions(startMillis); // calling `setDataSource()` with `autoPlay` starts as soon as possible, but often yields initial artifacts // so we introduce a small delay after the player is declared `prepared`, before playing await _instance.setDataSourceUntilPrepared(entry.uri); + await _applyVolume(); if (speed != 1) { - _applySpeed(); + await _applySpeed(); } _initialPlayTimer = Timer(initialPlayDelay, play); } @@ -294,7 +311,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController { @override Future seekTo(int targetMillis) async { - targetMillis = max(0, targetMillis); + targetMillis = targetMillis.clamp(0, duration); if (isReady) { await _instance.seekTo(targetMillis); } else { @@ -313,6 +330,9 @@ class IjkPlayerAvesVideoController extends AvesVideoController { @override Stream get statusStream => _valueStream.map((value) => value.state.toAves); + @override + Stream get volumeStream => _volumeStreamController.stream; + @override bool get isReady => _instance.isPlayable(); @@ -332,6 +352,18 @@ class IjkPlayerAvesVideoController extends AvesVideoController { @override Stream get timedTextStream => _timedTextStreamController.stream; + @override + bool get isMuted => _volume == 0; + + @override + Future toggleMute() async { + _volume = isMuted ? 1 : 0; + _volumeStreamController.add(_volume); + await _applyVolume(); + } + + Future _applyVolume() => _instance.setVolume(_volume); + @override double get speed => _speed; @@ -351,7 +383,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController { bool _needSoundTouch(double speed) => speed > 1; // TODO TLAD [video] bug: setting speed fails when there is no audio stream or audio is disabled - void _applySpeed() => _instance.setSpeed(speed); + 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. diff --git a/lib/widgets/viewer/video_action_delegate.dart b/lib/widgets/viewer/video_action_delegate.dart index b81ff5549..da920b545 100644 --- a/lib/widgets/viewer/video_action_delegate.dart +++ b/lib/widgets/viewer/video_action_delegate.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:aves/model/actions/video_actions.dart'; +import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/highlight.dart'; import 'package:aves/model/source/collection_lens.dart'; @@ -13,6 +13,7 @@ import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/action_mixins/permission_aware.dart'; import 'package:aves/widgets/common/action_mixins/size_aware.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:aves/widgets/dialogs/video_speed_dialog.dart'; import 'package:aves/widgets/dialogs/video_stream_selection_dialog.dart'; import 'package:aves/widgets/settings/video/video.dart'; @@ -34,37 +35,44 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix stopOverlayHidingTimer(); } - void onActionSelected(BuildContext context, AvesVideoController controller, VideoAction action) { + void onActionSelected(BuildContext context, AvesVideoController controller, EntryAction action) { // make sure overlay is not disappearing when selecting an action stopOverlayHidingTimer(); const ToggleOverlayNotification(visible: true).dispatch(context); switch (action) { - case VideoAction.captureFrame: + case EntryAction.videoCaptureFrame: _captureFrame(context, controller); break; - case VideoAction.playOutside: - final entry = controller.entry; - androidAppService.open(entry.uri, entry.mimeTypeAnySubtype); + case EntryAction.videoToggleMute: + controller.toggleMute(); break; - case VideoAction.replay10: - if (controller.isReady) controller.seekTo(controller.currentPosition - 10000); - break; - case VideoAction.skip10: - if (controller.isReady) controller.seekTo(controller.currentPosition + 10000); - break; - case VideoAction.selectStreams: + case EntryAction.videoSelectStreams: _showStreamSelectionDialog(context, controller); break; - case VideoAction.setSpeed: + case EntryAction.videoSetSpeed: _showSpeedDialog(context, controller); break; - case VideoAction.settings: - _showSettings(context); + case EntryAction.videoSettings: + _showSettings(context, controller); break; - case VideoAction.togglePlay: + case EntryAction.videoTogglePlay: _togglePlayPause(context, controller); break; + case EntryAction.videoReplay10: + controller.seekTo(controller.currentPosition - 10000); + break; + case EntryAction.videoSkip10: + controller.seekTo(controller.currentPosition + 10000); + break; + case EntryAction.open: + final entry = controller.entry; + androidAppService.open(entry.uri, entry.mimeTypeAnySubtype).then((success) { + if (!success) showNoMatchingAppDialog(context); + }); + break; + default: + throw UnsupportedError('$action is not a video action'); } } @@ -171,14 +179,25 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix controller.speed = newSpeed; } - void _showSettings(BuildContext context) { - Navigator.push( + Future _showSettings(BuildContext context, AvesVideoController controller) async { + int? resumePosition; + if (controller.isPlaying) { + resumePosition = controller.currentPosition; + await controller.pause(); + } + await Navigator.push( context, MaterialPageRoute( settings: const RouteSettings(name: VideoSettingsPage.routeName), builder: (context) => const VideoSettingsPage(), ), ); + if (resumePosition != null) { + if (!controller.isReady) { + await controller.seekTo(resumePosition); + } + await controller.play(); + } } Future _togglePlayPause(BuildContext context, AvesVideoController controller) async { diff --git a/lib/widgets/viewer/visual/conductor.dart b/lib/widgets/viewer/visual/conductor.dart index 043bc62cd..c66004bf4 100644 --- a/lib/widgets/viewer/visual/conductor.dart +++ b/lib/widgets/viewer/visual/conductor.dart @@ -29,15 +29,16 @@ class ViewStateConductor { // try to initialize the view state to match magnifier initial state const initialScale = ScaleLevel(ref: ScaleReference.contained); final initialValue = ViewState( - Offset.zero, - ScaleBoundaries( + position: Offset.zero, + scale: ScaleBoundaries( minScale: initialScale, maxScale: initialScale, initialScale: initialScale, viewportSize: _viewportSize, childSize: entry.displaySize, ).initialScale, - _viewportSize, + viewportSize: _viewportSize, + contentSize: entry.displaySize, ); controller = Tuple2(entry.uri, ValueNotifier(initialValue)); } diff --git a/lib/widgets/viewer/visual/entry_page_view.dart b/lib/widgets/viewer/visual/entry_page_view.dart index 20ad5e563..f89ff0c79 100644 --- a/lib/widgets/viewer/visual/entry_page_view.dart +++ b/lib/widgets/viewer/visual/entry_page_view.dart @@ -1,10 +1,13 @@ import 'dart:async'; +import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/entry_images.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/durations.dart'; +import 'package:aves/theme/icons.dart'; +import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/magnifier/controller/controller.dart'; import 'package:aves/widgets/common/magnifier/controller/state.dart'; import 'package:aves/widgets/common/magnifier/magnifier.dart'; @@ -24,8 +27,10 @@ import 'package:aves/widgets/viewer/visual/subtitle/subtitle.dart'; import 'package:aves/widgets/viewer/visual/vector.dart'; import 'package:aves/widgets/viewer/visual/video.dart'; import 'package:collection/collection.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; @@ -41,7 +46,7 @@ class EntryPageView extends StatefulWidget { }) : super(key: key); @override - _EntryPageViewState createState() => _EntryPageViewState(); + State createState() => _EntryPageViewState(); } class _EntryPageViewState extends State { @@ -51,6 +56,7 @@ class _EntryPageViewState extends State { ImageStream? _videoCoverStream; late ImageStreamListener _videoCoverStreamListener; final ValueNotifier _videoCoverInfoNotifier = ValueNotifier(null); + final ValueNotifier _actionFeedbackChildNotifier = ValueNotifier(null); MagnifierController? _dismissedCoverMagnifierController; @@ -168,7 +174,7 @@ class _EntryPageViewState extends State { } Widget _buildSvgView() { - var child = _buildMagnifier( + return _buildMagnifier( maxScale: const ScaleLevel(factor: 25), scaleStateCycle: _vectorScaleStateCycle, applyScale: false, @@ -181,48 +187,114 @@ class _EntryPageViewState extends State { ), ), ); - return child; } Widget _buildVideoView() { final videoController = context.read().getController(entry); if (videoController == null) return const SizedBox(); + return ValueListenableBuilder( valueListenable: videoController.sarNotifier, builder: (context, sar, child) { final videoDisplaySize = entry.videoDisplaySize(sar); - return Stack( - fit: StackFit.expand, - children: [ - Stack( + + return Selector>( + selector: (context, s) => Tuple2(s.videoGestureDoubleTapTogglePlay, s.videoGestureSideDoubleTapSeek), + builder: (context, s, child) { + final playGesture = s.item1; + final seekGesture = s.item2; + final useActionGesture = playGesture || seekGesture; + + void _applyAction(EntryAction action, {IconData? Function()? icon}) { + _actionFeedbackChildNotifier.value = DecoratedIcon( + icon?.call() ?? action.getIconData(), + size: 48, + shadows: const [ + Shadow( + color: Colors.black, + blurRadius: 4, + ) + ], + ); + VideoActionNotification( + controller: videoController, + action: action, + ).dispatch(context); + } + + MagnifierDoubleTapCallback? _onDoubleTap = useActionGesture + ? (alignment) { + final x = alignment.x; + if (seekGesture) { + if (x < .25) { + _applyAction(EntryAction.videoReplay10); + return true; + } else if (x > .75) { + _applyAction(EntryAction.videoSkip10); + return true; + } + } + if (playGesture) { + _applyAction( + EntryAction.videoTogglePlay, + icon: () => videoController.isPlaying ? AIcons.pause : AIcons.play, + ); + return true; + } + return false; + } + : null; + + return Stack( + fit: StackFit.expand, children: [ - _buildMagnifier( - displaySize: videoDisplaySize, - child: VideoView( - entry: entry, - controller: videoController, - ), + Stack( + children: [ + _buildMagnifier( + displaySize: videoDisplaySize, + onDoubleTap: _onDoubleTap, + child: VideoView( + entry: entry, + controller: videoController, + ), + ), + VideoSubtitles( + controller: videoController, + viewStateNotifier: _viewStateNotifier, + ), + if (settings.videoShowRawTimedText) + VideoSubtitles( + controller: videoController, + viewStateNotifier: _viewStateNotifier, + debugMode: true, + ), + if (useActionGesture) + ValueListenableBuilder( + valueListenable: _actionFeedbackChildNotifier, + builder: (context, feedbackChild, child) => ActionFeedback( + child: feedbackChild, + ), + ), + ], ), - VideoSubtitles( - controller: videoController, - viewStateNotifier: _viewStateNotifier, + _buildVideoCover( + videoController: videoController, + videoDisplaySize: videoDisplaySize, + onDoubleTap: _onDoubleTap, ), - if (settings.videoShowRawTimedText) - VideoSubtitles( - controller: videoController, - viewStateNotifier: _viewStateNotifier, - debugMode: true, - ), ], - ), - _buildVideoCover(videoController, videoDisplaySize), - ], + ); + }, ); }, ); } - StreamBuilder _buildVideoCover(AvesVideoController videoController, Size videoDisplaySize) { + StreamBuilder _buildVideoCover({ + required AvesVideoController videoController, + required Size videoDisplaySize, + required MagnifierDoubleTapCallback? onDoubleTap, + }) { // fade out image to ease transition with the player return StreamBuilder( stream: videoController.statusStream, @@ -250,6 +322,7 @@ class _EntryPageViewState extends State { return _buildMagnifier( controller: coverController, displaySize: coverSize, + onDoubleTap: onDoubleTap, child: Image( image: videoCoverUriImage, ), @@ -285,6 +358,7 @@ class _EntryPageViewState extends State { ScaleLevel maxScale = maxScale, ScaleStateCycle scaleStateCycle = defaultScaleStateCycle, bool applyScale = true, + MagnifierDoubleTapCallback? onDoubleTap, required Widget child, }) { return Magnifier( @@ -298,6 +372,7 @@ class _EntryPageViewState extends State { scaleStateCycle: scaleStateCycle, applyScale: applyScale, onTap: (c, d, s, o) => _onTap(), + onDoubleTap: onDoubleTap, child: child, ); } @@ -305,15 +380,17 @@ class _EntryPageViewState extends State { void _onTap() => const ToggleOverlayNotification().dispatch(context); void _onViewStateChanged(MagnifierState v) { - final current = _viewStateNotifier.value; - final viewState = ViewState(v.position, v.scale, current.viewportSize); - _viewStateNotifier.value = viewState; + _viewStateNotifier.value = _viewStateNotifier.value.copyWith( + position: v.position, + scale: v.scale, + ); } void _onViewScaleBoundariesChanged(ScaleBoundaries v) { - final current = _viewStateNotifier.value; - final viewState = ViewState(current.position, current.scale, v.viewportSize); - _viewStateNotifier.value = viewState; + _viewStateNotifier.value = _viewStateNotifier.value.copyWith( + viewportSize: v.viewportSize, + contentSize: v.childSize, + ); } static ScaleState _vectorScaleStateCycle(ScaleState actual) { diff --git a/lib/widgets/viewer/visual/error.dart b/lib/widgets/viewer/visual/error.dart index e53ecc716..0c7a3bd5a 100644 --- a/lib/widgets/viewer/visual/error.dart +++ b/lib/widgets/viewer/visual/error.dart @@ -18,7 +18,7 @@ class ErrorView extends StatefulWidget { }) : super(key: key); @override - _ErrorViewState createState() => _ErrorViewState(); + State createState() => _ErrorViewState(); } class _ErrorViewState extends State { diff --git a/lib/widgets/viewer/visual/raster.dart b/lib/widgets/viewer/visual/raster.dart index f15a7df16..077df7188 100644 --- a/lib/widgets/viewer/visual/raster.dart +++ b/lib/widgets/viewer/visual/raster.dart @@ -28,7 +28,7 @@ class RasterImageView extends StatefulWidget { }) : super(key: key); @override - _RasterImageViewState createState() => _RasterImageViewState(); + State createState() => _RasterImageViewState(); } class _RasterImageViewState extends State { @@ -345,7 +345,7 @@ class _RegionTile extends StatefulWidget { }) : super(key: key); @override - _RegionTileState createState() => _RegionTileState(); + State<_RegionTile> createState() => _RegionTileState(); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { diff --git a/lib/widgets/viewer/visual/state.dart b/lib/widgets/viewer/visual/state.dart index 416400db2..a523d7eb5 100644 --- a/lib/widgets/viewer/visual/state.dart +++ b/lib/widgets/viewer/visual/state.dart @@ -5,12 +5,36 @@ import 'package:flutter/widgets.dart'; class ViewState extends Equatable { final Offset position; final double? scale; - final Size? viewportSize; + final Size? viewportSize, contentSize; - static const ViewState zero = ViewState(Offset.zero, 0, null); + static const ViewState zero = ViewState( + position: Offset.zero, + scale: 0, + viewportSize: null, + contentSize: null, + ); @override - List get props => [position, scale, viewportSize]; + List get props => [position, scale, viewportSize, contentSize]; - const ViewState(this.position, this.scale, this.viewportSize); + const ViewState({ + required this.position, + required this.scale, + required this.viewportSize, + required this.contentSize, + }); + + ViewState copyWith({ + Offset? position, + double? scale, + Size? viewportSize, + Size? contentSize, + }) { + return ViewState( + position: position ?? this.position, + scale: scale ?? this.scale, + viewportSize: viewportSize ?? this.viewportSize, + contentSize: contentSize ?? this.contentSize, + ); + } } diff --git a/lib/widgets/viewer/visual/vector.dart b/lib/widgets/viewer/visual/vector.dart index dc41b5708..cc0e1277e 100644 --- a/lib/widgets/viewer/visual/vector.dart +++ b/lib/widgets/viewer/visual/vector.dart @@ -28,7 +28,7 @@ class VectorImageView extends StatefulWidget { }) : super(key: key); @override - _VectorImageViewState createState() => _VectorImageViewState(); + State createState() => _VectorImageViewState(); } class _VectorImageViewState extends State { @@ -300,7 +300,7 @@ class _RegionTile extends StatefulWidget { }) : super(key: key); @override - _RegionTileState createState() => _RegionTileState(); + State<_RegionTile> createState() => _RegionTileState(); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { diff --git a/lib/widgets/welcome_page.dart b/lib/widgets/welcome_page.dart index d21f9c743..bd2e635d5 100644 --- a/lib/widgets/welcome_page.dart +++ b/lib/widgets/welcome_page.dart @@ -18,7 +18,7 @@ class WelcomePage extends StatefulWidget { const WelcomePage({Key? key}) : super(key: key); @override - _WelcomePageState createState() => _WelcomePageState(); + State createState() => _WelcomePageState(); } class _WelcomePageState extends State { diff --git a/pubspec.lock b/pubspec.lock index 532b1ddf8..b5cae8fce 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -305,42 +305,42 @@ packages: name: firebase_core url: "https://pub.dartlang.org" source: hosted - version: "1.12.0" + version: "1.13.1" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "4.2.4" + version: "4.2.5" firebase_core_web: dependency: transitive description: name: firebase_core_web url: "https://pub.dartlang.org" source: hosted - version: "1.5.4" + version: "1.6.1" firebase_crashlytics: dependency: transitive description: name: firebase_crashlytics url: "https://pub.dartlang.org" source: hosted - version: "2.5.1" + version: "2.5.3" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "3.1.13" + version: "3.2.1" flex_color_picker: dependency: "direct main" description: name: flex_color_picker url: "https://pub.dartlang.org" source: hosted - version: "2.3.0" + version: "2.3.1" fluster: dependency: "direct main" description: @@ -461,7 +461,7 @@ packages: name: google_maps_flutter url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" google_maps_flutter_platform_interface: dependency: transitive description: @@ -762,14 +762,14 @@ packages: name: permission_handler_android url: "https://pub.dartlang.org" source: hosted - version: "9.0.2" + version: "9.0.2+1" permission_handler_apple: dependency: transitive description: name: permission_handler_apple url: "https://pub.dartlang.org" source: hosted - version: "9.0.2" + version: "9.0.3" permission_handler_platform_interface: dependency: transitive description: @@ -1175,7 +1175,7 @@ packages: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.0.9" url_launcher_windows: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e45286bb8..8db73db97 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,7 @@ repository: https://github.com/deckerst/aves # - github changelog: /CHANGELOG.md # - play changelog: /whatsnew/whatsnew-en-US # - izzy changelog: /fastlane/metadata/android/en-US/changelogs/1XXX.txt -version: 1.6.1+67 +version: 1.6.2+68 publish_to: none environment: @@ -141,6 +141,9 @@ flutter: # `EagerScaleGestureRecognizer` in `/widgets/common/behaviour/eager_scale_gesture_recognizer.dart` # adapts from Flutter `ScaleGestureRecognizer` in `/gestures/scale.dart` # +# `KnownExtentScrollPhysics` in `/widgets/common/behaviour/known_extent_scroll_physics.dart` +# adapts from Flutter `FixedExtentScrollPhysics` in `/widgets/list_wheel_scroll_view.dart` +# # `TransitionImage` in `/widgets/common/fx/transition_image.dart` # adapts from Flutter `RawImage` in `/widgets/basic.dart` and `DecorationImagePainter` in `/painting/decoration_image.dart` # diff --git a/shaders_2.10.2.sksl.json b/shaders_2.10.2.sksl.json deleted file mode 100644 index 979f4d2f1..000000000 --- a/shaders_2.10.2.sksl.json +++ /dev/null @@ -1 +0,0 @@ -{"platform":"android","name":"SM G970N","engineRevision":"a83ed0e5e3b9cd2b5e2f07ef31c72f43c55e93b7","data":{"DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAIAHSADQAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAABAAAAWAMAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzE7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1MwOwpmbGF0IGluIGZsb2F0IHZUZXhJbmRleF9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gdGV4Q29sb3I7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA8AAABpblRleHR1cmVDb29yZHMAAQAAAAAAAAA=","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAEDAACATAAABAGYAAAICSBYQCA4AAAAAAEAZCBRE4GNEACAAAOAAAAAAAAAAABAAOAAAABAAAAAAABBAMAA":"CAAAAExTS1OxCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDIgdmFyY2Nvb3JkX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCWZsb2F0IGFhX2Jsb2F0X211bHRpcGxpZXIgPSAxOwoJZmxvYXQyIGNvcm5lciA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMueHk7CglmbG9hdDIgcmFkaXVzX291dHNldCA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMuenc7CglmbG9hdDIgYWFfYmxvYXRfZGlyZWN0aW9uID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnh5OwoJZmxvYXQgaXNfbGluZWFyX2NvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnc7CglmbG9hdDIgcGl4ZWxsZW5ndGggPSBpbnZlcnNlc3FydChmbG9hdDIoZG90KHNrZXcueHosIHNrZXcueHopLCBkb3Qoc2tldy55dywgc2tldy55dykpKTsKCWZsb2F0NCBub3JtYWxpemVkX2F4aXNfZGlycyA9IHNrZXcgKiBwaXhlbGxlbmd0aC54eXh5OwoJZmxvYXQyIGF4aXN3aWR0aHMgPSAoYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnh5KSArIGFicyhub3JtYWxpemVkX2F4aXNfZGlycy56dykpOwoJZmxvYXQyIGFhX2Jsb2F0cmFkaXVzID0gYXhpc3dpZHRocyAqIHBpeGVsbGVuZ3RoICogLjU7CglmbG9hdDQgcmFkaWlfYW5kX25laWdoYm9ycyA9IHJhZGlpX3NlbGVjdG9yKiBmbG9hdDR4NChyYWRpaV94LCByYWRpaV95LCByYWRpaV94Lnl4d3osIHJhZGlpX3kud3p5eCk7CglmbG9hdDIgcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnh5OwoJZmxvYXQyIG5laWdoYm9yX3JhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy56dzsKCWZsb2F0IGNvdmVyYWdlX211bHRpcGxpZXIgPSAxOwoJaWYgKGFueShncmVhdGVyVGhhbihhYV9ibG9hdHJhZGl1cywgZmxvYXQyKDEpKSkpIAoJewoJCWNvcm5lciA9IG1heChhYnMoY29ybmVyKSwgYWFfYmxvYXRyYWRpdXMpICogc2lnbihjb3JuZXIpOwoJCWNvdmVyYWdlX211bHRpcGxpZXIgPSAxIC8gKG1heChhYV9ibG9hdHJhZGl1cy54LCAxKSAqIG1heChhYV9ibG9hdHJhZGl1cy55LCAxKSk7CgkJcmFkaWkgPSBmbG9hdDIoMCk7Cgl9CglmbG9hdCBjb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS56OwoJaWYgKGFueShsZXNzVGhhbihyYWRpaSwgYWFfYmxvYXRyYWRpdXMgKiAxLjUpKSkgCgl7CgkJcmFkaWkgPSBmbG9hdDIoMCk7CgkJYWFfYmxvYXRfZGlyZWN0aW9uID0gc2lnbihjb3JuZXIpOwoJCWlmIChjb3ZlcmFnZSA+IC41KSAKCQl7CgkJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IC1hYV9ibG9hdF9kaXJlY3Rpb247CgkJfQoJCWlzX2xpbmVhcl9jb3ZlcmFnZSA9IDE7Cgl9CgllbHNlIAoJewoJCXJhZGlpID0gY2xhbXAocmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCW5laWdoYm9yX3JhZGlpID0gY2xhbXAobmVpZ2hib3JfcmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCWZsb2F0MiBzcGFjaW5nID0gMiAtIHJhZGlpIC0gbmVpZ2hib3JfcmFkaWk7CgkJZmxvYXQyIGV4dHJhX3BhZCA9IG1heChwaXhlbGxlbmd0aCAqIC4wNjI1IC0gc3BhY2luZywgZmxvYXQyKDApKTsKCQlyYWRpaSAtPSBleHRyYV9wYWQgKiAuNTsKCX0KCWZsb2F0MiBhYV9vdXRzZXQgPSBhYV9ibG9hdF9kaXJlY3Rpb24gKiBhYV9ibG9hdHJhZGl1cyAqIGFhX2Jsb2F0X211bHRpcGxpZXI7CglmbG9hdDIgdmVydGV4cG9zID0gY29ybmVyICsgcmFkaXVzX291dHNldCAqIHJhZGlpICsgYWFfb3V0c2V0OwoJaWYgKGNvdmVyYWdlID4gLjUpIAoJewoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueCAhPSAwICYmIHZlcnRleHBvcy54ICogY29ybmVyLnggPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLngpOwoJCQl2ZXJ0ZXhwb3MueCA9IDA7CgkJCXZlcnRleHBvcy55ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci55KSAqIHBpeGVsbGVuZ3RoLnkvcGl4ZWxsZW5ndGgueDsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLngpIC8gKGFicyhjb3JuZXIueCkgKyBiYWNrc2V0KSArIC41OwoJCX0KCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnkgIT0gMCAmJiB2ZXJ0ZXhwb3MueSAqIGNvcm5lci55IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy55KTsKCQkJdmVydGV4cG9zLnkgPSAwOwoJCQl2ZXJ0ZXhwb3MueCArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueCkgKiBwaXhlbGxlbmd0aC54L3BpeGVsbGVuZ3RoLnk7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci55KSAvIChhYnMoY29ybmVyLnkpICsgYmFja3NldCkgKyAuNTsKCQl9Cgl9CglmbG9hdDJ4MiBza2V3bWF0cml4ID0gZmxvYXQyeDIoc2tldy54eSwgc2tldy56dyk7CglmbG9hdDIgZGV2Y29vcmQgPSB2ZXJ0ZXhwb3MgKiBza2V3bWF0cml4ICsgdHJhbnNsYXRlOwoJaWYgKDAgIT0gaXNfbGluZWFyX2NvdmVyYWdlKSAKCXsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoYXJjY29vcmQueCsxLCBhcmNjb29yZC55KTsKCX0KCXNrX1Bvc2l0aW9uID0gZGV2Y29vcmQueHkwMTsKfQoAAAABAAAABwUAAGNvbnN0IGludCBrRmlsbEFBX1MxID0gMTsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEJXX1MxID0gMjsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEFBX1MxID0gMzsKdW5pZm9ybSBmbG9hdDQgdWNpcmNsZV9TMTsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2YXJjY29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmNsZV9TMShoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgZDsKCWlmIChpbnQoMykgPT0ga0ludmVyc2VGaWxsQldfUzEgfHwgaW50KDMpID09IGtJbnZlcnNlRmlsbEFBX1MxKSAKCXsKCQlkID0gaGFsZigobGVuZ3RoKCh1Y2lyY2xlX1MxLnh5IC0gc2tfRnJhZ0Nvb3JkLnh5KSAqIHVjaXJjbGVfUzEudykgLSAxLjApICogdWNpcmNsZV9TMS56KTsKCX0KCWVsc2UgCgl7CgkJZCA9IGhhbGYoKDEuMCAtIGxlbmd0aCgodWNpcmNsZV9TMS54eSAtIHNrX0ZyYWdDb29yZC54eSkgKiB1Y2lyY2xlX1MxLncpKSAqIHVjaXJjbGVfUzEueik7Cgl9CglpZiAoaW50KDMpID09IGtGaWxsQUFfUzEgfHwgaW50KDMpID09IGtJbnZlcnNlRmlsbEFBX1MxKSAKCXsKCQlyZXR1cm4gaGFsZjQoX2lucHV0ICogc2F0dXJhdGUoZCkpOwoJfQoJZWxzZSAKCXsKCQlyZXR1cm4gaGFsZjQoZCA+IDAuNSA/IF9pbnB1dCA6IGhhbGY0KDAuMCkpOwoJfQp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1MwLngsIHk9dmFyY2Nvb3JkX1MwLnk7CgloYWxmIGNvdmVyYWdlOwoJaWYgKDAgPT0geF9wbHVzXzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZih5KTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQgZm4gPSB4X3BsdXNfMSAqICh4X3BsdXNfMSAtIDIpOwoJCWZuID0gZm1hKHkseSwgZm4pOwoJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjbGVfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAEAAAAc2tldwkAAAB0cmFuc2xhdGUAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAFAAAAY29sb3IAAAABAAAAAAAAAA==","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAADAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAYAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIA":"CAAAAExTS1OAAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfNV9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAOMGAAB1bmlmb3JtIGhhbGY0IHVzdGFydF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1ZW5kX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzE7CnVuaWZvcm0gaGFsZjQgdWxlZnRCb3JkZXJDb2xvcl9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TMV9jMDsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJZmxvYXQyIF90bXBfMV9jb29yZHMgPSBfY29vcmRzOwoJcmV0dXJuIGhhbGY0KG1peCh1c3RhcnRfUzFfYzBfYzAsIHVlbmRfUzFfYzBfYzAsIGhhbGYoX3RtcF8xX2Nvb3Jkcy54KSkpOwp9CmhhbGY0IExpbmVhckxheW91dF9TMV9jMF9jMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzJfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzNfY29vcmRzID0gdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CglyZXR1cm4gaGFsZjQoaGFsZjQoaGFsZihfdG1wXzNfY29vcmRzLngpICsgOS45OTk5OTk3NDczNzg3NTE2ZS0wNiwgMS4wLCAwLjAsIDAuMCkpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyTGF5b3V0X1MxX2MwX2MxX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQ2xhbXBlZEdyYWRpZW50X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfNF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjQgdCA9IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShfdG1wXzRfaW5Db2xvcik7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghYm9vbChpbnQoMSkpICYmIHQueSA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSBoYWxmNCgwLjApOwoJfQoJZWxzZSBpZiAodC54IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IHVsZWZ0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIGlmICh0LnggPiAxLjApIAoJewoJCW91dENvbG9yID0gdXJpZ2h0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoX3RtcF80X2luQ29sb3IsIGZsb2F0MihoYWxmMih0LngsIDAuMCkpKTsKCX0KCWlmIChib29sKGludCgxKSkpIAoJewoJCW91dENvbG9yLnh5eiAqPSBvdXRDb2xvci53OwoJfQoJcmV0dXJuIGhhbGY0KG91dENvbG9yKTsKfQpoYWxmNCBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKGhhbGY0IF9pbnB1dCkgCnsKCV9pbnB1dCA9IENsYW1wZWRHcmFkaWVudF9TMV9jMChfaW5wdXQpOwoJaGFsZjQgX3RtcF81X2luQ29sb3IgPSBfaW5wdXQ7CglyZXR1cm4gaGFsZjQoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IERpc2FibGVDb3ZlcmFnZUFzQWxwaGFfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","HUQACAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CAAAAExTS1PPAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAkAQAAaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","AYQQ5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAACTAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGYgZGlzdGFuY2VUb0lubmVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKGQgLSBjaXJjbGVFZGdlLncpKTsKCWhhbGYgaW5uZXJBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9Jbm5lckVkZ2UpOwoJZWRnZUFscGhhICo9IGlubmVyQWxwaGE7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAIBAIAAAABLCIIBAAAAABAEGABBAMAACAIAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAD6AwAAdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZCA9IGNsYW1wKHN1YnNldENvb3JkLCB1Y2xhbXBfUzFfYzBfYzAueHksIHVjbGFtcF9TMV9jMF9jMC56dyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMF9jMChfaW5wdXQpOwp9CmhhbGY0IEJsZW5kX1MxKGhhbGY0IF9zcmMsIGhhbGY0IF9kc3QpIAp7CgkvLyBCbGVuZCBtb2RlOiBNb2R1bGF0ZQoJcmV0dXJuIGJsZW5kX21vZHVsYXRlKE1hdHJpeEVmZmVjdF9TMV9jMChfc3JjKSwgX3NyYyk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBCbGVuZF9TMShvdXRwdXRDb2xvcl9TMCwgaGFsZjQoMSkpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","B2AAQAAABQAAIAABBYAAB7777777777774ABICAAAAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CAAAAExTS1MOAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmIGluQ292ZXJhZ2U7Cm91dCBoYWxmIHZpbkNvdmVyYWdlX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cgl2aW5Db3ZlcmFnZV9TMCA9IGluQ292ZXJhZ2U7Cglza19Qb3NpdGlvbiA9IF90bXBfMV9pblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAZQEAAHVuaWZvcm0gaGFsZjQgdUNvbG9yX1MwOwppbiBoYWxmIHZpbkNvdmVyYWdlX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdUNvbG9yX1MwOwoJaGFsZiBhbHBoYSA9IDEuMDsKCWFscGhhID0gdmluQ292ZXJhZ2VfUzA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAACgAAAGluQ292ZXJhZ2UAAAEAAAAAAAAA","HUQACAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CAAAAExTS1PPAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAEAAACzAgAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGhhbGY0IHZjb2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","HVJAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAABAAAAAABBAMABAAOAAAABAAAAAAABBAMAAA":"CAAAAExTS1MjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cgl2bG9jYWxDb29yZF9TMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAAAAADoAQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdDIgdGV4Q29vcmQ7Cgl0ZXhDb29yZCA9IHZsb2NhbENvb3JkX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSAoKHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdGV4Q29vcmQpICogb3V0cHV0Q29sb3JfUzApKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAABAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAIAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIA":"CAAAAExTS1OAAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfNV9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAOMGAAB1bmlmb3JtIGhhbGY0IHVzdGFydF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1ZW5kX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzE7CnVuaWZvcm0gaGFsZjQgdWxlZnRCb3JkZXJDb2xvcl9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TMV9jMDsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJZmxvYXQyIF90bXBfMV9jb29yZHMgPSBfY29vcmRzOwoJcmV0dXJuIGhhbGY0KG1peCh1c3RhcnRfUzFfYzBfYzAsIHVlbmRfUzFfYzBfYzAsIGhhbGYoX3RtcF8xX2Nvb3Jkcy54KSkpOwp9CmhhbGY0IExpbmVhckxheW91dF9TMV9jMF9jMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzJfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzNfY29vcmRzID0gdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CglyZXR1cm4gaGFsZjQoaGFsZjQoaGFsZihfdG1wXzNfY29vcmRzLngpICsgOS45OTk5OTk3NDczNzg3NTE2ZS0wNiwgMS4wLCAwLjAsIDAuMCkpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyTGF5b3V0X1MxX2MwX2MxX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQ2xhbXBlZEdyYWRpZW50X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfNF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjQgdCA9IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShfdG1wXzRfaW5Db2xvcik7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghYm9vbChpbnQoMSkpICYmIHQueSA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSBoYWxmNCgwLjApOwoJfQoJZWxzZSBpZiAodC54IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IHVsZWZ0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIGlmICh0LnggPiAxLjApIAoJewoJCW91dENvbG9yID0gdXJpZ2h0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoX3RtcF80X2luQ29sb3IsIGZsb2F0MihoYWxmMih0LngsIDAuMCkpKTsKCX0KCWlmIChib29sKGludCgwKSkpIAoJewoJCW91dENvbG9yLnh5eiAqPSBvdXRDb2xvci53OwoJfQoJcmV0dXJuIGhhbGY0KG91dENvbG9yKTsKfQpoYWxmNCBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKGhhbGY0IF9pbnB1dCkgCnsKCV9pbnB1dCA9IENsYW1wZWRHcmFkaWVudF9TMV9jMChfaW5wdXQpOwoJaGFsZjQgX3RtcF81X2luQ29sb3IgPSBfaW5wdXQ7CglyZXR1cm4gaGFsZjQoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IERpc2FibGVDb3ZlcmFnZUFzQWxwaGFfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAyQEAAHVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdlRleHR1cmVDb29yZHNfUzA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gdGV4Q29sb3I7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA8AAABpblRleHR1cmVDb29yZHMAAQAAAAAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABYQACAAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAADkAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5ID0gbWF4KHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHksIDAuMCk7CgloYWxmIHJpZ2h0QWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVpbm5lclJlY3RfUzEuUiAtIHNrX0ZyYWdDb29yZC54KSk7CgloYWxmIGJvdHRvbUFscGhhID0gaGFsZihzYXR1cmF0ZSh1aW5uZXJSZWN0X1MxLkIgLSBza19GcmFnQ29vcmQueSkpOwoJaGFsZiBhbHBoYSA9IGJvdHRvbUFscGhhICogcmlnaHRBbHBoYSAqIGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY3VsYXJSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","BYIBQAAABQAAIAABBYAAAEIXBAAP777777777777AAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CAAAAExTS1M+AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwpvdXQgaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IGNvbG9yID0gaW5Db2xvcjsKCXZjb2xvcl9TMCA9IGNvbG9yOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzNfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IF90bXBfMV9pblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAHgEAAGluIGhhbGY0IHZjb2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIERlZmF1bHRHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IAAQAAAAAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAMQAHOMFARUBIAADAAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAAAcBQAAY29uc3QgaW50IGtGaWxsQldfUzEgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgY292ZXJhZ2U7CglpZiAoaW50KDEpID09IGtGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TMS56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fUzEueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCX0KCWVsc2UgCgl7CgkJaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fUzEpLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJaWYgKGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gaGFsZjQoX2lucHV0ICogY292ZXJhZ2UpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAABAEAAAABJYQAAAAAACAIAAAAAWCBAAAIBAAAAANAECAZAAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAADEGAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1XzBfSW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVfMV9LZXJuZWxfUzFfYzBbNF07CnVuaWZvcm0gaGFsZjQgdV8yX09mZnNldHNfUzFfYzBbNF07CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJZmxvYXQyIGluQ29vcmQgPSBfY29vcmRzOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBzdWJzZXRDb29yZC54OwoJY2xhbXBlZENvb3JkLnkgPSBjbGFtcChzdWJzZXRDb29yZC55LCB1Y2xhbXBfUzFfYzBfYzBfYzAueSwgdWNsYW1wX1MxX2MwX2MwX2MwLncpOwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKF9pbnB1dCwgZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMCkgKiBfY29vcmRzLnh5MSk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfM19jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzVfY29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKCWZvciAoaW50IF82X2kgPSAwOyAoXzZfaSA8IDEzKTsgXzZfaSsrKSAoXzNfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfaW5wdXQsIChfNV9jb29yZCArIGZsb2F0MigodV8yX09mZnNldHNfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0gKiB1XzBfSW5jcmVtZW50X1MxX2MwKSkpKSAqIHVfMV9LZXJuZWxfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0pKTsKCXJldHVybiBfM19jb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQBAEAQAAAAGQCBAMQACAIAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAGUDAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkID0gY2xhbXAoc3Vic2V0Q29vcmQsIHVjbGFtcF9TMV9jMC54eSwgdWNsYW1wX1MxX2MwLnp3KTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAAAAEAAAABJYQAAAAAAAAIAAAAAWCBAAAABAAAAANAECAZAAAAAAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAPIEAAB1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzA7CnVuaWZvcm0gaGFsZjIgdV8wX0luY3JlbWVudF9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1XzFfS2VybmVsX1MxX2MwWzRdOwp1bmlmb3JtIGhhbGY0IHVfMl9PZmZzZXRzX1MxX2MwWzRdOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIF9jb29yZHMpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBHYXVzc2lhbkNvbnZvbHV0aW9uX1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF8zX2NvbG9yID0gaGFsZjQoMC4wKTsKCWZsb2F0MiBfNV9jb29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZm9yIChpbnQgXzZfaSA9IDA7IChfNl9pIDwgMTMpOyBfNl9pKyspIChfM19jb2xvciArPSAoTWF0cml4RWZmZWN0X1MxX2MwX2MwKF9pbnB1dCwgKF81X2Nvb3JkICsgZmxvYXQyKCh1XzJfT2Zmc2V0c19TMV9jMFsoXzZfaSAvIDQpXVsoXzZfaSAmIDMpXSAqIHVfMF9JbmNyZW1lbnRfUzFfYzApKSkpICogdV8xX0tlcm5lbF9TMV9jMFsoXzZfaSAvIDQpXVsoXzZfaSAmIDMpXSkpOwoJcmV0dXJuIF8zX2NvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwID0gaGFsZjQoMSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gTWF0cml4RWZmZWN0X1MxKG91dHB1dENvbG9yX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfUzEgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","B2ABSAAABQAAIAABBYAAB7777777777774ABICAAAAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CAAAAExTS1N4AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gaGFsZjQgdUNvbG9yX1MwOwppbiBmbG9hdDIgaW5Qb3NpdGlvbjsKaW4gaGFsZiBpbkNvdmVyYWdlOwpvdXQgaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IGNvbG9yID0gdUNvbG9yX1MwOwoJY29sb3IgPSBjb2xvciAqIGluQ292ZXJhZ2U7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8zX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzFfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAeAQAAaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAoAAABpblBvc2l0aW9uAAAKAAAAaW5Db3ZlcmFnZQAAAQAAAAAAAAA=","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CAAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoBAAAAuAIAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzE7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABYQA6AAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACRAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAACAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAGIBIAAABAAAAANAEAAAAAAAAAAAAAABAAOAAAABAAAAAAABBAMAAAAA":"CAAAAExTS1N0AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMl9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAIsCAAB1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwKS5ycnJyOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gTWF0cml4RWZmZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","AYTRVAADQAAAOAEARAFQJAABBADAAAILBYAACCYUQD777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1NyAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7CmluIGhhbGYzIGluQ2xpcFBsYW5lOwppbiBoYWxmMyBpbklzZWN0UGxhbmU7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGYzIHZpbkNsaXBQbGFuZV9TMDsKb3V0IGhhbGYzIHZpbklzZWN0UGxhbmVfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCXZpbkNpcmNsZUVkZ2VfUzAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5DbGlwUGxhbmVfUzAgPSBpbkNsaXBQbGFuZTsKCXZpbklzZWN0UGxhbmVfUzAgPSBpbklzZWN0UGxhbmU7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gdWxvY2FsTWF0cml4X1MwLnh6ICogaW5Qb3NpdGlvbiArIHVsb2NhbE1hdHJpeF9TMC55dzsKCXNrX1Bvc2l0aW9uID0gX3RtcF8wX2luUG9zaXRpb24ueHkwMTsKfQoAAAAAAAD1AwAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGYzIHZpbkNsaXBQbGFuZV9TMDsKaW4gaGFsZjMgdmluSXNlY3RQbGFuZV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGYzIGNsaXBQbGFuZTsKCWNsaXBQbGFuZSA9IHZpbkNsaXBQbGFuZV9TMDsKCWhhbGYzIGlzZWN0UGxhbmU7Cglpc2VjdFBsYW5lID0gdmluSXNlY3RQbGFuZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZiBkaXN0YW5jZVRvSW5uZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoZCAtIGNpcmNsZUVkZ2UudykpOwoJaGFsZiBpbm5lckFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb0lubmVyRWRnZSk7CgllZGdlQWxwaGEgKj0gaW5uZXJBbHBoYTsKCWhhbGYgY2xpcCA9IGhhbGYoc2F0dXJhdGUoY2lyY2xlRWRnZS56ICogZG90KGNpcmNsZUVkZ2UueHksIGNsaXBQbGFuZS54eSkgKyBjbGlwUGxhbmUueikpOwoJY2xpcCAqPSBoYWxmKHNhdHVyYXRlKGNpcmNsZUVkZ2UueiAqIGRvdChjaXJjbGVFZGdlLnh5LCBpc2VjdFBsYW5lLnh5KSArIGlzZWN0UGxhbmUueikpOwoJZWRnZUFscGhhICo9IGNsaXA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAFAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2ULAAAAaW5DbGlwUGxhbmUADAAAAGluSXNlY3RQbGFuZQEAAAAAAAAA","HVIACAAAABQAAGAAAQ4AAAAAGQQAARC4GAAAIOCAAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAADAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAYAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAIAAQAAAAAQGIA":"CAAAAExTS1PlAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdCBjb3ZlcmFnZTsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdCB2Y292ZXJhZ2VfUzA7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwoJewoJCXZUcmFuc2Zvcm1lZENvb3Jkc181X1MwID0gZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMSkgKiBsb2NhbENvb3JkLnh5MTsKCX0KfQoAAAAAAAAAMAcAAHVuaWZvcm0gaGFsZjQgdXN0YXJ0X1MxX2MwX2MwOwp1bmlmb3JtIGhhbGY0IHVlbmRfUzFfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKdW5pZm9ybSBoYWxmNCB1bGVmdEJvcmRlckNvbG9yX1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVyaWdodEJvcmRlckNvbG9yX1MxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQgdmNvdmVyYWdlX1MwOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFNpbmdsZUludGVydmFsQ29sb3JpemVyX1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzFfY29vcmRzID0gX2Nvb3JkczsKCXJldHVybiBoYWxmNChtaXgodXN0YXJ0X1MxX2MwX2MwLCB1ZW5kX1MxX2MwX2MwLCBoYWxmKF90bXBfMV9jb29yZHMueCkpKTsKfQpoYWxmNCBMaW5lYXJMYXlvdXRfUzFfYzBfYzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8yX2luQ29sb3IgPSBfaW5wdXQ7CglmbG9hdDIgX3RtcF8zX2Nvb3JkcyA9IHZUcmFuc2Zvcm1lZENvb3Jkc181X1MwOwoJcmV0dXJuIGhhbGY0KGhhbGY0KGhhbGYoX3RtcF8zX2Nvb3Jkcy54KSArIDkuOTk5OTk5NzQ3Mzc4NzUxNmUtMDYsIDEuMCwgMC4wLCAwLjApKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIExpbmVhckxheW91dF9TMV9jMF9jMV9jMChfaW5wdXQpOwp9CmhhbGY0IENsYW1wZWRHcmFkaWVudF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzRfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGY0IHQgPSBNYXRyaXhFZmZlY3RfUzFfYzBfYzEoX3RtcF80X2luQ29sb3IpOwoJaGFsZjQgb3V0Q29sb3I7CglpZiAoIWJvb2woaW50KDEpKSAmJiB0LnkgPCAwLjApIAoJewoJCW91dENvbG9yID0gaGFsZjQoMC4wKTsKCX0KCWVsc2UgaWYgKHQueCA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSB1bGVmdEJvcmRlckNvbG9yX1MxX2MwOwoJfQoJZWxzZSBpZiAodC54ID4gMS4wKSAKCXsKCQlvdXRDb2xvciA9IHVyaWdodEJvcmRlckNvbG9yX1MxX2MwOwoJfQoJZWxzZSAKCXsKCQlvdXRDb2xvciA9IFNpbmdsZUludGVydmFsQ29sb3JpemVyX1MxX2MwX2MwKF90bXBfNF9pbkNvbG9yLCBmbG9hdDIoaGFsZjIodC54LCAwLjApKSk7Cgl9CglpZiAoYm9vbChpbnQoMSkpKSAKCXsKCQlvdXRDb2xvci54eXogKj0gb3V0Q29sb3IudzsKCX0KCXJldHVybiBoYWxmNChvdXRDb2xvcik7Cn0KaGFsZjQgRGlzYWJsZUNvdmVyYWdlQXNBbHBoYV9TMShoYWxmNCBfaW5wdXQpIAp7CglfaW5wdXQgPSBDbGFtcGVkR3JhZGllbnRfUzFfYzAoX2lucHV0KTsKCWhhbGY0IF90bXBfNV9pbkNvbG9yID0gX2lucHV0OwoJcmV0dXJuIGhhbGY0KF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWZsb2F0IGNvdmVyYWdlID0gdmNvdmVyYWdlX1MwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChoYWxmKGNvdmVyYWdlKSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKG91dHB1dENvbG9yX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSAoaGFsZjQoMS4wKSAtIG91dHB1dF9TMSkgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAEAAAACAAAAHBvc2l0aW9uCAAAAGNvdmVyYWdlBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","AYAA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1OCAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCXZpbkNpcmNsZUVkZ2VfUzAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMl9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gX3RtcF8wX2luUG9zaXRpb24ueHkwMTsKfQoAAAAAAAACAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAEDAACATAAABAGYAAAICSBYQCA4AAAAAAEADZABYAAAIAAAAAACQAGAAAAAQAAAAAAAQQG":"CAAAAExTS1OxCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDIgdmFyY2Nvb3JkX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCWZsb2F0IGFhX2Jsb2F0X211bHRpcGxpZXIgPSAxOwoJZmxvYXQyIGNvcm5lciA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMueHk7CglmbG9hdDIgcmFkaXVzX291dHNldCA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMuenc7CglmbG9hdDIgYWFfYmxvYXRfZGlyZWN0aW9uID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnh5OwoJZmxvYXQgaXNfbGluZWFyX2NvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnc7CglmbG9hdDIgcGl4ZWxsZW5ndGggPSBpbnZlcnNlc3FydChmbG9hdDIoZG90KHNrZXcueHosIHNrZXcueHopLCBkb3Qoc2tldy55dywgc2tldy55dykpKTsKCWZsb2F0NCBub3JtYWxpemVkX2F4aXNfZGlycyA9IHNrZXcgKiBwaXhlbGxlbmd0aC54eXh5OwoJZmxvYXQyIGF4aXN3aWR0aHMgPSAoYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnh5KSArIGFicyhub3JtYWxpemVkX2F4aXNfZGlycy56dykpOwoJZmxvYXQyIGFhX2Jsb2F0cmFkaXVzID0gYXhpc3dpZHRocyAqIHBpeGVsbGVuZ3RoICogLjU7CglmbG9hdDQgcmFkaWlfYW5kX25laWdoYm9ycyA9IHJhZGlpX3NlbGVjdG9yKiBmbG9hdDR4NChyYWRpaV94LCByYWRpaV95LCByYWRpaV94Lnl4d3osIHJhZGlpX3kud3p5eCk7CglmbG9hdDIgcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnh5OwoJZmxvYXQyIG5laWdoYm9yX3JhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy56dzsKCWZsb2F0IGNvdmVyYWdlX211bHRpcGxpZXIgPSAxOwoJaWYgKGFueShncmVhdGVyVGhhbihhYV9ibG9hdHJhZGl1cywgZmxvYXQyKDEpKSkpIAoJewoJCWNvcm5lciA9IG1heChhYnMoY29ybmVyKSwgYWFfYmxvYXRyYWRpdXMpICogc2lnbihjb3JuZXIpOwoJCWNvdmVyYWdlX211bHRpcGxpZXIgPSAxIC8gKG1heChhYV9ibG9hdHJhZGl1cy54LCAxKSAqIG1heChhYV9ibG9hdHJhZGl1cy55LCAxKSk7CgkJcmFkaWkgPSBmbG9hdDIoMCk7Cgl9CglmbG9hdCBjb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS56OwoJaWYgKGFueShsZXNzVGhhbihyYWRpaSwgYWFfYmxvYXRyYWRpdXMgKiAxLjUpKSkgCgl7CgkJcmFkaWkgPSBmbG9hdDIoMCk7CgkJYWFfYmxvYXRfZGlyZWN0aW9uID0gc2lnbihjb3JuZXIpOwoJCWlmIChjb3ZlcmFnZSA+IC41KSAKCQl7CgkJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IC1hYV9ibG9hdF9kaXJlY3Rpb247CgkJfQoJCWlzX2xpbmVhcl9jb3ZlcmFnZSA9IDE7Cgl9CgllbHNlIAoJewoJCXJhZGlpID0gY2xhbXAocmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCW5laWdoYm9yX3JhZGlpID0gY2xhbXAobmVpZ2hib3JfcmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCWZsb2F0MiBzcGFjaW5nID0gMiAtIHJhZGlpIC0gbmVpZ2hib3JfcmFkaWk7CgkJZmxvYXQyIGV4dHJhX3BhZCA9IG1heChwaXhlbGxlbmd0aCAqIC4wNjI1IC0gc3BhY2luZywgZmxvYXQyKDApKTsKCQlyYWRpaSAtPSBleHRyYV9wYWQgKiAuNTsKCX0KCWZsb2F0MiBhYV9vdXRzZXQgPSBhYV9ibG9hdF9kaXJlY3Rpb24gKiBhYV9ibG9hdHJhZGl1cyAqIGFhX2Jsb2F0X211bHRpcGxpZXI7CglmbG9hdDIgdmVydGV4cG9zID0gY29ybmVyICsgcmFkaXVzX291dHNldCAqIHJhZGlpICsgYWFfb3V0c2V0OwoJaWYgKGNvdmVyYWdlID4gLjUpIAoJewoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueCAhPSAwICYmIHZlcnRleHBvcy54ICogY29ybmVyLnggPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLngpOwoJCQl2ZXJ0ZXhwb3MueCA9IDA7CgkJCXZlcnRleHBvcy55ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci55KSAqIHBpeGVsbGVuZ3RoLnkvcGl4ZWxsZW5ndGgueDsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLngpIC8gKGFicyhjb3JuZXIueCkgKyBiYWNrc2V0KSArIC41OwoJCX0KCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnkgIT0gMCAmJiB2ZXJ0ZXhwb3MueSAqIGNvcm5lci55IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy55KTsKCQkJdmVydGV4cG9zLnkgPSAwOwoJCQl2ZXJ0ZXhwb3MueCArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueCkgKiBwaXhlbGxlbmd0aC54L3BpeGVsbGVuZ3RoLnk7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci55KSAvIChhYnMoY29ybmVyLnkpICsgYmFja3NldCkgKyAuNTsKCQl9Cgl9CglmbG9hdDJ4MiBza2V3bWF0cml4ID0gZmxvYXQyeDIoc2tldy54eSwgc2tldy56dyk7CglmbG9hdDIgZGV2Y29vcmQgPSB2ZXJ0ZXhwb3MgKiBza2V3bWF0cml4ICsgdHJhbnNsYXRlOwoJaWYgKDAgIT0gaXNfbGluZWFyX2NvdmVyYWdlKSAKCXsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoYXJjY29vcmQueCsxLCBhcmNjb29yZC55KTsKCX0KCXNrX1Bvc2l0aW9uID0gZGV2Y29vcmQueHkwMTsKfQoAAAABAAAA7AMAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzE7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZhcmNjb29yZF9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWZsb2F0IHhfcGx1c18xPXZhcmNjb29yZF9TMC54LCB5PXZhcmNjb29yZF9TMC55OwoJaGFsZiBjb3ZlcmFnZTsKCWlmICgwID09IHhfcGx1c18xKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoeSk7Cgl9CgllbHNlIAoJewoJCWZsb2F0IGZuID0geF9wbHVzXzEgKiAoeF9wbHVzXzEgLSAyKTsKCQlmbiA9IGZtYSh5LHksIGZuKTsKCQlmbG9hdCBmbndpZHRoID0gZndpZHRoKGZuKTsKCQljb3ZlcmFnZSA9IC41IC0gaGFsZihmbi9mbndpZHRoKTsKCQljb3ZlcmFnZSA9IGNsYW1wKGNvdmVyYWdlLCAwLCAxKTsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoY292ZXJhZ2UpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY3VsYXJSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABAAAAHNrZXcJAAAAdHJhbnNsYXRlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABQAAAGNvbG9yAAAAAQAAAAAAAAA=","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAEQQGABZAA6IAAAAACAAAAAADUAAAAAAAEAAAAAIDEAAAAAAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABPAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlckV4dGVybmFsT0VTIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAIBSQB5VRECGAEAAAMAAAAAAAAAAACAA4AAAACAAAAAAACCAYAA":"CAAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAABAAAA4wQAAGNvbnN0IGludCBrRmlsbEJXX1MxID0gMDsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEJXX1MxID0gMjsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEFBX1MxID0gMzsKdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1MwOwpmbGF0IGluIGZsb2F0IHZUZXhJbmRleF9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGNvdmVyYWdlOwoJaWYgKGludCgxKSA9PSBrRmlsbEJXX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKGFsbChncmVhdGVyVGhhbihmbG9hdDQoc2tfRnJhZ0Nvb3JkLnh5LCB1cmVjdFVuaWZvcm1fUzEuencpLCBmbG9hdDQodXJlY3RVbmlmb3JtX1MxLnh5LCBza19GcmFnQ29vcmQueHkpKSkgPyAxIDogMCk7Cgl9CgllbHNlIAoJewoJCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEuMCwgMS4wLCAtMS4wLCAtMS4wKSAqIGhhbGY0KHNrX0ZyYWdDb29yZC54eXh5IC0gdXJlY3RVbmlmb3JtX1MxKSwgMC4wLCAxLjApOwoJCWhhbGYyIGRpc3RzMiA9IChkaXN0czQueHkgKyBkaXN0czQuencpIC0gMS4wOwoJCWNvdmVyYWdlID0gZGlzdHMyLnggKiBkaXN0czIueTsKCX0KCWlmIChpbnQoMSkgPT0ga0ludmVyc2VGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEFBX1MxKSAKCXsKCQljb3ZlcmFnZSA9IDEuMCAtIGNvdmVyYWdlOwoJfQoJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIGNvdmVyYWdlKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZjQgdGV4Q29sb3I7Cgl7CgkJdGV4Q29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHZUZXh0dXJlQ29vcmRzX1MwKS5ycnJyOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSB0ZXhDb2xvcjsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IFJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA8AAABpblRleHR1cmVDb29yZHMAAQAAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQLAAAAAAIAAEAAAABJWQAAAAAQAAIAAAAAWCBACAABAAAAANAECAZAAEAAAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAADEGAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1XzBfSW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVfMV9LZXJuZWxfUzFfYzBbM107CnVuaWZvcm0gaGFsZjQgdV8yX09mZnNldHNfUzFfYzBbM107CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJZmxvYXQyIGluQ29vcmQgPSBfY29vcmRzOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBjbGFtcChzdWJzZXRDb29yZC54LCB1Y2xhbXBfUzFfYzBfYzBfYzAueCwgdWNsYW1wX1MxX2MwX2MwX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKF9pbnB1dCwgZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMCkgKiBfY29vcmRzLnh5MSk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfM19jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzVfY29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKCWZvciAoaW50IF82X2kgPSAwOyAoXzZfaSA8IDEyKTsgXzZfaSsrKSAoXzNfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfaW5wdXQsIChfNV9jb29yZCArIGZsb2F0MigodV8yX09mZnNldHNfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0gKiB1XzBfSW5jcmVtZW50X1MxX2MwKSkpKSAqIHVfMV9LZXJuZWxfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0pKTsKCXJldHVybiBfM19jb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGACQAGAAAAAQAAAAAAAQQGAAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAAAAAC3AQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGZsb2F0MiB2bG9jYWxDb29yZF9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGAARAGQWMHGBRIAAAAABQAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABhBAAAY29uc3QgaW50IGtGaWxsQUFfUzEgPSAxOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1Y2lyY2xlX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZsb2NhbENvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjbGVfUzEoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGQ7CglpZiAoaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMSkgCgl7CgkJZCA9IGhhbGYoKGxlbmd0aCgodWNpcmNsZV9TMS54eSAtIHNrX0ZyYWdDb29yZC54eSkgKiB1Y2lyY2xlX1MxLncpIC0gMS4wKSAqIHVjaXJjbGVfUzEueik7Cgl9CgllbHNlIAoJewoJCWQgPSBoYWxmKCgxLjAgLSBsZW5ndGgoKHVjaXJjbGVfUzEueHkgLSBza19GcmFnQ29vcmQueHkpICogdWNpcmNsZV9TMS53KSkgKiB1Y2lyY2xlX1MxLnopOwoJfQoJaWYgKGludCgxKSA9PSBrRmlsbEFBX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMSkgCgl7CgkJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIHNhdHVyYXRlKGQpKTsKCX0KCWVsc2UgCgl7CgkJcmV0dXJuIGhhbGY0KGQgPiAwLjUgPyBfaW5wdXQgOiBoYWxmNCgwLjApKTsKCX0KfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJZmxvYXQyIHRleENvb3JkOwoJdGV4Q29vcmQgPSB2bG9jYWxDb29yZF9TMDsKCW91dHB1dENvbG9yX1MwID0gKChzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHRleENvb3JkKSAqIGhhbGY0KDEpKSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY2xlX1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","EABQAAAAAEAAAAAQAABQAAIOAAABCFYIAAKAUDAAAAAAAAABAAAAAAAAAAANAAIAAAABAAAAACAJAAIAAAAA":"CAAAAExTS1OhAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc0RpbWVuc2lvbnNJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgZmxvYXQyIHZJbnRUZXh0dXJlQ29vcmRzX1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIERpc3RhbmNlRmllbGRQYXRoCglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkcyAqIHVBdGxhc0RpbWVuc2lvbnNJbnZfUzA7Cgl2VGV4SW5kZXhfUzAgPSBmbG9hdCh0ZXhJZHgpOwoJdkludFRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkczsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8yX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAAAAC3AgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBpbiBmbG9hdCB2VGV4SW5kZXhfUzA7CmluIGZsb2F0MiB2SW50VGV4dHVyZUNvb3Jkc19TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBEaXN0YW5jZUZpZWxkUGF0aAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQyIHV2ID0gdlRleHR1cmVDb29yZHNfUzA7CgloYWxmNCB0ZXhDb2xvcjsKCXsKCQl0ZXhDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdXYpLnJycnI7Cgl9CgloYWxmIGRpc3RhbmNlID0gNy45Njg3NSoodGV4Q29sb3IuciAtIDAuNTAxOTYwNzg0MzEpOwoJaGFsZiBhZndpZHRoOwoJYWZ3aWR0aCA9IGFicygwLjY1KmhhbGYoZEZkeCh2SW50VGV4dHVyZUNvb3Jkc19TMC54KSkpOwoJaGFsZiB2YWwgPSBzbW9vdGhzdGVwKC1hZndpZHRoLCBhZndpZHRoLCBkaXN0YW5jZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KHZhbCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAPAAAAaW5UZXh0dXJlQ29vcmRzAAEAAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAIAAEAAAABJYQAAAAAQAAIAAAAAWCBACAABAAAAANAECAZAAEAAAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAADEGAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1XzBfSW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVfMV9LZXJuZWxfUzFfYzBbNF07CnVuaWZvcm0gaGFsZjQgdV8yX09mZnNldHNfUzFfYzBbNF07CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJZmxvYXQyIGluQ29vcmQgPSBfY29vcmRzOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBjbGFtcChzdWJzZXRDb29yZC54LCB1Y2xhbXBfUzFfYzBfYzBfYzAueCwgdWNsYW1wX1MxX2MwX2MwX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKF9pbnB1dCwgZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMCkgKiBfY29vcmRzLnh5MSk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfM19jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzVfY29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKCWZvciAoaW50IF82X2kgPSAwOyAoXzZfaSA8IDEzKTsgXzZfaSsrKSAoXzNfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfaW5wdXQsIChfNV9jb29yZCArIGZsb2F0MigodV8yX09mZnNldHNfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0gKiB1XzBfSW5jcmVtZW50X1MxX2MwKSkpKSAqIHVfMV9LZXJuZWxfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0pKTsKCXJldHVybiBfM19jb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAEDAACATAAABAGYAAAICSBYQCA4AAAAAAEAZIA62YSBDACAAAGAAAAAAAAAAABAAOAAAABAAAAAAABBAMAA":"CAAAAExTS1OxCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDIgdmFyY2Nvb3JkX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCWZsb2F0IGFhX2Jsb2F0X211bHRpcGxpZXIgPSAxOwoJZmxvYXQyIGNvcm5lciA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMueHk7CglmbG9hdDIgcmFkaXVzX291dHNldCA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMuenc7CglmbG9hdDIgYWFfYmxvYXRfZGlyZWN0aW9uID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnh5OwoJZmxvYXQgaXNfbGluZWFyX2NvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnc7CglmbG9hdDIgcGl4ZWxsZW5ndGggPSBpbnZlcnNlc3FydChmbG9hdDIoZG90KHNrZXcueHosIHNrZXcueHopLCBkb3Qoc2tldy55dywgc2tldy55dykpKTsKCWZsb2F0NCBub3JtYWxpemVkX2F4aXNfZGlycyA9IHNrZXcgKiBwaXhlbGxlbmd0aC54eXh5OwoJZmxvYXQyIGF4aXN3aWR0aHMgPSAoYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnh5KSArIGFicyhub3JtYWxpemVkX2F4aXNfZGlycy56dykpOwoJZmxvYXQyIGFhX2Jsb2F0cmFkaXVzID0gYXhpc3dpZHRocyAqIHBpeGVsbGVuZ3RoICogLjU7CglmbG9hdDQgcmFkaWlfYW5kX25laWdoYm9ycyA9IHJhZGlpX3NlbGVjdG9yKiBmbG9hdDR4NChyYWRpaV94LCByYWRpaV95LCByYWRpaV94Lnl4d3osIHJhZGlpX3kud3p5eCk7CglmbG9hdDIgcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnh5OwoJZmxvYXQyIG5laWdoYm9yX3JhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy56dzsKCWZsb2F0IGNvdmVyYWdlX211bHRpcGxpZXIgPSAxOwoJaWYgKGFueShncmVhdGVyVGhhbihhYV9ibG9hdHJhZGl1cywgZmxvYXQyKDEpKSkpIAoJewoJCWNvcm5lciA9IG1heChhYnMoY29ybmVyKSwgYWFfYmxvYXRyYWRpdXMpICogc2lnbihjb3JuZXIpOwoJCWNvdmVyYWdlX211bHRpcGxpZXIgPSAxIC8gKG1heChhYV9ibG9hdHJhZGl1cy54LCAxKSAqIG1heChhYV9ibG9hdHJhZGl1cy55LCAxKSk7CgkJcmFkaWkgPSBmbG9hdDIoMCk7Cgl9CglmbG9hdCBjb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS56OwoJaWYgKGFueShsZXNzVGhhbihyYWRpaSwgYWFfYmxvYXRyYWRpdXMgKiAxLjUpKSkgCgl7CgkJcmFkaWkgPSBmbG9hdDIoMCk7CgkJYWFfYmxvYXRfZGlyZWN0aW9uID0gc2lnbihjb3JuZXIpOwoJCWlmIChjb3ZlcmFnZSA+IC41KSAKCQl7CgkJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IC1hYV9ibG9hdF9kaXJlY3Rpb247CgkJfQoJCWlzX2xpbmVhcl9jb3ZlcmFnZSA9IDE7Cgl9CgllbHNlIAoJewoJCXJhZGlpID0gY2xhbXAocmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCW5laWdoYm9yX3JhZGlpID0gY2xhbXAobmVpZ2hib3JfcmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCWZsb2F0MiBzcGFjaW5nID0gMiAtIHJhZGlpIC0gbmVpZ2hib3JfcmFkaWk7CgkJZmxvYXQyIGV4dHJhX3BhZCA9IG1heChwaXhlbGxlbmd0aCAqIC4wNjI1IC0gc3BhY2luZywgZmxvYXQyKDApKTsKCQlyYWRpaSAtPSBleHRyYV9wYWQgKiAuNTsKCX0KCWZsb2F0MiBhYV9vdXRzZXQgPSBhYV9ibG9hdF9kaXJlY3Rpb24gKiBhYV9ibG9hdHJhZGl1cyAqIGFhX2Jsb2F0X211bHRpcGxpZXI7CglmbG9hdDIgdmVydGV4cG9zID0gY29ybmVyICsgcmFkaXVzX291dHNldCAqIHJhZGlpICsgYWFfb3V0c2V0OwoJaWYgKGNvdmVyYWdlID4gLjUpIAoJewoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueCAhPSAwICYmIHZlcnRleHBvcy54ICogY29ybmVyLnggPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLngpOwoJCQl2ZXJ0ZXhwb3MueCA9IDA7CgkJCXZlcnRleHBvcy55ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci55KSAqIHBpeGVsbGVuZ3RoLnkvcGl4ZWxsZW5ndGgueDsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLngpIC8gKGFicyhjb3JuZXIueCkgKyBiYWNrc2V0KSArIC41OwoJCX0KCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnkgIT0gMCAmJiB2ZXJ0ZXhwb3MueSAqIGNvcm5lci55IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy55KTsKCQkJdmVydGV4cG9zLnkgPSAwOwoJCQl2ZXJ0ZXhwb3MueCArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueCkgKiBwaXhlbGxlbmd0aC54L3BpeGVsbGVuZ3RoLnk7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci55KSAvIChhYnMoY29ybmVyLnkpICsgYmFja3NldCkgKyAuNTsKCQl9Cgl9CglmbG9hdDJ4MiBza2V3bWF0cml4ID0gZmxvYXQyeDIoc2tldy54eSwgc2tldy56dyk7CglmbG9hdDIgZGV2Y29vcmQgPSB2ZXJ0ZXhwb3MgKiBza2V3bWF0cml4ICsgdHJhbnNsYXRlOwoJaWYgKDAgIT0gaXNfbGluZWFyX2NvdmVyYWdlKSAKCXsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoYXJjY29vcmQueCsxLCBhcmNjb29yZC55KTsKCX0KCXNrX1Bvc2l0aW9uID0gZGV2Y29vcmQueHkwMTsKfQoAAAABAAAAdwUAAGNvbnN0IGludCBrRmlsbEJXX1MxID0gMDsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEJXX1MxID0gMjsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEFBX1MxID0gMzsKdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZhcmNjb29yZF9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgY292ZXJhZ2U7CglpZiAoaW50KDEpID09IGtGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TMS56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fUzEueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCX0KCWVsc2UgCgl7CgkJaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fUzEpLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJaWYgKGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gaGFsZjQoX2lucHV0ICogY292ZXJhZ2UpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1MwLngsIHk9dmFyY2Nvb3JkX1MwLnk7CgloYWxmIGNvdmVyYWdlOwoJaWYgKDAgPT0geF9wbHVzXzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZih5KTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQgZm4gPSB4X3BsdXNfMSAqICh4X3BsdXNfMSAtIDIpOwoJCWZuID0gZm1hKHkseSwgZm4pOwoJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABAAAAHNrZXcJAAAAdHJhbnNsYXRlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABQAAAGNvbG9yAAAAAQAAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAAAAIAAAABLAIABAAAAABAEGABBAMAAAAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAADnAgAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoX2lucHV0KTsKfQpoYWxmNCBCbGVuZF9TMShoYWxmNCBfc3JjLCBoYWxmNCBfZHN0KSAKewoJLy8gQmxlbmQgbW9kZTogTW9kdWxhdGUKCXJldHVybiBibGVuZF9tb2R1bGF0ZShNYXRyaXhFZmZlY3RfUzFfYzAoX3NyYyksIF9zcmMpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwID0gaGFsZjQoMSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQmxlbmRfUzEob3V0cHV0Q29sb3JfUzAsIGhhbGY0KDEpKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfUzEgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","GEMAAAYAAEHAAAARC4EAAAQWBQAAAAAAAAAQAAAAIBCAAAGQAEAAAAAQAAAABAEQAEAAAAA":"CAAAAExTS1NUAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBoYWxmMyBpblNoYWRvd1BhcmFtczsKb3V0IGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBSUmVjdFNoYWRvdwoJdmluU2hhZG93UGFyYW1zX1MwID0gaW5TaGFkb3dQYXJhbXM7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAjAgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBSUmVjdFNoYWRvdwoJaGFsZjMgc2hhZG93UGFyYW1zOwoJc2hhZG93UGFyYW1zID0gdmluU2hhZG93UGFyYW1zX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZiBkID0gbGVuZ3RoKHNoYWRvd1BhcmFtcy54eSk7CglmbG9hdDIgdXYgPSBmbG9hdDIoc2hhZG93UGFyYW1zLnogKiAoMS4wIC0gZCksIDAuNSk7CgloYWxmIGZhY3RvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdXYpLjAwMHIuYTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZmFjdG9yKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA4AAABpblNoYWRvd1BhcmFtcwAAAQAAAAAAAAA=","DASAAAAAQAAWAABAYAAQBYH7777Z6QQBAEAAAAAAEAAAAAAAEBSAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1PVAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBCaXRtYXBUZXh0CglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkcyAqIHVBdGxhc1NpemVJbnZfUzA7Cgl2VGV4SW5kZXhfUzAgPSBmbG9hdCh0ZXhJZHgpOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAAAAD4AQAAdW5pZm9ybSBoYWxmNCB1Q29sb3JfUzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdlRleHR1cmVDb29yZHNfUzA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHVDb2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCk7Cgl9CglvdXRwdXRDb2xvcl9TMCA9IG91dHB1dENvbG9yX1MwICogdGV4Q29sb3I7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAoAAABpblBvc2l0aW9uAAAPAAAAaW5UZXh0dXJlQ29vcmRzAAEAAAAAAAAA","HTQAAGAABBYAAAEIXBAAAGEAMAAAAAAAAAAAAAAAQAHAAAAAQAAAAAAAQQGAAAAA":"CAAAAExTS1M/AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5RdWFkRWRnZTsKb3V0IGZsb2F0NCB2UXVhZEVkZ2VfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZEVkZ2UKCXZRdWFkRWRnZV9TMCA9IGluUXVhZEVkZ2U7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgABAAAAHQMAAGluIGZsb2F0NCB2UXVhZEVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZEVkZ2UKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGYgZWRnZUFscGhhOwoJaGFsZjIgZHV2ZHggPSBoYWxmMihkRmR4KHZRdWFkRWRnZV9TMC54eSkpOwoJaGFsZjIgZHV2ZHkgPSBoYWxmMihkRmR5KHZRdWFkRWRnZV9TMC54eSkpOwoJaWYgKHZRdWFkRWRnZV9TMC56ID4gMC4wICYmIHZRdWFkRWRnZV9TMC53ID4gMC4wKSAKCXsKCQllZGdlQWxwaGEgPSBoYWxmKG1pbihtaW4odlF1YWRFZGdlX1MwLnosIHZRdWFkRWRnZV9TMC53KSArIDAuNSwgMS4wKSk7Cgl9CgllbHNlIAoJewoJCWhhbGYyIGdGID0gaGFsZjIoaGFsZigyLjAqdlF1YWRFZGdlX1MwLngqZHV2ZHgueCAtIGR1dmR4LnkpLCAgICAgICAgICAgICAgICAgaGFsZigyLjAqdlF1YWRFZGdlX1MwLngqZHV2ZHkueCAtIGR1dmR5LnkpKTsKCQllZGdlQWxwaGEgPSBoYWxmKHZRdWFkRWRnZV9TMC54KnZRdWFkRWRnZV9TMC54IC0gdlF1YWRFZGdlX1MwLnkpOwoJCWVkZ2VBbHBoYSA9IHNhdHVyYXRlKDAuNSAtIGVkZ2VBbHBoYSAvIGxlbmd0aChnRikpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAKAAAAaW5RdWFkRWRnZQAAAQAAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQLAAAAAAABAEAAAABJWQAAAAAACAIAAAAAWCBAAAIBAAAAANAECAZAAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAADEGAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1XzBfSW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVfMV9LZXJuZWxfUzFfYzBbM107CnVuaWZvcm0gaGFsZjQgdV8yX09mZnNldHNfUzFfYzBbM107CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJZmxvYXQyIGluQ29vcmQgPSBfY29vcmRzOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBzdWJzZXRDb29yZC54OwoJY2xhbXBlZENvb3JkLnkgPSBjbGFtcChzdWJzZXRDb29yZC55LCB1Y2xhbXBfUzFfYzBfYzBfYzAueSwgdWNsYW1wX1MxX2MwX2MwX2MwLncpOwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKF9pbnB1dCwgZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMCkgKiBfY29vcmRzLnh5MSk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfM19jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzVfY29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKCWZvciAoaW50IF82X2kgPSAwOyAoXzZfaSA8IDEyKTsgXzZfaSsrKSAoXzNfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfaW5wdXQsIChfNV9jb29yZCArIGZsb2F0MigodV8yX09mZnNldHNfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0gKiB1XzBfSW5jcmVtZW50X1MxX2MwKSkpKSAqIHVfMV9LZXJuZWxfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0pKTsKCXJldHVybiBfM19jb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABZQA6AAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACnAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCWFscGhhID0gMS4wIC0gYWxwaGE7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDQgY2lyY2xlRWRnZTsKCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAEDAACATAAABAGYAAAICSBYQCA4AAAAAAAA5AAAAAAABAAAAACAZAAAAA":"CAAAAExTS1OxCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDIgdmFyY2Nvb3JkX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCWZsb2F0IGFhX2Jsb2F0X211bHRpcGxpZXIgPSAxOwoJZmxvYXQyIGNvcm5lciA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMueHk7CglmbG9hdDIgcmFkaXVzX291dHNldCA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMuenc7CglmbG9hdDIgYWFfYmxvYXRfZGlyZWN0aW9uID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnh5OwoJZmxvYXQgaXNfbGluZWFyX2NvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnc7CglmbG9hdDIgcGl4ZWxsZW5ndGggPSBpbnZlcnNlc3FydChmbG9hdDIoZG90KHNrZXcueHosIHNrZXcueHopLCBkb3Qoc2tldy55dywgc2tldy55dykpKTsKCWZsb2F0NCBub3JtYWxpemVkX2F4aXNfZGlycyA9IHNrZXcgKiBwaXhlbGxlbmd0aC54eXh5OwoJZmxvYXQyIGF4aXN3aWR0aHMgPSAoYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnh5KSArIGFicyhub3JtYWxpemVkX2F4aXNfZGlycy56dykpOwoJZmxvYXQyIGFhX2Jsb2F0cmFkaXVzID0gYXhpc3dpZHRocyAqIHBpeGVsbGVuZ3RoICogLjU7CglmbG9hdDQgcmFkaWlfYW5kX25laWdoYm9ycyA9IHJhZGlpX3NlbGVjdG9yKiBmbG9hdDR4NChyYWRpaV94LCByYWRpaV95LCByYWRpaV94Lnl4d3osIHJhZGlpX3kud3p5eCk7CglmbG9hdDIgcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnh5OwoJZmxvYXQyIG5laWdoYm9yX3JhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy56dzsKCWZsb2F0IGNvdmVyYWdlX211bHRpcGxpZXIgPSAxOwoJaWYgKGFueShncmVhdGVyVGhhbihhYV9ibG9hdHJhZGl1cywgZmxvYXQyKDEpKSkpIAoJewoJCWNvcm5lciA9IG1heChhYnMoY29ybmVyKSwgYWFfYmxvYXRyYWRpdXMpICogc2lnbihjb3JuZXIpOwoJCWNvdmVyYWdlX211bHRpcGxpZXIgPSAxIC8gKG1heChhYV9ibG9hdHJhZGl1cy54LCAxKSAqIG1heChhYV9ibG9hdHJhZGl1cy55LCAxKSk7CgkJcmFkaWkgPSBmbG9hdDIoMCk7Cgl9CglmbG9hdCBjb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS56OwoJaWYgKGFueShsZXNzVGhhbihyYWRpaSwgYWFfYmxvYXRyYWRpdXMgKiAxLjUpKSkgCgl7CgkJcmFkaWkgPSBmbG9hdDIoMCk7CgkJYWFfYmxvYXRfZGlyZWN0aW9uID0gc2lnbihjb3JuZXIpOwoJCWlmIChjb3ZlcmFnZSA+IC41KSAKCQl7CgkJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IC1hYV9ibG9hdF9kaXJlY3Rpb247CgkJfQoJCWlzX2xpbmVhcl9jb3ZlcmFnZSA9IDE7Cgl9CgllbHNlIAoJewoJCXJhZGlpID0gY2xhbXAocmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCW5laWdoYm9yX3JhZGlpID0gY2xhbXAobmVpZ2hib3JfcmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCWZsb2F0MiBzcGFjaW5nID0gMiAtIHJhZGlpIC0gbmVpZ2hib3JfcmFkaWk7CgkJZmxvYXQyIGV4dHJhX3BhZCA9IG1heChwaXhlbGxlbmd0aCAqIC4wNjI1IC0gc3BhY2luZywgZmxvYXQyKDApKTsKCQlyYWRpaSAtPSBleHRyYV9wYWQgKiAuNTsKCX0KCWZsb2F0MiBhYV9vdXRzZXQgPSBhYV9ibG9hdF9kaXJlY3Rpb24gKiBhYV9ibG9hdHJhZGl1cyAqIGFhX2Jsb2F0X211bHRpcGxpZXI7CglmbG9hdDIgdmVydGV4cG9zID0gY29ybmVyICsgcmFkaXVzX291dHNldCAqIHJhZGlpICsgYWFfb3V0c2V0OwoJaWYgKGNvdmVyYWdlID4gLjUpIAoJewoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueCAhPSAwICYmIHZlcnRleHBvcy54ICogY29ybmVyLnggPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLngpOwoJCQl2ZXJ0ZXhwb3MueCA9IDA7CgkJCXZlcnRleHBvcy55ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci55KSAqIHBpeGVsbGVuZ3RoLnkvcGl4ZWxsZW5ndGgueDsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLngpIC8gKGFicyhjb3JuZXIueCkgKyBiYWNrc2V0KSArIC41OwoJCX0KCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnkgIT0gMCAmJiB2ZXJ0ZXhwb3MueSAqIGNvcm5lci55IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy55KTsKCQkJdmVydGV4cG9zLnkgPSAwOwoJCQl2ZXJ0ZXhwb3MueCArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueCkgKiBwaXhlbGxlbmd0aC54L3BpeGVsbGVuZ3RoLnk7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci55KSAvIChhYnMoY29ybmVyLnkpICsgYmFja3NldCkgKyAuNTsKCQl9Cgl9CglmbG9hdDJ4MiBza2V3bWF0cml4ID0gZmxvYXQyeDIoc2tldy54eSwgc2tldy56dyk7CglmbG9hdDIgZGV2Y29vcmQgPSB2ZXJ0ZXhwb3MgKiBza2V3bWF0cml4ICsgdHJhbnNsYXRlOwoJaWYgKDAgIT0gaXNfbGluZWFyX2NvdmVyYWdlKSAKCXsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoYXJjY29vcmQueCsxLCBhcmNjb29yZC55KTsKCX0KCXNrX1Bvc2l0aW9uID0gZGV2Y29vcmQueHkwMTsKfQoAAAAAAAAAXQIAAGZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWZsb2F0IHhfcGx1c18xPXZhcmNjb29yZF9TMC54LCB5PXZhcmNjb29yZF9TMC55OwoJaGFsZiBjb3ZlcmFnZTsKCWlmICgwID09IHhfcGx1c18xKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoeSk7Cgl9CgllbHNlIAoJewoJCWZsb2F0IGZuID0geF9wbHVzXzEgKiAoeF9wbHVzXzEgLSAyKTsKCQlmbiA9IGZtYSh5LHksIGZuKTsKCQlmbG9hdCBmbndpZHRoID0gZndpZHRoKGZuKTsKCQljb3ZlcmFnZSA9IC41IC0gaGFsZihmbi9mbndpZHRoKTsKCQljb3ZlcmFnZSA9IGNsYW1wKGNvdmVyYWdlLCAwLCAxKTsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoY292ZXJhZ2UpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABAAAAHNrZXcJAAAAdHJhbnNsYXRlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABQAAAGNvbG9yAAAAAQAAAAAAAAA=","AYQQ5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAMQAHOMFARUBIAADAAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACtBQAAY29uc3QgaW50IGtGaWxsQldfUzEgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgY292ZXJhZ2U7CglpZiAoaW50KDEpID09IGtGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TMS56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fUzEueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCX0KCWVsc2UgCgl7CgkJaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fUzEpLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJaWYgKGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gaGFsZjQoX2lucHV0ICogY292ZXJhZ2UpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZiBkaXN0YW5jZVRvSW5uZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoZCAtIGNpcmNsZUVkZ2UudykpOwoJaGFsZiBpbm5lckFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb0lubmVyRWRnZSk7CgllZGdlQWxwaGEgKj0gaW5uZXJBbHBoYTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IFJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQBAAAQAAAAGQCBAMQACAAAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAIgDAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBjbGFtcChzdWJzZXRDb29yZC54LCB1Y2xhbXBfUzFfYzAueCwgdWNsYW1wX1MxX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAIAAIAAAABLCIABAAAAABAEGABBAMAACAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAdBAAAdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZC54ID0gY2xhbXAoc3Vic2V0Q29vcmQueCwgdWNsYW1wX1MxX2MwX2MwLngsIHVjbGFtcF9TMV9jMF9jMC56KTsKCWNsYW1wZWRDb29yZC55ID0gc3Vic2V0Q29vcmQueTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQmxlbmRfUzEoaGFsZjQgX3NyYywgaGFsZjQgX2RzdCkgCnsKCS8vIEJsZW5kIG1vZGU6IE1vZHVsYXRlCglyZXR1cm4gYmxlbmRfbW9kdWxhdGUoTWF0cml4RWZmZWN0X1MxX2MwKF9zcmMpLCBfc3JjKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IEJsZW5kX1MxKG91dHB1dENvbG9yX1MwLCBoYWxmNCgxKSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CAAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoAAAAAKQEAAGZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAACEA2X4PLOGEAAAAAAAAACAAAAAVQQAAQAAAAAQCDIBCAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAAAAAAA":"CAAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoBAAAACwQAAHVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1Y2lyY2xlRGF0YV9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBfY29vcmRzKS4wMDByOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBDaXJjbGVCbHVyX1MxKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjIgdmVjID0gaGFsZjIoKHNrX0ZyYWdDb29yZC54eSAtIGZsb2F0Mih1Y2lyY2xlRGF0YV9TMS54eSkpICogZmxvYXQodWNpcmNsZURhdGFfUzEudykpOwoJaGFsZiBkaXN0ID0gbGVuZ3RoKHZlYykgKyAoMC41IC0gdWNpcmNsZURhdGFfUzEueikgKiB1Y2lyY2xlRGF0YV9TMS53OwoJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIE1hdHJpeEVmZmVjdF9TMV9jMChfdG1wXzBfaW5Db2xvciwgZmxvYXQyKGhhbGYyKGRpc3QsIDAuNSkpKS53KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmNsZUJsdXJfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGABZAA6IAAAAACAAAAAADUAAAAAAAEAAAAAIDEAAAAAAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABGAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA=="}} \ No newline at end of file diff --git a/shaders_2.10.3.sksl.json b/shaders_2.10.3.sksl.json new file mode 100644 index 000000000..1f5352f7e --- /dev/null +++ b/shaders_2.10.3.sksl.json @@ -0,0 +1 @@ +{"platform":"android","name":"SM G970N","engineRevision":"bd539267b42051b0da3d16ffa8f48949dce8aa8f","data":{"HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAACEA2X4PLOGEAAAAAAAAACAAAAAVQQAAQAAAAAQCDIBCAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAAAAAAA":"CAAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoBAAAACwQAAHVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1Y2lyY2xlRGF0YV9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBfY29vcmRzKS4wMDByOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBDaXJjbGVCbHVyX1MxKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjIgdmVjID0gaGFsZjIoKHNrX0ZyYWdDb29yZC54eSAtIGZsb2F0Mih1Y2lyY2xlRGF0YV9TMS54eSkpICogZmxvYXQodWNpcmNsZURhdGFfUzEudykpOwoJaGFsZiBkaXN0ID0gbGVuZ3RoKHZlYykgKyAoMC41IC0gdWNpcmNsZURhdGFfUzEueikgKiB1Y2lyY2xlRGF0YV9TMS53OwoJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIE1hdHJpeEVmZmVjdF9TMV9jMChfdG1wXzBfaW5Db2xvciwgZmxvYXQyKGhhbGYyKGRpc3QsIDAuNSkpKS53KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmNsZUJsdXJfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","DASAAAAAQAAWAABAYAAQBYH7777Z6QQBAEAAAAAAEAAAAAAAEBSAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1PVAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBCaXRtYXBUZXh0CglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkcyAqIHVBdGxhc1NpemVJbnZfUzA7Cgl2VGV4SW5kZXhfUzAgPSBmbG9hdCh0ZXhJZHgpOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAAAAD4AQAAdW5pZm9ybSBoYWxmNCB1Q29sb3JfUzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdlRleHR1cmVDb29yZHNfUzA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHVDb2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCk7Cgl9CglvdXRwdXRDb2xvcl9TMCA9IG91dHB1dENvbG9yX1MwICogdGV4Q29sb3I7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAoAAABpblBvc2l0aW9uAAAPAAAAaW5UZXh0dXJlQ29vcmRzAAEAAAAAAAAA","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAEDAACATAAABAGYAAAICSBYQCA4AAAAAAEADZABYAAAIAAAAAACQAGAAAAAQAAAAAAAQQG":"CAAAAExTS1OxCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDIgdmFyY2Nvb3JkX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCWZsb2F0IGFhX2Jsb2F0X211bHRpcGxpZXIgPSAxOwoJZmxvYXQyIGNvcm5lciA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMueHk7CglmbG9hdDIgcmFkaXVzX291dHNldCA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMuenc7CglmbG9hdDIgYWFfYmxvYXRfZGlyZWN0aW9uID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnh5OwoJZmxvYXQgaXNfbGluZWFyX2NvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnc7CglmbG9hdDIgcGl4ZWxsZW5ndGggPSBpbnZlcnNlc3FydChmbG9hdDIoZG90KHNrZXcueHosIHNrZXcueHopLCBkb3Qoc2tldy55dywgc2tldy55dykpKTsKCWZsb2F0NCBub3JtYWxpemVkX2F4aXNfZGlycyA9IHNrZXcgKiBwaXhlbGxlbmd0aC54eXh5OwoJZmxvYXQyIGF4aXN3aWR0aHMgPSAoYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnh5KSArIGFicyhub3JtYWxpemVkX2F4aXNfZGlycy56dykpOwoJZmxvYXQyIGFhX2Jsb2F0cmFkaXVzID0gYXhpc3dpZHRocyAqIHBpeGVsbGVuZ3RoICogLjU7CglmbG9hdDQgcmFkaWlfYW5kX25laWdoYm9ycyA9IHJhZGlpX3NlbGVjdG9yKiBmbG9hdDR4NChyYWRpaV94LCByYWRpaV95LCByYWRpaV94Lnl4d3osIHJhZGlpX3kud3p5eCk7CglmbG9hdDIgcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnh5OwoJZmxvYXQyIG5laWdoYm9yX3JhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy56dzsKCWZsb2F0IGNvdmVyYWdlX211bHRpcGxpZXIgPSAxOwoJaWYgKGFueShncmVhdGVyVGhhbihhYV9ibG9hdHJhZGl1cywgZmxvYXQyKDEpKSkpIAoJewoJCWNvcm5lciA9IG1heChhYnMoY29ybmVyKSwgYWFfYmxvYXRyYWRpdXMpICogc2lnbihjb3JuZXIpOwoJCWNvdmVyYWdlX211bHRpcGxpZXIgPSAxIC8gKG1heChhYV9ibG9hdHJhZGl1cy54LCAxKSAqIG1heChhYV9ibG9hdHJhZGl1cy55LCAxKSk7CgkJcmFkaWkgPSBmbG9hdDIoMCk7Cgl9CglmbG9hdCBjb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS56OwoJaWYgKGFueShsZXNzVGhhbihyYWRpaSwgYWFfYmxvYXRyYWRpdXMgKiAxLjUpKSkgCgl7CgkJcmFkaWkgPSBmbG9hdDIoMCk7CgkJYWFfYmxvYXRfZGlyZWN0aW9uID0gc2lnbihjb3JuZXIpOwoJCWlmIChjb3ZlcmFnZSA+IC41KSAKCQl7CgkJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IC1hYV9ibG9hdF9kaXJlY3Rpb247CgkJfQoJCWlzX2xpbmVhcl9jb3ZlcmFnZSA9IDE7Cgl9CgllbHNlIAoJewoJCXJhZGlpID0gY2xhbXAocmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCW5laWdoYm9yX3JhZGlpID0gY2xhbXAobmVpZ2hib3JfcmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCWZsb2F0MiBzcGFjaW5nID0gMiAtIHJhZGlpIC0gbmVpZ2hib3JfcmFkaWk7CgkJZmxvYXQyIGV4dHJhX3BhZCA9IG1heChwaXhlbGxlbmd0aCAqIC4wNjI1IC0gc3BhY2luZywgZmxvYXQyKDApKTsKCQlyYWRpaSAtPSBleHRyYV9wYWQgKiAuNTsKCX0KCWZsb2F0MiBhYV9vdXRzZXQgPSBhYV9ibG9hdF9kaXJlY3Rpb24gKiBhYV9ibG9hdHJhZGl1cyAqIGFhX2Jsb2F0X211bHRpcGxpZXI7CglmbG9hdDIgdmVydGV4cG9zID0gY29ybmVyICsgcmFkaXVzX291dHNldCAqIHJhZGlpICsgYWFfb3V0c2V0OwoJaWYgKGNvdmVyYWdlID4gLjUpIAoJewoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueCAhPSAwICYmIHZlcnRleHBvcy54ICogY29ybmVyLnggPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLngpOwoJCQl2ZXJ0ZXhwb3MueCA9IDA7CgkJCXZlcnRleHBvcy55ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci55KSAqIHBpeGVsbGVuZ3RoLnkvcGl4ZWxsZW5ndGgueDsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLngpIC8gKGFicyhjb3JuZXIueCkgKyBiYWNrc2V0KSArIC41OwoJCX0KCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnkgIT0gMCAmJiB2ZXJ0ZXhwb3MueSAqIGNvcm5lci55IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy55KTsKCQkJdmVydGV4cG9zLnkgPSAwOwoJCQl2ZXJ0ZXhwb3MueCArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueCkgKiBwaXhlbGxlbmd0aC54L3BpeGVsbGVuZ3RoLnk7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci55KSAvIChhYnMoY29ybmVyLnkpICsgYmFja3NldCkgKyAuNTsKCQl9Cgl9CglmbG9hdDJ4MiBza2V3bWF0cml4ID0gZmxvYXQyeDIoc2tldy54eSwgc2tldy56dyk7CglmbG9hdDIgZGV2Y29vcmQgPSB2ZXJ0ZXhwb3MgKiBza2V3bWF0cml4ICsgdHJhbnNsYXRlOwoJaWYgKDAgIT0gaXNfbGluZWFyX2NvdmVyYWdlKSAKCXsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoYXJjY29vcmQueCsxLCBhcmNjb29yZC55KTsKCX0KCXNrX1Bvc2l0aW9uID0gZGV2Y29vcmQueHkwMTsKfQoAAAABAAAA7AMAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzE7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZhcmNjb29yZF9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWZsb2F0IHhfcGx1c18xPXZhcmNjb29yZF9TMC54LCB5PXZhcmNjb29yZF9TMC55OwoJaGFsZiBjb3ZlcmFnZTsKCWlmICgwID09IHhfcGx1c18xKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoeSk7Cgl9CgllbHNlIAoJewoJCWZsb2F0IGZuID0geF9wbHVzXzEgKiAoeF9wbHVzXzEgLSAyKTsKCQlmbiA9IGZtYSh5LHksIGZuKTsKCQlmbG9hdCBmbndpZHRoID0gZndpZHRoKGZuKTsKCQljb3ZlcmFnZSA9IC41IC0gaGFsZihmbi9mbndpZHRoKTsKCQljb3ZlcmFnZSA9IGNsYW1wKGNvdmVyYWdlLCAwLCAxKTsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoY292ZXJhZ2UpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY3VsYXJSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABAAAAHNrZXcJAAAAdHJhbnNsYXRlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABQAAAGNvbG9yAAAAAQAAAAAAAAA=","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGABZAA6IAAAAACAAAAAADUAAAAAAAEAAAAAIDEAAAAAAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABGAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAIBAIAAAABLCIIBAAAAABAEGABBAMAACAIAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAD6AwAAdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZCA9IGNsYW1wKHN1YnNldENvb3JkLCB1Y2xhbXBfUzFfYzBfYzAueHksIHVjbGFtcF9TMV9jMF9jMC56dyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMF9jMChfaW5wdXQpOwp9CmhhbGY0IEJsZW5kX1MxKGhhbGY0IF9zcmMsIGhhbGY0IF9kc3QpIAp7CgkvLyBCbGVuZCBtb2RlOiBNb2R1bGF0ZQoJcmV0dXJuIGJsZW5kX21vZHVsYXRlKE1hdHJpeEVmZmVjdF9TMV9jMChfc3JjKSwgX3NyYyk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBCbGVuZF9TMShvdXRwdXRDb2xvcl9TMCwgaGFsZjQoMSkpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HVIACAAAABQAAGAAAQ4AAAAAGQQAARC4GAAAIOCAAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAADAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAYAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAIAAQAAAAAQGIA":"CAAAAExTS1PlAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdCBjb3ZlcmFnZTsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdCB2Y292ZXJhZ2VfUzA7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwoJewoJCXZUcmFuc2Zvcm1lZENvb3Jkc181X1MwID0gZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMSkgKiBsb2NhbENvb3JkLnh5MTsKCX0KfQoAAAAAAAAAMAcAAHVuaWZvcm0gaGFsZjQgdXN0YXJ0X1MxX2MwX2MwOwp1bmlmb3JtIGhhbGY0IHVlbmRfUzFfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKdW5pZm9ybSBoYWxmNCB1bGVmdEJvcmRlckNvbG9yX1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVyaWdodEJvcmRlckNvbG9yX1MxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQgdmNvdmVyYWdlX1MwOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFNpbmdsZUludGVydmFsQ29sb3JpemVyX1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzFfY29vcmRzID0gX2Nvb3JkczsKCXJldHVybiBoYWxmNChtaXgodXN0YXJ0X1MxX2MwX2MwLCB1ZW5kX1MxX2MwX2MwLCBoYWxmKF90bXBfMV9jb29yZHMueCkpKTsKfQpoYWxmNCBMaW5lYXJMYXlvdXRfUzFfYzBfYzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8yX2luQ29sb3IgPSBfaW5wdXQ7CglmbG9hdDIgX3RtcF8zX2Nvb3JkcyA9IHZUcmFuc2Zvcm1lZENvb3Jkc181X1MwOwoJcmV0dXJuIGhhbGY0KGhhbGY0KGhhbGYoX3RtcF8zX2Nvb3Jkcy54KSArIDkuOTk5OTk5NzQ3Mzc4NzUxNmUtMDYsIDEuMCwgMC4wLCAwLjApKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIExpbmVhckxheW91dF9TMV9jMF9jMV9jMChfaW5wdXQpOwp9CmhhbGY0IENsYW1wZWRHcmFkaWVudF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzRfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGY0IHQgPSBNYXRyaXhFZmZlY3RfUzFfYzBfYzEoX3RtcF80X2luQ29sb3IpOwoJaGFsZjQgb3V0Q29sb3I7CglpZiAoIWJvb2woaW50KDEpKSAmJiB0LnkgPCAwLjApIAoJewoJCW91dENvbG9yID0gaGFsZjQoMC4wKTsKCX0KCWVsc2UgaWYgKHQueCA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSB1bGVmdEJvcmRlckNvbG9yX1MxX2MwOwoJfQoJZWxzZSBpZiAodC54ID4gMS4wKSAKCXsKCQlvdXRDb2xvciA9IHVyaWdodEJvcmRlckNvbG9yX1MxX2MwOwoJfQoJZWxzZSAKCXsKCQlvdXRDb2xvciA9IFNpbmdsZUludGVydmFsQ29sb3JpemVyX1MxX2MwX2MwKF90bXBfNF9pbkNvbG9yLCBmbG9hdDIoaGFsZjIodC54LCAwLjApKSk7Cgl9CglpZiAoYm9vbChpbnQoMSkpKSAKCXsKCQlvdXRDb2xvci54eXogKj0gb3V0Q29sb3IudzsKCX0KCXJldHVybiBoYWxmNChvdXRDb2xvcik7Cn0KaGFsZjQgRGlzYWJsZUNvdmVyYWdlQXNBbHBoYV9TMShoYWxmNCBfaW5wdXQpIAp7CglfaW5wdXQgPSBDbGFtcGVkR3JhZGllbnRfUzFfYzAoX2lucHV0KTsKCWhhbGY0IF90bXBfNV9pbkNvbG9yID0gX2lucHV0OwoJcmV0dXJuIGhhbGY0KF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWZsb2F0IGNvdmVyYWdlID0gdmNvdmVyYWdlX1MwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChoYWxmKGNvdmVyYWdlKSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKG91dHB1dENvbG9yX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSAoaGFsZjQoMS4wKSAtIG91dHB1dF9TMSkgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAEAAAACAAAAHBvc2l0aW9uCAAAAGNvdmVyYWdlBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAIAAIAAAABLCIABAAAAABAEGABBAMAACAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAdBAAAdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZC54ID0gY2xhbXAoc3Vic2V0Q29vcmQueCwgdWNsYW1wX1MxX2MwX2MwLngsIHVjbGFtcF9TMV9jMF9jMC56KTsKCWNsYW1wZWRDb29yZC55ID0gc3Vic2V0Q29vcmQueTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQmxlbmRfUzEoaGFsZjQgX3NyYywgaGFsZjQgX2RzdCkgCnsKCS8vIEJsZW5kIG1vZGU6IE1vZHVsYXRlCglyZXR1cm4gYmxlbmRfbW9kdWxhdGUoTWF0cml4RWZmZWN0X1MxX2MwKF9zcmMpLCBfc3JjKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IEJsZW5kX1MxKG91dHB1dENvbG9yX1MwLCBoYWxmNCgxKSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAAAAIAAAABLAIABAAAAABAEGABBAMAAAAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAADnAgAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoX2lucHV0KTsKfQpoYWxmNCBCbGVuZF9TMShoYWxmNCBfc3JjLCBoYWxmNCBfZHN0KSAKewoJLy8gQmxlbmQgbW9kZTogTW9kdWxhdGUKCXJldHVybiBibGVuZF9tb2R1bGF0ZShNYXRyaXhFZmZlY3RfUzFfYzAoX3NyYyksIF9zcmMpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwID0gaGFsZjQoMSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQmxlbmRfUzEob3V0cHV0Q29sb3JfUzAsIGhhbGY0KDEpKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfUzEgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","AYQQ5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAMQAHOMFARUBIAADAAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACtBQAAY29uc3QgaW50IGtGaWxsQldfUzEgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgY292ZXJhZ2U7CglpZiAoaW50KDEpID09IGtGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TMS56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fUzEueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCX0KCWVsc2UgCgl7CgkJaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fUzEpLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJaWYgKGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gaGFsZjQoX2lucHV0ICogY292ZXJhZ2UpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZiBkaXN0YW5jZVRvSW5uZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoZCAtIGNpcmNsZUVkZ2UudykpOwoJaGFsZiBpbm5lckFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb0lubmVyRWRnZSk7CgllZGdlQWxwaGEgKj0gaW5uZXJBbHBoYTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IFJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CAAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoBAAAAuAIAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzE7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAADAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAYAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIA":"CAAAAExTS1OAAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfNV9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAOMGAAB1bmlmb3JtIGhhbGY0IHVzdGFydF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1ZW5kX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzE7CnVuaWZvcm0gaGFsZjQgdWxlZnRCb3JkZXJDb2xvcl9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TMV9jMDsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJZmxvYXQyIF90bXBfMV9jb29yZHMgPSBfY29vcmRzOwoJcmV0dXJuIGhhbGY0KG1peCh1c3RhcnRfUzFfYzBfYzAsIHVlbmRfUzFfYzBfYzAsIGhhbGYoX3RtcF8xX2Nvb3Jkcy54KSkpOwp9CmhhbGY0IExpbmVhckxheW91dF9TMV9jMF9jMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzJfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzNfY29vcmRzID0gdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CglyZXR1cm4gaGFsZjQoaGFsZjQoaGFsZihfdG1wXzNfY29vcmRzLngpICsgOS45OTk5OTk3NDczNzg3NTE2ZS0wNiwgMS4wLCAwLjAsIDAuMCkpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyTGF5b3V0X1MxX2MwX2MxX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQ2xhbXBlZEdyYWRpZW50X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfNF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjQgdCA9IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShfdG1wXzRfaW5Db2xvcik7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghYm9vbChpbnQoMSkpICYmIHQueSA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSBoYWxmNCgwLjApOwoJfQoJZWxzZSBpZiAodC54IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IHVsZWZ0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIGlmICh0LnggPiAxLjApIAoJewoJCW91dENvbG9yID0gdXJpZ2h0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoX3RtcF80X2luQ29sb3IsIGZsb2F0MihoYWxmMih0LngsIDAuMCkpKTsKCX0KCWlmIChib29sKGludCgxKSkpIAoJewoJCW91dENvbG9yLnh5eiAqPSBvdXRDb2xvci53OwoJfQoJcmV0dXJuIGhhbGY0KG91dENvbG9yKTsKfQpoYWxmNCBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKGhhbGY0IF9pbnB1dCkgCnsKCV9pbnB1dCA9IENsYW1wZWRHcmFkaWVudF9TMV9jMChfaW5wdXQpOwoJaGFsZjQgX3RtcF81X2luQ29sb3IgPSBfaW5wdXQ7CglyZXR1cm4gaGFsZjQoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IERpc2FibGVDb3ZlcmFnZUFzQWxwaGFfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","FAAAMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAEDAACATAAABAGYAAAICSBYQCA4AAAAAAEAZIA62YSBDACAAAGAAAAAAAAAAABAAOAAAABAAAAAAABBAMAA":"CAAAAExTS1MXDAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDQgdmFyY2Nvb3JkX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCWZsb2F0IGFhX2Jsb2F0X211bHRpcGxpZXIgPSAxOwoJZmxvYXQyIGNvcm5lciA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMueHk7CglmbG9hdDIgcmFkaXVzX291dHNldCA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMuenc7CglmbG9hdDIgYWFfYmxvYXRfZGlyZWN0aW9uID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnh5OwoJZmxvYXQgaXNfbGluZWFyX2NvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnc7CglmbG9hdDIgcGl4ZWxsZW5ndGggPSBpbnZlcnNlc3FydChmbG9hdDIoZG90KHNrZXcueHosIHNrZXcueHopLCBkb3Qoc2tldy55dywgc2tldy55dykpKTsKCWZsb2F0NCBub3JtYWxpemVkX2F4aXNfZGlycyA9IHNrZXcgKiBwaXhlbGxlbmd0aC54eXh5OwoJZmxvYXQyIGF4aXN3aWR0aHMgPSAoYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnh5KSArIGFicyhub3JtYWxpemVkX2F4aXNfZGlycy56dykpOwoJZmxvYXQyIGFhX2Jsb2F0cmFkaXVzID0gYXhpc3dpZHRocyAqIHBpeGVsbGVuZ3RoICogLjU7CglmbG9hdDQgcmFkaWlfYW5kX25laWdoYm9ycyA9IHJhZGlpX3NlbGVjdG9yKiBmbG9hdDR4NChyYWRpaV94LCByYWRpaV95LCByYWRpaV94Lnl4d3osIHJhZGlpX3kud3p5eCk7CglmbG9hdDIgcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnh5OwoJZmxvYXQyIG5laWdoYm9yX3JhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy56dzsKCWZsb2F0IGNvdmVyYWdlX211bHRpcGxpZXIgPSAxOwoJaWYgKGFueShncmVhdGVyVGhhbihhYV9ibG9hdHJhZGl1cywgZmxvYXQyKDEpKSkpIAoJewoJCWNvcm5lciA9IG1heChhYnMoY29ybmVyKSwgYWFfYmxvYXRyYWRpdXMpICogc2lnbihjb3JuZXIpOwoJCWNvdmVyYWdlX211bHRpcGxpZXIgPSAxIC8gKG1heChhYV9ibG9hdHJhZGl1cy54LCAxKSAqIG1heChhYV9ibG9hdHJhZGl1cy55LCAxKSk7CgkJcmFkaWkgPSBmbG9hdDIoMCk7Cgl9CglmbG9hdCBjb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS56OwoJaWYgKGFueShsZXNzVGhhbihyYWRpaSwgYWFfYmxvYXRyYWRpdXMgKiAxLjUpKSkgCgl7CgkJcmFkaWkgPSBmbG9hdDIoMCk7CgkJYWFfYmxvYXRfZGlyZWN0aW9uID0gc2lnbihjb3JuZXIpOwoJCWlmIChjb3ZlcmFnZSA+IC41KSAKCQl7CgkJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IC1hYV9ibG9hdF9kaXJlY3Rpb247CgkJfQoJCWlzX2xpbmVhcl9jb3ZlcmFnZSA9IDE7Cgl9CgllbHNlIAoJewoJCXJhZGlpID0gY2xhbXAocmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCW5laWdoYm9yX3JhZGlpID0gY2xhbXAobmVpZ2hib3JfcmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCWZsb2F0MiBzcGFjaW5nID0gMiAtIHJhZGlpIC0gbmVpZ2hib3JfcmFkaWk7CgkJZmxvYXQyIGV4dHJhX3BhZCA9IG1heChwaXhlbGxlbmd0aCAqIC4wNjI1IC0gc3BhY2luZywgZmxvYXQyKDApKTsKCQlyYWRpaSAtPSBleHRyYV9wYWQgKiAuNTsKCX0KCWZsb2F0MiBhYV9vdXRzZXQgPSBhYV9ibG9hdF9kaXJlY3Rpb24gKiBhYV9ibG9hdHJhZGl1cyAqIGFhX2Jsb2F0X211bHRpcGxpZXI7CglmbG9hdDIgdmVydGV4cG9zID0gY29ybmVyICsgcmFkaXVzX291dHNldCAqIHJhZGlpICsgYWFfb3V0c2V0OwoJaWYgKGNvdmVyYWdlID4gLjUpIAoJewoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueCAhPSAwICYmIHZlcnRleHBvcy54ICogY29ybmVyLnggPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLngpOwoJCQl2ZXJ0ZXhwb3MueCA9IDA7CgkJCXZlcnRleHBvcy55ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci55KSAqIHBpeGVsbGVuZ3RoLnkvcGl4ZWxsZW5ndGgueDsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLngpIC8gKGFicyhjb3JuZXIueCkgKyBiYWNrc2V0KSArIC41OwoJCX0KCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnkgIT0gMCAmJiB2ZXJ0ZXhwb3MueSAqIGNvcm5lci55IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy55KTsKCQkJdmVydGV4cG9zLnkgPSAwOwoJCQl2ZXJ0ZXhwb3MueCArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueCkgKiBwaXhlbGxlbmd0aC54L3BpeGVsbGVuZ3RoLnk7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci55KSAvIChhYnMoY29ybmVyLnkpICsgYmFja3NldCkgKyAuNTsKCQl9Cgl9CglmbG9hdDJ4MiBza2V3bWF0cml4ID0gZmxvYXQyeDIoc2tldy54eSwgc2tldy56dyk7CglmbG9hdDIgZGV2Y29vcmQgPSB2ZXJ0ZXhwb3MgKiBza2V3bWF0cml4ICsgdHJhbnNsYXRlOwoJaWYgKDAgIT0gaXNfbGluZWFyX2NvdmVyYWdlKSAKCXsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoYXJjY29vcmQueCsxLCBhcmNjb29yZC55KTsKCQlmbG9hdDJ4MiBkZXJpdmF0aXZlcyA9IGludmVyc2Uoc2tld21hdHJpeCk7CgkJdmFyY2Nvb3JkX1MwLnp3ID0gZGVyaXZhdGl2ZXMgKiAoYXJjY29vcmQvcmFkaWkgKiAyKTsKCX0KCXNrX1Bvc2l0aW9uID0gZGV2Y29vcmQueHkwMTsKfQoAAQAAAKwFAABjb25zdCBpbnQga0ZpbGxCV19TMSA9IDA7CmNvbnN0IGludCBrSW52ZXJzZUZpbGxCV19TMSA9IDI7CmNvbnN0IGludCBrSW52ZXJzZUZpbGxBQV9TMSA9IDM7CnVuaWZvcm0gZmxvYXQ0IHVyZWN0VW5pZm9ybV9TMTsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0NCB2YXJjY29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGNvdmVyYWdlOwoJaWYgKGludCgxKSA9PSBrRmlsbEJXX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKGFsbChncmVhdGVyVGhhbihmbG9hdDQoc2tfRnJhZ0Nvb3JkLnh5LCB1cmVjdFVuaWZvcm1fUzEuencpLCBmbG9hdDQodXJlY3RVbmlmb3JtX1MxLnh5LCBza19GcmFnQ29vcmQueHkpKSkgPyAxIDogMCk7Cgl9CgllbHNlIAoJewoJCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEuMCwgMS4wLCAtMS4wLCAtMS4wKSAqIGhhbGY0KHNrX0ZyYWdDb29yZC54eXh5IC0gdXJlY3RVbmlmb3JtX1MxKSwgMC4wLCAxLjApOwoJCWhhbGYyIGRpc3RzMiA9IChkaXN0czQueHkgKyBkaXN0czQuencpIC0gMS4wOwoJCWNvdmVyYWdlID0gZGlzdHMyLnggKiBkaXN0czIueTsKCX0KCWlmIChpbnQoMSkgPT0ga0ludmVyc2VGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEFBX1MxKSAKCXsKCQljb3ZlcmFnZSA9IDEuMCAtIGNvdmVyYWdlOwoJfQoJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIGNvdmVyYWdlKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWZsb2F0IHhfcGx1c18xPXZhcmNjb29yZF9TMC54LCB5PXZhcmNjb29yZF9TMC55OwoJaGFsZiBjb3ZlcmFnZTsKCWlmICgwID09IHhfcGx1c18xKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoeSk7Cgl9CgllbHNlIAoJewoJCWZsb2F0IGZuID0geF9wbHVzXzEgKiAoeF9wbHVzXzEgLSAyKTsKCQlmbiA9IGZtYSh5LHksIGZuKTsKCQlmbG9hdCBneD12YXJjY29vcmRfUzAueiwgZ3k9dmFyY2Nvb3JkX1MwLnc7CgkJZmxvYXQgZm53aWR0aCA9IGFicyhneCkgKyBhYnMoZ3kpOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAEAAAAc2tldwkAAAB0cmFuc2xhdGUAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAFAAAAY29sb3IAAAABAAAAAAAAAA==","HVJAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAABAAAAAABBAMABAAOAAAABAAAAAAABBAMAAA":"CAAAAExTS1MjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cgl2bG9jYWxDb29yZF9TMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAAAAADoAQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdDIgdGV4Q29vcmQ7Cgl0ZXhDb29yZCA9IHZsb2NhbENvb3JkX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSAoKHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdGV4Q29vcmQpICogb3V0cHV0Q29sb3JfUzApKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGAARAGQWMHGBRIAAAAABQAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABhBAAAY29uc3QgaW50IGtGaWxsQUFfUzEgPSAxOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1Y2lyY2xlX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZsb2NhbENvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjbGVfUzEoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGQ7CglpZiAoaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMSkgCgl7CgkJZCA9IGhhbGYoKGxlbmd0aCgodWNpcmNsZV9TMS54eSAtIHNrX0ZyYWdDb29yZC54eSkgKiB1Y2lyY2xlX1MxLncpIC0gMS4wKSAqIHVjaXJjbGVfUzEueik7Cgl9CgllbHNlIAoJewoJCWQgPSBoYWxmKCgxLjAgLSBsZW5ndGgoKHVjaXJjbGVfUzEueHkgLSBza19GcmFnQ29vcmQueHkpICogdWNpcmNsZV9TMS53KSkgKiB1Y2lyY2xlX1MxLnopOwoJfQoJaWYgKGludCgxKSA9PSBrRmlsbEFBX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMSkgCgl7CgkJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIHNhdHVyYXRlKGQpKTsKCX0KCWVsc2UgCgl7CgkJcmV0dXJuIGhhbGY0KGQgPiAwLjUgPyBfaW5wdXQgOiBoYWxmNCgwLjApKTsKCX0KfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJZmxvYXQyIHRleENvb3JkOwoJdGV4Q29vcmQgPSB2bG9jYWxDb29yZF9TMDsKCW91dHB1dENvbG9yX1MwID0gKChzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHRleENvb3JkKSAqIGhhbGY0KDEpKSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY2xlX1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQBAAAQAAAAGQCBAMQACAAAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAIgDAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBjbGFtcChzdWJzZXRDb29yZC54LCB1Y2xhbXBfUzFfYzAueCwgdWNsYW1wX1MxX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAIBSQB5VRECGAEAAAMAAAAAAAAAAACAA4AAAACAAAAAAACCAYAA":"CAAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAABAAAA4wQAAGNvbnN0IGludCBrRmlsbEJXX1MxID0gMDsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEJXX1MxID0gMjsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEFBX1MxID0gMzsKdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1MwOwpmbGF0IGluIGZsb2F0IHZUZXhJbmRleF9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGNvdmVyYWdlOwoJaWYgKGludCgxKSA9PSBrRmlsbEJXX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKGFsbChncmVhdGVyVGhhbihmbG9hdDQoc2tfRnJhZ0Nvb3JkLnh5LCB1cmVjdFVuaWZvcm1fUzEuencpLCBmbG9hdDQodXJlY3RVbmlmb3JtX1MxLnh5LCBza19GcmFnQ29vcmQueHkpKSkgPyAxIDogMCk7Cgl9CgllbHNlIAoJewoJCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEuMCwgMS4wLCAtMS4wLCAtMS4wKSAqIGhhbGY0KHNrX0ZyYWdDb29yZC54eXh5IC0gdXJlY3RVbmlmb3JtX1MxKSwgMC4wLCAxLjApOwoJCWhhbGYyIGRpc3RzMiA9IChkaXN0czQueHkgKyBkaXN0czQuencpIC0gMS4wOwoJCWNvdmVyYWdlID0gZGlzdHMyLnggKiBkaXN0czIueTsKCX0KCWlmIChpbnQoMSkgPT0ga0ludmVyc2VGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEFBX1MxKSAKCXsKCQljb3ZlcmFnZSA9IDEuMCAtIGNvdmVyYWdlOwoJfQoJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIGNvdmVyYWdlKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZjQgdGV4Q29sb3I7Cgl7CgkJdGV4Q29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHZUZXh0dXJlQ29vcmRzX1MwKS5ycnJyOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSB0ZXhDb2xvcjsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IFJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA8AAABpblRleHR1cmVDb29yZHMAAQAAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAABAEAAAABJYQAAAAAACAIAAAAAWCBAAAIBAAAAANAECAZAAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAADEGAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1XzBfSW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVfMV9LZXJuZWxfUzFfYzBbNF07CnVuaWZvcm0gaGFsZjQgdV8yX09mZnNldHNfUzFfYzBbNF07CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJZmxvYXQyIGluQ29vcmQgPSBfY29vcmRzOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBzdWJzZXRDb29yZC54OwoJY2xhbXBlZENvb3JkLnkgPSBjbGFtcChzdWJzZXRDb29yZC55LCB1Y2xhbXBfUzFfYzBfYzBfYzAueSwgdWNsYW1wX1MxX2MwX2MwX2MwLncpOwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKF9pbnB1dCwgZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMCkgKiBfY29vcmRzLnh5MSk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfM19jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzVfY29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKCWZvciAoaW50IF82X2kgPSAwOyAoXzZfaSA8IDEzKTsgXzZfaSsrKSAoXzNfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfaW5wdXQsIChfNV9jb29yZCArIGZsb2F0MigodV8yX09mZnNldHNfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0gKiB1XzBfSW5jcmVtZW50X1MxX2MwKSkpKSAqIHVfMV9LZXJuZWxfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0pKTsKCXJldHVybiBfM19jb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAABAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAIAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIA":"CAAAAExTS1OAAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfNV9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAOMGAAB1bmlmb3JtIGhhbGY0IHVzdGFydF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1ZW5kX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzE7CnVuaWZvcm0gaGFsZjQgdWxlZnRCb3JkZXJDb2xvcl9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TMV9jMDsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJZmxvYXQyIF90bXBfMV9jb29yZHMgPSBfY29vcmRzOwoJcmV0dXJuIGhhbGY0KG1peCh1c3RhcnRfUzFfYzBfYzAsIHVlbmRfUzFfYzBfYzAsIGhhbGYoX3RtcF8xX2Nvb3Jkcy54KSkpOwp9CmhhbGY0IExpbmVhckxheW91dF9TMV9jMF9jMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzJfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzNfY29vcmRzID0gdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CglyZXR1cm4gaGFsZjQoaGFsZjQoaGFsZihfdG1wXzNfY29vcmRzLngpICsgOS45OTk5OTk3NDczNzg3NTE2ZS0wNiwgMS4wLCAwLjAsIDAuMCkpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyTGF5b3V0X1MxX2MwX2MxX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQ2xhbXBlZEdyYWRpZW50X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfNF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjQgdCA9IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShfdG1wXzRfaW5Db2xvcik7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghYm9vbChpbnQoMSkpICYmIHQueSA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSBoYWxmNCgwLjApOwoJfQoJZWxzZSBpZiAodC54IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IHVsZWZ0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIGlmICh0LnggPiAxLjApIAoJewoJCW91dENvbG9yID0gdXJpZ2h0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoX3RtcF80X2luQ29sb3IsIGZsb2F0MihoYWxmMih0LngsIDAuMCkpKTsKCX0KCWlmIChib29sKGludCgwKSkpIAoJewoJCW91dENvbG9yLnh5eiAqPSBvdXRDb2xvci53OwoJfQoJcmV0dXJuIGhhbGY0KG91dENvbG9yKTsKfQpoYWxmNCBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKGhhbGY0IF9pbnB1dCkgCnsKCV9pbnB1dCA9IENsYW1wZWRHcmFkaWVudF9TMV9jMChfaW5wdXQpOwoJaGFsZjQgX3RtcF81X2luQ29sb3IgPSBfaW5wdXQ7CglyZXR1cm4gaGFsZjQoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IERpc2FibGVDb3ZlcmFnZUFzQWxwaGFfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAACgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","BYIBQAAABQAAIAABBYAAAEIXBAAP777777777777AAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CAAAAExTS1M+AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwpvdXQgaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IGNvbG9yID0gaW5Db2xvcjsKCXZjb2xvcl9TMCA9IGNvbG9yOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzNfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IF90bXBfMV9pblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAHgEAAGluIGhhbGY0IHZjb2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIERlZmF1bHRHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IAAQAAAAAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAACAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAIAHSADQAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAABAAAAWAMAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzE7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1MwOwpmbGF0IGluIGZsb2F0IHZUZXhJbmRleF9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gdGV4Q29sb3I7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA8AAABpblRleHR1cmVDb29yZHMAAQAAAAAAAAA=","HUJD4AQCAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGACQAGAAAAAQAAAAAAAQQGAAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAAAAABABgAAdW5pZm9ybSBoYWxmIHVTcmNURl9TMFs3XTsKdW5pZm9ybSBoYWxmM3gzIHVDb2xvclhmb3JtX1MwOwp1bmlmb3JtIGhhbGYgdURzdFRGX1MwWzddOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZsb2NhbENvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmIHNyY190Zl9TMChoYWxmIHgpIAp7CgloYWxmIEcgPSB1U3JjVEZfUzBbMF07CgloYWxmIEEgPSB1U3JjVEZfUzBbMV07CgloYWxmIEIgPSB1U3JjVEZfUzBbMl07CgloYWxmIEMgPSB1U3JjVEZfUzBbM107CgloYWxmIEQgPSB1U3JjVEZfUzBbNF07CgloYWxmIEUgPSB1U3JjVEZfUzBbNV07CgloYWxmIEYgPSB1U3JjVEZfUzBbNl07CgloYWxmIHMgPSBzaWduKHgpOwoJeCA9IGFicyh4KTsKCXggPSAoeCA8IEQpID8gKEMgKiB4KSArIEYgOiBwb3coQSAqIHggKyBCLCBHKSArIEU7CglyZXR1cm4gcyAqIHg7Cn0KaGFsZiBkc3RfdGZfUzAoaGFsZiB4KSAKewoJaGFsZiBHID0gdURzdFRGX1MwWzBdOwoJaGFsZiBBID0gdURzdFRGX1MwWzFdOwoJaGFsZiBCID0gdURzdFRGX1MwWzJdOwoJaGFsZiBDID0gdURzdFRGX1MwWzNdOwoJaGFsZiBEID0gdURzdFRGX1MwWzRdOwoJaGFsZiBFID0gdURzdFRGX1MwWzVdOwoJaGFsZiBGID0gdURzdFRGX1MwWzZdOwoJaGFsZiBzID0gc2lnbih4KTsKCXggPSBhYnMoeCk7Cgl4ID0gKHggPCBEKSA/IChDICogeCkgKyBGIDogcG93KEEgKiB4ICsgQiwgRykgKyBFOwoJcmV0dXJuIHMgKiB4Owp9CmhhbGY0IGdhbXV0X3hmb3JtX1MwKGhhbGY0IGNvbG9yKSAKewoJY29sb3IucmdiID0gKHVDb2xvclhmb3JtX1MwICogY29sb3IucmdiKTsKCXJldHVybiBjb2xvcjsKfQpoYWxmNCBjb2xvcl94Zm9ybV9TMChmbG9hdDQgY29sb3IpIAp7Cgljb2xvciA9IHVucHJlbXVsKGNvbG9yKTsKCWNvbG9yLnIgPSBzcmNfdGZfUzAoaGFsZihjb2xvci5yKSk7Cgljb2xvci5nID0gc3JjX3RmX1MwKGhhbGYoY29sb3IuZykpOwoJY29sb3IuYiA9IHNyY190Zl9TMChoYWxmKGNvbG9yLmIpKTsKCWNvbG9yID0gZ2FtdXRfeGZvcm1fUzAoaGFsZjQoY29sb3IpKTsKCWNvbG9yLnIgPSBkc3RfdGZfUzAoaGFsZihjb2xvci5yKSk7Cgljb2xvci5nID0gZHN0X3RmX1MwKGhhbGYoY29sb3IuZykpOwoJY29sb3IuYiA9IGRzdF90Zl9TMChoYWxmKGNvbG9yLmIpKTsKCWNvbG9yLnJnYiAqPSBjb2xvci5hOwoJcmV0dXJuIGhhbGY0KGNvbG9yKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJZmxvYXQyIHRleENvb3JkOwoJdGV4Q29vcmQgPSB2bG9jYWxDb29yZF9TMDsKCW91dHB1dENvbG9yX1MwID0gKChjb2xvcl94Zm9ybV9TMChzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHRleENvb3JkKSkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABYQA6AAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACRAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAEDAACATAAABAGYAAAICSBYQCA4AAAAAAEAZIA62YSBDACAAAGAAAAAAAAAAABAAOAAAABAAAAAAABBAMAA":"CAAAAExTS1OxCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDIgdmFyY2Nvb3JkX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCWZsb2F0IGFhX2Jsb2F0X211bHRpcGxpZXIgPSAxOwoJZmxvYXQyIGNvcm5lciA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMueHk7CglmbG9hdDIgcmFkaXVzX291dHNldCA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMuenc7CglmbG9hdDIgYWFfYmxvYXRfZGlyZWN0aW9uID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnh5OwoJZmxvYXQgaXNfbGluZWFyX2NvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnc7CglmbG9hdDIgcGl4ZWxsZW5ndGggPSBpbnZlcnNlc3FydChmbG9hdDIoZG90KHNrZXcueHosIHNrZXcueHopLCBkb3Qoc2tldy55dywgc2tldy55dykpKTsKCWZsb2F0NCBub3JtYWxpemVkX2F4aXNfZGlycyA9IHNrZXcgKiBwaXhlbGxlbmd0aC54eXh5OwoJZmxvYXQyIGF4aXN3aWR0aHMgPSAoYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnh5KSArIGFicyhub3JtYWxpemVkX2F4aXNfZGlycy56dykpOwoJZmxvYXQyIGFhX2Jsb2F0cmFkaXVzID0gYXhpc3dpZHRocyAqIHBpeGVsbGVuZ3RoICogLjU7CglmbG9hdDQgcmFkaWlfYW5kX25laWdoYm9ycyA9IHJhZGlpX3NlbGVjdG9yKiBmbG9hdDR4NChyYWRpaV94LCByYWRpaV95LCByYWRpaV94Lnl4d3osIHJhZGlpX3kud3p5eCk7CglmbG9hdDIgcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnh5OwoJZmxvYXQyIG5laWdoYm9yX3JhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy56dzsKCWZsb2F0IGNvdmVyYWdlX211bHRpcGxpZXIgPSAxOwoJaWYgKGFueShncmVhdGVyVGhhbihhYV9ibG9hdHJhZGl1cywgZmxvYXQyKDEpKSkpIAoJewoJCWNvcm5lciA9IG1heChhYnMoY29ybmVyKSwgYWFfYmxvYXRyYWRpdXMpICogc2lnbihjb3JuZXIpOwoJCWNvdmVyYWdlX211bHRpcGxpZXIgPSAxIC8gKG1heChhYV9ibG9hdHJhZGl1cy54LCAxKSAqIG1heChhYV9ibG9hdHJhZGl1cy55LCAxKSk7CgkJcmFkaWkgPSBmbG9hdDIoMCk7Cgl9CglmbG9hdCBjb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS56OwoJaWYgKGFueShsZXNzVGhhbihyYWRpaSwgYWFfYmxvYXRyYWRpdXMgKiAxLjUpKSkgCgl7CgkJcmFkaWkgPSBmbG9hdDIoMCk7CgkJYWFfYmxvYXRfZGlyZWN0aW9uID0gc2lnbihjb3JuZXIpOwoJCWlmIChjb3ZlcmFnZSA+IC41KSAKCQl7CgkJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IC1hYV9ibG9hdF9kaXJlY3Rpb247CgkJfQoJCWlzX2xpbmVhcl9jb3ZlcmFnZSA9IDE7Cgl9CgllbHNlIAoJewoJCXJhZGlpID0gY2xhbXAocmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCW5laWdoYm9yX3JhZGlpID0gY2xhbXAobmVpZ2hib3JfcmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCWZsb2F0MiBzcGFjaW5nID0gMiAtIHJhZGlpIC0gbmVpZ2hib3JfcmFkaWk7CgkJZmxvYXQyIGV4dHJhX3BhZCA9IG1heChwaXhlbGxlbmd0aCAqIC4wNjI1IC0gc3BhY2luZywgZmxvYXQyKDApKTsKCQlyYWRpaSAtPSBleHRyYV9wYWQgKiAuNTsKCX0KCWZsb2F0MiBhYV9vdXRzZXQgPSBhYV9ibG9hdF9kaXJlY3Rpb24gKiBhYV9ibG9hdHJhZGl1cyAqIGFhX2Jsb2F0X211bHRpcGxpZXI7CglmbG9hdDIgdmVydGV4cG9zID0gY29ybmVyICsgcmFkaXVzX291dHNldCAqIHJhZGlpICsgYWFfb3V0c2V0OwoJaWYgKGNvdmVyYWdlID4gLjUpIAoJewoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueCAhPSAwICYmIHZlcnRleHBvcy54ICogY29ybmVyLnggPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLngpOwoJCQl2ZXJ0ZXhwb3MueCA9IDA7CgkJCXZlcnRleHBvcy55ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci55KSAqIHBpeGVsbGVuZ3RoLnkvcGl4ZWxsZW5ndGgueDsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLngpIC8gKGFicyhjb3JuZXIueCkgKyBiYWNrc2V0KSArIC41OwoJCX0KCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnkgIT0gMCAmJiB2ZXJ0ZXhwb3MueSAqIGNvcm5lci55IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy55KTsKCQkJdmVydGV4cG9zLnkgPSAwOwoJCQl2ZXJ0ZXhwb3MueCArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueCkgKiBwaXhlbGxlbmd0aC54L3BpeGVsbGVuZ3RoLnk7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci55KSAvIChhYnMoY29ybmVyLnkpICsgYmFja3NldCkgKyAuNTsKCQl9Cgl9CglmbG9hdDJ4MiBza2V3bWF0cml4ID0gZmxvYXQyeDIoc2tldy54eSwgc2tldy56dyk7CglmbG9hdDIgZGV2Y29vcmQgPSB2ZXJ0ZXhwb3MgKiBza2V3bWF0cml4ICsgdHJhbnNsYXRlOwoJaWYgKDAgIT0gaXNfbGluZWFyX2NvdmVyYWdlKSAKCXsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoYXJjY29vcmQueCsxLCBhcmNjb29yZC55KTsKCX0KCXNrX1Bvc2l0aW9uID0gZGV2Y29vcmQueHkwMTsKfQoAAAABAAAAdwUAAGNvbnN0IGludCBrRmlsbEJXX1MxID0gMDsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEJXX1MxID0gMjsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEFBX1MxID0gMzsKdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZhcmNjb29yZF9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgY292ZXJhZ2U7CglpZiAoaW50KDEpID09IGtGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TMS56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fUzEueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCX0KCWVsc2UgCgl7CgkJaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fUzEpLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJaWYgKGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gaGFsZjQoX2lucHV0ICogY292ZXJhZ2UpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1MwLngsIHk9dmFyY2Nvb3JkX1MwLnk7CgloYWxmIGNvdmVyYWdlOwoJaWYgKDAgPT0geF9wbHVzXzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZih5KTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQgZm4gPSB4X3BsdXNfMSAqICh4X3BsdXNfMSAtIDIpOwoJCWZuID0gZm1hKHkseSwgZm4pOwoJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABAAAAHNrZXcJAAAAdHJhbnNsYXRlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABQAAAGNvbG9yAAAAAQAAAAAAAAA=","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAGIBIAAABAAAAANAEAAAAAAAAAAAAAABAAOAAAABAAAAAAABBAMAAAAA":"CAAAAExTS1N0AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMl9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAIsCAAB1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwKS5ycnJyOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gTWF0cml4RWZmZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","AYQQ5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAACTAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGYgZGlzdGFuY2VUb0lubmVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKGQgLSBjaXJjbGVFZGdlLncpKTsKCWhhbGYgaW5uZXJBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9Jbm5lckVkZ2UpOwoJZWRnZUFscGhhICo9IGlubmVyQWxwaGE7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","AYAA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1OCAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCXZpbkNpcmNsZUVkZ2VfUzAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMl9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gX3RtcF8wX2luUG9zaXRpb24ueHkwMTsKfQoAAAAAAAACAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","EABQAAAAAEAAAAAQAABQAAIOAAABCFYIAAKAUDAAAAAAAAABAAAAAAAAAAANAAIAAAABAAAAACAJAAIAAAAA":"CAAAAExTS1OhAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc0RpbWVuc2lvbnNJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgZmxvYXQyIHZJbnRUZXh0dXJlQ29vcmRzX1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIERpc3RhbmNlRmllbGRQYXRoCglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkcyAqIHVBdGxhc0RpbWVuc2lvbnNJbnZfUzA7Cgl2VGV4SW5kZXhfUzAgPSBmbG9hdCh0ZXhJZHgpOwoJdkludFRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkczsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8yX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAAAAC3AgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBpbiBmbG9hdCB2VGV4SW5kZXhfUzA7CmluIGZsb2F0MiB2SW50VGV4dHVyZUNvb3Jkc19TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBEaXN0YW5jZUZpZWxkUGF0aAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQyIHV2ID0gdlRleHR1cmVDb29yZHNfUzA7CgloYWxmNCB0ZXhDb2xvcjsKCXsKCQl0ZXhDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdXYpLnJycnI7Cgl9CgloYWxmIGRpc3RhbmNlID0gNy45Njg3NSoodGV4Q29sb3IuciAtIDAuNTAxOTYwNzg0MzEpOwoJaGFsZiBhZndpZHRoOwoJYWZ3aWR0aCA9IGFicygwLjY1KmhhbGYoZEZkeCh2SW50VGV4dHVyZUNvb3Jkc19TMC54KSkpOwoJaGFsZiB2YWwgPSBzbW9vdGhzdGVwKC1hZndpZHRoLCBhZndpZHRoLCBkaXN0YW5jZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KHZhbCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAPAAAAaW5UZXh0dXJlQ29vcmRzAAEAAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABYQACAAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAADkAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5ID0gbWF4KHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHksIDAuMCk7CgloYWxmIHJpZ2h0QWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVpbm5lclJlY3RfUzEuUiAtIHNrX0ZyYWdDb29yZC54KSk7CgloYWxmIGJvdHRvbUFscGhhID0gaGFsZihzYXR1cmF0ZSh1aW5uZXJSZWN0X1MxLkIgLSBza19GcmFnQ29vcmQueSkpOwoJaGFsZiBhbHBoYSA9IGJvdHRvbUFscGhhICogcmlnaHRBbHBoYSAqIGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY3VsYXJSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","HUQACAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CAAAAExTS1PPAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAEAAACzAgAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGhhbGY0IHZjb2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CAAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoAAAAAKQEAAGZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAEDAACATAAABAGYAAAICSBYQCA4AAAAAAAA5AAAAAAABAAAAACAZAAAAA":"CAAAAExTS1OxCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDIgdmFyY2Nvb3JkX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCWZsb2F0IGFhX2Jsb2F0X211bHRpcGxpZXIgPSAxOwoJZmxvYXQyIGNvcm5lciA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMueHk7CglmbG9hdDIgcmFkaXVzX291dHNldCA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMuenc7CglmbG9hdDIgYWFfYmxvYXRfZGlyZWN0aW9uID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnh5OwoJZmxvYXQgaXNfbGluZWFyX2NvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnc7CglmbG9hdDIgcGl4ZWxsZW5ndGggPSBpbnZlcnNlc3FydChmbG9hdDIoZG90KHNrZXcueHosIHNrZXcueHopLCBkb3Qoc2tldy55dywgc2tldy55dykpKTsKCWZsb2F0NCBub3JtYWxpemVkX2F4aXNfZGlycyA9IHNrZXcgKiBwaXhlbGxlbmd0aC54eXh5OwoJZmxvYXQyIGF4aXN3aWR0aHMgPSAoYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnh5KSArIGFicyhub3JtYWxpemVkX2F4aXNfZGlycy56dykpOwoJZmxvYXQyIGFhX2Jsb2F0cmFkaXVzID0gYXhpc3dpZHRocyAqIHBpeGVsbGVuZ3RoICogLjU7CglmbG9hdDQgcmFkaWlfYW5kX25laWdoYm9ycyA9IHJhZGlpX3NlbGVjdG9yKiBmbG9hdDR4NChyYWRpaV94LCByYWRpaV95LCByYWRpaV94Lnl4d3osIHJhZGlpX3kud3p5eCk7CglmbG9hdDIgcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnh5OwoJZmxvYXQyIG5laWdoYm9yX3JhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy56dzsKCWZsb2F0IGNvdmVyYWdlX211bHRpcGxpZXIgPSAxOwoJaWYgKGFueShncmVhdGVyVGhhbihhYV9ibG9hdHJhZGl1cywgZmxvYXQyKDEpKSkpIAoJewoJCWNvcm5lciA9IG1heChhYnMoY29ybmVyKSwgYWFfYmxvYXRyYWRpdXMpICogc2lnbihjb3JuZXIpOwoJCWNvdmVyYWdlX211bHRpcGxpZXIgPSAxIC8gKG1heChhYV9ibG9hdHJhZGl1cy54LCAxKSAqIG1heChhYV9ibG9hdHJhZGl1cy55LCAxKSk7CgkJcmFkaWkgPSBmbG9hdDIoMCk7Cgl9CglmbG9hdCBjb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS56OwoJaWYgKGFueShsZXNzVGhhbihyYWRpaSwgYWFfYmxvYXRyYWRpdXMgKiAxLjUpKSkgCgl7CgkJcmFkaWkgPSBmbG9hdDIoMCk7CgkJYWFfYmxvYXRfZGlyZWN0aW9uID0gc2lnbihjb3JuZXIpOwoJCWlmIChjb3ZlcmFnZSA+IC41KSAKCQl7CgkJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IC1hYV9ibG9hdF9kaXJlY3Rpb247CgkJfQoJCWlzX2xpbmVhcl9jb3ZlcmFnZSA9IDE7Cgl9CgllbHNlIAoJewoJCXJhZGlpID0gY2xhbXAocmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCW5laWdoYm9yX3JhZGlpID0gY2xhbXAobmVpZ2hib3JfcmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCWZsb2F0MiBzcGFjaW5nID0gMiAtIHJhZGlpIC0gbmVpZ2hib3JfcmFkaWk7CgkJZmxvYXQyIGV4dHJhX3BhZCA9IG1heChwaXhlbGxlbmd0aCAqIC4wNjI1IC0gc3BhY2luZywgZmxvYXQyKDApKTsKCQlyYWRpaSAtPSBleHRyYV9wYWQgKiAuNTsKCX0KCWZsb2F0MiBhYV9vdXRzZXQgPSBhYV9ibG9hdF9kaXJlY3Rpb24gKiBhYV9ibG9hdHJhZGl1cyAqIGFhX2Jsb2F0X211bHRpcGxpZXI7CglmbG9hdDIgdmVydGV4cG9zID0gY29ybmVyICsgcmFkaXVzX291dHNldCAqIHJhZGlpICsgYWFfb3V0c2V0OwoJaWYgKGNvdmVyYWdlID4gLjUpIAoJewoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueCAhPSAwICYmIHZlcnRleHBvcy54ICogY29ybmVyLnggPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLngpOwoJCQl2ZXJ0ZXhwb3MueCA9IDA7CgkJCXZlcnRleHBvcy55ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci55KSAqIHBpeGVsbGVuZ3RoLnkvcGl4ZWxsZW5ndGgueDsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLngpIC8gKGFicyhjb3JuZXIueCkgKyBiYWNrc2V0KSArIC41OwoJCX0KCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnkgIT0gMCAmJiB2ZXJ0ZXhwb3MueSAqIGNvcm5lci55IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy55KTsKCQkJdmVydGV4cG9zLnkgPSAwOwoJCQl2ZXJ0ZXhwb3MueCArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueCkgKiBwaXhlbGxlbmd0aC54L3BpeGVsbGVuZ3RoLnk7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci55KSAvIChhYnMoY29ybmVyLnkpICsgYmFja3NldCkgKyAuNTsKCQl9Cgl9CglmbG9hdDJ4MiBza2V3bWF0cml4ID0gZmxvYXQyeDIoc2tldy54eSwgc2tldy56dyk7CglmbG9hdDIgZGV2Y29vcmQgPSB2ZXJ0ZXhwb3MgKiBza2V3bWF0cml4ICsgdHJhbnNsYXRlOwoJaWYgKDAgIT0gaXNfbGluZWFyX2NvdmVyYWdlKSAKCXsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoYXJjY29vcmQueCsxLCBhcmNjb29yZC55KTsKCX0KCXNrX1Bvc2l0aW9uID0gZGV2Y29vcmQueHkwMTsKfQoAAAAAAAAAXQIAAGZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWZsb2F0IHhfcGx1c18xPXZhcmNjb29yZF9TMC54LCB5PXZhcmNjb29yZF9TMC55OwoJaGFsZiBjb3ZlcmFnZTsKCWlmICgwID09IHhfcGx1c18xKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoeSk7Cgl9CgllbHNlIAoJewoJCWZsb2F0IGZuID0geF9wbHVzXzEgKiAoeF9wbHVzXzEgLSAyKTsKCQlmbiA9IGZtYSh5LHksIGZuKTsKCQlmbG9hdCBmbndpZHRoID0gZndpZHRoKGZuKTsKCQljb3ZlcmFnZSA9IC41IC0gaGFsZihmbi9mbndpZHRoKTsKCQljb3ZlcmFnZSA9IGNsYW1wKGNvdmVyYWdlLCAwLCAxKTsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoY292ZXJhZ2UpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABAAAAHNrZXcJAAAAdHJhbnNsYXRlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABQAAAGNvbG9yAAAAAQAAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAIAAEAAAABJYQAAAAAQAAIAAAAAWCBACAABAAAAANAECAZAAEAAAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAADEGAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1XzBfSW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVfMV9LZXJuZWxfUzFfYzBbNF07CnVuaWZvcm0gaGFsZjQgdV8yX09mZnNldHNfUzFfYzBbNF07CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJZmxvYXQyIGluQ29vcmQgPSBfY29vcmRzOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBjbGFtcChzdWJzZXRDb29yZC54LCB1Y2xhbXBfUzFfYzBfYzBfYzAueCwgdWNsYW1wX1MxX2MwX2MwX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKF9pbnB1dCwgZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMCkgKiBfY29vcmRzLnh5MSk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfM19jb2xvciA9IGhhbGY0KDAuMCk7CglmbG9hdDIgXzVfY29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKCWZvciAoaW50IF82X2kgPSAwOyAoXzZfaSA8IDEzKTsgXzZfaSsrKSAoXzNfY29sb3IgKz0gKE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfaW5wdXQsIChfNV9jb29yZCArIGZsb2F0MigodV8yX09mZnNldHNfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0gKiB1XzBfSW5jcmVtZW50X1MxX2MwKSkpKSAqIHVfMV9LZXJuZWxfUzFfYzBbKF82X2kgLyA0KV1bKF82X2kgJiAzKV0pKTsKCXJldHVybiBfM19jb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","GEMAAAYAAEHAAAARC4EAAAQWBQAAAAAAAAAQAAAAIBCAAAGQAEAAAAAQAAAABAEQAEAAAAA":"CAAAAExTS1NUAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBoYWxmMyBpblNoYWRvd1BhcmFtczsKb3V0IGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBSUmVjdFNoYWRvdwoJdmluU2hhZG93UGFyYW1zX1MwID0gaW5TaGFkb3dQYXJhbXM7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAjAgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBSUmVjdFNoYWRvdwoJaGFsZjMgc2hhZG93UGFyYW1zOwoJc2hhZG93UGFyYW1zID0gdmluU2hhZG93UGFyYW1zX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZiBkID0gbGVuZ3RoKHNoYWRvd1BhcmFtcy54eSk7CglmbG9hdDIgdXYgPSBmbG9hdDIoc2hhZG93UGFyYW1zLnogKiAoMS4wIC0gZCksIDAuNSk7CgloYWxmIGZhY3RvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdXYpLjAwMHIuYTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZmFjdG9yKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA4AAABpblNoYWRvd1BhcmFtcwAAAQAAAAAAAAA=","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGACQAGAAAAAQAAAAAAAQQGAAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAAAAAC3AQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGZsb2F0MiB2bG9jYWxDb29yZF9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","HUJBYAQCAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGACQAGAAAAAQAAAAAAAQQGAAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAAAAAAPBgAAdW5pZm9ybSBoYWxmIHVTcmNURl9TMFs3XTsKdW5pZm9ybSBoYWxmM3gzIHVDb2xvclhmb3JtX1MwOwp1bmlmb3JtIGhhbGYgdURzdFRGX1MwWzddOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZsb2NhbENvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmIHNyY190Zl9TMChoYWxmIHgpIAp7CgloYWxmIEcgPSB1U3JjVEZfUzBbMF07CgloYWxmIEEgPSB1U3JjVEZfUzBbMV07CgloYWxmIEIgPSB1U3JjVEZfUzBbMl07CgloYWxmIEMgPSB1U3JjVEZfUzBbM107CgloYWxmIEQgPSB1U3JjVEZfUzBbNF07CgloYWxmIEUgPSB1U3JjVEZfUzBbNV07CgloYWxmIEYgPSB1U3JjVEZfUzBbNl07CgloYWxmIHMgPSBzaWduKHgpOwoJeCA9IGFicyh4KTsKCXggPSAoeCA8IEQpID8gKEMgKiB4KSArIEYgOiBwb3coQSAqIHggKyBCLCBHKSArIEU7CglyZXR1cm4gcyAqIHg7Cn0KaGFsZiBkc3RfdGZfUzAoaGFsZiB4KSAKewoJaGFsZiBHID0gdURzdFRGX1MwWzBdOwoJaGFsZiBBID0gdURzdFRGX1MwWzFdOwoJaGFsZiBCID0gdURzdFRGX1MwWzJdOwoJaGFsZiBDID0gdURzdFRGX1MwWzNdOwoJaGFsZiBEID0gdURzdFRGX1MwWzRdOwoJaGFsZiBFID0gdURzdFRGX1MwWzVdOwoJaGFsZiBGID0gdURzdFRGX1MwWzZdOwoJaGFsZiBzID0gc2lnbih4KTsKCXggPSBhYnMoeCk7Cgl4ID0gKHggPCBEKSA/IChDICogeCkgKyBGIDogcG93KEEgKiB4ICsgQiwgRykgKyBFOwoJcmV0dXJuIHMgKiB4Owp9CmhhbGY0IGdhbXV0X3hmb3JtX1MwKGhhbGY0IGNvbG9yKSAKewoJY29sb3IucmdiID0gKHVDb2xvclhmb3JtX1MwICogY29sb3IucmdiKTsKCXJldHVybiBjb2xvcjsKfQpoYWxmNCBjb2xvcl94Zm9ybV9TMChmbG9hdDQgY29sb3IpIAp7Cgljb2xvci5yID0gc3JjX3RmX1MwKGhhbGYoY29sb3IucikpOwoJY29sb3IuZyA9IHNyY190Zl9TMChoYWxmKGNvbG9yLmcpKTsKCWNvbG9yLmIgPSBzcmNfdGZfUzAoaGFsZihjb2xvci5iKSk7Cgljb2xvciA9IGdhbXV0X3hmb3JtX1MwKGhhbGY0KGNvbG9yKSk7Cgljb2xvci5yID0gZHN0X3RmX1MwKGhhbGYoY29sb3IucikpOwoJY29sb3IuZyA9IGRzdF90Zl9TMChoYWxmKGNvbG9yLmcpKTsKCWNvbG9yLmIgPSBkc3RfdGZfUzAoaGFsZihjb2xvci5iKSk7CglyZXR1cm4gaGFsZjQoY29sb3IpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwID0gaGFsZjQoMSk7CglmbG9hdDIgdGV4Q29vcmQ7Cgl0ZXhDb29yZCA9IHZsb2NhbENvb3JkX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSAoKGNvbG9yX3hmb3JtX1MwKHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdGV4Q29vcmQpKSAqIGhhbGY0KDEpKSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAAAAEAAAABJYQAAAAAAAAIAAAAAWCBAAAABAAAAANAECAZAAAAAAAAAAAFAAMAAAABAAAAAAABBAM":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAPIEAAB1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzA7CnVuaWZvcm0gaGFsZjIgdV8wX0luY3JlbWVudF9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1XzFfS2VybmVsX1MxX2MwWzRdOwp1bmlmb3JtIGhhbGY0IHVfMl9PZmZzZXRzX1MxX2MwWzRdOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIF9jb29yZHMpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBHYXVzc2lhbkNvbnZvbHV0aW9uX1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF8zX2NvbG9yID0gaGFsZjQoMC4wKTsKCWZsb2F0MiBfNV9jb29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZm9yIChpbnQgXzZfaSA9IDA7IChfNl9pIDwgMTMpOyBfNl9pKyspIChfM19jb2xvciArPSAoTWF0cml4RWZmZWN0X1MxX2MwX2MwKF9pbnB1dCwgKF81X2Nvb3JkICsgZmxvYXQyKCh1XzJfT2Zmc2V0c19TMV9jMFsoXzZfaSAvIDQpXVsoXzZfaSAmIDMpXSAqIHVfMF9JbmNyZW1lbnRfUzFfYzApKSkpICogdV8xX0tlcm5lbF9TMV9jMFsoXzZfaSAvIDQpXVsoXzZfaSAmIDMpXSkpOwoJcmV0dXJuIF8zX2NvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwID0gaGFsZjQoMSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gTWF0cml4RWZmZWN0X1MxKG91dHB1dENvbG9yX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfUzEgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","B2AAQAAABQAAIAABBYAAB7777777777774ABICAAAAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CAAAAExTS1MOAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmIGluQ292ZXJhZ2U7Cm91dCBoYWxmIHZpbkNvdmVyYWdlX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cgl2aW5Db3ZlcmFnZV9TMCA9IGluQ292ZXJhZ2U7Cglza19Qb3NpdGlvbiA9IF90bXBfMV9pblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAZQEAAHVuaWZvcm0gaGFsZjQgdUNvbG9yX1MwOwppbiBoYWxmIHZpbkNvdmVyYWdlX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdUNvbG9yX1MwOwoJaGFsZiBhbHBoYSA9IDEuMDsKCWFscGhhID0gdmluQ292ZXJhZ2VfUzA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAACgAAAGluQ292ZXJhZ2UAAAEAAAAAAAAA","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAEDAACATAAABAGYAAAICSBYQCA4AAAAAAEAZCBRE4GNEACAAAOAAAAAAAAAAABAAOAAAABAAAAAAABBAMAA":"CAAAAExTS1OxCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCBza2V3OwppbiBmbG9hdDIgdHJhbnNsYXRlOwppbiBmbG9hdDQgcmFkaWlfeDsKaW4gZmxvYXQ0IHJhZGlpX3k7CmluIGhhbGY0IGNvbG9yOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDIgdmFyY2Nvb3JkX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCWZsb2F0IGFhX2Jsb2F0X211bHRpcGxpZXIgPSAxOwoJZmxvYXQyIGNvcm5lciA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMueHk7CglmbG9hdDIgcmFkaXVzX291dHNldCA9IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMuenc7CglmbG9hdDIgYWFfYmxvYXRfZGlyZWN0aW9uID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnh5OwoJZmxvYXQgaXNfbGluZWFyX2NvdmVyYWdlID0gYWFfYmxvYXRfYW5kX2NvdmVyYWdlLnc7CglmbG9hdDIgcGl4ZWxsZW5ndGggPSBpbnZlcnNlc3FydChmbG9hdDIoZG90KHNrZXcueHosIHNrZXcueHopLCBkb3Qoc2tldy55dywgc2tldy55dykpKTsKCWZsb2F0NCBub3JtYWxpemVkX2F4aXNfZGlycyA9IHNrZXcgKiBwaXhlbGxlbmd0aC54eXh5OwoJZmxvYXQyIGF4aXN3aWR0aHMgPSAoYWJzKG5vcm1hbGl6ZWRfYXhpc19kaXJzLnh5KSArIGFicyhub3JtYWxpemVkX2F4aXNfZGlycy56dykpOwoJZmxvYXQyIGFhX2Jsb2F0cmFkaXVzID0gYXhpc3dpZHRocyAqIHBpeGVsbGVuZ3RoICogLjU7CglmbG9hdDQgcmFkaWlfYW5kX25laWdoYm9ycyA9IHJhZGlpX3NlbGVjdG9yKiBmbG9hdDR4NChyYWRpaV94LCByYWRpaV95LCByYWRpaV94Lnl4d3osIHJhZGlpX3kud3p5eCk7CglmbG9hdDIgcmFkaWkgPSByYWRpaV9hbmRfbmVpZ2hib3JzLnh5OwoJZmxvYXQyIG5laWdoYm9yX3JhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy56dzsKCWZsb2F0IGNvdmVyYWdlX211bHRpcGxpZXIgPSAxOwoJaWYgKGFueShncmVhdGVyVGhhbihhYV9ibG9hdHJhZGl1cywgZmxvYXQyKDEpKSkpIAoJewoJCWNvcm5lciA9IG1heChhYnMoY29ybmVyKSwgYWFfYmxvYXRyYWRpdXMpICogc2lnbihjb3JuZXIpOwoJCWNvdmVyYWdlX211bHRpcGxpZXIgPSAxIC8gKG1heChhYV9ibG9hdHJhZGl1cy54LCAxKSAqIG1heChhYV9ibG9hdHJhZGl1cy55LCAxKSk7CgkJcmFkaWkgPSBmbG9hdDIoMCk7Cgl9CglmbG9hdCBjb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS56OwoJaWYgKGFueShsZXNzVGhhbihyYWRpaSwgYWFfYmxvYXRyYWRpdXMgKiAxLjUpKSkgCgl7CgkJcmFkaWkgPSBmbG9hdDIoMCk7CgkJYWFfYmxvYXRfZGlyZWN0aW9uID0gc2lnbihjb3JuZXIpOwoJCWlmIChjb3ZlcmFnZSA+IC41KSAKCQl7CgkJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IC1hYV9ibG9hdF9kaXJlY3Rpb247CgkJfQoJCWlzX2xpbmVhcl9jb3ZlcmFnZSA9IDE7Cgl9CgllbHNlIAoJewoJCXJhZGlpID0gY2xhbXAocmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCW5laWdoYm9yX3JhZGlpID0gY2xhbXAobmVpZ2hib3JfcmFkaWksIHBpeGVsbGVuZ3RoICogMS41LCAyIC0gcGl4ZWxsZW5ndGggKiAxLjUpOwoJCWZsb2F0MiBzcGFjaW5nID0gMiAtIHJhZGlpIC0gbmVpZ2hib3JfcmFkaWk7CgkJZmxvYXQyIGV4dHJhX3BhZCA9IG1heChwaXhlbGxlbmd0aCAqIC4wNjI1IC0gc3BhY2luZywgZmxvYXQyKDApKTsKCQlyYWRpaSAtPSBleHRyYV9wYWQgKiAuNTsKCX0KCWZsb2F0MiBhYV9vdXRzZXQgPSBhYV9ibG9hdF9kaXJlY3Rpb24gKiBhYV9ibG9hdHJhZGl1cyAqIGFhX2Jsb2F0X211bHRpcGxpZXI7CglmbG9hdDIgdmVydGV4cG9zID0gY29ybmVyICsgcmFkaXVzX291dHNldCAqIHJhZGlpICsgYWFfb3V0c2V0OwoJaWYgKGNvdmVyYWdlID4gLjUpIAoJewoJCWlmIChhYV9ibG9hdF9kaXJlY3Rpb24ueCAhPSAwICYmIHZlcnRleHBvcy54ICogY29ybmVyLnggPCAwKSAKCQl7CgkJCWZsb2F0IGJhY2tzZXQgPSBhYnModmVydGV4cG9zLngpOwoJCQl2ZXJ0ZXhwb3MueCA9IDA7CgkJCXZlcnRleHBvcy55ICs9IGJhY2tzZXQgKiBzaWduKGNvcm5lci55KSAqIHBpeGVsbGVuZ3RoLnkvcGl4ZWxsZW5ndGgueDsKCQkJY292ZXJhZ2UgPSAoY292ZXJhZ2UgLSAuNSkgKiBhYnMoY29ybmVyLngpIC8gKGFicyhjb3JuZXIueCkgKyBiYWNrc2V0KSArIC41OwoJCX0KCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnkgIT0gMCAmJiB2ZXJ0ZXhwb3MueSAqIGNvcm5lci55IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy55KTsKCQkJdmVydGV4cG9zLnkgPSAwOwoJCQl2ZXJ0ZXhwb3MueCArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueCkgKiBwaXhlbGxlbmd0aC54L3BpeGVsbGVuZ3RoLnk7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci55KSAvIChhYnMoY29ybmVyLnkpICsgYmFja3NldCkgKyAuNTsKCQl9Cgl9CglmbG9hdDJ4MiBza2V3bWF0cml4ID0gZmxvYXQyeDIoc2tldy54eSwgc2tldy56dyk7CglmbG9hdDIgZGV2Y29vcmQgPSB2ZXJ0ZXhwb3MgKiBza2V3bWF0cml4ICsgdHJhbnNsYXRlOwoJaWYgKDAgIT0gaXNfbGluZWFyX2NvdmVyYWdlKSAKCXsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoMCwgY292ZXJhZ2UgKiBjb3ZlcmFnZV9tdWx0aXBsaWVyKTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQyIGFyY2Nvb3JkID0gMSAtIGFicyhyYWRpdXNfb3V0c2V0KSArIGFhX291dHNldC9yYWRpaSAqIGNvcm5lcjsKCQl2YXJjY29vcmRfUzAueHkgPSBmbG9hdDIoYXJjY29vcmQueCsxLCBhcmNjb29yZC55KTsKCX0KCXNrX1Bvc2l0aW9uID0gZGV2Y29vcmQueHkwMTsKfQoAAAABAAAABwUAAGNvbnN0IGludCBrRmlsbEFBX1MxID0gMTsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEJXX1MxID0gMjsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEFBX1MxID0gMzsKdW5pZm9ybSBmbG9hdDQgdWNpcmNsZV9TMTsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2YXJjY29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmNsZV9TMShoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgZDsKCWlmIChpbnQoMykgPT0ga0ludmVyc2VGaWxsQldfUzEgfHwgaW50KDMpID09IGtJbnZlcnNlRmlsbEFBX1MxKSAKCXsKCQlkID0gaGFsZigobGVuZ3RoKCh1Y2lyY2xlX1MxLnh5IC0gc2tfRnJhZ0Nvb3JkLnh5KSAqIHVjaXJjbGVfUzEudykgLSAxLjApICogdWNpcmNsZV9TMS56KTsKCX0KCWVsc2UgCgl7CgkJZCA9IGhhbGYoKDEuMCAtIGxlbmd0aCgodWNpcmNsZV9TMS54eSAtIHNrX0ZyYWdDb29yZC54eSkgKiB1Y2lyY2xlX1MxLncpKSAqIHVjaXJjbGVfUzEueik7Cgl9CglpZiAoaW50KDMpID09IGtGaWxsQUFfUzEgfHwgaW50KDMpID09IGtJbnZlcnNlRmlsbEFBX1MxKSAKCXsKCQlyZXR1cm4gaGFsZjQoX2lucHV0ICogc2F0dXJhdGUoZCkpOwoJfQoJZWxzZSAKCXsKCQlyZXR1cm4gaGFsZjQoZCA+IDAuNSA/IF9pbnB1dCA6IGhhbGY0KDAuMCkpOwoJfQp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1MwLngsIHk9dmFyY2Nvb3JkX1MwLnk7CgloYWxmIGNvdmVyYWdlOwoJaWYgKDAgPT0geF9wbHVzXzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZih5KTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQgZm4gPSB4X3BsdXNfMSAqICh4X3BsdXNfMSAtIDIpOwoJCWZuID0gZm1hKHkseSwgZm4pOwoJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjbGVfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAEAAAAc2tldwkAAAB0cmFuc2xhdGUAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAFAAAAY29sb3IAAAABAAAAAAAAAA==","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CAAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAyQEAAHVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdlRleHR1cmVDb29yZHNfUzA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gdGV4Q29sb3I7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA8AAABpblRleHR1cmVDb29yZHMAAQAAAAAAAAA=","B2ABSAAABQAAIAABBYAAB7777777777774ABICAAAAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CAAAAExTS1N4AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gaGFsZjQgdUNvbG9yX1MwOwppbiBmbG9hdDIgaW5Qb3NpdGlvbjsKaW4gaGFsZiBpbkNvdmVyYWdlOwpvdXQgaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IGNvbG9yID0gdUNvbG9yX1MwOwoJY29sb3IgPSBjb2xvciAqIGluQ292ZXJhZ2U7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8zX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzFfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAeAQAAaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAoAAABpblBvc2l0aW9uAAAKAAAAaW5Db3ZlcmFnZQAAAQAAAAAAAAA=","HUQACAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CAAAAExTS1PPAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAkAQAAaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAABAAAAAAAAAA==","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAEQQGABZAA6IAAAAACAAAAAADUAAAAAAAEAAAAAIDEAAAAAAA":"CAAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABPAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlckV4dGVybmFsT0VTIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQBAEAQAAAAGQCBAMQACAIAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CAAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAGUDAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkID0gY2xhbXAoc3Vic2V0Q29vcmQsIHVjbGFtcF9TMV9jMC54eSwgdWNsYW1wX1MxX2MwLnp3KTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAMQAHOMFARUBIAADAAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAAAcBQAAY29uc3QgaW50IGtGaWxsQldfUzEgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgY292ZXJhZ2U7CglpZiAoaW50KDEpID09IGtGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TMS56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fUzEueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCX0KCWVsc2UgCgl7CgkJaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fUzEpLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJaWYgKGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gaGFsZjQoX2lucHV0ICogY292ZXJhZ2UpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABZQA6AAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CAAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACnAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCWFscGhhID0gMS4wIC0gYWxwaGE7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDQgY2lyY2xlRWRnZTsKCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","AYTRVAADQAAAOAEARAFQJAABBADAAAILBYAACCYUQD777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CAAAAExTS1NyAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7CmluIGhhbGYzIGluQ2xpcFBsYW5lOwppbiBoYWxmMyBpbklzZWN0UGxhbmU7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGYzIHZpbkNsaXBQbGFuZV9TMDsKb3V0IGhhbGYzIHZpbklzZWN0UGxhbmVfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCXZpbkNpcmNsZUVkZ2VfUzAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5DbGlwUGxhbmVfUzAgPSBpbkNsaXBQbGFuZTsKCXZpbklzZWN0UGxhbmVfUzAgPSBpbklzZWN0UGxhbmU7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gdWxvY2FsTWF0cml4X1MwLnh6ICogaW5Qb3NpdGlvbiArIHVsb2NhbE1hdHJpeF9TMC55dzsKCXNrX1Bvc2l0aW9uID0gX3RtcF8wX2luUG9zaXRpb24ueHkwMTsKfQoAAAAAAAD1AwAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGYzIHZpbkNsaXBQbGFuZV9TMDsKaW4gaGFsZjMgdmluSXNlY3RQbGFuZV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGYzIGNsaXBQbGFuZTsKCWNsaXBQbGFuZSA9IHZpbkNsaXBQbGFuZV9TMDsKCWhhbGYzIGlzZWN0UGxhbmU7Cglpc2VjdFBsYW5lID0gdmluSXNlY3RQbGFuZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZiBkaXN0YW5jZVRvSW5uZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoZCAtIGNpcmNsZUVkZ2UudykpOwoJaGFsZiBpbm5lckFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb0lubmVyRWRnZSk7CgllZGdlQWxwaGEgKj0gaW5uZXJBbHBoYTsKCWhhbGYgY2xpcCA9IGhhbGYoc2F0dXJhdGUoY2lyY2xlRWRnZS56ICogZG90KGNpcmNsZUVkZ2UueHksIGNsaXBQbGFuZS54eSkgKyBjbGlwUGxhbmUueikpOwoJY2xpcCAqPSBoYWxmKHNhdHVyYXRlKGNpcmNsZUVkZ2UueiAqIGRvdChjaXJjbGVFZGdlLnh5LCBpc2VjdFBsYW5lLnh5KSArIGlzZWN0UGxhbmUueikpOwoJZWRnZUFscGhhICo9IGNsaXA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAFAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2ULAAAAaW5DbGlwUGxhbmUADAAAAGluSXNlY3RQbGFuZQEAAAAAAAAA","HTQAAGAABBYAAAEIXBAAAGEAMAAAAAAAAAAAAAAAQAHAAAAAQAAAAAAAQQGAAAAA":"CAAAAExTS1M/AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5RdWFkRWRnZTsKb3V0IGZsb2F0NCB2UXVhZEVkZ2VfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZEVkZ2UKCXZRdWFkRWRnZV9TMCA9IGluUXVhZEVkZ2U7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgABAAAAHQMAAGluIGZsb2F0NCB2UXVhZEVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZEVkZ2UKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGYgZWRnZUFscGhhOwoJaGFsZjIgZHV2ZHggPSBoYWxmMihkRmR4KHZRdWFkRWRnZV9TMC54eSkpOwoJaGFsZjIgZHV2ZHkgPSBoYWxmMihkRmR5KHZRdWFkRWRnZV9TMC54eSkpOwoJaWYgKHZRdWFkRWRnZV9TMC56ID4gMC4wICYmIHZRdWFkRWRnZV9TMC53ID4gMC4wKSAKCXsKCQllZGdlQWxwaGEgPSBoYWxmKG1pbihtaW4odlF1YWRFZGdlX1MwLnosIHZRdWFkRWRnZV9TMC53KSArIDAuNSwgMS4wKSk7Cgl9CgllbHNlIAoJewoJCWhhbGYyIGdGID0gaGFsZjIoaGFsZigyLjAqdlF1YWRFZGdlX1MwLngqZHV2ZHgueCAtIGR1dmR4LnkpLCAgICAgICAgICAgICAgICAgaGFsZigyLjAqdlF1YWRFZGdlX1MwLngqZHV2ZHkueCAtIGR1dmR5LnkpKTsKCQllZGdlQWxwaGEgPSBoYWxmKHZRdWFkRWRnZV9TMC54KnZRdWFkRWRnZV9TMC54IC0gdlF1YWRFZGdlX1MwLnkpOwoJCWVkZ2VBbHBoYSA9IHNhdHVyYXRlKDAuNSAtIGVkZ2VBbHBoYSAvIGxlbmd0aChnRikpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAKAAAAaW5RdWFkRWRnZQAAAQAAAAAAAAA="}} \ No newline at end of file diff --git a/test_driver/driver_screenshots.dart b/test_driver/driver_screenshots.dart index cc1a095be..515018fc6 100644 --- a/test_driver/driver_screenshots.dart +++ b/test_driver/driver_screenshots.dart @@ -34,6 +34,7 @@ Future configureAndLaunch() async { ..showOverlayMinimap = false ..showOverlayInfo = true ..showOverlayShootingDetails = false + ..showOverlayThumbnailPreview = false ..enableOverlayBlurEffect = true ..viewerUseCutout = true // info diff --git a/test_driver/driver_shaders.dart b/test_driver/driver_shaders.dart index c5c053d2c..ba95337da 100644 --- a/test_driver/driver_shaders.dart +++ b/test_driver/driver_shaders.dart @@ -23,6 +23,12 @@ Future configureAndLaunch() async { // collection ..collectionBrowsingQuickActions = SettingsDefaults.collectionBrowsingQuickActions // viewer + ..showOverlayOnOpening = true + ..showOverlayMinimap = true + ..showOverlayInfo = true + ..showOverlayShootingDetails = true + ..showOverlayThumbnailPreview = true + ..enableOverlayBlurEffect = true ..imageBackground = EntryBackground.checkered // info ..infoMapStyle = EntryMapStyle.googleNormal; diff --git a/untranslated.json b/untranslated.json index be3e30d53..30b7b78cf 100644 --- a/untranslated.json +++ b/untranslated.json @@ -1,9 +1,22 @@ { - "de": [ - "entryActionConvert" + "es": [ + "videoActionMute", + "videoActionUnmute" ], - "ru": [ - "entryActionConvert" + "id": [ + "videoActionMute", + "videoActionUnmute", + "videoControlsPlay", + "videoControlsPlaySeek", + "videoControlsPlayOutside", + "videoControlsNone", + "settingsViewerShowOverlayThumbnails", + "settingsVideoControlsTile", + "settingsVideoControlsTitle", + "settingsVideoButtonsTile", + "settingsVideoButtonsTitle", + "settingsVideoGestureDoubleTapTogglePlay", + "settingsVideoGestureSideDoubleTapSeek" ] } diff --git a/whatsnew/whatsnew-en-US b/whatsnew/whatsnew-en-US index 7d89576ed..a8d209fe8 100644 --- a/whatsnew/whatsnew-en-US +++ b/whatsnew/whatsnew-en-US @@ -1,6 +1,6 @@ Thanks for using Aves! -In v1.6.1: -- recycle bin -- view small and large images at their actual size -- enjoy the app in Indonesian +In v1.6.2: +- revisited viewer: new layout, thumbnail previews, video gestures +- storage related fixes for Android 10 and older +- enjoy the app in Japanese Full changelog available on GitHub \ No newline at end of file