diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 7a4728df5..a7501e099 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -17,8 +17,8 @@ jobs: # Available versions may lag behind https://github.com/flutter/flutter.git - uses: subosito/flutter-action@v2 with: - flutter-version: '3.3.0-0.5.pre' - channel: 'beta' + flutter-version: '3.3.2' + channel: 'stable' - name: Clone the repository. uses: actions/checkout@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 23aa3e59e..659f91280 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,8 +19,8 @@ jobs: # Available versions may lag behind https://github.com/flutter/flutter.git - uses: subosito/flutter-action@v2 with: - flutter-version: '3.3.0-0.5.pre' - channel: 'beta' + flutter-version: '3.3.2' + channel: 'stable' # Workaround for this Android Gradle Plugin issue (supposedly fixed in AGP 4.1): # https://issuetracker.google.com/issues/144111441 @@ -56,15 +56,15 @@ 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_3.3.0-0.5.pre.sksl.json + flutter build appbundle -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_3.3.2.sksl.json cp build/app/outputs/bundle/playRelease/*.aab outputs - flutter build apk -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_3.3.0-0.5.pre.sksl.json + flutter build apk -t lib/main_play.dart --flavor play --bundle-sksl-path shaders_3.3.2.sksl.json cp build/app/outputs/apk/play/release/*.apk outputs (cd scripts/; ./apply_flavor_huawei.sh) - flutter build apk -t lib/main_huawei.dart --flavor huawei --bundle-sksl-path shaders_3.3.0-0.5.pre.sksl.json + flutter build apk -t lib/main_huawei.dart --flavor huawei --bundle-sksl-path shaders_3.3.2.sksl.json cp build/app/outputs/apk/huawei/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_3.3.0-0.5.pre.sksl.json + flutter build apk -t lib/main_izzy.dart --flavor izzy --split-per-abi --bundle-sksl-path shaders_3.3.2.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 61d67a565..4a258bf65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [v1.7.0] - 2022-09-19 + +### Added + +- Collection: view settings allow changing the sort order (aka ascending/descending) +- Collection / Info: edit title via IPTC / XMP +- Albums / Countries / Tags: size displayed in list view details, sort by size +- Search: `undated` and `untitled` filters +- Greek translation (thanks Emmanouil Papavergis) + +### Changed + +- upgraded Flutter to stable v3.3.2 + +### Fixed + +- opening viewer with directory context in some cases +- photo frame widget rendering in some cases +- exporting large images to BMP +- replacing entries during move/copy +- deleting binned item from the Download album + ## [v1.6.13] - 2022-08-29 ### Changed diff --git a/android/app/build.gradle b/android/app/build.gradle index aebce2c0e..7347da8c8 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -154,7 +154,7 @@ repositories { dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2' - implementation 'androidx.core:core-ktx:1.8.0' + implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.exifinterface:exifinterface:1.3.3' implementation 'androidx.multidex:multidex:2.0.1' implementation 'com.caverock:androidsvg-aar:1.4' diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c6189d907..28fa877f6 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -241,5 +241,9 @@ This change eventually prevents building the app with Flutter v3.0.2. + + diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt index eab4536ff..4da0d671c 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt @@ -12,9 +12,8 @@ import android.os.Bundle import android.util.Log import android.widget.RemoteViews import app.loup.streams_channel.StreamsChannel -import deckers.thibault.aves.channel.calls.DeviceHandler -import deckers.thibault.aves.channel.calls.MediaFetchHandler -import deckers.thibault.aves.channel.calls.MediaStoreHandler +import deckers.thibault.aves.channel.AvesByteSendingMethodCodec +import deckers.thibault.aves.channel.calls.* import deckers.thibault.aves.channel.streams.ImageByteStreamHandler import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler import deckers.thibault.aves.utils.FlutterUtils @@ -190,7 +189,9 @@ class HomeWidgetProvider : AppWidgetProvider() { // - need Context MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(context)) MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(context)) - MethodChannel(messenger, MediaFetchHandler.CHANNEL).setMethodCallHandler(MediaFetchHandler(context)) + MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(context)) + MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(context)) + MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(context)) // result streaming: dart -> platform ->->-> dart // - need Context diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt index ad2196561..d2cade1c9 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt @@ -16,6 +16,7 @@ import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.graphics.drawable.IconCompat import app.loup.streams_channel.StreamsChannel +import deckers.thibault.aves.channel.AvesByteSendingMethodCodec import deckers.thibault.aves.channel.calls.* import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler import deckers.thibault.aves.channel.calls.window.WindowHandler @@ -70,7 +71,8 @@ open class MainActivity : FlutterActivity() { MethodChannel(messenger, GeocodingHandler.CHANNEL).setMethodCallHandler(GeocodingHandler(this)) MethodChannel(messenger, GlobalSearchHandler.CHANNEL).setMethodCallHandler(GlobalSearchHandler(this)) MethodChannel(messenger, HomeWidgetHandler.CHANNEL).setMethodCallHandler(HomeWidgetHandler(this)) - MethodChannel(messenger, MediaFetchHandler.CHANNEL).setMethodCallHandler(MediaFetchHandler(this)) + MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this)) + MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this)) MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(this)) MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this)) MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this)) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/ScreenSaverService.kt b/android/app/src/main/kotlin/deckers/thibault/aves/ScreenSaverService.kt index 18847b2ea..be332d36f 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/ScreenSaverService.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/ScreenSaverService.kt @@ -4,6 +4,7 @@ import android.service.dreams.DreamService import android.util.Log import android.view.View import app.loup.streams_channel.StreamsChannel +import deckers.thibault.aves.channel.AvesByteSendingMethodCodec import deckers.thibault.aves.channel.calls.* import deckers.thibault.aves.channel.calls.window.ServiceWindowHandler import deckers.thibault.aves.channel.calls.window.WindowHandler @@ -99,7 +100,8 @@ class ScreenSaverService : DreamService() { // - need Context MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this)) MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this)) - MethodChannel(messenger, MediaFetchHandler.CHANNEL).setMethodCallHandler(MediaFetchHandler(this)) + MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this)) + MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this)) MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(this)) MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this)) MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this)) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt index ccee5b904..60c37d9ec 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt @@ -8,6 +8,7 @@ import android.os.Handler import android.os.Looper import android.util.Log import app.loup.streams_channel.StreamsChannel +import deckers.thibault.aves.channel.AvesByteSendingMethodCodec import deckers.thibault.aves.channel.calls.* import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler import deckers.thibault.aves.channel.calls.window.WindowHandler @@ -34,7 +35,8 @@ class WallpaperActivity : FlutterActivity() { // - need Context MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this)) MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this)) - MethodChannel(messenger, MediaFetchHandler.CHANNEL).setMethodCallHandler(MediaFetchHandler(this)) + MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(context)) + MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this)) MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this)) MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this)) // - need ContextWrapper diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/AvesByteSendingMethodCodec.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/AvesByteSendingMethodCodec.kt new file mode 100644 index 000000000..9ae286aff --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/AvesByteSendingMethodCodec.kt @@ -0,0 +1,52 @@ +package deckers.thibault.aves.channel + +import android.util.Log +import deckers.thibault.aves.utils.LogUtils +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodCodec +import io.flutter.plugin.common.StandardMessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import java.nio.ByteBuffer + +class AvesByteSendingMethodCodec private constructor() : MethodCodec { + override fun decodeMethodCall(methodCall: ByteBuffer): MethodCall { + return STANDARD.decodeMethodCall(methodCall) + } + + override fun decodeEnvelope(envelope: ByteBuffer): Any { + return STANDARD.decodeEnvelope(envelope) + } + + override fun encodeMethodCall(methodCall: MethodCall): ByteBuffer { + return STANDARD.encodeMethodCall(methodCall) + } + + override fun encodeSuccessEnvelope(result: Any?): ByteBuffer { + if (result is ByteArray) { + val size = result.size + return ByteBuffer.allocateDirect(4 + size).apply { + put(0) + put(result) + } + } + + Log.e(LOG_TAG, "encodeSuccessEnvelope failed with result=$result") + return ByteBuffer.allocateDirect(0) + } + + override fun encodeErrorEnvelope(errorCode: String, errorMessage: String?, errorDetails: Any?): ByteBuffer { + Log.e(LOG_TAG, "encodeErrorEnvelope failed with errorCode=$errorCode, errorMessage=$errorMessage, errorDetails=$errorDetails") + return ByteBuffer.allocateDirect(0) + } + + override fun encodeErrorEnvelopeWithStacktrace(errorCode: String, errorMessage: String?, errorDetails: Any?, errorStacktrace: String?): ByteBuffer { + Log.e(LOG_TAG, "encodeErrorEnvelopeWithStacktrace failed with errorCode=$errorCode, errorMessage=$errorMessage, errorDetails=$errorDetails, errorStacktrace=$errorStacktrace") + return ByteBuffer.allocateDirect(0) + } + + companion object { + private val LOG_TAG = LogUtils.createTag() + val INSTANCE = AvesByteSendingMethodCodec() + private val STANDARD = StandardMethodCodec(StandardMessageCodec.INSTANCE) + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt index 4ad4d93fc..fe779c968 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt @@ -21,6 +21,7 @@ import deckers.thibault.aves.model.provider.ContentImageProvider import deckers.thibault.aves.model.provider.ImageProvider import deckers.thibault.aves.utils.BitmapUtils import deckers.thibault.aves.utils.BitmapUtils.getBytes +import deckers.thibault.aves.utils.FileUtils.transferFrom import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.MimeTypes import deckers.thibault.aves.utils.MimeTypes.canReadWithExifInterface @@ -96,7 +97,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler { val videoStartOffset = sizeBytes - videoSizeBytes StorageUtils.openInputStream(context, uri)?.let { input -> input.skip(videoStartOffset) - copyEmbeddedBytes(result, MimeTypes.MP4, displayName, input) + copyEmbeddedBytes(result, MimeTypes.MP4, displayName, input, videoSizeBytes) } return } @@ -121,7 +122,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler { Helper.readMimeType(input)?.let { embedMimeType = it } } embedMimeType?.let { mime -> - copyEmbeddedBytes(result, mime, displayName, bytes.inputStream()) + copyEmbeddedBytes(result, mime, displayName, bytes.inputStream(), bytes.size.toLong()) return } } @@ -172,7 +173,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler { } } - copyEmbeddedBytes(result, embedMimeType, displayName, embedBytes.inputStream()) + copyEmbeddedBytes(result, embedMimeType, displayName, embedBytes.inputStream(), embedBytes.size.toLong()) return } catch (e: XMPException) { result.error("extractXmpDataProp-xmp", "failed to read XMP directory for uri=$uri prop=$dataProp", e.message) @@ -190,16 +191,19 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler { result.error("extractXmpDataProp-empty", "failed to extract file from XMP uri=$uri prop=$dataProp", null) } - private fun copyEmbeddedBytes(result: MethodChannel.Result, mimeType: String, displayName: String?, embeddedByteStream: InputStream) { + private fun copyEmbeddedBytes( + result: MethodChannel.Result, + mimeType: String, + displayName: String?, + embeddedByteStream: InputStream, + embeddedByteLength: Long, + ) { val extension = extensionFor(mimeType) - val file = File.createTempFile("aves", extension, context.cacheDir).apply { + val targetFile = File.createTempFile("aves", extension, context.cacheDir).apply { deleteOnExit() - outputStream().use { output -> - embeddedByteStream.use { input -> - input.copyTo(output) - } - } + transferFrom(embeddedByteStream, embeddedByteLength) } + val authority = "${context.applicationContext.packageName}.file_provider" val uri = if (displayName != null) { // add extension to ease type identification when sharing this content @@ -208,9 +212,9 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler { } else { "$displayName$extension" } - FileProvider.getUriForFile(context, authority, file, displayNameWithExtension) + FileProvider.getUriForFile(context, authority, targetFile, displayNameWithExtension) } else { - FileProvider.getUriForFile(context, authority, file) + FileProvider.getUriForFile(context, authority, targetFile) } val resultFields: FieldMap = hashMapOf( "uri" to uri.toString(), diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchBytesHandler.kt similarity index 71% rename from android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchHandler.kt rename to android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchBytesHandler.kt index ba032ec2b..3a46e1e1e 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchBytesHandler.kt @@ -3,16 +3,11 @@ package deckers.thibault.aves.channel.calls import android.content.Context import android.graphics.Rect import android.net.Uri -import com.bumptech.glide.Glide -import deckers.thibault.aves.channel.calls.Coresult.Companion.safe import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend import deckers.thibault.aves.channel.calls.fetchers.RegionFetcher import deckers.thibault.aves.channel.calls.fetchers.SvgRegionFetcher import deckers.thibault.aves.channel.calls.fetchers.ThumbnailFetcher import deckers.thibault.aves.channel.calls.fetchers.TiffRegionFetcher -import deckers.thibault.aves.model.FieldMap -import deckers.thibault.aves.model.provider.ImageProvider.ImageOpCallback -import deckers.thibault.aves.model.provider.ImageProviderFactory.getProvider import deckers.thibault.aves.utils.MimeTypes import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel @@ -23,7 +18,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import kotlin.math.roundToInt -class MediaFetchHandler(private val context: Context) : MethodCallHandler { +class MediaFetchBytesHandler(private val context: Context) : MethodCallHandler { private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private val density = context.resources.displayMetrics.density @@ -31,34 +26,12 @@ class MediaFetchHandler(private val context: Context) : MethodCallHandler { override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { - "getEntry" -> ioScope.launch { safe(call, result, ::getEntry) } "getThumbnail" -> ioScope.launch { safeSuspend(call, result, ::getThumbnail) } "getRegion" -> ioScope.launch { safeSuspend(call, result, ::getRegion) } - "clearSizedThumbnailDiskCache" -> ioScope.launch { safe(call, result, ::clearSizedThumbnailDiskCache) } else -> result.notImplemented() } } - private fun getEntry(call: MethodCall, result: MethodChannel.Result) { - val mimeType = call.argument("mimeType") // MIME type is optional - val uri = call.argument("uri")?.let { Uri.parse(it) } - if (uri == null) { - result.error("getEntry-args", "missing arguments", null) - return - } - - val provider = getProvider(uri) - if (provider == null) { - result.error("getEntry-provider", "failed to find provider for uri=$uri", null) - return - } - - provider.fetchSingle(context, uri, mimeType, object : ImageOpCallback { - override fun onSuccess(fields: FieldMap) = result.success(fields) - override fun onFailure(throwable: Throwable) = result.error("getEntry-failure", "failed to get entry for uri=$uri", throwable.message) - }) - } - private suspend fun getThumbnail(call: MethodCall, result: MethodChannel.Result) { val uri = call.argument("uri") val mimeType = call.argument("mimeType") @@ -77,17 +50,17 @@ class MediaFetchHandler(private val context: Context) : MethodCallHandler { // convert DIP to physical pixels here, instead of using `devicePixelRatio` in Flutter ThumbnailFetcher( - context, - uri, - mimeType, - dateModifiedSecs, - rotationDegrees, - isFlipped, + context = context, + uri = uri, + mimeType = mimeType, + dateModifiedSecs = dateModifiedSecs, + rotationDegrees = rotationDegrees, + isFlipped = isFlipped, width = (widthDip * density).roundToInt(), height = (heightDip * density).roundToInt(), pageId = pageId, defaultSize = (defaultSizeDip * density).roundToInt(), - result, + result = result, ).fetch() } @@ -137,12 +110,7 @@ class MediaFetchHandler(private val context: Context) : MethodCallHandler { } } - private fun clearSizedThumbnailDiskCache(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) { - Glide.get(context).clearDiskCache() - result.success(null) - } - companion object { - const val CHANNEL = "deckers.thibault/aves/media_fetch" + const val CHANNEL = "deckers.thibault/aves/media_fetch_bytes" } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchObjectHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchObjectHandler.kt new file mode 100644 index 000000000..665d17a17 --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchObjectHandler.kt @@ -0,0 +1,57 @@ +package deckers.thibault.aves.channel.calls + +import android.content.Context +import android.net.Uri +import com.bumptech.glide.Glide +import deckers.thibault.aves.channel.calls.Coresult.Companion.safe +import deckers.thibault.aves.model.FieldMap +import deckers.thibault.aves.model.provider.ImageProvider.ImageOpCallback +import deckers.thibault.aves.model.provider.ImageProviderFactory.getProvider +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch + +class MediaFetchObjectHandler(private val context: Context) : MethodCallHandler { + private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "getEntry" -> ioScope.launch { safe(call, result, ::getEntry) } + "clearSizedThumbnailDiskCache" -> ioScope.launch { safe(call, result, ::clearSizedThumbnailDiskCache) } + else -> result.notImplemented() + } + } + + private fun getEntry(call: MethodCall, result: MethodChannel.Result) { + val mimeType = call.argument("mimeType") // MIME type is optional + val uri = call.argument("uri")?.let { Uri.parse(it) } + if (uri == null) { + result.error("getEntry-args", "missing arguments", null) + return + } + + val provider = getProvider(uri) + if (provider == null) { + result.error("getEntry-provider", "failed to find provider for uri=$uri", null) + return + } + + provider.fetchSingle(context, uri, mimeType, object : ImageOpCallback { + override fun onSuccess(fields: FieldMap) = result.success(fields) + override fun onFailure(throwable: Throwable) = result.error("getEntry-failure", "failed to get entry for uri=$uri", throwable.message) + }) + } + + private fun clearSizedThumbnailDiskCache(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) { + Glide.get(context).clearDiskCache() + result.success(null) + } + + companion object { + const val CHANNEL = "deckers.thibault/aves/media_fetch_object" + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt index 6314e9c2a..7df4f8f25 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt @@ -427,8 +427,9 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { // - XMP / photoshop:DateCreated // - PNG / TIME / LAST_MODIFICATION_TIME // - Video / METADATA_KEY_DATE - // set `KEY_XMP_TITLE` from this field: + // set `KEY_XMP_TITLE` from these fields (by precedence): // - XMP / dc:title + // - IPTC / object-name // set `KEY_XMP_SUBJECTS` from these fields (by precedence): // - XMP / dc:subject // - IPTC / keywords @@ -567,9 +568,14 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { metadata.getDirectoriesOfType(XmpDirectory::class.java).map { it.xmpMeta }.forEach(::processXmp) // XMP fallback to IPTC - if (!metadataMap.containsKey(KEY_XMP_SUBJECTS)) { + if (!metadataMap.containsKey(KEY_XMP_TITLE) || !metadataMap.containsKey(KEY_XMP_SUBJECTS)) { for (dir in metadata.getDirectoriesOfType(IptcDirectory::class.java)) { - dir.keywords?.let { metadataMap[KEY_XMP_SUBJECTS] = it.joinToString(XMP_SUBJECTS_SEPARATOR) } + if (!metadataMap.containsKey(KEY_XMP_TITLE)) { + dir.getSafeString(IptcDirectory.TAG_OBJECT_NAME) { metadataMap[KEY_XMP_TITLE] = it } + } + if (!metadataMap.containsKey(KEY_XMP_SUBJECTS)) { + dir.keywords?.let { metadataMap[KEY_XMP_SUBJECTS] = it.joinToString(XMP_SUBJECTS_SEPARATOR) } + } } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt index 504573e6b..2066e5035 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt @@ -23,7 +23,10 @@ import deckers.thibault.aves.utils.MimeTypes.needRotationAfterGlide import deckers.thibault.aves.utils.StorageUtils import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.EventChannel.EventSink -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch import java.io.InputStream class ImageByteStreamHandler(private val context: Context, private val arguments: Any?) : EventChannel.StreamHandler { @@ -82,6 +85,7 @@ class ImageByteStreamHandler(private val context: Context, private val arguments val mimeType = arguments["mimeType"] as String? val uri = (arguments["uri"] as String?)?.let { Uri.parse(it) } + val sizeBytes = (arguments["sizeBytes"] as Number?)?.toLong() val rotationDegrees = arguments["rotationDegrees"] as Int val isFlipped = arguments["isFlipped"] as Boolean val pageId = arguments["pageId"] as Int? @@ -96,7 +100,7 @@ class ImageByteStreamHandler(private val context: Context, private val arguments streamVideoByGlide(uri, mimeType) } else if (!canDecodeWithFlutter(mimeType, rotationDegrees, isFlipped)) { // decode exotic format on platform side, then encode it in portable format for Flutter - streamImageByGlide(uri, pageId, mimeType, rotationDegrees, isFlipped) + streamImageByGlide(uri, pageId, mimeType, sizeBytes, rotationDegrees, isFlipped) } else { // to be decoded by Flutter streamImageAsIs(uri, mimeType) @@ -112,13 +116,20 @@ class ImageByteStreamHandler(private val context: Context, private val arguments } } - private suspend fun streamImageByGlide(uri: Uri, pageId: Int?, mimeType: String, rotationDegrees: Int, isFlipped: Boolean) { + private suspend fun streamImageByGlide( + uri: Uri, + pageId: Int?, + mimeType: String, + sizeBytes: Long?, + rotationDegrees: Int, + isFlipped: Boolean, + ) { val model: Any = if (isHeic(mimeType) && pageId != null) { MultiTrackImage(context, uri, pageId) } else if (mimeType == MimeTypes.TIFF) { TiffImage(context, uri, pageId) } else { - StorageUtils.getGlideSafeUri(context, uri, mimeType) + StorageUtils.getGlideSafeUri(context, uri, mimeType, sizeBytes) } val target = Glide.with(context) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt index 93c5795f6..635148196 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt @@ -3,6 +3,7 @@ package deckers.thibault.aves.metadata import android.content.Context import android.net.Uri import androidx.exifinterface.media.ExifInterface +import deckers.thibault.aves.utils.FileUtils.transferFrom import deckers.thibault.aves.utils.MimeTypes import deckers.thibault.aves.utils.StorageUtils import java.io.File @@ -122,7 +123,7 @@ object Metadata { // we try and read metadata from large files by copying an arbitrary amount from its beginning // to a temporary file, and reusing that preview file for all metadata reading purposes - private const val previewSize = 5 * (1 shl 20) // MB + private const val previewSize: Long = 5 * (1 shl 20) // MB private val previewFiles = HashMap() @@ -155,13 +156,7 @@ object Metadata { fun createPreviewFile(context: Context, uri: Uri): File { return File.createTempFile("aves", null, context.cacheDir).apply { deleteOnExit() - outputStream().use { output -> - StorageUtils.openInputStream(context, uri)?.use { input -> - val b = ByteArray(previewSize) - input.read(b, 0, previewSize) - output.write(b) - } - } + transferFrom(StorageUtils.openInputStream(context, uri), previewSize) } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/AvesEntry.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/AvesEntry.kt index 54aded97e..abb2afed5 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/AvesEntry.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/AvesEntry.kt @@ -11,6 +11,15 @@ class AvesEntry(map: FieldMap) { val height = map["height"] as Int val rotationDegrees = map["rotationDegrees"] as Int val isFlipped = map["isFlipped"] as Boolean + val sizeBytes = toLong(map["sizeBytes"]) val trashed = map["trashed"] as Boolean val trashPath = map["trashPath"] as String? + + companion object { + // convenience method + private fun toLong(o: Any?): Long? = when (o) { + is Int -> o.toLong() + else -> o as? Long + } + } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt index 42855da21..4e7ff6ca3 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt @@ -258,7 +258,6 @@ class SourceEntry { } } - companion object { // convenience method private fun toLong(o: Any?): Long? = when (o) { 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 a4dc00da7..3503afe89 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 @@ -29,6 +29,8 @@ import deckers.thibault.aves.model.ExifOrientationOp import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.model.NameConflictStrategy import deckers.thibault.aves.utils.* +import deckers.thibault.aves.utils.FileUtils.transferFrom +import deckers.thibault.aves.utils.FileUtils.transferTo import deckers.thibault.aves.utils.MimeTypes.canEditExif import deckers.thibault.aves.utils.MimeTypes.canEditIptc import deckers.thibault.aves.utils.MimeTypes.canEditXmp @@ -178,7 +180,7 @@ abstract class ImageProvider { } else if (sourceMimeType == MimeTypes.SVG) { SvgImage(activity, sourceUri) } else { - StorageUtils.getGlideSafeUri(activity, sourceUri, sourceMimeType) + StorageUtils.getGlideSafeUri(activity, sourceUri, sourceMimeType, sourceEntry.sizeBytes) } // request a fresh image with the highest quality format @@ -298,11 +300,7 @@ abstract class ImageProvider { } else { val editableFile = File.createTempFile("aves", null).apply { deleteOnExit() - outputStream().use { output -> - ByteArrayInputStream(bytes).use { imageInput -> - imageInput.copyTo(output) - } - } + transferFrom(ByteArrayInputStream(bytes), bytes.size.toLong()) } val exif = ExifInterface(editableFile) @@ -425,29 +423,24 @@ abstract class ImageProvider { val editableFile = File.createTempFile("aves", null).apply { deleteOnExit() try { - outputStream().use { output -> - if (videoSize != null) { - // handle motion photo and embedded video separately - val imageSize = (originalFileSize - videoSize).toInt() - videoBytes = ByteArray(videoSize) + if (videoSize != null) { + // handle motion photo and embedded video separately + val imageSize = (originalFileSize - videoSize).toInt() + videoBytes = ByteArray(videoSize) - StorageUtils.openInputStream(context, uri)?.let { input -> - val imageBytes = ByteArray(imageSize) - input.read(imageBytes, 0, imageSize) - input.read(videoBytes, 0, videoSize) + StorageUtils.openInputStream(context, uri)?.let { input -> + val imageBytes = ByteArray(imageSize) + input.read(imageBytes, 0, imageSize) + input.read(videoBytes, 0, videoSize) - // copy only the image to a temporary file for editing - // video will be appended after metadata modification - ByteArrayInputStream(imageBytes).use { imageInput -> - imageInput.copyTo(output) - } - } - } else { - // copy original file to a temporary file for editing - StorageUtils.openInputStream(context, uri)?.use { imageInput -> - imageInput.copyTo(output) - } + // copy only the image to a temporary file for editing + // video will be appended after metadata modification + transferFrom(ByteArrayInputStream(imageBytes), imageBytes.size.toLong()) } + } else { + // copy original file to a temporary file for editing + val inputStream = StorageUtils.openInputStream(context, uri) + transferFrom(inputStream, originalFileSize) } } catch (e: Exception) { callback.onFailure(e) @@ -464,7 +457,7 @@ abstract class ImageProvider { } // copy the edited temporary file back to the original - copyTo(context, mimeType, sourceFile = editableFile, targetUri = uri, targetPath = path) + copyFileTo(context, mimeType, sourceFile = editableFile, targetUri = uri, targetPath = path) if (autoCorrectTrailerOffset && !checkTrailerOffset(context, path, uri, mimeType, videoSize, editableFile, callback)) { return false @@ -498,29 +491,24 @@ abstract class ImageProvider { val editableFile = File.createTempFile("aves", null).apply { deleteOnExit() try { - outputStream().use { output -> - if (videoSize != null) { - // handle motion photo and embedded video separately - val imageSize = (originalFileSize - videoSize).toInt() - videoBytes = ByteArray(videoSize) + if (videoSize != null) { + // handle motion photo and embedded video separately + val imageSize = (originalFileSize - videoSize).toInt() + videoBytes = ByteArray(videoSize) - StorageUtils.openInputStream(context, uri)?.let { input -> - val imageBytes = ByteArray(imageSize) - input.read(imageBytes, 0, imageSize) - input.read(videoBytes, 0, videoSize) + StorageUtils.openInputStream(context, uri)?.let { input -> + val imageBytes = ByteArray(imageSize) + input.read(imageBytes, 0, imageSize) + input.read(videoBytes, 0, videoSize) - // copy only the image to a temporary file for editing - // video will be appended after metadata modification - ByteArrayInputStream(imageBytes).use { imageInput -> - imageInput.copyTo(output) - } - } - } else { - // copy original file to a temporary file for editing - StorageUtils.openInputStream(context, uri)?.use { imageInput -> - imageInput.copyTo(output) - } + // copy only the image to a temporary file for editing + // video will be appended after metadata modification + transferFrom(ByteArrayInputStream(imageBytes), imageBytes.size.toLong()) } + } else { + // copy original file to a temporary file for editing + val inputStream = StorageUtils.openInputStream(context, uri) + transferFrom(inputStream, originalFileSize) } } catch (e: Exception) { callback.onFailure(e) @@ -551,7 +539,7 @@ abstract class ImageProvider { } // copy the edited temporary file back to the original - copyTo(context, mimeType, sourceFile = editableFile, targetUri = uri, targetPath = path) + copyFileTo(context, mimeType, sourceFile = editableFile, targetUri = uri, targetPath = path) if (autoCorrectTrailerOffset && !checkTrailerOffset(context, path, uri, mimeType, videoSize, editableFile, callback)) { return false @@ -626,7 +614,7 @@ abstract class ImageProvider { try { // copy the edited temporary file back to the original - copyTo(context, mimeType, sourceFile = editableFile, targetUri = uri, targetPath = path) + copyFileTo(context, mimeType, sourceFile = editableFile, targetUri = uri, targetPath = path) if (autoCorrectTrailerOffset && !checkTrailerOffset(context, path, uri, mimeType, videoSize, editableFile, callback)) { return false @@ -926,26 +914,13 @@ abstract class ImageProvider { callback.onFailure(Exception("failed to get trailer video size")) return } - val bytesToCopy = originalFileSize - videoSize val editableFile = File.createTempFile("aves", null).apply { deleteOnExit() try { - outputStream().use { output -> - // reopen input to read from start - StorageUtils.openInputStream(context, uri)?.use { input -> - // partial copy - var bytesRemaining: Long = bytesToCopy - val buffer = ByteArray(DEFAULT_BUFFER_SIZE) - var bytes = input.read(buffer) - while (bytes >= 0 && bytesRemaining > 0) { - val len = if (bytes > bytesRemaining) bytesRemaining.toInt() else bytes - output.write(buffer, 0, len) - bytesRemaining -= len - bytes = input.read(buffer) - } - } - } + val inputStream = StorageUtils.openInputStream(context, uri) + // partial copy + transferFrom(inputStream, originalFileSize - videoSize) } catch (e: Exception) { Log.d(LOG_TAG, "failed to remove trailer video", e) callback.onFailure(e) @@ -955,7 +930,7 @@ abstract class ImageProvider { try { // copy the edited temporary file back to the original - copyTo(context, mimeType, sourceFile = editableFile, targetUri = uri, targetPath = path) + copyFileTo(context, mimeType, sourceFile = editableFile, targetUri = uri, targetPath = path) } catch (e: IOException) { callback.onFailure(e) return @@ -998,7 +973,7 @@ abstract class ImageProvider { try { // copy the edited temporary file back to the original - copyTo(context, mimeType, sourceFile = editableFile, targetUri = uri, targetPath = path) + copyFileTo(context, mimeType, sourceFile = editableFile, targetUri = uri, targetPath = path) if (!types.contains(Metadata.TYPE_XMP) && !checkTrailerOffset(context, path, uri, mimeType, videoSize, editableFile, callback)) { return @@ -1012,25 +987,21 @@ abstract class ImageProvider { scanPostMetadataEdit(context, path, uri, mimeType, newFields, callback) } - private fun copyTo( + private fun copyFileTo( context: Context, mimeType: String, sourceFile: File, targetUri: Uri, targetPath: String ) { - sourceFile.inputStream().use { input -> - // truncate is necessary when overwriting a longer file - val targetStream = if (isMediaUriPermissionGranted(context, targetUri, mimeType)) { - StorageUtils.openOutputStream(context, targetUri, mimeType, "wt") ?: throw Exception("failed to open output stream for uri=$targetUri") - } else { - val documentUri = StorageUtils.getDocumentFile(context, targetPath, targetUri)?.uri ?: throw Exception("failed to get document file for path=$targetPath, uri=$targetUri") - context.contentResolver.openOutputStream(documentUri, "wt") ?: throw Exception("failed to open output stream from documentUri=$documentUri for path=$targetPath, uri=$targetUri") - } - targetStream.use { output -> - input.copyTo(output) - } + // truncate is necessary when overwriting a longer file + val targetStream = if (isMediaUriPermissionGranted(context, targetUri, mimeType)) { + StorageUtils.openOutputStream(context, targetUri, mimeType, "wt") ?: throw Exception("failed to open output stream for uri=$targetUri") + } else { + val documentUri = StorageUtils.getDocumentFile(context, targetPath, targetUri)?.uri ?: throw Exception("failed to get document file for path=$targetPath, uri=$targetUri") + context.contentResolver.openOutputStream(documentUri, "wt") ?: throw Exception("failed to open output stream from documentUri=$documentUri for path=$targetPath, uri=$targetUri") } + sourceFile.transferTo(targetStream) } interface ImageOpCallback { 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 b171334fd..442d35190 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 @@ -56,7 +56,7 @@ class MediaStoreImageProvider : ImageProvider() { val relativePath = PathSegments(context, relativePathDirectory).relativeDir if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && relativePath != null) { selection = "${MediaStore.MediaColumns.RELATIVE_PATH} = ? AND ${MediaColumns.PATH} LIKE ?" - selectionArgs = arrayOf(relativePath, "relativePathDirectory%") + selectionArgs = arrayOf(relativePath, "$relativePathDirectory%") } else { selection = "${MediaColumns.PATH} LIKE ?" selectionArgs = arrayOf("$relativePathDirectory%") diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/BmpWriter.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/BmpWriter.kt index 29599a028..8e097c77b 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/BmpWriter.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/BmpWriter.kt @@ -54,9 +54,8 @@ object BmpWriter { val padPerRow = (4 - (biWidth * BYTE_PER_PIXEL) % 4) % 4 val biSizeImage = (biWidth * BYTE_PER_PIXEL + padPerRow) * biHeight val bfSize = FILE_HEADER_SIZE + INFO_HEADER_SIZE + biSizeImage - val buffer = ByteBuffer.allocate(bfSize) - val pixels = IntArray(biWidth * biHeight) - bitmap.getPixels(pixels, 0, biWidth, 0, 0, biWidth, biHeight) + + var buffer = ByteBuffer.allocate(FILE_HEADER_SIZE + INFO_HEADER_SIZE) // file header buffer.put(bfType) @@ -78,11 +77,17 @@ object BmpWriter { buffer.put(biClrUsed) buffer.put(biClrImportant) + outputStream.write(buffer.array()) + // pixels + buffer = ByteBuffer.allocate(biWidth * BYTE_PER_PIXEL + padPerRow) + val pixels = IntArray(biWidth) val rgb = ByteArray(BYTE_PER_PIXEL) var value: Int var row = biHeight - 1 while (row >= 0) { + bitmap.getPixels(pixels, 0, biWidth, 0, row, biWidth, 1) + var column = 0 while (column < biWidth) { /* @@ -91,7 +96,7 @@ object BmpWriter { green: (value shr 8 and 0xFF).toByte() blue: (value and 0xFF).toByte() */ - value = pixels[row * biWidth + column] + value = pixels[column] // blue: [0], green: [1], red: [2] rgb[0] = (value and 0xFF).toByte() rgb[1] = (value shr 8 and 0xFF).toByte() @@ -102,10 +107,9 @@ object BmpWriter { if (padPerRow > 0) { buffer.put(pad, 0, padPerRow) } + outputStream.write(buffer.array()) + buffer.clear() row-- } - - // write to output stream - outputStream.write(buffer.array()) } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/FileUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/FileUtils.kt new file mode 100644 index 000000000..6b8a37ebd --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/FileUtils.kt @@ -0,0 +1,45 @@ +package deckers.thibault.aves.utils + +import android.os.Build +import java.io.File +import java.io.InputStream +import java.io.OutputStream +import java.nio.channels.Channels +import java.nio.channels.FileChannel +import java.nio.file.StandardOpenOption + +object FileUtils { + fun File.transferFrom(inputStream: InputStream?, streamLength: Long?) { + inputStream ?: return + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && streamLength != null) { + FileChannel.open(toPath(), StandardOpenOption.WRITE).use { fileOutput -> + Channels.newChannel(inputStream).use { input -> + fileOutput.transferFrom(input, 0, streamLength) + } + } + } else { + outputStream().use { fileOutput -> + inputStream.use { input -> + input.copyTo(fileOutput) + } + } + } + } + + fun File.transferTo(outputStream: OutputStream) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + FileChannel.open(toPath()).use { fileInput -> + Channels.newChannel(outputStream).use { output -> + fileInput.transferTo(0, fileInput.size(), output) + } + } + } else { + inputStream().use { fileInput -> + outputStream.use { output -> + fileInput.copyTo(output) + } + } + } + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt index 4491eec38..2d78f9a52 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt @@ -171,6 +171,7 @@ object PermissionManager { // returns paths accessible to the app (granted by the user or by default) private fun getAccessibleDirs(context: Context): Set { val accessibleDirs = HashSet(getGrantedDirs(context)) + accessibleDirs.addAll(context.getExternalFilesDirs(null).filterNotNull().map { it.path }) // until API 18 / Android 4.3 / Jelly Bean MR2, removable storage is accessible by default like primary storage // from API 19 / Android 4.4 / KitKat, removable storage requires access permission, at the file level diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt index 33db8d6f6..fef6748b5 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt @@ -16,6 +16,7 @@ import android.text.TextUtils import android.util.Log import androidx.annotation.RequiresApi import com.commonsware.cwac.document.DocumentFileCompat +import deckers.thibault.aves.utils.FileUtils.transferFrom import deckers.thibault.aves.utils.MimeTypes.isImage import deckers.thibault.aves.utils.MimeTypes.isVideo import deckers.thibault.aves.utils.PermissionManager.getGrantedDirForPath @@ -507,7 +508,7 @@ object StorageUtils { // to work around a bug from Android 10 where metadata redaction corrupts HEIC images. // This loader relies on `MediaStore.setRequireOriginal` but this yields a `SecurityException` // for some non image/video content URIs (e.g. `downloads`, `file`) - fun getGlideSafeUri(context: Context, uri: Uri, mimeType: String): Uri { + fun getGlideSafeUri(context: Context, uri: Uri, mimeType: String, sizeBytes: Long? = null): Uri { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && isMediaStoreContentUri(uri)) { val uriPath = uri.path when { @@ -521,11 +522,7 @@ object StorageUtils { File.createTempFile("aves", null).apply { deleteOnExit() try { - outputStream().use { output -> - openInputStream(context, uri)?.use { input -> - input.copyTo(output) - } - } + transferFrom(openInputStream(context, uri), sizeBytes) return Uri.fromFile(this) } catch (e: Exception) { Log.e(LOG_TAG, "failed to create temporary file from uri=$uri", e) diff --git a/android/app/src/main/res/values-el/strings.xml b/android/app/src/main/res/values-el/strings.xml new file mode 100644 index 000000000..2635a337b --- /dev/null +++ b/android/app/src/main/res/values-el/strings.xml @@ -0,0 +1,12 @@ + + + Aves + Κορνίζα + Ταπετσαρία + Αναζήτηση + Βίντεο + Σάρωση πολυμέσων + Σάρωση εικόνων & Βίντεο + Σάρωση στοιχείων + Διακοπή + \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 56449bb12..f35c09e44 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -10,8 +10,8 @@ buildscript { classpath 'com.android.tools.build:gradle:7.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // GMS & Firebase Crashlytics (used by some flavors only) - classpath 'com.google.gms:google-services:4.3.13' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.1' + classpath 'com.google.gms:google-services:4.3.14' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' // HMS (used by some flavors only) classpath 'com.huawei.agconnect:agcp:1.5.2.300' } diff --git a/fastlane/metadata/android/de/images/phoneScreenshots/5.png b/fastlane/metadata/android/de/images/phoneScreenshots/5.png index 988a95b07..64e691838 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/el/full_description.txt b/fastlane/metadata/android/el/full_description.txt new file mode 100644 index 000000000..c183af4c3 --- /dev/null +++ b/fastlane/metadata/android/el/full_description.txt @@ -0,0 +1,5 @@ +Η εφαρμογή Aves μπορεί να διαχειριστεί όλα τα είδη εικόνων και βίντεο, συμπεριλαμβανομένων των κλασσικών JPEG και MP4, αλλά και πιο προχωρημένα πράγματα όπως multi-page TIFFs, SVGs, old AVIs και ακόμα περισσότερα! Σαρώνει τη συλλογή των πολυμέσων σας για να αναγνωρίσει Φωτογραφίες με κίνηση, Πανοραμικές (aka photo spheres), Βίντεο 360°, καθώς και GeoTIFF αρχεία. + +Η πλοήγηση και η αναζήτηση αποτελούν σημαντικό μέρος της εφαρμογής Aves. Ο στόχος της εφαρμογής είναι να παρέχει στους χρήστες εύκολη και γρήγορη πρόσβαση στα άλμπουμ, σε φωτογραφίες, ετικέτες, χάρτες κ.λπ. + +Η εφαρμογή Aves εγκαθίσταται στο λογισμικό Android (συμβατότητα από API 19 έως 33, δηλαδή από KitKat έως Android 13) με δυνατότητες όπως γραφικά στοιχεία, συντομεύσεις, προφύλαξη οθόνης και global search. Μπορεί επίσης να χρησιμοποιηθεί ως εφαρμογή προβολής και επιλογής πολυμέσων. \ No newline at end of file diff --git a/fastlane/metadata/android/el/images/featureGraphic.png b/fastlane/metadata/android/el/images/featureGraphic.png new file mode 100644 index 000000000..eaadbbef5 Binary files /dev/null and b/fastlane/metadata/android/el/images/featureGraphic.png differ diff --git a/fastlane/metadata/android/el/images/phoneScreenshots/1.png b/fastlane/metadata/android/el/images/phoneScreenshots/1.png new file mode 100644 index 000000000..54c24fa81 Binary files /dev/null and b/fastlane/metadata/android/el/images/phoneScreenshots/1.png differ diff --git a/fastlane/metadata/android/el/images/phoneScreenshots/2.png b/fastlane/metadata/android/el/images/phoneScreenshots/2.png new file mode 100644 index 000000000..96dae9f00 Binary files /dev/null and b/fastlane/metadata/android/el/images/phoneScreenshots/2.png differ diff --git a/fastlane/metadata/android/el/images/phoneScreenshots/3.png b/fastlane/metadata/android/el/images/phoneScreenshots/3.png new file mode 100644 index 000000000..bc74450d4 Binary files /dev/null and b/fastlane/metadata/android/el/images/phoneScreenshots/3.png differ diff --git a/fastlane/metadata/android/el/images/phoneScreenshots/4.png b/fastlane/metadata/android/el/images/phoneScreenshots/4.png new file mode 100644 index 000000000..31a9b5db9 Binary files /dev/null and b/fastlane/metadata/android/el/images/phoneScreenshots/4.png differ diff --git a/fastlane/metadata/android/el/images/phoneScreenshots/5.png b/fastlane/metadata/android/el/images/phoneScreenshots/5.png new file mode 100644 index 000000000..24d25bb3f Binary files /dev/null and b/fastlane/metadata/android/el/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/el/images/phoneScreenshots/6.png b/fastlane/metadata/android/el/images/phoneScreenshots/6.png new file mode 100644 index 000000000..b8873682f Binary files /dev/null and b/fastlane/metadata/android/el/images/phoneScreenshots/6.png differ diff --git a/fastlane/metadata/android/el/images/phoneScreenshots/7.png b/fastlane/metadata/android/el/images/phoneScreenshots/7.png new file mode 100644 index 000000000..be70a0288 Binary files /dev/null and b/fastlane/metadata/android/el/images/phoneScreenshots/7.png differ diff --git a/fastlane/metadata/android/el/short_description.txt b/fastlane/metadata/android/el/short_description.txt new file mode 100644 index 000000000..fd9a88558 --- /dev/null +++ b/fastlane/metadata/android/el/short_description.txt @@ -0,0 +1 @@ +Συλλογή φωτογραφιών και εξερεύνηση των μεταδεδομένων. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/1080.txt b/fastlane/metadata/android/en-US/changelogs/1080.txt new file mode 100644 index 000000000..af4200674 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/1080.txt @@ -0,0 +1,5 @@ +In v1.7.0: +- change the sort order +- edit image titles +- enjoy the app in Greek +Full changelog available on GitHub \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png index 1d4f88433..82206f5be 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/es-MX/images/phoneScreenshots/5.png b/fastlane/metadata/android/es-MX/images/phoneScreenshots/5.png index 98b0f8691..0014da392 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/fr/images/phoneScreenshots/5.png b/fastlane/metadata/android/fr/images/phoneScreenshots/5.png index d43e85964..3378ac3e7 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/id/images/phoneScreenshots/5.png b/fastlane/metadata/android/id/images/phoneScreenshots/5.png index e65c058db..a0efbf155 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/it/images/phoneScreenshots/5.png b/fastlane/metadata/android/it/images/phoneScreenshots/5.png index af8bc2c4d..7fc3c9d00 100644 Binary files a/fastlane/metadata/android/it/images/phoneScreenshots/5.png and b/fastlane/metadata/android/it/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/ja/images/phoneScreenshots/5.png b/fastlane/metadata/android/ja/images/phoneScreenshots/5.png index a893c9401..6750f2a99 100644 Binary files a/fastlane/metadata/android/ja/images/phoneScreenshots/5.png and b/fastlane/metadata/android/ja/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/ko/images/phoneScreenshots/5.png b/fastlane/metadata/android/ko/images/phoneScreenshots/5.png index e8ab9c453..09ff885d5 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/nl/images/phoneScreenshots/5.png b/fastlane/metadata/android/nl/images/phoneScreenshots/5.png index 0b67697c2..d37238c12 100644 Binary files a/fastlane/metadata/android/nl/images/phoneScreenshots/5.png and b/fastlane/metadata/android/nl/images/phoneScreenshots/5.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 79190592c..8fdd9acfa 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/ru/images/phoneScreenshots/5.png b/fastlane/metadata/android/ru/images/phoneScreenshots/5.png index 94174ea57..4eb0476ff 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/tr/images/phoneScreenshots/5.png b/fastlane/metadata/android/tr/images/phoneScreenshots/5.png index 031655f8e..6601ee21a 100644 Binary files a/fastlane/metadata/android/tr/images/phoneScreenshots/5.png and b/fastlane/metadata/android/tr/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/5.png b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/5.png index 815a53272..ef45bfcfb 100644 Binary files a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/5.png and b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/5.png differ diff --git a/lib/geo/topojson.dart b/lib/geo/topojson.dart index a6a499c73..c61dc4b73 100644 --- a/lib/geo/topojson.dart +++ b/lib/geo/topojson.dart @@ -12,7 +12,7 @@ class TopoJson { static Topology? _isoParse(String jsonData) { try { - final data = json.decode(jsonData) as Map; + final data = jsonDecode(jsonData) as Map; return Topology.parse(data); } catch (error, stack) { // an unhandled error in a spawn isolate would make the app crash diff --git a/lib/image_providers/uri_image_provider.dart b/lib/image_providers/uri_image_provider.dart index e2850b0e2..1d1e3c578 100644 --- a/lib/image_providers/uri_image_provider.dart +++ b/lib/image_providers/uri_image_provider.dart @@ -9,7 +9,7 @@ import 'package:flutter/widgets.dart'; @immutable class UriImage extends ImageProvider with EquatableMixin { final String uri, mimeType; - final int? pageId, rotationDegrees, expectedContentLength; + final int? pageId, rotationDegrees, sizeBytes; final bool isFlipped; final double scale; @@ -22,7 +22,7 @@ class UriImage extends ImageProvider with EquatableMixin { required this.pageId, required this.rotationDegrees, required this.isFlipped, - this.expectedContentLength, + this.sizeBytes, this.scale = 1.0, }); @@ -55,7 +55,7 @@ class UriImage extends ImageProvider with EquatableMixin { rotationDegrees, isFlipped, pageId: pageId, - expectedContentLength: expectedContentLength, + sizeBytes: sizeBytes, onBytesReceived: (cumulative, total) { chunkEvents.add(ImageChunkEvent( cumulativeBytesLoaded: cumulative, diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index e965f4c6f..595392152 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -27,6 +27,7 @@ "actionRemove": "Entfernen", "resetTooltip": "Zurücksetzen", "saveTooltip": "Speichern", + "pickTooltip": "Wähle", "doubleBackExitMessage": "Zum Verlassen erneut auf „Zurück“ tippen.", "doNotAskAgain": "Nicht noch einmal fragen", @@ -87,18 +88,17 @@ "entryInfoActionEditDate": "Datum & Uhrzeit bearbeiten", "entryInfoActionEditLocation": "Standort bearbeiten", - "entryInfoActionEditDescription": "Beschreibung bearbeiten", "entryInfoActionEditRating": "Bewertung bearbeiten", "entryInfoActionEditTags": "Tags bearbeiten", "entryInfoActionRemoveMetadata": "Metadaten entfernen", "filterBinLabel": "Papierkorb", "filterFavouriteLabel": "Favorit", - "filterLocationEmptyLabel": "Ungeortet", - "filterTagEmptyLabel": "Unmarkiert", + "filterNoLocationLabel": "Ungeortet", + "filterNoRatingLabel": "Nicht bewertet", + "filterNoTagLabel": "Unmarkiert", "filterOnThisDayLabel": "Am heutigen Tag", "filterRecentlyAddedLabel": "Kürzlich hinzugefügt", - "filterRatingUnratedLabel": "Nicht bewertet", "filterRatingRejectedLabel": "Verworfen", "filterTypeAnimatedLabel": "Animationen", "filterTypeMotionPhotoLabel": "Bewegtes Foto", @@ -179,16 +179,11 @@ "storageVolumeDescriptionFallbackNonPrimary": "SD-Karte", "rootDirectoryDescription": "Hauptverzeichnis", "otherDirectoryDescription": "„{name}“ Verzeichnis", - "storageAccessDialogTitle": "Speicherzugriff", "storageAccessDialogMessage": "Bitte den {directory} von „{volume}“ auf dem nächsten Bildschirm auswählen, um dieser App Zugriff darauf zu geben.", - "restrictedAccessDialogTitle": "Eingeschränkter Zugang", "restrictedAccessDialogMessage": "Diese Anwendung darf keine Dateien im {directory} von „{volume}“ verändern.\n\nBitte einen vorinstallierten Dateimanager verwenden oder eine Galerie-App, um die Objekte in ein anderes Verzeichnis zu verschieben.", - "notEnoughSpaceDialogTitle": "Nicht genug Platz", "notEnoughSpaceDialogMessage": "Diese Operation benötigt {neededSize} freien Platz auf „{volume}“, um abgeschlossen zu werden, aber es ist nur noch {freeSize} übrig.", - "missingSystemFilePickerDialogTitle": "Fehlender System-Dateiauswahldialog", "missingSystemFilePickerDialogMessage": "Der System-Dateiauswahldialog fehlt oder ist deaktiviert. Bitte aktivieren und es erneut versuchen.", - "unsupportedTypeDialogTitle": "Nicht unterstützte Typen", "unsupportedTypeDialogMessage": "{count, plural, =1{Dieser Vorgang wird für Elemente des folgenden Typs nicht unterstützt: {types}.} other{Dieser Vorgang wird für Elemente der folgenden Typen nicht unterstützt: {types}.}}", "nameConflictDialogSingleSourceMessage": "Einige Dateien im Zielordner haben den gleichen Namen.", @@ -197,7 +192,6 @@ "addShortcutDialogLabel": "Shortcut-Etikett", "addShortcutButtonLabel": "Hinzufügen", - "noMatchingAppDialogTitle": "Keine passende App", "noMatchingAppDialogMessage": "Es gibt keine Anwendungen, die dies bewältigen können.", "binEntriesConfirmationDialogMessage": "{count, plural, =1{Dieses Element in den Papierkorb verschieben?} other{Diese {count} Elemente in den Papierkorb verschieben?}}", @@ -226,7 +220,7 @@ "renameEntrySetPageTitle": "Umbenennen", "renameEntrySetPagePatternFieldLabel": "Benennungsmuster", "renameEntrySetPageInsertTooltip": "Feld einfügen", - "renameEntrySetPagePreview": "Vorschau", + "renameEntrySetPagePreviewSectionTitle": "Vorschau", "renameProcessorCounter": "Zähler", "renameProcessorName": "Name", @@ -259,8 +253,6 @@ "locationPickerUseThisLocationButton": "Diesen Standort verwenden", - "editEntryDescriptionDialogTitle": "Beschreibung", - "editEntryRatingDialogTitle": "Bewertung", "removeEntryMetadataDialogTitle": "Entfernung von Metadaten", @@ -289,9 +281,9 @@ "menuActionSlideshow": "Diashow", "menuActionStats": "Statistiken", - "viewDialogTabSort": "Sortieren", - "viewDialogTabGroup": "Gruppe", - "viewDialogTabLayout": "Layout", + "viewDialogSortSectionTitle": "Sortieren", + "viewDialogGroupSectionTitle": "Gruppe", + "viewDialogLayoutSectionTitle": "Layout", "tileLayoutGrid": "Kacheln", "tileLayoutList": "Liste", @@ -308,24 +300,24 @@ "aboutLinkLicense": "Lizenz", "aboutLinkPolicy": "Datenschutzrichtlinie", - "aboutBug": "Fehlerbericht", + "aboutBugSectionTitle": "Fehlerbericht", "aboutBugSaveLogInstruction": "Anwendungsprotokolle in einer Datei speichern", "aboutBugCopyInfoInstruction": "Systeminformationen kopieren", "aboutBugCopyInfoButton": "Kopieren", "aboutBugReportInstruction": "Bericht auf GitHub mit den Protokollen und Systeminformationen", "aboutBugReportButton": "Bericht", - "aboutCredits": "Credits", + "aboutCreditsSectionTitle": "Credits", "aboutCreditsWorldAtlas1": "Diese Anwendung verwendet eine TopoJSON-Datei von", "aboutCreditsWorldAtlas2": "unter ISC-Lizenz.", - "aboutCreditsTranslators": "Übersetzer", + "aboutTranslatorsSectionTitle": "Übersetzer", - "aboutLicenses": "Open-Source-Lizenzen", + "aboutLicensesSectionTitle": "Open-Source-Lizenzen", "aboutLicensesBanner": "Diese Anwendung verwendet die folgenden Open-Source-Pakete und -Bibliotheken.", - "aboutLicensesAndroidLibraries": "Android-Bibliotheken", - "aboutLicensesFlutterPlugins": "Flutter-Plugins", - "aboutLicensesFlutterPackages": "Flatter-Pakete", - "aboutLicensesDartPackages": "Dart-Pakete", + "aboutLicensesAndroidLibrariesSectionTitle": "Android-Bibliotheken", + "aboutLicensesFlutterPluginsSectionTitle": "Flutter-Plugins", + "aboutLicensesFlutterPackagesSectionTitle": "Flatter-Pakete", + "aboutLicensesDartPackagesSectionTitle": "Dart-Pakete", "aboutLicensesShowAllButtonLabel": "Alle Lizenzen anzeigen", "policyPageTitle": "Datenschutzrichtlinie", @@ -345,11 +337,6 @@ "collectionSearchTitlesHintText": "Titel suchen", - "collectionSortDate": "Nach Datum", - "collectionSortSize": "Nach Größe", - "collectionSortName": "Nach Album & Dateiname", - "collectionSortRating": "Nach Bewertung", - "collectionGroupAlbum": "Nach Album", "collectionGroupMonth": "Nach Monat", "collectionGroupDay": "Nach Tag", @@ -378,6 +365,8 @@ "collectionSelectSectionTooltip": "Bereich auswählen", "collectionDeselectSectionTooltip": "Bereich abwählen", + "drawerAboutButton": "Über", + "drawerSettingsButton": "Einstellungen", "drawerCollectionAll": "Alle Bilder", "drawerCollectionFavourites": "Favoriten", "drawerCollectionImages": "Bilder", @@ -387,10 +376,16 @@ "drawerCollectionPanoramas": "Panoramen", "drawerCollectionRaws": "Rohdaten Fotos", "drawerCollectionSphericalVideos": "360°-Videos", + "drawerAlbumPage": "Alben", + "drawerCountryPage": "Länder", + "drawerTagPage": "Tags", - "chipSortDate": "Nach Datum", - "chipSortName": "Nach Name", - "chipSortCount": "Nach Anzahl", + "sortByDate": "Nach Datum", + "sortByName": "Nach Name", + "sortByItemCount": "Nach Anzahl", + "sortBySize": "Nach Größe", + "sortByAlbumFileName": "Nach Album & Dateiname", + "sortByRating": "Nach Bewertung", "albumGroupTier": "Nach Ebene", "albumGroupVolume": "Nach Speichervolumen", @@ -422,13 +417,13 @@ "binPageTitle": "Papierkorb", "searchCollectionFieldHint": "Sammlung durchsuchen", - "searchSectionRecent": "Neueste", - "searchSectionDate": "Datum", - "searchSectionAlbums": "Alben", - "searchSectionCountries": "Länder", - "searchSectionPlaces": "Orte", - "searchSectionTags": "Tags", - "searchSectionRating": "Bewertungen", + "searchRecentSectionTitle": "Neueste", + "searchDateSectionTitle": "Datum", + "searchAlbumsSectionTitle": "Alben", + "searchCountriesSectionTitle": "Länder", + "searchPlacesSectionTitle": "Orte", + "searchTagsSectionTitle": "Tags", + "searchRatingSectionTitle": "Bewertungen", "settingsPageTitle": "Einstellungen", "settingsSystemDefault": "System", @@ -437,37 +432,40 @@ "settingsSearchFieldLabel": "Einstellungen durchsuchen", "settingsSearchEmpty": "Keine passende Einstellung", "settingsActionExport": "Exportieren", + "settingsActionExportDialogTitle": "Exportieren", "settingsActionImport": "Importieren", + "settingsActionImportDialogTitle": "Importieren", "appExportCovers": "Titelbilder", "appExportFavourites": "Favoriten", "appExportSettings": "Einstellungen", - "settingsSectionNavigation": "Navigation", - "settingsHome": "Startseite", + "settingsNavigationSectionTitle": "Navigation", + "settingsHomeTile": "Startseite", + "settingsHomeDialogTitle": "Startseite", "settingsShowBottomNavigationBar": "Untere Navigationsleiste anzeigen", "settingsKeepScreenOnTile": "Bildschirm eingeschaltet lassen", - "settingsKeepScreenOnTitle": "Bildschirm eingeschaltet lassen", + "settingsKeepScreenOnDialogTitle": "Bildschirm eingeschaltet lassen", "settingsDoubleBackExit": "Zum Verlassen zweimal „zurück“ tippen", - "settingsConfirmationDialogTile": "Bestätigungsdialoge", + "settingsConfirmationTile": "Bestätigungsdialoge", "settingsConfirmationDialogTitle": "Bestätigungsdialoge", - "settingsConfirmationDialogDeleteItems": "Vor dem endgültigen Löschen von Elementen fragen", - "settingsConfirmationDialogMoveToBinItems": "Vor dem Verschieben von Elementen in den Papierkorb fragen", - "settingsConfirmationDialogMoveUndatedItems": "Vor Verschiebung von Objekten ohne Metadaten-Datum fragen", + "settingsConfirmationBeforeDeleteItems": "Vor dem endgültigen Löschen von Elementen fragen", + "settingsConfirmationBeforeMoveToBinItems": "Vor dem Verschieben von Elementen in den Papierkorb fragen", + "settingsConfirmationBeforeMoveUndatedItems": "Vor Verschiebung von Objekten ohne Metadaten-Datum fragen", "settingsConfirmationAfterMoveToBinItems": "Nachricht nach dem Verschieben von Elementen in den Papierkorb anzeigen", "settingsNavigationDrawerTile": "Menü Navigation", - "settingsNavigationDrawerEditorTitle": "Menü Navigation", + "settingsNavigationDrawerEditorPageTitle": "Menü Navigation", "settingsNavigationDrawerBanner": "Die Taste berühren und halten, um Menüpunkte zu verschieben und neu anzuordnen.", "settingsNavigationDrawerTabTypes": "Typen", "settingsNavigationDrawerTabAlbums": "Alben", "settingsNavigationDrawerTabPages": "Seiten", "settingsNavigationDrawerAddAlbum": "Album hinzufügen", - "settingsSectionThumbnails": "Vorschaubilder", + "settingsThumbnailSectionTitle": "Vorschaubilder", "settingsThumbnailOverlayTile": "Überlagerung", - "settingsThumbnailOverlayTitle": "Überlagerung", + "settingsThumbnailOverlayPageTitle": "Überlagerung", "settingsThumbnailShowFavouriteIcon": "Favoriten-Symbol anzeigen", "settingsThumbnailShowTagIcon": "Tag-Symbol anzeigen", "settingsThumbnailShowLocationIcon": "Standort-Symbol anzeigen", @@ -477,13 +475,13 @@ "settingsThumbnailShowVideoDuration": "Videodauer anzeigen", "settingsCollectionQuickActionsTile": "Schnelle Aktionen", - "settingsCollectionQuickActionEditorTitle": "Schnelle Aktionen", + "settingsCollectionQuickActionEditorPageTitle": "Schnelle Aktionen", "settingsCollectionQuickActionTabBrowsing": "Durchsuchen", "settingsCollectionQuickActionTabSelecting": "Auswahl", "settingsCollectionBrowsingQuickActionEditorBanner": "Die Taste gedrückt halten, um die Schaltflächen zu bewegen und auszuwählen, welche Aktionen beim Durchsuchen von Elementen angezeigt werden.", "settingsCollectionSelectionQuickActionEditorBanner": "Die Taste gedrückt halten, um die Schaltflächen zu bewegen und auszuwählen, welche Aktionen beim Durchsuchen von Elementen angezeigt werden.", - "settingsSectionViewer": "Anzeige", + "settingsViewerSectionTitle": "Anzeige", "settingsViewerGestureSideTapNext": "Tippen auf den Bildschirmrand, um das vorheriges/nächstes Element anzuzeigen", "settingsViewerUseCutout": "Ausgeschnittenen Bereich verwenden", "settingsViewerMaximumBrightness": "Maximale Helligkeit", @@ -491,14 +489,14 @@ "settingsImageBackground": "Bild-Hintergrund", "settingsViewerQuickActionsTile": "Schnelle Aktionen", - "settingsViewerQuickActionEditorTitle": "Schnelle Aktionen", + "settingsViewerQuickActionEditorPageTitle": "Schnelle Aktionen", "settingsViewerQuickActionEditorBanner": "Die Taste gedrückt halten, um die Schaltflächen zu bewegen und auszuwählen, welche Aktionen im Viewer angezeigt werden sollen.", - "settingsViewerQuickActionEditorDisplayedButtons": "Angezeigte Schaltflächen", - "settingsViewerQuickActionEditorAvailableButtons": "Verfügbare Schaltflächen", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Angezeigte Schaltflächen", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Verfügbare Schaltflächen", "settingsViewerQuickActionEmpty": "Keine Tasten", "settingsViewerOverlayTile": "Überlagerung", - "settingsViewerOverlayTitle": "Überlagerung", + "settingsViewerOverlayPageTitle": "Überlagerung", "settingsViewerShowOverlayOnOpening": "Bei Eröffnung anzeigen", "settingsViewerShowMinimap": "Minimap anzeigen", "settingsViewerShowInformation": "Informationen anzeigen", @@ -508,30 +506,30 @@ "settingsViewerEnableOverlayBlurEffect": "Unschärfe-Effekt", "settingsViewerSlideshowTile": "Diashow", - "settingsViewerSlideshowTitle": "Diashow", + "settingsViewerSlideshowPageTitle": "Diashow", "settingsSlideshowRepeat": "Wiederholung", "settingsSlideshowShuffle": "Mischen", "settingsSlideshowFillScreen": "Bildschirm ausfüllen", "settingsSlideshowTransitionTile": "Übergang", - "settingsSlideshowTransitionTitle": "Übergang", + "settingsSlideshowTransitionDialogTitle": "Übergang", "settingsSlideshowIntervalTile": "Intervall", - "settingsSlideshowIntervalTitle": "Intervall", + "settingsSlideshowIntervalDialogTitle": "Intervall", "settingsSlideshowVideoPlaybackTile": "Videowiedergabe", - "settingsSlideshowVideoPlaybackTitle": "Videowiedergabe", + "settingsSlideshowVideoPlaybackDialogTitle": "Videowiedergabe", "settingsVideoPageTitle": "Video-Einstellungen", - "settingsSectionVideo": "Video", + "settingsVideoSectionTitle": "Video", "settingsVideoShowVideos": "Videos anzeigen", "settingsVideoEnableHardwareAcceleration": "Hardware-Beschleunigung", "settingsVideoEnableAutoPlay": "Automatische Wiedergabe", "settingsVideoLoopModeTile": "Schleifen-Modus", - "settingsVideoLoopModeTitle": "Schleifen-Modus", + "settingsVideoLoopModeDialogTitle": "Schleifen-Modus", "settingsSubtitleThemeTile": "Untertitel", - "settingsSubtitleThemeTitle": "Untertitel", + "settingsSubtitleThemePageTitle": "Untertitel", "settingsSubtitleThemeSample": "Dies ist ein Beispiel.", "settingsSubtitleThemeTextAlignmentTile": "Textausrichtung", - "settingsSubtitleThemeTextAlignmentTitle": "Textausrichtung", + "settingsSubtitleThemeTextAlignmentDialogTitle": "Textausrichtung", "settingsSubtitleThemeTextSize": "Textgröße", "settingsSubtitleThemeShowOutline": "Umriss und Schatten anzeigen", "settingsSubtitleThemeTextColor": "Textfarbe", @@ -543,13 +541,13 @@ "settingsSubtitleThemeTextAlignmentRight": "Rechts", "settingsVideoControlsTile": "Steuerung", - "settingsVideoControlsTitle": "Steuerung", + "settingsVideoControlsPageTitle": "Steuerung", "settingsVideoButtonsTile": "Schaltflächen", - "settingsVideoButtonsTitle": "Schaltflächen", + "settingsVideoButtonsDialogTitle": "Schaltflächen", "settingsVideoGestureDoubleTapTogglePlay": "Doppeltippen zum Abspielen/Pausieren", "settingsVideoGestureSideDoubleTapSeek": "Doppeltippen auf die Bildschirmränder zum Rückwärts-/Vorwärtsspringen", - "settingsSectionPrivacy": "Datenschutz", + "settingsPrivacySectionTitle": "Datenschutz", "settingsAllowInstalledAppAccess": "Zugriff auf die Liste der installierten Apps", "settingsAllowInstalledAppAccessSubtitle": "zur Gruppierung von Bildern nach Apps", "settingsAllowErrorReporting": "Anonyme Fehlermeldungen zulassen", @@ -558,52 +556,56 @@ "settingsEnableBinSubtitle": "Gelöschte Elemente 30 Tage lang aufbewahren", "settingsHiddenItemsTile": "Versteckte Elemente", - "settingsHiddenItemsTitle": "Versteckte Elemente", + "settingsHiddenItemsPageTitle": "Versteckte Elemente", - "settingsHiddenFiltersTitle": "Versteckte Filter", + "settingsHiddenItemsTabFilters": "Versteckte Filter", "settingsHiddenFiltersBanner": "Fotos und Videos, die versteckten Filtern entsprechen, werden nicht in Ihrer Sammlung angezeigt.", "settingsHiddenFiltersEmpty": "Keine versteckten Filter", - "settingsHiddenPathsTitle": "Verborgene Pfade", + "settingsHiddenItemsTabPaths": "Verborgene Pfade", "settingsHiddenPathsBanner": "Fotos und Videos, die sich in diesen Ordnern oder in einem ihrer Unterordner befinden, werden nicht in Ihrer Sammlung angezeigt.", "addPathTooltip": "Pfad hinzufügen", "settingsStorageAccessTile": "Speicherzugriff", - "settingsStorageAccessTitle": "Speicherzugriff", + "settingsStorageAccessPageTitle": "Speicherzugriff", "settingsStorageAccessBanner": "Einige Verzeichnisse erfordern eine explizite Zugriffsberechtigung, um Dateien darin zu ändern. Hier können Verzeichnisse überprüft werden, auf die zuvor Zugriff gewährt wurde.", "settingsStorageAccessEmpty": "Keine Zugangsberechtigungen", "settingsStorageAccessRevokeTooltip": "Widerrufen", - "settingsSectionAccessibility": "Barrierefreiheit", + "settingsAccessibilitySectionTitle": "Barrierefreiheit", "settingsRemoveAnimationsTile": "Animationen entfernen", - "settingsRemoveAnimationsTitle": "Animationen entfernen", + "settingsRemoveAnimationsDialogTitle": "Animationen entfernen", "settingsTimeToTakeActionTile": "Zeit zum Reagieren", - "settingsTimeToTakeActionTitle": "Zeit zum Reagieren", + "settingsTimeToTakeActionDialogTitle": "Zeit zum Reagieren", - "settingsSectionDisplay": "Anzeige", - "settingsThemeBrightness": "Thema", + "settingsDisplaySectionTitle": "Anzeige", + "settingsThemeBrightnessTile": "Thema", + "settingsThemeBrightnessDialogTitle": "Thema", "settingsThemeColorHighlights": "Farbige Highlights", "settingsThemeEnableDynamicColor": "Dynamische Farben", "settingsDisplayRefreshRateModeTile": "Bildwiederholrate der Anzeige", - "settingsDisplayRefreshRateModeTitle": "Bildwiederholrate", + "settingsDisplayRefreshRateModeDialogTitle": "Bildwiederholrate", - "settingsSectionLanguage": "Sprache & Formate", - "settingsLanguage": "Sprache", + "settingsLanguageSectionTitle": "Sprache & Formate", + "settingsLanguageTile": "Sprache", + "settingsLanguagePageTitle": "Sprache", "settingsCoordinateFormatTile": "Koordinatenformat", - "settingsCoordinateFormatTitle": "Koordinatenformat", + "settingsCoordinateFormatDialogTitle": "Koordinatenformat", "settingsUnitSystemTile": "Einheiten", - "settingsUnitSystemTitle": "Einheiten", + "settingsUnitSystemDialogTitle": "Einheiten", "settingsScreenSaverPageTitle": "Bildschirmschoner", "settingsWidgetPageTitle": "Bilderrahmen", "settingsWidgetShowOutline": "Gliederung", + "settingsCollectionTile": "Sammlung", + "statsPageTitle": "Statistiken", "statsWithGps": "{count, plural, =1{1 Element mit Standort} other{{count} Elemente mit Standort}}", - "statsTopCountries": "Top-Länder", - "statsTopPlaces": "Top-Plätze", - "statsTopTags": "Top-Tags", + "statsTopCountriesSectionTitle": "Top-Länder", + "statsTopPlacesSectionTitle": "Top-Plätze", + "statsTopTagsSectionTitle": "Top-Tags", "viewerOpenPanoramaButtonLabel": "ÖFFNE PANORAMA", "viewerSetWallpaperButtonLabel": "HINTERGRUNDBILD EINSTELLEN", @@ -614,6 +616,7 @@ "viewerInfoBackToViewerTooltip": "Zurück zum Betrachter", "viewerInfoUnknown": "Unbekannt", + "viewerInfoLabelDescription": "Beschreibung", "viewerInfoLabelTitle": "Titel", "viewerInfoLabelDate": "Datum", "viewerInfoLabelResolution": "Auflösung", @@ -625,7 +628,7 @@ "viewerInfoLabelCoordinates": "Koordinaten", "viewerInfoLabelAddress": "Adresse", - "mapStyleTitle": "Kartenstil", + "mapStyleDialogTitle": "Kartenstil", "mapStyleTooltip": "Kartenstil auswählen", "mapZoomInTooltip": "Vergrößern", "mapZoomOutTooltip": "Verkleinern", diff --git a/lib/l10n/app_el.arb b/lib/l10n/app_el.arb new file mode 100644 index 000000000..f00605985 --- /dev/null +++ b/lib/l10n/app_el.arb @@ -0,0 +1,682 @@ +{ + "appName": "Aves", + "welcomeMessage": "Καλωσορίσατε στην εφαρμογή Aves", + "welcomeOptional": "Προαιρετικό", + "welcomeTermsToggle": "Συμφωνώ με τους όρους και τις προυποθέσεις", + "itemCount": "{count, plural, =1{1 στοιχείο} other{{count} στοιχεία}}", + + "timeSeconds": "{seconds, plural, =1{1 δευτερόλεπτο} other{{seconds} δευτερόλεπτα}}", + "timeMinutes": "{minutes, plural, =1{1 λεπτό} other{{minutes} λεπτά}}", + "timeDays": "{days, plural, =1{1 ημέρα} other{{days} ημέρες}}", + "focalLength": "{length} mm", + + "applyButtonLabel": "ΕΦΑΡΜΟΓΗ", + "deleteButtonLabel": "ΔΙΑΓΡΑΦΗ", + "nextButtonLabel": "ΕΠΟΜΕΝΟ", + "showButtonLabel": "ΕΜΦΑΝΙΣΗ", + "hideButtonLabel": "ΑΠΟΚΡΥΨΗ", + "continueButtonLabel": "ΣΥΝΕΧΕΙΑ", + + "cancelTooltip": "Ακύρωση", + "changeTooltip": "Αλλαγή", + "clearTooltip": "Εκκαθάριση", + "previousTooltip": "Προηγούμενο", + "nextTooltip": "Επόμενο", + "showTooltip": "Εμφάνιση", + "hideTooltip": "Απόκρυψη", + "actionRemove": "Αφαίρεση", + "resetTooltip": "Επαναφορά", + "saveTooltip": "Αποθήκευση", + "pickTooltip": "Διαλέξτε", + + "doubleBackExitMessage": "Αγγίξτε ξανά το «πίσω» για έξοδο.", + "doNotAskAgain": "Να μην ερωτηθώ ξανά", + + "sourceStateLoading": "Φόρτωση", + "sourceStateCataloguing": "Καταλογογράφηση", + "sourceStateLocatingCountries": "Εντοπισμός χωρών", + "sourceStateLocatingPlaces": "Εντοπισμός τοποθεσιών", + + "chipActionDelete": "Διαγραφή", + "chipActionGoToAlbumPage": "Εμφάνιση στα Άλμπουμ", + "chipActionGoToCountryPage": "Εμφάνιση στις χώρες", + "chipActionGoToTagPage": "Εμφάνιση στις ετικέτες", + "chipActionHide": "Απόκρυψη", + "chipActionPin": "Καρφίτσωμα στην κορυφή", + "chipActionUnpin": "Ξέκαρφίτσωμα από την κορυφή", + "chipActionRename": "Μετονομασία", + "chipActionSetCover": "Ορισμός ως εξώφυλλο", + "chipActionCreateAlbum": "Δημιουργία άλμπουμ", + + "entryActionCopyToClipboard": "Αντιγραφή στο πρόχειρο", + "entryActionDelete": "Διαγραφή", + "entryActionConvert": "Μετατροπή", + "entryActionExport": "Εξαγωγή", + "entryActionInfo": "Λεπτομέρειες", + "entryActionRename": "Μετονομασία", + "entryActionRestore": "Επαναφορά", + "entryActionRotateCCW": "Περιστροφή προς τα αριστερά", + "entryActionRotateCW": "Περιστροφή προς τα δεξιά", + "entryActionFlip": "Αντανάκλαση καθρέφτη", + "entryActionPrint": "Εκτύπωση", + "entryActionShare": "Κοινοποίηση", + "entryActionViewSource": "Προβολή πηγής", + "entryActionShowGeoTiffOnMap": "Εμφάνιση ως επικάλυψη στον χάρτη", + "entryActionConvertMotionPhotoToStillImage": "Μετατροπή σε στατική εικόνα", + "entryActionViewMotionPhotoVideo": "Άνοιγμα του βίντεο", + "entryActionEdit": "Επεξεργασία", + "entryActionOpen": "Άνοιγμα με", + "entryActionSetAs": "Ορισμός ως", + "entryActionOpenMap": "Εμφάνιση στην εφαρμογή χάρτες", + "entryActionRotateScreen": "Περιστροφή οθόνης", + "entryActionAddFavourite": "Προσθήκη στα αγαπημένα", + "entryActionRemoveFavourite": "Αφαίρεση από τα αγαπημένα", + + "videoActionCaptureFrame": "Λήψη στιγμιότυπου", + "videoActionMute": "Σίγαση", + "videoActionUnmute": "Αναίρεση σίγασης", + "videoActionPause": "Παύση", + "videoActionPlay": "Αναπαραγωγή", + "videoActionReplay10": "10 δευτερόλεπτα πίσω ", + "videoActionSkip10": "10 δευτερόλεπτα μπροστά", + "videoActionSelectStreams": "Επιλογή γλώσσας", + "videoActionSetSpeed": "Ταχύτητα αναπαραγωγής", + "videoActionSettings": "Ρυθμίσεις", + + "slideshowActionResume": "Συνέχιση", + "slideshowActionShowInCollection": "Εμφάνιση στη Συλλογή", + + "entryInfoActionEditDate": "Επεξεργασία ημερομηνίας και ώρας", + "entryInfoActionEditLocation": "Επεξεργασία τοποθεσίας", + "entryInfoActionEditTitleDescription": "Επεξεργασία ονομασίας & περιγραφής", + "entryInfoActionEditRating": "Επεξεργασία βαθμολογίας", + "entryInfoActionEditTags": "Επεξεργασία ετικετών", + "entryInfoActionRemoveMetadata": "Κατάργηση μεταδεδομένων", + + "filterBinLabel": "Κάδος ανακύκλωσης", + "filterFavouriteLabel": "Αγαπημένα", + "filterNoDateLabel": "Χωρίς ημερομηνία", + "filterNoLocationLabel": "Χωρίς τοποθεσία", + "filterNoRatingLabel": "Χωρίς βαθμολογία", + "filterNoTagLabel": "Χωρίς ετικέτα", + "filterNoTitleLabel": "Χωρίς ονομασία", + "filterOnThisDayLabel": "Αυτή τη μέρα", + "filterRecentlyAddedLabel": "Προστέθηκαν πρόσφατα", + "filterRatingRejectedLabel": "Απορριφθέντα", + "filterTypeAnimatedLabel": "Κινούμενα", + "filterTypeMotionPhotoLabel": "Φωτογραφίες με κίνηση", + "filterTypePanoramaLabel": "Πανοραμικές", + "filterTypeRawLabel": "Raw", + "filterTypeSphericalVideoLabel": "Βίντεο 360°", + "filterTypeGeotiffLabel": "GeoTIFF", + "filterMimeImageLabel": "Εικόνα", + "filterMimeVideoLabel": "Βίντεο", + + "coordinateFormatDms": "DMS", + "coordinateFormatDecimal": "Δεκαδικές μοίρες", + "coordinateDms": "{coordinate} {direction}", + "coordinateDmsNorth": "Β", + "coordinateDmsSouth": "Ν", + "coordinateDmsEast": "Α", + "coordinateDmsWest": "Δ", + + "unitSystemMetric": "Μετρικό", + "unitSystemImperial": "Aγγλοσαξονικό", + + "videoLoopModeNever": "Ποτέ", + "videoLoopModeShortOnly": "Μόνο για βίντεο σύντομης διάρκειας", + "videoLoopModeAlways": "Πάντα", + + "videoControlsPlay": "Αναπαραγωγή", + "videoControlsPlaySeek": "Αναπαραγωγή & πίσω/μπροστά", + "videoControlsPlayOutside": "Άνοιγμα με άλλη εφαρμογή", + "videoControlsNone": "Καμία επιλογή", + + "mapStyleGoogleNormal": "Google Maps", + "mapStyleGoogleHybrid": "Google Maps (Hybrid)", + "mapStyleGoogleTerrain": "Google Maps (Terrain)", + "mapStyleHuaweiNormal": "Petal Maps", + "mapStyleHuaweiTerrain": "Petal Maps (Terrain)", + "mapStyleOsmHot": "Humanitarian OSM", + "mapStyleStamenToner": "Stamen Toner", + "mapStyleStamenWatercolor": "Stamen Watercolor", + + "nameConflictStrategyRename": "Μετονομασία", + "nameConflictStrategyReplace": "Αντικατάσταση", + "nameConflictStrategySkip": "Παράλειψη", + + "keepScreenOnNever": "Ποτέ", + "keepScreenOnViewerOnly": "Μόνο όταν πραγματοποιείται προβολή στοιχείων", + "keepScreenOnAlways": "Πάντα", + + "accessibilityAnimationsRemove": "Απενεργοποίηση εφέ οθόνης", + "accessibilityAnimationsKeep": "Διατήρηση εφέ οθόνης", + + "displayRefreshRatePreferHighest": "Υψηλότερος ρυθμός", + "displayRefreshRatePreferLowest": "Χαμηλότερος ρυθμός", + + "slideshowVideoPlaybackSkip": "Παράλειψη", + "slideshowVideoPlaybackMuted": "Αναπαραγωγή σε σίγαση", + "slideshowVideoPlaybackWithSound": "Αναπαραγωγή με ήχο", + + "themeBrightnessLight": "Φωτεινό", + "themeBrightnessDark": "Σκούρο", + "themeBrightnessBlack": "Μαύρο", + + "viewerTransitionSlide": "Ολίσθηση", + "viewerTransitionParallax": "Παράλλαξη", + "viewerTransitionFade": "Ξεθώριασμα", + "viewerTransitionZoomIn": "Μεγέθυνση", + + "wallpaperTargetHome": "Αρχική οθόνη", + "wallpaperTargetLock": "Οθόνη κλειδώματος", + "wallpaperTargetHomeLock": "Αρχική οθόνη και οθόνη κλειδώματος", + + "albumTierNew": "Νέα", + "albumTierPinned": "Καρφιτσωμένα", + "albumTierSpecial": "Συστήματος", + "albumTierApps": "Εφαρμογές", + "albumTierRegular": "Προσωπικά", + + "storageVolumeDescriptionFallbackPrimary": "Εσωτερικός χώρος αποθήκευσης", + "storageVolumeDescriptionFallbackNonPrimary": "SD card/Εξωτερικός χώρος αποθήκευσης", + "rootDirectoryDescription": "Κατάλογος ρίζας", + "otherDirectoryDescription": "«{name}» κατάλογος", + "storageAccessDialogMessage": "Παρακαλώ επιλέξτε τον {directory} του «{volume}» στην επόμενη οθόνη για να δώσετε πρόσβαση στην εφαρμογή", + "restrictedAccessDialogMessage": "Αυτή η εφαρμογή δεν επιτρέπεται να τροποποιεί αρχεία στο {directory} του «{volume}».\n\nΠαρακαλώ χρησιμοποιήστε μια άλλη εφαρμογή διαχείρισης αρχείων ή συλλογή φωτογραφιών για να μετακινήσετε τα στοιχεία αλλού.", + "notEnoughSpaceDialogMessage": "Αυτή η λειτουργία χρειάζεται {neededSize} ελεύθερου χώρου στο «{volume}» για να ολοκληρωθεί, αλλά μόνο {freeSize} απομένουν ελεύθερα.", + "missingSystemFilePickerDialogMessage": "Η εφαρμογή επιλογής αρχείων του δεν βρέθηκε ή είναι απενεργοποιημένη. Ενεργοποιήστε την και δοκιμάστε ξανά.", + + "unsupportedTypeDialogMessage": "{count, plural, =1{Αυτή η λειτουργία δεν υποστηρίζεται για το ακόλουθο αρχείο: {types}.} other{Αυτή η λειτουργία δεν υποστηρίζεται για τα ακόλουθα αρχεία: {types}.}}", + + "nameConflictDialogSingleSourceMessage": "Ορισμένα αρχεία στον φάκελο προορισμού έχουν το ίδιο όνομα.", + "nameConflictDialogMultipleSourceMessage": "Ορισμένα αρχεία έχουν το ίδιο όνομα.", + + "addShortcutDialogLabel": "Ετικέτα συντόμευσης", + "addShortcutButtonLabel": "ΠΡΟΣΘΗΚΗ", + + "noMatchingAppDialogMessage": "Δεν βρέθηκαν εγκατεστημένες εφαρμογές που μπορούν να το κάνουν αυτό.", + + "binEntriesConfirmationDialogMessage": "{count, plural, =1{Μετακίνηση αυτού του στοιχείου στον κάδο ανακύκλωσης;} other{Μετακίνηση αυτών των {count} στοιχείων στον κάδο ανακύκλωσης;}}", + "deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Διαγραφή αυτού του στοιχείου;} other{Διαγραφή αυτών των {count} στοιχείων;}}", + "moveUndatedConfirmationDialogMessage": "Αποθήκευση ημερομηνιών στοιχείων πριν συνεχίσετε;", + "moveUndatedConfirmationDialogSetDate": "Αποθήκευση ημερομηνιών", + + "videoResumeDialogMessage": "Συνέχεια αναπαραγωγής από {time}?", + "videoStartOverButtonLabel": "ΑΝΑΠΑΡΑΓΩΓΗ ΑΠΟ ΤΗΝ ΑΡΧΗ", + "videoResumeButtonLabel": "ΣΥΝΕΧΙΣΗ ΑΝΑΠΑΡΑΓΩΓΗΣ", + + "setCoverDialogLatest": "Πιο πρόσφατο στοιχείο", + "setCoverDialogAuto": "Αυτόματο", + "setCoverDialogCustom": "Προσαρμοσμένο", + + "hideFilterConfirmationDialogMessage": "Οι φωτογραφίες και τα βίντεο που ταιριάζουν θα κρυφτούν από τη συλλογή σας. Μπορείτε να τα εμφανίσετε ξανά από τις ρυθμίσεις «Απόρρητο».\n\nΕίστε σίγουροι ότι θέλετε να τα κρύψετε;", + + "newAlbumDialogTitle": "Νεο αλμπουμ", + "newAlbumDialogNameLabel": "Όνομα άλμπουμ", + "newAlbumDialogNameLabelAlreadyExistsHelper": "Ο κατάλογος υπάρχει ήδη", + "newAlbumDialogStorageLabel": "Aποθηκευτικός χώρος:", + + "renameAlbumDialogLabel": "Νέο όνομα", + "renameAlbumDialogLabelAlreadyExistsHelper": "Ο κατάλογος υπάρχει ήδη", + + "renameEntrySetPageTitle": "Μετονομασια", + "renameEntrySetPagePatternFieldLabel": "Μοτίβο ονομασίας", + "renameEntrySetPageInsertTooltip": "Εισαγωγή πεδίου", + "renameEntrySetPagePreviewSectionTitle": "Προεπισκοπηση", + + "renameProcessorCounter": "Counter", + "renameProcessorName": "Όνομα", + + "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Διαγραφή αυτού του άλμπουμ και του περιεχομένου του;} other{Διαγράψτε αυτό το άλμπουμ και όλα τα {count} στοιχεία του;}}", + "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Διαγράψτε αυτά τα άλμπουμ και το περιεχόμενό τους;} other{Διαγράψτε αυτά τα άλμπουμ και όλα τα {count} στοιχεία τους;}}", + + "exportEntryDialogFormat": "Μορφή:", + "exportEntryDialogWidth": "Πλάτος", + "exportEntryDialogHeight": "Ύψος", + + "renameEntryDialogLabel": "Νέο όνομα", + + "editEntryDialogTargetFieldsHeader": "Πεδία προς τροποποίηση", + + "editEntryDateDialogTitle": "Ημερομηνια & Ώρα", + "editEntryDateDialogSetCustom": "Ορισμός προσαρμοσμένης ημερομηνίας", + "editEntryDateDialogCopyField": "Αντιγραφή από άλλη ημερομηνία", + "editEntryDateDialogCopyItem": "Αντιγραφή από άλλο στοιχείο", + "editEntryDateDialogExtractFromTitle": "Εξαγωγή από το όνομα του κάθε αρχείου", + "editEntryDateDialogShift": "Μετατόπιση", + "editEntryDateDialogSourceFileModifiedDate": "Ημερομηνία τροποποίησης αρχείου", + "editEntryDateDialogHours": "Ώρες", + "editEntryDateDialogMinutes": "Λεπτά", + + "editEntryLocationDialogTitle": "Τοποθεσια", + "editEntryLocationDialogChooseOnMapTooltip": "Επιλογή στο χάρτη", + "editEntryLocationDialogLatitude": "Γεωγραφικό πλάτος", + "editEntryLocationDialogLongitude": "Γεωγραφικό μήκος", + + "locationPickerUseThisLocationButton": "Χρησιμοποίηση της τοποθεσίας", + + "editEntryRatingDialogTitle": "Βαθμολογια", + + "removeEntryMetadataDialogTitle": "Αφαιρεση μεταδεδομενων", + "removeEntryMetadataDialogMore": "Περισσότερα", + + "removeEntryMetadataMotionPhotoXmpWarningDialogMessage": "Απαιτείται XMP για την αναπαραγωγή του βίντεο μέσα σε μια κινούμενη φωτογραφία.\n\nΕίστε βέβαιοι ότι θέλετε να αφαιρεθεί;", + "convertMotionPhotoToStillImageWarningDialogMessage": "Είστε βέβαιοι;", + + "videoSpeedDialogLabel": "Ταχύτητα αναπαραγωγής", + + "videoStreamSelectionDialogVideo": "Βίντεο", + "videoStreamSelectionDialogAudio": "Ήχος", + "videoStreamSelectionDialogText": "Υπότιτλοι", + "videoStreamSelectionDialogOff": "Απενεργοποίηση", + "videoStreamSelectionDialogTrack": "Κομμάτι", + "videoStreamSelectionDialogNoSelection": "Δεν υπάρχουν άλλα κομμάτια.", + + "genericSuccessFeedback": "Ολοκληρώθηκε!", + "genericFailureFeedback": "Απέτυχε", + + "menuActionConfigureView": "Προβολή", + "menuActionSelect": "Επιλογή", + "menuActionSelectAll": "Επιλογή όλων", + "menuActionSelectNone": "Να μην επιλεγεί κανένα", + "menuActionMap": "Χάρτης", + "menuActionSlideshow": "Παρουσίαση φωτογραφιών", + "menuActionStats": "Στατιστικά", + + "viewDialogSortSectionTitle": "Ταξινομηση", + "viewDialogGroupSectionTitle": "Ομαδοποιηση", + "viewDialogLayoutSectionTitle": "Διαταξη", + "viewDialogReverseSortOrder": "Αντίστροφη σειρά ταξινόμησης", + + "tileLayoutGrid": "Πλέγμα", + "tileLayoutList": "Λίστα", + + "coverDialogTabCover": "Εξώφυλλο", + "coverDialogTabApp": "Εφαρμογή", + "coverDialogTabColor": "Χρώμα", + + "appPickDialogTitle": "Επιλογη εφαρμογης", + "appPickDialogNone": "Καμία επιλογή", + + "aboutPageTitle": "Διαφορα", + "aboutLinkSources": "Πηγές", + "aboutLinkLicense": "Άδειες", + "aboutLinkPolicy": "Πολιτική Απορρήτου", + + "aboutBugSectionTitle": "Αναφορα σφαλματος", + "aboutBugSaveLogInstruction": "Αποθήκευση των αρχείων καταγραφής της εφαρμογής σε ένα ξεχωριστό αρχείο", + "aboutBugCopyInfoInstruction": "Αντιγραφή πληροφοριών του συστήματος", + "aboutBugCopyInfoButton": "Αντιγραφή", + "aboutBugReportInstruction": "Αναφορά στο GitHub με τα αρχεία καταγραφής και τις πληροφορίες του συστήματος", + "aboutBugReportButton": "Αναφορά", + + "aboutCreditsSectionTitle": "Αναφορες", + "aboutCreditsWorldAtlas1": "Αυτή η εφαρμογή χρησιμοποιεί ένα αρχείο TopoJSON από", + "aboutCreditsWorldAtlas2": "υπό την άδεια ISC.", + "aboutTranslatorsSectionTitle": "Μεταφραστες", + + "aboutLicensesSectionTitle": "Αδειες ανοιχτου κωδικα", + "aboutLicensesBanner": "Αυτή η εφαρμογή χρησιμοποιεί τα ακόλουθα πακέτα και βιβλιοθήκες ανοιχτού κώδικα.", + "aboutLicensesAndroidLibrariesSectionTitle": "Βιβλιοθηκες Android", + "aboutLicensesFlutterPluginsSectionTitle": "Προσθετα Flutter", + "aboutLicensesFlutterPackagesSectionTitle": "Πακετα Flutter", + "aboutLicensesDartPackagesSectionTitle": "Πακετα Dart", + "aboutLicensesShowAllButtonLabel": "Εμφάνιση όλων των αδειών", + + "policyPageTitle": "Πολιτικη απορρητου", + + "collectionPageTitle": "Συλλογη", + "collectionPickPageTitle": "Διαλεξτε", + "collectionSelectPageTitle": "Επιλογη στοιχειων", + + "collectionActionShowTitleSearch": "Εμφάνιση φίλτρου τίτλου", + "collectionActionHideTitleSearch": "Απόκρυψη φίλτρου τίτλου", + "collectionActionAddShortcut": "Προσθήκη συντόμευσης", + "collectionActionEmptyBin": "Καθαρισμός του κάδου ανακύκλωσης", + "collectionActionCopy": "Αντιγραφή στο άλμπουμ", + "collectionActionMove": "Μετακίνηση στο άλμπουμ", + "collectionActionRescan": "Εκ νέου σάρωση", + "collectionActionEdit": "Επεξεργασία", + + "collectionSearchTitlesHintText": "Αναζήτηση τίτλων", + + "collectionGroupAlbum": "Ανά άλμπουμ", + "collectionGroupMonth": "Ανά μήνα", + "collectionGroupDay": "Ανά ημέρα", + "collectionGroupNone": "Να μην γίνει ομαδοποίηση", + + "sectionUnknown": "Χωρίς λεπτομέρειες", + "dateToday": "Σήμερα", + "dateYesterday": "Εχθές", + "dateThisMonth": "Αυτό το μήνα", + "collectionDeleteFailureFeedback": "{count, plural, =1{Αποτυχία διαγραφής του στοιχείου} other{Αποτυχία διαγραφής των {count} στοιχείων}}", + "collectionCopyFailureFeedback": "{count, plural, =1{Αποτυχία αντιγραφής του στοιχείου} other{Αποτυχία αντιγραφής των {count} στοιχείων}}", + "collectionMoveFailureFeedback": "{count, plural, =1{Αποτυχία μετακίνησης του στοιχείου} other{Αποτυχία μετακίνησης των {count} στοιχείων}}", + "collectionRenameFailureFeedback": "{count, plural, =1{Αποτυχία μετονομασίας του στοιχείου} other{Αποτυχία μετονομασίας των {count} στοιχείων}}", + "collectionEditFailureFeedback": "{count, plural, =1{Απέτυχε η επεξεργασία του στοιχείου} other{Απέτυχε η επεξεργασία των {count} στοιχείων}}", + "collectionExportFailureFeedback": "{count, plural, =1{Απέτυχε η εξαγωγή της σελίδας} other{Απέτυχε η εξαγωγή των {count} σελίδων}}", + "collectionCopySuccessFeedback": "{count, plural, =1{Αντιγράφηκε το στοιχείο} other{Αντιγράφηκαν τα {count} στοιχεία}}", + "collectionMoveSuccessFeedback": "{count, plural, =1{Μετακινήθηκε το στοιχείο} other{Μετακινήθηκαν τα {count} στοιχεία}}", + "collectionRenameSuccessFeedback": "{count, plural, =1{Μετονομάστηκε το στοιχείο} other{Μετονομάστηκαν τα {count} στοιχεία}}", + "collectionEditSuccessFeedback": "{count, plural, =1{Επεξεργάστηκε το στοιχείο} other{Επεξεργάστηκαν τα {count} στοιχεία}}", + + "collectionEmptyFavourites": "Δεν έχουν καταχωρηθεί αγαπημένα στοιχεία", + "collectionEmptyVideos": "Δεν υπάρχουν βίντεο", + "collectionEmptyImages": "Δεν υπάρχουν εικόνες", + "collectionEmptyGrantAccessButtonLabel": "Παραχώρηση πρόσβασης", + + "collectionSelectSectionTooltip": "Επιλέξτε ενότητα", + "collectionDeselectSectionTooltip": "Αφαιρέστε ενότητα", + + "drawerAboutButton": "Διάφορα", + "drawerSettingsButton": "Ρυθμίσεις", + "drawerCollectionAll": "Όλη η συλλογή", + "drawerCollectionFavourites": "Αγαπημένα", + "drawerCollectionImages": "Εικόνες", + "drawerCollectionVideos": "Βίντεο", + "drawerCollectionAnimated": "Κινούμενα", + "drawerCollectionMotionPhotos": "Φωτογραφίες με κίνηση", + "drawerCollectionPanoramas": "Πανοραμικές", + "drawerCollectionRaws": "Raw φωτογραφίες", + "drawerCollectionSphericalVideos": "Βίντεο 360°", + "drawerAlbumPage": "Άλμπουμ", + "drawerCountryPage": "Χώρες", + "drawerTagPage": "Ετικέτες", + + "sortByDate": "Ανά ημερομηνία", + "sortByName": "Ανά όνομα", + "sortByItemCount": "Ανά μέτρηση στοιχείων", + "sortBySize": "Ανά μέγεθος", + "sortByAlbumFileName": "Ανά άλμπουμ και όνομα αρχείου", + "sortByRating": "Ανά βαθμολογία", + + "sortOrderNewestFirst": "Τα πιο πρόσφατα πρώτα", + "sortOrderOldestFirst": "Τα παλαιότερα πρώτα", + "sortOrderAtoZ": "Α έως Ω", + "sortOrderZtoA": "Ω έως Α", + "sortOrderHighestFirst": "Υψηλότερη βαθμολογία πρώτα", + "sortOrderLowestFirst": "Χαμηλότερη βαθμολογία πρώτα", + "sortOrderLargestFirst": "Τα μεγαλύτερα πρώτα", + "sortOrderSmallestFirst": "Τα μικρότερα πρώτα", + + "albumGroupTier": "Ανά βαθμίδα", + "albumGroupVolume": "Ανά αποθηκευτική μονάδα", + "albumGroupNone": "Να μην γίνει ομαδοποίηση", + + "albumPickPageTitleCopy": "Αντιγραφή στο άλμπουμ", + "albumPickPageTitleExport": "Εξαγωγή στο άλμπουμ", + "albumPickPageTitleMove": "Μετακίνηση στο άλμπουμ", + "albumPickPageTitlePick": "Επιλογή άλμπουμ", + + "albumCamera": "Κάμερα", + "albumDownload": "Λήψεις", + "albumScreenshots": "Στιγμιότυπα οθόνης", + "albumScreenRecordings": "Εγγραφές οθόνης", + "albumVideoCaptures": "Στιγμιότυπα οθόνης από βίντεο", + + "albumPageTitle": "Άλμπουμ", + "albumEmpty": "Δεν υπάρχουν άλμπουμ", + "createAlbumTooltip": "Δημιουργία άλμπουμ", + "createAlbumButtonLabel": "ΔΗΜΙΟΥΡΓΙΑ", + "newFilterBanner": "Νέα", + + "countryPageTitle": "Χωρες", + "countryEmpty": "Δεν έχουν καταχωρηθεί χώρες", + + "tagPageTitle": "Ετικετες", + "tagEmpty": "Δεν έχουν καταχωρηθεί ετικέτες", + + "binPageTitle": "Καδος ανακυκλωσης", + + "searchCollectionFieldHint": "Αναζήτηση στην συλλογή", + "searchRecentSectionTitle": "Προσφατα", + "searchDateSectionTitle": "Ημερομηνια", + "searchAlbumsSectionTitle": "Άλμπουμ", + "searchCountriesSectionTitle": "Χωρες", + "searchPlacesSectionTitle": "Τοποθεσιες", + "searchTagsSectionTitle": "Ετικετες", + "searchRatingSectionTitle": "Βαθμολογιες", + "searchMetadataSectionTitle": "Μεταδεδομενα", + + "settingsPageTitle": "Ρυθμισεις", + "settingsSystemDefault": "Σύστημα", + "settingsDefault": "Προεπιλογή", + + "settingsSearchFieldLabel": "Αναζήτηση ρυθμίσεων", + "settingsSearchEmpty": "Δεν υπάρχει αντίστοιχη ρύθμιση", + "settingsActionExport": "Εξαγωγή", + "settingsActionExportDialogTitle": "Εξαγωγη", + "settingsActionImport": "Εισαγωγή", + "settingsActionImportDialogTitle": "Εισαγωγη", + + "appExportCovers": "Εξώφυλλα", + "appExportFavourites": "Αγαπημένα", + "appExportSettings": "Ρυθμίσεις", + + "settingsNavigationSectionTitle": "Πλοηγηση", + "settingsHomeTile": "Αρχική σελίδα της εφαρμογής", + "settingsHomeDialogTitle": "Αρχικη σελιδα της εφαρμογης", + "settingsShowBottomNavigationBar": "Εμφάνιση κάτω γραμμής πλοήγησης", + "settingsKeepScreenOnTile": "Διατήρηση της οθόνης ενεργοποιημένη", + "settingsKeepScreenOnDialogTitle": "Διατηρηση της Οθονης Ενεργοποιημενη", + "settingsDoubleBackExit": "Αγγίξτε το «πίσω» δύο φορές για έξοδο", + + "settingsConfirmationTile": "Παράθυρα επιβεβαίωσης", + "settingsConfirmationDialogTitle": "Παραθυρα Επιβεβαιωσης", + "settingsConfirmationBeforeDeleteItems": "Ερώτηση πριν διαγραφούν στοιχεία οριστικά", + "settingsConfirmationBeforeMoveToBinItems": "Ερώτηση πριν μεταφερθούν στοιχεία στον κάδο ανακύκλωσης", + "settingsConfirmationBeforeMoveUndatedItems": "Ερώτηση πριν μεταφερθούν στοιχεία χωρίς ημερομηνία", + "settingsConfirmationAfterMoveToBinItems": "Εμφάνιση μηνύματος μετά τη μεταφορά στοιχείων στον κάδο ανακύκλωσης", + + "settingsNavigationDrawerTile": "Μενού πλοήγησης", + "settingsNavigationDrawerEditorPageTitle": "Μενου Πλοηγησης", + "settingsNavigationDrawerBanner": "Αγγίξτε παρατεταμένα για να μετακινήσετε και να αναδιατάξετε τα στοιχεία του μενού.", + "settingsNavigationDrawerTabTypes": "Τύποι", + "settingsNavigationDrawerTabAlbums": "Άλμπουμ", + "settingsNavigationDrawerTabPages": "Σελίδες", + "settingsNavigationDrawerAddAlbum": "Προσθήκη άλμπουμ", + + "settingsThumbnailSectionTitle": "Μικρογραφιες", + "settingsThumbnailOverlayTile": "Βοηθητικές πληροφορίες", + "settingsThumbnailOverlayPageTitle": "Βοηθητικές πληροφοριες", + "settingsThumbnailShowFavouriteIcon": "Εμφάνιση εικονιδίου για αγαπημένο", + "settingsThumbnailShowTagIcon": "Εμφάνιση εικονιδίου για ετικέτα", + "settingsThumbnailShowLocationIcon": "Εμφάνιση εικονιδίου για τοποθεσία", + "settingsThumbnailShowMotionPhotoIcon": "Εμφάνιση εικονιδίου για φωτογραφία με κίνηση", + "settingsThumbnailShowRating": "Εμφάνιση βαθμολογίας", + "settingsThumbnailShowRawIcon": "Εμφάνιση εικονιδίου για raw στοιχεία", + "settingsThumbnailShowVideoDuration": "Εμφάνιση διάρκειας βίντεο", + + "settingsCollectionQuickActionsTile": "Γρήγορες ενέργειες", + "settingsCollectionQuickActionEditorPageTitle": "Γρηγορες Ενεργειες", + "settingsCollectionQuickActionTabBrowsing": "Περιήγηση", + "settingsCollectionQuickActionTabSelecting": "Επιλογή", + "settingsCollectionBrowsingQuickActionEditorBanner": "Αγγίξτε παρατεταμένα για να μετακινήσετε τα κουμπιά και επιλέξτε ποιες ενέργειες θα εμφανίζονται κατά την περιήγηση στοιχείων.", + "settingsCollectionSelectionQuickActionEditorBanner": "Αγγίξτε παρατεταμένα για να μετακινήσετε τα κουμπιά και επιλέξτε ποιες ενέργειες θα εμφανίζονται κατά την επιλογή στοιχείων.", + + "settingsViewerSectionTitle": "Προβολη", + "settingsViewerGestureSideTapNext": "Πατήστε στις άκρες της οθόνης για να εμφανίσετε το προηγούμενο/επόμενο στοιχείο", + "settingsViewerUseCutout": "Χρησιμοποιήστε την περιοχή αποκοπής", + "settingsViewerMaximumBrightness": "Μέγιστη φωτεινότητα", + "settingsMotionPhotoAutoPlay": "Αυτόματη αναπαραγωγή φωτογραφιών κίνησης", + "settingsImageBackground": "Φόντο κατά την προβολή στοιχείων", + + "settingsViewerQuickActionsTile": "Γρήγορες ενέργειες", + "settingsViewerQuickActionEditorPageTitle": "Γρηγορες Ενεργειες", + "settingsViewerQuickActionEditorBanner": "Αγγίξτε παρατεταμένα για να μετακινήσετε τα κουμπιά και επιλέξτε ποιες ενέργειες θα εμφανίζονται κατά την προβολή.", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Εμφανιζομενα κουμπια", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Διαθεσιμα κουμπια", + "settingsViewerQuickActionEmpty": "Χωρίς κουμπιά", + + "settingsViewerOverlayTile": "Επικάλυψη", + "settingsViewerOverlayPageTitle": "Επικαλυψη", + "settingsViewerShowOverlayOnOpening": "Εμφάνιση κατά το άνοιγμα", + "settingsViewerShowMinimap": "Εμφάνιση μικρού χάρτη", + "settingsViewerShowInformation": "Εμφάνιση πληροφοριών", + "settingsViewerShowInformationSubtitle": "Εμφάνιση ονομασίας, ημερομηνίας, τοποθεσίας κ.λπ.", + "settingsViewerShowShootingDetails": "Εμφάνιση λεπτομερειών λήψης", + "settingsViewerShowOverlayThumbnails": "Εμφάνιση μικρογραφιών", + "settingsViewerEnableOverlayBlurEffect": "Εφέ θαμπώματος", + + "settingsViewerSlideshowTile": "Παρουσίαση φωτογραφιών", + "settingsViewerSlideshowPageTitle": "Παρουσιαση φωτογραφιων", + "settingsSlideshowRepeat": "Επανάληψη", + "settingsSlideshowShuffle": "Τυχαία σειρά", + "settingsSlideshowFillScreen": "Χρησιμοποίηση πλήρης οθόνης", + "settingsSlideshowTransitionTile": "Μετάβαση", + "settingsSlideshowTransitionDialogTitle": "Μεταβαση", + "settingsSlideshowIntervalTile": "Διάρκεια", + "settingsSlideshowIntervalDialogTitle": "Διαρκεια", + "settingsSlideshowVideoPlaybackTile": "Αναπαραγωγή βίντεο", + "settingsSlideshowVideoPlaybackDialogTitle": "Αναπαραγωγη Βιντεο", + + "settingsVideoPageTitle": "Ρυθμισεις Βιντεο", + "settingsVideoSectionTitle": "Βιντεο", + "settingsVideoShowVideos": "Εμφάνιση των βίντεο στη συλλογή", + "settingsVideoEnableHardwareAcceleration": "Επιτάχυνση υλισμικού", + "settingsVideoEnableAutoPlay": "Αυτόματη αναπαραγωγή κατά το άνοιγμα", + "settingsVideoLoopModeTile": "Επανάληψη αυτόματα στο τέλος κάθε βίντεο", + "settingsVideoLoopModeDialogTitle": "Επαναληψη Αυτοματα στο Τελος Καθε Βιντεο", + + "settingsSubtitleThemeTile": "Υπότιτλοι", + "settingsSubtitleThemePageTitle": "Υποτιτλοι", + "settingsSubtitleThemeSample": "Αυτό είναι ένα δείγμα.", + "settingsSubtitleThemeTextAlignmentTile": "Στοίχιση κειμένου", + "settingsSubtitleThemeTextAlignmentDialogTitle": "Στοιχιση Κειμενου", + "settingsSubtitleThemeTextSize": "Μέγεθος κειμένου", + "settingsSubtitleThemeShowOutline": "Εμφάνιση περιγράμματος και σκιάς", + "settingsSubtitleThemeTextColor": "Χρώμα κειμένου", + "settingsSubtitleThemeTextOpacity": "Αδιαφάνεια κειμένου", + "settingsSubtitleThemeBackgroundColor": "Χρώμα του φόντου", + "settingsSubtitleThemeBackgroundOpacity": "Αδιαφάνεια φόντου", + "settingsSubtitleThemeTextAlignmentLeft": "Αριστερά", + "settingsSubtitleThemeTextAlignmentCenter": "Κέντρο", + "settingsSubtitleThemeTextAlignmentRight": "Δεξιά", + + "settingsVideoControlsTile": "Έλεγχος", + "settingsVideoControlsPageTitle": "Ελεγχος", + "settingsVideoButtonsTile": "Κουμπιά", + "settingsVideoButtonsDialogTitle": "Κουμπια", + "settingsVideoGestureDoubleTapTogglePlay": "Αγγίξτε την οθόνη δύο φορές για αναπαραγωγή/παύση", + "settingsVideoGestureSideDoubleTapSeek": "Αγγίξτε δύο φορές τις άκρες της οθόνης για να πάτε πίσω/εμπρός", + + "settingsPrivacySectionTitle": "Απορρητο", + "settingsAllowInstalledAppAccess": "Να επιτρέπεται η πρόσβαση στον κατάλογο εγκατεστημένων εφαρμογών της συσκευής", + "settingsAllowInstalledAppAccessSubtitle": "Χρησιμοποιείται για τη βελτίωση της εμφάνισης των στοιχείων από τα άλμπουμ", + "settingsAllowErrorReporting": "Να επιτρέπεται η ανώνυμη αναφορά σφαλμάτων", + "settingsSaveSearchHistory": "Αποθήκευση ιστορικού αναζήτησης", + "settingsEnableBin": "Χρησιμοποίηση κάδου ανακύκλωσης", + "settingsEnableBinSubtitle": "Διατήρηση των διαγραμμένων στοιχείων για 30 ημέρες", + + "settingsHiddenItemsTile": "Κρυφά στοιχεία", + "settingsHiddenItemsPageTitle": "Κρυφα Στοιχεια", + + "settingsHiddenItemsTabFilters": "Κρυφά φίλτρα", + "settingsHiddenFiltersBanner": "Οι φωτογραφίες και τα βίντεο που είναι κρυφά δεν θα εμφανίζονται στη συλλογή σας.", + "settingsHiddenFiltersEmpty": "Χωρίς κρυφά φίλτρα", + + "settingsHiddenItemsTabPaths": "Κρυφές διαδρομές", + "settingsHiddenPathsBanner": "Οι φωτογραφίες και τα βίντεο σε αυτούς τους φακέλους ή σε οποιονδήποτε από τους υποφακέλους τους, δεν θα εμφανίζονται στη συλλογή σας.", + "addPathTooltip": "Προσθήκη διαδρομής", + + "settingsStorageAccessTile": "Πρόσβαση στον χώρο αποθήκευσης", + "settingsStorageAccessPageTitle": "Προσβαση στον Χωρο Αποθηκευσης", + "settingsStorageAccessBanner": "Ορισμένοι κατάλογοι απαιτούν ρητή άδεια πρόσβασης για την τροποποίηση των αρχείων που βρίσκονται σε αυτούς. Μπορείτε να δείτε εδώ τους καταλόγους στους οποίους έχετε ήδη χορηγήσει πρόσβαση.", + "settingsStorageAccessEmpty": "Δεν έχει χορηγηθεί πρόσβαση", + "settingsStorageAccessRevokeTooltip": "Ανάκληση χορήγησης πρόσβασης", + + "settingsAccessibilitySectionTitle": "Προσβασιμοτητα", + "settingsRemoveAnimationsTile": "Κατάργηση κινούμενων εικόνων", + "settingsRemoveAnimationsDialogTitle": "Καταργηση Κινουμενων Εικονων", + "settingsTimeToTakeActionTile": "Χρόνος λήψης ενεργειών", + "settingsTimeToTakeActionDialogTitle": "Χρονος Ληψης Ενεργειων", + + "settingsDisplaySectionTitle": "Οθονη", + "settingsThemeBrightnessTile": "Θέμα", + "settingsThemeBrightnessDialogTitle": "Θεμα", + "settingsThemeColorHighlights": "Χρωματιστά εικονίδια", + "settingsThemeEnableDynamicColor": "Έντονο χρώμα", + "settingsDisplayRefreshRateModeTile": "Εμφάνιση ρυθμού ανανέωσης", + "settingsDisplayRefreshRateModeDialogTitle": "Ρυθμος ανανεωσης", + + "settingsLanguageSectionTitle": "Γλωσσα & Μορφες", + "settingsLanguageTile": "Γλώσσα", + "settingsLanguagePageTitle": "Γλωσσα", + "settingsCoordinateFormatTile": "Μορφή συντεταγμένων", + "settingsCoordinateFormatDialogTitle": "Μορφη Συντεταγμενων", + "settingsUnitSystemTile": "Σύστημα μέτρησης", + "settingsUnitSystemDialogTitle": "Συστημα μετρησης", + + "settingsScreenSaverPageTitle": "Προφυλαξη οθονης", + + "settingsWidgetPageTitle": "Κορνιζα", + "settingsWidgetShowOutline": "Περίγραμμα", + + "settingsCollectionTile": "Συλλογή", + + "statsPageTitle": "Στατιστικα", + "statsWithGps": "{count, plural, =1{1 στοιχείο με τοποθεσία} other{{count} στοιχεία με τοποθεσία}}", + "statsTopCountriesSectionTitle": "Κορυφαιες Χωρες", + "statsTopPlacesSectionTitle": "Κορυφαια Μερη", + "statsTopTagsSectionTitle": "Κορυφαιες Ετικετες", + + "viewerOpenPanoramaButtonLabel": "Άνοιγμα πανοραμικών", + "viewerSetWallpaperButtonLabel": "ΟΡΙΣΜΟΣ ΤΑΠΕΤΣΑΡΙΑΣ", + "viewerErrorUnknown": "Ωχ!", + "viewerErrorDoesNotExist": "Το αρχείο δεν υπάρχει πλέον.", + + "viewerInfoPageTitle": "Πληροφοριες", + "viewerInfoBackToViewerTooltip": "Επιστροφή στην προβολή", + + "viewerInfoUnknown": "Άγνωστο", + "viewerInfoLabelDescription": "Περιγραφή", + "viewerInfoLabelTitle": "Όνομα", + "viewerInfoLabelDate": "Ημερομηνία", + "viewerInfoLabelResolution": "Ανάλυση", + "viewerInfoLabelSize": "Μέγεθος", + "viewerInfoLabelUri": "URI", + "viewerInfoLabelPath": "Διαδρομή", + "viewerInfoLabelDuration": "Διάρκεια", + "viewerInfoLabelOwner": "Κάτοχος", + "viewerInfoLabelCoordinates": "Συντεταγμένες", + "viewerInfoLabelAddress": "Διεύθυνση", + + "mapStyleDialogTitle": "Στυλ χαρτη", + "mapStyleTooltip": "Επιλέξτε στυλ χάρτη", + "mapZoomInTooltip": "Μεγέθυνση", + "mapZoomOutTooltip": "Σμίκρυνση", + "mapPointNorthUpTooltip": "Εμφάνιση του βορρά στην κορυφή", + "mapAttributionOsmHot": "Map data © [OpenStreetMap](https://www.openstreetmap.org/copyright) contributors • Tiles by [HOT](https://www.hotosm.org/) • Hosted by [OSM France](https://openstreetmap.fr/)", + "mapAttributionStamen": "Map data © [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_en.arb b/lib/l10n/app_en.arb index 0028aeeca..b563fe5f1 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -55,6 +55,7 @@ "actionRemove": "Remove", "resetTooltip": "Reset", "saveTooltip": "Save", + "pickTooltip": "Pick", "doubleBackExitMessage": "Tap “back” again to exit.", "doNotAskAgain": "Do not ask again", @@ -115,18 +116,20 @@ "entryInfoActionEditDate": "Edit date & time", "entryInfoActionEditLocation": "Edit location", - "entryInfoActionEditDescription": "Edit description", + "entryInfoActionEditTitleDescription": "Edit title & description", "entryInfoActionEditRating": "Edit rating", "entryInfoActionEditTags": "Edit tags", "entryInfoActionRemoveMetadata": "Remove metadata", "filterBinLabel": "Recycle bin", "filterFavouriteLabel": "Favorite", - "filterLocationEmptyLabel": "Unlocated", - "filterTagEmptyLabel": "Untagged", + "filterNoDateLabel": "Undated", + "filterNoLocationLabel": "Unlocated", + "filterNoRatingLabel": "Unrated", + "filterNoTagLabel": "Untagged", + "filterNoTitleLabel": "Untitled", "filterOnThisDayLabel": "On this day", "filterRecentlyAddedLabel": "Recently added", - "filterRatingUnratedLabel": "Unrated", "filterRatingRejectedLabel": "Rejected", "filterTypeAnimatedLabel": "Animated", "filterTypeMotionPhotoLabel": "Motion Photo", @@ -228,7 +231,6 @@ } } }, - "storageAccessDialogTitle": "Storage Access", "storageAccessDialogMessage": "Please select the {directory} of “{volume}” in the next screen to give this app access to it.", "@storageAccessDialogMessage": { "placeholders": { @@ -243,7 +245,6 @@ } } }, - "restrictedAccessDialogTitle": "Restricted Access", "restrictedAccessDialogMessage": "This app is not allowed to modify files in the {directory} of “{volume}”.\n\nPlease use a pre-installed file manager or gallery app to move the items to another directory.", "@restrictedAccessDialogMessage": { "placeholders": { @@ -258,7 +259,6 @@ } } }, - "notEnoughSpaceDialogTitle": "Not Enough Space", "notEnoughSpaceDialogMessage": "This operation needs {neededSize} of free space on “{volume}” to complete, but there is only {freeSize} left.", "@notEnoughSpaceDialogMessage": { "placeholders": { @@ -277,10 +277,8 @@ } } }, - "missingSystemFilePickerDialogTitle": "Missing System File Picker", "missingSystemFilePickerDialogMessage": "The system file picker is missing or disabled. Please enable it and try again.", - "unsupportedTypeDialogTitle": "Unsupported Types", "unsupportedTypeDialogMessage": "{count, plural, =1{This operation is not supported for items of the following type: {types}.} other{This operation is not supported for items of the following types: {types}.}}", "@unsupportedTypeDialogMessage": { "placeholders": { @@ -299,7 +297,6 @@ "addShortcutDialogLabel": "Shortcut label", "addShortcutButtonLabel": "ADD", - "noMatchingAppDialogTitle": "No Matching App", "noMatchingAppDialogMessage": "There are no apps that can handle this.", "binEntriesConfirmationDialogMessage": "{count, plural, =1{Move this item to the recycle bin?} other{Move these {count} items to the recycle bin?}}", @@ -346,7 +343,7 @@ "renameEntrySetPageTitle": "Rename", "renameEntrySetPagePatternFieldLabel": "Naming pattern", "renameEntrySetPageInsertTooltip": "Insert field", - "renameEntrySetPagePreview": "Preview", + "renameEntrySetPagePreviewSectionTitle": "Preview", "renameProcessorCounter": "Counter", "renameProcessorName": "Name", @@ -389,8 +386,6 @@ "locationPickerUseThisLocationButton": "Use this location", - "editEntryDescriptionDialogTitle": "Description", - "editEntryRatingDialogTitle": "Rating", "removeEntryMetadataDialogTitle": "Metadata Removal", @@ -419,9 +414,10 @@ "menuActionSlideshow": "Slideshow", "menuActionStats": "Stats", - "viewDialogTabSort": "Sort", - "viewDialogTabGroup": "Group", - "viewDialogTabLayout": "Layout", + "viewDialogSortSectionTitle": "Sort", + "viewDialogGroupSectionTitle": "Group", + "viewDialogLayoutSectionTitle": "Layout", + "viewDialogReverseSortOrder": "Reverse sort order", "tileLayoutGrid": "Grid", "tileLayoutList": "List", @@ -438,24 +434,24 @@ "aboutLinkLicense": "License", "aboutLinkPolicy": "Privacy Policy", - "aboutBug": "Bug Report", + "aboutBugSectionTitle": "Bug Report", "aboutBugSaveLogInstruction": "Save app logs to a file", "aboutBugCopyInfoInstruction": "Copy system information", "aboutBugCopyInfoButton": "Copy", "aboutBugReportInstruction": "Report on GitHub with the logs and system information", "aboutBugReportButton": "Report", - "aboutCredits": "Credits", + "aboutCreditsSectionTitle": "Credits", "aboutCreditsWorldAtlas1": "This app uses a TopoJSON file from", "aboutCreditsWorldAtlas2": "under ISC License.", - "aboutCreditsTranslators": "Translators", + "aboutTranslatorsSectionTitle": "Translators", - "aboutLicenses": "Open-Source Licenses", + "aboutLicensesSectionTitle": "Open-Source Licenses", "aboutLicensesBanner": "This app uses the following open-source packages and libraries.", - "aboutLicensesAndroidLibraries": "Android Libraries", - "aboutLicensesFlutterPlugins": "Flutter Plugins", - "aboutLicensesFlutterPackages": "Flutter Packages", - "aboutLicensesDartPackages": "Dart Packages", + "aboutLicensesAndroidLibrariesSectionTitle": "Android Libraries", + "aboutLicensesFlutterPluginsSectionTitle": "Flutter Plugins", + "aboutLicensesFlutterPackagesSectionTitle": "Flutter Packages", + "aboutLicensesDartPackagesSectionTitle": "Dart Packages", "aboutLicensesShowAllButtonLabel": "Show All Licenses", "policyPageTitle": "Privacy Policy", @@ -475,11 +471,6 @@ "collectionSearchTitlesHintText": "Search titles", - "collectionSortDate": "By date", - "collectionSortSize": "By size", - "collectionSortName": "By album & file name", - "collectionSortRating": "By rating", - "collectionGroupAlbum": "By album", "collectionGroupMonth": "By month", "collectionGroupDay": "By day", @@ -558,6 +549,8 @@ "collectionSelectSectionTooltip": "Select section", "collectionDeselectSectionTooltip": "Deselect section", + "drawerAboutButton": "About", + "drawerSettingsButton": "Settings", "drawerCollectionAll": "All collection", "drawerCollectionFavourites": "Favorites", "drawerCollectionImages": "Images", @@ -567,10 +560,25 @@ "drawerCollectionPanoramas": "Panoramas", "drawerCollectionRaws": "Raw photos", "drawerCollectionSphericalVideos": "360° Videos", + "drawerAlbumPage": "Albums", + "drawerCountryPage": "Countries", + "drawerTagPage": "Tags", - "chipSortDate": "By date", - "chipSortName": "By name", - "chipSortCount": "By item count", + "sortByDate": "By date", + "sortByName": "By name", + "sortByItemCount": "By item count", + "sortBySize": "By size", + "sortByAlbumFileName": "By album & file name", + "sortByRating": "By rating", + + "sortOrderNewestFirst": "Newest first", + "sortOrderOldestFirst": "Oldest first", + "sortOrderAtoZ": "A to Z", + "sortOrderZtoA": "Z to A", + "sortOrderHighestFirst": "Highest first", + "sortOrderLowestFirst": "Lowest first", + "sortOrderLargestFirst": "Largest first", + "sortOrderSmallestFirst": "Smallest first", "albumGroupTier": "By tier", "albumGroupVolume": "By storage volume", @@ -602,13 +610,14 @@ "binPageTitle": "Recycle Bin", "searchCollectionFieldHint": "Search collection", - "searchSectionRecent": "Recent", - "searchSectionDate": "Date", - "searchSectionAlbums": "Albums", - "searchSectionCountries": "Countries", - "searchSectionPlaces": "Places", - "searchSectionTags": "Tags", - "searchSectionRating": "Ratings", + "searchRecentSectionTitle": "Recent", + "searchDateSectionTitle": "Date", + "searchAlbumsSectionTitle": "Albums", + "searchCountriesSectionTitle": "Countries", + "searchPlacesSectionTitle": "Places", + "searchTagsSectionTitle": "Tags", + "searchRatingSectionTitle": "Ratings", + "searchMetadataSectionTitle": "Metadata", "settingsPageTitle": "Settings", "settingsSystemDefault": "System", @@ -617,37 +626,40 @@ "settingsSearchFieldLabel": "Search settings", "settingsSearchEmpty": "No matching setting", "settingsActionExport": "Export", + "settingsActionExportDialogTitle": "Export", "settingsActionImport": "Import", + "settingsActionImportDialogTitle": "Import", "appExportCovers": "Covers", "appExportFavourites": "Favorites", "appExportSettings": "Settings", - "settingsSectionNavigation": "Navigation", - "settingsHome": "Home", + "settingsNavigationSectionTitle": "Navigation", + "settingsHomeTile": "Home", + "settingsHomeDialogTitle": "Home", "settingsShowBottomNavigationBar": "Show bottom navigation bar", "settingsKeepScreenOnTile": "Keep screen on", - "settingsKeepScreenOnTitle": "Keep Screen On", + "settingsKeepScreenOnDialogTitle": "Keep Screen On", "settingsDoubleBackExit": "Tap “back” twice to exit", - "settingsConfirmationDialogTile": "Confirmation dialogs", + "settingsConfirmationTile": "Confirmation dialogs", "settingsConfirmationDialogTitle": "Confirmation Dialogs", - "settingsConfirmationDialogDeleteItems": "Ask before deleting items forever", - "settingsConfirmationDialogMoveToBinItems": "Ask before moving items to the recycle bin", - "settingsConfirmationDialogMoveUndatedItems": "Ask before moving undated items", + "settingsConfirmationBeforeDeleteItems": "Ask before deleting items forever", + "settingsConfirmationBeforeMoveToBinItems": "Ask before moving items to the recycle bin", + "settingsConfirmationBeforeMoveUndatedItems": "Ask before moving undated items", "settingsConfirmationAfterMoveToBinItems": "Show message after moving items to the recycle bin", "settingsNavigationDrawerTile": "Navigation menu", - "settingsNavigationDrawerEditorTitle": "Navigation Menu", + "settingsNavigationDrawerEditorPageTitle": "Navigation Menu", "settingsNavigationDrawerBanner": "Touch and hold to move and reorder menu items.", "settingsNavigationDrawerTabTypes": "Types", "settingsNavigationDrawerTabAlbums": "Albums", "settingsNavigationDrawerTabPages": "Pages", "settingsNavigationDrawerAddAlbum": "Add album", - "settingsSectionThumbnails": "Thumbnails", + "settingsThumbnailSectionTitle": "Thumbnails", "settingsThumbnailOverlayTile": "Overlay", - "settingsThumbnailOverlayTitle": "Overlay", + "settingsThumbnailOverlayPageTitle": "Overlay", "settingsThumbnailShowFavouriteIcon": "Show favorite icon", "settingsThumbnailShowTagIcon": "Show tag icon", "settingsThumbnailShowLocationIcon": "Show location icon", @@ -657,13 +669,13 @@ "settingsThumbnailShowVideoDuration": "Show video duration", "settingsCollectionQuickActionsTile": "Quick actions", - "settingsCollectionQuickActionEditorTitle": "Quick Actions", + "settingsCollectionQuickActionEditorPageTitle": "Quick Actions", "settingsCollectionQuickActionTabBrowsing": "Browsing", "settingsCollectionQuickActionTabSelecting": "Selecting", "settingsCollectionBrowsingQuickActionEditorBanner": "Touch and hold to move buttons and select which actions are displayed when browsing items.", "settingsCollectionSelectionQuickActionEditorBanner": "Touch and hold to move buttons and select which actions are displayed when selecting items.", - "settingsSectionViewer": "Viewer", + "settingsViewerSectionTitle": "Viewer", "settingsViewerGestureSideTapNext": "Tap on screen edges to show previous/next item", "settingsViewerUseCutout": "Use cutout area", "settingsViewerMaximumBrightness": "Maximum brightness", @@ -671,14 +683,14 @@ "settingsImageBackground": "Image background", "settingsViewerQuickActionsTile": "Quick actions", - "settingsViewerQuickActionEditorTitle": "Quick Actions", + "settingsViewerQuickActionEditorPageTitle": "Quick Actions", "settingsViewerQuickActionEditorBanner": "Touch and hold to move buttons and select which actions are displayed in the viewer.", - "settingsViewerQuickActionEditorDisplayedButtons": "Displayed Buttons", - "settingsViewerQuickActionEditorAvailableButtons": "Available Buttons", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Displayed Buttons", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Available Buttons", "settingsViewerQuickActionEmpty": "No buttons", "settingsViewerOverlayTile": "Overlay", - "settingsViewerOverlayTitle": "Overlay", + "settingsViewerOverlayPageTitle": "Overlay", "settingsViewerShowOverlayOnOpening": "Show on opening", "settingsViewerShowMinimap": "Show minimap", "settingsViewerShowInformation": "Show information", @@ -688,30 +700,30 @@ "settingsViewerEnableOverlayBlurEffect": "Blur effect", "settingsViewerSlideshowTile": "Slideshow", - "settingsViewerSlideshowTitle": "Slideshow", + "settingsViewerSlideshowPageTitle": "Slideshow", "settingsSlideshowRepeat": "Repeat", "settingsSlideshowShuffle": "Shuffle", "settingsSlideshowFillScreen": "Fill screen", "settingsSlideshowTransitionTile": "Transition", - "settingsSlideshowTransitionTitle": "Transition", + "settingsSlideshowTransitionDialogTitle": "Transition", "settingsSlideshowIntervalTile": "Interval", - "settingsSlideshowIntervalTitle": "Interval", + "settingsSlideshowIntervalDialogTitle": "Interval", "settingsSlideshowVideoPlaybackTile": "Video playback", - "settingsSlideshowVideoPlaybackTitle": "Video Playback", + "settingsSlideshowVideoPlaybackDialogTitle": "Video Playback", "settingsVideoPageTitle": "Video Settings", - "settingsSectionVideo": "Video", + "settingsVideoSectionTitle": "Video", "settingsVideoShowVideos": "Show videos", "settingsVideoEnableHardwareAcceleration": "Hardware acceleration", "settingsVideoEnableAutoPlay": "Auto play", "settingsVideoLoopModeTile": "Loop mode", - "settingsVideoLoopModeTitle": "Loop Mode", + "settingsVideoLoopModeDialogTitle": "Loop Mode", "settingsSubtitleThemeTile": "Subtitles", - "settingsSubtitleThemeTitle": "Subtitles", + "settingsSubtitleThemePageTitle": "Subtitles", "settingsSubtitleThemeSample": "This is a sample.", "settingsSubtitleThemeTextAlignmentTile": "Text alignment", - "settingsSubtitleThemeTextAlignmentTitle": "Text Alignment", + "settingsSubtitleThemeTextAlignmentDialogTitle": "Text Alignment", "settingsSubtitleThemeTextSize": "Text size", "settingsSubtitleThemeShowOutline": "Show outline and shadow", "settingsSubtitleThemeTextColor": "Text color", @@ -723,13 +735,13 @@ "settingsSubtitleThemeTextAlignmentRight": "Right", "settingsVideoControlsTile": "Controls", - "settingsVideoControlsTitle": "Controls", + "settingsVideoControlsPageTitle": "Controls", "settingsVideoButtonsTile": "Buttons", - "settingsVideoButtonsTitle": "Buttons", + "settingsVideoButtonsDialogTitle": "Buttons", "settingsVideoGestureDoubleTapTogglePlay": "Double tap to play/pause", "settingsVideoGestureSideDoubleTapSeek": "Double tap on screen edges to seek backward/forward", - "settingsSectionPrivacy": "Privacy", + "settingsPrivacySectionTitle": "Privacy", "settingsAllowInstalledAppAccess": "Allow access to app inventory", "settingsAllowInstalledAppAccessSubtitle": "Used to improve album display", "settingsAllowErrorReporting": "Allow anonymous error reporting", @@ -738,47 +750,51 @@ "settingsEnableBinSubtitle": "Keep deleted items for 30 days", "settingsHiddenItemsTile": "Hidden items", - "settingsHiddenItemsTitle": "Hidden Items", + "settingsHiddenItemsPageTitle": "Hidden Items", - "settingsHiddenFiltersTitle": "Hidden Filters", + "settingsHiddenItemsTabFilters": "Hidden Filters", "settingsHiddenFiltersBanner": "Photos and videos matching hidden filters will not appear in your collection.", "settingsHiddenFiltersEmpty": "No hidden filters", - "settingsHiddenPathsTitle": "Hidden Paths", + "settingsHiddenItemsTabPaths": "Hidden Paths", "settingsHiddenPathsBanner": "Photos and videos in these folders, or any of their subfolders, will not appear in your collection.", "addPathTooltip": "Add path", "settingsStorageAccessTile": "Storage access", - "settingsStorageAccessTitle": "Storage Access", + "settingsStorageAccessPageTitle": "Storage Access", "settingsStorageAccessBanner": "Some directories require an explicit access grant to modify files in them. You can review here directories to which you previously gave access.", "settingsStorageAccessEmpty": "No access grants", "settingsStorageAccessRevokeTooltip": "Revoke", - "settingsSectionAccessibility": "Accessibility", + "settingsAccessibilitySectionTitle": "Accessibility", "settingsRemoveAnimationsTile": "Remove animations", - "settingsRemoveAnimationsTitle": "Remove Animations", + "settingsRemoveAnimationsDialogTitle": "Remove Animations", "settingsTimeToTakeActionTile": "Time to take action", - "settingsTimeToTakeActionTitle": "Time to Take Action", + "settingsTimeToTakeActionDialogTitle": "Time to Take Action", - "settingsSectionDisplay": "Display", - "settingsThemeBrightness": "Theme", + "settingsDisplaySectionTitle": "Display", + "settingsThemeBrightnessTile": "Theme", + "settingsThemeBrightnessDialogTitle": "Theme", "settingsThemeColorHighlights": "Color highlights", "settingsThemeEnableDynamicColor": "Dynamic color", "settingsDisplayRefreshRateModeTile": "Display refresh rate", - "settingsDisplayRefreshRateModeTitle": "Refresh Rate", + "settingsDisplayRefreshRateModeDialogTitle": "Refresh Rate", - "settingsSectionLanguage": "Language & Formats", - "settingsLanguage": "Language", + "settingsLanguageSectionTitle": "Language & Formats", + "settingsLanguageTile": "Language", + "settingsLanguagePageTitle": "Language", "settingsCoordinateFormatTile": "Coordinate format", - "settingsCoordinateFormatTitle": "Coordinate Format", + "settingsCoordinateFormatDialogTitle": "Coordinate Format", "settingsUnitSystemTile": "Units", - "settingsUnitSystemTitle": "Units", + "settingsUnitSystemDialogTitle": "Units", "settingsScreenSaverPageTitle": "Screen Saver", "settingsWidgetPageTitle": "Photo Frame", "settingsWidgetShowOutline": "Outline", + "settingsCollectionTile": "Collection", + "statsPageTitle": "Stats", "statsWithGps": "{count, plural, =1{1 item with location} other{{count} items with location}}", "@statsWithGps": { @@ -786,9 +802,9 @@ "count": {} } }, - "statsTopCountries": "Top Countries", - "statsTopPlaces": "Top Places", - "statsTopTags": "Top Tags", + "statsTopCountriesSectionTitle": "Top Countries", + "statsTopPlacesSectionTitle": "Top Places", + "statsTopTagsSectionTitle": "Top Tags", "viewerOpenPanoramaButtonLabel": "OPEN PANORAMA", "viewerSetWallpaperButtonLabel": "SET WALLPAPER", @@ -799,6 +815,7 @@ "viewerInfoBackToViewerTooltip": "Back to viewer", "viewerInfoUnknown": "unknown", + "viewerInfoLabelDescription": "Description", "viewerInfoLabelTitle": "Title", "viewerInfoLabelDate": "Date", "viewerInfoLabelResolution": "Resolution", @@ -810,7 +827,7 @@ "viewerInfoLabelCoordinates": "Coordinates", "viewerInfoLabelAddress": "Address", - "mapStyleTitle": "Map Style", + "mapStyleDialogTitle": "Map Style", "mapStyleTooltip": "Select map style", "mapZoomInTooltip": "Zoom in", "mapZoomOutTooltip": "Zoom out", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 09f052491..e84adbb27 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -27,6 +27,7 @@ "actionRemove": "Remover", "resetTooltip": "Restablecer", "saveTooltip": "Guardar", + "pickTooltip": "Elegir", "doubleBackExitMessage": "Presione «atrás» nuevamente para salir.", "doNotAskAgain": "No preguntar nuevamente", @@ -93,10 +94,10 @@ "filterBinLabel": "Cesto de basura", "filterFavouriteLabel": "Favorito", - "filterLocationEmptyLabel": "No localizado", - "filterTagEmptyLabel": "Sin etiquetar", + "filterNoLocationLabel": "No localizado", + "filterNoRatingLabel": "Sin clasificar", + "filterNoTagLabel": "Sin etiquetar", "filterOnThisDayLabel": "De este día", - "filterRatingUnratedLabel": "Sin clasificar", "filterRatingRejectedLabel": "Rechazado", "filterTypeAnimatedLabel": "Animado", "filterTypeMotionPhotoLabel": "Foto en movimiento", @@ -177,16 +178,11 @@ "storageVolumeDescriptionFallbackNonPrimary": "Tarjeta de memoria", "rootDirectoryDescription": "el directorio raíz", "otherDirectoryDescription": "el directorio «{name}»", - "storageAccessDialogTitle": "Acceso al almacenamiento", "storageAccessDialogMessage": "Por favor seleccione {directory} en «{volume}» en la siguiente pantalla para permitir a esta aplicación tener acceso.", - "restrictedAccessDialogTitle": "Acceso restringido", "restrictedAccessDialogMessage": "Esta aplicación no tiene permiso para modificar archivos de {directory} en «{volume}».\n\nPor favor use un gestor de archivos o la aplicación de galería preinstalada para mover los elementos a otro directorio.", - "notEnoughSpaceDialogTitle": "Espacio insuficiente", "notEnoughSpaceDialogMessage": "Esta operación necesita {neededSize} de espacio libre en «{volume}» para completarse, pero sólo hay {freeSize} disponible.", - "missingSystemFilePickerDialogTitle": "Selector de archivos del sistema no disponible", "missingSystemFilePickerDialogMessage": "El selector de archivos del sistema no se encuentra disponible o fue deshabilitado. Por favor habilítelo e intente nuevamente.", - "unsupportedTypeDialogTitle": "Tipos de archivo incompatibles", "unsupportedTypeDialogMessage": "{count, plural, =1{Esta operación no está disponible para un elemento del siguiente tipo: {types}.} other{Esta operación no está disponible para elementos de los siguientes tipos: {types}.}}", "nameConflictDialogSingleSourceMessage": "Algunos archivos en el directorio de destino tienen el mismo nombre.", @@ -195,7 +191,6 @@ "addShortcutDialogLabel": "Etiqueta del atajo", "addShortcutButtonLabel": "AGREGAR", - "noMatchingAppDialogTitle": "Sin aplicación compatible", "noMatchingAppDialogMessage": "No se encontraron aplicaciones para manejar esto.", "binEntriesConfirmationDialogMessage": "{count, plural, =1{¿Mover este elemento al cesto de basura?} other{¿Mover estos {count} elementos al cesto de basura?}}", @@ -224,7 +219,7 @@ "renameEntrySetPageTitle": "Renombrar", "renameEntrySetPagePatternFieldLabel": "Patrón de nombramiento", "renameEntrySetPageInsertTooltip": "Insertar campo", - "renameEntrySetPagePreview": "Vista previa", + "renameEntrySetPagePreviewSectionTitle": "Vista previa", "renameProcessorCounter": "Contador", "renameProcessorName": "Nombre", @@ -285,9 +280,9 @@ "menuActionSlideshow": "Presentación", "menuActionStats": "Estadísticas", - "viewDialogTabSort": "Ordenar", - "viewDialogTabGroup": "Grupo", - "viewDialogTabLayout": "Disposición", + "viewDialogSortSectionTitle": "Ordenar", + "viewDialogGroupSectionTitle": "Grupo", + "viewDialogLayoutSectionTitle": "Disposición", "tileLayoutGrid": "Cuadrícula", "tileLayoutList": "Lista", @@ -304,24 +299,24 @@ "aboutLinkLicense": "Licencia", "aboutLinkPolicy": "Política de privacidad", - "aboutBug": "Reporte de errores", + "aboutBugSectionTitle": "Reporte de errores", "aboutBugSaveLogInstruction": "Guardar registros de la aplicación a un archivo", "aboutBugCopyInfoInstruction": "Copiar información del sistema", "aboutBugCopyInfoButton": "Copiar", "aboutBugReportInstruction": "Reportar en GitHub con los registros y la información del sistema", "aboutBugReportButton": "Reportar", - "aboutCredits": "Créditos", + "aboutCreditsSectionTitle": "Créditos", "aboutCreditsWorldAtlas1": "Esta aplicación usa un archivo TopoJSON de", "aboutCreditsWorldAtlas2": "bajo licencia ISC.", - "aboutCreditsTranslators": "Traductores:", + "aboutTranslatorsSectionTitle": "Traductores:", - "aboutLicenses": "Licencias de código abierto", + "aboutLicensesSectionTitle": "Licencias de código abierto", "aboutLicensesBanner": "Esta aplicación usa los siguientes paquetes y librerías de código abierto.", - "aboutLicensesAndroidLibraries": "Librerías de Android", - "aboutLicensesFlutterPlugins": "Añadidos de Flutter", - "aboutLicensesFlutterPackages": "Paquetes de Flutter", - "aboutLicensesDartPackages": "Paquetes de Dart", + "aboutLicensesAndroidLibrariesSectionTitle": "Librerías de Android", + "aboutLicensesFlutterPluginsSectionTitle": "Añadidos de Flutter", + "aboutLicensesFlutterPackagesSectionTitle": "Paquetes de Flutter", + "aboutLicensesDartPackagesSectionTitle": "Paquetes de Dart", "aboutLicensesShowAllButtonLabel": "Mostrar todas las licencias", "policyPageTitle": "Política de privacidad", @@ -341,11 +336,6 @@ "collectionSearchTitlesHintText": "Buscar títulos", - "collectionSortDate": "Por fecha", - "collectionSortSize": "Por tamaño", - "collectionSortName": "Por nombre de álbum y archivo", - "collectionSortRating": "Por clasificación", - "collectionGroupAlbum": "Por álbum", "collectionGroupMonth": "Por mes", "collectionGroupDay": "Por día", @@ -374,6 +364,8 @@ "collectionSelectSectionTooltip": "Seleccionar sección", "collectionDeselectSectionTooltip": "Deseleccionar sección", + "drawerAboutButton": "Acerca de", + "drawerSettingsButton": "Ajustes", "drawerCollectionAll": "Toda la colección", "drawerCollectionFavourites": "Favoritos", "drawerCollectionImages": "Imágenes", @@ -383,10 +375,16 @@ "drawerCollectionPanoramas": "Panorámicas", "drawerCollectionRaws": "Fotos Raw", "drawerCollectionSphericalVideos": "Videos en 360°", + "drawerAlbumPage": "Álbumes", + "drawerCountryPage": "Países", + "drawerTagPage": "Etiquetas", - "chipSortDate": "Por fecha", - "chipSortName": "Por nombre", - "chipSortCount": "Por número de elementos", + "sortByDate": "Por fecha", + "sortByName": "Por nombre", + "sortByItemCount": "Por número de elementos", + "sortBySize": "Por tamaño", + "sortByAlbumFileName": "Por nombre de álbum y archivo", + "sortByRating": "Por clasificación", "albumGroupTier": "Por nivel", "albumGroupVolume": "Por volumen de almacenamiento", @@ -418,13 +416,13 @@ "binPageTitle": "Cesto de basura", "searchCollectionFieldHint": "Buscar en colección", - "searchSectionRecent": "Reciente", - "searchSectionDate": "Fecha", - "searchSectionAlbums": "Álbumes", - "searchSectionCountries": "Países", - "searchSectionPlaces": "Lugares", - "searchSectionTags": "Etiquetas", - "searchSectionRating": "Clasificaciones", + "searchRecentSectionTitle": "Reciente", + "searchDateSectionTitle": "Fecha", + "searchAlbumsSectionTitle": "Álbumes", + "searchCountriesSectionTitle": "Países", + "searchPlacesSectionTitle": "Lugares", + "searchTagsSectionTitle": "Etiquetas", + "searchRatingSectionTitle": "Clasificaciones", "settingsPageTitle": "Ajustes", "settingsSystemDefault": "Sistema", @@ -433,36 +431,39 @@ "settingsSearchFieldLabel": "Buscar ajustes", "settingsSearchEmpty": "Sin coincidencias", "settingsActionExport": "Exportar", + "settingsActionExportDialogTitle": "Exportar", "settingsActionImport": "Importar", + "settingsActionImportDialogTitle": "Importar", "appExportCovers": "Carátulas", "appExportFavourites": "Favoritos", "appExportSettings": "Ajustes", - "settingsSectionNavigation": "Navegación", - "settingsHome": "Inicio", + "settingsNavigationSectionTitle": "Navegación", + "settingsHomeTile": "Inicio", + "settingsHomeDialogTitle": "Inicio", "settingsShowBottomNavigationBar": "Mostrar barra de navegación inferior", "settingsKeepScreenOnTile": "Mantener pantalla encendida", - "settingsKeepScreenOnTitle": "Mantener pantalla encendida", + "settingsKeepScreenOnDialogTitle": "Mantener pantalla encendida", "settingsDoubleBackExit": "Presione «atrás» dos veces para salir", - "settingsConfirmationDialogTile": "Diálogos de confirmación", + "settingsConfirmationTile": "Diálogos de confirmación", "settingsConfirmationDialogTitle": "Diálogos de confirmación", - "settingsConfirmationDialogDeleteItems": "Preguntar antes de eliminar elementos permanentemente", - "settingsConfirmationDialogMoveToBinItems": "Preguntar antes de mover elementos al cesto de basura", - "settingsConfirmationDialogMoveUndatedItems": "Preguntar antes de mover elementos sin una fecha de metadatos", + "settingsConfirmationBeforeDeleteItems": "Preguntar antes de eliminar elementos permanentemente", + "settingsConfirmationBeforeMoveToBinItems": "Preguntar antes de mover elementos al cesto de basura", + "settingsConfirmationBeforeMoveUndatedItems": "Preguntar antes de mover elementos sin una fecha de metadatos", "settingsNavigationDrawerTile": "Menú de navegación", - "settingsNavigationDrawerEditorTitle": "Menú de navegación", + "settingsNavigationDrawerEditorPageTitle": "Menú de navegación", "settingsNavigationDrawerBanner": "Toque y mantenga para mover y reordenar elementos del menú.", "settingsNavigationDrawerTabTypes": "Tipos", "settingsNavigationDrawerTabAlbums": "Álbumes", "settingsNavigationDrawerTabPages": "Páginas", "settingsNavigationDrawerAddAlbum": "Agregar álbum", - "settingsSectionThumbnails": "Miniaturas", + "settingsThumbnailSectionTitle": "Miniaturas", "settingsThumbnailOverlayTile": "Incrustaciones", - "settingsThumbnailOverlayTitle": "Incrustaciones", + "settingsThumbnailOverlayPageTitle": "Incrustaciones", "settingsThumbnailShowFavouriteIcon": "Mostrar icono de favoritos", "settingsThumbnailShowTagIcon": "Mostrar ícono de etiqueta", "settingsThumbnailShowLocationIcon": "Mostrar icono de ubicación", @@ -472,13 +473,13 @@ "settingsThumbnailShowVideoDuration": "Mostrar duración de video", "settingsCollectionQuickActionsTile": "Acciones rápidas", - "settingsCollectionQuickActionEditorTitle": "Acciones rápidas", + "settingsCollectionQuickActionEditorPageTitle": "Acciones rápidas", "settingsCollectionQuickActionTabBrowsing": "Búsqueda", "settingsCollectionQuickActionTabSelecting": "Selección", "settingsCollectionBrowsingQuickActionEditorBanner": "Toque y mantenga para mover botones y seleccionar cuáles acciones se muestran mientras busca elementos.", "settingsCollectionSelectionQuickActionEditorBanner": "Toque y mantenga para mover botones y seleccionar cuáles acciones se muestran mientras selecciona elementos.", - "settingsSectionViewer": "Visor", + "settingsViewerSectionTitle": "Visor", "settingsViewerGestureSideTapNext": "Toque en los bordes de la pantalla para mostrar el elemento anterior/siguiente", "settingsViewerUseCutout": "Usar área recortada", "settingsViewerMaximumBrightness": "Brillo máximo", @@ -486,14 +487,14 @@ "settingsImageBackground": "Imagen de fondo", "settingsViewerQuickActionsTile": "Acciones rápidas", - "settingsViewerQuickActionEditorTitle": "Acciones rápidas", + "settingsViewerQuickActionEditorPageTitle": "Acciones rápidas", "settingsViewerQuickActionEditorBanner": "Toque y mantenga para mover botones y seleccionar cuáles acciones se muestran en el visor.", - "settingsViewerQuickActionEditorDisplayedButtons": "Botones mostrados", - "settingsViewerQuickActionEditorAvailableButtons": "Botones disponibles", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Botones mostrados", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Botones disponibles", "settingsViewerQuickActionEmpty": "Sin botones", "settingsViewerOverlayTile": "Incrustaciones", - "settingsViewerOverlayTitle": "Incrustaciones", + "settingsViewerOverlayPageTitle": "Incrustaciones", "settingsViewerShowOverlayOnOpening": "Mostrar durante apertura", "settingsViewerShowMinimap": "Mostrar mapa en miniatura", "settingsViewerShowInformation": "Mostrar información", @@ -503,30 +504,30 @@ "settingsViewerEnableOverlayBlurEffect": "Efecto de difuminado", "settingsViewerSlideshowTile": "Presentación", - "settingsViewerSlideshowTitle": "Presentación", + "settingsViewerSlideshowPageTitle": "Presentación", "settingsSlideshowRepeat": "Repetir", "settingsSlideshowShuffle": "Mezclar", "settingsSlideshowFillScreen": "Llenar pantalla", "settingsSlideshowTransitionTile": "Transición", - "settingsSlideshowTransitionTitle": "Transición", + "settingsSlideshowTransitionDialogTitle": "Transición", "settingsSlideshowIntervalTile": "Intervalo", - "settingsSlideshowIntervalTitle": "Intervalo", + "settingsSlideshowIntervalDialogTitle": "Intervalo", "settingsSlideshowVideoPlaybackTile": "Reproducción de video", - "settingsSlideshowVideoPlaybackTitle": "Reproducción de video", + "settingsSlideshowVideoPlaybackDialogTitle": "Reproducción de video", "settingsVideoPageTitle": "Ajustes de video", - "settingsSectionVideo": "Video", + "settingsVideoSectionTitle": "Video", "settingsVideoShowVideos": "Mostrar videos", "settingsVideoEnableHardwareAcceleration": "Aceleración por hardware", "settingsVideoEnableAutoPlay": "Reproducción automática", "settingsVideoLoopModeTile": "Modo bucle", - "settingsVideoLoopModeTitle": "Modo bucle", + "settingsVideoLoopModeDialogTitle": "Modo bucle", "settingsSubtitleThemeTile": "Subtítulos", - "settingsSubtitleThemeTitle": "Subtítulos", + "settingsSubtitleThemePageTitle": "Subtítulos", "settingsSubtitleThemeSample": "Esto es un ejemplo.", "settingsSubtitleThemeTextAlignmentTile": "Alineación de texto", - "settingsSubtitleThemeTextAlignmentTitle": "Alineación de texto", + "settingsSubtitleThemeTextAlignmentDialogTitle": "Alineación de texto", "settingsSubtitleThemeTextSize": "Tamaño de texto", "settingsSubtitleThemeShowOutline": "Mostrar contorno y sombra", "settingsSubtitleThemeTextColor": "Color de texto", @@ -538,13 +539,13 @@ "settingsSubtitleThemeTextAlignmentRight": "Derecha", "settingsVideoControlsTile": "Controles", - "settingsVideoControlsTitle": "Controles", + "settingsVideoControlsPageTitle": "Controles", "settingsVideoButtonsTile": "Botones", - "settingsVideoButtonsTitle": "Botones", + "settingsVideoButtonsDialogTitle": "Botones", "settingsVideoGestureDoubleTapTogglePlay": "Doble toque para reproducir/pausar", "settingsVideoGestureSideDoubleTapSeek": "Doble toque en los bordes de la pantalla para buscar atrás/adelante", - "settingsSectionPrivacy": "Privacidad", + "settingsPrivacySectionTitle": "Privacidad", "settingsAllowInstalledAppAccess": "Permita el acceso a lista de aplicaciones", "settingsAllowInstalledAppAccessSubtitle": "Usado para mejorar los álbumes mostrados", "settingsAllowErrorReporting": "Permitir reporte de errores anónimo", @@ -553,52 +554,56 @@ "settingsEnableBinSubtitle": "Guardar los elementos eliminados por 30 días", "settingsHiddenItemsTile": "Elementos ocultos", - "settingsHiddenItemsTitle": "Elementos ocultos", + "settingsHiddenItemsPageTitle": "Elementos ocultos", - "settingsHiddenFiltersTitle": "Filtros", + "settingsHiddenItemsTabFilters": "Filtros", "settingsHiddenFiltersBanner": "Fotos y videos que concuerden con los filtros no aparecerán en su colección.", "settingsHiddenFiltersEmpty": "Sin filtros", - "settingsHiddenPathsTitle": "Ubicaciones ocultas", + "settingsHiddenItemsTabPaths": "Ubicaciones ocultas", "settingsHiddenPathsBanner": "Fotos y videos que se encuentren en estos directorios y cualquiera de sus subdirectorios no aparecerán en su colección.", "addPathTooltip": "Añadir ubicación", "settingsStorageAccessTile": "Acceso al almacenamiento", - "settingsStorageAccessTitle": "Acceso al almacenamiento", + "settingsStorageAccessPageTitle": "Acceso al almacenamiento", "settingsStorageAccessBanner": "Algunos directorios requieren un permiso de acceso explícito para que sea posible modificar los archivos que contienen. Puede revisar los directorios con permiso aquí.", "settingsStorageAccessEmpty": "Sin permisos de acceso", "settingsStorageAccessRevokeTooltip": "Revocar", - "settingsSectionAccessibility": "Accesibilidad", + "settingsAccessibilitySectionTitle": "Accesibilidad", "settingsRemoveAnimationsTile": "Remover animaciones", - "settingsRemoveAnimationsTitle": "Remover animaciones", + "settingsRemoveAnimationsDialogTitle": "Remover animaciones", "settingsTimeToTakeActionTile": "Retraso para ejecutar una acción", - "settingsTimeToTakeActionTitle": "Retraso para ejecutar una acción", + "settingsTimeToTakeActionDialogTitle": "Retraso para ejecutar una acción", - "settingsSectionDisplay": "Pantalla", - "settingsThemeBrightness": "Tema", + "settingsDisplaySectionTitle": "Pantalla", + "settingsThemeBrightnessTile": "Tema", + "settingsThemeBrightnessDialogTitle": "Tema", "settingsThemeColorHighlights": "Acentos de color", "settingsThemeEnableDynamicColor": "Color dinámico", "settingsDisplayRefreshRateModeTile": "Tasa de refresco de la pantalla", - "settingsDisplayRefreshRateModeTitle": "Tasa de refresco", + "settingsDisplayRefreshRateModeDialogTitle": "Tasa de refresco", - "settingsSectionLanguage": "Idioma y formatos", - "settingsLanguage": "Idioma", + "settingsLanguageSectionTitle": "Idioma y formatos", + "settingsLanguageTile": "Idioma", + "settingsLanguagePageTitle": "Idioma", "settingsCoordinateFormatTile": "Formato de coordenadas", - "settingsCoordinateFormatTitle": "Formato de coordenadas", + "settingsCoordinateFormatDialogTitle": "Formato de coordenadas", "settingsUnitSystemTile": "Unidades", - "settingsUnitSystemTitle": "Unidades", + "settingsUnitSystemDialogTitle": "Unidades", "settingsScreenSaverPageTitle": "Protector de pantalla", "settingsWidgetPageTitle": "Marco de foto", "settingsWidgetShowOutline": "Borde", + "settingsCollectionTile": "Colección", + "statsPageTitle": "Stats", "statsWithGps": "{count, plural, =1{1 elemento con ubicación} other{{count} elementos con ubicación}}", - "statsTopCountries": "Países principales", - "statsTopPlaces": "Lugares principales", - "statsTopTags": "Etiquetas principales", + "statsTopCountriesSectionTitle": "Países principales", + "statsTopPlacesSectionTitle": "Lugares principales", + "statsTopTagsSectionTitle": "Etiquetas principales", "viewerOpenPanoramaButtonLabel": "ABRIR PANORÁMICA", "viewerSetWallpaperButtonLabel": "ESTABLECER FONDO", @@ -620,7 +625,7 @@ "viewerInfoLabelCoordinates": "Coordinadas", "viewerInfoLabelAddress": "Dirección", - "mapStyleTitle": "Estilo de mapa", + "mapStyleDialogTitle": "Estilo de mapa", "mapStyleTooltip": "Selección de estilo de mapa", "mapZoomInTooltip": "Acercar", "mapZoomOutTooltip": "Alejar", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 5a91e964c..67fff4117 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -27,6 +27,7 @@ "actionRemove": "Supprimer", "resetTooltip": "Réinitialiser", "saveTooltip": "Sauvegarder", + "pickTooltip": "Sélectionner", "doubleBackExitMessage": "Pressez «\u00A0retour\u00A0» à nouveau pour quitter.", "doNotAskAgain": "Ne pas demander de nouveau", @@ -87,18 +88,20 @@ "entryInfoActionEditDate": "Modifier la date", "entryInfoActionEditLocation": "Modifier le lieu", - "entryInfoActionEditDescription": "Modifier la description", + "entryInfoActionEditTitleDescription": "Modifier titre et description", "entryInfoActionEditRating": "Modifier la notation", "entryInfoActionEditTags": "Modifier les libellés", "entryInfoActionRemoveMetadata": "Retirer les métadonnées", "filterBinLabel": "Corbeille", "filterFavouriteLabel": "Favori", - "filterLocationEmptyLabel": "Sans lieu", - "filterTagEmptyLabel": "Sans libellé", + "filterNoDateLabel": "Sans date", + "filterNoLocationLabel": "Sans lieu", + "filterNoRatingLabel": "Sans notation", + "filterNoTagLabel": "Sans libellé", + "filterNoTitleLabel": "Sans titre", "filterOnThisDayLabel": "Ce jour-là", "filterRecentlyAddedLabel": "Ajouté récemment", - "filterRatingUnratedLabel": "Sans notation", "filterRatingRejectedLabel": "Rejeté", "filterTypeAnimatedLabel": "Animation", "filterTypeMotionPhotoLabel": "Photo animée", @@ -179,16 +182,11 @@ "storageVolumeDescriptionFallbackNonPrimary": "Carte SD", "rootDirectoryDescription": "dossier racine", "otherDirectoryDescription": "dossier «\u00A0{name}\u00A0»", - "storageAccessDialogTitle": "Accès au dossier", "storageAccessDialogMessage": "Veuillez sélectionner le {directory} de «\u00A0{volume}\u00A0» à l’écran suivant, pour que l’app puisse modifier son contenu.", - "restrictedAccessDialogTitle": "Accès restreint", "restrictedAccessDialogMessage": "Cette app ne peut pas modifier les fichiers du {directory} de «\u00A0{volume}\u00A0».\n\nVeuillez utiliser une app pré-installée pour déplacer les fichiers vers un autre dossier.", - "notEnoughSpaceDialogTitle": "Espace insuffisant", "notEnoughSpaceDialogMessage": "Cette opération nécessite {neededSize} d’espace disponible sur «\u00A0{volume}\u00A0», mais il ne reste que {freeSize}.", - "missingSystemFilePickerDialogTitle": "Sélecteur de fichiers désactivé", "missingSystemFilePickerDialogMessage": "Le sélecteur de fichiers du système est absent ou désactivé. Veuillez le réactiver et réessayer.", - "unsupportedTypeDialogTitle": "Formats non supportés", "unsupportedTypeDialogMessage": "{count, plural, =1{Cette opération n’est pas disponible pour les fichiers au format suivant : {types}.} other{Cette opération n’est pas disponible pour les fichiers aux formats suivants : {types}.}}", "nameConflictDialogSingleSourceMessage": "Certains fichiers dans le dossier de destination ont le même nom.", @@ -197,7 +195,6 @@ "addShortcutDialogLabel": "Nom du raccourci", "addShortcutButtonLabel": "AJOUTER", - "noMatchingAppDialogTitle": "App indisponible", "noMatchingAppDialogMessage": "Aucune app ne peut effectuer cette opération.", "binEntriesConfirmationDialogMessage": "{count, plural, =1{Mettre cet élément à la corbeille ?} other{Mettre ces {count} éléments à la corbeille ?}}", @@ -226,7 +223,7 @@ "renameEntrySetPageTitle": "Renommage", "renameEntrySetPagePatternFieldLabel": "Modèle de nommage", "renameEntrySetPageInsertTooltip": "Ajouter un champ", - "renameEntrySetPagePreview": "Aperçu", + "renameEntrySetPagePreviewSectionTitle": "Aperçu", "renameProcessorCounter": "Compteur", "renameProcessorName": "Nom", @@ -259,8 +256,6 @@ "locationPickerUseThisLocationButton": "Utiliser ce lieu", - "editEntryDescriptionDialogTitle": "Description", - "editEntryRatingDialogTitle": "Notation", "removeEntryMetadataDialogTitle": "Retrait de métadonnées", @@ -289,9 +284,10 @@ "menuActionSlideshow": "Diaporama", "menuActionStats": "Statistiques", - "viewDialogTabSort": "Tri", - "viewDialogTabGroup": "Groupes", - "viewDialogTabLayout": "Vue", + "viewDialogSortSectionTitle": "Tri", + "viewDialogGroupSectionTitle": "Groupes", + "viewDialogLayoutSectionTitle": "Vue", + "viewDialogReverseSortOrder": "Inverser l’ordre", "tileLayoutGrid": "Grille", "tileLayoutList": "Liste", @@ -308,24 +304,24 @@ "aboutLinkLicense": "Licence", "aboutLinkPolicy": "Politique de confidentialité", - "aboutBug": "Rapports d’erreur", + "aboutBugSectionTitle": "Rapports d’erreur", "aboutBugSaveLogInstruction": "Sauvegarder les logs de l’app vers un fichier", "aboutBugCopyInfoInstruction": "Copier les informations d’environnement", "aboutBugCopyInfoButton": "Copier", "aboutBugReportInstruction": "Créer une «\u00A0issue\u00A0» sur GitHub en attachant les logs et informations d’environnement", "aboutBugReportButton": "Créer", - "aboutCredits": "Remerciements", + "aboutCreditsSectionTitle": "Remerciements", "aboutCreditsWorldAtlas1": "Cette app utilise un fichier TopoJSON de ", "aboutCreditsWorldAtlas2": "sous licence ISC.", - "aboutCreditsTranslators": "Traducteurs", + "aboutTranslatorsSectionTitle": "Traducteurs", - "aboutLicenses": "Licences open-source", + "aboutLicensesSectionTitle": "Licences open-source", "aboutLicensesBanner": "Cette app utilise ces librairies et packages open-source.", - "aboutLicensesAndroidLibraries": "Librairies Android", - "aboutLicensesFlutterPlugins": "Plugins Flutter", - "aboutLicensesFlutterPackages": "Packages Flutter", - "aboutLicensesDartPackages": "Packages Dart", + "aboutLicensesAndroidLibrariesSectionTitle": "Librairies Android", + "aboutLicensesFlutterPluginsSectionTitle": "Plugins Flutter", + "aboutLicensesFlutterPackagesSectionTitle": "Packages Flutter", + "aboutLicensesDartPackagesSectionTitle": "Packages Dart", "aboutLicensesShowAllButtonLabel": "Afficher toutes les licences", "policyPageTitle": "Politique de confidentialité", @@ -345,11 +341,6 @@ "collectionSearchTitlesHintText": "Recherche de titres", - "collectionSortDate": "par date", - "collectionSortSize": "par taille", - "collectionSortName": "alphabétique", - "collectionSortRating": "par notation", - "collectionGroupAlbum": "par album", "collectionGroupMonth": "par mois", "collectionGroupDay": "par jour", @@ -378,6 +369,8 @@ "collectionSelectSectionTooltip": "Sélectionner la section", "collectionDeselectSectionTooltip": "Désélectionner la section", + "drawerAboutButton": "À propos", + "drawerSettingsButton": "Réglages", "drawerCollectionAll": "Toute la collection", "drawerCollectionFavourites": "Favoris", "drawerCollectionImages": "Images", @@ -387,10 +380,25 @@ "drawerCollectionPanoramas": "Panoramas", "drawerCollectionRaws": "Photos Raw", "drawerCollectionSphericalVideos": "Vidéos à 360°", + "drawerAlbumPage": "Albums", + "drawerCountryPage": "Pays", + "drawerTagPage": "Libellés", - "chipSortDate": "par date", - "chipSortName": "alphabétique", - "chipSortCount": "par nombre d’éléments", + "sortByDate": "par date", + "sortByName": "alphabétique", + "sortByItemCount": "par nombre d’éléments", + "sortBySize": "par taille", + "sortByAlbumFileName": "alphabétique", + "sortByRating": "par notation", + + "sortOrderNewestFirst": "Plus récents d’abord", + "sortOrderOldestFirst": "Plus anciens d’abord", + "sortOrderAtoZ": "De A à Z", + "sortOrderZtoA": "De Z à A", + "sortOrderHighestFirst": "Meilleurs d’abord", + "sortOrderLowestFirst": "Moins bons d’abord", + "sortOrderLargestFirst": "Plus larges d’abord", + "sortOrderSmallestFirst": "Moins larges d’abord", "albumGroupTier": "par importance", "albumGroupVolume": "par volume de stockage", @@ -422,13 +430,14 @@ "binPageTitle": "Corbeille", "searchCollectionFieldHint": "Recherche", - "searchSectionRecent": "Historique", - "searchSectionDate": "Date", - "searchSectionAlbums": "Albums", - "searchSectionCountries": "Pays", - "searchSectionPlaces": "Lieux", - "searchSectionTags": "Libellés", - "searchSectionRating": "Notations", + "searchRecentSectionTitle": "Historique", + "searchDateSectionTitle": "Date", + "searchAlbumsSectionTitle": "Albums", + "searchCountriesSectionTitle": "Pays", + "searchPlacesSectionTitle": "Lieux", + "searchTagsSectionTitle": "Libellés", + "searchRatingSectionTitle": "Notations", + "searchMetadataSectionTitle": "Métadonnées", "settingsPageTitle": "Réglages", "settingsSystemDefault": "Système", @@ -437,37 +446,40 @@ "settingsSearchFieldLabel": "Recherche de réglages", "settingsSearchEmpty": "Aucun réglage correspondant", "settingsActionExport": "Exporter", + "settingsActionExportDialogTitle": "Exporter", "settingsActionImport": "Importer", + "settingsActionImportDialogTitle": "Importer", "appExportCovers": "Couvertures", "appExportFavourites": "Favoris", "appExportSettings": "Réglages", - "settingsSectionNavigation": "Navigation", - "settingsHome": "Page d’accueil", + "settingsNavigationSectionTitle": "Navigation", + "settingsHomeTile": "Page d’accueil", + "settingsHomeDialogTitle": "Page d’accueil", "settingsShowBottomNavigationBar": "Afficher la barre de navigation", "settingsKeepScreenOnTile": "Maintenir l’écran allumé", - "settingsKeepScreenOnTitle": "Allumage de l’écran", + "settingsKeepScreenOnDialogTitle": "Allumage de l’écran", "settingsDoubleBackExit": "Presser «\u00A0retour\u00A0» 2 fois pour quitter", - "settingsConfirmationDialogTile": "Demandes de confirmation", + "settingsConfirmationTile": "Demandes de confirmation", "settingsConfirmationDialogTitle": "Demandes de confirmation", - "settingsConfirmationDialogDeleteItems": "Suppression définitive d’éléments", - "settingsConfirmationDialogMoveToBinItems": "Mise d’éléments à la corbeille", - "settingsConfirmationDialogMoveUndatedItems": "Déplacement d’éléments non datés", + "settingsConfirmationBeforeDeleteItems": "Suppression définitive d’éléments", + "settingsConfirmationBeforeMoveToBinItems": "Mise d’éléments à la corbeille", + "settingsConfirmationBeforeMoveUndatedItems": "Déplacement d’éléments non datés", "settingsConfirmationAfterMoveToBinItems": "Confirmation après mise d’éléments à la corbeille", "settingsNavigationDrawerTile": "Menu de navigation", - "settingsNavigationDrawerEditorTitle": "Menu de navigation", + "settingsNavigationDrawerEditorPageTitle": "Menu de navigation", "settingsNavigationDrawerBanner": "Maintenez votre doigt appuyé pour déplacer et réorganiser les éléments de menu.", "settingsNavigationDrawerTabTypes": "Types", "settingsNavigationDrawerTabAlbums": "Albums", "settingsNavigationDrawerTabPages": "Pages", "settingsNavigationDrawerAddAlbum": "Ajouter un album", - "settingsSectionThumbnails": "Vignettes", + "settingsThumbnailSectionTitle": "Vignettes", "settingsThumbnailOverlayTile": "Incrustations", - "settingsThumbnailOverlayTitle": "Incrustations", + "settingsThumbnailOverlayPageTitle": "Incrustations", "settingsThumbnailShowFavouriteIcon": "Afficher l’icône de favori", "settingsThumbnailShowTagIcon": "Afficher l’icône de libellé", "settingsThumbnailShowLocationIcon": "Afficher l’icône de lieu", @@ -477,13 +489,13 @@ "settingsThumbnailShowVideoDuration": "Afficher la durée de la vidéo", "settingsCollectionQuickActionsTile": "Actions rapides", - "settingsCollectionQuickActionEditorTitle": "Actions rapides", + "settingsCollectionQuickActionEditorPageTitle": "Actions rapides", "settingsCollectionQuickActionTabBrowsing": "Navigation", "settingsCollectionQuickActionTabSelecting": "Sélection", "settingsCollectionBrowsingQuickActionEditorBanner": "Maintenez votre doigt appuyé pour déplacer les boutons et choisir les actions affichées lors de la navigation.", "settingsCollectionSelectionQuickActionEditorBanner": "Maintenez votre doigt appuyé pour déplacer les boutons et choisir les actions affichées lors de la sélection d’éléments.", - "settingsSectionViewer": "Visionneuse", + "settingsViewerSectionTitle": "Visionneuse", "settingsViewerGestureSideTapNext": "Appuyer sur les bords de l’écran pour passer à l’élément précédent/suivant", "settingsViewerUseCutout": "Utiliser la zone d’encoche", "settingsViewerMaximumBrightness": "Luminosité maximale", @@ -491,14 +503,14 @@ "settingsImageBackground": "Arrière-plan de l’image", "settingsViewerQuickActionsTile": "Actions rapides", - "settingsViewerQuickActionEditorTitle": "Actions rapides", + "settingsViewerQuickActionEditorPageTitle": "Actions rapides", "settingsViewerQuickActionEditorBanner": "Maintenez votre doigt appuyé pour déplacer les boutons et choisir les actions affichées sur la visionneuse.", - "settingsViewerQuickActionEditorDisplayedButtons": "Boutons affichés", - "settingsViewerQuickActionEditorAvailableButtons": "Boutons disponibles", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Boutons affichés", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Boutons disponibles", "settingsViewerQuickActionEmpty": "Aucun bouton", "settingsViewerOverlayTile": "Incrustations", - "settingsViewerOverlayTitle": "Incrustations", + "settingsViewerOverlayPageTitle": "Incrustations", "settingsViewerShowOverlayOnOpening": "Afficher à l’ouverture", "settingsViewerShowMinimap": "Afficher la mini-carte", "settingsViewerShowInformation": "Afficher les détails", @@ -508,30 +520,30 @@ "settingsViewerEnableOverlayBlurEffect": "Effets de flou", "settingsViewerSlideshowTile": "Diaporama", - "settingsViewerSlideshowTitle": "Diaporama", + "settingsViewerSlideshowPageTitle": "Diaporama", "settingsSlideshowRepeat": "Répéter", "settingsSlideshowShuffle": "Aléatoire", "settingsSlideshowFillScreen": "Remplir l’écran", "settingsSlideshowTransitionTile": "Transition", - "settingsSlideshowTransitionTitle": "Transition", + "settingsSlideshowTransitionDialogTitle": "Transition", "settingsSlideshowIntervalTile": "Intervalle", - "settingsSlideshowIntervalTitle": "Intervalle", + "settingsSlideshowIntervalDialogTitle": "Intervalle", "settingsSlideshowVideoPlaybackTile": "Lecture de vidéos", - "settingsSlideshowVideoPlaybackTitle": "Lecture de vidéos", + "settingsSlideshowVideoPlaybackDialogTitle": "Lecture de vidéos", "settingsVideoPageTitle": "Réglages vidéo", - "settingsSectionVideo": "Vidéo", + "settingsVideoSectionTitle": "Vidéo", "settingsVideoShowVideos": "Afficher les vidéos", "settingsVideoEnableHardwareAcceleration": "Accélération matérielle", "settingsVideoEnableAutoPlay": "Lecture automatique", "settingsVideoLoopModeTile": "Lecture répétée", - "settingsVideoLoopModeTitle": "Lecture répétée", + "settingsVideoLoopModeDialogTitle": "Lecture répétée", "settingsSubtitleThemeTile": "Sous-titres", - "settingsSubtitleThemeTitle": "Sous-titres", + "settingsSubtitleThemePageTitle": "Sous-titres", "settingsSubtitleThemeSample": "Ceci est un exemple.", "settingsSubtitleThemeTextAlignmentTile": "Alignement du texte", - "settingsSubtitleThemeTextAlignmentTitle": "Alignement du texte", + "settingsSubtitleThemeTextAlignmentDialogTitle": "Alignement du texte", "settingsSubtitleThemeTextSize": "Taille du texte", "settingsSubtitleThemeShowOutline": "Afficher les contours et ombres", "settingsSubtitleThemeTextColor": "Couleur du texte", @@ -543,13 +555,13 @@ "settingsSubtitleThemeTextAlignmentRight": "droite", "settingsVideoControlsTile": "Contrôles", - "settingsVideoControlsTitle": "Contrôles", + "settingsVideoControlsPageTitle": "Contrôles", "settingsVideoButtonsTile": "Boutons", - "settingsVideoButtonsTitle": "Boutons", + "settingsVideoButtonsDialogTitle": "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é", + "settingsPrivacySectionTitle": "Confidentialité", "settingsAllowInstalledAppAccess": "Autoriser l’accès à l’inventaire des apps", "settingsAllowInstalledAppAccessSubtitle": "Pour un affichage amélioré des albums", "settingsAllowErrorReporting": "Autoriser l’envoi de rapports d’erreur", @@ -558,52 +570,56 @@ "settingsEnableBinSubtitle": "Conserver les éléments supprimés pendant 30 jours", "settingsHiddenItemsTile": "Éléments masqués", - "settingsHiddenItemsTitle": "Éléments masqués", + "settingsHiddenItemsPageTitle": "Éléments masqués", - "settingsHiddenFiltersTitle": "Filtres masqués", + "settingsHiddenItemsTabFilters": "Filtres masqués", "settingsHiddenFiltersBanner": "Les images et vidéos correspondantes aux filtres masqués n’apparaîtront pas dans votre collection.", "settingsHiddenFiltersEmpty": "Aucun filtre masqué", - "settingsHiddenPathsTitle": "Chemins masqués", + "settingsHiddenItemsTabPaths": "Chemins masqués", "settingsHiddenPathsBanner": "Les images et vidéos dans ces dossiers, ou leurs sous-dossiers, n’apparaîtront pas dans votre collection.", "addPathTooltip": "Ajouter un chemin", "settingsStorageAccessTile": "Accès au stockage", - "settingsStorageAccessTitle": "Accès au stockage", + "settingsStorageAccessPageTitle": "Accès au stockage", "settingsStorageAccessBanner": "Une autorisation d’accès au stockage est nécessaire pour modifier le contenu de certains dossiers. Voici la liste des autorisations couramment en vigueur.", "settingsStorageAccessEmpty": "Aucune autorisation d’accès", "settingsStorageAccessRevokeTooltip": "Retirer", - "settingsSectionAccessibility": "Accessibilité", + "settingsAccessibilitySectionTitle": "Accessibilité", "settingsRemoveAnimationsTile": "Suppression des animations", - "settingsRemoveAnimationsTitle": "Suppression des animations", + "settingsRemoveAnimationsDialogTitle": "Suppression des animations", "settingsTimeToTakeActionTile": "Délai pour effectuer une action", - "settingsTimeToTakeActionTitle": "Délai pour effectuer une action", + "settingsTimeToTakeActionDialogTitle": "Délai pour effectuer une action", - "settingsSectionDisplay": "Affichage", - "settingsThemeBrightness": "Thème", + "settingsDisplaySectionTitle": "Affichage", + "settingsThemeBrightnessTile": "Thème", + "settingsThemeBrightnessDialogTitle": "Thème", "settingsThemeColorHighlights": "Surlignages colorés", "settingsThemeEnableDynamicColor": "Couleur dynamique", "settingsDisplayRefreshRateModeTile": "Fréquence d’actualisation de l’écran", - "settingsDisplayRefreshRateModeTitle": "Fréquence d’actualisation", + "settingsDisplayRefreshRateModeDialogTitle": "Fréquence d’actualisation", - "settingsSectionLanguage": "Langue & Formats", - "settingsLanguage": "Langue", + "settingsLanguageSectionTitle": "Langue & Formats", + "settingsLanguageTile": "Langue", + "settingsLanguagePageTitle": "Langue", "settingsCoordinateFormatTile": "Format de coordonnées", - "settingsCoordinateFormatTitle": "Format de coordonnées", + "settingsCoordinateFormatDialogTitle": "Format de coordonnées", "settingsUnitSystemTile": "Unités", - "settingsUnitSystemTitle": "Unités", + "settingsUnitSystemDialogTitle": "Unités", "settingsScreenSaverPageTitle": "Économiseur d’écran", "settingsWidgetPageTitle": "Cadre photo", "settingsWidgetShowOutline": "Contours", + "settingsCollectionTile": "Collection", + "statsPageTitle": "Statistiques", "statsWithGps": "{count, plural, =1{1 élément localisé} other{{count} éléments localisés}}", - "statsTopCountries": "Top pays", - "statsTopPlaces": "Top lieux", - "statsTopTags": "Top libellés", + "statsTopCountriesSectionTitle": "Top pays", + "statsTopPlacesSectionTitle": "Top lieux", + "statsTopTagsSectionTitle": "Top libellés", "viewerOpenPanoramaButtonLabel": "OUVRIR LE PANORAMA", "viewerSetWallpaperButtonLabel": "APPLIQUER", @@ -614,6 +630,7 @@ "viewerInfoBackToViewerTooltip": "Retour à la visionneuse", "viewerInfoUnknown": "inconnu", + "viewerInfoLabelDescription": "Description", "viewerInfoLabelTitle": "Titre", "viewerInfoLabelDate": "Date", "viewerInfoLabelResolution": "Résolution", @@ -625,7 +642,7 @@ "viewerInfoLabelCoordinates": "Coordonnées", "viewerInfoLabelAddress": "Adresse", - "mapStyleTitle": "Type de carte", + "mapStyleDialogTitle": "Type de carte", "mapStyleTooltip": "Sélectionner le type de carte", "mapZoomInTooltip": "Zoomer", "mapZoomOutTooltip": "Dézoomer", diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index c39b2d872..50b866d50 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -27,6 +27,7 @@ "actionRemove": "Hapus", "resetTooltip": "Ulang", "saveTooltip": "Simpan", + "pickTooltip": "Pilih", "doubleBackExitMessage": "Ketuk “kembali” lagi untuk keluar.", "doNotAskAgain": "Jangan tanya lagi", @@ -87,16 +88,20 @@ "entryInfoActionEditDate": "Ubah tanggal & waktu", "entryInfoActionEditLocation": "Ubah lokasi", + "entryInfoActionEditTitleDescription": "Ubah judul & deskripsi", "entryInfoActionEditRating": "Ubah nilai", "entryInfoActionEditTags": "Ubah label", "entryInfoActionRemoveMetadata": "Hapus metadata", "filterBinLabel": "Tong sampah", "filterFavouriteLabel": "Favorit", - "filterLocationEmptyLabel": "Lokasi yang tidak ditemukan", - "filterTagEmptyLabel": "Tidak dilabel", + "filterNoDateLabel": "Tak ada tanggal", + "filterNoLocationLabel": "Lokasi yang tidak ditemukan", + "filterNoRatingLabel": "Belum diberi nilai", + "filterNoTagLabel": "Tidak dilabel", + "filterNoTitleLabel": "Tak ada judul", "filterOnThisDayLabel": "Di hari ini", - "filterRatingUnratedLabel": "Belum diberi nilai", + "filterRecentlyAddedLabel": "Baru-baru ini ditambahkan", "filterRatingRejectedLabel": "Ditolak", "filterTypeAnimatedLabel": "Teranimasi", "filterTypeMotionPhotoLabel": "Foto bergerak", @@ -177,16 +182,11 @@ "storageVolumeDescriptionFallbackNonPrimary": "kartu SD", "rootDirectoryDescription": "direktori root", "otherDirectoryDescription": "direktori “{name}”", - "storageAccessDialogTitle": "Akses Penyimpanan", "storageAccessDialogMessage": "Silahkan pilih {directory} dari “{volume}” di layar berikutnya untuk memberikan akses aplikasi ini ke sana.", - "restrictedAccessDialogTitle": "Akses Terbatas", "restrictedAccessDialogMessage": "Aplikasi ini tidak diizinkan untuk mengubah file di {directory} dari “{volume}”.\n\nSilahkan pakai aplikasi Manager File atau aplikasi gallery untuk gerakkan benda ke direktori lain.", - "notEnoughSpaceDialogTitle": "Tidak Cukup Ruang", "notEnoughSpaceDialogMessage": "Operasi ini memerlukan {neededSize} ruang kosong di “{volume}” untuk menyelesaikan, tetapi hanya ada {freeSize} tersisa.", - "missingSystemFilePickerDialogTitle": "Pemilih File Sistem Tidak Ada", "missingSystemFilePickerDialogMessage": "Pemilih file sistem tidak ada atau dinonaktifkan. Harap aktifkan dan coba lagi.", - "unsupportedTypeDialogTitle": "Tipe Yang Tidak Didukung", "unsupportedTypeDialogMessage": "{count, plural, other{Operasi ini tidak didukung untuk benda dari jenis berikut: {types}.}}", "nameConflictDialogSingleSourceMessage": "Beberapa file di folder tujuan memiliki nama yang sama.", @@ -195,7 +195,6 @@ "addShortcutDialogLabel": "Label pintasan", "addShortcutButtonLabel": "TAMBAH", - "noMatchingAppDialogTitle": "Tidak Ada Aplikasi Yang Cocok", "noMatchingAppDialogMessage": "Tidak ada aplikasi yang cocok untuk menangani ini.", "binEntriesConfirmationDialogMessage": "{count, plural, =1{Pindahkan benda ini ke tong sampah?} other{Pindahkan {count} benda ke tempat sampah?}}", @@ -224,7 +223,7 @@ "renameEntrySetPageTitle": "Ganti nama", "renameEntrySetPagePatternFieldLabel": "Pola penamaan", "renameEntrySetPageInsertTooltip": "Masukkan bidang", - "renameEntrySetPagePreview": "Pratinjau", + "renameEntrySetPagePreviewSectionTitle": "Pratinjau", "renameProcessorCounter": "Penghitungan", "renameProcessorName": "Nama", @@ -285,9 +284,10 @@ "menuActionSlideshow": "Tampilan slide", "menuActionStats": "Statistik", - "viewDialogTabSort": "Sortir", - "viewDialogTabGroup": "Grup", - "viewDialogTabLayout": "Tata letak", + "viewDialogSortSectionTitle": "Sortir", + "viewDialogGroupSectionTitle": "Grup", + "viewDialogLayoutSectionTitle": "Tata letak", + "viewDialogReverseSortOrder": "Urutan pengurutan terbalik", "tileLayoutGrid": "Grid", "tileLayoutList": "Daftar", @@ -304,24 +304,24 @@ "aboutLinkLicense": "Lisensi", "aboutLinkPolicy": "Aturan Privasi", - "aboutBug": "Lapor Bug", + "aboutBugSectionTitle": "Lapor Bug", "aboutBugSaveLogInstruction": "Simpan log aplikasi ke file", "aboutBugCopyInfoInstruction": "Salin informasi sistem", "aboutBugCopyInfoButton": "Salin", "aboutBugReportInstruction": "Laporkan ke GitHub dengan log dan informasi sistem", "aboutBugReportButton": "Rapor", - "aboutCredits": "Kredit", + "aboutCreditsSectionTitle": "Kredit", "aboutCreditsWorldAtlas1": "Aplikasi ini menggunakan file TopoJSON dari", "aboutCreditsWorldAtlas2": "dibawah Lisensi ISC.", - "aboutCreditsTranslators": "Penerjemah", + "aboutTranslatorsSectionTitle": "Penerjemah", - "aboutLicenses": "Lisensi Sumber Terbuka", + "aboutLicensesSectionTitle": "Lisensi Sumber Terbuka", "aboutLicensesBanner": "Aplikasi ini menggunakan paket dan perpustakaan sumber terbuka berikut.", - "aboutLicensesAndroidLibraries": "Perpustakaan Android", - "aboutLicensesFlutterPlugins": "Plugin Flutter", - "aboutLicensesFlutterPackages": "Paket Flutter", - "aboutLicensesDartPackages": "Paket Dart", + "aboutLicensesAndroidLibrariesSectionTitle": "Perpustakaan Android", + "aboutLicensesFlutterPluginsSectionTitle": "Plugin Flutter", + "aboutLicensesFlutterPackagesSectionTitle": "Paket Flutter", + "aboutLicensesDartPackagesSectionTitle": "Paket Dart", "aboutLicensesShowAllButtonLabel": "Tampilkan Semua Lisensi", "policyPageTitle": "Aturan Privasi", @@ -341,11 +341,6 @@ "collectionSearchTitlesHintText": "Cari judul", - "collectionSortDate": "Lewat tanggal", - "collectionSortSize": "Lewat ukuran", - "collectionSortName": "Lewat nama album & file", - "collectionSortRating": "Lewat nilai", - "collectionGroupAlbum": "Lewat album", "collectionGroupMonth": "Lewat bulan", "collectionGroupDay": "Lewat hari", @@ -374,6 +369,8 @@ "collectionSelectSectionTooltip": "Pilih bagian", "collectionDeselectSectionTooltip": "Batalkan pilihan bagian", + "drawerAboutButton": "Tentang", + "drawerSettingsButton": "Pengaturan", "drawerCollectionAll": "Semua koleksi", "drawerCollectionFavourites": "Favorit", "drawerCollectionImages": "Gambar", @@ -383,10 +380,25 @@ "drawerCollectionPanoramas": "Panorama", "drawerCollectionRaws": "Foto Raw", "drawerCollectionSphericalVideos": "Video 360°", + "drawerAlbumPage": "Album", + "drawerCountryPage": "Negara", + "drawerTagPage": "Label", - "chipSortDate": "Lewat tanggal", - "chipSortName": "Lewat nama", - "chipSortCount": "Lewat jumlah benda", + "sortByDate": "Lewat tanggal", + "sortByName": "Lewat nama", + "sortByItemCount": "Lewat jumlah benda", + "sortBySize": "Lewat ukuran", + "sortByAlbumFileName": "Lewat nama album & file", + "sortByRating": "Lewat nilai", + + "sortOrderNewestFirst": "Terbaru pertama", + "sortOrderOldestFirst": "Tertua pertama", + "sortOrderAtoZ": "A ke Z", + "sortOrderZtoA": "Z ke A", + "sortOrderHighestFirst": "Tertinggi pertama", + "sortOrderLowestFirst": "Terendah pertama", + "sortOrderLargestFirst": "Terbesar pertama", + "sortOrderSmallestFirst": "Terkecil pertama", "albumGroupTier": "Lewat tingkat", "albumGroupVolume": "Lewat volume penyimpanan", @@ -418,13 +430,14 @@ "binPageTitle": "Tong Sampah", "searchCollectionFieldHint": "Cari koleksi", - "searchSectionRecent": "Terkini", - "searchSectionDate": "Tanggal", - "searchSectionAlbums": "Album", - "searchSectionCountries": "Negara", - "searchSectionPlaces": "Tempat", - "searchSectionTags": "Label", - "searchSectionRating": "Nilai", + "searchRecentSectionTitle": "Terkini", + "searchDateSectionTitle": "Tanggal", + "searchAlbumsSectionTitle": "Album", + "searchCountriesSectionTitle": "Negara", + "searchPlacesSectionTitle": "Tempat", + "searchTagsSectionTitle": "Label", + "searchRatingSectionTitle": "Nilai", + "searchMetadataSectionTitle": "Metadata", "settingsPageTitle": "Pengaturan", "settingsSystemDefault": "Sistem", @@ -433,36 +446,40 @@ "settingsSearchFieldLabel": "Cari peraturan", "settingsSearchEmpty": "Tidak ada peraturan yang cocok", "settingsActionExport": "Ekspor", + "settingsActionExportDialogTitle": "Ekspor", "settingsActionImport": "Impor", + "settingsActionImportDialogTitle": "Impor", "appExportCovers": "Sampul", "appExportFavourites": "Favorit", "appExportSettings": "Pengaturan", - "settingsSectionNavigation": "Navigasi", - "settingsHome": "Beranda", + "settingsNavigationSectionTitle": "Navigasi", + "settingsHomeTile": "Beranda", + "settingsHomeDialogTitle": "Beranda", "settingsShowBottomNavigationBar": "Tampilkan bilah navigasi bawah", "settingsKeepScreenOnTile": "Biarkan layarnya menyala", - "settingsKeepScreenOnTitle": "Biarkan Layarnya Menyala", + "settingsKeepScreenOnDialogTitle": "Biarkan Layarnya Menyala", "settingsDoubleBackExit": "Ketuk “kembali” dua kali untuk keluar", - "settingsConfirmationDialogTile": "Dialog konfirmasi", + "settingsConfirmationTile": "Dialog konfirmasi", "settingsConfirmationDialogTitle": "Dialog Konfirmasi", - "settingsConfirmationDialogDeleteItems": "Tanya sebelum menghapus benda selamanya", - "settingsConfirmationDialogMoveToBinItems": "Tanya sebelum memindahkan benda ke tong sampah", - "settingsConfirmationDialogMoveUndatedItems": "Tanyakan sebelum memindahkan barang tanpa metadata tanggal", + "settingsConfirmationBeforeDeleteItems": "Tanya sebelum menghapus benda selamanya", + "settingsConfirmationBeforeMoveToBinItems": "Tanya sebelum memindahkan benda ke tong sampah", + "settingsConfirmationBeforeMoveUndatedItems": "Tanyakan sebelum memindahkan barang tanpa metadata tanggal", + "settingsConfirmationAfterMoveToBinItems": "Tampilkan pesan setelah menggerakkan barang ke tong sampah", "settingsNavigationDrawerTile": "Menu navigasi", - "settingsNavigationDrawerEditorTitle": "Menu Navigasi", + "settingsNavigationDrawerEditorPageTitle": "Menu Navigasi", "settingsNavigationDrawerBanner": "Sentuh dan tahan untuk memindahkan dan menyusun ulang benda menu.", "settingsNavigationDrawerTabTypes": "Tipe", "settingsNavigationDrawerTabAlbums": "Album", "settingsNavigationDrawerTabPages": "Halaman", "settingsNavigationDrawerAddAlbum": "Tambahkan album", - "settingsSectionThumbnails": "Thumbnail", + "settingsThumbnailSectionTitle": "Thumbnail", "settingsThumbnailOverlayTile": "Hamparan", - "settingsThumbnailOverlayTitle": "Hamparan", + "settingsThumbnailOverlayPageTitle": "Hamparan", "settingsThumbnailShowFavouriteIcon": "Tampilkan ikon favorit", "settingsThumbnailShowTagIcon": "Tampilkan ikon label", "settingsThumbnailShowLocationIcon": "Tampilkan ikon lokasi", @@ -472,27 +489,28 @@ "settingsThumbnailShowVideoDuration": "Tampilkan durasi video", "settingsCollectionQuickActionsTile": "Aksi cepat", - "settingsCollectionQuickActionEditorTitle": "Aksi Cepat", + "settingsCollectionQuickActionEditorPageTitle": "Aksi Cepat", "settingsCollectionQuickActionTabBrowsing": "Menjelajah", "settingsCollectionQuickActionTabSelecting": "Memilih", "settingsCollectionBrowsingQuickActionEditorBanner": "Sentuh dan tahan untuk memindahkan tombol dan memilih tindakan yang ditampilkan saat menelusuri benda.", "settingsCollectionSelectionQuickActionEditorBanner": "Sentuh dan tahan untuk memindahkan tombol dan memilih tindakan yang ditampilkan saat memilih benda.", - "settingsSectionViewer": "Penonton", + "settingsViewerSectionTitle": "Penonton", + "settingsViewerGestureSideTapNext": "Ketuk tepi layar untuk menampilkan benda sebelumnya/berikutnya", "settingsViewerUseCutout": "Gunakan area potongan", "settingsViewerMaximumBrightness": "Kecerahan maksimum", "settingsMotionPhotoAutoPlay": "Putar foto bergerak otomatis", "settingsImageBackground": "Latar belakang gambar", "settingsViewerQuickActionsTile": "Aksi cepat", - "settingsViewerQuickActionEditorTitle": "Aksi Cepat", + "settingsViewerQuickActionEditorPageTitle": "Aksi Cepat", "settingsViewerQuickActionEditorBanner": "Sentuh dan tahan untuk memindahkan tombol dan memilih tindakan yang ditampilkan di penampil.", - "settingsViewerQuickActionEditorDisplayedButtons": "Tombol yang Ditampilkan", - "settingsViewerQuickActionEditorAvailableButtons": "Tombol yang tersedia", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Tombol yang Ditampilkan", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Tombol yang tersedia", "settingsViewerQuickActionEmpty": "Tidak ada tombol", "settingsViewerOverlayTile": "Hamparan", - "settingsViewerOverlayTitle": "Hamparan", + "settingsViewerOverlayPageTitle": "Hamparan", "settingsViewerShowOverlayOnOpening": "Tampilkan saat pembukaan", "settingsViewerShowMinimap": "Tampilkan minimap", "settingsViewerShowInformation": "Tampilkan informasi", @@ -502,30 +520,30 @@ "settingsViewerEnableOverlayBlurEffect": "Efek Kabur", "settingsViewerSlideshowTile": "Tampilan slide", - "settingsViewerSlideshowTitle": "Tampilan Slide", + "settingsViewerSlideshowPageTitle": "Tampilan Slide", "settingsSlideshowRepeat": "Ulangi", "settingsSlideshowShuffle": "Acak", "settingsSlideshowFillScreen": "Isi layar", "settingsSlideshowTransitionTile": "Transisi", - "settingsSlideshowTransitionTitle": "Transisi", + "settingsSlideshowTransitionDialogTitle": "Transisi", "settingsSlideshowIntervalTile": "Interval", - "settingsSlideshowIntervalTitle": "Interval", + "settingsSlideshowIntervalDialogTitle": "Interval", "settingsSlideshowVideoPlaybackTile": "Putaran ulang video", - "settingsSlideshowVideoPlaybackTitle": "Putaran Ulang Video", + "settingsSlideshowVideoPlaybackDialogTitle": "Putaran Ulang Video", "settingsVideoPageTitle": "Pengaturan Video", - "settingsSectionVideo": "Video", + "settingsVideoSectionTitle": "Video", "settingsVideoShowVideos": "Tampilkan video", "settingsVideoEnableHardwareAcceleration": "Akselerasi perangkat keras", "settingsVideoEnableAutoPlay": "Putar otomatis", "settingsVideoLoopModeTile": "Putar ulang", - "settingsVideoLoopModeTitle": "Putar Ulang", + "settingsVideoLoopModeDialogTitle": "Putar Ulang", "settingsSubtitleThemeTile": "Subjudul", - "settingsSubtitleThemeTitle": "Subjudul", + "settingsSubtitleThemePageTitle": "Subjudul", "settingsSubtitleThemeSample": "Ini adalah sampel.", "settingsSubtitleThemeTextAlignmentTile": "Perataan teks", - "settingsSubtitleThemeTextAlignmentTitle": "Perataan Teks", + "settingsSubtitleThemeTextAlignmentDialogTitle": "Perataan Teks", "settingsSubtitleThemeTextSize": "Ukuran teks", "settingsSubtitleThemeShowOutline": "Tampilkan garis besar dan bayangan", "settingsSubtitleThemeTextColor": "Warna teks", @@ -537,13 +555,13 @@ "settingsSubtitleThemeTextAlignmentRight": "Kanan", "settingsVideoControlsTile": "Kontrol", - "settingsVideoControlsTitle": "Kontrol", + "settingsVideoControlsPageTitle": "Kontrol", "settingsVideoButtonsTile": "Tombol", - "settingsVideoButtonsTitle": "Tombol", + "settingsVideoButtonsDialogTitle": "Tombol", "settingsVideoGestureDoubleTapTogglePlay": "Ketuk dua kali untuk mainkan/hentikan", "settingsVideoGestureSideDoubleTapSeek": "Ketuk dua kali di tepi layar untuk mencari kebelakang/kedepan", - "settingsSectionPrivacy": "Privasi", + "settingsPrivacySectionTitle": "Privasi", "settingsAllowInstalledAppAccess": "Izinkan akses ke inventori aplikasi", "settingsAllowInstalledAppAccessSubtitle": "Digunakan untuk meningkatkan tampilan album", "settingsAllowErrorReporting": "Izinkan pelaporan kesalahan secara anonim", @@ -552,52 +570,56 @@ "settingsEnableBinSubtitle": "Simpan benda yang dihapus selama 30 hari", "settingsHiddenItemsTile": "Benda tersembunyi", - "settingsHiddenItemsTitle": "Benda Tersembunyi", + "settingsHiddenItemsPageTitle": "Benda Tersembunyi", - "settingsHiddenFiltersTitle": "Filter Tersembunyi", + "settingsHiddenItemsTabFilters": "Filter Tersembunyi", "settingsHiddenFiltersBanner": "Foto dan video filter tersembunyi yang cocok tidak akan muncul di koleksi Anda.", "settingsHiddenFiltersEmpty": "Tidak ada filter tersembunyi", - "settingsHiddenPathsTitle": "Jalan Tersembunyi", + "settingsHiddenItemsTabPaths": "Jalan Tersembunyi", "settingsHiddenPathsBanner": "Foto dan video di folder ini, atau subfoldernya, tidak akan muncul di koleksi Anda.", "addPathTooltip": "Tambahkan jalan", "settingsStorageAccessTile": "Akses penyimpanan", - "settingsStorageAccessTitle": "Akses Penyimpanan", + "settingsStorageAccessPageTitle": "Akses Penyimpanan", "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", + "settingsAccessibilitySectionTitle": "Aksesibilitas", "settingsRemoveAnimationsTile": "Hapus animasi", - "settingsRemoveAnimationsTitle": "Hapus Animasi", + "settingsRemoveAnimationsDialogTitle": "Hapus Animasi", "settingsTimeToTakeActionTile": "Waktu untuk mengambil tindakan", - "settingsTimeToTakeActionTitle": "Saatnya Bertindak", + "settingsTimeToTakeActionDialogTitle": "Saatnya Bertindak", - "settingsSectionDisplay": "Tampilan", - "settingsThemeBrightness": "Tema", + "settingsDisplaySectionTitle": "Tampilan", + "settingsThemeBrightnessTile": "Tema", + "settingsThemeBrightnessDialogTitle": "Tema", "settingsThemeColorHighlights": "Highlight warna", "settingsThemeEnableDynamicColor": "Warna dinamis", "settingsDisplayRefreshRateModeTile": "Tingkat penyegaran tampilan", - "settingsDisplayRefreshRateModeTitle": "Tingkat Penyegaran", + "settingsDisplayRefreshRateModeDialogTitle": "Tingkat Penyegaran", - "settingsSectionLanguage": "Bahasa & Format", - "settingsLanguage": "Bahasa", + "settingsLanguageSectionTitle": "Bahasa & Format", + "settingsLanguageTile": "Bahasa", + "settingsLanguagePageTitle": "Bahasa", "settingsCoordinateFormatTile": "Format koordinat", - "settingsCoordinateFormatTitle": "Format Koordinat", + "settingsCoordinateFormatDialogTitle": "Format Koordinat", "settingsUnitSystemTile": "Unit", - "settingsUnitSystemTitle": "Unit", + "settingsUnitSystemDialogTitle": "Unit", "settingsScreenSaverPageTitle": "Screensaver", "settingsWidgetPageTitle": "Bingkai Foto", "settingsWidgetShowOutline": "Garis luar", + "settingsCollectionTile": "Koleksi", + "statsPageTitle": "Statistik", "statsWithGps": "{count, plural, other{{count} benda dengan lokasi}}", - "statsTopCountries": "Negara Teratas", - "statsTopPlaces": "Tempat Teratas", - "statsTopTags": "Label Teratas", + "statsTopCountriesSectionTitle": "Negara Teratas", + "statsTopPlacesSectionTitle": "Tempat Teratas", + "statsTopTagsSectionTitle": "Label Teratas", "viewerOpenPanoramaButtonLabel": "BUKA PANORAMA", "viewerSetWallpaperButtonLabel": "TETAPKAN SEBAGAI WALLPAPER", @@ -608,6 +630,7 @@ "viewerInfoBackToViewerTooltip": "Kembali ke penonton", "viewerInfoUnknown": "tidak dikenal", + "viewerInfoLabelDescription": "Deskripsi", "viewerInfoLabelTitle": "Judul", "viewerInfoLabelDate": "Tanggal", "viewerInfoLabelResolution": "Resolusi", @@ -619,7 +642,7 @@ "viewerInfoLabelCoordinates": "Koordinat", "viewerInfoLabelAddress": "Alamat", - "mapStyleTitle": "Gaya Peta", + "mapStyleDialogTitle": "Gaya Peta", "mapStyleTooltip": "Pilih gaya peta", "mapZoomInTooltip": "Perbesar", "mapZoomOutTooltip": "Perkecil", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 3d24c1639..c2fe6374a 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -27,6 +27,7 @@ "actionRemove": "Rimuovi", "resetTooltip": "Reimposta", "saveTooltip": "Salva", + "pickTooltip": "Seleziona", "doubleBackExitMessage": "Tocca di nuovo «indietro» per uscire", "doNotAskAgain": "Non chiedere di nuovo", @@ -87,18 +88,20 @@ "entryInfoActionEditDate": "Modifica data e ora", "entryInfoActionEditLocation": "Modifica posizione", - "entryInfoActionEditDescription": "Modifica descrizione", + "entryInfoActionEditTitleDescription": "Modifica titolo & descrizione", "entryInfoActionEditRating": "Modifica valutazione", "entryInfoActionEditTags": "Modifica etichetta", "entryInfoActionRemoveMetadata": "Rimuovi metadati", "filterBinLabel": "Cestino", "filterFavouriteLabel": "Preferiti", - "filterLocationEmptyLabel": "Senza posizione", - "filterTagEmptyLabel": "Senza etichetta", + "filterNoDateLabel": "Senza data", + "filterNoLocationLabel": "Senza posizione", + "filterNoRatingLabel": "Non valutato", + "filterNoTagLabel": "Senza etichetta", + "filterNoTitleLabel": "Senza titolo", "filterOnThisDayLabel": "In questo giorno", "filterRecentlyAddedLabel": "Aggiunto di recente", - "filterRatingUnratedLabel": "Non valutato", "filterRatingRejectedLabel": "Rifiutato", "filterTypeAnimatedLabel": "Animato", "filterTypeMotionPhotoLabel": "Foto in movimento", @@ -179,16 +182,11 @@ "storageVolumeDescriptionFallbackNonPrimary": "Scheda SD", "rootDirectoryDescription": "cartella root", "otherDirectoryDescription": "cartella «{name}»", - "storageAccessDialogTitle": "Accesso a tutti i file", "storageAccessDialogMessage": "Si prega di selezionare la {directory} di «{volume}» nella prossima schermata per dare accesso a questa applicazione", - "restrictedAccessDialogTitle": "Accesso limitato", "restrictedAccessDialogMessage": "Questa applicazione non è autorizzata a modificare i file nella {directory} di «{volume}»", - "notEnoughSpaceDialogTitle": "Spazio insufficiente", "notEnoughSpaceDialogMessage": "Questa operazione ha bisogno di {needSize} di spazio libero su «{volume}» per essere completata, ma è rimasto solo {freeSize}", - "missingSystemFilePickerDialogTitle": "Selezionatore file mancante", "missingSystemFilePickerDialogMessage": "Il selezionatore file di sistema è mancante o disabilitato. Per favore abilitalo e riprova", - "unsupportedTypeDialogTitle": "Formati non supportati", "unsupportedTypeDialogMessage": "{count, plural, =1{Questa operazione non è supportata per elementi del seguente tipo: {types}.} other{Questa operazione non è supportata per elementi dei seguenti tipi: {types}.}}", "nameConflictDialogSingleSourceMessage": "Alcuni file nella cartella di destinazione hanno lo stesso nome", @@ -197,7 +195,6 @@ "addShortcutDialogLabel": "Etichetta della scorciatoia", "addShortcutButtonLabel": "AGGIUNGI", - "noMatchingAppDialogTitle": "Nessuna app corrispondente", "noMatchingAppDialogMessage": "Non ci sono app che possono gestire questo", "binEntriesConfirmationDialogMessage": "{count, plural, =1{Spostare questo elemento nel cestino?} other{Spostare questi {count} elementi nel cestino?}}", @@ -226,7 +223,7 @@ "renameEntrySetPageTitle": "Rinomina", "renameEntrySetPagePatternFieldLabel": "Schema per i nomi", "renameEntrySetPageInsertTooltip": "Inserisci campo", - "renameEntrySetPagePreview": "Anteprima", + "renameEntrySetPagePreviewSectionTitle": "Anteprima", "renameProcessorCounter": "Contatore", "renameProcessorName": "Nome", @@ -259,8 +256,6 @@ "locationPickerUseThisLocationButton": "Usa questa posizione", - "editEntryDescriptionDialogTitle": "Descrizione", - "editEntryRatingDialogTitle": "Valutazione", "removeEntryMetadataDialogTitle": "Rimozione dei metadati", @@ -289,9 +284,10 @@ "menuActionSlideshow": "Presentazione", "menuActionStats": "Statistiche", - "viewDialogTabSort": "Ordina", - "viewDialogTabGroup": "Raggruppa", - "viewDialogTabLayout": "Layout", + "viewDialogSortSectionTitle": "Ordina", + "viewDialogGroupSectionTitle": "Raggruppa", + "viewDialogLayoutSectionTitle": "Layout", + "viewDialogReverseSortOrder": "Inverti ordinamento", "tileLayoutGrid": "Griglia", "tileLayoutList": "Lista", @@ -308,24 +304,24 @@ "aboutLinkLicense": "Licenza", "aboutLinkPolicy": "Informativa sulla privacy", - "aboutBug": "Segnalazione bug", + "aboutBugSectionTitle": "Segnalazione bug", "aboutBugSaveLogInstruction": "Salva i log dell’app in un file", "aboutBugCopyInfoInstruction": "Copia le informazioni di sistema", "aboutBugCopyInfoButton": "Copia", "aboutBugReportInstruction": "Segnala su GitHub con i log e le informazioni di sistema", "aboutBugReportButton": "Segnala", - "aboutCredits": "Crediti", + "aboutCreditsSectionTitle": "Crediti", "aboutCreditsWorldAtlas1": "Questa applicazione utilizza un file TopoJSON di", "aboutCreditsWorldAtlas2": "sotto licenza ISC", - "aboutCreditsTranslators": "Traduttori", + "aboutTranslatorsSectionTitle": "Traduttori", - "aboutLicenses": "Licenze Open-Source", + "aboutLicensesSectionTitle": "Licenze Open-Source", "aboutLicensesBanner": "Questa applicazione utilizza i seguenti pacchetti e librerie open-source", - "aboutLicensesAndroidLibraries": "Librerie Android", - "aboutLicensesFlutterPlugins": "Plugin Flutter", - "aboutLicensesFlutterPackages": "Pacchetti Flutter", - "aboutLicensesDartPackages": "Pacchetti Dart", + "aboutLicensesAndroidLibrariesSectionTitle": "Librerie Android", + "aboutLicensesFlutterPluginsSectionTitle": "Plugin Flutter", + "aboutLicensesFlutterPackagesSectionTitle": "Pacchetti Flutter", + "aboutLicensesDartPackagesSectionTitle": "Pacchetti Dart", "aboutLicensesShowAllButtonLabel": "Mostra tutte le licenze", "policyPageTitle": "Informativa sulla privacy", @@ -345,11 +341,6 @@ "collectionSearchTitlesHintText": "Cerca titoli", - "collectionSortDate": "Per data", - "collectionSortSize": "Per dimensione", - "collectionSortName": "Per album e nome del file", - "collectionSortRating": "Per valutazione", - "collectionGroupAlbum": "Per album", "collectionGroupMonth": "Per mese", "collectionGroupDay": "Per giorno", @@ -378,6 +369,8 @@ "collectionSelectSectionTooltip": "Seleziona sezione", "collectionDeselectSectionTooltip": "Deseleziona sezione", + "drawerAboutButton": "Informazioni", + "drawerSettingsButton": "Impostazioni", "drawerCollectionAll": "Tutte le collezioni", "drawerCollectionFavourites": "Preferiti", "drawerCollectionImages": "Immagini", @@ -387,10 +380,24 @@ "drawerCollectionPanoramas": "Panorami", "drawerCollectionRaws": "Foto raw", "drawerCollectionSphericalVideos": "Video a 360°", + "drawerAlbumPage": "Album", + "drawerCountryPage": "Paesi", + "drawerTagPage": "Etichette", - "chipSortDate": "Per data", - "chipSortName": "Per nome", - "chipSortCount": "Per numero di elementi", + "sortByDate": "Per data", + "sortByName": "Per nome", + "sortByItemCount": "Per numero di elementi", + "sortBySize": "Per dimensione", + "sortByAlbumFileName": "Per album e nome del file", + "sortByRating": "Per valutazione", + "sortOrderNewestFirst": "Prima i più nuovi", + "sortOrderOldestFirst": "Prima i più vecchi", + "sortOrderAtoZ": "Dalla A alla Z", + "sortOrderZtoA": "Dalla Z alla A", + "sortOrderHighestFirst": "Prima le più alte", + "sortOrderLowestFirst": "Prima le più basse", + "sortOrderLargestFirst": "Prima i più grandi", + "sortOrderSmallestFirst": "Prima i più piccoli", "albumGroupTier": "Per importanza", "albumGroupVolume": "Per volume di archiviazione", @@ -422,13 +429,14 @@ "binPageTitle": "Cestino", "searchCollectionFieldHint": "Cerca raccolta", - "searchSectionRecent": "Recenti", - "searchSectionDate": "Data", - "searchSectionAlbums": "Album", - "searchSectionCountries": "Paesi", - "searchSectionPlaces": "Luoghi", - "searchSectionTags": "Etichette", - "searchSectionRating": "Valutazioni", + "searchRecentSectionTitle": "Recenti", + "searchDateSectionTitle": "Data", + "searchAlbumsSectionTitle": "Album", + "searchCountriesSectionTitle": "Paesi", + "searchPlacesSectionTitle": "Luoghi", + "searchTagsSectionTitle": "Etichette", + "searchRatingSectionTitle": "Valutazioni", + "searchMetadataSectionTitle": "Metadati", "settingsPageTitle": "Impostazioni", "settingsSystemDefault": "Sistema", @@ -437,37 +445,40 @@ "settingsSearchFieldLabel": "Ricerca impostazioni", "settingsSearchEmpty": "Nessuna impostazione corrispondente", "settingsActionExport": "Esporta", + "settingsActionExportDialogTitle": "Esporta", "settingsActionImport": "Importa", + "settingsActionImportDialogTitle": "Importa", "appExportCovers": "Copertine", "appExportFavourites": "Preferiti", "appExportSettings": "Impostazioni", - "settingsSectionNavigation": "Navigazione", - "settingsHome": "Pagina iniziale", + "settingsNavigationSectionTitle": "Navigazione", + "settingsHomeTile": "Pagina iniziale", + "settingsHomeDialogTitle": "Pagina iniziale", "settingsShowBottomNavigationBar": "Mostra la barra di navigazione in basso", "settingsKeepScreenOnTile": "Mantieni acceso lo schermo", - "settingsKeepScreenOnTitle": "Illuminazione schermo", + "settingsKeepScreenOnDialogTitle": "Illuminazione schermo", "settingsDoubleBackExit": "Tocca «indietro» due volte per uscire", - "settingsConfirmationDialogTile": "Richieste di conferma", + "settingsConfirmationTile": "Richieste di conferma", "settingsConfirmationDialogTitle": "Richieste di conferma", - "settingsConfirmationDialogDeleteItems": "Chiedi prima di cancellare gli elementi definitivamente", - "settingsConfirmationDialogMoveToBinItems": "Chiedi prima di spostare gli elementi nel cestino", - "settingsConfirmationDialogMoveUndatedItems": "Chiedi prima di spostare gli elementi senza data", + "settingsConfirmationBeforeDeleteItems": "Chiedi prima di cancellare gli elementi definitivamente", + "settingsConfirmationBeforeMoveToBinItems": "Chiedi prima di spostare gli elementi nel cestino", + "settingsConfirmationBeforeMoveUndatedItems": "Chiedi prima di spostare gli elementi senza data", "settingsConfirmationAfterMoveToBinItems": "Mostra un messaggio dopo aver spostato gli elementi nel cestino", "settingsNavigationDrawerTile": "Menu di navigazione", - "settingsNavigationDrawerEditorTitle": "Menu di navigazione", + "settingsNavigationDrawerEditorPageTitle": "Menu di navigazione", "settingsNavigationDrawerBanner": "Tocca e tieni premuto per spostare e riordinare gli elementi del menu", "settingsNavigationDrawerTabTypes": "Tipi", "settingsNavigationDrawerTabAlbums": "Album", "settingsNavigationDrawerTabPages": "Pagine", "settingsNavigationDrawerAddAlbum": "Aggiungi album", - "settingsSectionThumbnails": "Miniature", + "settingsThumbnailSectionTitle": "Miniature", "settingsThumbnailOverlayTile": "Sovrapposizione", - "settingsThumbnailOverlayTitle": "Sovrapposizione", + "settingsThumbnailOverlayPageTitle": "Sovrapposizione", "settingsThumbnailShowFavouriteIcon": "Mostra l’icona Preferiti", "settingsThumbnailShowTagIcon": "Mostra l’icona Etichetta", "settingsThumbnailShowLocationIcon": "Mostra l’icona Posizione", @@ -477,13 +488,13 @@ "settingsThumbnailShowVideoDuration": "Mostra la durata del video", "settingsCollectionQuickActionsTile": "Azioni rapide", - "settingsCollectionQuickActionEditorTitle": "Azioni rapide", + "settingsCollectionQuickActionEditorPageTitle": "Azioni rapide", "settingsCollectionQuickActionTabBrowsing": "Navigazione", "settingsCollectionQuickActionTabSelecting": "Selezione", "settingsCollectionBrowsingQuickActionEditorBanner": "Tocca e tieni premuto per spostare i pulsanti e selezionare quali azioni vengono visualizzate durante la navigazione degli elementi", "settingsCollectionSelectionQuickActionEditorBanner": "Tocca e tieni premuto per spostare i pulsanti e selezionare quali azioni vengono visualizzate quando si selezionano gli elementi", - "settingsSectionViewer": "Visualizzazione", + "settingsViewerSectionTitle": "Visualizzazione", "settingsViewerGestureSideTapNext": "Tocca i bordi dello schermo per visualizzare l'elemento precedente/successivo", "settingsViewerUseCutout": "Usa area di ritaglio", "settingsViewerMaximumBrightness": "Luminosità massima", @@ -491,14 +502,14 @@ "settingsImageBackground": "Sfondo immagine", "settingsViewerQuickActionsTile": "Azioni rapide", - "settingsViewerQuickActionEditorTitle": "Azioni rapide", + "settingsViewerQuickActionEditorPageTitle": "Azioni rapide", "settingsViewerQuickActionEditorBanner": "Tocca e tieni premuto per spostare i pulsanti e selezionare quali azioni vengono mostrate durante la visualizione", - "settingsViewerQuickActionEditorDisplayedButtons": "Pulsanti visualizzati", - "settingsViewerQuickActionEditorAvailableButtons": "Pulsanti disponibili", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Pulsanti visualizzati", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Pulsanti disponibili", "settingsViewerQuickActionEmpty": "Nessun pulsante", "settingsViewerOverlayTile": "Sovrapposizione", - "settingsViewerOverlayTitle": "Sovrapposizione", + "settingsViewerOverlayPageTitle": "Sovrapposizione", "settingsViewerShowOverlayOnOpening": "Mostra all’apertura", "settingsViewerShowMinimap": "Mostra la minimappa", "settingsViewerShowInformation": "Mostra informazioni", @@ -508,30 +519,30 @@ "settingsViewerEnableOverlayBlurEffect": "Effetto sfocatura", "settingsViewerSlideshowTile": "Presentazione", - "settingsViewerSlideshowTitle": "Presentazione", + "settingsViewerSlideshowPageTitle": "Presentazione", "settingsSlideshowRepeat": "Ripeti", "settingsSlideshowShuffle": "Ordine casuale", "settingsSlideshowFillScreen": "Riempi schermo", "settingsSlideshowTransitionTile": "Transizione", - "settingsSlideshowTransitionTitle": "Transizione", + "settingsSlideshowTransitionDialogTitle": "Transizione", "settingsSlideshowIntervalTile": "Intervallo", - "settingsSlideshowIntervalTitle": "Intervallo", + "settingsSlideshowIntervalDialogTitle": "Intervallo", "settingsSlideshowVideoPlaybackTile": "Riproduzione video", - "settingsSlideshowVideoPlaybackTitle": "Riproduzione video", + "settingsSlideshowVideoPlaybackDialogTitle": "Riproduzione video", "settingsVideoPageTitle": "Impostazioni video", - "settingsSectionVideo": "Video", + "settingsVideoSectionTitle": "Video", "settingsVideoShowVideos": "Mostra video", "settingsVideoEnableHardwareAcceleration": "Accelerazione hardware", "settingsVideoEnableAutoPlay": "Riproduzione automatica", "settingsVideoLoopModeTile": "Modalità loop", - "settingsVideoLoopModeTitle": "Modalità loop", + "settingsVideoLoopModeDialogTitle": "Modalità loop", "settingsSubtitleThemeTile": "Sottotitoli", - "settingsSubtitleThemeTitle": "Sottotitoli", + "settingsSubtitleThemePageTitle": "Sottotitoli", "settingsSubtitleThemeSample": "Questo è un campione", "settingsSubtitleThemeTextAlignmentTile": "Allineamento del testo", - "settingsSubtitleThemeTextAlignmentTitle": "Allineamento del testo", + "settingsSubtitleThemeTextAlignmentDialogTitle": "Allineamento del testo", "settingsSubtitleThemeTextSize": "Dimensione del testo", "settingsSubtitleThemeShowOutline": "Mostra contorno e ombra", "settingsSubtitleThemeTextColor": "Colore del testo", @@ -543,13 +554,13 @@ "settingsSubtitleThemeTextAlignmentRight": "Destra", "settingsVideoControlsTile": "Controlli", - "settingsVideoControlsTitle": "Controlli", + "settingsVideoControlsPageTitle": "Controlli", "settingsVideoButtonsTile": "Pulsanti", - "settingsVideoButtonsTitle": "Pulsanti", + "settingsVideoButtonsDialogTitle": "Pulsanti", "settingsVideoGestureDoubleTapTogglePlay": "Doppio tocco per play/pausa", "settingsVideoGestureSideDoubleTapSeek": "Doppio tocco sui bordi dello schermo per cercare avanti/indietro", - "settingsSectionPrivacy": "Privacy", + "settingsPrivacySectionTitle": "Privacy", "settingsAllowInstalledAppAccess": "Consentire l’accesso all’inventario delle app", "settingsAllowInstalledAppAccessSubtitle": "Utilizzato per migliorare la visualizzazione degli album", "settingsAllowErrorReporting": "Consenti segnalazione anonima degli errori", @@ -558,52 +569,56 @@ "settingsEnableBinSubtitle": "Conserva gli elementi cancellati per 30 giorni", "settingsHiddenItemsTile": "Elementi nascosti", - "settingsHiddenItemsTitle": "Elementi nascosti", + "settingsHiddenItemsPageTitle": "Elementi nascosti", - "settingsHiddenFiltersTitle": "Filtri nascosti", + "settingsHiddenItemsTabFilters": "Filtri nascosti", "settingsHiddenFiltersBanner": "Le foto e i video che corrispondono ai filtri nascosti non appariranno nella tua collezione", "settingsHiddenFiltersEmpty": "Nessun filtro nascosto", - "settingsHiddenPathsTitle": "Percorsi nascosti", + "settingsHiddenItemsTabPaths": "Percorsi nascosti", "settingsHiddenPathsBanner": "Le foto e i video in queste cartelle, o in qualsiasi loro sottocartella, non appariranno nella tua collezione", "addPathTooltip": "Aggiungi percorso", "settingsStorageAccessTile": "Accesso a tutti i file", - "settingsStorageAccessTitle": "Accesso a tutti i file", + "settingsStorageAccessPageTitle": "Accesso a tutti i file", "settingsStorageAccessBanner": "Alcune cartelle richiedono una concessione di accesso esplicita per modificare i file al loro interno. Puoi rivedere qui le cartelle a cui hai dato accesso in precedenza", "settingsStorageAccessEmpty": "Nessuna autorizzazione concessa", "settingsStorageAccessRevokeTooltip": "Rifiuta autorizzazione", - "settingsSectionAccessibility": "Accessibilità", + "settingsAccessibilitySectionTitle": "Accessibilità", "settingsRemoveAnimationsTile": "Rimuovi animazioni", - "settingsRemoveAnimationsTitle": "Rimuovi animazioni", + "settingsRemoveAnimationsDialogTitle": "Rimuovi animazioni", "settingsTimeToTakeActionTile": "Tempo di reazione", - "settingsTimeToTakeActionTitle": "Tempo di reazione", + "settingsTimeToTakeActionDialogTitle": "Tempo di reazione", - "settingsSectionDisplay": "Schermo", - "settingsThemeBrightness": "Tema", + "settingsDisplaySectionTitle": "Schermo", + "settingsThemeBrightnessTile": "Tema", + "settingsThemeBrightnessDialogTitle": "Tema", "settingsThemeColorHighlights": "Colori evidenziati", "settingsThemeEnableDynamicColor": "Colori dinamici", "settingsDisplayRefreshRateModeTile": "Frequenza di aggiornamento dello schermo", - "settingsDisplayRefreshRateModeTitle": "Frequenza di aggiornamento", + "settingsDisplayRefreshRateModeDialogTitle": "Frequenza di aggiornamento", - "settingsSectionLanguage": "Lingua e formati", - "settingsLanguage": "Lingua", + "settingsLanguageSectionTitle": "Lingua e formati", + "settingsLanguageTile": "Lingua", + "settingsLanguagePageTitle": "Lingua", "settingsCoordinateFormatTile": "Formato coordinate", - "settingsCoordinateFormatTitle": "Formato coordinate", + "settingsCoordinateFormatDialogTitle": "Formato coordinate", "settingsUnitSystemTile": "Unità", - "settingsUnitSystemTitle": "Unità", + "settingsUnitSystemDialogTitle": "Unità", "settingsScreenSaverPageTitle": "Salvaschermo", "settingsWidgetPageTitle": "Cornice foto", "settingsWidgetShowOutline": "Contorno", + "settingsCollectionTile": "Collezione", + "statsPageTitle": "Statistiche", "statsWithGps": "{count, plural, =1{1 elemento con posizione} other{{count} elementi con posizione}}", - "statsTopCountries": "Paesi più frequenti", - "statsTopPlaces": "Luoghi più frequenti", - "statsTopTags": "Etichette più frequenti", + "statsTopCountriesSectionTitle": "Paesi più frequenti", + "statsTopPlacesSectionTitle": "Luoghi più frequenti", + "statsTopTagsSectionTitle": "Etichette più frequenti", "viewerOpenPanoramaButtonLabel": "APRI PANORAMA", "viewerSetWallpaperButtonLabel": "IMPOSTA SFONDO", @@ -614,6 +629,7 @@ "viewerInfoBackToViewerTooltip": "Torna alla visualizzazione", "viewerInfoUnknown": "sconosciuto", + "viewerInfoLabelDescription": "Descrizione", "viewerInfoLabelTitle": "Titolo", "viewerInfoLabelDate": "Data", "viewerInfoLabelResolution": "Risoluzione", @@ -625,7 +641,7 @@ "viewerInfoLabelCoordinates": "Coordinate", "viewerInfoLabelAddress": "Indirizzo", - "mapStyleTitle": "Stile Mappa", + "mapStyleDialogTitle": "Stile Mappa", "mapStyleTooltip": "Seleziona lo stile della mappa", "mapZoomInTooltip": "Ingrandisci", "mapZoomOutTooltip": "Riduci", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index ea180e6f1..a65a58789 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -27,6 +27,7 @@ "actionRemove": "削除", "resetTooltip": "リセット", "saveTooltip": "保存", + "pickTooltip": "ピック", "doubleBackExitMessage": "終了するには「戻る」をもう一度タップしてください。", "doNotAskAgain": "今後このメッセージを表示しない", @@ -93,10 +94,10 @@ "filterBinLabel": "ごみ箱", "filterFavouriteLabel": "お気に入り", - "filterLocationEmptyLabel": "位置情報なし", - "filterTagEmptyLabel": "タグ情報なし", + "filterNoLocationLabel": "位置情報なし", + "filterNoRatingLabel": "評価情報なし", + "filterNoTagLabel": "タグ情報なし", "filterOnThisDayLabel": "過去のこの日", - "filterRatingUnratedLabel": "評価情報なし", "filterRatingRejectedLabel": "拒否", "filterTypeAnimatedLabel": "アニメーション", "filterTypeMotionPhotoLabel": "モーションフォト", @@ -177,16 +178,11 @@ "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": "目的フォルダに同じ名前のファイルが存在しています。", @@ -195,7 +191,6 @@ "addShortcutDialogLabel": "ショートカット ラベル", "addShortcutButtonLabel": "追加", - "noMatchingAppDialogTitle": "一致するアプリなし", "noMatchingAppDialogMessage": "処理できるアプリが見つかりません。", "binEntriesConfirmationDialogMessage": "{count, plural, =1{このアイテムをごみ箱に移動しますか?} other{{count} 件のアイテムをごみ箱に移動しますか?}}", @@ -224,7 +219,7 @@ "renameEntrySetPageTitle": "名前を変更", "renameEntrySetPagePatternFieldLabel": "名前付けのパターン", "renameEntrySetPageInsertTooltip": "フィールドを挿入", - "renameEntrySetPagePreview": "プレビュー", + "renameEntrySetPagePreviewSectionTitle": "プレビュー", "renameProcessorCounter": "連番", "renameProcessorName": "名前", @@ -285,9 +280,9 @@ "menuActionSlideshow": "スライドショー", "menuActionStats": "統計", - "viewDialogTabSort": "並べ替え", - "viewDialogTabGroup": "グループ", - "viewDialogTabLayout": "レイアウト", + "viewDialogSortSectionTitle": "並べ替え", + "viewDialogGroupSectionTitle": "グループ", + "viewDialogLayoutSectionTitle": "レイアウト", "tileLayoutGrid": "グリッド表示", "tileLayoutList": "リスト表示", @@ -304,24 +299,24 @@ "aboutLinkLicense": "ライセンス", "aboutLinkPolicy": "プライバシー ポリシー", - "aboutBug": "バグの報告", + "aboutBugSectionTitle": "バグの報告", "aboutBugSaveLogInstruction": "アプリのログをファイルに保存", "aboutBugCopyInfoInstruction": "システム情報をコピー", "aboutBugCopyInfoButton": "コピー", "aboutBugReportInstruction": "ログとシステム情報とともに GitHub で報告", "aboutBugReportButton": "報告", - "aboutCredits": "クレジット", + "aboutCreditsSectionTitle": "クレジット", "aboutCreditsWorldAtlas1": "このアプリは TopoJSON ファイルを", "aboutCreditsWorldAtlas2": "ISC License の下使用しています。", - "aboutCreditsTranslators": "翻訳", + "aboutTranslatorsSectionTitle": "翻訳", - "aboutLicenses": "オープンソース ライセンス", + "aboutLicensesSectionTitle": "オープンソース ライセンス", "aboutLicensesBanner": "このアプリは下記のオープンソース パッケージおよびライブラリを使用しています。", - "aboutLicensesAndroidLibraries": "Android ライブラリ", - "aboutLicensesFlutterPlugins": "Flutter プラグイン", - "aboutLicensesFlutterPackages": "Flutter パッケージ", - "aboutLicensesDartPackages": "Dart パッケージ", + "aboutLicensesAndroidLibrariesSectionTitle": "Android ライブラリ", + "aboutLicensesFlutterPluginsSectionTitle": "Flutter プラグイン", + "aboutLicensesFlutterPackagesSectionTitle": "Flutter パッケージ", + "aboutLicensesDartPackagesSectionTitle": "Dart パッケージ", "aboutLicensesShowAllButtonLabel": "すべてのライセンスを表示", "policyPageTitle": "プライバシー ポリシー", @@ -341,11 +336,6 @@ "collectionSearchTitlesHintText": "タイトルを検索", - "collectionSortDate": "日付", - "collectionSortSize": "サイズ", - "collectionSortName": "アルバムとファイル名", - "collectionSortRating": "評価", - "collectionGroupAlbum": "アルバム別", "collectionGroupMonth": "月別", "collectionGroupDay": "日別", @@ -374,6 +364,8 @@ "collectionSelectSectionTooltip": "セクションを選択", "collectionDeselectSectionTooltip": "セクションの選択を解除", + "drawerAboutButton": "アプリについて", + "drawerSettingsButton": "設定", "drawerCollectionAll": "すべてのコレクション", "drawerCollectionFavourites": "お気に入り", "drawerCollectionImages": "画像", @@ -383,10 +375,16 @@ "drawerCollectionPanoramas": "パノラマ", "drawerCollectionRaws": "Raw 写真", "drawerCollectionSphericalVideos": "360° 動画", + "drawerAlbumPage": "アルバム", + "drawerCountryPage": "国", + "drawerTagPage": "タグ", - "chipSortDate": "日付", - "chipSortName": "名前", - "chipSortCount": "アイテム件数", + "sortByDate": "日付", + "sortByName": "名前", + "sortByItemCount": "アイテム件数", + "sortBySize": "サイズ", + "sortByAlbumFileName": "アルバムとファイル名", + "sortByRating": "評価", "albumGroupTier": "階層別", "albumGroupVolume": "ストレージ ボリューム別", @@ -418,13 +416,13 @@ "binPageTitle": "ごみ箱", "searchCollectionFieldHint": "コレクションを検索", - "searchSectionRecent": "最近", - "searchSectionDate": "日付", - "searchSectionAlbums": "アルバム", - "searchSectionCountries": "国", - "searchSectionPlaces": "場所", - "searchSectionTags": "タグ", - "searchSectionRating": "評価", + "searchRecentSectionTitle": "最近", + "searchDateSectionTitle": "日付", + "searchAlbumsSectionTitle": "アルバム", + "searchCountriesSectionTitle": "国", + "searchPlacesSectionTitle": "場所", + "searchTagsSectionTitle": "タグ", + "searchRatingSectionTitle": "評価", "settingsPageTitle": "設定", "settingsSystemDefault": "システム", @@ -433,36 +431,39 @@ "settingsSearchFieldLabel": "検索設定", "settingsSearchEmpty": "一致する設定なし", "settingsActionExport": "エクスポート", + "settingsActionExportDialogTitle": "エクスポート", "settingsActionImport": "インポート", + "settingsActionImportDialogTitle": "インポート", "appExportCovers": "カバー", "appExportFavourites": "お気に入り", "appExportSettings": "設定", - "settingsSectionNavigation": "ナビゲーション", - "settingsHome": "ホーム", + "settingsNavigationSectionTitle": "ナビゲーション", + "settingsHomeTile": "ホーム", + "settingsHomeDialogTitle": "ホーム", "settingsShowBottomNavigationBar": "下部のナビゲーションバーを表示", "settingsKeepScreenOnTile": "画面をオンのままにする", - "settingsKeepScreenOnTitle": "画面をオンのままにする", + "settingsKeepScreenOnDialogTitle": "画面をオンのままにする", "settingsDoubleBackExit": "「戻る」を2回タップして終了", - "settingsConfirmationDialogTile": "確認メッセージ", + "settingsConfirmationTile": "確認メッセージ", "settingsConfirmationDialogTitle": "確認メッセージ", - "settingsConfirmationDialogDeleteItems": "アイテムを完全に削除する前に確認", - "settingsConfirmationDialogMoveToBinItems": "アイテムをごみ箱に移動する前に確認", - "settingsConfirmationDialogMoveUndatedItems": "メタデータ上に日付のないアイテムを移動する前に確認", + "settingsConfirmationBeforeDeleteItems": "アイテムを完全に削除する前に確認", + "settingsConfirmationBeforeMoveToBinItems": "アイテムをごみ箱に移動する前に確認", + "settingsConfirmationBeforeMoveUndatedItems": "メタデータ上に日付のないアイテムを移動する前に確認", "settingsNavigationDrawerTile": "ナビゲーション メニュー", - "settingsNavigationDrawerEditorTitle": "ナビゲーション メニュー", + "settingsNavigationDrawerEditorPageTitle": "ナビゲーション メニュー", "settingsNavigationDrawerBanner": "メニュー項目を長押しして、移動および並べ替え", "settingsNavigationDrawerTabTypes": "タイプ", "settingsNavigationDrawerTabAlbums": "アルバム", "settingsNavigationDrawerTabPages": "ページ", "settingsNavigationDrawerAddAlbum": "アルバムを追加", - "settingsSectionThumbnails": "サムネイル", + "settingsThumbnailSectionTitle": "サムネイル", "settingsThumbnailOverlayTile": "オーバーレイ", - "settingsThumbnailOverlayTitle": "オーバーレイ", + "settingsThumbnailOverlayPageTitle": "オーバーレイ", "settingsThumbnailShowFavouriteIcon": "お気に入りアイコンを表示", "settingsThumbnailShowTagIcon": "タグアイコンを表示", "settingsThumbnailShowLocationIcon": "位置情報アイコンを表示", @@ -472,27 +473,27 @@ "settingsThumbnailShowVideoDuration": "動画の再生時間を表示", "settingsCollectionQuickActionsTile": "クイック アクション", - "settingsCollectionQuickActionEditorTitle": "クイック アクション", + "settingsCollectionQuickActionEditorPageTitle": "クイック アクション", "settingsCollectionQuickActionTabBrowsing": "ブラウズ中", "settingsCollectionQuickActionTabSelecting": "選択中", "settingsCollectionBrowsingQuickActionEditorBanner": "長押ししてボタンを移動しアイテムを閲覧中に表示されるアクションを選択します。", "settingsCollectionSelectionQuickActionEditorBanner": "長押ししてボタンを移動しアイテムを選択中に表示されるアクションを選択します。", - "settingsSectionViewer": "ビューアー", + "settingsViewerSectionTitle": "ビューアー", "settingsViewerUseCutout": "切り取り領域を使用", "settingsViewerMaximumBrightness": "明るさ最大", "settingsMotionPhotoAutoPlay": "モーションフォトを自動再生", "settingsImageBackground": "画像の背景", "settingsViewerQuickActionsTile": "クイックアクション", - "settingsViewerQuickActionEditorTitle": "クイックアクション", + "settingsViewerQuickActionEditorPageTitle": "クイックアクション", "settingsViewerQuickActionEditorBanner": "長押ししてボタンを移動しビューアーで表示されるアクションを選択します。", - "settingsViewerQuickActionEditorDisplayedButtons": "表示ボタン", - "settingsViewerQuickActionEditorAvailableButtons": "利用可能なボタン", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "表示ボタン", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "利用可能なボタン", "settingsViewerQuickActionEmpty": "ボタンなし", "settingsViewerOverlayTile": "オーバーレイ", - "settingsViewerOverlayTitle": "オーバーレイ", + "settingsViewerOverlayPageTitle": "オーバーレイ", "settingsViewerShowOverlayOnOpening": "起動時に表示", "settingsViewerShowMinimap": "小さな地図を表示", "settingsViewerShowInformation": "情報を表示", @@ -502,30 +503,30 @@ "settingsViewerEnableOverlayBlurEffect": "ぼかし効果", "settingsViewerSlideshowTile": "スライドショー", - "settingsViewerSlideshowTitle": "スライドショー", + "settingsViewerSlideshowPageTitle": "スライドショー", "settingsSlideshowRepeat": "繰り返し", "settingsSlideshowShuffle": "シャッフル", "settingsSlideshowFillScreen": "画面いっぱいに表示", "settingsSlideshowTransitionTile": "トランジション", - "settingsSlideshowTransitionTitle": "トランジション", + "settingsSlideshowTransitionDialogTitle": "トランジション", "settingsSlideshowIntervalTile": "間隔", - "settingsSlideshowIntervalTitle": "間隔", + "settingsSlideshowIntervalDialogTitle": "間隔", "settingsSlideshowVideoPlaybackTile": "動画を再生", - "settingsSlideshowVideoPlaybackTitle": "動画再生", + "settingsSlideshowVideoPlaybackDialogTitle": "動画再生", "settingsVideoPageTitle": "動画設定", - "settingsSectionVideo": "動画", + "settingsVideoSectionTitle": "動画", "settingsVideoShowVideos": "動画を表示", "settingsVideoEnableHardwareAcceleration": "ハードウェア アクセラレーション", "settingsVideoEnableAutoPlay": "自動再生", "settingsVideoLoopModeTile": "ループ モード", - "settingsVideoLoopModeTitle": "ループ モード", + "settingsVideoLoopModeDialogTitle": "ループ モード", "settingsSubtitleThemeTile": "字幕", - "settingsSubtitleThemeTitle": "字幕", + "settingsSubtitleThemePageTitle": "字幕", "settingsSubtitleThemeSample": "これはサンプルです。", "settingsSubtitleThemeTextAlignmentTile": "テキストの配置", - "settingsSubtitleThemeTextAlignmentTitle": "テキストの配置", + "settingsSubtitleThemeTextAlignmentDialogTitle": "テキストの配置", "settingsSubtitleThemeTextSize": "テキストのサイズ", "settingsSubtitleThemeShowOutline": "アウトラインと影を表示", "settingsSubtitleThemeTextColor": "テキストの色", @@ -537,13 +538,13 @@ "settingsSubtitleThemeTextAlignmentRight": "右揃え", "settingsVideoControlsTile": "操作", - "settingsVideoControlsTitle": "操作", + "settingsVideoControlsPageTitle": "操作", "settingsVideoButtonsTile": "ボタン", - "settingsVideoButtonsTitle": "ボタン", + "settingsVideoButtonsDialogTitle": "ボタン", "settingsVideoGestureDoubleTapTogglePlay": "2回タップして再生/一時停止", "settingsVideoGestureSideDoubleTapSeek": "画面の角を2回タップして早送り/早戻し", - "settingsSectionPrivacy": "プライバシー", + "settingsPrivacySectionTitle": "プライバシー", "settingsAllowInstalledAppAccess": "アプリのインベントリへのアクセスを許可する", "settingsAllowInstalledAppAccessSubtitle": "アルバム表示の改善に使用されます", "settingsAllowErrorReporting": "匿名でのエラー報告を許可する", @@ -552,52 +553,56 @@ "settingsEnableBinSubtitle": "削除したアイテムを30日間保持します", "settingsHiddenItemsTile": "非表示アイテム", - "settingsHiddenItemsTitle": "非表示アイテム", + "settingsHiddenItemsPageTitle": "非表示アイテム", - "settingsHiddenFiltersTitle": "非表示フィルター", + "settingsHiddenItemsTabFilters": "非表示フィルター", "settingsHiddenFiltersBanner": "非表示のフィルターに一致する写真と動画は、コレクションに表示されません。", "settingsHiddenFiltersEmpty": "非表示フィルターがありません", - "settingsHiddenPathsTitle": "非表示パス", + "settingsHiddenItemsTabPaths": "非表示パス", "settingsHiddenPathsBanner": "これらのフォルダまたはそのサブフォルダにある写真と動画は、コレクションに表示されません。", "addPathTooltip": "パスを追加", "settingsStorageAccessTile": "ストレージへのアクセス", - "settingsStorageAccessTitle": "ストレージへのアクセス", + "settingsStorageAccessPageTitle": "ストレージへのアクセス", "settingsStorageAccessBanner": "ディレクトリによっては、ファイルの編集のためにアクセス許可が必要です。ここには、これまでにアクセスを許可したディレクトリが表示されます。", "settingsStorageAccessEmpty": "許可したアクセスはありません", "settingsStorageAccessRevokeTooltip": "許可を取り消し", - "settingsSectionAccessibility": "アクセシビリティ", + "settingsAccessibilitySectionTitle": "アクセシビリティ", "settingsRemoveAnimationsTile": "アニメーションの削除", - "settingsRemoveAnimationsTitle": "アニメーションの削除", + "settingsRemoveAnimationsDialogTitle": "アニメーションの削除", "settingsTimeToTakeActionTile": "操作までの時間", - "settingsTimeToTakeActionTitle": "操作までの時間", + "settingsTimeToTakeActionDialogTitle": "操作までの時間", - "settingsSectionDisplay": "ディスプレイ", - "settingsThemeBrightness": "テーマ", + "settingsDisplaySectionTitle": "ディスプレイ", + "settingsThemeBrightnessTile": "テーマ", + "settingsThemeBrightnessDialogTitle": "テーマ", "settingsThemeColorHighlights": "カラー強調表示", "settingsThemeEnableDynamicColor": "ダイナミックカラー", "settingsDisplayRefreshRateModeTile": "ディスプレイ リフレッシュ レート", - "settingsDisplayRefreshRateModeTitle": "リフレッシュ レート", + "settingsDisplayRefreshRateModeDialogTitle": "リフレッシュ レート", - "settingsSectionLanguage": "言語と形式", - "settingsLanguage": "言語", + "settingsLanguageSectionTitle": "言語と形式", + "settingsLanguageTile": "言語", + "settingsLanguagePageTitle": "言語", "settingsCoordinateFormatTile": "座標形式", - "settingsCoordinateFormatTitle": "座標形式", + "settingsCoordinateFormatDialogTitle": "座標形式", "settingsUnitSystemTile": "単位", - "settingsUnitSystemTitle": "単位", + "settingsUnitSystemDialogTitle": "単位", "settingsScreenSaverPageTitle": "スクリーンセーバー", "settingsWidgetPageTitle": "フォトフレーム", "settingsWidgetShowOutline": "枠", + "settingsCollectionTile": "コレクション", + "statsPageTitle": "統計", "statsWithGps": "{count, plural, other{位置情報のあるアイテム {count} 件}}", - "statsTopCountries": "上位の国", - "statsTopPlaces": "上位の場所", - "statsTopTags": "上位のタグ", + "statsTopCountriesSectionTitle": "上位の国", + "statsTopPlacesSectionTitle": "上位の場所", + "statsTopTagsSectionTitle": "上位のタグ", "viewerOpenPanoramaButtonLabel": "パノラマを開く", "viewerSetWallpaperButtonLabel": "壁紙設定", @@ -619,7 +624,7 @@ "viewerInfoLabelCoordinates": "座標", "viewerInfoLabelAddress": "アドレス", - "mapStyleTitle": "地図のスタイル", + "mapStyleDialogTitle": "地図のスタイル", "mapStyleTooltip": "地図のスタイルを選択", "mapZoomInTooltip": "ズームイン", "mapZoomOutTooltip": "ズームアウト", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 1d555672f..ea9379b64 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -27,6 +27,7 @@ "actionRemove": "제거", "resetTooltip": "복원", "saveTooltip": "저장", + "pickTooltip": "선택", "doubleBackExitMessage": "종료하려면 한번 더 누르세요.", "doNotAskAgain": "다시 묻지 않기", @@ -87,18 +88,20 @@ "entryInfoActionEditDate": "날짜 및 시간 수정", "entryInfoActionEditLocation": "위치 수정", - "entryInfoActionEditDescription": "설명 수정", + "entryInfoActionEditTitleDescription": "제목 및 설명 수정", "entryInfoActionEditRating": "별점 수정", "entryInfoActionEditTags": "태그 수정", "entryInfoActionRemoveMetadata": "메타데이터 삭제", "filterBinLabel": "휴지통", "filterFavouriteLabel": "즐겨찾기", - "filterLocationEmptyLabel": "장소 없음", - "filterTagEmptyLabel": "태그 없음", + "filterNoDateLabel": "날짜 없음", + "filterNoLocationLabel": "장소 없음", + "filterNoRatingLabel": "별점 없음", + "filterNoTagLabel": "태그 없음", + "filterNoTitleLabel": "제목 없음", "filterOnThisDayLabel": "이 날", "filterRecentlyAddedLabel": "최근 추가된", - "filterRatingUnratedLabel": "별점 없음", "filterRatingRejectedLabel": "거부됨", "filterTypeAnimatedLabel": "애니메이션", "filterTypeMotionPhotoLabel": "모션 사진", @@ -179,16 +182,11 @@ "storageVolumeDescriptionFallbackNonPrimary": "SD 카드", "rootDirectoryDescription": "루트 폴더", "otherDirectoryDescription": "“{name}” 폴더", - "storageAccessDialogTitle": "저장공간 접근", "storageAccessDialogMessage": "파일에 접근하도록 다음 화면에서 “{volume}”의 {directory}를 선택하세요.", - "restrictedAccessDialogTitle": "접근 제한", "restrictedAccessDialogMessage": "“{volume}”의 {directory}에 있는 파일의 접근이 제한됩니다.\n\n기본으로 설치된 갤러리나 파일 관리 앱을 사용해서 다른 폴더로 파일을 이동하세요.", - "notEnoughSpaceDialogTitle": "저장공간 부족", "notEnoughSpaceDialogMessage": "“{volume}”에 필요 공간은 {neededSize}인데 사용 가능한 용량은 {freeSize}만 남아있습니다.", - "missingSystemFilePickerDialogTitle": "기본 파일 선택기 없음", "missingSystemFilePickerDialogMessage": "기본 파일 선택기가 없거나 비활성화딥니다. 파일 선택기를 켜고 다시 시도하세요.", - "unsupportedTypeDialogTitle": "미지원 형식", "unsupportedTypeDialogMessage": "{count, plural, other{이 작업은 다음 항목의 형식을 지원하지 않습니다: {types}.}}", "nameConflictDialogSingleSourceMessage": "이동할 폴더에 이름이 같은 파일이 있습니다.", @@ -197,7 +195,6 @@ "addShortcutDialogLabel": "바로가기 라벨", "addShortcutButtonLabel": "추가", - "noMatchingAppDialogTitle": "처리할 앱 없음", "noMatchingAppDialogMessage": "이 작업을 처리할 수 있는 앱이 없습니다.", "binEntriesConfirmationDialogMessage": "{count, plural, =1{이 항목을 휴지통으로 이동하시겠습니까?} other{항목 {count}개를 휴지통으로 이동하시겠습니까?}}", @@ -226,7 +223,7 @@ "renameEntrySetPageTitle": "이름 변경", "renameEntrySetPagePatternFieldLabel": "이름 양식", "renameEntrySetPageInsertTooltip": "필드 추가", - "renameEntrySetPagePreview": "미리보기", + "renameEntrySetPagePreviewSectionTitle": "미리보기", "renameProcessorCounter": "숫자 증가", "renameProcessorName": "이름", @@ -259,8 +256,6 @@ "locationPickerUseThisLocationButton": "이 위치 사용", - "editEntryDescriptionDialogTitle": "설명", - "editEntryRatingDialogTitle": "별점", "removeEntryMetadataDialogTitle": "메타데이터 삭제", @@ -289,9 +284,10 @@ "menuActionSlideshow": "슬라이드쇼", "menuActionStats": "통계", - "viewDialogTabSort": "정렬", - "viewDialogTabGroup": "묶음", - "viewDialogTabLayout": "배치", + "viewDialogSortSectionTitle": "정렬", + "viewDialogGroupSectionTitle": "묶음", + "viewDialogLayoutSectionTitle": "배치", + "viewDialogReverseSortOrder": "순서를 뒤바꾸기", "tileLayoutGrid": "바둑판", "tileLayoutList": "목록", @@ -308,24 +304,24 @@ "aboutLinkLicense": "라이선스", "aboutLinkPolicy": "개인정보 보호정책", - "aboutBug": "버그 보고", + "aboutBugSectionTitle": "버그 보고", "aboutBugSaveLogInstruction": "앱 로그를 파일에 저장하기", "aboutBugCopyInfoInstruction": "시스템 정보를 복사하기", "aboutBugCopyInfoButton": "복사", "aboutBugReportInstruction": "로그와 시스템 정보를 첨부하여 깃허브에서 이슈를 제출하기", "aboutBugReportButton": "제출", - "aboutCredits": "크레딧", + "aboutCreditsSectionTitle": "크레딧", "aboutCreditsWorldAtlas1": "이 앱은", "aboutCreditsWorldAtlas2": "의 TopoJSON 파일(ISC 라이선스)을 이용합니다.", - "aboutCreditsTranslators": "번역가", + "aboutTranslatorsSectionTitle": "번역가", - "aboutLicenses": "오픈 소스 라이선스", + "aboutLicensesSectionTitle": "오픈 소스 라이선스", "aboutLicensesBanner": "이 앱은 다음의 오픈 소스 패키지와 라이브러리를 이용합니다.", - "aboutLicensesAndroidLibraries": "안드로이드 라이브러리", - "aboutLicensesFlutterPlugins": "플러터 플러그인", - "aboutLicensesFlutterPackages": "플러터 패키지", - "aboutLicensesDartPackages": "다트 패키지", + "aboutLicensesAndroidLibrariesSectionTitle": "안드로이드 라이브러리", + "aboutLicensesFlutterPluginsSectionTitle": "플러터 플러그인", + "aboutLicensesFlutterPackagesSectionTitle": "플러터 패키지", + "aboutLicensesDartPackagesSectionTitle": "다트 패키지", "aboutLicensesShowAllButtonLabel": "라이선스 모두 보기", "policyPageTitle": "개인정보 보호정책", @@ -345,11 +341,6 @@ "collectionSearchTitlesHintText": "제목 검색", - "collectionSortDate": "날짜", - "collectionSortSize": "크기", - "collectionSortName": "이름", - "collectionSortRating": "별점", - "collectionGroupAlbum": "앨범별로", "collectionGroupMonth": "월별로", "collectionGroupDay": "날짜별로", @@ -378,6 +369,8 @@ "collectionSelectSectionTooltip": "묶음 선택", "collectionDeselectSectionTooltip": "묶음 선택 해제", + "drawerAboutButton": "앱 정보", + "drawerSettingsButton": "설정", "drawerCollectionAll": "모든 미디어", "drawerCollectionFavourites": "즐겨찾기", "drawerCollectionImages": "사진", @@ -387,10 +380,25 @@ "drawerCollectionPanoramas": "파노라마", "drawerCollectionRaws": "Raw 이미지", "drawerCollectionSphericalVideos": "360° 동영상", + "drawerAlbumPage": "앨범", + "drawerCountryPage": "국가", + "drawerTagPage": "태그", - "chipSortDate": "날짜", - "chipSortName": "이름", - "chipSortCount": "항목수", + "sortByDate": "날짜", + "sortByName": "이름", + "sortByItemCount": "항목수", + "sortBySize": "크기", + "sortByAlbumFileName": "이름", + "sortByRating": "별점", + + "sortOrderNewestFirst": "최근 날짜순", + "sortOrderOldestFirst": "오래된 날짜순", + "sortOrderAtoZ": "A~Z", + "sortOrderZtoA": "Z~A", + "sortOrderHighestFirst": "높은 별점순", + "sortOrderLowestFirst": "낮은 별점순", + "sortOrderLargestFirst": "큰 파일순", + "sortOrderSmallestFirst": "작은 파일순", "albumGroupTier": "단계별로", "albumGroupVolume": "저장공간별로", @@ -422,13 +430,14 @@ "binPageTitle": "휴지통", "searchCollectionFieldHint": "미디어 검색", - "searchSectionRecent": "최근 검색기록", - "searchSectionDate": "날짜", - "searchSectionAlbums": "앨범", - "searchSectionCountries": "국가", - "searchSectionPlaces": "장소", - "searchSectionTags": "태그", - "searchSectionRating": "별점", + "searchRecentSectionTitle": "최근 검색기록", + "searchDateSectionTitle": "날짜", + "searchAlbumsSectionTitle": "앨범", + "searchCountriesSectionTitle": "국가", + "searchPlacesSectionTitle": "장소", + "searchTagsSectionTitle": "태그", + "searchRatingSectionTitle": "별점", + "searchMetadataSectionTitle": "메타데이터", "settingsPageTitle": "설정", "settingsSystemDefault": "시스템", @@ -437,37 +446,40 @@ "settingsSearchFieldLabel": "설정 검색", "settingsSearchEmpty": "결과가 없습니다", "settingsActionExport": "내보내기", + "settingsActionExportDialogTitle": "내보내기", "settingsActionImport": "가져오기", + "settingsActionImportDialogTitle": "가져오기", "appExportCovers": "대표 이미지", "appExportFavourites": "즐겨찾기", "appExportSettings": "설정", - "settingsSectionNavigation": "탐색", - "settingsHome": "홈", + "settingsNavigationSectionTitle": "탐색", + "settingsHomeTile": "홈", + "settingsHomeDialogTitle": "홈", "settingsShowBottomNavigationBar": "하단 탐색 모음 표시", "settingsKeepScreenOnTile": "화면 자동 꺼짐 방지", - "settingsKeepScreenOnTitle": "화면 자동 꺼짐 방지", + "settingsKeepScreenOnDialogTitle": "화면 자동 꺼짐 방지", "settingsDoubleBackExit": "뒤로가기 두번 눌러 앱 종료하기", - "settingsConfirmationDialogTile": "확정 대화상자", + "settingsConfirmationTile": "확정 대화상자", "settingsConfirmationDialogTitle": "확정 대화상자", - "settingsConfirmationDialogDeleteItems": "항목을 완전히 삭제 시", - "settingsConfirmationDialogMoveToBinItems": "항목을 휴지통으로 이동 시", - "settingsConfirmationDialogMoveUndatedItems": "날짜가 지정되지 않은 항목을 이동 시", + "settingsConfirmationBeforeDeleteItems": "항목을 완전히 삭제 시", + "settingsConfirmationBeforeMoveToBinItems": "항목을 휴지통으로 이동 시", + "settingsConfirmationBeforeMoveUndatedItems": "날짜가 지정되지 않은 항목을 이동 시", "settingsConfirmationAfterMoveToBinItems": "항목을 휴지통으로 이동 후", "settingsNavigationDrawerTile": "탐색 메뉴", - "settingsNavigationDrawerEditorTitle": "탐색 메뉴", + "settingsNavigationDrawerEditorPageTitle": "탐색 메뉴", "settingsNavigationDrawerBanner": "항목을 길게 누른 후 이동하여 탐색 메뉴에 표시될 항목의 순서를 수정하세요.", "settingsNavigationDrawerTabTypes": "유형", "settingsNavigationDrawerTabAlbums": "앨범", "settingsNavigationDrawerTabPages": "페이지", "settingsNavigationDrawerAddAlbum": "앨범 추가", - "settingsSectionThumbnails": "섬네일", + "settingsThumbnailSectionTitle": "섬네일", "settingsThumbnailOverlayTile": "오버레이", - "settingsThumbnailOverlayTitle": "오버레이", + "settingsThumbnailOverlayPageTitle": "오버레이", "settingsThumbnailShowFavouriteIcon": "즐겨찾기 아이콘 표시", "settingsThumbnailShowTagIcon": "태그 아이콘 표시", "settingsThumbnailShowLocationIcon": "위치 아이콘 표시", @@ -477,13 +489,13 @@ "settingsThumbnailShowVideoDuration": "동영상 길이 표시", "settingsCollectionQuickActionsTile": "빠른 작업", - "settingsCollectionQuickActionEditorTitle": "빠른 작업", + "settingsCollectionQuickActionEditorPageTitle": "빠른 작업", "settingsCollectionQuickActionTabBrowsing": "탐색 시", "settingsCollectionQuickActionTabSelecting": "선택 시", "settingsCollectionBrowsingQuickActionEditorBanner": "버튼을 길게 누른 후 이동하여 항목 탐색할 때 표시될 버튼을 선택하세요.", "settingsCollectionSelectionQuickActionEditorBanner": "버튼을 길게 누른 후 이동하여 항목 선택할 때 표시될 버튼을 선택하세요.", - "settingsSectionViewer": "뷰어", + "settingsViewerSectionTitle": "뷰어", "settingsViewerGestureSideTapNext": "화면 측면에서 탭해서 이전/다음 항목 보기", "settingsViewerUseCutout": "컷아웃 영역 사용", "settingsViewerMaximumBrightness": "최대 밝기", @@ -491,14 +503,14 @@ "settingsImageBackground": "이미지 배경", "settingsViewerQuickActionsTile": "빠른 작업", - "settingsViewerQuickActionEditorTitle": "빠른 작업", + "settingsViewerQuickActionEditorPageTitle": "빠른 작업", "settingsViewerQuickActionEditorBanner": "버튼을 길게 누른 후 이동하여 뷰어에 표시될 버튼을 선택하세요.", - "settingsViewerQuickActionEditorDisplayedButtons": "표시될 버튼", - "settingsViewerQuickActionEditorAvailableButtons": "추가 가능한 버튼", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "표시될 버튼", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "추가 가능한 버튼", "settingsViewerQuickActionEmpty": "버튼이 없습니다", "settingsViewerOverlayTile": "오버레이", - "settingsViewerOverlayTitle": "오버레이", + "settingsViewerOverlayPageTitle": "오버레이", "settingsViewerShowOverlayOnOpening": "열릴 때 표시", "settingsViewerShowMinimap": "미니맵 표시", "settingsViewerShowInformation": "상세 정보 표시", @@ -508,30 +520,30 @@ "settingsViewerEnableOverlayBlurEffect": "흐림 효과", "settingsViewerSlideshowTile": "슬라이드쇼", - "settingsViewerSlideshowTitle": "슬라이드쇼", + "settingsViewerSlideshowPageTitle": "슬라이드쇼", "settingsSlideshowRepeat": "반복", "settingsSlideshowShuffle": "순서섞기", "settingsSlideshowFillScreen": "화면 채우기", "settingsSlideshowTransitionTile": "전환 효과", - "settingsSlideshowTransitionTitle": "전환 효과", + "settingsSlideshowTransitionDialogTitle": "전환 효과", "settingsSlideshowIntervalTile": "교체 주기", - "settingsSlideshowIntervalTitle": "교체 주기", + "settingsSlideshowIntervalDialogTitle": "교체 주기", "settingsSlideshowVideoPlaybackTile": "동영상 재생", - "settingsSlideshowVideoPlaybackTitle": "동영상 재생", + "settingsSlideshowVideoPlaybackDialogTitle": "동영상 재생", "settingsVideoPageTitle": "동영상 설정", - "settingsSectionVideo": "동영상", + "settingsVideoSectionTitle": "동영상", "settingsVideoShowVideos": "미디어에 동영상 표시", "settingsVideoEnableHardwareAcceleration": "하드웨어 가속", "settingsVideoEnableAutoPlay": "자동 재생", "settingsVideoLoopModeTile": "반복 모드", - "settingsVideoLoopModeTitle": "반복 모드", + "settingsVideoLoopModeDialogTitle": "반복 모드", "settingsSubtitleThemeTile": "자막", - "settingsSubtitleThemeTitle": "자막", + "settingsSubtitleThemePageTitle": "자막", "settingsSubtitleThemeSample": "샘플입니다.", "settingsSubtitleThemeTextAlignmentTile": "정렬", - "settingsSubtitleThemeTextAlignmentTitle": "정렬", + "settingsSubtitleThemeTextAlignmentDialogTitle": "정렬", "settingsSubtitleThemeTextSize": "글자 크기", "settingsSubtitleThemeShowOutline": "윤곽 및 그림자 표시", "settingsSubtitleThemeTextColor": "글자 색상", @@ -543,13 +555,13 @@ "settingsSubtitleThemeTextAlignmentRight": "오른쪽", "settingsVideoControlsTile": "제어", - "settingsVideoControlsTitle": "제어", + "settingsVideoControlsPageTitle": "제어", "settingsVideoButtonsTile": "버튼", - "settingsVideoButtonsTitle": "버튼", + "settingsVideoButtonsDialogTitle": "버튼", "settingsVideoGestureDoubleTapTogglePlay": "두 번 탭해서 재생이나 일시정지하기", "settingsVideoGestureSideDoubleTapSeek": "화면 측면에서 두 번 탭해서 앞뒤로 가기", - "settingsSectionPrivacy": "개인정보 보호", + "settingsPrivacySectionTitle": "개인정보 보호", "settingsAllowInstalledAppAccess": "설치된 앱의 목록 접근 허용", "settingsAllowInstalledAppAccessSubtitle": "앨범 표시 개선을 위해", "settingsAllowErrorReporting": "오류 보고서 보내기", @@ -558,52 +570,56 @@ "settingsEnableBinSubtitle": "삭제한 항목을 30일 동안 보관하기", "settingsHiddenItemsTile": "숨겨진 항목", - "settingsHiddenItemsTitle": "숨겨진 항목", + "settingsHiddenItemsPageTitle": "숨겨진 항목", - "settingsHiddenFiltersTitle": "숨겨진 필터", + "settingsHiddenItemsTabFilters": "숨겨진 필터", "settingsHiddenFiltersBanner": "이 필터에 맞는 사진과 동영상이 숨겨지고 있으며 이 앱에서 보여지지 않을 것입니다.", "settingsHiddenFiltersEmpty": "숨겨진 필터가 없습니다", - "settingsHiddenPathsTitle": "숨겨진 경로", + "settingsHiddenItemsTabPaths": "숨겨진 경로", "settingsHiddenPathsBanner": "이 경로에 있는 사진과 동영상이 숨겨지고 있으며 이 앱에서 보여지지 않을 것입니다.", "addPathTooltip": "경로 추가", "settingsStorageAccessTile": "저장공간 접근", - "settingsStorageAccessTitle": "저장공간 접근", + "settingsStorageAccessPageTitle": "저장공간 접근", "settingsStorageAccessBanner": "어떤 폴더는 사용자의 허용을 받아야만 앱이 파일에 접근이 가능합니다. 이 화면에 허용을 받은 폴더를 확인할 수 있으며 원하지 않으면 취소할 수 있습니다.", "settingsStorageAccessEmpty": "접근 허용이 없습니다", "settingsStorageAccessRevokeTooltip": "취소", - "settingsSectionAccessibility": "접근성", + "settingsAccessibilitySectionTitle": "접근성", "settingsRemoveAnimationsTile": "애니메이션 삭제", - "settingsRemoveAnimationsTitle": "애니메이션 삭제", + "settingsRemoveAnimationsDialogTitle": "애니메이션 삭제", "settingsTimeToTakeActionTile": "액션 취하기 전 대기 시간", - "settingsTimeToTakeActionTitle": "액션 취하기 전 대기 시간", + "settingsTimeToTakeActionDialogTitle": "액션 취하기 전 대기 시간", - "settingsSectionDisplay": "디스플레이", - "settingsThemeBrightness": "테마", + "settingsDisplaySectionTitle": "디스플레이", + "settingsThemeBrightnessTile": "테마", + "settingsThemeBrightnessDialogTitle": "테마", "settingsThemeColorHighlights": "색 강조", "settingsThemeEnableDynamicColor": "동적 색상", "settingsDisplayRefreshRateModeTile": "화면 재생률", - "settingsDisplayRefreshRateModeTitle": "화면 재생률", + "settingsDisplayRefreshRateModeDialogTitle": "화면 재생률", - "settingsSectionLanguage": "언어 및 표시 형식", - "settingsLanguage": "언어", + "settingsLanguageSectionTitle": "언어 및 표시 형식", + "settingsLanguageTile": "언어", + "settingsLanguagePageTitle": "언어", "settingsCoordinateFormatTile": "좌표 표현", - "settingsCoordinateFormatTitle": "좌표 표현", + "settingsCoordinateFormatDialogTitle": "좌표 표현", "settingsUnitSystemTile": "단위법", - "settingsUnitSystemTitle": "단위법", + "settingsUnitSystemDialogTitle": "단위법", "settingsScreenSaverPageTitle": "화면보호기", "settingsWidgetPageTitle": "사진 액자", "settingsWidgetShowOutline": "윤곽", + "settingsCollectionTile": "미디어", + "statsPageTitle": "통계", "statsWithGps": "{count, plural, other{{count}개 위치가 있음}}", - "statsTopCountries": "국가 랭킹", - "statsTopPlaces": "장소 랭킹", - "statsTopTags": "태그 랭킹", + "statsTopCountriesSectionTitle": "국가 랭킹", + "statsTopPlacesSectionTitle": "장소 랭킹", + "statsTopTagsSectionTitle": "태그 랭킹", "viewerOpenPanoramaButtonLabel": "파노라마 열기", "viewerSetWallpaperButtonLabel": "설정", @@ -614,6 +630,7 @@ "viewerInfoBackToViewerTooltip": "뷰어로", "viewerInfoUnknown": "알 수 없음", + "viewerInfoLabelDescription": "설명", "viewerInfoLabelTitle": "제목", "viewerInfoLabelDate": "날짜", "viewerInfoLabelResolution": "해상도", @@ -625,7 +642,7 @@ "viewerInfoLabelCoordinates": "좌표", "viewerInfoLabelAddress": "주소", - "mapStyleTitle": "지도 유형", + "mapStyleDialogTitle": "지도 유형", "mapStyleTooltip": "지도 유형 선택", "mapZoomInTooltip": "확대", "mapZoomOutTooltip": "축소", diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 6274340e6..4105b5218 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -27,6 +27,7 @@ "actionRemove": "Verwijderen", "resetTooltip": "Resetten", "saveTooltip": "Opslaan", + "pickTooltip": "Kies", "doubleBackExitMessage": "Tap nogmaals “Terug” om te sluiten.", "doNotAskAgain": "Niet opnieuw vragen", @@ -85,20 +86,22 @@ "slideshowActionResume": "Hervatten", "slideshowActionShowInCollection": "Tonen in Collectie", - "entryInfoActionEditDate": "Bewerk Datum & Tijd", - "entryInfoActionEditLocation": "Bewerk Locatie", - "entryInfoActionEditDescription": "Omschrijving wijzigen", + "entryInfoActionEditDate": "Bewerk datum & tijd", + "entryInfoActionEditLocation": "Bewerk locatie", + "entryInfoActionEditTitleDescription": "Wijzig titel & omschrijving", "entryInfoActionEditRating": "Bewerk waardering", "entryInfoActionEditTags": "Bewerk labels", "entryInfoActionRemoveMetadata": "Verwijder metadata", "filterBinLabel": "Prullenbak", "filterFavouriteLabel": "Favorieten", - "filterLocationEmptyLabel": "Geen locatie", - "filterTagEmptyLabel": "Geen label", + "filterNoDateLabel": "Geen datum", + "filterNoLocationLabel": "Geen locatie", + "filterNoRatingLabel": "Geen rating", + "filterNoTagLabel": "Geen label", + "filterNoTitleLabel": "Geen titel", "filterOnThisDayLabel": "Op deze dag", "filterRecentlyAddedLabel": "Recent toegevoegd", - "filterRatingUnratedLabel": "Geen rating", "filterRatingRejectedLabel": "Afgekeurd", "filterTypeAnimatedLabel": "Geanimeerd", "filterTypeMotionPhotoLabel": "Bewegende Foto", @@ -179,16 +182,11 @@ "storageVolumeDescriptionFallbackNonPrimary": "SD kaart", "rootDirectoryDescription": "root map", "otherDirectoryDescription": "“{name}” map", - "storageAccessDialogTitle": "Toegang tot opslag", "storageAccessDialogMessage": "Selecteer de {directory} van “{volume}”, in het volgende scherm om deze app er toegang toe te geven.", - "restrictedAccessDialogTitle": "Beperkte toegang", "restrictedAccessDialogMessage": "Deze applicatie mag geen bestanden wijzigen in de {directory} van “{volume}”,.\n\n Gebruik een vooraf geïnstalleerde filemanager of galerij-app om de items naar een andere map te verplaatsen.", - "notEnoughSpaceDialogTitle": "Te weinig vrije opslagruimte", "notEnoughSpaceDialogMessage": "Deze bewerking heeft {neededSize} vrije ruimte op “{volume}”, nodig om te voltooien, maar er is nog slechts {freeSize} over.", - "missingSystemFilePickerDialogTitle": "Ontbrekende systeembestandkiezer", "missingSystemFilePickerDialogMessage": "De systeembestandskiezer ontbreekt of is uitgeschakeld. Schakel het in en probeer het opnieuw.", - "unsupportedTypeDialogTitle": "Niet-ondersteunde Bestandstypen", "unsupportedTypeDialogMessage": "{count, plural, other{Deze bewerking wordt niet ondersteund voor items van het volgende bestandstype: {types}.}}", "nameConflictDialogSingleSourceMessage": "Sommige bestanden in de doelmap hebben dezelfde naam.", @@ -197,7 +195,6 @@ "addShortcutDialogLabel": "Label snelkoppeling", "addShortcutButtonLabel": "TOEVOEGEN", - "noMatchingAppDialogTitle": "Geen overeenkomende applicatie", "noMatchingAppDialogMessage": "Er zijn geen apps die dit ondersteunen.", "binEntriesConfirmationDialogMessage": "{count, plural, =1{Dit item naar de prullenbak verplaatsen??} other{Verplaats deze {count} items naar de prullenbak?}}", @@ -226,7 +223,7 @@ "renameEntrySetPageTitle": "Hernoemen", "renameEntrySetPagePatternFieldLabel": "Naamgevingspatroon", "renameEntrySetPageInsertTooltip": "Veld invoegen", - "renameEntrySetPagePreview": "Voorbeeld", + "renameEntrySetPagePreviewSectionTitle": "Voorbeeld", "renameProcessorCounter": "Teller", "renameProcessorName": "Naam", @@ -259,8 +256,6 @@ "locationPickerUseThisLocationButton": "Gebruik deze locatie", - "editEntryDescriptionDialogTitle": "Omschrijving", - "editEntryRatingDialogTitle": "Beoordeling", "removeEntryMetadataDialogTitle": "Verwijderen metadata", @@ -289,9 +284,10 @@ "menuActionSlideshow": "Diavoorstelling", "menuActionStats": "Statistieken", - "viewDialogTabSort": "Sorteer", - "viewDialogTabGroup": "Groeperen", - "viewDialogTabLayout": "Layout", + "viewDialogSortSectionTitle": "Sorteer", + "viewDialogGroupSectionTitle": "Groeperen", + "viewDialogLayoutSectionTitle": "Layout", + "viewDialogReverseSortOrder": "Draai sorteerrichting om", "tileLayoutGrid": "Raster", "tileLayoutList": "Lijst", @@ -308,24 +304,24 @@ "aboutLinkLicense": "Licentie", "aboutLinkPolicy": "Privacy Policy", - "aboutBug": "Bug Reporteren", + "aboutBugSectionTitle": "Bug Reporteren", "aboutBugSaveLogInstruction": "Sla applicatielogs op in een bestand", "aboutBugCopyInfoInstruction": "Kopieer systeem informatie", "aboutBugCopyInfoButton": "Kopieer", "aboutBugReportInstruction": "Reporteer op GitHub met de logs en systeeminformatie", "aboutBugReportButton": "Reporteer", - "aboutCredits": "Credits", + "aboutCreditsSectionTitle": "Credits", "aboutCreditsWorldAtlas1": "Deze applicatie gebruikt een TopoJSON-bestand van", "aboutCreditsWorldAtlas2": "Gebruik makend van de ISC License.", - "aboutCreditsTranslators": "Vdertalers", + "aboutTranslatorsSectionTitle": "Vdertalers", - "aboutLicenses": "Open-Source Licenties", + "aboutLicensesSectionTitle": "Open-Source Licenties", "aboutLicensesBanner": "Deze app maakt gebruik van de volgende open-sourcepakketten en bibliotheken.", - "aboutLicensesAndroidLibraries": "Android bibliotheken", - "aboutLicensesFlutterPlugins": "Flutter Plugins", - "aboutLicensesFlutterPackages": "Flutter Packages", - "aboutLicensesDartPackages": "Dart Packages", + "aboutLicensesAndroidLibrariesSectionTitle": "Android bibliotheken", + "aboutLicensesFlutterPluginsSectionTitle": "Flutter Plugins", + "aboutLicensesFlutterPackagesSectionTitle": "Flutter Packages", + "aboutLicensesDartPackagesSectionTitle": "Dart Packages", "aboutLicensesShowAllButtonLabel": "Laat alle licenties zien", "policyPageTitle": "Privacy Policy", @@ -345,11 +341,6 @@ "collectionSearchTitlesHintText": "Zoek op titel", - "collectionSortDate": "Op datum", - "collectionSortSize": "Op grootte", - "collectionSortName": "Op album- en bestandsnaam", - "collectionSortRating": "Op rating", - "collectionGroupAlbum": "Op Albumnaam", "collectionGroupMonth": "Op maand", "collectionGroupDay": "Op dag", @@ -378,6 +369,8 @@ "collectionSelectSectionTooltip": "Selecteer sectie", "collectionDeselectSectionTooltip": "Deselecteer sectie", + "drawerAboutButton": "Over", + "drawerSettingsButton": "Instellingen", "drawerCollectionAll": "Alle verzamelingen", "drawerCollectionFavourites": "Favourieten", "drawerCollectionImages": "Afbeeldingen", @@ -387,10 +380,25 @@ "drawerCollectionPanoramas": "Panoramas", "drawerCollectionRaws": "Raw foto’s", "drawerCollectionSphericalVideos": "360° video’s", + "drawerAlbumPage": "Albums", + "drawerCountryPage": "Landen", + "drawerTagPage": "Labels", - "chipSortDate": "Op datum", - "chipSortName": "Op naam", - "chipSortCount": "Op aantal items", + "sortByDate": "Op datum", + "sortByName": "Op naam", + "sortByItemCount": "Op aantal items", + "sortBySize": "Op grootte", + "sortByAlbumFileName": "Op album- en bestandsnaam", + "sortByRating": "Op rating", + + "sortOrderNewestFirst": "Nieuwste eerst", + "sortOrderOldestFirst": "Oudste eerst", + "sortOrderAtoZ": "A tot Z", + "sortOrderZtoA": "Z tot A", + "sortOrderHighestFirst": "Hoogste eerst", + "sortOrderLowestFirst": "Laagste eerst", + "sortOrderLargestFirst": "Grootste eerst", + "sortOrderSmallestFirst": "Kleinste eerst", "albumGroupTier": "Op rang", "albumGroupVolume": "Op opslagvolume", @@ -422,13 +430,14 @@ "binPageTitle": "Prullenbak", "searchCollectionFieldHint": "Doorzoek collectie", - "searchSectionRecent": "Recent", - "searchSectionDate": "Datum", - "searchSectionAlbums": "Albums", - "searchSectionCountries": "Landen", - "searchSectionPlaces": "Plaatsen", - "searchSectionTags": "Labels", - "searchSectionRating": "Beoordeling", + "searchRecentSectionTitle": "Recent", + "searchDateSectionTitle": "Datum", + "searchAlbumsSectionTitle": "Albums", + "searchCountriesSectionTitle": "Landen", + "searchPlacesSectionTitle": "Plaatsen", + "searchTagsSectionTitle": "Labels", + "searchRatingSectionTitle": "Beoordeling", + "searchMetadataSectionTitle": "Metadata", "settingsPageTitle": "Instellingen", "settingsSystemDefault": "Systeem", @@ -437,37 +446,40 @@ "settingsSearchFieldLabel": "Instellingen doorzoeken", "settingsSearchEmpty": "Geen instellingen gevonden", "settingsActionExport": "Exporteer", + "settingsActionExportDialogTitle": "Exporteer", "settingsActionImport": "Importeer", + "settingsActionImportDialogTitle": "Importeer", "appExportCovers": "Omslagen", "appExportFavourites": "Favorieten", "appExportSettings": "Instellingen", - "settingsSectionNavigation": "Navigatie", - "settingsHome": "Startscherm", + "settingsNavigationSectionTitle": "Navigatie", + "settingsHomeTile": "Startscherm", + "settingsHomeDialogTitle": "Startscherm", "settingsShowBottomNavigationBar": "Laat onderste navigatiebalk zien", "settingsKeepScreenOnTile": "Houd het scherm aan", - "settingsKeepScreenOnTitle": "Houd het scherm aan", + "settingsKeepScreenOnDialogTitle": "Houd het scherm aan", "settingsDoubleBackExit": "Tik twee keer op “terug” om af te sluiten", - "settingsConfirmationDialogTile": "Bevestigingsscherm", + "settingsConfirmationTile": "Bevestigingsscherm", "settingsConfirmationDialogTitle": "Bevestigingsschermen", - "settingsConfirmationDialogDeleteItems": "Bevestig voordat je items voor altijd verwijdert", - "settingsConfirmationDialogMoveToBinItems": "Bevestig voordat u items naar de prullenbak verplaatst", - "settingsConfirmationDialogMoveUndatedItems": "Bevestigvoordat u ongedateerde items verplaatst", + "settingsConfirmationBeforeDeleteItems": "Bevestig voordat je items voor altijd verwijdert", + "settingsConfirmationBeforeMoveToBinItems": "Bevestig voordat u items naar de prullenbak verplaatst", + "settingsConfirmationBeforeMoveUndatedItems": "Bevestigvoordat u ongedateerde items verplaatst", "settingsConfirmationAfterMoveToBinItems": "Toon bevestigingsbericht na het verplaatsen van items naar de prullenbak", "settingsNavigationDrawerTile": "Navigatiemenu", - "settingsNavigationDrawerEditorTitle": "Navigatiemenu", + "settingsNavigationDrawerEditorPageTitle": "Navigatiemenu", "settingsNavigationDrawerBanner": "Houd ingedrukt om menu-items te verplaatsen en opnieuw te ordenen.", "settingsNavigationDrawerTabTypes": "Typen", "settingsNavigationDrawerTabAlbums": "Albums", "settingsNavigationDrawerTabPages": "Pagina’s", "settingsNavigationDrawerAddAlbum": "Album toevoegen", - "settingsSectionThumbnails": "Miniaturen", + "settingsThumbnailSectionTitle": "Miniaturen", "settingsThumbnailOverlayTile": "Overlay", - "settingsThumbnailOverlayTitle": "Overlay", + "settingsThumbnailOverlayPageTitle": "Overlay", "settingsThumbnailShowFavouriteIcon": "Favorieten icoon zichtbaar", "settingsThumbnailShowTagIcon": "Label icoon zichtbaar", "settingsThumbnailShowLocationIcon": "Locatie icoon zichtbaar", @@ -477,13 +489,13 @@ "settingsThumbnailShowVideoDuration": "Videoduur zichtbaar", "settingsCollectionQuickActionsTile": "Snelle bewerkingen", - "settingsCollectionQuickActionEditorTitle": "Snelle bewerkingen", + "settingsCollectionQuickActionEditorPageTitle": "Snelle bewerkingen", "settingsCollectionQuickActionTabBrowsing": "Blader", "settingsCollectionQuickActionTabSelecting": "Selecteren", "settingsCollectionBrowsingQuickActionEditorBanner": "Houd ingedrukt om knoppen te verplaatsen en te selecteren welke acties worden weergegeven bij het bladeren door items.", "settingsCollectionSelectionQuickActionEditorBanner": "Houd ingedrukt om knoppen te verplaatsen en te selecteren welke acties worden weergegeven bij het selecteren van items.", - "settingsSectionViewer": "Voorbeeld", + "settingsViewerSectionTitle": "Voorbeeld", "settingsViewerGestureSideTapNext": "Druk op het scherm om het vorige/volgende item weer te geven", "settingsViewerUseCutout": "Uitgesneden gebied gebruiken", "settingsViewerMaximumBrightness": "Maximale helderheid", @@ -491,14 +503,14 @@ "settingsImageBackground": "Afbeeldingsachtergrond", "settingsViewerQuickActionsTile": "Snelle bewerkingen", - "settingsViewerQuickActionEditorTitle": "Snelle bewerkingen", + "settingsViewerQuickActionEditorPageTitle": "Snelle bewerkingen", "settingsViewerQuickActionEditorBanner": "Houd ingedrukt om knoppen te verplaatsen en te selecteren welke acties in de viewer worden weergegeven.", - "settingsViewerQuickActionEditorDisplayedButtons": "Zichtbare knoppen", - "settingsViewerQuickActionEditorAvailableButtons": "Beschikbare knoppen", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Zichtbare knoppen", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Beschikbare knoppen", "settingsViewerQuickActionEmpty": "Geen knoppen", "settingsViewerOverlayTile": "Overlay", - "settingsViewerOverlayTitle": "Overlay", + "settingsViewerOverlayPageTitle": "Overlay", "settingsViewerShowOverlayOnOpening": "Zichtbaar bij openen", "settingsViewerShowMinimap": "Laat kleine kaart zien", "settingsViewerShowInformation": "Laat informatie zien", @@ -508,30 +520,30 @@ "settingsViewerEnableOverlayBlurEffect": "Vervagingseffect", "settingsViewerSlideshowTile": "Diavoorstelling", - "settingsViewerSlideshowTitle": "Diavoorstelling", + "settingsViewerSlideshowPageTitle": "Diavoorstelling", "settingsSlideshowRepeat": "Herhalen", "settingsSlideshowShuffle": "Shuffle", "settingsSlideshowFillScreen": "Volledig scherm", "settingsSlideshowTransitionTile": "Overgang", - "settingsSlideshowTransitionTitle": "Overgang", + "settingsSlideshowTransitionDialogTitle": "Overgang", "settingsSlideshowIntervalTile": "Interval", - "settingsSlideshowIntervalTitle": "Interval", + "settingsSlideshowIntervalDialogTitle": "Interval", "settingsSlideshowVideoPlaybackTile": "Video afspelen", - "settingsSlideshowVideoPlaybackTitle": "Video afspelen", + "settingsSlideshowVideoPlaybackDialogTitle": "Video afspelen", "settingsVideoPageTitle": "Video Instellingen", - "settingsSectionVideo": "Video", + "settingsVideoSectionTitle": "Video", "settingsVideoShowVideos": "Videos", "settingsVideoEnableHardwareAcceleration": "Hardware acceleratie", "settingsVideoEnableAutoPlay": "Automatisch afspelen", "settingsVideoLoopModeTile": "Herhaald afspelen", - "settingsVideoLoopModeTitle": "Herhaald afspelen", + "settingsVideoLoopModeDialogTitle": "Herhaald afspelen", "settingsSubtitleThemeTile": "Ondertiteling", - "settingsSubtitleThemeTitle": "Ondertiteling", + "settingsSubtitleThemePageTitle": "Ondertiteling", "settingsSubtitleThemeSample": "Dit is een voorbeeld", "settingsSubtitleThemeTextAlignmentTile": "Tekst uitlijnen", - "settingsSubtitleThemeTextAlignmentTitle": "Tekst uitlijnen", + "settingsSubtitleThemeTextAlignmentDialogTitle": "Tekst uitlijnen", "settingsSubtitleThemeTextSize": "Tekstgroote", "settingsSubtitleThemeShowOutline": "Laat omtrek en schaduw zien", "settingsSubtitleThemeTextColor": "Tekstkleur", @@ -543,13 +555,13 @@ "settingsSubtitleThemeTextAlignmentRight": "Rechts", "settingsVideoControlsTile": "Bediening", - "settingsVideoControlsTitle": "Bediening", + "settingsVideoControlsPageTitle": "Bediening", "settingsVideoButtonsTile": "Knoppen", - "settingsVideoButtonsTitle": "Knoppen", + "settingsVideoButtonsDialogTitle": "Knoppen", "settingsVideoGestureDoubleTapTogglePlay": "Dubbeltik om te spelen/pauzeren", "settingsVideoGestureSideDoubleTapSeek": "Dubbeltik op schermranden om achteruit/vooruit te zoeken", - "settingsSectionPrivacy": "Privacy", + "settingsPrivacySectionTitle": "Privacy", "settingsAllowInstalledAppAccess": "Toegang tot app-inventaris toestaan", "settingsAllowInstalledAppAccessSubtitle": "Gebruikt om de albumweergave te verbeteren", "settingsAllowErrorReporting": "Anonieme foutrapportage toestaan", @@ -558,52 +570,56 @@ "settingsEnableBinSubtitle": "Bewaar verwijderde items 30 dagen", "settingsHiddenItemsTile": "Verborgen items", - "settingsHiddenItemsTitle": "Verborgen Items", + "settingsHiddenItemsPageTitle": "Verborgen Items", - "settingsHiddenFiltersTitle": "Verborgen Filters", + "settingsHiddenItemsTabFilters": "Verborgen Filters", "settingsHiddenFiltersBanner": "Foto’s en video’s die overeenkomen met verborgen filters, worden niet weergegeven in uw verzameling.", "settingsHiddenFiltersEmpty": "Geen verborgen filters", - "settingsHiddenPathsTitle": "Verborgen paden", + "settingsHiddenItemsTabPaths": "Verborgen paden", "settingsHiddenPathsBanner": "Foto’s en video’s in deze mappen, of een van hun submappen, verschijnen niet in uw verzameling.", "addPathTooltip": "Pad toevoegen", "settingsStorageAccessTile": "Toegang tot opslag", - "settingsStorageAccessTitle": "Toegang tot opslag", + "settingsStorageAccessPageTitle": "Toegang tot opslag", "settingsStorageAccessBanner": "Sommige mappen vereisen een expliciete toegangstoekenning om bestanden erin te wijzigen. U kunt hier directory’s bekijken waartoe u eerder toegang heeft verleend.", "settingsStorageAccessEmpty": "Geen toegang verleend", "settingsStorageAccessRevokeTooltip": "Herroepen", - "settingsSectionAccessibility": "Toegankelijkheid", + "settingsAccessibilitySectionTitle": "Toegankelijkheid", "settingsRemoveAnimationsTile": "Animaties verwijderen", - "settingsRemoveAnimationsTitle": "Animaties verwijderen", + "settingsRemoveAnimationsDialogTitle": "Animaties verwijderen", "settingsTimeToTakeActionTile": "Tijd om actie te ondernemen", - "settingsTimeToTakeActionTitle": "Tijd om actie te ondernemen", + "settingsTimeToTakeActionDialogTitle": "Tijd om actie te ondernemen", - "settingsSectionDisplay": "Scherm", - "settingsThemeBrightness": "Thema", + "settingsDisplaySectionTitle": "Scherm", + "settingsThemeBrightnessTile": "Thema", + "settingsThemeBrightnessDialogTitle": "Thema", "settingsThemeColorHighlights": "Kleur highlights", "settingsThemeEnableDynamicColor": "Dynamische kleur", "settingsDisplayRefreshRateModeTile": "Vernieuwingsfrequentie weergeven", - "settingsDisplayRefreshRateModeTitle": "Vernieuwingsfrequentie", + "settingsDisplayRefreshRateModeDialogTitle": "Vernieuwingsfrequentie", - "settingsSectionLanguage": "Taal & landinstellingen", - "settingsLanguage": "Taal", - "settingsCoordinateFormatTile": "Coördineer formaat", - "settingsCoordinateFormatTitle": "Coördineer formaat", + "settingsLanguageSectionTitle": "Taal & landinstellingen", + "settingsLanguageTile": "Taal", + "settingsLanguagePageTitle": "Taal", + "settingsCoordinateFormatTile": "Coördinaten format", + "settingsCoordinateFormatDialogTitle": "Coördinaten format", "settingsUnitSystemTile": "Eenheden", - "settingsUnitSystemTitle": "Eenheden", + "settingsUnitSystemDialogTitle": "Eenheden", "settingsScreenSaverPageTitle": "Schermbeveiliging", "settingsWidgetPageTitle": "Foto Lijstje", "settingsWidgetShowOutline": "Contour", + "settingsCollectionTile": "Verzameling", + "statsPageTitle": "Stats", "statsWithGps": "{count, plural, =1{1 item met locatie} other{{count} items met locatie}}", - "statsTopCountries": "Top Landen", - "statsTopPlaces": "Top Plaatsen", - "statsTopTags": "Top Labels", + "statsTopCountriesSectionTitle": "Top Landen", + "statsTopPlacesSectionTitle": "Top Plaatsen", + "statsTopTagsSectionTitle": "Top Labels", "viewerOpenPanoramaButtonLabel": "OPEN PANORAMA", "viewerSetWallpaperButtonLabel": "ALS ACHTERGROND INSTELLEN", @@ -614,6 +630,7 @@ "viewerInfoBackToViewerTooltip": "Terug naar viewer", "viewerInfoUnknown": "onbekendd", + "viewerInfoLabelDescription": "Omschrijving", "viewerInfoLabelTitle": "Titel", "viewerInfoLabelDate": "Datum", "viewerInfoLabelResolution": "Resolutie", @@ -625,7 +642,7 @@ "viewerInfoLabelCoordinates": "Coördinaten", "viewerInfoLabelAddress": "Adres", - "mapStyleTitle": "Kaartstijl", + "mapStyleDialogTitle": "Kaartstijl", "mapStyleTooltip": "Selecteer kaart stijl", "mapZoomInTooltip": "Inzoomen", "mapZoomOutTooltip": "Uitzoomen", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index e8f129ec7..f70c7674c 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -27,6 +27,7 @@ "actionRemove": "Remover", "resetTooltip": "Resetar", "saveTooltip": "Salve", + "pickTooltip": "Escolher", "doubleBackExitMessage": "Toque em “voltar” novamente para sair.", "doNotAskAgain": "Não pergunte novamente", @@ -87,18 +88,17 @@ "entryInfoActionEditDate": "Editar data e hora", "entryInfoActionEditLocation": "Editar localização", - "entryInfoActionEditDescription": "Editar descrição", "entryInfoActionEditRating": "Editar classificação", "entryInfoActionEditTags": "Editar etiquetas", "entryInfoActionRemoveMetadata": "Remover metadados", "filterBinLabel": "Lixeira", "filterFavouriteLabel": "Favorito", - "filterLocationEmptyLabel": "Não localizado", - "filterTagEmptyLabel": "Sem etiqueta", + "filterNoLocationLabel": "Não localizado", + "filterNoRatingLabel": "Sem classificação", + "filterNoTagLabel": "Sem etiqueta", "filterOnThisDayLabel": "Neste dia", "filterRecentlyAddedLabel": "Adicionado recentemente", - "filterRatingUnratedLabel": "Sem classificação", "filterRatingRejectedLabel": "Rejeitado", "filterTypeAnimatedLabel": "Animado", "filterTypeMotionPhotoLabel": "Foto em movimento", @@ -179,16 +179,11 @@ "storageVolumeDescriptionFallbackNonPrimary": "cartão SD", "rootDirectoryDescription": "diretório raiz", "otherDirectoryDescription": "diretório “{name}”", - "storageAccessDialogTitle": "Acesso de armazenamento", "storageAccessDialogMessage": "Selecione o {directory} de “{volume}” na próxima tela para dar acesso a este aplicativo.", - "restrictedAccessDialogTitle": "Acesso restrito", "restrictedAccessDialogMessage": "Este aplicativo não tem permissão para modificar arquivos no {directory} de “{volume}”.\n\nUse um gerenciador de arquivos ou aplicativo de galeria pré-instalado para mover os itens para outro diretório.", - "notEnoughSpaceDialogTitle": "Espaço insuficiente", "notEnoughSpaceDialogMessage": "Esta operação precisa {neededSize} de espaço livre em “{volume}” para completar, mas só {freeSize} restantes.", - "missingSystemFilePickerDialogTitle": "Seletor de arquivos do sistema ausente", "missingSystemFilePickerDialogMessage": "O seletor de arquivos do sistema está ausente ou desabilitado. Por favor, habilite e tente novamente.", - "unsupportedTypeDialogTitle": "Tipos não suportados", "unsupportedTypeDialogMessage": "{count, plural, =1{Esta operação não é suportada para itens do seguinte tipo: {types}.} other{Esta operação não é suportada para itens dos seguintes tipos: {types}.}}", "nameConflictDialogSingleSourceMessage": "Alguns arquivos na pasta de destino têm o mesmo nome.", @@ -197,7 +192,6 @@ "addShortcutDialogLabel": "Rótulo de atalho", "addShortcutButtonLabel": "ADICIONAR", - "noMatchingAppDialogTitle": "Nenhum aplicativo correspondente", "noMatchingAppDialogMessage": "Não há aplicativos que possam lidar com isso.", "binEntriesConfirmationDialogMessage": "{count, plural, =1{Mover esse item para a lixeira?} other{Mova estes {count} itens para a lixeira?}}", @@ -226,7 +220,7 @@ "renameEntrySetPageTitle": "Renomear", "renameEntrySetPagePatternFieldLabel": "Padrão de nomeação", "renameEntrySetPageInsertTooltip": "Inserir campo", - "renameEntrySetPagePreview": "Visualizar", + "renameEntrySetPagePreviewSectionTitle": "Visualizar", "renameProcessorCounter": "Contador", "renameProcessorName": "Nome", @@ -259,8 +253,6 @@ "locationPickerUseThisLocationButton": "Usar essa localização", - "editEntryDescriptionDialogTitle": "Descrição", - "editEntryRatingDialogTitle": "Avaliação", "removeEntryMetadataDialogTitle": "Remoção de metadados", @@ -289,9 +281,9 @@ "menuActionSlideshow": "Apresentação de slides", "menuActionStats": "Estatísticas", - "viewDialogTabSort": "Organizar", - "viewDialogTabGroup": "Grupo", - "viewDialogTabLayout": "Layout", + "viewDialogSortSectionTitle": "Organizar", + "viewDialogGroupSectionTitle": "Grupo", + "viewDialogLayoutSectionTitle": "Layout", "tileLayoutGrid": "Grid", "tileLayoutList": "Lista", @@ -308,24 +300,24 @@ "aboutLinkLicense": "Licença", "aboutLinkPolicy": "Política de Privacidade", - "aboutBug": "Relatório de erro", + "aboutBugSectionTitle": "Relatório de erro", "aboutBugSaveLogInstruction": "Salvar registros de aplicativos em um arquivo", "aboutBugCopyInfoInstruction": "Copiar informações do sistema", "aboutBugCopyInfoButton": "Copiar", "aboutBugReportInstruction": "Relatório no GitHub com os logs e informações do sistema", "aboutBugReportButton": "Relatório", - "aboutCredits": "Créditos", + "aboutCreditsSectionTitle": "Créditos", "aboutCreditsWorldAtlas1": "Este aplicativo usa um arquivo de TopoJSON", "aboutCreditsWorldAtlas2": "sob licença ISC.", - "aboutCreditsTranslators": "Tradutores", + "aboutTranslatorsSectionTitle": "Tradutores", - "aboutLicenses": "Licenças de código aberto", + "aboutLicensesSectionTitle": "Licenças de código aberto", "aboutLicensesBanner": "Este aplicativo usa os seguintes pacotes e bibliotecas de código aberto.", - "aboutLicensesAndroidLibraries": "Bibliotecas Android", - "aboutLicensesFlutterPlugins": "Plug-ins Flutter", - "aboutLicensesFlutterPackages": "Pacotes Flutter", - "aboutLicensesDartPackages": "Pacotes Dart", + "aboutLicensesAndroidLibrariesSectionTitle": "Bibliotecas Android", + "aboutLicensesFlutterPluginsSectionTitle": "Plug-ins Flutter", + "aboutLicensesFlutterPackagesSectionTitle": "Pacotes Flutter", + "aboutLicensesDartPackagesSectionTitle": "Pacotes Dart", "aboutLicensesShowAllButtonLabel": "Mostrar todas as licenças", "policyPageTitle": "Política de Privacidade", @@ -345,11 +337,6 @@ "collectionSearchTitlesHintText": "Pesquisar títulos", - "collectionSortDate": "Por data", - "collectionSortSize": "Por tamanho", - "collectionSortName": "Por álbum e nome de arquivo", - "collectionSortRating": "Por classificação", - "collectionGroupAlbum": "Por álbum", "collectionGroupMonth": "Por mês", "collectionGroupDay": "Por dia", @@ -378,6 +365,8 @@ "collectionSelectSectionTooltip": "Selecionar seção", "collectionDeselectSectionTooltip": "Desmarcar seção", + "drawerAboutButton": "Sobre", + "drawerSettingsButton": "Configurações", "drawerCollectionAll": "Toda a coleção", "drawerCollectionFavourites": "Favoritos", "drawerCollectionImages": "Imagens", @@ -387,10 +376,16 @@ "drawerCollectionPanoramas": "Panoramas", "drawerCollectionRaws": "Fotos Raw", "drawerCollectionSphericalVideos": "360° Videos", + "drawerAlbumPage": "Álbuns", + "drawerCountryPage": "Países", + "drawerTagPage": "Etiquetas", - "chipSortDate": "Por data", - "chipSortName": "Por nome", - "chipSortCount": "Por contagem de itens", + "sortByDate": "Por data", + "sortByName": "Por nome", + "sortByItemCount": "Por contagem de itens", + "sortBySize": "Por tamanho", + "sortByAlbumFileName": "Por álbum e nome de arquivo", + "sortByRating": "Por classificação", "albumGroupTier": "Por nível", "albumGroupVolume": "Por volume de armazenamento", @@ -422,13 +417,13 @@ "binPageTitle": "Lixeira", "searchCollectionFieldHint": "Pesquisar coleção", - "searchSectionRecent": "Recente", - "searchSectionDate": "Data", - "searchSectionAlbums": "Álbuns", - "searchSectionCountries": "Países", - "searchSectionPlaces": "Locais", - "searchSectionTags": "Etiquetas", - "searchSectionRating": "Classificações", + "searchRecentSectionTitle": "Recente", + "searchDateSectionTitle": "Data", + "searchAlbumsSectionTitle": "Álbuns", + "searchCountriesSectionTitle": "Países", + "searchPlacesSectionTitle": "Locais", + "searchTagsSectionTitle": "Etiquetas", + "searchRatingSectionTitle": "Classificações", "settingsPageTitle": "Configurações", "settingsSystemDefault": "Sistema", @@ -437,37 +432,40 @@ "settingsSearchFieldLabel": "Pesquisar configuração", "settingsSearchEmpty": "Nenhuma configuração correspondente", "settingsActionExport": "Exportar", + "settingsActionExportDialogTitle": "Exportar", "settingsActionImport": "Importar", + "settingsActionImportDialogTitle": "Importar", "appExportCovers": "Capas", "appExportFavourites": "Favoritos", "appExportSettings": "Configurações", - "settingsSectionNavigation": "Navegação", - "settingsHome": "Início", + "settingsNavigationSectionTitle": "Navegação", + "settingsHomeTile": "Início", + "settingsHomeDialogTitle": "Início", "settingsShowBottomNavigationBar": "Mostrar barra de navegação inferior", "settingsKeepScreenOnTile": "Manter a tela ligada", - "settingsKeepScreenOnTitle": "Manter a tela ligada", + "settingsKeepScreenOnDialogTitle": "Manter a tela ligada", "settingsDoubleBackExit": "Toque em “voltar” duas vezes para sair", - "settingsConfirmationDialogTile": "Caixas de diálogo de confirmação", + "settingsConfirmationTile": "Caixas de diálogo de confirmação", "settingsConfirmationDialogTitle": "Caixas de diálogo de confirmação", - "settingsConfirmationDialogDeleteItems": "Pergunte antes de excluir itens para sempre", - "settingsConfirmationDialogMoveToBinItems": "Pergunte antes de mover itens para a lixeira", - "settingsConfirmationDialogMoveUndatedItems": "Pergunte antes de mover itens sem data de metadados", + "settingsConfirmationBeforeDeleteItems": "Pergunte antes de excluir itens para sempre", + "settingsConfirmationBeforeMoveToBinItems": "Pergunte antes de mover itens para a lixeira", + "settingsConfirmationBeforeMoveUndatedItems": "Pergunte antes de mover itens sem data de metadados", "settingsConfirmationAfterMoveToBinItems": "Mostrar mensagem depois de mover itens para a lixeira", "settingsNavigationDrawerTile": "Menu de navegação", - "settingsNavigationDrawerEditorTitle": "Menu de navegação", + "settingsNavigationDrawerEditorPageTitle": "Menu de navegação", "settingsNavigationDrawerBanner": "Toque e segure para mover e reordenar os itens do menu.", "settingsNavigationDrawerTabTypes": "Tipos", "settingsNavigationDrawerTabAlbums": "Álbuns", "settingsNavigationDrawerTabPages": "Páginas", "settingsNavigationDrawerAddAlbum": "Adicionar álbum", - "settingsSectionThumbnails": "Miniaturas", + "settingsThumbnailSectionTitle": "Miniaturas", "settingsThumbnailOverlayTile": "Sobreposição", - "settingsThumbnailOverlayTitle": "Sobreposição", + "settingsThumbnailOverlayPageTitle": "Sobreposição", "settingsThumbnailShowFavouriteIcon": "Mostrar ícone favorito", "settingsThumbnailShowTagIcon": "Mostrar ícone de etiqueta", "settingsThumbnailShowLocationIcon": "Mostrar ícone de localização", @@ -477,13 +475,13 @@ "settingsThumbnailShowVideoDuration": "Mostrar duração do vídeo", "settingsCollectionQuickActionsTile": "Ações rápidas", - "settingsCollectionQuickActionEditorTitle": "Ações rápidas", + "settingsCollectionQuickActionEditorPageTitle": "Ações rápidas", "settingsCollectionQuickActionTabBrowsing": "Navegando", "settingsCollectionQuickActionTabSelecting": "Selecionando", "settingsCollectionBrowsingQuickActionEditorBanner": "Toque e segure para mover os botões e selecionar quais ações são exibidas ao navegar pelos itens.", "settingsCollectionSelectionQuickActionEditorBanner": "Toque e segure para mover os botões e selecionar quais ações são exibidas ao selecionar itens.", - "settingsSectionViewer": "Visualizador", + "settingsViewerSectionTitle": "Visualizador", "settingsViewerGestureSideTapNext": "Toque nas bordas da tela para mostrar anterior/seguinte", "settingsViewerUseCutout": "Usar área de recorte", "settingsViewerMaximumBrightness": "Brilho máximo", @@ -491,14 +489,14 @@ "settingsImageBackground": "Plano de fundo da imagem", "settingsViewerQuickActionsTile": "Ações rápidas", - "settingsViewerQuickActionEditorTitle": "Ações rápidas", + "settingsViewerQuickActionEditorPageTitle": "Ações rápidas", "settingsViewerQuickActionEditorBanner": "Toque e segure para mover os botões e selecionar quais ações são exibidas no visualizador.", - "settingsViewerQuickActionEditorDisplayedButtons": "Botões exibidos", - "settingsViewerQuickActionEditorAvailableButtons": "Botões disponíveis", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Botões exibidos", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Botões disponíveis", "settingsViewerQuickActionEmpty": "Sem botões", "settingsViewerOverlayTile": "Sobreposição", - "settingsViewerOverlayTitle": "Sobreposição", + "settingsViewerOverlayPageTitle": "Sobreposição", "settingsViewerShowOverlayOnOpening": "Mostrar na abertura", "settingsViewerShowMinimap": "Mostrar minimapa", "settingsViewerShowInformation": "Mostrar informações", @@ -508,30 +506,30 @@ "settingsViewerEnableOverlayBlurEffect": "Efeito de desfoque", "settingsViewerSlideshowTile": "Apresentação de slides", - "settingsViewerSlideshowTitle": "Apresentação de slides", + "settingsViewerSlideshowPageTitle": "Apresentação de slides", "settingsSlideshowRepeat": "Repetir", "settingsSlideshowShuffle": "Embaralhar", "settingsSlideshowFillScreen": "Preencher tela", "settingsSlideshowTransitionTile": "Transição", - "settingsSlideshowTransitionTitle": "Transição", + "settingsSlideshowTransitionDialogTitle": "Transição", "settingsSlideshowIntervalTile": "Intervalo", - "settingsSlideshowIntervalTitle": "Intervalo", + "settingsSlideshowIntervalDialogTitle": "Intervalo", "settingsSlideshowVideoPlaybackTile": "Reprodução de vídeo", - "settingsSlideshowVideoPlaybackTitle": "Reprodução de vídeo", + "settingsSlideshowVideoPlaybackDialogTitle": "Reprodução de vídeo", "settingsVideoPageTitle": "Configurações de vídeo", - "settingsSectionVideo": "Vídeo", + "settingsVideoSectionTitle": "Vídeo", "settingsVideoShowVideos": "Mostrar vídeos", "settingsVideoEnableHardwareAcceleration": "Aceleraçao do hardware", "settingsVideoEnableAutoPlay": "Reprodução automática", "settingsVideoLoopModeTile": "Modo de loop", - "settingsVideoLoopModeTitle": "Modo de loop", + "settingsVideoLoopModeDialogTitle": "Modo de loop", "settingsSubtitleThemeTile": "Legendas", - "settingsSubtitleThemeTitle": "Legendas", + "settingsSubtitleThemePageTitle": "Legendas", "settingsSubtitleThemeSample": "Esta é uma amostra.", "settingsSubtitleThemeTextAlignmentTile": "Alinhamento de texto", - "settingsSubtitleThemeTextAlignmentTitle": "Alinhamento de Texto", + "settingsSubtitleThemeTextAlignmentDialogTitle": "Alinhamento de Texto", "settingsSubtitleThemeTextSize": "Tamanho do texto", "settingsSubtitleThemeShowOutline": "Mostrar contorno e sombra", "settingsSubtitleThemeTextColor": "Cor do texto", @@ -543,13 +541,13 @@ "settingsSubtitleThemeTextAlignmentRight": "Direita", "settingsVideoControlsTile": "Controles", - "settingsVideoControlsTitle": "Controles", + "settingsVideoControlsPageTitle": "Controles", "settingsVideoButtonsTile": "Botões", - "settingsVideoButtonsTitle": "Botões", + "settingsVideoButtonsDialogTitle": "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", + "settingsPrivacySectionTitle": "Privacidade", "settingsAllowInstalledAppAccess": "Permitir acesso ao inventário de aplicativos", "settingsAllowInstalledAppAccessSubtitle": "Usado para melhorar a exibição do álbum", "settingsAllowErrorReporting": "Permitir relatórios de erros anônimos", @@ -558,52 +556,56 @@ "settingsEnableBinSubtitle": "Manter itens excluídos por 30 dias", "settingsHiddenItemsTile": "Itens ocultos", - "settingsHiddenItemsTitle": "Itens ocultos", + "settingsHiddenItemsPageTitle": "Itens ocultos", - "settingsHiddenFiltersTitle": "Filtros ocultos", + "settingsHiddenItemsTabFilters": "Filtros ocultos", "settingsHiddenFiltersBanner": "Fotos e vídeos que correspondem a filtros ocultos não aparecerão em sua coleção.", "settingsHiddenFiltersEmpty": "Sem filtros ocultos", - "settingsHiddenPathsTitle": "Caminhos Ocultos", + "settingsHiddenItemsTabPaths": "Caminhos Ocultos", "settingsHiddenPathsBanner": "Fotos e vídeos nessas pastas, ou em qualquer uma de suas subpastas, não aparecerão em sua coleção.", "addPathTooltip": "Adicionar caminho", "settingsStorageAccessTile": "Acesso ao armazenamento", - "settingsStorageAccessTitle": "Acesso ao armazenamento", + "settingsStorageAccessPageTitle": "Acesso ao armazenamento", "settingsStorageAccessBanner": "Alguns diretórios exigem uma concessão de acesso explícito para modificar arquivos neles. Você pode revisar aqui os diretórios aos quais você deu acesso anteriormente.", "settingsStorageAccessEmpty": "Sem concessões de acesso", "settingsStorageAccessRevokeTooltip": "Revogar", - "settingsSectionAccessibility": "Acessibilidade", + "settingsAccessibilitySectionTitle": "Acessibilidade", "settingsRemoveAnimationsTile": "Remover animações", - "settingsRemoveAnimationsTitle": "Remover Animações", + "settingsRemoveAnimationsDialogTitle": "Remover Animações", "settingsTimeToTakeActionTile": "Tempo para executar uma ação", - "settingsTimeToTakeActionTitle": "Tempo para executar uma ação", + "settingsTimeToTakeActionDialogTitle": "Tempo para executar uma ação", - "settingsSectionDisplay": "Tela", - "settingsThemeBrightness": "Tema", + "settingsDisplaySectionTitle": "Tela", + "settingsThemeBrightnessTile": "Tema", + "settingsThemeBrightnessDialogTitle": "Tema", "settingsThemeColorHighlights": "Destaques de cores", "settingsThemeEnableDynamicColor": "Cor dinâmica", "settingsDisplayRefreshRateModeTile": "Taxa de atualização de exibição", - "settingsDisplayRefreshRateModeTitle": "Taxa de atualização", + "settingsDisplayRefreshRateModeDialogTitle": "Taxa de atualização", - "settingsSectionLanguage": "Idioma e Formatos", - "settingsLanguage": "Língua", + "settingsLanguageSectionTitle": "Idioma e Formatos", + "settingsLanguageTile": "Língua", + "settingsLanguagePageTitle": "Língua", "settingsCoordinateFormatTile": "Formato de coordenadas", - "settingsCoordinateFormatTitle": "Formato de coordenadas", + "settingsCoordinateFormatDialogTitle": "Formato de coordenadas", "settingsUnitSystemTile": "Unidades", - "settingsUnitSystemTitle": "Unidades", + "settingsUnitSystemDialogTitle": "Unidades", "settingsScreenSaverPageTitle": "Protetor de tela", "settingsWidgetPageTitle": "Porta-retratos", "settingsWidgetShowOutline": "Contorno", + "settingsCollectionTile": "Coleção", + "statsPageTitle": "Estatísticas", "statsWithGps": "{count, plural, =1{1 item com localização} other{{count} itens com localização}}", - "statsTopCountries": "Principais Países", - "statsTopPlaces": "Principais Lugares", - "statsTopTags": "Principais Etiquetas", + "statsTopCountriesSectionTitle": "Principais Países", + "statsTopPlacesSectionTitle": "Principais Lugares", + "statsTopTagsSectionTitle": "Principais Etiquetas", "viewerOpenPanoramaButtonLabel": "ABRIR PANORAMA", "viewerSetWallpaperButtonLabel": "DEFINIR PAPEL DE PAREDE", @@ -614,6 +616,7 @@ "viewerInfoBackToViewerTooltip": "Voltar ao visualizador", "viewerInfoUnknown": "desconhecido", + "viewerInfoLabelDescription": "Descrição", "viewerInfoLabelTitle": "Título", "viewerInfoLabelDate": "Data", "viewerInfoLabelResolution": "Resolução", @@ -625,7 +628,7 @@ "viewerInfoLabelCoordinates": "Coordenadas", "viewerInfoLabelAddress": "Endereço", - "mapStyleTitle": "Estilo do mapa", + "mapStyleDialogTitle": "Estilo do mapa", "mapStyleTooltip": "Selecione o estilo do mapa", "mapZoomInTooltip": "Mais zoom", "mapZoomOutTooltip": "Reduzir o zoom", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 6c552fe37..d6c29cb27 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -27,6 +27,7 @@ "actionRemove": "Удалить", "resetTooltip": "Сбросить", "saveTooltip": "Сохранить", + "pickTooltip": "Выбрать", "doubleBackExitMessage": "Нажмите «Назад» еще раз, чтобы выйти.", "doNotAskAgain": "Больше не спрашивать", @@ -87,15 +88,20 @@ "entryInfoActionEditDate": "Изменить дату и время", "entryInfoActionEditLocation": "Изменить местоположение", + "entryInfoActionEditTitleDescription": "Изменить название & описание", "entryInfoActionEditRating": "Изменить рейтинг", "entryInfoActionEditTags": "Изменить теги", "entryInfoActionRemoveMetadata": "Удалить метаданные", "filterBinLabel": "Корзина", "filterFavouriteLabel": "Избранное", - "filterLocationEmptyLabel": "Без местоположения", - "filterTagEmptyLabel": "Без тегов", - "filterRatingUnratedLabel": "Без рейтинга", + "filterNoDateLabel": "Без даты", + "filterNoLocationLabel": "Без местоположения", + "filterNoRatingLabel": "Без рейтинга", + "filterNoTagLabel": "Без тегов", + "filterNoTitleLabel": "Без названия", + "filterOnThisDayLabel": "В этот день", + "filterRecentlyAddedLabel": "Недавно добавленные", "filterRatingRejectedLabel": "Отклонённое", "filterTypeAnimatedLabel": "GIF", "filterTypeMotionPhotoLabel": "Живое фото", @@ -176,16 +182,11 @@ "storageVolumeDescriptionFallbackNonPrimary": "SD-карта", "rootDirectoryDescription": "корневой каталог", "otherDirectoryDescription": "каталог «{name}»", - "storageAccessDialogTitle": "Доступ к хранилищу", "storageAccessDialogMessage": "Пожалуйста, выберите {directory} на накопителе «{volume}» на следующем экране, чтобы предоставить этому приложению доступ к нему.", - "restrictedAccessDialogTitle": "Ограниченный доступ", "restrictedAccessDialogMessage": "Этому приложению не разрешается изменять файлы в каталоге {directory} накопителя «{volume}».\n\nПожалуйста, используйте предустановленный файловый менеджер или галерею, чтобы переместить объекты в другой каталог.", - "notEnoughSpaceDialogTitle": "Недостаточно свободного места.", "notEnoughSpaceDialogMessage": "Для завершения этой операции требуется {neededSize} свободного места на «{volume}», но осталось только {freeSize}.", - "missingSystemFilePickerDialogTitle": "Отсутствует системное приложение выбора файлов", "missingSystemFilePickerDialogMessage": "Системное приложение выбора файлов отсутствует или отключено. Пожалуйста, включите его и повторите попытку.", - "unsupportedTypeDialogTitle": "Неподдерживаемые форматы", "unsupportedTypeDialogMessage": "{count, plural, =1{Эта операция не поддерживается для объектов следующего формата: {types}.} other{Эта операция не поддерживается для объектов следующих форматов: {types}.}}", "nameConflictDialogSingleSourceMessage": "Некоторые файлы в папке назначения имеют одно и то же имя.", @@ -194,7 +195,6 @@ "addShortcutDialogLabel": "Название ярлыка", "addShortcutButtonLabel": "СОЗДАТЬ", - "noMatchingAppDialogTitle": "Нет подходящего приложения", "noMatchingAppDialogMessage": "Нет приложений, которые могли бы с этим справиться.", "binEntriesConfirmationDialogMessage": "{count, plural, =1{Переместить этот объект в корзину?} few{Переместить эти {count} объекта в корзину?} other{Переместить эти {count} объектов в корзину?}}", @@ -223,7 +223,7 @@ "renameEntrySetPageTitle": "Переименовать", "renameEntrySetPagePatternFieldLabel": "Образец наименования", "renameEntrySetPageInsertTooltip": "Вставить поле", - "renameEntrySetPagePreview": "Предпросмотр", + "renameEntrySetPagePreviewSectionTitle": "Предпросмотр", "renameProcessorCounter": "Счётчик", "renameProcessorName": "Название", @@ -284,9 +284,10 @@ "menuActionSlideshow": "Слайд-шоу", "menuActionStats": "Статистика", - "viewDialogTabSort": "Сортировка", - "viewDialogTabGroup": "Группировка", - "viewDialogTabLayout": "Макет", + "viewDialogSortSectionTitle": "Сортировка", + "viewDialogGroupSectionTitle": "Группировка", + "viewDialogLayoutSectionTitle": "Макет", + "viewDialogReverseSortOrder": "Обратный порядок сортировки", "tileLayoutGrid": "Сетка", "tileLayoutList": "Список", @@ -303,24 +304,24 @@ "aboutLinkLicense": "Лицензия", "aboutLinkPolicy": "Политика конфиденциальности", - "aboutBug": "Отчет об ошибке", + "aboutBugSectionTitle": "Отчет об ошибке", "aboutBugSaveLogInstruction": "Сохраните логи приложения в файл", "aboutBugCopyInfoInstruction": "Скопируйте системную информацию", "aboutBugCopyInfoButton": "Скопировать", "aboutBugReportInstruction": "Отправьте отчёт об ошибке на GitHub вместе с логами и системной информацией", "aboutBugReportButton": "Отправить", - "aboutCredits": "Благодарности", + "aboutCreditsSectionTitle": "Благодарности", "aboutCreditsWorldAtlas1": "Это приложение использует файл TopoJSON из", "aboutCreditsWorldAtlas2": "под лицензией ISC.", - "aboutCreditsTranslators": "Переводчики", + "aboutTranslatorsSectionTitle": "Переводчики", - "aboutLicenses": "Лицензии с открытым исходным кодом", + "aboutLicensesSectionTitle": "Лицензии с открытым исходным кодом", "aboutLicensesBanner": "Это приложение использует следующие пакеты и библиотеки с открытым исходным кодом.", - "aboutLicensesAndroidLibraries": "Библиотеки Android", - "aboutLicensesFlutterPlugins": "Плагины Flutter", - "aboutLicensesFlutterPackages": "Пакеты Flutter", - "aboutLicensesDartPackages": "Пакеты Dart", + "aboutLicensesAndroidLibrariesSectionTitle": "Библиотеки Android", + "aboutLicensesFlutterPluginsSectionTitle": "Плагины Flutter", + "aboutLicensesFlutterPackagesSectionTitle": "Пакеты Flutter", + "aboutLicensesDartPackagesSectionTitle": "Пакеты Dart", "aboutLicensesShowAllButtonLabel": "Показать все лицензии", "policyPageTitle": "Политика конфиденциальности", @@ -340,11 +341,6 @@ "collectionSearchTitlesHintText": "Поиск заголовков", - "collectionSortDate": "По дате", - "collectionSortSize": "По размеру", - "collectionSortName": "По имени альбома и файла", - "collectionSortRating": "По рейтингу", - "collectionGroupAlbum": "По альбому", "collectionGroupMonth": "По месяцу", "collectionGroupDay": "По дню", @@ -373,6 +369,8 @@ "collectionSelectSectionTooltip": "Выбрать раздел", "collectionDeselectSectionTooltip": "Снять выбор с раздела", + "drawerAboutButton": "О нас", + "drawerSettingsButton": "Настройки", "drawerCollectionAll": "Вся коллекция", "drawerCollectionFavourites": "Избранное", "drawerCollectionImages": "Изображения", @@ -382,10 +380,25 @@ "drawerCollectionPanoramas": "Панорамы", "drawerCollectionRaws": "RAW", "drawerCollectionSphericalVideos": "360° видео", + "drawerAlbumPage": "Альбомы", + "drawerCountryPage": "Страны", + "drawerTagPage": "Теги", - "chipSortDate": "По дате", - "chipSortName": "По названию", - "chipSortCount": "По количеству объектов", + "sortByDate": "По дате", + "sortByName": "По названию", + "sortByItemCount": "По количеству объектов", + "sortBySize": "По размеру", + "sortByAlbumFileName": "По имени альбома и файла", + "sortByRating": "По рейтингу", + + "sortOrderNewestFirst": "Сначала новые", + "sortOrderOldestFirst": "Сначала старые", + "sortOrderAtoZ": "От А до Я", + "sortOrderZtoA": "От Я до А", + "sortOrderHighestFirst": "Сначала с высоким", + "sortOrderLowestFirst": "Сначала с низким", + "sortOrderLargestFirst": "Сначала большие", + "sortOrderSmallestFirst": "Сначала маленькие", "albumGroupTier": "По уровню", "albumGroupVolume": "По накопителю", @@ -417,13 +430,14 @@ "binPageTitle": "Корзина", "searchCollectionFieldHint": "Поиск по коллекции", - "searchSectionRecent": "Недавние", - "searchSectionDate": "Дата", - "searchSectionAlbums": "Альбомы", - "searchSectionCountries": "Страны", - "searchSectionPlaces": "Локации", - "searchSectionTags": "Теги", - "searchSectionRating": "Рейтинги", + "searchRecentSectionTitle": "Недавние", + "searchDateSectionTitle": "Дата", + "searchAlbumsSectionTitle": "Альбомы", + "searchCountriesSectionTitle": "Страны", + "searchPlacesSectionTitle": "Локации", + "searchTagsSectionTitle": "Теги", + "searchRatingSectionTitle": "Рейтинги", + "searchMetadataSectionTitle": "Метаданные", "settingsPageTitle": "Настройки", "settingsSystemDefault": "Система", @@ -432,36 +446,40 @@ "settingsSearchFieldLabel": "Поиск настроек", "settingsSearchEmpty": "Нет соответствующих настроек", "settingsActionExport": "Экспорт", + "settingsActionExportDialogTitle": "Экспорт", "settingsActionImport": "Импорт", + "settingsActionImportDialogTitle": "Импорт", "appExportCovers": "Обложки", "appExportFavourites": "Избранное", "appExportSettings": "Настройки", - "settingsSectionNavigation": "Навигация", - "settingsHome": "Домашний каталог", + "settingsNavigationSectionTitle": "Навигация", + "settingsHomeTile": "Домашний каталог", + "settingsHomeDialogTitle": "Домашний каталог", "settingsShowBottomNavigationBar": "Показать нижнюю панель навигации", "settingsKeepScreenOnTile": "Держать экран включенным", - "settingsKeepScreenOnTitle": "Держать экран включенным", + "settingsKeepScreenOnDialogTitle": "Держать экран включенным", "settingsDoubleBackExit": "Дважды нажмите «Назад», чтобы выйти", - "settingsConfirmationDialogTile": "Диалоги подтверждения", + "settingsConfirmationTile": "Диалоги подтверждения", "settingsConfirmationDialogTitle": "Диалоги подтверждения", - "settingsConfirmationDialogDeleteItems": "Спросить, прежде чем удалять объекты навсегда", - "settingsConfirmationDialogMoveToBinItems": "Спросить, прежде чем перемещать объекты в корзину", - "settingsConfirmationDialogMoveUndatedItems": "Спросить, прежде чем перемещать объекты без даты в метаданных", + "settingsConfirmationBeforeDeleteItems": "Спросить, прежде чем удалять объекты навсегда", + "settingsConfirmationBeforeMoveToBinItems": "Спросить, прежде чем перемещать объекты в корзину", + "settingsConfirmationBeforeMoveUndatedItems": "Спросить, прежде чем перемещать объекты без даты в метаданных", + "settingsConfirmationAfterMoveToBinItems": "Показывать сообщение после перемещения в корзину", "settingsNavigationDrawerTile": "Навигационное меню", - "settingsNavigationDrawerEditorTitle": "Навигационное меню", + "settingsNavigationDrawerEditorPageTitle": "Навигационное меню", "settingsNavigationDrawerBanner": "Нажмите и удерживайте, чтобы переместить и изменить порядок пунктов меню.", "settingsNavigationDrawerTabTypes": "Типы", "settingsNavigationDrawerTabAlbums": "Альбомы", "settingsNavigationDrawerTabPages": "Страницы", "settingsNavigationDrawerAddAlbum": "Добавить альбом", - "settingsSectionThumbnails": "Эскизы", + "settingsThumbnailSectionTitle": "Эскизы", "settingsThumbnailOverlayTile": "Наложение", - "settingsThumbnailOverlayTitle": "Наложение", + "settingsThumbnailOverlayPageTitle": "Наложение", "settingsThumbnailShowFavouriteIcon": "Показать значок избранного", "settingsThumbnailShowTagIcon": "Показать значок тега", "settingsThumbnailShowLocationIcon": "Показать значок местоположения", @@ -471,27 +489,28 @@ "settingsThumbnailShowVideoDuration": "Показать продолжительность видео", "settingsCollectionQuickActionsTile": "Быстрые действия", - "settingsCollectionQuickActionEditorTitle": "Быстрые действия", + "settingsCollectionQuickActionEditorPageTitle": "Быстрые действия", "settingsCollectionQuickActionTabBrowsing": "Просмотр", "settingsCollectionQuickActionTabSelecting": "Выбор", "settingsCollectionBrowsingQuickActionEditorBanner": "Нажмите и удерживайте для перемещения кнопок и выбора действий, отображаемых при просмотре объектов.", "settingsCollectionSelectionQuickActionEditorBanner": "Нажмите и удерживайте, чтобы переместить кнопки и выбрать, какие действия будут отображаться при выборе элементов.", - "settingsSectionViewer": "Просмотрщик", + "settingsViewerSectionTitle": "Просмотрщик", + "settingsViewerGestureSideTapNext": "Нажатие на край экрана для перехода назад/вперед", "settingsViewerUseCutout": "Использовать область выреза", "settingsViewerMaximumBrightness": "Максимальная яркость", "settingsMotionPhotoAutoPlay": "Автовоспроизведение «живых фото»", "settingsImageBackground": "Фон изображения", "settingsViewerQuickActionsTile": "Быстрые действия", - "settingsViewerQuickActionEditorTitle": "Быстрые действия", + "settingsViewerQuickActionEditorPageTitle": "Быстрые действия", "settingsViewerQuickActionEditorBanner": "Нажмите и удерживайте для перемещения кнопок и выбора действий, отображаемых в просмотрщике.", - "settingsViewerQuickActionEditorDisplayedButtons": "Отображаемые кнопки", - "settingsViewerQuickActionEditorAvailableButtons": "Доступные кнопки", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Отображаемые кнопки", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Доступные кнопки", "settingsViewerQuickActionEmpty": "Нет кнопок", "settingsViewerOverlayTile": "Наложение", - "settingsViewerOverlayTitle": "Наложение", + "settingsViewerOverlayPageTitle": "Наложение", "settingsViewerShowOverlayOnOpening": "Показать наложение при открытии", "settingsViewerShowMinimap": "Показать миникарту", "settingsViewerShowInformation": "Показать информацию", @@ -501,29 +520,30 @@ "settingsViewerEnableOverlayBlurEffect": "Наложение эффекта размытия", "settingsViewerSlideshowTile": "Слайд-шоу", - "settingsViewerSlideshowTitle": "Слайд-шоу", + "settingsViewerSlideshowPageTitle": "Слайд-шоу", "settingsSlideshowRepeat": "Повтор", "settingsSlideshowShuffle": "Вперемешку", + "settingsSlideshowFillScreen": "Полный экран", "settingsSlideshowTransitionTile": "Эффект перехода", - "settingsSlideshowTransitionTitle": "Эффект Перехода", + "settingsSlideshowTransitionDialogTitle": "Эффект Перехода", "settingsSlideshowIntervalTile": "Интервал", - "settingsSlideshowIntervalTitle": "Интервал", + "settingsSlideshowIntervalDialogTitle": "Интервал", "settingsSlideshowVideoPlaybackTile": "Проигрывание видео", - "settingsSlideshowVideoPlaybackTitle": "Проигрывание Видео", + "settingsSlideshowVideoPlaybackDialogTitle": "Проигрывание Видео", "settingsVideoPageTitle": "Настройки видео", - "settingsSectionVideo": "Видео", + "settingsVideoSectionTitle": "Видео", "settingsVideoShowVideos": "Показать видео", "settingsVideoEnableHardwareAcceleration": "Аппаратное ускорение", "settingsVideoEnableAutoPlay": "Автозапуск воспроизведения", "settingsVideoLoopModeTile": "Циклический режим", - "settingsVideoLoopModeTitle": "Цикличный режим", + "settingsVideoLoopModeDialogTitle": "Цикличный режим", "settingsSubtitleThemeTile": "Субтитры", - "settingsSubtitleThemeTitle": "Субтитры", + "settingsSubtitleThemePageTitle": "Субтитры", "settingsSubtitleThemeSample": "Образец.", "settingsSubtitleThemeTextAlignmentTile": "Выравнивание текста", - "settingsSubtitleThemeTextAlignmentTitle": "Выравнивание текста", + "settingsSubtitleThemeTextAlignmentDialogTitle": "Выравнивание текста", "settingsSubtitleThemeTextSize": "Размер текста", "settingsSubtitleThemeShowOutline": "Показать контур и тень", "settingsSubtitleThemeTextColor": "Цвет текста", @@ -535,13 +555,13 @@ "settingsSubtitleThemeTextAlignmentRight": "По правой стороне", "settingsVideoControlsTile": "Элементы управления", - "settingsVideoControlsTitle": "Элементы управления", + "settingsVideoControlsPageTitle": "Элементы управления", "settingsVideoButtonsTile": "Кнопки", - "settingsVideoButtonsTitle": "Кнопки", + "settingsVideoButtonsDialogTitle": "Кнопки", "settingsVideoGestureDoubleTapTogglePlay": "Двойное нажатие для воспроизведения/паузы", "settingsVideoGestureSideDoubleTapSeek": "Двойное нажатие на края экрана для перехода назад/вперёд", - "settingsSectionPrivacy": "Конфиденциальность", + "settingsPrivacySectionTitle": "Конфиденциальность", "settingsAllowInstalledAppAccess": "Разрешить доступ к библиотеке приложения", "settingsAllowInstalledAppAccessSubtitle": "Используется для улучшения отображения альбомов", "settingsAllowErrorReporting": "Разрешить анонимную отправку логов", @@ -550,49 +570,56 @@ "settingsEnableBinSubtitle": "Хранить удалённые объекты в течение 30 дней", "settingsHiddenItemsTile": "Скрытые объекты", - "settingsHiddenItemsTitle": "Скрытые объекты", + "settingsHiddenItemsPageTitle": "Скрытые объекты", - "settingsHiddenFiltersTitle": "Скрытые фильтры", + "settingsHiddenItemsTabFilters": "Скрытые фильтры", "settingsHiddenFiltersBanner": "Фотографии и видео, соответствующие скрытым фильтрам, не появятся в вашей коллекции.", "settingsHiddenFiltersEmpty": "Нет скрытых фильтров", - "settingsHiddenPathsTitle": "Скрытые каталоги", + "settingsHiddenItemsTabPaths": "Скрытые каталоги", "settingsHiddenPathsBanner": "Фотографии и видео в этих каталогах или любых их вложенных каталогах не будут отображаться в вашей коллекции.", "addPathTooltip": "Добавить каталог", "settingsStorageAccessTile": "Доступ к хранилищу", - "settingsStorageAccessTitle": "Доступ к хранилищу", + "settingsStorageAccessPageTitle": "Доступ к хранилищу", "settingsStorageAccessBanner": "Некоторые каталоги требуют обязательного предоставления доступа для изменения файлов в них. Вы можете просмотреть здесь каталоги, к которым вы ранее предоставили доступ.", "settingsStorageAccessEmpty": "Нет прав доступа", "settingsStorageAccessRevokeTooltip": "Отменить", - "settingsSectionAccessibility": "Специальные возможности", + "settingsAccessibilitySectionTitle": "Специальные возможности", "settingsRemoveAnimationsTile": "Удалить анимацию", - "settingsRemoveAnimationsTitle": "Удалить анимацию", + "settingsRemoveAnimationsDialogTitle": "Удалить анимацию", "settingsTimeToTakeActionTile": "Время на выполнение действия", - "settingsTimeToTakeActionTitle": "Время на выполнение действия", + "settingsTimeToTakeActionDialogTitle": "Время на выполнение действия", - "settingsSectionDisplay": "Отображение", - "settingsThemeBrightness": "Тема", + "settingsDisplaySectionTitle": "Отображение", + "settingsThemeBrightnessTile": "Тема", + "settingsThemeBrightnessDialogTitle": "Тема", "settingsThemeColorHighlights": "Цветовые акценты", "settingsThemeEnableDynamicColor": "Динамический цвет", "settingsDisplayRefreshRateModeTile": "Частота обновления экрана", - "settingsDisplayRefreshRateModeTitle": "Частота обновления", + "settingsDisplayRefreshRateModeDialogTitle": "Частота обновления", - "settingsSectionLanguage": "Язык и форматы", - "settingsLanguage": "Язык", + "settingsLanguageSectionTitle": "Язык и форматы", + "settingsLanguageTile": "Язык", + "settingsLanguagePageTitle": "Язык", "settingsCoordinateFormatTile": "Формат координат", - "settingsCoordinateFormatTitle": "Формат координат", + "settingsCoordinateFormatDialogTitle": "Формат координат", "settingsUnitSystemTile": "Единицы измерения", - "settingsUnitSystemTitle": "Единицы измерения", + "settingsUnitSystemDialogTitle": "Единицы измерения", + + "settingsScreenSaverPageTitle": "Скринсейвер", "settingsWidgetPageTitle": "Фоторамка", + "settingsWidgetShowOutline": "Выделение", + + "settingsCollectionTile": "Коллекция", "statsPageTitle": "Статистика", "statsWithGps": "{count, plural, =1{1 объект с местоположением} few{{count} объекта с местоположением} other{{count} объектов с местоположением}}", - "statsTopCountries": "Топ стран", - "statsTopPlaces": "Топ локаций", - "statsTopTags": "Топ тегов", + "statsTopCountriesSectionTitle": "Топ стран", + "statsTopPlacesSectionTitle": "Топ локаций", + "statsTopTagsSectionTitle": "Топ тегов", "viewerOpenPanoramaButtonLabel": "ОТКРЫТЬ ПАНОРАМУ", "viewerSetWallpaperButtonLabel": "УСТАНОВИТЬ КАК ОБОИ", @@ -603,6 +630,7 @@ "viewerInfoBackToViewerTooltip": "Вернуться к просмотрщику", "viewerInfoUnknown": "неизвестный", + "viewerInfoLabelDescription": "Описание", "viewerInfoLabelTitle": "Название", "viewerInfoLabelDate": "Дата", "viewerInfoLabelResolution": "Разрешение", @@ -614,7 +642,7 @@ "viewerInfoLabelCoordinates": "Координаты", "viewerInfoLabelAddress": "Адрес", - "mapStyleTitle": "Стиль карты", + "mapStyleDialogTitle": "Стиль карты", "mapStyleTooltip": "Выберите стиль карты", "mapZoomInTooltip": "Увеличить", "mapZoomOutTooltip": "Уменьшить", diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index 4da8322a6..1dc670844 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -27,6 +27,7 @@ "actionRemove": "Kaldır", "resetTooltip": "Sıfırla", "saveTooltip": "Kaydet", + "pickTooltip": "Seç", "doubleBackExitMessage": "Çıkmak için tekrar “geri”, düğmesine dokunun.", "doNotAskAgain": "Bir daha sorma", @@ -90,9 +91,9 @@ "filterBinLabel": "Geri dönüşüm kutusu", "filterFavouriteLabel": "Favori", - "filterLocationEmptyLabel": "Konumsuz", - "filterTagEmptyLabel": "Etiketsiz", - "filterRatingUnratedLabel": "Derecelendirilmemiş", + "filterNoLocationLabel": "Konumsuz", + "filterNoRatingLabel": "Derecelendirilmemiş", + "filterNoTagLabel": "Etiketsiz", "filterRatingRejectedLabel": "Reddedilmiş", "filterTypeAnimatedLabel": "Hareketli", "filterTypeMotionPhotoLabel": "Hareketli Fotoğraf", @@ -161,20 +162,11 @@ "storageVolumeDescriptionFallbackNonPrimary": "SD kart", "rootDirectoryDescription": "kök dizin", "otherDirectoryDescription": "“{name}” dizin", - - "storageAccessDialogTitle": "Depolama Erişimi", "storageAccessDialogMessage": "Bu uygulamaya erişim sağlamak için lütfen bir sonraki ekranda “{volume}” öğesinin {directory} dizinini seçin.", - - "restrictedAccessDialogTitle": "Kısıtlı Erişim", "restrictedAccessDialogMessage": "Bu uygulamanın “{volume}” içindeki {directory} dosyaları değiştirmesine izin verilmiyor.\n\nÖğeleri başka bir dizine taşımak için lütfen önceden yüklenmiş bir dosya yöneticisi veya galeri uygulaması kullanın.", - - "notEnoughSpaceDialogTitle": "Yeterli Yer Yok", "notEnoughSpaceDialogMessage": "Bu işlemin tamamlanması için “{volume}” üzerinde {needSize} boş alana ihtiyaç var, ancak yalnızca {freeSize} kaldı.", - - "missingSystemFilePickerDialogTitle": "Eksik Sistem Dosya Seçicisi", "missingSystemFilePickerDialogMessage": "Sistem dosya seçicisi eksik veya devre dışı. Lütfen etkinleştirin ve tekrar deneyin.", - "unsupportedTypeDialogTitle": "Desteklenmeyen Türler", "unsupportedTypeDialogMessage": "{count, plural, =1{Bu işlem aşağıdaki türdeki öğeler için desteklenmez: {types}.} other{Bu işlem aşağıdaki türlerdeki öğeler için desteklenmez: {types}.}}", "nameConflictDialogSingleSourceMessage": "Hedef klasördeki bazı dosyalar aynı ada sahip.", @@ -183,7 +175,6 @@ "addShortcutDialogLabel": "Kısayol etiketi", "addShortcutButtonLabel": "EKLE", - "noMatchingAppDialogTitle": "Eşleşen Uygulama Yok", "noMatchingAppDialogMessage": "Bununla ilgilenebilecek bir uygulama yok.", "binEntriesConfirmationDialogMessage": "{count, plural, =1{Bu öğe geri dönüşüm kutusuna taşınsın mı?} other{Bu {count} madde geri dönüşüm kutusuna atılsın mı?}}", @@ -215,7 +206,7 @@ "renameEntrySetPageTitle": "Yeniden adlandır", "renameEntrySetPagePatternFieldLabel": "İsimlendirme şekli", "renameEntrySetPageInsertTooltip": "Alan ekle", - "renameEntrySetPagePreview": "Önizleme", + "renameEntrySetPagePreviewSectionTitle": "Önizleme", "renameProcessorCounter": "Sayaç", "renameProcessorName": "Ad", @@ -276,9 +267,9 @@ "menuActionMap": "Harita", "menuActionStats": "İstatistikler", - "viewDialogTabSort": "Sırala", - "viewDialogTabGroup": "Grup", - "viewDialogTabLayout": "Düzen", + "viewDialogSortSectionTitle": "Sırala", + "viewDialogGroupSectionTitle": "Grup", + "viewDialogLayoutSectionTitle": "Düzen", "tileLayoutGrid": "Izgara", "tileLayoutList": "Liste", @@ -295,24 +286,24 @@ "aboutLinkLicense": "Lisans", "aboutLinkPolicy": "Gizlilik Politikası", - "aboutBug": "Hata Bildirimi", + "aboutBugSectionTitle": "Hata Bildirimi", "aboutBugSaveLogInstruction": "Uygulama günlüklerini bir dosyaya kaydet", "aboutBugCopyInfoInstruction": "Sistem bilgilerini kopyala", "aboutBugCopyInfoButton": "Kopyala", "aboutBugReportInstruction": "GitHub'da günlükleri ve sistem bilgilerini içeren bir rapor oluştur", "aboutBugReportButton": "Raporla", - "aboutCredits": "Kredi", + "aboutCreditsSectionTitle": "Kredi", "aboutCreditsWorldAtlas1": "Bu uygulama bir TopoJSON dosyası kullanır", "aboutCreditsWorldAtlas2": "ISC Lisansı kapsamında.", - "aboutCreditsTranslators": "Tercümanlar", + "aboutTranslatorsSectionTitle": "Tercümanlar", - "aboutLicenses": "Açık Kaynak Lisansları", + "aboutLicensesSectionTitle": "Açık Kaynak Lisansları", "aboutLicensesBanner": "Bu uygulama aşağıdaki açık kaynaklı paketleri ve kütüphaneleri kullanır.", - "aboutLicensesAndroidLibraries": "Android Kütüphaneleri", - "aboutLicensesFlutterPlugins": "Flutter Eklentileri", - "aboutLicensesFlutterPackages": "Flutter Paketleri", - "aboutLicensesDartPackages": "Dart Paketleri", + "aboutLicensesAndroidLibrariesSectionTitle": "Android Kütüphaneleri", + "aboutLicensesFlutterPluginsSectionTitle": "Flutter Eklentileri", + "aboutLicensesFlutterPackagesSectionTitle": "Flutter Paketleri", + "aboutLicensesDartPackagesSectionTitle": "Dart Paketleri", "aboutLicensesShowAllButtonLabel": "Tüm Lisansları Göster", "policyPageTitle": "Gizlilik Politikası", @@ -332,11 +323,6 @@ "collectionSearchTitlesHintText": "Başlıkları ara", - "collectionSortDate": "Tarihe göre", - "collectionSortSize": "Boyuta göre", - "collectionSortName": "Albüm ve dosya adına göre", - "collectionSortRating": "Derecelendirmeye göre", - "collectionGroupAlbum": "Albüme göre", "collectionGroupMonth": "Aya göre", "collectionGroupDay": "Güne göre", @@ -374,6 +360,8 @@ "collectionSelectSectionTooltip": "Bölüm seç", "collectionDeselectSectionTooltip": "Bölüm seçimini kaldır", + "drawerAboutButton": "Hakkında", + "drawerSettingsButton": "Ayarlar", "drawerCollectionAll": "Tüm koleksiyon", "drawerCollectionFavourites": "Favoriler", "drawerCollectionImages": "Resimler", @@ -383,10 +371,16 @@ "drawerCollectionPanoramas": "Panoramalar", "drawerCollectionRaws": "Raw fotoğraflar", "drawerCollectionSphericalVideos": "360° Videolar", + "drawerAlbumPage": "Albümler", + "drawerCountryPage": "Ülkeler", + "drawerTagPage": "Etiketler", - "chipSortDate": "Tarihe göre", - "chipSortName": "Adına göre", - "chipSortCount": "Öğe sayısına göre", + "sortByDate": "Tarihe göre", + "sortByName": "Adına göre", + "sortByItemCount": "Öğe sayısına göre", + "sortBySize": "Boyuta göre", + "sortByAlbumFileName": "Albüm ve dosya adına göre", + "sortByRating": "Derecelendirmeye göre", "albumGroupTier": "Kademeye göre", "albumGroupVolume": "Depolama hacmine göre", @@ -418,13 +412,13 @@ "binPageTitle": "Geri Dönüşüm Kutusu", "searchCollectionFieldHint": "Koleksiyonu ara", - "searchSectionRecent": "Yakın zamanda", - "searchSectionDate": "Tarih", - "searchSectionAlbums": "Albümler", - "searchSectionCountries": "Ülkeler", - "searchSectionPlaces": "Yerler", - "searchSectionTags": "Etiketler", - "searchSectionRating": "Derecelendirmeler", + "searchRecentSectionTitle": "Yakın zamanda", + "searchDateSectionTitle": "Tarih", + "searchAlbumsSectionTitle": "Albümler", + "searchCountriesSectionTitle": "Ülkeler", + "searchPlacesSectionTitle": "Yerler", + "searchTagsSectionTitle": "Etiketler", + "searchRatingSectionTitle": "Derecelendirmeler", "settingsPageTitle": "Ayarlar", "settingsSystemDefault": "Sistem", @@ -433,36 +427,39 @@ "settingsSearchFieldLabel": "Ayarlarda ara", "settingsSearchEmpty": "Eşleşen ayar bulunamadı", "settingsActionExport": "Dışa aktar", + "settingsActionExportDialogTitle": "Dışa aktar", "settingsActionImport": "İçe aktar", + "settingsActionImportDialogTitle": "İçe aktar", "appExportCovers": "Kapaklar", "appExportFavourites": "Favoriler", "appExportSettings": "Ayarlar", - "settingsSectionNavigation": "Gezinti", - "settingsHome": "Anasayfa", + "settingsNavigationSectionTitle": "Gezinti", + "settingsHomeTile": "Anasayfa", + "settingsHomeDialogTitle": "Anasayfa", "settingsShowBottomNavigationBar": "Alt gezinti çubuğunu göster", "settingsKeepScreenOnTile": "Ekranı açık tut", - "settingsKeepScreenOnTitle": "Ekranı Açık Tut", + "settingsKeepScreenOnDialogTitle": "Ekranı Açık Tut", "settingsDoubleBackExit": "Çıkmak için iki kez “geri” düğmesine dokunun", - "settingsConfirmationDialogTile": "Onaylama diyalogları", + "settingsConfirmationTile": "Onaylama diyalogları", "settingsConfirmationDialogTitle": "Onaylama Diyalogları", - "settingsConfirmationDialogDeleteItems": "Öğeleri sonsuza dek silmeden önce sor", - "settingsConfirmationDialogMoveToBinItems": "Eşyaları geri dönüşüm kutusuna atmadan önce sor", - "settingsConfirmationDialogMoveUndatedItems": "Tarihsiz eşyaları taşımadan önce sor", + "settingsConfirmationBeforeDeleteItems": "Öğeleri sonsuza dek silmeden önce sor", + "settingsConfirmationBeforeMoveToBinItems": "Eşyaları geri dönüşüm kutusuna atmadan önce sor", + "settingsConfirmationBeforeMoveUndatedItems": "Tarihsiz eşyaları taşımadan önce sor", "settingsNavigationDrawerTile": "Gezinti menüsü", - "settingsNavigationDrawerEditorTitle": "Gezinti Menüsü", + "settingsNavigationDrawerEditorPageTitle": "Gezinti Menüsü", "settingsNavigationDrawerBanner": "Menü öğelerini taşımak ve yeniden sıralamak için dokunun ve basılı tutun.", "settingsNavigationDrawerTabTypes": "Türler", "settingsNavigationDrawerTabAlbums": "Albümler", "settingsNavigationDrawerTabPages": "Sayfalar", "settingsNavigationDrawerAddAlbum": "Albüm ekle", - "settingsSectionThumbnails": "Küçük resimler", + "settingsThumbnailSectionTitle": "Küçük resimler", "settingsThumbnailOverlayTile": "Kaplama", - "settingsThumbnailOverlayTitle": "Kaplama", + "settingsThumbnailOverlayPageTitle": "Kaplama", "settingsThumbnailShowFavouriteIcon": "Favori simgeyi göster", "settingsThumbnailShowTagIcon": "Etiket simgesini göster", "settingsThumbnailShowLocationIcon": "Konum simgesini göster", @@ -472,27 +469,27 @@ "settingsThumbnailShowVideoDuration": "Video süresini göster", "settingsCollectionQuickActionsTile": "Hızlı eylemler", - "settingsCollectionQuickActionEditorTitle": "Hızlı Eylemler", + "settingsCollectionQuickActionEditorPageTitle": "Hızlı Eylemler", "settingsCollectionQuickActionTabBrowsing": "Gözatma", "settingsCollectionQuickActionTabSelecting": "Seçme", "settingsCollectionBrowsingQuickActionEditorBanner": "Düğmeleri hareket ettirmek ve öğelere göz atarken hangi eylemlerin görüntüleneceğini seçmek için dokunun ve basılı tutun.", "settingsCollectionSelectionQuickActionEditorBanner": "Düğmeleri hareket ettirmek ve öğeleri seçerken hangi eylemlerin görüntüleneceğini seçmek için dokunun ve basılı tutun.", - "settingsSectionViewer": "Görüntüleyici", + "settingsViewerSectionTitle": "Görüntüleyici", "settingsViewerUseCutout": "Kesim alanını kullan", "settingsViewerMaximumBrightness": "Maksimum parlaklık", "settingsMotionPhotoAutoPlay": "Hareketli fotoğrafları otomatik oynat", "settingsImageBackground": "Resim arka planı", "settingsViewerQuickActionsTile": "Hızlı eylemler", - "settingsViewerQuickActionEditorTitle": "Hızlı Eylemler", + "settingsViewerQuickActionEditorPageTitle": "Hızlı Eylemler", "settingsViewerQuickActionEditorBanner": "Düğmeleri hareket ettirmek ve görüntüleyicide hangi eylemlerin görüntüleneceğini seçmek için dokunun ve basılı tutun.", - "settingsViewerQuickActionEditorDisplayedButtons": "Gösterilen Düğmeler", - "settingsViewerQuickActionEditorAvailableButtons": "Mevcut Düğmeler", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "Gösterilen Düğmeler", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "Mevcut Düğmeler", "settingsViewerQuickActionEmpty": "Düğme yok", "settingsViewerOverlayTile": "Kaplama", - "settingsViewerOverlayTitle": "Kaplama", + "settingsViewerOverlayPageTitle": "Kaplama", "settingsViewerShowOverlayOnOpening": "Açılışta göster", "settingsViewerShowMinimap": "Mini haritayı göster", "settingsViewerShowInformation": "Bilgileri göster", @@ -502,18 +499,18 @@ "settingsViewerEnableOverlayBlurEffect": "Bulanıklık efekti", "settingsVideoPageTitle": "Video Ayarları", - "settingsSectionVideo": "Video", + "settingsVideoSectionTitle": "Video", "settingsVideoShowVideos": "Videoları göster", "settingsVideoEnableHardwareAcceleration": "Donanım hızlandırma", "settingsVideoEnableAutoPlay": "Otomatik oynat", "settingsVideoLoopModeTile": "Döngü modu", - "settingsVideoLoopModeTitle": "Döngü Modu", + "settingsVideoLoopModeDialogTitle": "Döngü Modu", "settingsSubtitleThemeTile": "Altyazılar", - "settingsSubtitleThemeTitle": "Altyazılar", + "settingsSubtitleThemePageTitle": "Altyazılar", "settingsSubtitleThemeSample": "Bu bir örnek.", "settingsSubtitleThemeTextAlignmentTile": "Metin hizalama", - "settingsSubtitleThemeTextAlignmentTitle": "Metin Hizalama", + "settingsSubtitleThemeTextAlignmentDialogTitle": "Metin Hizalama", "settingsSubtitleThemeTextSize": "Metin boyutu", "settingsSubtitleThemeShowOutline": "Dış çizgiyi ve gölgeyi göster", "settingsSubtitleThemeTextColor": "Metin rengi", @@ -525,13 +522,13 @@ "settingsSubtitleThemeTextAlignmentRight": "Sağ", "settingsVideoControlsTile": "Kontroller", - "settingsVideoControlsTitle": "Kontroller", + "settingsVideoControlsPageTitle": "Kontroller", "settingsVideoButtonsTile": "Düğmeler", - "settingsVideoButtonsTitle": "Düğmeler", + "settingsVideoButtonsDialogTitle": "Düğmeler", "settingsVideoGestureDoubleTapTogglePlay": "Oynatmak/duraklatmak için çift dokunun", "settingsVideoGestureSideDoubleTapSeek": "Geri/ileri aramak için ekran kenarlarına çift dokunun", - "settingsSectionPrivacy": "Gizlilik", + "settingsPrivacySectionTitle": "Gizlilik", "settingsAllowInstalledAppAccess": "Uygulama envanterine erişime izin ver", "settingsAllowInstalledAppAccessSubtitle": "Albüm görüntüsünü iyileştirmek için kullanılır", "settingsAllowErrorReporting": "Anonim hata raporlamasına izin ver", @@ -540,49 +537,53 @@ "settingsEnableBinSubtitle": "Silinen öğeleri 30 gün boyunca saklar", "settingsHiddenItemsTile": "Gizli öğeler", - "settingsHiddenItemsTitle": "Gizli Öğeler", + "settingsHiddenItemsPageTitle": "Gizli Öğeler", - "settingsHiddenFiltersTitle": "Gizli Filtreler", + "settingsHiddenItemsTabFilters": "Gizli Filtreler", "settingsHiddenFiltersBanner": "Gizli filtrelerle eşleşen fotoğraflar ve videolar koleksiyonunuzda görünmeyecektir.", "settingsHiddenFiltersEmpty": "Gizli filtre yok", - "settingsHiddenPathsTitle": "Gizli Yollar", + "settingsHiddenItemsTabPaths": "Gizli Yollar", "settingsHiddenPathsBanner": "Bu klasörlerdeki veya alt klasörlerindeki fotoğraflar ve videolar koleksiyonunuzda görünmeyecektir.", "addPathTooltip": "Yol ekle", "settingsStorageAccessTile": "Depolama erişimi", - "settingsStorageAccessTitle": "Depolama Erişimi", + "settingsStorageAccessPageTitle": "Depolama Erişimi", "settingsStorageAccessBanner": "Bazı dizinler, içlerindeki dosyaları değiştirmek için açık bir erişim izni gerektirir. Daha önce erişim izni verdiğiniz dizinleri buradan inceleyebilirsiniz.", "settingsStorageAccessEmpty": "Erişim izni yok", "settingsStorageAccessRevokeTooltip": "Geri al", - "settingsSectionAccessibility": "Erişilebilirlik", + "settingsAccessibilitySectionTitle": "Erişilebilirlik", "settingsRemoveAnimationsTile": "Animasyonları kaldır", - "settingsRemoveAnimationsTitle": "Animasyonları Kaldır", + "settingsRemoveAnimationsDialogTitle": "Animasyonları Kaldır", "settingsTimeToTakeActionTile": "Harekete geçme zamanı", - "settingsTimeToTakeActionTitle": "Harekete Geçme Zamanı", + "settingsTimeToTakeActionDialogTitle": "Harekete Geçme Zamanı", - "settingsSectionDisplay": "Ekran", - "settingsThemeBrightness": "Tema", + "settingsDisplaySectionTitle": "Ekran", + "settingsThemeBrightnessTile": "Tema", + "settingsThemeBrightnessDialogTitle": "Tema", "settingsThemeColorHighlights": "Renk vurguları", "settingsThemeEnableDynamicColor": "Dinamik renk", "settingsDisplayRefreshRateModeTile": "Görüntü yenileme hızı", - "settingsDisplayRefreshRateModeTitle": "Yenileme Hızı", + "settingsDisplayRefreshRateModeDialogTitle": "Yenileme Hızı", - "settingsSectionLanguage": "Dil ve Biçimler", - "settingsLanguage": "Dil", + "settingsLanguageSectionTitle": "Dil ve Biçimler", + "settingsLanguageTile": "Dil", + "settingsLanguagePageTitle": "Dil", "settingsCoordinateFormatTile": "Koordinat formatı", - "settingsCoordinateFormatTitle": "Koordinat Formatı", + "settingsCoordinateFormatDialogTitle": "Koordinat Formatı", "settingsUnitSystemTile": "Birimler", - "settingsUnitSystemTitle": "Birimler", + "settingsUnitSystemDialogTitle": "Birimler", "settingsWidgetPageTitle": "Fotoğraf Çerçevesi", + "settingsCollectionTile": "Koleksiyon", + "statsPageTitle": "İstatistikler", "statsWithGps": "{count, plural, =1{1 konuma sahip öğe} other{{count} konuma sahip öğe}}", - "statsTopCountries": "Başlıca Ülkeler", - "statsTopPlaces": "Başlıca Yerler", - "statsTopTags": "Başlıca Etiketler", + "statsTopCountriesSectionTitle": "Başlıca Ülkeler", + "statsTopPlacesSectionTitle": "Başlıca Yerler", + "statsTopTagsSectionTitle": "Başlıca Etiketler", "viewerOpenPanoramaButtonLabel": "PANORAMAYI AÇ", "viewerErrorUnknown": "Tüh!", @@ -603,7 +604,7 @@ "viewerInfoLabelCoordinates": "Koordinatlar", "viewerInfoLabelAddress": "Adres", - "mapStyleTitle": "Harita Şekli", + "mapStyleDialogTitle": "Harita Şekli", "mapStyleTooltip": "Harita şeklini seç", "mapZoomInTooltip": "Yakınlaştır", "mapZoomOutTooltip": "Uzaklaştır", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 2e6ebfd63..539f42cd0 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -27,6 +27,7 @@ "actionRemove": "移除", "resetTooltip": "重置", "saveTooltip": "保存", + "pickTooltip": "挑选", "doubleBackExitMessage": "再按一次退出", "doNotAskAgain": "不再询问", @@ -87,18 +88,20 @@ "entryInfoActionEditDate": "编辑日期和时间", "entryInfoActionEditLocation": "编辑位置", - "entryInfoActionEditDescription": "编辑备注", + "entryInfoActionEditTitleDescription": "编辑标题和描述", "entryInfoActionEditRating": "修改评分", "entryInfoActionEditTags": "编辑标签", "entryInfoActionRemoveMetadata": "移除元数据", "filterBinLabel": "回收站", "filterFavouriteLabel": "收藏夹", - "filterLocationEmptyLabel": "未定位", - "filterTagEmptyLabel": "无标签", + "filterNoDateLabel": "未注明日期", + "filterNoLocationLabel": "未定位", + "filterNoRatingLabel": "未评分", + "filterNoTagLabel": "无标签", + "filterNoTitleLabel": "无标题", "filterOnThisDayLabel": "选择日期", "filterRecentlyAddedLabel": "最近添加", - "filterRatingUnratedLabel": "未评分", "filterRatingRejectedLabel": "拒绝", "filterTypeAnimatedLabel": "动画", "filterTypeMotionPhotoLabel": "动态照片", @@ -179,16 +182,11 @@ "storageVolumeDescriptionFallbackNonPrimary": "SD 卡", "rootDirectoryDescription": "根目录", "otherDirectoryDescription": "“{name}” 目录", - "storageAccessDialogTitle": "存储访问权限", "storageAccessDialogMessage": "请在下一屏幕中选择“{volume}”的{directory},以授予本应用对其的访问权限", - "restrictedAccessDialogTitle": "限制访问", "restrictedAccessDialogMessage": "本应用无权修改“{volume}”的{directory}中的文件\n\n请使用预装的文件管理器或图库应用将项目移至其他目录", - "notEnoughSpaceDialogTitle": "空间不足", "notEnoughSpaceDialogMessage": "此操作需要“{volume}”上具有 {neededSize} 可用空间才能完成,实际仅剩 {freeSize}", - "missingSystemFilePickerDialogTitle": "找不到系统文件选择器", "missingSystemFilePickerDialogMessage": "系统文件选择器缺失或被禁用,请将其启用后再试", - "unsupportedTypeDialogTitle": "不支持的类型", "unsupportedTypeDialogMessage": "{count, plural, other{此操作不支持以下类型的项目: {types}.}}", "nameConflictDialogSingleSourceMessage": "目标文件夹中具有同名文件", @@ -197,7 +195,6 @@ "addShortcutDialogLabel": "快捷方式标签", "addShortcutButtonLabel": "添加", - "noMatchingAppDialogTitle": "无匹配的应用", "noMatchingAppDialogMessage": "无对应的处理程序", "binEntriesConfirmationDialogMessage": "{count, plural, =1{将此项移至回收站?} other{将这 {count} 项移至回收站?}}", @@ -226,7 +223,7 @@ "renameEntrySetPageTitle": "重命名", "renameEntrySetPagePatternFieldLabel": "命名模式", "renameEntrySetPageInsertTooltip": "插入字段", - "renameEntrySetPagePreview": "预览", + "renameEntrySetPagePreviewSectionTitle": "预览", "renameProcessorCounter": "计数器", "renameProcessorName": "名称", @@ -259,8 +256,6 @@ "locationPickerUseThisLocationButton": "使用此位置", - "editEntryDescriptionDialogTitle": "备注", - "editEntryRatingDialogTitle": "评分", "removeEntryMetadataDialogTitle": "元数据移除工具", @@ -289,9 +284,10 @@ "menuActionSlideshow": "幻灯片", "menuActionStats": "统计", - "viewDialogTabSort": "排序", - "viewDialogTabGroup": "分组", - "viewDialogTabLayout": "布局", + "viewDialogSortSectionTitle": "排序", + "viewDialogGroupSectionTitle": "分组", + "viewDialogLayoutSectionTitle": "布局", + "viewDialogReverseSortOrder": "反向排序", "tileLayoutGrid": "网格", "tileLayoutList": "列表", @@ -308,24 +304,24 @@ "aboutLinkLicense": "许可协议", "aboutLinkPolicy": "隐私政策", - "aboutBug": "报告错误", + "aboutBugSectionTitle": "报告错误", "aboutBugSaveLogInstruction": "将应用日志保存到文件", "aboutBugCopyInfoInstruction": "复制系统信息", "aboutBugCopyInfoButton": "复制", "aboutBugReportInstruction": "在 GitHub 上报告日志和系统信息", "aboutBugReportButton": "报告", - "aboutCredits": "鸣谢", + "aboutCreditsSectionTitle": "鸣谢", "aboutCreditsWorldAtlas1": "本应用使用的 TopoJSON 文件来自", "aboutCreditsWorldAtlas2": "符合 ISC 许可协议", - "aboutCreditsTranslators": "翻译人员", + "aboutTranslatorsSectionTitle": "翻译人员", - "aboutLicenses": "开源许可协议", + "aboutLicensesSectionTitle": "开源许可协议", "aboutLicensesBanner": "本应用使用以下开源软件包和库", - "aboutLicensesAndroidLibraries": "Android Libraries", - "aboutLicensesFlutterPlugins": "Flutter Plugins", - "aboutLicensesFlutterPackages": "Flutter Packages", - "aboutLicensesDartPackages": "Dart Packages", + "aboutLicensesAndroidLibrariesSectionTitle": "Android Libraries", + "aboutLicensesFlutterPluginsSectionTitle": "Flutter Plugins", + "aboutLicensesFlutterPackagesSectionTitle": "Flutter Packages", + "aboutLicensesDartPackagesSectionTitle": "Dart Packages", "aboutLicensesShowAllButtonLabel": "显示所有许可协议", "policyPageTitle": "隐私政策", @@ -345,11 +341,6 @@ "collectionSearchTitlesHintText": "搜索标题", - "collectionSortDate": "按日期", - "collectionSortSize": "按大小", - "collectionSortName": "按相册和文件名", - "collectionSortRating": "按评分", - "collectionGroupAlbum": "按相册", "collectionGroupMonth": "按月份", "collectionGroupDay": "按天", @@ -378,6 +369,8 @@ "collectionSelectSectionTooltip": "选择部分", "collectionDeselectSectionTooltip": "取消选择部分", + "drawerAboutButton": "关于", + "drawerSettingsButton": "设置", "drawerCollectionAll": "所有媒体集", "drawerCollectionFavourites": "收藏夹", "drawerCollectionImages": "图像", @@ -387,10 +380,25 @@ "drawerCollectionPanoramas": "全景图", "drawerCollectionRaws": "Raw 照片", "drawerCollectionSphericalVideos": "360° 视频", + "drawerAlbumPage": "相册", + "drawerCountryPage": "国家", + "drawerTagPage": "标签", - "chipSortDate": "按日期", - "chipSortName": "按名称", - "chipSortCount": "按数量", + "sortByDate": "按日期", + "sortByName": "按名称", + "sortByItemCount": "按数量", + "sortBySize": "按大小", + "sortByAlbumFileName": "按相册和文件名", + "sortByRating": "按评分", + + "sortOrderNewestFirst": "降序", + "sortOrderOldestFirst": "升序", + "sortOrderAtoZ": "A — Z", + "sortOrderZtoA": "Z — A", + "sortOrderHighestFirst": "由高到低", + "sortOrderLowestFirst": "由低到高", + "sortOrderLargestFirst": "由大到小", + "sortOrderSmallestFirst": "由小到大", "albumGroupTier": "按层级", "albumGroupVolume": "按存储卷", @@ -422,13 +430,14 @@ "binPageTitle": "回收站", "searchCollectionFieldHint": "搜索媒体集", - "searchSectionRecent": "最近", - "searchSectionDate": "日期", - "searchSectionAlbums": "相册", - "searchSectionCountries": "国家", - "searchSectionPlaces": "地点", - "searchSectionTags": "标签", - "searchSectionRating": "评分", + "searchRecentSectionTitle": "最近", + "searchDateSectionTitle": "日期", + "searchAlbumsSectionTitle": "相册", + "searchCountriesSectionTitle": "国家", + "searchPlacesSectionTitle": "地点", + "searchTagsSectionTitle": "标签", + "searchRatingSectionTitle": "评分", + "searchMetadataSectionTitle": "元数据", "settingsPageTitle": "设置", "settingsSystemDefault": "系统", @@ -437,37 +446,40 @@ "settingsSearchFieldLabel": "搜索设置", "settingsSearchEmpty": "无匹配设置项", "settingsActionExport": "导出", + "settingsActionExportDialogTitle": "导出", "settingsActionImport": "导入", + "settingsActionImportDialogTitle": "导入", "appExportCovers": "封面", "appExportFavourites": "收藏夹", "appExportSettings": "设置", - "settingsSectionNavigation": "导航", - "settingsHome": "主页", + "settingsNavigationSectionTitle": "导航", + "settingsHomeTile": "主页", + "settingsHomeDialogTitle": "主页", "settingsShowBottomNavigationBar": "显示底部导航栏", "settingsKeepScreenOnTile": "保持亮屏", - "settingsKeepScreenOnTitle": "保持亮屏", + "settingsKeepScreenOnDialogTitle": "保持亮屏", "settingsDoubleBackExit": "按两次返回键退出", - "settingsConfirmationDialogTile": "确认对话框", + "settingsConfirmationTile": "确认对话框", "settingsConfirmationDialogTitle": "确认对话框", - "settingsConfirmationDialogDeleteItems": "永久删除项目之前询问", - "settingsConfirmationDialogMoveToBinItems": "移至回收站之前询问", - "settingsConfirmationDialogMoveUndatedItems": "移动未注明日期的项目之前询问", + "settingsConfirmationBeforeDeleteItems": "永久删除项目之前询问", + "settingsConfirmationBeforeMoveToBinItems": "移至回收站之前询问", + "settingsConfirmationBeforeMoveUndatedItems": "移动未注明日期的项目之前询问", "settingsConfirmationAfterMoveToBinItems": "移至回收站后显示消息", "settingsNavigationDrawerTile": "导航栏菜单", - "settingsNavigationDrawerEditorTitle": "导航栏菜单", + "settingsNavigationDrawerEditorPageTitle": "导航栏菜单", "settingsNavigationDrawerBanner": "长按移动和重新排序菜单项", "settingsNavigationDrawerTabTypes": "类型", "settingsNavigationDrawerTabAlbums": "相册", "settingsNavigationDrawerTabPages": "页面", "settingsNavigationDrawerAddAlbum": "添加相册", - "settingsSectionThumbnails": "缩略图", + "settingsThumbnailSectionTitle": "缩略图", "settingsThumbnailOverlayTile": "叠加层", - "settingsThumbnailOverlayTitle": "叠加层", + "settingsThumbnailOverlayPageTitle": "叠加层", "settingsThumbnailShowFavouriteIcon": "显示收藏图标", "settingsThumbnailShowTagIcon": "显示标签图标", "settingsThumbnailShowLocationIcon": "显示位置图标", @@ -477,13 +489,13 @@ "settingsThumbnailShowVideoDuration": "显示视频时长", "settingsCollectionQuickActionsTile": "快速操作", - "settingsCollectionQuickActionEditorTitle": "快速操作", + "settingsCollectionQuickActionEditorPageTitle": "快速操作", "settingsCollectionQuickActionTabBrowsing": "浏览", "settingsCollectionQuickActionTabSelecting": "选择", "settingsCollectionBrowsingQuickActionEditorBanner": "按住并拖拽可移动按钮并选择浏览项目时显示的操作", "settingsCollectionSelectionQuickActionEditorBanner": "按住并拖拽可移动按钮并选择选择项目时显示的操作", - "settingsSectionViewer": "查看器", + "settingsViewerSectionTitle": "查看器", "settingsViewerGestureSideTapNext": "轻触屏幕边缘显示上/下一个项目", "settingsViewerUseCutout": "使用剪切区域", "settingsViewerMaximumBrightness": "最大亮度", @@ -491,14 +503,14 @@ "settingsImageBackground": "图像背景", "settingsViewerQuickActionsTile": "快速操作", - "settingsViewerQuickActionEditorTitle": "快速操作", + "settingsViewerQuickActionEditorPageTitle": "快速操作", "settingsViewerQuickActionEditorBanner": "按住并拖拽可移动按钮并选择查看器中显示的操作", - "settingsViewerQuickActionEditorDisplayedButtons": "显示的按钮", - "settingsViewerQuickActionEditorAvailableButtons": "可用按钮", + "settingsViewerQuickActionEditorDisplayedButtonsSectionTitle": "显示的按钮", + "settingsViewerQuickActionEditorAvailableButtonsSectionTitle": "可用按钮", "settingsViewerQuickActionEmpty": "无按钮", "settingsViewerOverlayTile": "叠加层", - "settingsViewerOverlayTitle": "叠加层", + "settingsViewerOverlayPageTitle": "叠加层", "settingsViewerShowOverlayOnOpening": "打开时显示", "settingsViewerShowMinimap": "显示小地图", "settingsViewerShowInformation": "显示信息", @@ -508,30 +520,30 @@ "settingsViewerEnableOverlayBlurEffect": "模糊特效", "settingsViewerSlideshowTile": "幻灯片", - "settingsViewerSlideshowTitle": "幻灯片", + "settingsViewerSlideshowPageTitle": "幻灯片", "settingsSlideshowRepeat": "重复", "settingsSlideshowShuffle": "随机播放", "settingsSlideshowFillScreen": "填充屏幕", "settingsSlideshowTransitionTile": "过渡动画", - "settingsSlideshowTransitionTitle": "过渡动画", + "settingsSlideshowTransitionDialogTitle": "过渡动画", "settingsSlideshowIntervalTile": "时间间隔", - "settingsSlideshowIntervalTitle": "时间间隔", + "settingsSlideshowIntervalDialogTitle": "时间间隔", "settingsSlideshowVideoPlaybackTile": "视频回放", - "settingsSlideshowVideoPlaybackTitle": "视频回放", + "settingsSlideshowVideoPlaybackDialogTitle": "视频回放", "settingsVideoPageTitle": "视频设置", - "settingsSectionVideo": "视频", + "settingsVideoSectionTitle": "视频", "settingsVideoShowVideos": "显示视频", "settingsVideoEnableHardwareAcceleration": "硬件加速", "settingsVideoEnableAutoPlay": "自动播放", "settingsVideoLoopModeTile": "循环模式", - "settingsVideoLoopModeTitle": "循环模式", + "settingsVideoLoopModeDialogTitle": "循环模式", "settingsSubtitleThemeTile": "字幕", - "settingsSubtitleThemeTitle": "字幕", + "settingsSubtitleThemePageTitle": "字幕", "settingsSubtitleThemeSample": "这是一个字幕示例。", "settingsSubtitleThemeTextAlignmentTile": "对齐方式", - "settingsSubtitleThemeTextAlignmentTitle": "对齐方式", + "settingsSubtitleThemeTextAlignmentDialogTitle": "对齐方式", "settingsSubtitleThemeTextSize": "文本大小", "settingsSubtitleThemeShowOutline": "显示轮廓和阴影", "settingsSubtitleThemeTextColor": "文本颜色", @@ -543,13 +555,13 @@ "settingsSubtitleThemeTextAlignmentRight": "居右", "settingsVideoControlsTile": "控件", - "settingsVideoControlsTitle": "控件", + "settingsVideoControlsPageTitle": "控件", "settingsVideoButtonsTile": "按钮", - "settingsVideoButtonsTitle": "按钮", + "settingsVideoButtonsDialogTitle": "按钮", "settingsVideoGestureDoubleTapTogglePlay": "双击播放/暂停", "settingsVideoGestureSideDoubleTapSeek": "双击屏幕边缘步进/步退", - "settingsSectionPrivacy": "隐私", + "settingsPrivacySectionTitle": "隐私", "settingsAllowInstalledAppAccess": "允许访问应用清单", "settingsAllowInstalledAppAccessSubtitle": "用于改善相册显示结果", "settingsAllowErrorReporting": "允许匿名错误报告", @@ -558,52 +570,56 @@ "settingsEnableBinSubtitle": "将删除项保留 30 天", "settingsHiddenItemsTile": "隐藏项", - "settingsHiddenItemsTitle": "隐藏项", + "settingsHiddenItemsPageTitle": "隐藏项", - "settingsHiddenFiltersTitle": "隐藏过滤器", + "settingsHiddenItemsTabFilters": "隐藏过滤器", "settingsHiddenFiltersBanner": "匹配隐藏过滤器的照片和视频将不会出现在你的媒体集中", "settingsHiddenFiltersEmpty": "无隐藏过滤器", - "settingsHiddenPathsTitle": "隐藏路径", + "settingsHiddenItemsTabPaths": "隐藏路径", "settingsHiddenPathsBanner": "以下文件夹及其子文件夹中的照片和视频将不会出现在你的媒体集中", "addPathTooltip": "添加路径", "settingsStorageAccessTile": "存储访问", - "settingsStorageAccessTitle": "存储访问", + "settingsStorageAccessPageTitle": "存储访问", "settingsStorageAccessBanner": "某些目录需要具有明确的访问权限才能修改其中的文件,你可以在此处查看你之前已授予访问权限的目录", "settingsStorageAccessEmpty": "尚未授予访问权限", "settingsStorageAccessRevokeTooltip": "撤消", - "settingsSectionAccessibility": "无障碍", + "settingsAccessibilitySectionTitle": "无障碍", "settingsRemoveAnimationsTile": "移除动画", - "settingsRemoveAnimationsTitle": "移除动画", + "settingsRemoveAnimationsDialogTitle": "移除动画", "settingsTimeToTakeActionTile": "生效时间", - "settingsTimeToTakeActionTitle": "生效时间", + "settingsTimeToTakeActionDialogTitle": "生效时间", - "settingsSectionDisplay": "显示", - "settingsThemeBrightness": "主题", + "settingsDisplaySectionTitle": "显示", + "settingsThemeBrightnessTile": "主题", + "settingsThemeBrightnessDialogTitle": "主题", "settingsThemeColorHighlights": "色彩强调", "settingsThemeEnableDynamicColor": "动态色彩", "settingsDisplayRefreshRateModeTile": "显示刷新率", - "settingsDisplayRefreshRateModeTitle": "刷新率", + "settingsDisplayRefreshRateModeDialogTitle": "刷新率", - "settingsSectionLanguage": "语言和格式", - "settingsLanguage": "界面语言", + "settingsLanguageSectionTitle": "语言和格式", + "settingsLanguageTile": "界面语言", + "settingsLanguagePageTitle": "界面语言", "settingsCoordinateFormatTile": "坐标格式", - "settingsCoordinateFormatTitle": "坐标格式", + "settingsCoordinateFormatDialogTitle": "坐标格式", "settingsUnitSystemTile": "单位", - "settingsUnitSystemTitle": "单位", + "settingsUnitSystemDialogTitle": "单位", "settingsScreenSaverPageTitle": "屏保", "settingsWidgetPageTitle": "相框", "settingsWidgetShowOutline": "轮廓", + "settingsCollectionTile": "媒体集", + "statsPageTitle": "统计", "statsWithGps": "{count, plural, other{{count} 项带位置信息}}", - "statsTopCountries": "热门国家", - "statsTopPlaces": "热门地点", - "statsTopTags": "热门标签", + "statsTopCountriesSectionTitle": "热门国家", + "statsTopPlacesSectionTitle": "热门地点", + "statsTopTagsSectionTitle": "热门标签", "viewerOpenPanoramaButtonLabel": "打开全景", "viewerSetWallpaperButtonLabel": "设置壁纸", @@ -614,6 +630,7 @@ "viewerInfoBackToViewerTooltip": "返回查看器", "viewerInfoUnknown": "未知", + "viewerInfoLabelDescription": "备注", "viewerInfoLabelTitle": "标题", "viewerInfoLabelDate": "日期", "viewerInfoLabelResolution": "分辨率", @@ -625,7 +642,7 @@ "viewerInfoLabelCoordinates": "坐标", "viewerInfoLabelAddress": "地址", - "mapStyleTitle": "地图样式", + "mapStyleDialogTitle": "地图样式", "mapStyleTooltip": "选择地图样式", "mapZoomInTooltip": "放大", "mapZoomOutTooltip": "缩小", diff --git a/lib/model/actions/entry_info_actions.dart b/lib/model/actions/entry_info_actions.dart index eca523345..d1b6a67b7 100644 --- a/lib/model/actions/entry_info_actions.dart +++ b/lib/model/actions/entry_info_actions.dart @@ -7,7 +7,7 @@ enum EntryInfoAction { // general editDate, editLocation, - editDescription, + editTitleDescription, editRating, editTags, removeMetadata, @@ -24,7 +24,7 @@ class EntryInfoActions { static const common = [ EntryInfoAction.editDate, EntryInfoAction.editLocation, - EntryInfoAction.editDescription, + EntryInfoAction.editTitleDescription, EntryInfoAction.editRating, EntryInfoAction.editTags, EntryInfoAction.removeMetadata, @@ -45,8 +45,8 @@ extension ExtraEntryInfoAction on EntryInfoAction { return context.l10n.entryInfoActionEditDate; case EntryInfoAction.editLocation: return context.l10n.entryInfoActionEditLocation; - case EntryInfoAction.editDescription: - return context.l10n.entryInfoActionEditDescription; + case EntryInfoAction.editTitleDescription: + return context.l10n.entryInfoActionEditTitleDescription; case EntryInfoAction.editRating: return context.l10n.entryInfoActionEditRating; case EntryInfoAction.editTags: @@ -88,7 +88,7 @@ extension ExtraEntryInfoAction on EntryInfoAction { return AIcons.date; case EntryInfoAction.editLocation: return AIcons.location; - case EntryInfoAction.editDescription: + case EntryInfoAction.editTitleDescription: return AIcons.description; case EntryInfoAction.editRating: return AIcons.editRating; diff --git a/lib/model/actions/entry_set_actions.dart b/lib/model/actions/entry_set_actions.dart index f6f25d39d..0efb48962 100644 --- a/lib/model/actions/entry_set_actions.dart +++ b/lib/model/actions/entry_set_actions.dart @@ -31,7 +31,7 @@ enum EntrySetAction { flip, editDate, editLocation, - editDescription, + editTitleDescription, editRating, editTags, removeMetadata, @@ -100,7 +100,7 @@ class EntrySetActions { static const edit = [ EntrySetAction.editDate, EntrySetAction.editLocation, - EntrySetAction.editDescription, + EntrySetAction.editTitleDescription, EntrySetAction.editRating, EntrySetAction.editTags, EntrySetAction.removeMetadata, @@ -164,8 +164,8 @@ extension ExtraEntrySetAction on EntrySetAction { return context.l10n.entryInfoActionEditDate; case EntrySetAction.editLocation: return context.l10n.entryInfoActionEditLocation; - case EntrySetAction.editDescription: - return context.l10n.entryInfoActionEditDescription; + case EntrySetAction.editTitleDescription: + return context.l10n.entryInfoActionEditTitleDescription; case EntrySetAction.editRating: return context.l10n.entryInfoActionEditRating; case EntrySetAction.editTags: @@ -233,7 +233,7 @@ extension ExtraEntrySetAction on EntrySetAction { return AIcons.date; case EntrySetAction.editLocation: return AIcons.location; - case EntrySetAction.editDescription: + case EntrySetAction.editTitleDescription: return AIcons.description; case EntrySetAction.editRating: return AIcons.editRating; diff --git a/lib/model/entry.dart b/lib/model/entry.dart index 1a70bf464..65443958b 100644 --- a/lib/model/entry.dart +++ b/lib/model/entry.dart @@ -213,9 +213,13 @@ class AvesEntry { return _extension; } + String? get storagePath => trashed ? trashDetails?.path : path; + + String? get storageDirectory => trashed ? pContext.dirname(trashDetails!.path) : directory; + bool get isMissingAtPath { - final effectivePath = trashed ? trashDetails?.path : path; - return effectivePath != null && !File(effectivePath).existsSync(); + final _storagePath = storagePath; + return _storagePath != null && !File(_storagePath).existsSync(); } // the MIME type reported by the Media Store is unreliable @@ -279,7 +283,7 @@ class AvesEntry { bool get canEditLocation => canEdit && canEditExif; - bool get canEditDescription => canEdit && (canEditExif || canEditXmp); + bool get canEditTitleDescription => canEdit && canEditXmp; bool get canEditRating => canEdit && canEditXmp; @@ -738,7 +742,12 @@ class AvesEntry { } // when the MIME type or the image itself changed (e.g. after rotation) - Future _onVisualFieldChanged(String oldMimeType, int? oldDateModifiedSecs, int oldRotationDegrees, bool oldIsFlipped) async { + Future _onVisualFieldChanged( + String oldMimeType, + int? oldDateModifiedSecs, + int oldRotationDegrees, + bool oldIsFlipped, + ) async { if ((!MimeTypes.refersToSameType(oldMimeType, mimeType) && !MimeTypes.isVideo(oldMimeType)) || oldDateModifiedSecs != dateModifiedSecs || oldRotationDegrees != rotationDegrees || oldIsFlipped != isFlipped) { await EntryCache.evict(uri, oldMimeType, oldDateModifiedSecs, oldRotationDegrees, oldIsFlipped); imageChangeNotifier.notify(); diff --git a/lib/model/entry_images.dart b/lib/model/entry_images.dart index b0f44359f..256770f66 100644 --- a/lib/model/entry_images.dart +++ b/lib/model/entry_images.dart @@ -53,7 +53,7 @@ extension ExtraAvesEntryImages on AvesEntry { pageId: pageId, rotationDegrees: rotationDegrees, isFlipped: isFlipped, - expectedContentLength: sizeBytes, + sizeBytes: sizeBytes, ); bool _isReady(Object providerKey) => imageCache.statusForKey(providerKey).keepAlive; diff --git a/lib/model/entry_metadata_edition.dart b/lib/model/entry_metadata_edition.dart index f6fdc4cc1..1659b20bb 100644 --- a/lib/model/entry_metadata_edition.dart +++ b/lib/model/entry_metadata_edition.dart @@ -140,37 +140,62 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry { return _changeOrientation(() => metadataEditService.flip(this)); } - // write: + // write title: + // - IPTC / object-name, if IPTC exists + // - XMP / dc:title + // write description: // - Exif / ImageDescription // - IPTC / caption-abstract, if IPTC exists // - XMP / dc:description - Future> editDescription(String? description) async { + Future> editTitleDescription(Map fields) async { final Set dataTypes = {}; final Map metadata = {}; final missingDate = await _missingDateCheckAndExifEdit(dataTypes); - if (canEditExif) { + final editTitle = fields.keys.contains(DescriptionField.title); + final editDescription = fields.keys.contains(DescriptionField.description); + final title = fields[DescriptionField.title]; + final description = fields[DescriptionField.description]; + + if (canEditExif && editDescription) { metadata[MetadataType.exif] = {MetadataField.exifImageDescription.exifInterfaceTag!: description}; } if (canEditIptc) { final iptc = await metadataFetchService.getIptc(this); if (iptc != null) { - editIptcValues(iptc, IPTC.applicationRecord, IPTC.captionAbstractTag, {if (description != null) description}); + if (editTitle) { + editIptcValues(iptc, IPTC.applicationRecord, IPTC.objectName, {if (title != null) title}); + } + if (editDescription) { + editIptcValues(iptc, IPTC.applicationRecord, IPTC.captionAbstractTag, {if (description != null) description}); + } metadata[MetadataType.iptc] = iptc; } } if (canEditXmp) { metadata[MetadataType.xmp] = await _editXmp((descriptions) { - final modified = XMP.setAttribute( - descriptions, - XMP.dcDescription, - description, - namespace: Namespaces.dc, - strat: XmpEditStrategy.always, - ); + var modified = false; + if (editTitle) { + modified |= XMP.setAttribute( + descriptions, + XMP.dcTitle, + title, + namespace: Namespaces.dc, + strat: XmpEditStrategy.always, + ); + } + if (editDescription) { + modified |= XMP.setAttribute( + descriptions, + XMP.dcDescription, + description, + namespace: Namespaces.dc, + strat: XmpEditStrategy.always, + ); + } if (modified && missingDate != null) { editCreateDateXmp(descriptions, missingDate); } @@ -182,6 +207,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry { if (newFields.isNotEmpty) { dataTypes.addAll({ EntryDataType.basic, + EntryDataType.catalog, }); } @@ -467,3 +493,5 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry { }; } } + +enum DescriptionField { title, description } diff --git a/lib/model/filters/filters.dart b/lib/model/filters/filters.dart index 1c0b7a300..a57b0cce7 100644 --- a/lib/model/filters/filters.dart +++ b/lib/model/filters/filters.dart @@ -8,6 +8,7 @@ import 'package:aves/model/filters/date.dart'; import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/location.dart'; import 'package:aves/model/filters/mime.dart'; +import 'package:aves/model/filters/missing.dart'; import 'package:aves/model/filters/path.dart'; import 'package:aves/model/filters/query.dart'; import 'package:aves/model/filters/rating.dart'; @@ -30,12 +31,14 @@ abstract class CollectionFilter extends Equatable implements Comparable _location; @override - String getLabel(BuildContext context) => _location.isEmpty ? context.l10n.filterLocationEmptyLabel : _location; + String getLabel(BuildContext context) => _location.isEmpty ? context.l10n.filterNoLocationLabel : _location; @override Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) { diff --git a/lib/model/filters/missing.dart b/lib/model/filters/missing.dart new file mode 100644 index 000000000..119355e3e --- /dev/null +++ b/lib/model/filters/missing.dart @@ -0,0 +1,73 @@ +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/theme/icons.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:flutter/widgets.dart'; + +class MissingFilter extends CollectionFilter { + static const type = 'missing'; + + static const _date = 'date'; + static const _title = 'title'; + + final String metadataType; + late final EntryFilter _test; + late final IconData _icon; + + static final date = MissingFilter._private(_date); + static final title = MissingFilter._private(_title); + + @override + List get props => [metadataType]; + + MissingFilter._private(this.metadataType) { + switch (metadataType) { + case _date: + _test = (entry) => (entry.catalogMetadata?.dateMillis ?? 0) == 0; + _icon = AIcons.dateUndated; + break; + case _title: + _test = (entry) => (entry.catalogMetadata?.xmpTitle ?? '').isEmpty; + _icon = AIcons.descriptionUntitled; + break; + } + } + + factory MissingFilter.fromMap(Map json) { + return MissingFilter._private( + json['metadataType'], + ); + } + + @override + Map toMap() => { + 'type': type, + 'metadataType': metadataType, + }; + + @override + EntryFilter get test => _test; + + @override + String get universalLabel => metadataType; + + @override + String getLabel(BuildContext context) { + switch (metadataType) { + case _date: + return context.l10n.filterNoDateLabel; + case _title: + return context.l10n.filterNoTitleLabel; + default: + return metadataType; + } + } + + @override + Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(_icon, size: size); + + @override + String get category => type; + + @override + String get key => '$type-$metadataType'; +} diff --git a/lib/model/filters/rating.dart b/lib/model/filters/rating.dart index 7eadd4e8c..ad516aa5e 100644 --- a/lib/model/filters/rating.dart +++ b/lib/model/filters/rating.dart @@ -57,7 +57,7 @@ class RatingFilter extends CollectionFilter { case -1: return context.l10n.filterRatingRejectedLabel; case 0: - return context.l10n.filterRatingUnratedLabel; + return context.l10n.filterNoRatingLabel; default: return '\u2B50' * rating; } diff --git a/lib/model/filters/recent.dart b/lib/model/filters/recent.dart index 3d8e8ae92..f6ffb1a0b 100644 --- a/lib/model/filters/recent.dart +++ b/lib/model/filters/recent.dart @@ -38,7 +38,7 @@ class RecentlyAddedFilter extends CollectionFilter { String getLabel(BuildContext context) => context.l10n.filterRecentlyAddedLabel; @override - Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.recent, size: size); + Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.dateRecent, size: size); @override String get category => type; diff --git a/lib/model/filters/tag.dart b/lib/model/filters/tag.dart index 8c57586ab..b72624da1 100644 --- a/lib/model/filters/tag.dart +++ b/lib/model/filters/tag.dart @@ -44,7 +44,7 @@ class TagFilter extends CoveredCollectionFilter { String get universalLabel => tag; @override - String getLabel(BuildContext context) => tag.isEmpty ? context.l10n.filterTagEmptyLabel : tag; + String getLabel(BuildContext context) => tag.isEmpty ? context.l10n.filterNoTagLabel : tag; @override Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => showGenericIcon ? Icon(tag.isEmpty ? AIcons.tagUntagged : AIcons.tag, size: size) : null; diff --git a/lib/model/filters/type.dart b/lib/model/filters/type.dart index 548163004..bef26c27b 100644 --- a/lib/model/filters/type.dart +++ b/lib/model/filters/type.dart @@ -46,7 +46,7 @@ class TypeFilter extends CollectionFilter { break; case _panorama: _test = (entry) => entry.isImage && entry.is360; - _icon = AIcons.threeSixty; + _icon = AIcons.panorama; break; case _raw: _test = (entry) => entry.isRaw; @@ -54,7 +54,7 @@ class TypeFilter extends CollectionFilter { break; case _sphericalVideo: _test = (entry) => entry.isVideo && entry.is360; - _icon = AIcons.threeSixty; + _icon = AIcons.sphericalVideo; break; } } diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart index 9a0c27f6e..4e21d4ba8 100644 --- a/lib/model/settings/defaults.dart +++ b/lib/model/settings/defaults.dart @@ -5,7 +5,7 @@ import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/filters/recent.dart'; import 'package:aves/model/naming_pattern.dart'; import 'package:aves/model/settings/enums/enums.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart'; diff --git a/lib/model/settings/enums/home_page.dart b/lib/model/settings/enums/home_page.dart index 8dac921c6..c52228d8a 100644 --- a/lib/model/settings/enums/home_page.dart +++ b/lib/model/settings/enums/home_page.dart @@ -9,9 +9,9 @@ extension ExtraHomePageSetting on HomePageSetting { String getName(BuildContext context) { switch (this) { case HomePageSetting.collection: - return context.l10n.collectionPageTitle; + return context.l10n.drawerCollectionAll; case HomePageSetting.albums: - return context.l10n.albumPageTitle; + return context.l10n.drawerAlbumPage; } } diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index fdc95a463..fd21a2409 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:math'; import 'package:aves/l10n/l10n.dart'; @@ -8,13 +9,14 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/defaults.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/map_style.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/services/common/optional_event_channel.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves_map/aves_map.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:latlong2/latlong.dart'; final Settings settings = Settings._private(); @@ -73,6 +75,7 @@ class Settings extends ChangeNotifier { // collection static const collectionGroupFactorKey = 'collection_group_factor'; static const collectionSortFactorKey = 'collection_sort_factor'; + static const collectionSortReverseKey = 'collection_sort_reverse'; static const collectionBrowsingQuickActionsKey = 'collection_browsing_quick_actions'; static const collectionSelectionQuickActionsKey = 'collection_selection_quick_actions'; static const showThumbnailFavouriteKey = 'show_thumbnail_favourite'; @@ -88,6 +91,9 @@ class Settings extends ChangeNotifier { static const albumSortFactorKey = 'album_sort_factor'; static const countrySortFactorKey = 'country_sort_factor'; static const tagSortFactorKey = 'tag_sort_factor'; + static const albumSortReverseKey = 'album_sort_reverse'; + static const countrySortReverseKey = 'country_sort_reverse'; + static const tagSortReverseKey = 'tag_sort_reverse'; static const pinnedFiltersKey = 'pinned_filters'; static const hiddenFiltersKey = 'hidden_filters'; @@ -121,11 +127,14 @@ class Settings extends ChangeNotifier { static const subtitleBackgroundColorKey = 'subtitle_background_color'; // info - static const infoMapStyleKey = 'info_map_style'; static const infoMapZoomKey = 'info_map_zoom'; static const coordinateFormatKey = 'coordinates_format'; static const unitSystemKey = 'unit_system'; + // map + static const mapStyleKey = 'info_map_style'; + static const mapDefaultCenterKey = 'map_default_center'; + // search static const saveSearchHistoryKey = 'save_search_history'; static const searchHistoryKey = 'search_history'; @@ -172,6 +181,7 @@ class Settings extends ChangeNotifier { Future init({required bool monitorPlatformSettings}) async { await settingsStore.init(); + _appliedLocale = null; if (monitorPlatformSettings) { _platformSettingsChangeChannel.receiveBroadcastStream().listen((event) => _onPlatformSettingsChange(event as Map?)); } @@ -197,10 +207,10 @@ class Settings extends ChangeNotifier { // availability final defaultMapStyle = mobileServices.defaultMapStyle; if (mobileServices.mapStyles.contains(defaultMapStyle)) { - infoMapStyle = defaultMapStyle; + mapStyle = defaultMapStyle; } else { final styles = EntryMapStyle.values.whereNot((v) => v.needMobileService).toList(); - infoMapStyle = styles[Random().nextInt(styles.length)]; + mapStyle = styles[Random().nextInt(styles.length)]; } } @@ -382,6 +392,10 @@ class Settings extends ChangeNotifier { set collectionSortFactor(EntrySortFactor newValue) => setAndNotify(collectionSortFactorKey, newValue.toString()); + bool get collectionSortReverse => getBoolOrDefault(collectionSortReverseKey, false); + + set collectionSortReverse(bool newValue) => setAndNotify(collectionSortReverseKey, newValue); + List get collectionBrowsingQuickActions => getEnumListOrDefault(collectionBrowsingQuickActionsKey, SettingsDefaults.collectionBrowsingQuickActions, EntrySetAction.values); set collectionBrowsingQuickActions(List newValue) => setAndNotify(collectionBrowsingQuickActionsKey, newValue.map((v) => v.toString()).toList()); @@ -436,6 +450,18 @@ class Settings extends ChangeNotifier { set tagSortFactor(ChipSortFactor newValue) => setAndNotify(tagSortFactorKey, newValue.toString()); + bool get albumSortReverse => getBoolOrDefault(albumSortReverseKey, false); + + set albumSortReverse(bool newValue) => setAndNotify(albumSortReverseKey, newValue); + + bool get countrySortReverse => getBoolOrDefault(countrySortReverseKey, false); + + set countrySortReverse(bool newValue) => setAndNotify(countrySortReverseKey, newValue); + + bool get tagSortReverse => getBoolOrDefault(tagSortReverseKey, false); + + set tagSortReverse(bool newValue) => setAndNotify(tagSortReverseKey, newValue); + Set get pinnedFilters => (getStringList(pinnedFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet(); set pinnedFilters(Set newValue) => setAndNotify(pinnedFiltersKey, newValue.map((filter) => filter.toJson()).toList()); @@ -555,14 +581,6 @@ class Settings extends ChangeNotifier { // info - EntryMapStyle get infoMapStyle { - final preferred = getEnumOrDefault(infoMapStyleKey, SettingsDefaults.infoMapStyle, EntryMapStyle.values); - final available = availability.mapStyles; - return available.contains(preferred) ? preferred : available.first; - } - - set infoMapStyle(EntryMapStyle newValue) => setAndNotify(infoMapStyleKey, newValue.toString()); - double get infoMapZoom => getDouble(infoMapZoomKey) ?? SettingsDefaults.infoMapZoom; set infoMapZoom(double newValue) => setAndNotify(infoMapZoomKey, newValue); @@ -575,6 +593,23 @@ class Settings extends ChangeNotifier { set unitSystem(UnitSystem newValue) => setAndNotify(unitSystemKey, newValue.toString()); + // map + + EntryMapStyle get mapStyle { + final preferred = getEnumOrDefault(mapStyleKey, SettingsDefaults.infoMapStyle, EntryMapStyle.values); + final available = availability.mapStyles; + return available.contains(preferred) ? preferred : available.first; + } + + set mapStyle(EntryMapStyle newValue) => setAndNotify(mapStyleKey, newValue.toString()); + + LatLng? get mapDefaultCenter { + final json = getString(mapDefaultCenterKey); + return json != null ? LatLng.fromJson(jsonDecode(json)) : null; + } + + set mapDefaultCenter(LatLng? newValue) => setAndNotify(mapDefaultCenterKey, newValue != null ? jsonEncode(newValue.toJson()) : null); + // search bool get saveSearchHistory => getBoolOrDefault(saveSearchHistoryKey, SettingsDefaults.saveSearchHistory); @@ -813,6 +848,7 @@ class Settings extends ChangeNotifier { case confirmMoveUndatedItemsKey: case confirmAfterMoveToBinKey: case setMetadataDateBeforeFileOpKey: + case collectionSortReverseKey: case showThumbnailFavouriteKey: case showThumbnailTagKey: case showThumbnailLocationKey: @@ -820,6 +856,9 @@ class Settings extends ChangeNotifier { case showThumbnailRatingKey: case showThumbnailRawKey: case showThumbnailVideoDurationKey: + case albumSortReverseKey: + case countrySortReverseKey: + case tagSortReverseKey: case showOverlayOnOpeningKey: case showOverlayMinimapKey: case showOverlayInfoKey: @@ -862,7 +901,8 @@ class Settings extends ChangeNotifier { case videoLoopModeKey: case videoControlsKey: case subtitleTextAlignmentKey: - case infoMapStyleKey: + case mapStyleKey: + case mapDefaultCenterKey: case coordinateFormatKey: case unitSystemKey: case accessibilityAnimationsKey: diff --git a/lib/model/source/album.dart b/lib/model/source/album.dart index 2f91dccbc..6099639a6 100644 --- a/lib/model/source/album.dart +++ b/lib/model/source/album.dart @@ -4,12 +4,13 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/utils/android_file_utils.dart'; +import 'package:aves/utils/collection_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:collection/collection.dart'; import 'package:flutter/widgets.dart'; mixin AlbumMixin on SourceBase { - final Set _directories = {}; + final Set _directories = {}; final Set _newAlbums = {}; List get rawAlbums => List.unmodifiable(_directories); @@ -67,7 +68,7 @@ mixin AlbumMixin on SourceBase { void addDirectories({required Set albums, bool notify = true}) { if (!_directories.containsAll(albums)) { - _directories.addAll(albums); + _directories.addAll(albums.whereNotNull()); _onAlbumChanged(notify: notify); } } @@ -95,7 +96,7 @@ mixin AlbumMixin on SourceBase { // filter summary // by directory - final Map _filterEntryCountMap = {}; + final Map _filterEntryCountMap = {}, _filterSizeMap = {}; final Map _filterRecentEntryMap = {}; void invalidateAlbumFilterSummary({ @@ -103,10 +104,11 @@ mixin AlbumMixin on SourceBase { Set? directories, bool notify = true, }) { - if (_filterEntryCountMap.isEmpty && _filterRecentEntryMap.isEmpty) return; + if (_filterEntryCountMap.isEmpty && _filterSizeMap.isEmpty && _filterRecentEntryMap.isEmpty) return; if (entries == null && directories == null) { _filterEntryCountMap.clear(); + _filterSizeMap.clear(); _filterRecentEntryMap.clear(); } else { directories ??= {}; @@ -115,6 +117,7 @@ mixin AlbumMixin on SourceBase { } directories.forEach((directory) { _filterEntryCountMap.remove(directory); + _filterSizeMap.remove(directory); _filterRecentEntryMap.remove(directory); }); } @@ -127,6 +130,10 @@ mixin AlbumMixin on SourceBase { return _filterEntryCountMap.putIfAbsent(filter.album, () => visibleEntries.where(filter.test).length); } + int albumSize(AlbumFilter filter) { + return _filterSizeMap.putIfAbsent(filter.album, () => visibleEntries.where(filter.test).map((v) => v.sizeBytes).sum); + } + AvesEntry? albumRecentEntry(AlbumFilter filter) { return _filterRecentEntryMap.putIfAbsent(filter.album, () => sortedEntriesByDate.firstWhereOrNull(filter.test)); } diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart index 71c94c239..fbeec1e64 100644 --- a/lib/model/source/collection_lens.dart +++ b/lib/model/source/collection_lens.dart @@ -22,13 +22,14 @@ import 'package:aves/utils/collection_utils.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; -import 'enums.dart'; +import 'enums/enums.dart'; class CollectionLens with ChangeNotifier { final CollectionSource source; final Set filters; EntryGroupFactor sectionFactor; EntrySortFactor sortFactor; + bool sortReverse; final AChangeNotifier filterChangeNotifier = AChangeNotifier(), sortSectionChangeNotifier = AChangeNotifier(); final List _subscriptions = []; int? id; @@ -49,7 +50,8 @@ class CollectionLens with ChangeNotifier { this.fixedSelection, }) : filters = (filters ?? {}).whereNotNull().toSet(), sectionFactor = settings.collectionSectionFactor, - sortFactor = settings.collectionSortFactor { + sortFactor = settings.collectionSortFactor, + sortReverse = settings.collectionSortReverse { id ??= hashCode; if (listenToSource) { final sourceEvents = source.eventBus; @@ -84,6 +86,7 @@ class CollectionLens with ChangeNotifier { .where((event) => [ Settings.collectionSortFactorKey, Settings.collectionGroupFactorKey, + Settings.collectionSortReverseKey, ].contains(event.key)) .listen((_) => _onSettingsChanged())); refresh(); @@ -218,6 +221,9 @@ class CollectionLens with ChangeNotifier { _filteredSortedEntries.sort(AvesEntry.compareBySize); break; } + if (sortReverse) { + _filteredSortedEntries = _filteredSortedEntries.reversed.toList(); + } } void _applySection() { @@ -247,7 +253,8 @@ class CollectionLens with ChangeNotifier { break; case EntrySortFactor.name: final byAlbum = groupBy(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory)); - sections = SplayTreeMap>.of(byAlbum, (a, b) => source.compareAlbumsByName(a.directory!, b.directory!)); + final compare = sortReverse ? (a, b) => source.compareAlbumsByName(b.directory!, a.directory!) : (a, b) => source.compareAlbumsByName(a.directory!, b.directory!); + sections = SplayTreeMap>.of(byAlbum, compare); break; case EntrySortFactor.rating: sections = groupBy(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating)); @@ -281,12 +288,14 @@ class CollectionLens with ChangeNotifier { void _onSettingsChanged() { final newSortFactor = settings.collectionSortFactor; final newSectionFactor = settings.collectionSectionFactor; + final newSortReverse = settings.collectionSortReverse; - final needSort = sortFactor != newSortFactor; + final needSort = sortFactor != newSortFactor || sortReverse != newSortReverse; final needSection = needSort || sectionFactor != newSectionFactor; if (needSort) { sortFactor = newSortFactor; + sortReverse = newSortReverse; _applySort(); } if (needSection) { diff --git a/lib/model/source/collection_source.dart b/lib/model/source/collection_source.dart index a9fd2886e..20b3a5063 100644 --- a/lib/model/source/collection_source.dart +++ b/lib/model/source/collection_source.dart @@ -13,7 +13,7 @@ import 'package:aves/model/metadata/trash.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/analysis_controller.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/model/source/events.dart'; import 'package:aves/model/source/location.dart'; import 'package:aves/model/source/tag.dart'; @@ -283,6 +283,16 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM }) async { if (movedOps.isEmpty) return; + final replacedUris = movedOps + .map((movedOp) => movedOp.newFields['path'] as String?) + .map((targetPath) { + final existingEntry = _rawEntries.firstWhereOrNull((entry) => entry.path == targetPath && !entry.trashed); + return existingEntry?.uri; + }) + .whereNotNull() + .toSet(); + await removeEntries(replacedUris, includeTrash: false); + final fromAlbums = {}; final movedEntries = {}; final copy = moveType == MoveType.copy; @@ -458,6 +468,13 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM return 0; } + int size(CollectionFilter filter) { + if (filter is AlbumFilter) return albumSize(filter); + if (filter is LocationFilter) return countrySize(filter); + if (filter is TagFilter) return tagSize(filter); + return 0; + } + AvesEntry? recentEntry(CollectionFilter filter) { if (filter is AlbumFilter) return albumRecentEntry(filter); if (filter is LocationFilter) return countryRecentEntry(filter); diff --git a/lib/model/source/enums.dart b/lib/model/source/enums/enums.dart similarity index 85% rename from lib/model/source/enums.dart rename to lib/model/source/enums/enums.dart index 6035caa19..ae59b22f3 100644 --- a/lib/model/source/enums.dart +++ b/lib/model/source/enums/enums.dart @@ -1,6 +1,6 @@ enum SourceState { loading, cataloguing, locatingCountries, locatingPlaces, ready } -enum ChipSortFactor { date, name, count } +enum ChipSortFactor { date, name, count, size } enum AlbumChipGroupFactor { none, importance, volume } diff --git a/lib/model/source/enums/view.dart b/lib/model/source/enums/view.dart new file mode 100644 index 000000000..a80c116c9 --- /dev/null +++ b/lib/model/source/enums/view.dart @@ -0,0 +1,105 @@ +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:flutter/widgets.dart'; + +import 'enums.dart'; + +extension ExtraEntrySortFactor on EntrySortFactor { + String getName(BuildContext context) { + final l10n = context.l10n; + switch (this) { + case EntrySortFactor.date: + return l10n.sortByDate; + case EntrySortFactor.name: + return l10n.sortByAlbumFileName; + case EntrySortFactor.rating: + return l10n.sortByRating; + case EntrySortFactor.size: + return l10n.sortBySize; + } + } + + String getOrderName(BuildContext context, bool reverse) { + final l10n = context.l10n; + switch (this) { + case EntrySortFactor.date: + return reverse ? l10n.sortOrderOldestFirst : l10n.sortOrderNewestFirst; + case EntrySortFactor.name: + return reverse ? l10n.sortOrderZtoA : l10n.sortOrderAtoZ; + case EntrySortFactor.rating: + return reverse ? l10n.sortOrderLowestFirst : l10n.sortOrderHighestFirst; + case EntrySortFactor.size: + return reverse ? l10n.sortOrderSmallestFirst : l10n.sortOrderLargestFirst; + } + } +} + +extension ExtraChipSortFactor on ChipSortFactor { + String getName(BuildContext context) { + final l10n = context.l10n; + switch (this) { + case ChipSortFactor.date: + return l10n.sortByDate; + case ChipSortFactor.name: + return l10n.sortByName; + case ChipSortFactor.count: + return l10n.sortByItemCount; + case ChipSortFactor.size: + return l10n.sortBySize; + } + } + + String getOrderName(BuildContext context, bool reverse) { + final l10n = context.l10n; + switch (this) { + case ChipSortFactor.date: + return reverse ? l10n.sortOrderOldestFirst : l10n.sortOrderNewestFirst; + case ChipSortFactor.name: + return reverse ? l10n.sortOrderZtoA : l10n.sortOrderAtoZ; + case ChipSortFactor.count: + case ChipSortFactor.size: + return reverse ? l10n.sortOrderSmallestFirst : l10n.sortOrderLargestFirst; + } + } +} + +extension ExtraEntryGroupFactor on EntryGroupFactor { + String getName(BuildContext context) { + final l10n = context.l10n; + switch (this) { + case EntryGroupFactor.album: + return l10n.collectionGroupAlbum; + case EntryGroupFactor.month: + return l10n.collectionGroupMonth; + case EntryGroupFactor.day: + return l10n.collectionGroupDay; + case EntryGroupFactor.none: + return l10n.collectionGroupNone; + } + } +} + +extension ExtraAlbumChipGroupFactor on AlbumChipGroupFactor { + String getName(BuildContext context) { + final l10n = context.l10n; + switch (this) { + case AlbumChipGroupFactor.importance: + return l10n.albumGroupTier; + case AlbumChipGroupFactor.volume: + return l10n.albumGroupVolume; + case AlbumChipGroupFactor.none: + return l10n.albumGroupNone; + } + } +} + +extension ExtraTileLayout on TileLayout { + String getName(BuildContext context) { + final l10n = context.l10n; + switch (this) { + case TileLayout.grid: + return l10n.tileLayoutGrid; + case TileLayout.list: + return l10n.tileLayoutList; + } + } +} diff --git a/lib/model/source/location.dart b/lib/model/source/location.dart index 1b29dc43c..f063aab80 100644 --- a/lib/model/source/location.dart +++ b/lib/model/source/location.dart @@ -7,8 +7,9 @@ import 'package:aves/model/metadata/address.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/analysis_controller.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/services/common/services.dart'; +import 'package:aves/utils/collection_utils.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:tuple/tuple.dart'; @@ -180,7 +181,7 @@ mixin LocationMixin on SourceBase { // filter summary // by country code - final Map _filterEntryCountMap = {}; + final Map _filterEntryCountMap = {}, _filterSizeMap = {}; final Map _filterRecentEntryMap = {}; void invalidateCountryFilterSummary({ @@ -188,10 +189,11 @@ mixin LocationMixin on SourceBase { Set? countryCodes, bool notify = true, }) { - if (_filterEntryCountMap.isEmpty && _filterRecentEntryMap.isEmpty) return; + if (_filterEntryCountMap.isEmpty && _filterSizeMap.isEmpty && _filterRecentEntryMap.isEmpty) return; if (entries == null && countryCodes == null) { _filterEntryCountMap.clear(); + _filterSizeMap.clear(); _filterRecentEntryMap.clear(); } else { countryCodes ??= {}; @@ -200,6 +202,7 @@ mixin LocationMixin on SourceBase { } countryCodes.forEach((countryCode) { _filterEntryCountMap.remove(countryCode); + _filterSizeMap.remove(countryCode); _filterRecentEntryMap.remove(countryCode); }); } @@ -214,6 +217,12 @@ mixin LocationMixin on SourceBase { return _filterEntryCountMap.putIfAbsent(countryCode, () => visibleEntries.where(filter.test).length); } + int countrySize(LocationFilter filter) { + final countryCode = filter.countryCode; + if (countryCode == null) return 0; + return _filterSizeMap.putIfAbsent(countryCode, () => visibleEntries.where(filter.test).map((v) => v.sizeBytes).sum); + } + AvesEntry? countryRecentEntry(LocationFilter filter) { final countryCode = filter.countryCode; if (countryCode == null) return null; diff --git a/lib/model/source/media_store_source.dart b/lib/model/source/media_store_source.dart index 80318c595..18e6d4d44 100644 --- a/lib/model/source/media_store_source.dart +++ b/lib/model/source/media_store_source.dart @@ -7,7 +7,7 @@ import 'package:aves/model/favourites.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/analysis_controller.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:collection/collection.dart'; diff --git a/lib/model/source/source_state.dart b/lib/model/source/source_state.dart index 5b5ff3a86..35d57bd7d 100644 --- a/lib/model/source/source_state.dart +++ b/lib/model/source/source_state.dart @@ -1,5 +1,5 @@ import 'package:aves/l10n/l10n.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; extension ExtraSourceState on SourceState { String? getName(AppLocalizations l10n) { diff --git a/lib/model/source/tag.dart b/lib/model/source/tag.dart index f7b597a18..e1ebd33e6 100644 --- a/lib/model/source/tag.dart +++ b/lib/model/source/tag.dart @@ -3,8 +3,9 @@ import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/metadata/catalog.dart'; import 'package:aves/model/source/analysis_controller.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/services/common/services.dart'; +import 'package:aves/utils/collection_utils.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; @@ -75,7 +76,7 @@ mixin TagMixin on SourceBase { // filter summary // by tag - final Map _filterEntryCountMap = {}; + final Map _filterEntryCountMap = {}, _filterSizeMap = {}; final Map _filterRecentEntryMap = {}; void invalidateTagFilterSummary({ @@ -83,10 +84,11 @@ mixin TagMixin on SourceBase { Set? tags, bool notify = true, }) { - if (_filterEntryCountMap.isEmpty && _filterRecentEntryMap.isEmpty) return; + if (_filterEntryCountMap.isEmpty && _filterSizeMap.isEmpty && _filterRecentEntryMap.isEmpty) return; if (entries == null && tags == null) { _filterEntryCountMap.clear(); + _filterSizeMap.clear(); _filterRecentEntryMap.clear(); } else { tags ??= {}; @@ -95,6 +97,7 @@ mixin TagMixin on SourceBase { } tags.forEach((tag) { _filterEntryCountMap.remove(tag); + _filterSizeMap.remove(tag); _filterRecentEntryMap.remove(tag); }); } @@ -107,6 +110,10 @@ mixin TagMixin on SourceBase { return _filterEntryCountMap.putIfAbsent(filter.tag, () => visibleEntries.where(filter.test).length); } + int tagSize(TagFilter filter) { + return _filterSizeMap.putIfAbsent(filter.tag, () => visibleEntries.where(filter.test).map((v) => v.sizeBytes).sum); + } + AvesEntry? tagRecentEntry(TagFilter filter) { return _filterRecentEntryMap.putIfAbsent(filter.tag, () => sortedEntriesByDate.firstWhereOrNull(filter.test)); } diff --git a/lib/model/video/metadata.dart b/lib/model/video/metadata.dart index 6d388c351..451361e18 100644 --- a/lib/model/video/metadata.dart +++ b/lib/model/video/metadata.dart @@ -22,8 +22,7 @@ import 'package:fijkplayer/fijkplayer.dart'; import 'package:flutter/foundation.dart'; class VideoMetadataFormatter { - static final _dateY4M2D2H2m2s2Pattern = RegExp(r'(\d{4})[-/](\d{2})[-/](\d{2}) (\d{2}):(\d{2}):(\d{2})'); - static final _dateY4M2D2H2m2s2APmPattern = RegExp(r'(\d{4})[-/](\d{1,2})[-/](\d{1,2})T(\d+):(\d+):(\d+)( ([ap]\.? ?m\.?))?Z'); + static final _dateY4M2D2H2m2s2Pattern = RegExp(r'(\d{4})[-./](\d{1,2})[-./](\d{1,2})([ T](\d{1,2}):(\d{1,2}):(\d{1,2})( ([ap]\.? ?m\.?))?)?'); static final _ambiguousDatePatterns = { RegExp(r'^\d{2}[-/]\d{2}[-/]\d{4}$'), }; @@ -126,6 +125,7 @@ class VideoMetadataFormatter { // - `2021-09-10T7:14:49 pmZ` // - `2022-01-28T5:07:46 p. m.Z` // - `2012-1-1T12:00:00Z` + // - `2020.10.14` // - `2021` (not enough to build a date) var match = _dateY4M2D2H2m2s2Pattern.firstMatch(dateString); @@ -133,27 +133,16 @@ class VideoMetadataFormatter { final year = int.tryParse(match.group(1)!); final month = int.tryParse(match.group(2)!); final day = int.tryParse(match.group(3)!); - final hour = int.tryParse(match.group(4)!); - final minute = int.tryParse(match.group(5)!); - final second = int.tryParse(match.group(6)!); - if (year != null && month != null && day != null && hour != null && minute != null && second != null) { - final date = DateTime(year, month, day, hour, minute, second, 0); - return date.millisecondsSinceEpoch; - } - } + if (year != null && month != null && day != null) { + var hour = 0, minute = 0, second = 0, pm = false; + if (match.groupCount >= 7 && match.group(4) != null) { + hour = int.tryParse(match.group(5)!) ?? 0; + minute = int.tryParse(match.group(6)!) ?? 0; + second = int.tryParse(match.group(7)!) ?? 0; + pm = match.group(9) == 'pm'; + } - match = _dateY4M2D2H2m2s2APmPattern.firstMatch(dateString); - if (match != null) { - final year = int.tryParse(match.group(1)!); - final month = int.tryParse(match.group(2)!); - final day = int.tryParse(match.group(3)!); - final hour = int.tryParse(match.group(4)!); - final minute = int.tryParse(match.group(5)!); - final second = int.tryParse(match.group(6)!); - final pm = match.group(8) == 'pm'; - - if (year != null && month != null && day != null && hour != null && minute != null && second != null) { final date = DateTime(year, month, day, hour + (pm ? 12 : 0), minute, second, 0); return date.millisecondsSinceEpoch; } diff --git a/lib/ref/iptc.dart b/lib/ref/iptc.dart index 74efd75e7..5ef584d94 100644 --- a/lib/ref/iptc.dart +++ b/lib/ref/iptc.dart @@ -2,6 +2,7 @@ class IPTC { static const int applicationRecord = 2; // ApplicationRecord tags + static const int objectName = 5; static const int keywordsTag = 25; static const int captionAbstractTag = 120; } diff --git a/lib/services/analysis_service.dart b/lib/services/analysis_service.dart index 1fb764e07..3118e2b44 100644 --- a/lib/services/analysis_service.dart +++ b/lib/services/analysis_service.dart @@ -4,7 +4,7 @@ import 'dart:ui'; import 'package:aves/l10n/l10n.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/analysis_controller.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/model/source/media_store_source.dart'; import 'package:aves/model/source/source_state.dart'; import 'package:aves/services/common/services.dart'; diff --git a/lib/services/media/byte_receiving_codec.dart b/lib/services/media/byte_receiving_codec.dart new file mode 100644 index 000000000..ea8f82eb2 --- /dev/null +++ b/lib/services/media/byte_receiving_codec.dart @@ -0,0 +1,27 @@ +import 'package:flutter/services.dart'; + +class AvesByteReceivingMethodCodec extends StandardMethodCodec { + const AvesByteReceivingMethodCodec() : super(); + + @override + dynamic decodeEnvelope(ByteData envelope) { + // First byte is zero in success case, and non-zero otherwise. + if (envelope.lengthInBytes == 0) { + throw const FormatException('Expected envelope, got nothing'); + } + final ReadBuffer buffer = ReadBuffer(envelope); + if (buffer.getUint8() == 0) { + return envelope.buffer.asUint8List(envelope.offsetInBytes + 1, envelope.lengthInBytes - 1); + } + + final Object? errorCode = messageCodec.readValue(buffer); + final Object? errorMessage = messageCodec.readValue(buffer); + final Object? errorDetails = messageCodec.readValue(buffer); + final String? errorStacktrace = (buffer.hasRemaining) ? messageCodec.readValue(buffer) as String? : null; + if (errorCode is String && (errorMessage == null || errorMessage is String) && !buffer.hasRemaining) { + throw PlatformException(code: errorCode, message: errorMessage as String?, details: errorDetails, stacktrace: errorStacktrace); + } else { + throw const FormatException('Invalid envelope'); + } + } +} diff --git a/lib/services/media/media_fetch_service.dart b/lib/services/media/media_fetch_service.dart index b5a3f6862..ad07eb85a 100644 --- a/lib/services/media/media_fetch_service.dart +++ b/lib/services/media/media_fetch_service.dart @@ -6,6 +6,7 @@ import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/common/output_buffer.dart'; import 'package:aves/services/common/service_policy.dart'; import 'package:aves/services/common/services.dart'; +import 'package:aves/services/media/byte_receiving_codec.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:streams_channel/streams_channel.dart'; @@ -26,7 +27,7 @@ abstract class MediaFetchService { int? rotationDegrees, bool isFlipped, { int? pageId, - int? expectedContentLength, + int? sizeBytes, BytesReceivedCallback? onBytesReceived, }); @@ -66,14 +67,15 @@ abstract class MediaFetchService { } class PlatformMediaFetchService implements MediaFetchService { - static const _platform = MethodChannel('deckers.thibault/aves/media_fetch'); + static const _platformObject = MethodChannel('deckers.thibault/aves/media_fetch_object'); + static const _platformBytes = MethodChannel('deckers.thibault/aves/media_fetch_bytes', AvesByteReceivingMethodCodec()); static final _byteStream = StreamsChannel('deckers.thibault/aves/media_byte_stream'); static const double _thumbnailDefaultSize = 64.0; @override Future getEntry(String uri, String? mimeType) async { try { - final result = await _platform.invokeMethod('getEntry', { + final result = await _platformObject.invokeMethod('getEntry', { 'uri': uri, 'mimeType': mimeType, }) as Map; @@ -100,7 +102,7 @@ class PlatformMediaFetchService implements MediaFetchService { mimeType, 0, false, - expectedContentLength: expectedContentLength, + sizeBytes: expectedContentLength, onBytesReceived: onBytesReceived, ); @@ -111,7 +113,7 @@ class PlatformMediaFetchService implements MediaFetchService { int? rotationDegrees, bool isFlipped, { int? pageId, - int? expectedContentLength, + int? sizeBytes, BytesReceivedCallback? onBytesReceived, }) async { try { @@ -121,6 +123,7 @@ class PlatformMediaFetchService implements MediaFetchService { _byteStream.receiveBroadcastStream({ 'uri': uri, 'mimeType': mimeType, + 'sizeBytes': sizeBytes, 'rotationDegrees': rotationDegrees ?? 0, 'isFlipped': isFlipped, 'pageId': pageId, @@ -131,7 +134,7 @@ class PlatformMediaFetchService implements MediaFetchService { if (onBytesReceived != null) { bytesReceived += chunk.length; try { - onBytesReceived(bytesReceived, expectedContentLength); + onBytesReceived(bytesReceived, sizeBytes); } catch (error, stack) { completer.completeError(error, stack); return; @@ -171,7 +174,7 @@ class PlatformMediaFetchService implements MediaFetchService { return servicePolicy.call( () async { try { - final result = await _platform.invokeMethod('getRegion', { + final result = await _platformBytes.invokeMethod('getRegion', { 'uri': uri, 'mimeType': mimeType, 'pageId': pageId, @@ -211,7 +214,7 @@ class PlatformMediaFetchService implements MediaFetchService { return servicePolicy.call( () async { try { - final result = await _platform.invokeMethod('getThumbnail', { + final result = await _platformBytes.invokeMethod('getThumbnail', { 'uri': uri, 'mimeType': mimeType, 'dateModifiedSecs': dateModifiedSecs, @@ -238,7 +241,7 @@ class PlatformMediaFetchService implements MediaFetchService { @override Future clearSizedThumbnailDiskCache() async { try { - return _platform.invokeMethod('clearSizedThumbnailDiskCache'); + return _platformObject.invokeMethod('clearSizedThumbnailDiskCache'); } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } diff --git a/lib/theme/durations.dart b/lib/theme/durations.dart index cf0562ea5..50e81aab5 100644 --- a/lib/theme/durations.dart +++ b/lib/theme/durations.dart @@ -95,6 +95,7 @@ class DurationsData { // common animations final Duration expansionTileAnimation; final Duration formTransition; + final Duration chartTransition; final Duration iconAnimation; final Duration staggeredAnimation; final Duration staggeredAnimationPageTarget; @@ -110,6 +111,7 @@ class DurationsData { const DurationsData({ this.expansionTileAnimation = const Duration(milliseconds: 200), this.formTransition = const Duration(milliseconds: 200), + this.chartTransition = const Duration(milliseconds: 400), this.iconAnimation = const Duration(milliseconds: 300), this.staggeredAnimation = const Duration(milliseconds: 375), this.staggeredAnimationPageTarget = const Duration(milliseconds: 800), @@ -123,6 +125,7 @@ class DurationsData { // as of Flutter v2.5.1, `ExpansionPanelList` throws if animation duration is zero expansionTileAnimation: const Duration(microseconds: 1), formTransition: Duration.zero, + chartTransition: Duration.zero, iconAnimation: Duration.zero, staggeredAnimation: Duration.zero, staggeredAnimationPageTarget: Duration.zero, diff --git a/lib/theme/icons.dart b/lib/theme/icons.dart index 922ef21ef..6dcdc8a03 100644 --- a/lib/theme/icons.dart +++ b/lib/theme/icons.dart @@ -16,7 +16,10 @@ class AIcons { static const IconData checked = Icons.done_outlined; static const IconData counter = Icons.plus_one_outlined; static const IconData date = Icons.calendar_today_outlined; + static const IconData dateRecent = Icons.today_outlined; + static const IconData dateUndated = Icons.event_busy_outlined; static const IconData description = Icons.description_outlined; + static const IconData descriptionUntitled = Icons.comments_disabled_outlined; static const IconData disc = Icons.fiber_manual_record; static const IconData display = Icons.light_mode_outlined; static const IconData error = Icons.error_outline; @@ -35,7 +38,6 @@ class AIcons { static const IconData ratingRejected = MdiIcons.starMinusOutline; static const IconData ratingUnrated = MdiIcons.starOffOutline; static const IconData raw = Icons.raw_on_outlined; - static const IconData recent = Icons.today_outlined; static const IconData shooting = Icons.camera_outlined; static const IconData removableStorage = Icons.sd_storage_outlined; static const IconData sensorControlEnabled = Icons.explore_outlined; @@ -49,6 +51,7 @@ class AIcons { static const IconData group = Icons.group_work_outlined; static const IconData layout = Icons.grid_view_outlined; static const IconData sort = Icons.sort_outlined; + static const IconData sortOrder = Icons.swap_vert_outlined; // actions static const IconData add = Icons.add_circle_outline; @@ -134,7 +137,8 @@ class AIcons { static const IconData geo = Icons.language_outlined; static const IconData motionPhoto = Icons.motion_photos_on_outlined; static const IconData multiPage = Icons.burst_mode_outlined; - static const IconData threeSixty = Icons.threesixty_outlined; + static const IconData panorama = Icons.vrpano_outlined; + static const IconData sphericalVideo = Icons.threesixty_outlined; static const IconData videoThumb = Icons.play_circle_outline; static const IconData selected = Icons.check_circle_outline; static const IconData unselected = Icons.radio_button_unchecked; diff --git a/lib/utils/collection_utils.dart b/lib/utils/collection_utils.dart index 74b87fac5..4983a8d62 100644 --- a/lib/utils/collection_utils.dart +++ b/lib/utils/collection_utils.dart @@ -13,3 +13,7 @@ extension ExtraMapNullableKeyValue on Map { Map whereNotNullValue() => {for (var kv in entries.where((kv) => kv.value != null)) kv.key: kv.value as V}; } + +extension ExtraNumIterable on Iterable { + int get sum => fold(0, (prev, v) => prev + (v ?? 0)); +} diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart index 64a0a629b..89aa6b2f4 100644 --- a/lib/utils/constants.dart +++ b/lib/utils/constants.dart @@ -11,12 +11,19 @@ class Constants { static const double colorPickerRadius = 16; - static const titleTextStyle = TextStyle( + static const knownTitleTextStyle = TextStyle( fontSize: 20, fontWeight: FontWeight.w300, fontFeatures: [FontFeature.enable('smcp')], ); + static TextStyle unknownTitleTextStyle = knownTitleTextStyle; + + static void updateStylesForLocale(Locale locale) { + final smcp = locale.languageCode != 'el'; + unknownTitleTextStyle = smcp ? knownTitleTextStyle : knownTitleTextStyle.copyWith(fontFeatures: []); + } + static const embossShadows = [ Shadow( color: Colors.black, diff --git a/lib/utils/time_utils.dart b/lib/utils/time_utils.dart index c6d4dd563..8cd65eb23 100644 --- a/lib/utils/time_utils.dart +++ b/lib/utils/time_utils.dart @@ -48,7 +48,8 @@ DateTime? dateTimeFromMillis(int? millis, {bool isUtc = false}) { final _unixStampMillisPattern = RegExp(r'\d{13}'); final _unixStampSecPattern = RegExp(r'\d{10}'); final _dateYMD8Hms6Sub3Pattern = RegExp(r'(\d{8})([_-\s](\d{6})([_-\s](\d{3}))?)?'); -final _dateY4M2D2H2m2s2Sub3Pattern = RegExp(r'(\d{4})-(\d{1,2})-(\d{1,2})-(\d{1,2})-(\d{1,2})-(\d{1,2})-(\d{1,3})'); +final _dateY4M2D2H2m2s2Sub3Pattern = RegExp(r'(\d{4})-(\d{1,2})-(\d{1,2})[ -](\d{1,2})[.-](\d{1,2})[.-](\d{1,2})([.-](\d{1,3})?)?'); +final _dateY4M2D2Hms6Pattern = RegExp(r'(\d{4})-(\d{1,2})-(\d{1,2}) (\d{6})'); DateTime? parseUnknownDateFormat(String? s) { if (s == null) return null; @@ -110,12 +111,31 @@ DateTime? parseUnknownDateFormat(String? s) { final hour = int.tryParse(match.group(4)!); final minute = int.tryParse(match.group(5)!); final second = int.tryParse(match.group(6)!); - final millis = int.tryParse(match.group(7)!); + final millis = match.groupCount >= 8 ? int.tryParse(match.group(8) ?? '0') : 0; if (year != null && month != null && day != null && hour != null && minute != null && second != null && millis != null) { return DateTime(year, month, day, hour, minute, second, millis); } } + match = _dateY4M2D2Hms6Pattern.firstMatch(s); + if (match != null) { + final year = int.tryParse(match.group(1)!); + final month = int.tryParse(match.group(2)!); + final day = int.tryParse(match.group(3)!); + final timeString = match.group(4); + + var hour = 0, minute = 0, second = 0; + if (timeString != null) { + hour = int.tryParse(timeString.substring(0, 2)) ?? 0; + minute = int.tryParse(timeString.substring(2, 4)) ?? 0; + second = int.tryParse(timeString.substring(4, 6)) ?? 0; + } + + if (year != null && month != null && day != null) { + return DateTime(year, month, day, hour, minute, second); + } + } + return null; } diff --git a/lib/utils/xmp_utils.dart b/lib/utils/xmp_utils.dart index eb5853533..49860cda7 100644 --- a/lib/utils/xmp_utils.dart +++ b/lib/utils/xmp_utils.dart @@ -153,6 +153,7 @@ class XMP { static const containerDirectory = 'Directory'; static const dcDescription = 'description'; static const dcSubject = 'subject'; + static const dcTitle = 'title'; static const msPhotoRating = 'Rating'; static const xmpRating = 'Rating'; diff --git a/lib/widget_common.dart b/lib/widget_common.dart index 8a267587d..50c569684 100644 --- a/lib/widget_common.dart +++ b/lib/widget_common.dart @@ -6,6 +6,7 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/media_store_source.dart'; import 'package:aves/services/common/services.dart'; +import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/home_widget.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -59,6 +60,8 @@ Future _getWidgetEntry(int widgetId, bool reuseEntry) async { if (entry != null) return entry; } + await androidFileUtils.init(); + final filters = settings.getWidgetCollectionFilters(widgetId); final source = MediaStoreSource(); final readyCompleter = Completer(); diff --git a/lib/widgets/about/bug_report.dart b/lib/widgets/about/bug_report.dart index ed44b7809..dadbfabc3 100644 --- a/lib/widgets/about/bug_report.dart +++ b/lib/widgets/about/bug_report.dart @@ -59,7 +59,7 @@ class _BugReportState extends State with FeedbackMixin { child: Container( padding: const EdgeInsets.symmetric(horizontal: 16), alignment: AlignmentDirectional.centerStart, - child: Text(l10n.aboutBug, style: Constants.titleTextStyle), + child: Text(l10n.aboutBugSectionTitle, style: Constants.knownTitleTextStyle), ), ), body: Padding( diff --git a/lib/widgets/about/credits.dart b/lib/widgets/about/credits.dart index 38b098b1c..52fb62b5c 100644 --- a/lib/widgets/about/credits.dart +++ b/lib/widgets/about/credits.dart @@ -15,7 +15,8 @@ class AboutCredits extends StatelessWidget { 'Nederlands': 'Martijn Fabrie, Koen Koppens', 'Português (Brasil)': 'Jonatas De Almeida Barros', 'Türkçe': 'metezd', - 'Русский': 'D3ZOXY', + 'Ελληνικά': 'Emmanouil Papavergis', + 'Русский': 'D3ZOXY, kha84', '日本語': 'Maki', '简体中文': '小默, Aerowolf', }; @@ -32,7 +33,7 @@ class AboutCredits extends StatelessWidget { constraints: const BoxConstraints(minHeight: 48), child: Align( alignment: AlignmentDirectional.centerStart, - child: Text(l10n.aboutCredits, style: Constants.titleTextStyle), + child: Text(l10n.aboutCreditsSectionTitle, style: Constants.knownTitleTextStyle), ), ), Text.rich( @@ -55,7 +56,7 @@ class AboutCredits extends StatelessWidget { constraints: const BoxConstraints(minHeight: 48), child: Align( alignment: AlignmentDirectional.centerStart, - child: Text(l10n.aboutCreditsTranslators, style: Constants.titleTextStyle), + child: Text(l10n.aboutTranslatorsSectionTitle, style: Constants.knownTitleTextStyle), ), ), const InfoRowGroup(info: translators), diff --git a/lib/widgets/about/licenses.dart b/lib/widgets/about/licenses.dart index e6193e7c1..752aecce5 100644 --- a/lib/widgets/about/licenses.dart +++ b/lib/widgets/about/licenses.dart @@ -50,25 +50,25 @@ class _LicensesState extends State { _buildHeader(), const SizedBox(height: 16), AvesExpansionTile( - title: context.l10n.aboutLicensesAndroidLibraries, + title: context.l10n.aboutLicensesAndroidLibrariesSectionTitle, highlightColor: colors.fromBrandColor(BrandColors.android), expandedNotifier: _expandedNotifier, children: _platform.map((package) => LicenseRow(package: package)).toList(), ), AvesExpansionTile( - title: context.l10n.aboutLicensesFlutterPlugins, + title: context.l10n.aboutLicensesFlutterPluginsSectionTitle, highlightColor: colors.fromBrandColor(BrandColors.flutter), expandedNotifier: _expandedNotifier, children: _flutterPlugins.map((package) => LicenseRow(package: package)).toList(), ), AvesExpansionTile( - title: context.l10n.aboutLicensesFlutterPackages, + title: context.l10n.aboutLicensesFlutterPackagesSectionTitle, highlightColor: colors.fromBrandColor(BrandColors.flutter), expandedNotifier: _expandedNotifier, children: _flutterPackages.map((package) => LicenseRow(package: package)).toList(), ), AvesExpansionTile( - title: context.l10n.aboutLicensesDartPackages, + title: context.l10n.aboutLicensesDartPackagesSectionTitle, highlightColor: colors.fromBrandColor(BrandColors.flutter), expandedNotifier: _expandedNotifier, children: _dartPackages.map((package) => LicenseRow(package: package)).toList(), @@ -106,7 +106,7 @@ class _LicensesState extends State { constraints: const BoxConstraints(minHeight: 48), child: Align( alignment: AlignmentDirectional.centerStart, - child: Text(context.l10n.aboutLicenses, style: Constants.titleTextStyle), + child: Text(context.l10n.aboutLicensesSectionTitle, style: Constants.knownTitleTextStyle), ), ), const SizedBox(height: 8), diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart index b9cf72735..86510c002 100644 --- a/lib/widgets/aves_app.dart +++ b/lib/widgets/aves_app.dart @@ -24,6 +24,7 @@ import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/theme/themes.dart'; import 'package:aves/utils/android_file_utils.dart'; +import 'package:aves/utils/constants.dart'; import 'package:aves/utils/debouncer.dart'; import 'package:aves/widgets/collection/collection_grid.dart'; import 'package:aves/widgets/collection/collection_page.dart'; @@ -103,11 +104,12 @@ class AvesApp extends StatefulWidget { class _AvesAppState extends State with WidgetsBindingObserver { final ValueNotifier appModeNotifier = ValueNotifier(AppMode.main); - late Future _appSetup; - late Future _dynamicColorPaletteLoader; - final _mediaStoreSource = MediaStoreSource(); + late final Future _appSetup; + late final Size _screenSize; + late final Future _dynamicColorPaletteLoader; + final CollectionSource _mediaStoreSource = MediaStoreSource(); final Debouncer _mediaStoreChangeDebouncer = Debouncer(delay: Durations.mediaContentChangeDebounceDelay); - final Set changedUris = {}; + final Set _changedUris = {}; // observers are not registered when using the same list object with different items // the list itself needs to be reassigned @@ -124,6 +126,8 @@ class _AvesAppState extends State with WidgetsBindingObserver { super.initState(); EquatableConfig.stringify = true; _appSetup = _setup(); + // remember screen size to use it later, when `context` and `window` are no longer reliable + _screenSize = window.physicalSize / window.devicePixelRatio; _dynamicColorPaletteLoader = DynamicColorPlugin.getCorePalette(); _mediaStoreChangeChannel.receiveBroadcastStream().listen((event) => _onMediaStoreChange(event as String?)); _newIntentChannel.receiveBroadcastStream().listen((event) => _onNewIntent(event as Map?)); @@ -172,6 +176,8 @@ class _AvesAppState extends State with WidgetsBindingObserver { final themeBrightness = s.item3; final enableDynamicColor = s.item4; + Constants.updateStylesForLocale(settings.appliedLocale); + final pageTransitionsTheme = areAnimationsEnabled // Flutter has various page transition implementations for Android: // - `FadeUpwardsPageTransitionsBuilder` on Oreo / API 27 and below @@ -290,20 +296,13 @@ class _AvesAppState extends State with WidgetsBindingObserver { if (!settings.initialized) return; final stopwatch = Stopwatch()..start(); - final Size screenSize; - try { - screenSize = window.physicalSize / window.devicePixelRatio; - } catch (error) { - // view may no longer be usable - return; - } var tileExtent = settings.getTileExtent(CollectionPage.routeName); if (tileExtent == 0) { - tileExtent = screenSize.shortestSide / CollectionGrid.columnCountDefault; + tileExtent = _screenSize.shortestSide / CollectionGrid.columnCountDefault; } - final rows = (screenSize.height / tileExtent).ceil(); - final columns = (screenSize.width / tileExtent).ceil(); + final rows = (_screenSize.height / tileExtent).ceil(); + final columns = (_screenSize.width / tileExtent).ceil(); final count = rows * columns; final collection = CollectionLens(source: _mediaStoreSource, listenToSource: false); settings.topEntryIds = collection.sortedEntries.take(count).map((entry) => entry.id).toList(); @@ -402,14 +401,14 @@ class _AvesAppState extends State with WidgetsBindingObserver { } void _onMediaStoreChange(String? uri) { - if (uri != null) changedUris.add(uri); - if (changedUris.isNotEmpty) { + if (uri != null) _changedUris.add(uri); + if (_changedUris.isNotEmpty) { _mediaStoreChangeDebouncer(() async { - final todo = changedUris.toSet(); - changedUris.clear(); + final todo = _changedUris.toSet(); + _changedUris.clear(); final tempUris = await _mediaStoreSource.refreshUris(todo); if (tempUris.isNotEmpty) { - changedUris.addAll(tempUris); + _changedUris.addAll(tempUris); _onMediaStoreChange(null); } }); diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index 2bb0ffe44..4a2d6ccdf 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -12,7 +12,8 @@ import 'package:aves/model/selection.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; +import 'package:aves/model/source/enums/view.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/collection/collection_page.dart'; @@ -67,6 +68,25 @@ class _CollectionAppBarState extends State with SingleTickerPr bool get showFilterBar => visibleFilters.isNotEmpty; + static const _sortOptions = [ + EntrySortFactor.date, + EntrySortFactor.size, + EntrySortFactor.name, + EntrySortFactor.rating, + ]; + + static const _groupOptions = [ + EntryGroupFactor.album, + EntryGroupFactor.month, + EntryGroupFactor.day, + EntryGroupFactor.none, + ]; + + static const _layoutOptions = [ + TileLayout.grid, + TileLayout.list, + ]; + @override void initState() { super.initState(); @@ -496,7 +516,7 @@ class _CollectionAppBarState extends State with SingleTickerPr case EntrySetAction.flip: case EntrySetAction.editDate: case EntrySetAction.editLocation: - case EntrySetAction.editDescription: + case EntrySetAction.editTitleDescription: case EntrySetAction.editRating: case EntrySetAction.editTags: case EntrySetAction.removeMetadata: @@ -506,33 +526,22 @@ class _CollectionAppBarState extends State with SingleTickerPr } Future _configureView() async { - final initialValue = Tuple3( + final initialValue = Tuple4( settings.collectionSortFactor, settings.collectionSectionFactor, settings.getTileLayout(CollectionPage.routeName), + settings.collectionSortReverse, ); - final value = await showDialog>( + final value = await showDialog>( context: context, builder: (context) { - final l10n = context.l10n; return TileViewDialog( initialValue: initialValue, - sortOptions: { - EntrySortFactor.date: l10n.collectionSortDate, - EntrySortFactor.size: l10n.collectionSortSize, - EntrySortFactor.name: l10n.collectionSortName, - EntrySortFactor.rating: l10n.collectionSortRating, - }, - groupOptions: { - EntryGroupFactor.album: l10n.collectionGroupAlbum, - EntryGroupFactor.month: l10n.collectionGroupMonth, - EntryGroupFactor.day: l10n.collectionGroupDay, - EntryGroupFactor.none: l10n.collectionGroupNone, - }, - layoutOptions: { - TileLayout.grid: l10n.tileLayoutGrid, - TileLayout.list: l10n.tileLayoutList, - }, + sortOptions: Map.fromEntries(_sortOptions.map((v) => MapEntry(v, v.getName(context)))), + groupOptions: Map.fromEntries(_groupOptions.map((v) => MapEntry(v, v.getName(context)))), + layoutOptions: Map.fromEntries(_layoutOptions.map((v) => MapEntry(v, v.getName(context)))), + sortOrder: (factor, reverse) => factor.getOrderName(context, reverse), + canGroup: (s, g, l) => s == EntrySortFactor.date, ); }, ); @@ -542,6 +551,7 @@ class _CollectionAppBarState extends State with SingleTickerPr settings.collectionSortFactor = value.item1!; settings.collectionSectionFactor = value.item2!; settings.setTileLayout(CollectionPage.routeName, value.item3!); + settings.collectionSortReverse = value.item4; } } diff --git a/lib/widgets/collection/collection_grid.dart b/lib/widgets/collection/collection_grid.dart index 1e262055e..39b161ac0 100644 --- a/lib/widgets/collection/collection_grid.dart +++ b/lib/widgets/collection/collection_grid.dart @@ -7,7 +7,7 @@ import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/model/source/section_keys.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/theme/durations.dart'; @@ -92,6 +92,7 @@ class _CollectionGridContent extends StatelessWidget { @override Widget build(BuildContext context) { + final selectable = context.select, bool>((v) => v.value.canSelectMedia); final settingsRouteKey = context.read().settingsRouteKey; final tileLayout = context.select((s) => s.getTileLayout(settingsRouteKey)); return Consumer( @@ -124,6 +125,7 @@ class _CollectionGridContent extends StatelessWidget { } return SectionedEntryListLayoutProvider( collection: collection, + selectable: selectable, scrollableWidth: scrollableWidth, tileLayout: tileLayout, columnCount: columnCount, @@ -160,6 +162,7 @@ class _CollectionGridContent extends StatelessWidget { isScrollingNotifier: _isScrollingNotifier, scrollController: PrimaryScrollController.of(context)!, tileLayout: tileLayout, + selectable: selectable, ), ); return sectionedListLayoutProvider; @@ -173,12 +176,14 @@ class _CollectionSectionedContent extends StatefulWidget { final ValueNotifier isScrollingNotifier; final ScrollController scrollController; final TileLayout tileLayout; + final bool selectable; const _CollectionSectionedContent({ required this.collection, required this.isScrollingNotifier, required this.scrollController, required this.tileLayout, + required this.selectable, }); @override @@ -220,7 +225,7 @@ class _CollectionSectionedContentState extends State<_CollectionSectionedContent final selector = GridSelectionGestureDetector( scrollableKey: _scrollableKey, - selectable: context.select, bool>((v) => v.value.canSelectMedia), + selectable: widget.selectable, items: collection.sortedEntries, scrollController: scrollController, appBarHeightNotifier: _appBarHeightNotifier, @@ -542,7 +547,7 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge final oldest = lastKey.date; if (newest != null && oldest != null) { final localeName = context.l10n.localeName; - final dateFormat = newest.difference(oldest).inDays > 365 ? DateFormat.y(localeName) : DateFormat.MMM(localeName); + final dateFormat = (newest.difference(oldest).inDays).abs() > 365 ? DateFormat.y(localeName) : DateFormat.MMM(localeName); String? lastLabel; sectionLayouts.forEach((section) { final date = (section.sectionKey as EntryDateSectionKey).date; diff --git a/lib/widgets/collection/collection_page.dart b/lib/widgets/collection/collection_page.dart index 210acd11f..31f02a9d2 100644 --- a/lib/widgets/collection/collection_page.dart +++ b/lib/widgets/collection/collection_page.dart @@ -153,7 +153,7 @@ class _CollectionPageState extends State { case AppMode.pickMultipleMediaExternal: return hasSelection ? AvesFab( - tooltip: context.l10n.collectionPickPageTitle, + tooltip: context.l10n.pickTooltip, onPressed: () { final items = context.read>().selectedItems; final uris = items.map((entry) => entry.uri).toList(); @@ -163,7 +163,7 @@ class _CollectionPageState extends State { : null; case AppMode.pickCollectionFiltersExternal: return AvesFab( - tooltip: context.l10n.collectionPickPageTitle, + tooltip: context.l10n.pickTooltip, onPressed: () { final filters = _collection.filters; IntentService.submitPickedCollectionFilters(filters); diff --git a/lib/widgets/collection/draggable_thumb_label.dart b/lib/widgets/collection/draggable_thumb_label.dart index f759854b9..145a00607 100644 --- a/lib/widgets/collection/draggable_thumb_label.dart +++ b/lib/widgets/collection/draggable_thumb_label.dart @@ -2,7 +2,7 @@ import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/utils/file_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/grid/draggable_thumb_label.dart'; diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index f8a9bb9cd..c21691f17 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -92,7 +92,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware case EntrySetAction.flip: case EntrySetAction.editDate: case EntrySetAction.editLocation: - case EntrySetAction.editDescription: + case EntrySetAction.editTitleDescription: case EntrySetAction.editRating: case EntrySetAction.editTags: case EntrySetAction.removeMetadata: @@ -144,7 +144,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware case EntrySetAction.flip: case EntrySetAction.editDate: case EntrySetAction.editLocation: - case EntrySetAction.editDescription: + case EntrySetAction.editTitleDescription: case EntrySetAction.editRating: case EntrySetAction.editTags: case EntrySetAction.removeMetadata: @@ -221,8 +221,8 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware case EntrySetAction.editLocation: _editLocation(context); break; - case EntrySetAction.editDescription: - _editDescription(context); + case EntrySetAction.editTitleDescription: + _editTitleDescription(context); break; case EntrySetAction.editRating: _editRating(context); @@ -275,7 +275,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware final l10n = context.l10n; final source = context.read(); - final selectionDirs = entries.map((e) => e.directory).whereNotNull().toSet(); + final storageDirs = entries.map((e) => e.storageDirectory).whereNotNull().toSet(); final todoCount = entries.length; if (!await showConfirmationDialog( @@ -285,7 +285,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware confirmationButtonLabel: l10n.deleteButtonLabel, )) return; - if (!pureTrash && !await checkStoragePermissionForAlbums(context, selectionDirs, entries: entries)) return; + if (!await checkStoragePermissionForAlbums(context, storageDirs, entries: entries)) return; source.pauseMonitoring(); final opId = mediaEditService.newOpId; @@ -308,7 +308,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware } // cleanup - await storageService.deleteEmptyDirectories(selectionDirs); + await storageService.deleteEmptyDirectories(storageDirs); }, ); @@ -434,7 +434,6 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware builder: (context) { final l10n = context.l10n; return AvesDialog( - title: l10n.unsupportedTypeDialogTitle, content: Text(l10n.unsupportedTypeDialogMessage(unsupportedTypes.length, unsupportedTypes.join(', '))), actions: [ TextButton( @@ -495,14 +494,14 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware await _edit(context, entries, (entry) => entry.editLocation(location)); } - Future _editDescription(BuildContext context) async { - final entries = await _getEditableTargetItems(context, canEdit: (entry) => entry.canEditDescription); + Future _editTitleDescription(BuildContext context) async { + final entries = await _getEditableTargetItems(context, canEdit: (entry) => entry.canEditTitleDescription); if (entries == null || entries.isEmpty) return; - final description = await selectDescription(context, entries); - if (description == null) return; + final modifier = await selectTitleDescriptionModifier(context, entries); + if (modifier == null) return; - await _edit(context, entries, (entry) => entry.editDescription(description)); + await _edit(context, entries, (entry) => entry.editTitleDescription(modifier)); } Future _editRating(BuildContext context) async { diff --git a/lib/widgets/collection/grid/headers/album.dart b/lib/widgets/collection/grid/headers/album.dart index 77bd42aef..85fb41b32 100644 --- a/lib/widgets/collection/grid/headers/album.dart +++ b/lib/widgets/collection/grid/headers/album.dart @@ -11,11 +11,13 @@ import 'package:flutter/material.dart'; class AlbumSectionHeader extends StatelessWidget { final String? directory, albumName; + final bool selectable; const AlbumSectionHeader({ super.key, required this.directory, required this.albumName, + required this.selectable, }); @override @@ -41,6 +43,7 @@ class AlbumSectionHeader extends StatelessWidget { color: Color(0xFF757575), ) : null, + selectable: selectable, ); } diff --git a/lib/widgets/collection/grid/headers/any.dart b/lib/widgets/collection/grid/headers/any.dart index d88929c68..35958d604 100644 --- a/lib/widgets/collection/grid/headers/any.dart +++ b/lib/widgets/collection/grid/headers/any.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'package:aves/model/entry.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/model/source/section_keys.dart'; import 'package:aves/widgets/collection/grid/headers/album.dart'; import 'package:aves/widgets/collection/grid/headers/date.dart'; @@ -15,12 +15,14 @@ class CollectionSectionHeader extends StatelessWidget { final CollectionLens collection; final SectionKey sectionKey; final double height; + final bool selectable; const CollectionSectionHeader({ super.key, required this.collection, required this.sectionKey, required this.height, + required this.selectable, }); @override @@ -41,9 +43,17 @@ class CollectionSectionHeader extends StatelessWidget { case EntryGroupFactor.album: return _buildAlbumHeader(context); case EntryGroupFactor.month: - return MonthSectionHeader(key: ValueKey(sectionKey), date: (sectionKey as EntryDateSectionKey).date); + return MonthSectionHeader( + key: ValueKey(sectionKey), + date: (sectionKey as EntryDateSectionKey).date, + selectable: selectable, + ); case EntryGroupFactor.day: - return DaySectionHeader(key: ValueKey(sectionKey), date: (sectionKey as EntryDateSectionKey).date); + return DaySectionHeader( + key: ValueKey(sectionKey), + date: (sectionKey as EntryDateSectionKey).date, + selectable: selectable, + ); case EntryGroupFactor.none: break; } @@ -51,7 +61,11 @@ class CollectionSectionHeader extends StatelessWidget { case EntrySortFactor.name: return _buildAlbumHeader(context); case EntrySortFactor.rating: - return RatingSectionHeader(key: ValueKey(sectionKey), rating: (sectionKey as EntryRatingSectionKey).rating); + return RatingSectionHeader( + key: ValueKey(sectionKey), + rating: (sectionKey as EntryRatingSectionKey).rating, + selectable: selectable, + ); case EntrySortFactor.size: break; } @@ -65,6 +79,7 @@ class CollectionSectionHeader extends StatelessWidget { key: ValueKey(sectionKey), directory: directory, albumName: directory != null ? source.getAlbumDisplayName(context, directory) : null, + selectable: selectable, ); } diff --git a/lib/widgets/collection/grid/headers/date.dart b/lib/widgets/collection/grid/headers/date.dart index 033492287..a772a0712 100644 --- a/lib/widgets/collection/grid/headers/date.dart +++ b/lib/widgets/collection/grid/headers/date.dart @@ -7,10 +7,12 @@ import 'package:intl/intl.dart'; class DaySectionHeader extends StatelessWidget { final DateTime? date; + final bool selectable; const DaySectionHeader({ super.key, required this.date, + required this.selectable, }); // Examples (en_US): @@ -48,16 +50,19 @@ class DaySectionHeader extends StatelessWidget { return SectionHeader( sectionKey: EntryDateSectionKey(date), title: _formatDate(context, date), + selectable: selectable, ); } } class MonthSectionHeader extends StatelessWidget { final DateTime? date; + final bool selectable; const MonthSectionHeader({ super.key, required this.date, + required this.selectable, }); static String _formatDate(BuildContext context, DateTime? date) { @@ -74,6 +79,7 @@ class MonthSectionHeader extends StatelessWidget { return SectionHeader( sectionKey: EntryDateSectionKey(date), title: _formatDate(context, date), + selectable: selectable, ); } } diff --git a/lib/widgets/collection/grid/headers/rating.dart b/lib/widgets/collection/grid/headers/rating.dart index c6e0198f0..4cdfc3ed8 100644 --- a/lib/widgets/collection/grid/headers/rating.dart +++ b/lib/widgets/collection/grid/headers/rating.dart @@ -5,10 +5,12 @@ import 'package:flutter/material.dart'; class RatingSectionHeader extends StatelessWidget { final int rating; + final bool selectable; const RatingSectionHeader({ super.key, required this.rating, + required this.selectable, }); @override @@ -16,6 +18,7 @@ class RatingSectionHeader extends StatelessWidget { return SectionHeader( sectionKey: EntryRatingSectionKey(rating), title: RatingFilter.formatRating(context, rating), + selectable: selectable, ); } } diff --git a/lib/widgets/collection/grid/section_layout.dart b/lib/widgets/collection/grid/section_layout.dart index bfced886b..6ca41975c 100644 --- a/lib/widgets/collection/grid/section_layout.dart +++ b/lib/widgets/collection/grid/section_layout.dart @@ -7,10 +7,12 @@ import 'package:flutter/material.dart'; class SectionedEntryListLayoutProvider extends SectionedListLayoutProvider { final CollectionLens collection; + final bool selectable; const SectionedEntryListLayoutProvider({ super.key, required this.collection, + required this.selectable, required super.scrollableWidth, required super.tileLayout, required super.columnCount, @@ -42,6 +44,7 @@ class SectionedEntryListLayoutProvider extends SectionedListLayoutProvider selectLocation(BuildContext context, Set entries, CollectionLens? collection) async { if (entries.isEmpty) return null; + final initialLocation = entries.firstWhereOrNull((entry) => entry.hasGps)?.latLng; + return showDialog( context: context, builder: (context) => EditEntryLocationDialog( - entry: entries.first, + initialLocation: initialLocation, collection: collection, ), ); } - Future selectDescription(BuildContext context, Set entries) async { + Future?> selectTitleDescriptionModifier(BuildContext context, Set entries) async { if (entries.isEmpty) return null; - final initialDescription = await metadataFetchService.getDescription(entries.first) ?? ''; + final entry = entries.first; + final initialTitle = entry.catalogMetadata?.xmpTitle ?? ''; + final initialDescription = await metadataFetchService.getDescription(entry) ?? ''; - return showDialog( + return showDialog>( context: context, - builder: (context) => EditEntryDescriptionDialog( + builder: (context) => EditEntryTitleDescriptionDialog( + initialTitle: initialTitle, initialDescription: initialDescription, ), ); diff --git a/lib/widgets/common/action_mixins/feedback.dart b/lib/widgets/common/action_mixins/feedback.dart index 0d5dbbfcb..a0694c98b 100644 --- a/lib/widgets/common/action_mixins/feedback.dart +++ b/lib/widgets/common/action_mixins/feedback.dart @@ -60,6 +60,7 @@ mixin FeedbackMixin { OverlaySupportEntry? notificationOverlayEntry; notificationOverlayEntry = showOverlayNotification( (context) => SafeArea( + bottom: false, child: Padding( padding: margin, child: OverlaySnackBar( diff --git a/lib/widgets/common/action_mixins/permission_aware.dart b/lib/widgets/common/action_mixins/permission_aware.dart index 340ee382f..61a8a918b 100644 --- a/lib/widgets/common/action_mixins/permission_aware.dart +++ b/lib/widgets/common/action_mixins/permission_aware.dart @@ -8,13 +8,14 @@ import 'package:flutter/material.dart'; mixin PermissionAwareMixin { Future checkStoragePermission(BuildContext context, Set entries) { - return checkStoragePermissionForAlbums(context, entries.map((e) => e.directory).whereNotNull().toSet(), entries: entries); + final storageDirs = entries.map((e) => e.storageDirectory).whereNotNull().toSet(); + return checkStoragePermissionForAlbums(context, storageDirs, entries: entries); } - Future checkStoragePermissionForAlbums(BuildContext context, Set albumPaths, {Set? entries}) async { + Future checkStoragePermissionForAlbums(BuildContext context, Set storageDirs, {Set? entries}) async { final restrictedDirs = await storageService.getRestrictedDirectories(); while (true) { - final dirs = await storageService.getInaccessibleDirectories(albumPaths); + final dirs = await storageService.getInaccessibleDirectories(storageDirs); final restrictedInaccessibleDirs = dirs.where(restrictedDirs.contains).toSet(); if (restrictedInaccessibleDirs.isNotEmpty) { @@ -51,7 +52,6 @@ mixin PermissionAwareMixin { final directory = dir.relativeDir.isEmpty ? l10n.rootDirectoryDescription : l10n.otherDirectoryDescription(dir.relativeDir); final volume = dir.getVolumeDescription(context); return AvesDialog( - title: l10n.storageAccessDialogTitle, content: Text(l10n.storageAccessDialogMessage(directory, volume)), actions: [ TextButton( @@ -73,10 +73,8 @@ mixin PermissionAwareMixin { await showDialog( context: context, builder: (context) { - final l10n = context.l10n; return AvesDialog( - title: l10n.missingSystemFilePickerDialogTitle, - content: Text(l10n.missingSystemFilePickerDialogMessage), + content: Text(context.l10n.missingSystemFilePickerDialogMessage), actions: [ TextButton( onPressed: () => Navigator.pop(context), @@ -104,7 +102,6 @@ mixin PermissionAwareMixin { final directory = dir.relativeDir.isEmpty ? context.l10n.rootDirectoryDescription : context.l10n.otherDirectoryDescription(dir.relativeDir); final volume = dir.getVolumeDescription(context); return AvesDialog( - title: context.l10n.restrictedAccessDialogTitle, content: Text(context.l10n.restrictedAccessDialogMessage(directory, volume)), actions: [ TextButton( diff --git a/lib/widgets/common/action_mixins/size_aware.dart b/lib/widgets/common/action_mixins/size_aware.dart index bb6a983c4..c6fb9c59a 100644 --- a/lib/widgets/common/action_mixins/size_aware.dart +++ b/lib/widgets/common/action_mixins/size_aware.dart @@ -84,7 +84,6 @@ mixin SizeAwareMixin { final freeSize = formatFileSize(locale, free); final volume = destinationVolume.getDescription(context); return AvesDialog( - title: l10n.notEnoughSpaceDialogTitle, content: Text(l10n.notEnoughSpaceDialogMessage(neededSize, freeSize, volume)), actions: [ TextButton( diff --git a/lib/widgets/common/app_bar/app_bar_subtitle.dart b/lib/widgets/common/app_bar/app_bar_subtitle.dart index 688ca0ab5..2e09fe94c 100644 --- a/lib/widgets/common/app_bar/app_bar_subtitle.dart +++ b/lib/widgets/common/app_bar/app_bar_subtitle.dart @@ -1,7 +1,7 @@ import 'dart:ui'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/model/source/events.dart'; import 'package:aves/model/source/source_state.dart'; import 'package:aves/theme/durations.dart'; @@ -38,7 +38,7 @@ class SourceStateAwareAppBarTitle extends StatelessWidget { ), ), child: sourceState == SourceState.ready - ? const SizedBox.shrink() + ? const SizedBox() : SourceStateSubtitle( source: source, ), diff --git a/lib/widgets/common/basic/labeled_checkbox.dart b/lib/widgets/common/basic/labeled_checkbox.dart new file mode 100644 index 000000000..286b89247 --- /dev/null +++ b/lib/widgets/common/basic/labeled_checkbox.dart @@ -0,0 +1,55 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +class LabeledCheckbox extends StatefulWidget { + final bool value; + final ValueChanged onChanged; + final String text; + + const LabeledCheckbox({ + Key? key, + required this.value, + required this.onChanged, + required this.text, + }) : super(key: key); + + @override + State createState() => _LabeledCheckboxState(); +} + +class _LabeledCheckboxState extends State { + late TapGestureRecognizer _tapRecognizer; + + @override + void initState() { + super.initState(); + _tapRecognizer = TapGestureRecognizer()..onTap = () => widget.onChanged(!widget.value); + } + + @override + void dispose() { + _tapRecognizer.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Text.rich( + TextSpan( + children: [ + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Checkbox( + value: widget.value, + onChanged: widget.onChanged, + ), + ), + TextSpan( + text: widget.text, + recognizer: _tapRecognizer, + ), + ], + ), + ); + } +} diff --git a/lib/widgets/common/basic/text_dropdown_button.dart b/lib/widgets/common/basic/text_dropdown_button.dart new file mode 100644 index 000000000..4f60ef164 --- /dev/null +++ b/lib/widgets/common/basic/text_dropdown_button.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +class TextDropdownButton extends DropdownButton { + TextDropdownButton({ + super.key, + required List values, + required String Function(T value) valueText, + super.value, + super.hint, + super.disabledHint, + required super.onChanged, + super.onTap, + super.elevation, + super.style, + super.underline, + super.icon, + super.iconDisabledColor, + super.iconEnabledColor, + super.iconSize, + super.isDense, + super.isExpanded, + super.itemHeight, + super.focusColor, + super.focusNode, + super.autofocus, + super.dropdownColor, + super.menuMaxHeight, + super.enableFeedback, + super.alignment, + super.borderRadius, + }) : super( + items: values + .map((v) => DropdownMenuItem( + value: v, + child: Text(valueText(v)), + )) + .toList(), + selectedItemBuilder: (context) => values + .map((v) => DropdownMenuItem( + value: v, + child: Align( + alignment: AlignmentDirectional.centerStart, + child: Text( + valueText(v), + softWrap: false, + overflow: TextOverflow.fade, + ), + ), + )) + .toList(), + ); +} diff --git a/lib/widgets/common/expandable_filter_row.dart b/lib/widgets/common/expandable_filter_row.dart index fdb82904e..c8e32ab18 100644 --- a/lib/widgets/common/expandable_filter_row.dart +++ b/lib/widgets/common/expandable_filter_row.dart @@ -44,7 +44,7 @@ class ExpandableFilterRow extends StatelessWidget { children: [ Text( title!, - style: Constants.titleTextStyle, + style: Constants.knownTitleTextStyle, ), const Spacer(), IconButton( diff --git a/lib/widgets/common/grid/header.dart b/lib/widgets/common/grid/header.dart index d5a4bbf2f..43137f3e2 100644 --- a/lib/widgets/common/grid/header.dart +++ b/lib/widgets/common/grid/header.dart @@ -36,6 +36,17 @@ class SectionHeader extends StatelessWidget { constraints: BoxConstraints(minHeight: leadingSize.height), child: GestureDetector( onTap: selectable ? () => _toggleSectionSelection(context) : null, + onLongPress: selectable + ? () { + final selection = context.read>(); + if (selection.isSelecting) { + _toggleSectionSelection(context); + } else { + selection.select(); + selection.addToSelection(_getSectionEntries(context)); + } + } + : null, child: Text.rich( TextSpan( children: [ @@ -55,11 +66,9 @@ class SectionHeader extends StatelessWidget { onPressed: selectable ? () => _toggleSectionSelection(context) : null, ), ), - // TODO TLAD [flutter 3] remove this zero-width span when this is fixed: https://github.com/flutter/flutter/issues/103615 - TextSpan(text: Constants.zwsp * 3, style: Constants.titleTextStyle), TextSpan( text: title, - style: Constants.titleTextStyle, + style: Constants.unknownTitleTextStyle, ), if (trailing != null) WidgetSpan( @@ -76,8 +85,10 @@ class SectionHeader extends StatelessWidget { ); } + List _getSectionEntries(BuildContext context) => context.read>().sections[sectionKey] ?? []; + void _toggleSectionSelection(BuildContext context) { - final sectionEntries = context.read>().sections[sectionKey] ?? []; + final sectionEntries = _getSectionEntries(context); final selection = context.read>(); final isSelected = selection.isSelected(sectionEntries); if (isSelected) { @@ -111,7 +122,7 @@ class SectionHeader extends StatelessWidget { if (hasTrailing) TextSpan(text: '\u200A' * 17), TextSpan( text: title, - style: Constants.titleTextStyle, + style: Constants.unknownTitleTextStyle, ), ], ), diff --git a/lib/widgets/common/grid/item_tracker.dart b/lib/widgets/common/grid/item_tracker.dart index f9b07c896..abaa6e063 100644 --- a/lib/widgets/common/grid/item_tracker.dart +++ b/lib/widgets/common/grid/item_tracker.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:math'; import 'package:aves/model/highlight.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/grid/section_layout.dart'; import 'package:collection/collection.dart'; diff --git a/lib/widgets/common/grid/scaling.dart b/lib/widgets/common/grid/scaling.dart index e24646e04..96988cbff 100644 --- a/lib/widgets/common/grid/scaling.dart +++ b/lib/widgets/common/grid/scaling.dart @@ -1,7 +1,7 @@ import 'dart:ui' as ui; import 'package:aves/model/highlight.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/behaviour/eager_scale_gesture_recognizer.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; diff --git a/lib/widgets/common/grid/section_layout.dart b/lib/widgets/common/grid/section_layout.dart index cb6ef28b5..040304d67 100644 --- a/lib/widgets/common/grid/section_layout.dart +++ b/lib/widgets/common/grid/section_layout.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/model/source/section_keys.dart'; import 'package:aves/theme/durations.dart'; import 'package:collection/collection.dart'; diff --git a/lib/widgets/common/identity/aves_icons.dart b/lib/widgets/common/identity/aves_icons.dart index a32ec6d71..700f16469 100644 --- a/lib/widgets/common/identity/aves_icons.dart +++ b/lib/widgets/common/identity/aves_icons.dart @@ -21,7 +21,7 @@ class VideoIcon extends StatelessWidget { final gridTheme = context.watch(); final showDuration = gridTheme.showVideoDuration; Widget child = OverlayIcon( - icon: entry.is360 ? AIcons.threeSixty : AIcons.videoThumb, + icon: entry.is360 ? AIcons.sphericalVideo : AIcons.videoThumb, text: showDuration ? entry.durationText : null, iconScale: entry.is360 && showDuration ? .9 : 1, ); @@ -62,13 +62,16 @@ class GeoTiffIcon extends StatelessWidget { } } -class SphericalImageIcon extends StatelessWidget { - const SphericalImageIcon({super.key}); +class PanoramaIcon extends StatelessWidget { + const PanoramaIcon({super.key}); + + static const scale = .85; @override Widget build(BuildContext context) { return const OverlayIcon( - icon: AIcons.threeSixty, + icon: AIcons.panorama, + iconScale: scale, ); } } diff --git a/lib/widgets/common/map/buttons/panel.dart b/lib/widgets/common/map/buttons/panel.dart index 30e33be5c..63c974513 100644 --- a/lib/widgets/common/map/buttons/panel.dart +++ b/lib/widgets/common/map/buttons/panel.dart @@ -129,11 +129,11 @@ class MapButtonPanel extends StatelessWidget { onPressed: () => showSelectionDialog( context: context, builder: (context) => AvesSelectionDialog( - initialValue: settings.infoMapStyle, + initialValue: settings.mapStyle, options: Map.fromEntries(availability.mapStyles.map((v) => MapEntry(v, v.getName(context)))), - title: context.l10n.mapStyleTitle, + title: context.l10n.mapStyleDialogTitle, ), - onSelection: (v) => settings.infoMapStyle = v, + onSelection: (v) => settings.mapStyle = v, ), tooltip: context.l10n.mapStyleTooltip, ), diff --git a/lib/widgets/common/map/geo_map.dart b/lib/widgets/common/map/geo_map.dart index 44de80547..3518e7095 100644 --- a/lib/widgets/common/map/geo_map.dart +++ b/lib/widgets/common/map/geo_map.dart @@ -149,7 +149,7 @@ class _GeoMapState extends State { } return Selector( - selector: (context, s) => s.infoMapStyle, + selector: (context, s) => s.mapStyle, builder: (context, mapStyle, child) { final isHeavy = mapStyle.isHeavy; Widget _buildMarkerWidget(MarkerKey key) => ImageMarker( @@ -281,6 +281,7 @@ class _GeoMapState extends State { final overlayEntry = widget.overlayEntry; if (overlayEntry != null) { + // fit map to overlaid item final corner1 = overlayEntry.topLeft; final corner2 = overlayEntry.bottomRight; if (corner1 != null && corner2 != null) { @@ -290,10 +291,26 @@ class _GeoMapState extends State { } } if (bounds == null) { + // fit map to located items final initialCenter = widget.initialCenter; final points = initialCenter != null ? {initialCenter} : entries.map((v) => v.latLng!).toSet(); + if (points.isNotEmpty) { + bounds = ZoomedBounds.fromPoints( + points: points, + collocationZoom: settings.infoMapZoom, + ); + final center = bounds.projectedCenter; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + settings.mapDefaultCenter = center; + }); + } + } + if (bounds == null) { + // fallback to default center + final center = settings.mapDefaultCenter ??= Constants.wonders[Random().nextInt(Constants.wonders.length)]; bounds = ZoomedBounds.fromPoints( - points: points.isNotEmpty ? points : {Constants.wonders[Random().nextInt(Constants.wonders.length)]}, + points: {center}, collocationZoom: settings.infoMapZoom, ); } diff --git a/lib/widgets/common/map/leaflet/map.dart b/lib/widgets/common/map/leaflet/map.dart index 43931d71b..d21b97774 100644 --- a/lib/widgets/common/map/leaflet/map.dart +++ b/lib/widgets/common/map/leaflet/map.dart @@ -155,7 +155,6 @@ class _EntryLeafletMapState extends State> with TickerProv // prevent triggering multiple gestures at once (e.g. rotating a bit when mostly zooming) enableMultiFingerGestureRace: true, onTap: (tapPosition, point) => widget.onMapTap?.call(point), - controller: _leafletMapController, ), mapController: _leafletMapController, nonRotatedChildren: [ @@ -168,27 +167,23 @@ class _EntryLeafletMapState extends State> with TickerProv children: [ _buildMapLayer(), if (widget.overlayEntry != null) _buildOverlayImageLayer(), - MarkerLayerWidget( - options: MarkerLayerOptions( - markers: markers, - rotate: true, - rotateAlignment: Alignment.bottomCenter, - ), + MarkerLayer( + markers: markers, + rotate: true, + rotateAlignment: Alignment.bottomCenter, ), ValueListenableBuilder( valueListenable: widget.dotLocationNotifier ?? ValueNotifier(null), - builder: (context, dotLocation, child) => MarkerLayerWidget( - options: MarkerLayerOptions( - markers: [ - if (dotLocation != null) - Marker( - point: dotLocation, - builder: (context) => const DotMarker(), - width: dotMarkerSize.width, - height: dotMarkerSize.height, - ) - ], - ), + builder: (context, dotLocation, child) => MarkerLayer( + markers: [ + if (dotLocation != null) + Marker( + point: dotLocation, + builder: (context) => const DotMarker(), + width: dotMarkerSize.width, + height: dotMarkerSize.height, + ) + ], ), ), ], @@ -219,16 +214,14 @@ class _EntryLeafletMapState extends State> with TickerProv return ValueListenableBuilder( valueListenable: widget.overlayOpacityNotifier ?? ValueNotifier(1), builder: (context, overlayOpacity, child) { - return OverlayImageLayerWidget( - options: OverlayImageLayerOptions( - overlayImages: [ - OverlayImage( - bounds: LatLngBounds(corner1, corner2), - imageProvider: overlayEntry.imageProvider, - opacity: overlayOpacity, - ), - ], - ), + return OverlayImageLayer( + overlayImages: [ + OverlayImage( + bounds: LatLngBounds(corner1, corner2), + imageProvider: overlayEntry.imageProvider, + opacity: overlayOpacity, + ), + ], ); }, ); diff --git a/lib/widgets/common/map/leaflet/scale_layer.dart b/lib/widgets/common/map/leaflet/scale_layer.dart index e07477766..3e3151f65 100644 --- a/lib/widgets/common/map/leaflet/scale_layer.dart +++ b/lib/widgets/common/map/leaflet/scale_layer.dart @@ -1,19 +1,19 @@ +import 'dart:math'; + import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/widgets/common/basic/outlined_text.dart'; -import 'package:aves/widgets/common/map/leaflet/scalebar_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/plugin_api.dart'; +import 'package:latlong2/latlong.dart'; -class ScaleLayerOptions extends LayerOptions { +class ScaleLayerOptions { final UnitSystem unitSystem; final Widget Function(double width, String distance) builder; ScaleLayerOptions({ - super.key, this.unitSystem = UnitSystem.metric, this.builder = defaultBuilder, - rebuild, - }) : super(rebuild: rebuild); + }); static Widget defaultBuilder(double width, String distance) { return ScaleBar( @@ -26,20 +26,13 @@ class ScaleLayerOptions extends LayerOptions { class ScaleLayerWidget extends StatelessWidget { final ScaleLayerOptions options; - ScaleLayerWidget({required this.options}) : super(key: options.key); + final Distance _distanceCalculator = const Distance(); - @override - Widget build(BuildContext context) { - final mapState = MapState.maybeOf(context); - return mapState != null ? ScaleLayer(options, mapState, mapState.onMoved) : const SizedBox(); - } -} + const ScaleLayerWidget({ + super.key, + required this.options, + }); -class ScaleLayer extends StatelessWidget { - final ScaleLayerOptions scaleLayerOpts; - final MapState map; - - final Stream stream; static const List scaleMeters = [ 25000000, 15000000, @@ -70,53 +63,47 @@ class ScaleLayer extends StatelessWidget { static const double metersInAMile = 1609.344; static const double metersInAFoot = 0.3048; - ScaleLayer(this.scaleLayerOpts, this.map, this.stream) : super(key: scaleLayerOpts.key); - @override Widget build(BuildContext context) { - return StreamBuilder( - stream: stream, - builder: (context, snapshot) { - final center = map.center; - final latitude = center.latitude.abs(); - final level = map.zoom.round() + - (latitude > 80 - ? 4 - : latitude > 60 - ? 3 - : 2); - final scaleLevel = level.clamp(0, 20); - late final double distanceMeters; - late final String displayDistance; - switch (scaleLayerOpts.unitSystem) { - case UnitSystem.metric: - // meters - distanceMeters = scaleMeters[scaleLevel]; - displayDistance = distanceMeters >= metersInAKilometer ? '${(distanceMeters / metersInAKilometer).toStringAsFixed(0)} km' : '${distanceMeters.toStringAsFixed(0)} m'; - break; - case UnitSystem.imperial: - if (scaleLevel < 15) { - // miles - final distanceMiles = scaleMeters[scaleLevel + 1] / 1000; - distanceMeters = distanceMiles * metersInAMile; - displayDistance = '${distanceMiles.toStringAsFixed(0)} mi'; - } else { - // feet - final distanceFeet = scaleMeters[scaleLevel - 1]; - distanceMeters = distanceFeet * metersInAFoot; - displayDistance = '${distanceFeet.toStringAsFixed(0)} ft'; - } - break; + final map = FlutterMapState.maybeOf(context)!; + final center = map.center; + final latitude = center.latitude.abs(); + final level = map.zoom.round() + + (latitude > 80 + ? 4 + : latitude > 60 + ? 3 + : 2); + final scaleLevel = level.clamp(0, 20); + late final double distanceMeters; + late final String displayDistance; + switch (options.unitSystem) { + case UnitSystem.metric: + // meters + distanceMeters = scaleMeters[scaleLevel]; + displayDistance = distanceMeters >= metersInAKilometer ? '${(distanceMeters / metersInAKilometer).toStringAsFixed(0)} km' : '${distanceMeters.toStringAsFixed(0)} m'; + break; + case UnitSystem.imperial: + if (scaleLevel < 15) { + // miles + final distanceMiles = scaleMeters[scaleLevel + 1] / 1000; + distanceMeters = distanceMiles * metersInAMile; + displayDistance = '${distanceMiles.toStringAsFixed(0)} mi'; + } else { + // feet + final distanceFeet = scaleMeters[scaleLevel - 1]; + distanceMeters = distanceFeet * metersInAFoot; + displayDistance = '${distanceFeet.toStringAsFixed(0)} ft'; } + break; + } - final start = map.project(center); - final targetPoint = ScaleBarUtils.calculateEndingGlobalCoordinates(center, 90, distanceMeters); - final end = map.project(targetPoint); - final width = end.x - (start.x as double); + final start = map.project(center); + final targetPoint = _distanceCalculator.offset(center, distanceMeters, 90); + final end = map.project(targetPoint); + final width = max(0, end.x - start.x).toDouble(); - return scaleLayerOpts.builder(width, displayDistance); - }, - ); + return options.builder(width, displayDistance); } } diff --git a/lib/widgets/common/map/leaflet/scalebar_utils.dart b/lib/widgets/common/map/leaflet/scalebar_utils.dart deleted file mode 100644 index 0027bc616..000000000 --- a/lib/widgets/common/map/leaflet/scalebar_utils.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'dart:math'; - -import 'package:latlong2/latlong.dart'; - -class ScaleBarUtils { - static LatLng calculateEndingGlobalCoordinates(LatLng start, double startBearing, double distanceMeters) { - var mSemiMajorAxis = 6378137.0; //WGS84 major axis - var mSemiMinorAxis = (1.0 - 1.0 / 298.257223563) * 6378137.0; - var mFlattening = 1.0 / 298.257223563; - // double mInverseFlattening = 298.257223563; - - var a = mSemiMajorAxis; - var b = mSemiMinorAxis; - var aSquared = a * a; - var bSquared = b * b; - var f = mFlattening; - var phi1 = degToRadian(start.latitude); - var alpha1 = degToRadian(startBearing); - var cosAlpha1 = cos(alpha1); - var sinAlpha1 = sin(alpha1); - var s = distanceMeters; - var tanU1 = (1.0 - f) * tan(phi1); - var cosU1 = 1.0 / sqrt(1.0 + tanU1 * tanU1); - var sinU1 = tanU1 * cosU1; - - // eq. 1 - var sigma1 = atan2(tanU1, cosAlpha1); - - // eq. 2 - var sinAlpha = cosU1 * sinAlpha1; - - var sin2Alpha = sinAlpha * sinAlpha; - var cos2Alpha = 1 - sin2Alpha; - var uSquared = cos2Alpha * (aSquared - bSquared) / bSquared; - - // eq. 3 - var A = 1 + (uSquared / 16384) * (4096 + uSquared * (-768 + uSquared * (320 - 175 * uSquared))); - - // eq. 4 - var B = (uSquared / 1024) * (256 + uSquared * (-128 + uSquared * (74 - 47 * uSquared))); - - // iterate until there is a negligible change in sigma - double deltaSigma; - var sOverbA = s / (b * A); - var sigma = sOverbA; - double sinSigma; - var prevSigma = sOverbA; - double sigmaM2; - double cosSigmaM2; - double cos2SigmaM2; - - for (;;) { - // eq. 5 - sigmaM2 = 2.0 * sigma1 + sigma; - cosSigmaM2 = cos(sigmaM2); - cos2SigmaM2 = cosSigmaM2 * cosSigmaM2; - sinSigma = sin(sigma); - var cosSignma = cos(sigma); - - // eq. 6 - deltaSigma = B * sinSigma * (cosSigmaM2 + (B / 4.0) * (cosSignma * (-1 + 2 * cos2SigmaM2) - (B / 6.0) * cosSigmaM2 * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM2))); - - // eq. 7 - sigma = sOverbA + deltaSigma; - - // break after converging to tolerance - if ((sigma - prevSigma).abs() < 0.0000000000001) break; - - prevSigma = sigma; - } - - sigmaM2 = 2.0 * sigma1 + sigma; - cosSigmaM2 = cos(sigmaM2); - cos2SigmaM2 = cosSigmaM2 * cosSigmaM2; - - var cosSigma = cos(sigma); - sinSigma = sin(sigma); - - // eq. 8 - var phi2 = atan2(sinU1 * cosSigma + cosU1 * sinSigma * cosAlpha1, (1.0 - f) * sqrt(sin2Alpha + pow(sinU1 * sinSigma - cosU1 * cosSigma * cosAlpha1, 2.0))); - - // eq. 9 - // This fixes the pole crossing defect spotted by Matt Feemster. When a - // path passes a pole and essentially crosses a line of latitude twice - - // once in each direction - the longitude calculation got messed up. - // Using - // atan2 instead of atan fixes the defect. The change is in the next 3 - // lines. - // double tanLambda = sinSigma * sinAlpha1 / (cosU1 * cosSigma - sinU1 * - // sinSigma * cosAlpha1); - // double lambda = Math.atan(tanLambda); - var lambda = atan2(sinSigma * sinAlpha1, (cosU1 * cosSigma - sinU1 * sinSigma * cosAlpha1)); - - // eq. 10 - var C = (f / 16) * cos2Alpha * (4 + f * (4 - 3 * cos2Alpha)); - - // eq. 11 - var L = lambda - (1 - C) * f * sinAlpha * (sigma + C * sinSigma * (cosSigmaM2 + C * cosSigma * (-1 + 2 * cos2SigmaM2))); - - // eq. 12 - // double alpha2 = Math.atan2(sinAlpha, -sinU1 * sinSigma + cosU1 * - // cosSigma * cosAlpha1); - - // build result - var latitude = radianToDeg(phi2); - var longitude = start.longitude + radianToDeg(L); - - // if ((endBearing != null) && (endBearing.length > 0)) { - // endBearing[0] = toDegrees(alpha2); - // } - - latitude = latitude < -90 ? -90 : latitude; - latitude = latitude > 90 ? 90 : latitude; - longitude = longitude < -180 ? -180 : longitude; - longitude = longitude > 180 ? 180 : longitude; - return LatLng(latitude, longitude); - } -} diff --git a/lib/widgets/common/map/leaflet/tile_layers.dart b/lib/widgets/common/map/leaflet/tile_layers.dart index 5b6f55191..5ea7d1bb9 100644 --- a/lib/widgets/common/map/leaflet/tile_layers.dart +++ b/lib/widgets/common/map/leaflet/tile_layers.dart @@ -10,14 +10,12 @@ class OSMHotLayer extends StatelessWidget { @override Widget build(BuildContext context) { - return TileLayerWidget( - options: TileLayerOptions( - urlTemplate: 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c'], - backgroundColor: _tileLayerBackgroundColor, - retinaMode: context.select((mq) => mq.devicePixelRatio) > 1, - userAgentPackageName: device.userAgent, - ), + return TileLayer( + urlTemplate: 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', + subdomains: const ['a', 'b', 'c'], + backgroundColor: _tileLayerBackgroundColor, + retinaMode: context.select((mq) => mq.devicePixelRatio) > 1, + userAgentPackageName: device.userAgent, ); } } @@ -27,14 +25,12 @@ class StamenTonerLayer extends StatelessWidget { @override Widget build(BuildContext context) { - return TileLayerWidget( - options: TileLayerOptions( - urlTemplate: 'https://stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}{r}.png', - subdomains: ['a', 'b', 'c', 'd'], - backgroundColor: _tileLayerBackgroundColor, - retinaMode: context.select((mq) => mq.devicePixelRatio) > 1, - userAgentPackageName: device.userAgent, - ), + return TileLayer( + urlTemplate: 'https://stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}{r}.png', + subdomains: const ['a', 'b', 'c', 'd'], + backgroundColor: _tileLayerBackgroundColor, + retinaMode: context.select((mq) => mq.devicePixelRatio) > 1, + userAgentPackageName: device.userAgent, ); } } @@ -44,14 +40,12 @@ class StamenWatercolorLayer extends StatelessWidget { @override Widget build(BuildContext context) { - return TileLayerWidget( - options: TileLayerOptions( - urlTemplate: 'https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg', - subdomains: ['a', 'b', 'c', 'd'], - backgroundColor: _tileLayerBackgroundColor, - retinaMode: context.select((mq) => mq.devicePixelRatio) > 1, - userAgentPackageName: device.userAgent, - ), + return TileLayer( + urlTemplate: 'https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg', + subdomains: const ['a', 'b', 'c', 'd'], + backgroundColor: _tileLayerBackgroundColor, + retinaMode: context.select((mq) => mq.devicePixelRatio) > 1, + userAgentPackageName: device.userAgent, ); } } diff --git a/lib/widgets/common/thumbnail/overlay.dart b/lib/widgets/common/thumbnail/overlay.dart index 393af4178..e9ca7f249 100644 --- a/lib/widgets/common/thumbnail/overlay.dart +++ b/lib/widgets/common/thumbnail/overlay.dart @@ -29,7 +29,7 @@ class ThumbnailEntryOverlay extends StatelessWidget { const AnimatedImageIcon() else ...[ if (entry.isRaw && context.select((t) => t.showRaw)) const RawIcon(), - if (entry.is360) const SphericalImageIcon(), + if (entry.is360) const PanoramaIcon(), ], if (entry.isMultiPage) ...[ if (entry.isMotionPhoto && context.select((t) => t.showMotionPhoto)) const MotionPhotoIcon(), diff --git a/lib/widgets/dialogs/aves_dialog.dart b/lib/widgets/dialogs/aves_dialog.dart index 5145c86bc..2b944e95c 100644 --- a/lib/widgets/dialogs/aves_dialog.dart +++ b/lib/widgets/dialogs/aves_dialog.dart @@ -154,7 +154,6 @@ void showNoMatchingAppDialog(BuildContext context) { context: context, builder: (context) { return AvesDialog( - title: context.l10n.noMatchingAppDialogTitle, content: Text(context.l10n.noMatchingAppDialogMessage), actions: [ TextButton( diff --git a/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart index 619b89bab..c83656741 100644 --- a/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_date_dialog.dart @@ -7,6 +7,7 @@ import 'package:aves/theme/durations.dart'; import 'package:aves/theme/format.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/theme/themes.dart'; +import 'package:aves/widgets/common/basic/text_dropdown_button.dart'; import 'package:aves/widgets/common/basic/wheel.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; @@ -80,13 +81,9 @@ class _EditEntryDateDialogState extends State { scrollableContent: [ Padding( padding: const EdgeInsets.only(left: 16, top: 8, right: 16), - child: DropdownButton( - items: DateEditAction.values - .map((v) => DropdownMenuItem( - value: v, - child: Text(_actionText(context, v)), - )) - .toList(), + child: TextDropdownButton( + values: DateEditAction.values, + valueText: (v) => _actionText(context, v), value: _action, onChanged: (v) => setState(() => _action = v!), isExpanded: true, @@ -159,23 +156,9 @@ class _EditEntryDateDialogState extends State { Widget _buildCopyFieldContent(BuildContext context) { return Padding( padding: const EdgeInsets.only(left: 16, top: 0, right: 16), - child: DropdownButton( - items: DateFieldSource.values - .map((v) => DropdownMenuItem( - value: v, - child: Text(_setSourceText(context, v)), - )) - .toList(), - selectedItemBuilder: (context) => DateFieldSource.values - .map((v) => DropdownMenuItem( - value: v, - child: Text( - _setSourceText(context, v), - softWrap: false, - overflow: TextOverflow.fade, - ), - )) - .toList(), + child: TextDropdownButton( + values: DateFieldSource.values, + valueText: (v) => _setSourceText(context, v), value: _copyFieldSource, onChanged: (v) => setState(() => _copyFieldSource = v!), isExpanded: true, diff --git a/lib/widgets/dialogs/entry_editors/edit_description_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_description_dialog.dart index ac43a2b2c..7e9c67473 100644 --- a/lib/widgets/dialogs/entry_editors/edit_description_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_description_dialog.dart @@ -1,52 +1,56 @@ +import 'package:aves/model/entry_metadata_edition.dart'; +import 'package:aves/widgets/common/basic/labeled_checkbox.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:flutter/material.dart'; -class EditEntryDescriptionDialog extends StatefulWidget { - final String initialDescription; +class EditEntryTitleDescriptionDialog extends StatefulWidget { + final String initialTitle, initialDescription; - const EditEntryDescriptionDialog({ + const EditEntryTitleDescriptionDialog({ super.key, + required this.initialTitle, required this.initialDescription, }); @override - State createState() => _EditEntryDescriptionDialogState(); + State createState() => _EditEntryTitleDescriptionDialogState(); } -class _EditEntryDescriptionDialogState extends State { - late final TextEditingController _textController; +class _EditEntryTitleDescriptionDialogState extends State { + final Set fields = { + DescriptionField.title, + DescriptionField.description, + }; + late final TextEditingController _titleTextController, _descriptionTextController; @override void initState() { super.initState(); - _textController = TextEditingController(text: widget.initialDescription); + _titleTextController = TextEditingController(text: widget.initialTitle); + _descriptionTextController = TextEditingController(text: widget.initialDescription); } @override Widget build(BuildContext context) { return MediaQueryDataProvider( child: Builder(builder: (context) { - final l10n = context.l10n; - return AvesDialog( - title: l10n.editEntryDescriptionDialogTitle, - content: TextField( - controller: _textController, - decoration: const InputDecoration( - border: OutlineInputBorder(), - ), - maxLines: null, - ), + scrollableContent: [ + const SizedBox(height: 8), + ..._buildFieldEditor(DescriptionField.title), + ..._buildFieldEditor(DescriptionField.description), + const SizedBox(height: 8), + ], actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text(MaterialLocalizations.of(context).cancelButtonLabel), ), TextButton( - onPressed: () => _submit(context), - child: Text(l10n.applyButtonLabel), + onPressed: fields.isEmpty ? null : () => _submit(context), + child: Text(context.l10n.applyButtonLabel), ), ], ); @@ -54,5 +58,54 @@ class _EditEntryDescriptionDialogState extends State ); } - void _submit(BuildContext context) => Navigator.pop(context, _textController.text); + List _buildFieldEditor(DescriptionField field) { + final editing = fields.contains(field); + return [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: LabeledCheckbox( + value: editing, + onChanged: (v) => setState(() => editing ? fields.remove(field) : fields.add(field)), + text: _fieldName(field), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: TextField( + controller: _fieldController(field), + decoration: const InputDecoration( + border: OutlineInputBorder(), + ), + maxLines: null, + enabled: editing, + ), + ), + ]; + } + + TextEditingController _fieldController(DescriptionField field) { + switch (field) { + case DescriptionField.title: + return _titleTextController; + case DescriptionField.description: + return _descriptionTextController; + } + } + + String _fieldName(DescriptionField field) { + switch (field) { + case DescriptionField.title: + return context.l10n.viewerInfoLabelTitle; + case DescriptionField.description: + return context.l10n.viewerInfoLabelDescription; + } + } + + void _submit(BuildContext context) { + final modifier = Map.fromEntries(fields.map((field) { + final text = _fieldController(field).text; + return MapEntry(field, text.isEmpty ? null : text); + })); + return Navigator.pop>(context, modifier); + } } diff --git a/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart index 943b2fb08..c490b9ee8 100644 --- a/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_location_dialog.dart @@ -1,4 +1,4 @@ -import 'package:aves/model/entry.dart'; +import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/constants.dart'; @@ -11,12 +11,12 @@ import 'package:intl/intl.dart'; import 'package:latlong2/latlong.dart'; class EditEntryLocationDialog extends StatefulWidget { - final AvesEntry entry; + final LatLng? initialLocation; final CollectionLens? collection; const EditEntryLocationDialog({ super.key, - required this.entry, + required this.initialLocation, this.collection, }); @@ -37,7 +37,7 @@ class _EditEntryLocationDialogState extends State { super.initState(); _latitudeFocusNode.addListener(_onLatLngFocusChange); _longitudeFocusNode.addListener(_onLatLngFocusChange); - WidgetsBinding.instance.addPostFrameCallback((_) => _setLocation(context, widget.entry.latLng)); + WidgetsBinding.instance.addPostFrameCallback((_) => _setLocation(context, widget.initialLocation)); } @override @@ -183,6 +183,7 @@ class _EditEntryLocationDialogState extends State { ), ); if (latLng != null) { + settings.mapDefaultCenter = latLng; _setLocation(context, latLng); } } diff --git a/lib/widgets/dialogs/entry_editors/edit_rating_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_rating_dialog.dart index 49c848423..2e07e0751 100644 --- a/lib/widgets/dialogs/entry_editors/edit_rating_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_rating_dialog.dart @@ -95,7 +95,7 @@ class _EditEntryRatingDialogState extends State { _action = v!; _rating = 0; }), - title: Text(l10n.filterRatingUnratedLabel), + title: Text(l10n.filterNoRatingLabel), ), ], actions: [ diff --git a/lib/widgets/dialogs/entry_editors/edit_tags_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_tags_dialog.dart index 545daab89..7ce92e34c 100644 --- a/lib/widgets/dialogs/entry_editors/edit_tags_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_tags_dialog.dart @@ -125,7 +125,7 @@ class _TagEditorPageState extends State { const Icon(AIcons.tagUntagged, color: untaggedColor), const SizedBox(width: 8), Text( - l10n.filterTagEmptyLabel, + l10n.filterNoTagLabel, style: const TextStyle(color: untaggedColor), ), ], @@ -162,7 +162,7 @@ class _TagEditorPageState extends State { onTap: _addTag, ), _FilterRow( - title: l10n.statsTopTags, + title: l10n.statsTopTagsSectionTitle, filters: topTagFilters, expandedNotifier: _expandedSectionNotifier, onTap: _addTag, diff --git a/lib/widgets/dialogs/entry_editors/rename_entry_set_dialog.dart b/lib/widgets/dialogs/entry_editors/rename_entry_set_dialog.dart index df9beaa39..6b718583b 100644 --- a/lib/widgets/dialogs/entry_editors/rename_entry_set_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/rename_entry_set_dialog.dart @@ -116,8 +116,8 @@ class _RenameEntrySetPageState extends State { Padding( padding: const EdgeInsets.all(16), child: Text( - l10n.renameEntrySetPagePreview, - style: Constants.titleTextStyle, + l10n.renameEntrySetPagePreviewSectionTitle, + style: Constants.knownTitleTextStyle, ), ), Expanded( diff --git a/lib/widgets/dialogs/export_entry_dialog.dart b/lib/widgets/dialogs/export_entry_dialog.dart index 7482b887b..5b59c90d2 100644 --- a/lib/widgets/dialogs/export_entry_dialog.dart +++ b/lib/widgets/dialogs/export_entry_dialog.dart @@ -2,6 +2,7 @@ import 'package:aves/model/entry.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/media/media_edit_service.dart'; import 'package:aves/utils/mime_utils.dart'; +import 'package:aves/widgets/common/basic/text_dropdown_button.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:flutter/material.dart'; @@ -63,13 +64,9 @@ class _ExportEntryDialogState extends State { children: [ Text(l10n.exportEntryDialogFormat), const SizedBox(width: AvesDialog.controlCaptionPadding), - DropdownButton( - items: imageExportFormats.map((mimeType) { - return DropdownMenuItem( - value: mimeType, - child: Text(MimeUtils.displayType(mimeType)), - ); - }).toList(), + TextDropdownButton( + values: imageExportFormats, + valueText: MimeUtils.displayType, value: _mimeType, onChanged: (selected) { if (selected != null) { diff --git a/lib/widgets/dialogs/location_pick_dialog.dart b/lib/widgets/dialogs/location_pick_dialog.dart index b9b7d1b14..6879d8603 100644 --- a/lib/widgets/dialogs/location_pick_dialog.dart +++ b/lib/widgets/dialogs/location_pick_dialog.dart @@ -78,7 +78,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin void initState() { super.initState(); - if (settings.infoMapStyle.isHeavy) { + if (settings.mapStyle.isHeavy) { _isPageAnimatingNotifier = ValueNotifier(true); Future.delayed(Durations.pageTransitionAnimation * timeDilation).then((_) { if (!mounted) return; diff --git a/lib/widgets/dialogs/tile_view_dialog.dart b/lib/widgets/dialogs/tile_view_dialog.dart index 490d632dc..a4074732e 100644 --- a/lib/widgets/dialogs/tile_view_dialog.dart +++ b/lib/widgets/dialogs/tile_view_dialog.dart @@ -1,19 +1,22 @@ -import 'dart:math'; - -import 'package:aves/model/source/enums.dart'; +import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; +import 'package:aves/theme/themes.dart'; +import 'package:aves/widgets/common/basic/text_dropdown_button.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/identity/highlight_title.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; +import 'package:provider/provider.dart'; import 'package:tuple/tuple.dart'; import 'aves_dialog.dart'; class TileViewDialog extends StatefulWidget { - final Tuple3 initialValue; + final Tuple4 initialValue; final Map sortOptions; final Map groupOptions; final Map layoutOptions; + final String Function(S sort, bool reverse) sortOrder; + final bool Function(S? sort, G? group, L? layout)? canGroup; const TileViewDialog({ super.key, @@ -21,6 +24,8 @@ class TileViewDialog extends StatefulWidget { this.sortOptions = const {}, this.groupOptions = const {}, this.layoutOptions = const {}, + required this.sortOrder, + this.canGroup, }); @override @@ -31,8 +36,7 @@ class _TileViewDialogState extends State> with late S? _selectedSort; late G? _selectedGroup; late L? _selectedLayout; - late final TabController _tabController; - late final String _optionLines; + late bool _reverseSort; Map get sortOptions => widget.sortOptions; @@ -40,11 +44,7 @@ class _TileViewDialogState extends State> with Map get layoutOptions => widget.layoutOptions; - static const int groupTabIndex = 1; - - double tabBarHeight(BuildContext context) => 64 * max(1, MediaQuery.textScaleFactorOf(context)); - - static const double tabIndicatorWeight = 2; + bool get canGroup => (widget.canGroup ?? (s, g, l) => true).call(_selectedSort, _selectedGroup, _selectedLayout); @override void initState() { @@ -53,259 +53,140 @@ class _TileViewDialogState extends State> with _selectedSort = initialValue.item1; _selectedGroup = initialValue.item2; _selectedLayout = initialValue.item3; - - final allOptions = [ - sortOptions, - groupOptions, - layoutOptions, - ]; - - final tabCount = allOptions.where((options) => options.isNotEmpty).length; - _tabController = TabController(length: tabCount, vsync: this); - _tabController.addListener(_onTabChange); - - _optionLines = allOptions.expand((v) => v.values).fold('', (previousValue, element) => '$previousValue\n$element'); - } - - @override - void dispose() { - _tabController.removeListener(_onTabChange); - _tabController.dispose(); - super.dispose(); + _reverseSort = initialValue.item4; } @override Widget build(BuildContext context) { final l10n = context.l10n; - final tabs = >[ - if (sortOptions.isNotEmpty) - Tuple2( - _buildTab( - context, - const Key('tab-sort'), - AIcons.sort, - l10n.viewDialogTabSort, + + return AvesDialog( + scrollableContent: [ + _buildSection( + icon: AIcons.sort, + title: l10n.viewDialogSortSectionTitle, + trailing: IconButton( + icon: const Icon(AIcons.sortOrder), + onPressed: () => setState(() => _reverseSort = !_reverseSort), + tooltip: l10n.viewDialogReverseSortOrder, ), - Column( - children: sortOptions.entries - .map((kv) => _buildRadioListTile( - kv.key, - kv.value, - () => _selectedSort, - (v) => _selectedSort = v, - )) - .toList(), + options: sortOptions, + value: _selectedSort, + onChanged: (v) { + _selectedSort = v; + _reverseSort = false; + }, + bottom: _selectedSort != null + ? Text( + widget.sortOrder(_selectedSort as S, _reverseSort), + style: Theme.of(context).textTheme.caption, + ) + : null, + ), + AnimatedSwitcher( + duration: context.read().formTransition, + switchInCurve: Curves.easeInOutCubic, + switchOutCurve: Curves.easeInOutCubic, + transitionBuilder: (child, animation) => FadeTransition( + opacity: animation, + child: SizeTransition( + sizeFactor: animation, + axisAlignment: -1, + child: child, + ), + ), + child: _buildSection( + show: canGroup, + icon: AIcons.group, + title: l10n.viewDialogGroupSectionTitle, + options: groupOptions, + value: _selectedGroup, + onChanged: (v) => _selectedGroup = v, ), ), - if (groupOptions.isNotEmpty) - Tuple2( - _buildTab( - context, - const Key('tab-group'), - AIcons.group, - l10n.viewDialogTabGroup, - color: canGroup ? null : Theme.of(context).disabledColor, - ), - Column( - children: groupOptions.entries - .map((kv) => _buildRadioListTile( - kv.key, - kv.value, - () => _selectedGroup, - (v) => _selectedGroup = v, - )) - .toList(), - ), + _buildSection( + icon: AIcons.layout, + title: l10n.viewDialogLayoutSectionTitle, + options: layoutOptions, + value: _selectedLayout, + onChanged: (v) => _selectedLayout = v, ), - if (layoutOptions.isNotEmpty) - Tuple2( - _buildTab( - context, - const Key('tab-layout'), - AIcons.layout, - l10n.viewDialogTabLayout, - ), - Column( - children: layoutOptions.entries - .map((kv) => _buildRadioListTile( - kv.key, - kv.value, - () => _selectedLayout, - (v) => _selectedLayout = v, - )) - .toList(), - ), + ], + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(MaterialLocalizations.of(context).cancelButtonLabel), ), - ]; - - final contentWidget = DecoratedBox( - decoration: AvesDialog.contentDecoration(context), - child: LayoutBuilder( - builder: (context, constraints) { - final availableBodyHeight = constraints.maxHeight - tabBarHeight(context) - tabIndicatorWeight; - final maxHeight = min(availableBodyHeight, tabBodyMaxHeight(context)); - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Material( - borderRadius: const BorderRadius.vertical( - top: AvesDialog.cornerRadius, - ), - clipBehavior: Clip.antiAlias, - child: TabBar( - indicatorWeight: tabIndicatorWeight, - tabs: tabs.map((t) => t.item1).toList(), - controller: _tabController, - ), - ), - ConstrainedBox( - constraints: BoxConstraints( - maxHeight: maxHeight, - ), - child: TabBarView( - controller: _tabController, - physics: const NeverScrollableScrollPhysics(), - children: tabs - .map((t) => SingleChildScrollView( - child: t.item2, - )) - .toList(), - ), - ), - ], - ); - }, - ), - ); - - final actionsWidget = Padding( - padding: AvesDialog.actionsPadding, - child: OverflowBar( - alignment: MainAxisAlignment.end, - spacing: AvesDialog.buttonPadding.horizontal / 2, - overflowAlignment: OverflowBarAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), - TextButton( - key: const Key('button-apply'), - onPressed: () => Navigator.pop(context, Tuple3(_selectedSort, _selectedGroup, _selectedLayout)), - child: Text(l10n.applyButtonLabel), - ) - ], - ), - ); - - Widget dialogChild = LayoutBuilder( - builder: (context, constraints) { - final availableBodyWidth = constraints.maxWidth; - final maxWidth = min(availableBodyWidth, tabBodyMaxWidth(context)); - return ConstrainedBox( - constraints: BoxConstraints( - maxWidth: maxWidth, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Flexible(child: contentWidget), - actionsWidget, - ], - ), - ); - }, - ); - - return Dialog( - shape: AvesDialog.shape(context), - child: dialogChild, + TextButton( + key: const Key('button-apply'), + onPressed: () => Navigator.pop(context, Tuple4(_selectedSort, _selectedGroup, _selectedLayout, _reverseSort)), + child: Text(l10n.applyButtonLabel), + ) + ], ); } - Widget _buildRadioListTile(T value, String title, T? Function() get, void Function(T value) set) { - return RadioListTile( - // key is expected by test driver - key: Key(value.toString()), - value: value, - groupValue: get(), - onChanged: (v) => setState(() => set(v as T)), - title: Text( - title, - softWrap: false, - overflow: TextOverflow.fade, - maxLines: 1, - ), - ); - } - - // tabs - - Tab _buildTab( - BuildContext context, - Key key, - IconData icon, - String text, { - Color? color, + Widget _buildSection({ + bool show = true, + required IconData icon, + required String title, + Widget? trailing, + required Map options, + required T value, + required ValueChanged onChanged, + Widget? bottom, }) { - // cannot use `IconTheme` over `TabBar` to change size, - // because `TabBar` does so internally - final textScaleFactor = MediaQuery.textScaleFactorOf(context); - final iconSize = IconTheme.of(context).size! * textScaleFactor; - return Tab( - key: key, - height: tabBarHeight(context), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - icon, - size: iconSize, - color: color, - ), - const SizedBox(height: 4), - Text( - text, - style: TextStyle(color: color), - softWrap: false, - overflow: TextOverflow.fade, - ), - ], + if (options.isEmpty || !show) return const SizedBox(); + + final iconSize = IconTheme.of(context).size! * MediaQuery.textScaleFactorOf(context); + return TooltipTheme( + data: TooltipTheme.of(context).copyWith( + preferBelow: false, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 8), + ConstrainedBox( + constraints: const BoxConstraints( + minHeight: kMinInteractiveDimension, + ), + child: Row( + children: [ + Icon(icon), + const SizedBox(width: 16), + Expanded( + child: HighlightTitle( + title: title, + showHighlight: false, + ), + ), + if (trailing != null) trailing, + ], + ), + ), + Padding( + padding: EdgeInsetsDirectional.only(start: iconSize + 16, end: 12), + child: TextDropdownButton( + values: options.keys.toList(), + valueText: (v) => options[v] ?? v.toString(), + value: value, + onChanged: (v) => setState(() => onChanged(v)), + isExpanded: true, + dropdownColor: Themes.thirdLayerColor(context), + ), + ), + if (bottom != null) + Padding( + padding: EdgeInsetsDirectional.only(start: iconSize + 16), + child: bottom, + ), + ], + ), ), ); } - - bool get canGroup => _selectedSort == EntrySortFactor.date || _selectedSort is ChipSortFactor; - - void _onTabChange() { - if (!canGroup && _tabController.index == groupTabIndex) { - _tabController.index = _tabController.previousIndex; - } - } - - // based on `ListTile` height computation (one line, no subtitle, not dense) - double singleOptionTileHeight(BuildContext context) => 56.0 + Theme.of(context).visualDensity.baseSizeAdjustment.dy; - - double tabBodyMaxWidth(BuildContext context) { - final para = RenderParagraph( - TextSpan(text: _optionLines, style: Theme.of(context).textTheme.subtitle1!), - textDirection: TextDirection.ltr, - textScaleFactor: MediaQuery.textScaleFactorOf(context), - )..layout(const BoxConstraints(), parentUsesSize: true); - final textWidth = para.getMaxIntrinsicWidth(double.infinity); - - // from `RadioListTile` layout - const contentPadding = 32; - const leadingWidth = kMinInteractiveDimension + 8; - return contentPadding + leadingWidth + textWidth; - } - - double tabBodyMaxHeight(BuildContext context) => - [ - sortOptions, - groupOptions, - layoutOptions, - ].map((v) => v.length).fold(0, max) * - singleOptionTileHeight(context); } diff --git a/lib/widgets/dialogs/video_stream_selection_dialog.dart b/lib/widgets/dialogs/video_stream_selection_dialog.dart index 87f5e8435..b89950b49 100644 --- a/lib/widgets/dialogs/video_stream_selection_dialog.dart +++ b/lib/widgets/dialogs/video_stream_selection_dialog.dart @@ -2,6 +2,7 @@ import 'package:aves/model/entry.dart'; import 'package:aves/ref/languages.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/theme/themes.dart'; +import 'package:aves/widgets/common/basic/text_dropdown_button.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/viewer/video/controller.dart'; import 'package:collection/collection.dart'; @@ -123,25 +124,6 @@ class _VideoStreamSelectionDialogState extends State return common; } - DropdownMenuItem _buildMenuItem(StreamSummary? value) { - return DropdownMenuItem( - value: value, - child: Text(_streamName(value)), - ); - } - - Widget _buildSelectedItem(StreamSummary? v) { - return Align( - alignment: AlignmentDirectional.centerStart, - child: Text( - _streamName(v), - softWrap: false, - overflow: TextOverflow.fade, - maxLines: 1, - ), - ); - } - List _buildSection({ required IconData icon, required String title, @@ -162,9 +144,9 @@ class _VideoStreamSelectionDialogState extends State ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: DropdownButton( - items: streams.map(_buildMenuItem).toList(), - selectedItemBuilder: (context) => streams.map(_buildSelectedItem).toList(), + child: TextDropdownButton( + values: streams.whereNotNull().toList(), + valueText: _streamName, value: current, onChanged: streams.length > 1 ? (newValue) => setState(() => setter(newValue)) : null, isExpanded: true, diff --git a/lib/widgets/filter_grids/album_pick.dart b/lib/widgets/filter_grids/album_pick.dart index a2f040eda..3a19327cd 100644 --- a/lib/widgets/filter_grids/album_pick.dart +++ b/lib/widgets/filter_grids/album_pick.dart @@ -7,7 +7,7 @@ import 'package:aves/model/selection.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/basic/menu.dart'; diff --git a/lib/widgets/filter_grids/albums_page.dart b/lib/widgets/filter_grids/albums_page.dart index fb5a47786..39160aee4 100644 --- a/lib/widgets/filter_grids/albums_page.dart +++ b/lib/widgets/filter_grids/albums_page.dart @@ -4,7 +4,7 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; @@ -25,12 +25,12 @@ class AlbumListPage extends StatelessWidget { @override Widget build(BuildContext context) { final source = context.read(); - return Selector>>( - selector: (context, s) => Tuple3(s.albumGroupFactor, s.albumSortFactor, s.pinnedFilters), + return Selector>>( + selector: (context, s) => Tuple4(s.albumGroupFactor, s.albumSortFactor, s.albumSortReverse, s.pinnedFilters), shouldRebuild: (t1, t2) { // `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN` const eq = DeepCollectionEquality(); - return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2) && eq.equals(t1.item3, t2.item3)); + return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2) && eq.equals(t1.item3, t2.item3) && eq.equals(t1.item4, t2.item4)); }, builder: (context, s, child) { return ValueListenableBuilder( @@ -75,7 +75,7 @@ class AlbumListPage extends StatelessWidget { static List> getAlbumGridItems(BuildContext context, CollectionSource source) { final filters = source.rawAlbums.map((album) => AlbumFilter(album, source.getAlbumDisplayName(context, album))).toSet(); - return FilterNavigationPage.sort(settings.albumSortFactor, source, filters); + return FilterNavigationPage.sort(settings.albumSortFactor, settings.albumSortReverse, source, filters); } static Map>> groupToSections(BuildContext context, CollectionSource source, Iterable> sortedMapEntries) { diff --git a/lib/widgets/filter_grids/common/action_delegates/album_set.dart b/lib/widgets/filter_grids/common/action_delegates/album_set.dart index 90f8b0a5a..c2945a12e 100644 --- a/lib/widgets/filter_grids/common/action_delegates/album_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/album_set.dart @@ -9,7 +9,8 @@ import 'package:aves/model/highlight.dart'; import 'package:aves/model/selection.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; +import 'package:aves/model/source/enums/view.dart'; import 'package:aves/services/common/image_op_events.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/services/media/enums.dart'; @@ -44,12 +45,24 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate with @override set sortFactor(ChipSortFactor factor) => settings.albumSortFactor = factor; + @override + bool get sortReverse => settings.albumSortReverse; + + @override + set sortReverse(bool value) => settings.albumSortReverse = value; + @override TileLayout get tileLayout => settings.getTileLayout(AlbumListPage.routeName); @override set tileLayout(TileLayout tileLayout) => settings.setTileLayout(AlbumListPage.routeName, tileLayout); + static const _groupOptions = [ + AlbumChipGroupFactor.importance, + AlbumChipGroupFactor.volume, + AlbumChipGroupFactor.none, + ]; + @override bool isVisible( ChipSetAction action, { @@ -125,31 +138,21 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate with @override Future configureView(BuildContext context) async { - final initialValue = Tuple3( + final initialValue = Tuple4( sortFactor, settings.albumGroupFactor, tileLayout, + sortReverse, ); - final value = await showDialog>( + final value = await showDialog>( context: context, builder: (context) { - final l10n = context.l10n; return TileViewDialog( initialValue: initialValue, - sortOptions: { - ChipSortFactor.date: context.l10n.chipSortDate, - ChipSortFactor.name: context.l10n.chipSortName, - ChipSortFactor.count: context.l10n.chipSortCount, - }, - groupOptions: { - AlbumChipGroupFactor.importance: context.l10n.albumGroupTier, - AlbumChipGroupFactor.volume: context.l10n.albumGroupVolume, - AlbumChipGroupFactor.none: context.l10n.albumGroupNone, - }, - layoutOptions: { - TileLayout.grid: l10n.tileLayoutGrid, - TileLayout.list: l10n.tileLayoutList, - }, + sortOptions: Map.fromEntries(ChipSetActionDelegate.sortOptions.map((v) => MapEntry(v, v.getName(context)))), + groupOptions: Map.fromEntries(_groupOptions.map((v) => MapEntry(v, v.getName(context)))), + layoutOptions: Map.fromEntries(ChipSetActionDelegate.layoutOptions.map((v) => MapEntry(v, v.getName(context)))), + sortOrder: (factor, reverse) => factor.getOrderName(context, reverse), ); }, ); @@ -159,6 +162,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate with sortFactor = value.item1!; settings.albumGroupFactor = value.item2!; tileLayout = value.item3!; + sortReverse = value.item4; } } diff --git a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart index 060559a3c..5455c9a7b 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart @@ -9,7 +9,8 @@ import 'package:aves/model/selection.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; +import 'package:aves/model/source/enums/view.dart'; import 'package:aves/theme/colors.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart'; @@ -37,10 +38,26 @@ abstract class ChipSetActionDelegate with FeedbackMi set sortFactor(ChipSortFactor factor); + bool get sortReverse; + + set sortReverse(bool value); + TileLayout get tileLayout; set tileLayout(TileLayout tileLayout); + static const sortOptions = [ + ChipSortFactor.date, + ChipSortFactor.name, + ChipSortFactor.count, + ChipSortFactor.size, + ]; + + static const layoutOptions = [ + TileLayout.grid, + TileLayout.list, + ]; + bool isVisible( ChipSetAction action, { required AppMode appMode, @@ -194,26 +211,20 @@ abstract class ChipSetActionDelegate with FeedbackMi } Future configureView(BuildContext context) async { - final initialValue = Tuple3( + final initialValue = Tuple4( sortFactor, null, tileLayout, + sortReverse, ); - final value = await showDialog>( + final value = await showDialog>( context: context, builder: (context) { - final l10n = context.l10n; return TileViewDialog( initialValue: initialValue, - sortOptions: { - ChipSortFactor.date: context.l10n.chipSortDate, - ChipSortFactor.name: context.l10n.chipSortName, - ChipSortFactor.count: context.l10n.chipSortCount, - }, - layoutOptions: { - TileLayout.grid: l10n.tileLayoutGrid, - TileLayout.list: l10n.tileLayoutList, - }, + sortOptions: Map.fromEntries(sortOptions.map((v) => MapEntry(v, v.getName(context)))), + layoutOptions: Map.fromEntries(layoutOptions.map((v) => MapEntry(v, v.getName(context)))), + sortOrder: (factor, reverse) => factor.getOrderName(context, reverse), ); }, ); @@ -222,6 +233,7 @@ abstract class ChipSetActionDelegate with FeedbackMi if (value != null && initialValue != value) { sortFactor = value.item1!; tileLayout = value.item3!; + sortReverse = value.item4; } } diff --git a/lib/widgets/filter_grids/common/action_delegates/country_set.dart b/lib/widgets/filter_grids/common/action_delegates/country_set.dart index 7ae5e9baf..1e2ea6a31 100644 --- a/lib/widgets/filter_grids/common/action_delegates/country_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/country_set.dart @@ -1,7 +1,7 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/location.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart'; @@ -19,6 +19,12 @@ class CountryChipSetActionDelegate extends ChipSetActionDelegate @override set sortFactor(ChipSortFactor factor) => settings.countrySortFactor = factor; + @override + bool get sortReverse => settings.countrySortReverse; + + @override + set sortReverse(bool value) => settings.countrySortReverse = value; + @override TileLayout get tileLayout => settings.getTileLayout(CountryListPage.routeName); diff --git a/lib/widgets/filter_grids/common/action_delegates/tag_set.dart b/lib/widgets/filter_grids/common/action_delegates/tag_set.dart index 87d7c3b07..e6403fd04 100644 --- a/lib/widgets/filter_grids/common/action_delegates/tag_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/tag_set.dart @@ -1,7 +1,7 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart'; import 'package:aves/widgets/filter_grids/tags_page.dart'; @@ -19,6 +19,12 @@ class TagChipSetActionDelegate extends ChipSetActionDelegate { @override set sortFactor(ChipSortFactor factor) => settings.tagSortFactor = factor; + @override + bool get sortReverse => settings.tagSortReverse; + + @override + set sortReverse(bool value) => settings.tagSortReverse = value; + @override TileLayout get tileLayout => settings.getTileLayout(TagListPage.routeName); diff --git a/lib/widgets/filter_grids/common/draggable_thumb_label.dart b/lib/widgets/filter_grids/common/draggable_thumb_label.dart index 5276ac819..75ace8365 100644 --- a/lib/widgets/filter_grids/common/draggable_thumb_label.dart +++ b/lib/widgets/filter_grids/common/draggable_thumb_label.dart @@ -1,6 +1,7 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; +import 'package:aves/utils/file_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/grid/draggable_thumb_label.dart'; import 'package:flutter/material.dart'; @@ -22,10 +23,6 @@ class FilterDraggableThumbLabel extends StatelessWid offsetY: offsetY, lineBuilder: (context, filterGridItem) { switch (sortFactor) { - case ChipSortFactor.count: - return [ - context.l10n.itemCount(context.read().count(filterGridItem.filter)), - ]; case ChipSortFactor.date: return [ DraggableThumbLabel.formatMonthThumbLabel(context, filterGridItem.entry?.bestDate), @@ -34,6 +31,15 @@ class FilterDraggableThumbLabel extends StatelessWid return [ filterGridItem.filter.getLabel(context), ]; + case ChipSortFactor.count: + return [ + context.l10n.itemCount(context.read().count(filterGridItem.filter)), + ]; + case ChipSortFactor.size: + final locale = context.l10n.localeName; + return [ + formatFileSize(locale, context.read().size(filterGridItem.filter)), + ]; } }, ); diff --git a/lib/widgets/filter_grids/common/filter_grid_page.dart b/lib/widgets/filter_grids/common/filter_grid_page.dart index ab4c9a984..b93899747 100644 --- a/lib/widgets/filter_grids/common/filter_grid_page.dart +++ b/lib/widgets/filter_grids/common/filter_grid_page.dart @@ -6,7 +6,7 @@ import 'package:aves/model/highlight.dart'; import 'package:aves/model/query.dart'; import 'package:aves/model/selection.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/basic/draggable_scrollbar.dart'; import 'package:aves/widgets/common/basic/insets.dart'; @@ -293,6 +293,7 @@ class _FilterGridContent extends StatelessWidget { child: SectionedFilterListLayoutProvider( sections: visibleSections, showHeaders: showHeaders, + selectable: selectable, tileLayout: tileLayout, scrollableWidth: scrollableWidth, columnCount: columnCount, diff --git a/lib/widgets/filter_grids/common/filter_nav_page.dart b/lib/widgets/filter_grids/common/filter_nav_page.dart index 7c68a3b89..c8fde49e0 100644 --- a/lib/widgets/filter_grids/common/filter_nav_page.dart +++ b/lib/widgets/filter_grids/common/filter_nav_page.dart @@ -1,6 +1,6 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/utils/time_utils.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/common/providers/selection_provider.dart'; @@ -47,11 +47,21 @@ class FilterNavigationPage a, MapEntry b) { + final c = b.value.compareTo(a.value); + return c != 0 ? c : a.key.compareTo(b.key); + } + static int compareFiltersByName(FilterGridItem a, FilterGridItem b) { return a.filter.compareTo(b.filter); } - static List> sort>(ChipSortFactor sortFactor, CollectionSource source, Set filters) { + static List> sort>( + ChipSortFactor sortFactor, + bool reverse, + CollectionSource source, + Set filters, + ) { List> toGridItem(CollectionSource source, Set filters) { return filters .map((filter) => FilterGridItem( @@ -75,6 +85,15 @@ class FilterNavigationPage kv.key).toSet(); allMapEntries = toGridItem(source, filters); break; + case ChipSortFactor.size: + final filtersWithSize = List.of(filters.map((filter) => MapEntry(filter, source.size(filter)))); + filtersWithSize.sort(compareFiltersBySize); + filters = filtersWithSize.map((kv) => kv.key).toSet(); + allMapEntries = toGridItem(source, filters); + break; + } + if (reverse) { + allMapEntries = allMapEntries.reversed.toList(); } return allMapEntries; } diff --git a/lib/widgets/filter_grids/common/filter_tile.dart b/lib/widgets/filter_grids/common/filter_tile.dart index 031c61cdc..00a0685b8 100644 --- a/lib/widgets/filter_grids/common/filter_tile.dart +++ b/lib/widgets/filter_grids/common/filter_tile.dart @@ -3,7 +3,7 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/selection.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/grid/scaling.dart'; diff --git a/lib/widgets/filter_grids/common/list_details.dart b/lib/widgets/filter_grids/common/list_details.dart index 3a7678d38..8bce14199 100644 --- a/lib/widgets/filter_grids/common/list_details.dart +++ b/lib/widgets/filter_grids/common/list_details.dart @@ -6,6 +6,7 @@ import 'package:aves/theme/format.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/constants.dart'; +import 'package:aves/utils/file_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/fx/borders.dart'; import 'package:aves/widgets/filter_grids/common/list_details_theme.dart'; @@ -140,6 +141,10 @@ class FilterListDetails extends StatelessWidget { child: Center(child: leading ?? const SizedBox()), ); + final l10n = context.l10n; + final locale = l10n.localeName; + final source = context.read(); + return IconTheme.merge( data: detailsTheme.captionIconTheme, child: Row( @@ -147,7 +152,7 @@ class FilterListDetails extends StatelessWidget { leading, const SizedBox(width: 8), Text( - context.l10n.itemCount(context.read().count(filter)), + '${l10n.itemCount(source.count(filter))} • ${formatFileSize(locale, source.size(filter))}', style: detailsTheme.captionStyle, strutStyle: Constants.overflowStrutStyle, softWrap: false, diff --git a/lib/widgets/filter_grids/common/section_header.dart b/lib/widgets/filter_grids/common/section_header.dart index 7a9960510..977eb5bb5 100644 --- a/lib/widgets/filter_grids/common/section_header.dart +++ b/lib/widgets/filter_grids/common/section_header.dart @@ -4,10 +4,12 @@ import 'package:flutter/material.dart'; class FilterChipSectionHeader extends StatelessWidget { final ChipSectionKey sectionKey; + final bool selectable; const FilterChipSectionHeader({ super.key, required this.sectionKey, + required this.selectable, }); @override @@ -16,6 +18,7 @@ class FilterChipSectionHeader extends StatelessWidget { sectionKey: sectionKey, leading: sectionKey.leading, title: sectionKey.title, + selectable: selectable, ); } diff --git a/lib/widgets/filter_grids/common/section_layout.dart b/lib/widgets/filter_grids/common/section_layout.dart index 832078152..a00bd9c95 100644 --- a/lib/widgets/filter_grids/common/section_layout.dart +++ b/lib/widgets/filter_grids/common/section_layout.dart @@ -6,10 +6,13 @@ import 'package:aves/widgets/filter_grids/common/section_keys.dart'; import 'package:flutter/material.dart'; class SectionedFilterListLayoutProvider extends SectionedListLayoutProvider> { + final bool selectable; + const SectionedFilterListLayoutProvider({ super.key, required this.sections, required this.showHeaders, + required this.selectable, required super.scrollableWidth, required super.tileLayout, required super.columnCount, @@ -37,6 +40,7 @@ class SectionedFilterListLayoutProvider extends Sect Widget buildHeader(BuildContext context, SectionKey sectionKey, double headerExtent) { return FilterChipSectionHeader>( sectionKey: sectionKey as ChipSectionKey, + selectable: selectable, ); } } diff --git a/lib/widgets/filter_grids/countries_page.dart b/lib/widgets/filter_grids/countries_page.dart index f33f824b0..6f79a6d78 100644 --- a/lib/widgets/filter_grids/countries_page.dart +++ b/lib/widgets/filter_grids/countries_page.dart @@ -2,7 +2,7 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/location.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/model/source/location.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; @@ -23,12 +23,12 @@ class CountryListPage extends StatelessWidget { @override Widget build(BuildContext context) { final source = context.read(); - return Selector>>( - selector: (context, s) => Tuple2(s.countrySortFactor, s.pinnedFilters), + return Selector>>( + selector: (context, s) => Tuple3(s.countrySortFactor, s.countrySortReverse, s.pinnedFilters), shouldRebuild: (t1, t2) { // `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN` const eq = DeepCollectionEquality(); - return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2)); + return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2) && eq.equals(t1.item3, t2.item3)); }, builder: (context, s, child) { return StreamBuilder( @@ -60,7 +60,7 @@ class CountryListPage extends StatelessWidget { List> _getGridItems(CollectionSource source) { final filters = source.sortedCountries.map((location) => LocationFilter(LocationLevel.country, location)).toSet(); - return FilterNavigationPage.sort(settings.countrySortFactor, source, filters); + return FilterNavigationPage.sort(settings.countrySortFactor, settings.countrySortReverse, source, filters); } static Map>> _groupToSections(Iterable> sortedMapEntries) { diff --git a/lib/widgets/filter_grids/tags_page.dart b/lib/widgets/filter_grids/tags_page.dart index 5eae618ac..23520b47f 100644 --- a/lib/widgets/filter_grids/tags_page.dart +++ b/lib/widgets/filter_grids/tags_page.dart @@ -2,7 +2,7 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/model/source/tag.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; @@ -23,12 +23,12 @@ class TagListPage extends StatelessWidget { @override Widget build(BuildContext context) { final source = context.read(); - return Selector>>( - selector: (context, s) => Tuple2(s.tagSortFactor, s.pinnedFilters), + return Selector>>( + selector: (context, s) => Tuple3(s.tagSortFactor, s.tagSortReverse, s.pinnedFilters), shouldRebuild: (t1, t2) { // `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN` const eq = DeepCollectionEquality(); - return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2)); + return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2) && eq.equals(t1.item3, t2.item3)); }, builder: (context, s, child) { return StreamBuilder( @@ -60,7 +60,7 @@ class TagListPage extends StatelessWidget { List> _getGridItems(CollectionSource source) { final filters = source.sortedTags.map(TagFilter.new).toSet(); - return FilterNavigationPage.sort(settings.tagSortFactor, source, filters); + return FilterNavigationPage.sort(settings.tagSortFactor, settings.tagSortReverse, source, filters); } static Map>> _groupToSections(Iterable> sortedMapEntries) { diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index e26607508..61b363e7f 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -9,7 +9,7 @@ import 'package:aves/model/settings/enums/home_page.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/services/analysis_service.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/services/global_search.dart'; @@ -22,8 +22,8 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/search/route.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/search/search_delegate.dart'; -import 'package:aves/widgets/settings/screen_saver_settings_page.dart'; import 'package:aves/widgets/settings/home_widget_settings_page.dart'; +import 'package:aves/widgets/settings/screen_saver_settings_page.dart'; import 'package:aves/widgets/viewer/entry_viewer_page.dart'; import 'package:aves/widgets/viewer/screen_saver_page.dart'; import 'package:aves/widgets/wallpaper_page.dart'; @@ -206,6 +206,7 @@ class _HomePageState extends State { final source = context.read(); await source.init( directory: directory, + canAnalyze: false, ); } } diff --git a/lib/widgets/map/map_page.dart b/lib/widgets/map/map_page.dart index e8ba5b67f..464c7b74e 100644 --- a/lib/widgets/map/map_page.dart +++ b/lib/widgets/map/map_page.dart @@ -107,7 +107,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin void initState() { super.initState(); - if (settings.infoMapStyle.isHeavy) { + if (settings.mapStyle.isHeavy) { _isPageAnimatingNotifier.value = true; Future.delayed(Durations.pageTransitionAnimation * timeDilation).then((_) { if (!mounted) return; @@ -170,7 +170,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin return true; }, child: Selector( - selector: (context, s) => s.infoMapStyle, + selector: (context, s) => s.mapStyle, builder: (context, mapStyle, child) { late Widget scroller; if (mapStyle.isHeavy) { diff --git a/lib/widgets/navigation/drawer/app_drawer.dart b/lib/widgets/navigation/drawer/app_drawer.dart index b2aeaed5f..e418c9540 100644 --- a/lib/widgets/navigation/drawer/app_drawer.dart +++ b/lib/widgets/navigation/drawer/app_drawer.dart @@ -160,14 +160,14 @@ class _AppDrawerState extends State { key: const Key('drawer-about-button'), onPressed: () => goTo(AboutPage.routeName, (_) => const AboutPage()), icon: const Icon(AIcons.info), - label: Text(context.l10n.aboutPageTitle), + label: Text(context.l10n.drawerAboutButton), ), OutlinedButton.icon( // key is expected by test driver key: const Key('drawer-settings-button'), onPressed: () => goTo(SettingsPage.routeName, (_) => const SettingsPage()), icon: const Icon(AIcons.settings), - label: Text(context.l10n.settingsPageTitle), + label: Text(context.l10n.drawerSettingsButton), ), ], ), diff --git a/lib/widgets/navigation/nav_display.dart b/lib/widgets/navigation/nav_display.dart index 6edc5971b..95756de10 100644 --- a/lib/widgets/navigation/nav_display.dart +++ b/lib/widgets/navigation/nav_display.dart @@ -30,11 +30,11 @@ class NavigationDisplay { final l10n = context.l10n; switch (route) { case AlbumListPage.routeName: - return l10n.albumPageTitle; + return l10n.drawerAlbumPage; case CountryListPage.routeName: - return l10n.countryPageTitle; + return l10n.drawerCountryPage; case TagListPage.routeName: - return l10n.tagPageTitle; + return l10n.drawerTagPage; case AppDebugPage.routeName: return 'Debug'; default: diff --git a/lib/widgets/search/search_delegate.dart b/lib/widgets/search/search_delegate.dart index 16b6f13a3..0b23308ab 100644 --- a/lib/widgets/search/search_delegate.dart +++ b/lib/widgets/search/search_delegate.dart @@ -4,6 +4,7 @@ import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/location.dart'; import 'package:aves/model/filters/mime.dart'; +import 'package:aves/model/filters/missing.dart'; import 'package:aves/model/filters/query.dart'; import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/recent.dart'; @@ -96,7 +97,7 @@ class CollectionSearchDelegate extends AvesSearchDelegate { if (upQuery.isEmpty && history.isNotEmpty) _buildFilterRow( context: context, - title: context.l10n.searchSectionRecent, + title: context.l10n.searchRecentSectionTitle, filters: history, ), _buildDateFilters(context, containQuery), @@ -105,6 +106,7 @@ class CollectionSearchDelegate extends AvesSearchDelegate { _buildPlaceFilters(containQuery), _buildTagFilters(containQuery), _buildRatingFilters(context, containQuery), + _buildMetadataFilters(context, containQuery), ], ); }); @@ -136,7 +138,7 @@ class CollectionSearchDelegate extends AvesSearchDelegate { ].where((f) => containQuery(f.getLabel(context))).toList(); return _buildFilterRow( context: context, - title: context.l10n.searchSectionDate, + title: context.l10n.searchDateSectionTitle, filters: filters, ); } @@ -155,7 +157,7 @@ class CollectionSearchDelegate extends AvesSearchDelegate { ..sort(); return _buildFilterRow( context: context, - title: context.l10n.searchSectionAlbums, + title: context.l10n.searchAlbumsSectionTitle, filters: filters, ); }, @@ -166,11 +168,10 @@ class CollectionSearchDelegate extends AvesSearchDelegate { return StreamBuilder( stream: source.eventBus.on(), builder: (context, snapshot) { - final filters = source.sortedCountries.where(containQuery).map((s) => LocationFilter(LocationLevel.country, s)).toList(); return _buildFilterRow( context: context, - title: context.l10n.searchSectionCountries, - filters: filters, + title: context.l10n.searchCountriesSectionTitle, + filters: source.sortedCountries.where(containQuery).map((s) => LocationFilter(LocationLevel.country, s)).toList(), ); }, ); @@ -180,15 +181,10 @@ class CollectionSearchDelegate extends AvesSearchDelegate { return StreamBuilder( stream: source.eventBus.on(), builder: (context, snapshot) { - final filters = source.sortedPlaces.where(containQuery).map((s) => LocationFilter(LocationLevel.place, s)); - final noFilter = LocationFilter(LocationLevel.place, ''); return _buildFilterRow( context: context, - title: context.l10n.searchSectionPlaces, - filters: [ - if (containQuery(noFilter.getLabel(context))) noFilter, - ...filters, - ], + title: context.l10n.searchPlacesSectionTitle, + filters: source.sortedPlaces.where(containQuery).map((s) => LocationFilter(LocationLevel.place, s)).toList(), ); }, ); @@ -198,15 +194,10 @@ class CollectionSearchDelegate extends AvesSearchDelegate { return StreamBuilder( stream: source.eventBus.on(), builder: (context, snapshot) { - final filters = source.sortedTags.where(containQuery).map(TagFilter.new); - final noFilter = TagFilter(''); return _buildFilterRow( context: context, - title: context.l10n.searchSectionTags, - filters: [ - if (containQuery(noFilter.getLabel(context))) noFilter, - ...filters, - ], + title: context.l10n.searchTagsSectionTitle, + filters: source.sortedTags.where(containQuery).map(TagFilter.new).toList(), ); }, ); @@ -215,8 +206,22 @@ class CollectionSearchDelegate extends AvesSearchDelegate { Widget _buildRatingFilters(BuildContext context, _ContainQuery containQuery) { return _buildFilterRow( context: context, - title: context.l10n.searchSectionRating, - filters: [0, 5, 4, 3, 2, 1, -1].map(RatingFilter.new).where((f) => containQuery(f.getLabel(context))).toList(), + title: context.l10n.searchRatingSectionTitle, + filters: [5, 4, 3, 2, 1, -1].map(RatingFilter.new).where((f) => containQuery(f.getLabel(context))).toList(), + ); + } + + Widget _buildMetadataFilters(BuildContext context, _ContainQuery containQuery) { + return _buildFilterRow( + context: context, + title: context.l10n.searchMetadataSectionTitle, + filters: [ + MissingFilter.date, + LocationFilter(LocationLevel.place, ''), + TagFilter(''), + const RatingFilter(0), + MissingFilter.title, + ].where((f) => containQuery(f.getLabel(context))).toList(), ); } diff --git a/lib/widgets/settings/accessibility/accessibility.dart b/lib/widgets/settings/accessibility/accessibility.dart index 418b163c6..74f2c9f52 100644 --- a/lib/widgets/settings/accessibility/accessibility.dart +++ b/lib/widgets/settings/accessibility/accessibility.dart @@ -24,7 +24,7 @@ class AccessibilitySection extends SettingsSection { ); @override - String title(BuildContext context) => context.l10n.settingsSectionAccessibility; + String title(BuildContext context) => context.l10n.settingsAccessibilitySectionTitle; @override FutureOr> tiles(BuildContext context) => [ @@ -44,7 +44,7 @@ class SettingsTileAccessibilityAnimations extends SettingsTile { selector: (context, s) => s.accessibilityAnimations, onSelection: (v) => settings.accessibilityAnimations = v, tileTitle: title(context), - dialogTitle: context.l10n.settingsRemoveAnimationsTitle, + dialogTitle: context.l10n.settingsRemoveAnimationsDialogTitle, ); } diff --git a/lib/widgets/settings/accessibility/time_to_take_action.dart b/lib/widgets/settings/accessibility/time_to_take_action.dart index 49ffe3a90..28de8e12f 100644 --- a/lib/widgets/settings/accessibility/time_to_take_action.dart +++ b/lib/widgets/settings/accessibility/time_to_take_action.dart @@ -37,7 +37,7 @@ class _TimeToTakeActionTileState extends State { selector: (context, s) => s.timeToTakeAction, onSelection: (v) => settings.timeToTakeAction = v, tileTitle: context.l10n.settingsTimeToTakeActionTile, - dialogTitle: context.l10n.settingsTimeToTakeActionTitle, + dialogTitle: context.l10n.settingsTimeToTakeActionDialogTitle, ); }, ); diff --git a/lib/widgets/settings/common/collection_tile.dart b/lib/widgets/settings/common/collection_tile.dart index 7f9a4c0fb..ef68ab0e1 100644 --- a/lib/widgets/settings/common/collection_tile.dart +++ b/lib/widgets/settings/common/collection_tile.dart @@ -38,7 +38,7 @@ class SettingsCollectionTile extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - l10n.collectionPageTitle, + l10n.settingsCollectionTile, style: textTheme.subtitle1!, ), if (hasSubtitle) diff --git a/lib/widgets/settings/common/quick_actions/editor_page.dart b/lib/widgets/settings/common/quick_actions/editor_page.dart index 5195e25fb..367bb1408 100644 --- a/lib/widgets/settings/common/quick_actions/editor_page.dart +++ b/lib/widgets/settings/common/quick_actions/editor_page.dart @@ -158,8 +158,8 @@ class _QuickActionEditorBodyState extends State( @@ -234,8 +234,8 @@ class _QuickActionEditorBodyState extends State( diff --git a/lib/widgets/settings/display/display.dart b/lib/widgets/settings/display/display.dart index 69a4e36f3..f81a641c8 100644 --- a/lib/widgets/settings/display/display.dart +++ b/lib/widgets/settings/display/display.dart @@ -25,7 +25,7 @@ class DisplaySection extends SettingsSection { ); @override - String title(BuildContext context) => context.l10n.settingsSectionDisplay; + String title(BuildContext context) => context.l10n.settingsDisplaySectionTitle; @override FutureOr> tiles(BuildContext context) => [ @@ -39,7 +39,7 @@ class DisplaySection extends SettingsSection { class SettingsTileDisplayThemeBrightness extends SettingsTile { @override - String title(BuildContext context) => context.l10n.settingsThemeBrightness; + String title(BuildContext context) => context.l10n.settingsThemeBrightnessTile; @override Widget build(BuildContext context) => SettingsSelectionListTile( @@ -48,7 +48,7 @@ class SettingsTileDisplayThemeBrightness extends SettingsTile { selector: (context, s) => s.themeBrightness, onSelection: (v) => settings.themeBrightness = v, tileTitle: title(context), - dialogTitle: context.l10n.settingsThemeBrightness, + dialogTitle: context.l10n.settingsThemeBrightnessDialogTitle, ); } @@ -99,6 +99,6 @@ class SettingsTileDisplayDisplayRefreshRateMode extends SettingsTile { selector: (context, s) => s.displayRefreshRateMode, onSelection: (v) => settings.displayRefreshRateMode = v, tileTitle: title(context), - dialogTitle: context.l10n.settingsDisplayRefreshRateModeTitle, + dialogTitle: context.l10n.settingsDisplayRefreshRateModeDialogTitle, ); } diff --git a/lib/widgets/settings/language/language.dart b/lib/widgets/settings/language/language.dart index 631981269..a40b735f2 100644 --- a/lib/widgets/settings/language/language.dart +++ b/lib/widgets/settings/language/language.dart @@ -26,7 +26,7 @@ class LanguageSection extends SettingsSection { ); @override - String title(BuildContext context) => context.l10n.settingsSectionLanguage; + String title(BuildContext context) => context.l10n.settingsLanguageSectionTitle; @override FutureOr> tiles(BuildContext context) => [ @@ -38,7 +38,7 @@ class LanguageSection extends SettingsSection { class SettingsTileLanguageLocale extends SettingsTile { @override - String title(BuildContext context) => context.l10n.settingsLanguage; + String title(BuildContext context) => context.l10n.settingsLanguageTile; @override Widget build(BuildContext context) => const LocaleTile(); @@ -55,7 +55,7 @@ class SettingsTileLanguageCoordinateFormat extends SettingsTile { selector: (context, s) => s.coordinateFormat, onSelection: (v) => settings.coordinateFormat = v, tileTitle: title(context), - dialogTitle: context.l10n.settingsCoordinateFormatTitle, + dialogTitle: context.l10n.settingsCoordinateFormatDialogTitle, optionSubtitleBuilder: (value) => value.format(context.l10n, Constants.pointNemo), ); } @@ -71,6 +71,6 @@ class SettingsTileLanguageUnitSystem extends SettingsTile { selector: (context, s) => s.unitSystem, onSelection: (v) => settings.unitSystem = v, tileTitle: title(context), - dialogTitle: context.l10n.settingsUnitSystemTitle, + dialogTitle: context.l10n.settingsUnitSystemDialogTitle, ); } diff --git a/lib/widgets/settings/language/locale.dart b/lib/widgets/settings/language/locale.dart index b3ffbb93c..5b04d3e97 100644 --- a/lib/widgets/settings/language/locale.dart +++ b/lib/widgets/settings/language/locale.dart @@ -23,7 +23,7 @@ class LocaleTile extends StatelessWidget { return ListTile( // key is expected by test driver key: const Key('tile-language'), - title: Text(context.l10n.settingsLanguage), + title: Text(context.l10n.settingsLanguageTile), subtitle: Selector( selector: (context, s) => settings.locale, builder: (context, locale, child) { @@ -78,7 +78,7 @@ class _LocaleSelectionPageState extends State { return MediaQueryDataProvider( child: Scaffold( appBar: AppBar( - title: Text(context.l10n.settingsLanguage), + title: Text(context.l10n.settingsLanguagePageTitle), ), body: SafeArea( child: ValueListenableBuilder( diff --git a/lib/widgets/settings/language/locales.dart b/lib/widgets/settings/language/locales.dart index 82e59231c..03236848e 100644 --- a/lib/widgets/settings/language/locales.dart +++ b/lib/widgets/settings/language/locales.dart @@ -1,8 +1,10 @@ // this class is kept minimal, without import // so it can be reused in driver tests + class SupportedLocales { static const languagesByLanguageCode = { 'de': 'Deutsch', + 'el': 'Ελληνικά', 'en': 'English', 'es': 'Español (México)', 'fr': 'Français', diff --git a/lib/widgets/settings/navigation/confirmation_dialogs.dart b/lib/widgets/settings/navigation/confirmation_dialogs.dart index 3ab1d5cf1..95172f23b 100644 --- a/lib/widgets/settings/navigation/confirmation_dialogs.dart +++ b/lib/widgets/settings/navigation/confirmation_dialogs.dart @@ -20,17 +20,17 @@ class ConfirmationDialogPage extends StatelessWidget { SettingsSwitchListTile( selector: (context, s) => s.confirmMoveUndatedItems, onChanged: (v) => settings.confirmMoveUndatedItems = v, - title: l10n.settingsConfirmationDialogMoveUndatedItems, + title: l10n.settingsConfirmationBeforeMoveUndatedItems, ), SettingsSwitchListTile( selector: (context, s) => s.confirmMoveToBin, onChanged: (v) => settings.confirmMoveToBin = v, - title: l10n.settingsConfirmationDialogMoveToBinItems, + title: l10n.settingsConfirmationBeforeMoveToBinItems, ), SettingsSwitchListTile( selector: (context, s) => s.confirmDeleteForever, onChanged: (v) => settings.confirmDeleteForever = v, - title: l10n.settingsConfirmationDialogDeleteItems, + title: l10n.settingsConfirmationBeforeDeleteItems, ), const Divider(height: 32), SettingsSwitchListTile( diff --git a/lib/widgets/settings/navigation/drawer.dart b/lib/widgets/settings/navigation/drawer.dart index 2e8d59e06..bb17b8fbf 100644 --- a/lib/widgets/settings/navigation/drawer.dart +++ b/lib/widgets/settings/navigation/drawer.dart @@ -91,7 +91,7 @@ class _NavigationDrawerEditorPageState extends State length: tabs.length, child: Scaffold( appBar: AppBar( - title: Text(l10n.settingsNavigationDrawerEditorTitle), + title: Text(l10n.settingsNavigationDrawerEditorPageTitle), bottom: TabBar( tabs: tabs.map((t) => t.item1).toList(), ), diff --git a/lib/widgets/settings/navigation/navigation.dart b/lib/widgets/settings/navigation/navigation.dart index 530feaeb3..5ed1ba76b 100644 --- a/lib/widgets/settings/navigation/navigation.dart +++ b/lib/widgets/settings/navigation/navigation.dart @@ -26,7 +26,7 @@ class NavigationSection extends SettingsSection { ); @override - String title(BuildContext context) => context.l10n.settingsSectionNavigation; + String title(BuildContext context) => context.l10n.settingsNavigationSectionTitle; @override FutureOr> tiles(BuildContext context) => [ @@ -41,7 +41,7 @@ class NavigationSection extends SettingsSection { class SettingsTileNavigationHomePage extends SettingsTile { @override - String title(BuildContext context) => context.l10n.settingsHome; + String title(BuildContext context) => context.l10n.settingsHomeTile; @override Widget build(BuildContext context) => SettingsSelectionListTile( @@ -50,7 +50,7 @@ class SettingsTileNavigationHomePage extends SettingsTile { selector: (context, s) => s.homePage, onSelection: (v) => settings.homePage = v, tileTitle: title(context), - dialogTitle: context.l10n.settingsHome, + dialogTitle: context.l10n.settingsHomeDialogTitle, ); } @@ -80,7 +80,7 @@ class SettingsTileNavigationDrawer extends SettingsTile { class SettingsTileNavigationConfirmationDialog extends SettingsTile { @override - String title(BuildContext context) => context.l10n.settingsConfirmationDialogTile; + String title(BuildContext context) => context.l10n.settingsConfirmationTile; @override Widget build(BuildContext context) => SettingsSubPageTile( @@ -101,7 +101,7 @@ class SettingsTileNavigationKeepScreenOn extends SettingsTile { selector: (context, s) => s.keepScreenOn, onSelection: (v) => settings.keepScreenOn = v, tileTitle: title(context), - dialogTitle: context.l10n.settingsKeepScreenOnTitle, + dialogTitle: context.l10n.settingsKeepScreenOnDialogTitle, ); } diff --git a/lib/widgets/settings/privacy/access_grants.dart b/lib/widgets/settings/privacy/access_grants.dart index bd63836d6..b35ffdd18 100644 --- a/lib/widgets/settings/privacy/access_grants.dart +++ b/lib/widgets/settings/privacy/access_grants.dart @@ -31,7 +31,7 @@ class _StorageAccessPageState extends State { return MediaQueryDataProvider( child: Scaffold( appBar: AppBar( - title: Text(context.l10n.settingsStorageAccessTitle), + title: Text(context.l10n.settingsStorageAccessPageTitle), ), body: SafeArea( child: FutureBuilder>( diff --git a/lib/widgets/settings/privacy/hidden_items.dart b/lib/widgets/settings/privacy/hidden_items.dart index 8ee06d60b..af92aa1f6 100644 --- a/lib/widgets/settings/privacy/hidden_items.dart +++ b/lib/widgets/settings/privacy/hidden_items.dart @@ -24,11 +24,11 @@ class HiddenItemsPage extends StatelessWidget { final l10n = context.l10n; final tabs = >[ Tuple2( - Tab(text: l10n.settingsHiddenFiltersTitle), + Tab(text: l10n.settingsHiddenItemsTabFilters), const _HiddenFilters(), ), Tuple2( - Tab(text: l10n.settingsHiddenPathsTitle), + Tab(text: l10n.settingsHiddenItemsTabPaths), const _HiddenPaths(), ), ]; @@ -38,7 +38,7 @@ class HiddenItemsPage extends StatelessWidget { length: tabs.length, child: Scaffold( appBar: AppBar( - title: Text(l10n.settingsHiddenItemsTitle), + title: Text(l10n.settingsHiddenItemsPageTitle), bottom: TabBar( tabs: tabs.map((t) => t.item1).toList(), ), diff --git a/lib/widgets/settings/privacy/privacy.dart b/lib/widgets/settings/privacy/privacy.dart index c5be45b24..4a07263a0 100644 --- a/lib/widgets/settings/privacy/privacy.dart +++ b/lib/widgets/settings/privacy/privacy.dart @@ -25,7 +25,7 @@ class PrivacySection extends SettingsSection { ); @override - String title(BuildContext context) => context.l10n.settingsSectionPrivacy; + String title(BuildContext context) => context.l10n.settingsPrivacySectionTitle; @override FutureOr> tiles(BuildContext context) async { diff --git a/lib/widgets/settings/screen_saver_settings_page.dart b/lib/widgets/settings/screen_saver_settings_page.dart index bc7c17fe6..e7618c257 100644 --- a/lib/widgets/settings/screen_saver_settings_page.dart +++ b/lib/widgets/settings/screen_saver_settings_page.dart @@ -38,7 +38,7 @@ class ScreenSaverSettingsPage extends StatelessWidget { selector: (context, s) => s.screenSaverTransition, onSelection: (v) => settings.screenSaverTransition = v, tileTitle: l10n.settingsSlideshowTransitionTile, - dialogTitle: l10n.settingsSlideshowTransitionTitle, + dialogTitle: l10n.settingsSlideshowTransitionDialogTitle, ), SettingsSelectionListTile( values: SlideshowInterval.values, @@ -46,7 +46,7 @@ class ScreenSaverSettingsPage extends StatelessWidget { selector: (context, s) => s.screenSaverInterval, onSelection: (v) => settings.screenSaverInterval = v, tileTitle: l10n.settingsSlideshowIntervalTile, - dialogTitle: l10n.settingsSlideshowIntervalTitle, + dialogTitle: l10n.settingsSlideshowIntervalDialogTitle, ), SettingsSelectionListTile( values: SlideshowVideoPlayback.values, @@ -54,7 +54,7 @@ class ScreenSaverSettingsPage extends StatelessWidget { selector: (context, s) => s.screenSaverVideoPlayback, onSelection: (v) => settings.screenSaverVideoPlayback = v, tileTitle: l10n.settingsSlideshowVideoPlaybackTile, - dialogTitle: l10n.settingsSlideshowVideoPlaybackTitle, + dialogTitle: l10n.settingsSlideshowVideoPlaybackDialogTitle, ), Selector>( selector: (context, s) => s.screenSaverCollectionFilters, diff --git a/lib/widgets/settings/settings_page.dart b/lib/widgets/settings/settings_page.dart index d3f2df1a2..2f7a3e685 100644 --- a/lib/widgets/settings/settings_page.dart +++ b/lib/widgets/settings/settings_page.dart @@ -145,7 +145,7 @@ class _SettingsPageState extends State with FeedbackMixin { final toExport = await showDialog>( context: context, builder: (context) => AppExportItemSelectionDialog( - title: context.l10n.settingsActionExport, + title: context.l10n.settingsActionExportDialogTitle, ), ); if (toExport == null || toExport.isEmpty) return; @@ -202,7 +202,7 @@ class _SettingsPageState extends State with FeedbackMixin { final toImport = await showDialog>( context: context, builder: (context) => AppExportItemSelectionDialog( - title: context.l10n.settingsActionImport, + title: context.l10n.settingsActionImportDialogTitle, selectableItems: importable.keys.toSet(), ), ); diff --git a/lib/widgets/settings/thumbnails/collection_actions_editor.dart b/lib/widgets/settings/thumbnails/collection_actions_editor.dart index 7b6474159..5bacb4ce5 100644 --- a/lib/widgets/settings/thumbnails/collection_actions_editor.dart +++ b/lib/widgets/settings/thumbnails/collection_actions_editor.dart @@ -44,7 +44,7 @@ class CollectionActionEditorPage extends StatelessWidget { length: tabs.length, child: Scaffold( appBar: AppBar( - title: Text(context.l10n.settingsCollectionQuickActionEditorTitle), + title: Text(context.l10n.settingsCollectionQuickActionEditorPageTitle), bottom: TabBar( tabs: tabs.map((t) => t.item1).toList(), ), diff --git a/lib/widgets/settings/thumbnails/overlay.dart b/lib/widgets/settings/thumbnails/overlay.dart index 72ea7e997..928a72638 100644 --- a/lib/widgets/settings/thumbnails/overlay.dart +++ b/lib/widgets/settings/thumbnails/overlay.dart @@ -19,7 +19,7 @@ class ThumbnailOverlayPage extends StatelessWidget { return Scaffold( appBar: AppBar( - title: Text(context.l10n.settingsThumbnailOverlayTitle), + title: Text(context.l10n.settingsThumbnailOverlayPageTitle), ), body: SafeArea( child: ListView( diff --git a/lib/widgets/settings/thumbnails/thumbnails.dart b/lib/widgets/settings/thumbnails/thumbnails.dart index 60f2c3729..040f660c0 100644 --- a/lib/widgets/settings/thumbnails/thumbnails.dart +++ b/lib/widgets/settings/thumbnails/thumbnails.dart @@ -20,7 +20,7 @@ class ThumbnailsSection extends SettingsSection { ); @override - String title(BuildContext context) => context.l10n.settingsSectionThumbnails; + String title(BuildContext context) => context.l10n.settingsThumbnailSectionTitle; @override List tiles(BuildContext context) => [ diff --git a/lib/widgets/settings/video/controls.dart b/lib/widgets/settings/video/controls.dart index 17711bff5..cee40f19e 100644 --- a/lib/widgets/settings/video/controls.dart +++ b/lib/widgets/settings/video/controls.dart @@ -14,7 +14,7 @@ class VideoControlsPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(context.l10n.settingsVideoControlsTitle), + title: Text(context.l10n.settingsVideoControlsPageTitle), ), body: SafeArea( child: ListView( @@ -25,7 +25,7 @@ class VideoControlsPage extends StatelessWidget { selector: (context, s) => s.videoControls, onSelection: (v) => settings.videoControls = v, tileTitle: context.l10n.settingsVideoButtonsTile, - dialogTitle: context.l10n.settingsVideoButtonsTitle, + dialogTitle: context.l10n.settingsVideoButtonsDialogTitle, ), SettingsSwitchListTile( selector: (context, s) => s.videoGestureDoubleTapTogglePlay, diff --git a/lib/widgets/settings/video/subtitle_theme.dart b/lib/widgets/settings/video/subtitle_theme.dart index 436962e1d..c60ab865f 100644 --- a/lib/widgets/settings/video/subtitle_theme.dart +++ b/lib/widgets/settings/video/subtitle_theme.dart @@ -16,7 +16,7 @@ class SubtitleThemePage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(context.l10n.settingsSubtitleThemeTitle), + title: Text(context.l10n.settingsSubtitleThemePageTitle), ), body: SafeArea( child: Consumer( @@ -38,7 +38,7 @@ class SubtitleThemePage extends StatelessWidget { selector: (context, s) => s.subtitleTextAlignment, onSelection: (v) => settings.subtitleTextAlignment = v, tileTitle: context.l10n.settingsSubtitleThemeTextAlignmentTile, - dialogTitle: context.l10n.settingsSubtitleThemeTextAlignmentTitle, + dialogTitle: context.l10n.settingsSubtitleThemeTextAlignmentDialogTitle, ), SliderListTile( title: context.l10n.settingsSubtitleThemeTextSize, diff --git a/lib/widgets/settings/video/video.dart b/lib/widgets/settings/video/video.dart index 4e5636c5a..d1711ac51 100644 --- a/lib/widgets/settings/video/video.dart +++ b/lib/widgets/settings/video/video.dart @@ -32,7 +32,7 @@ class VideoSection extends SettingsSection { ); @override - String title(BuildContext context) => context.l10n.settingsSectionVideo; + String title(BuildContext context) => context.l10n.settingsVideoSectionTitle; @override FutureOr> tiles(BuildContext context) async { @@ -94,7 +94,7 @@ class SettingsTileVideoLoopMode extends SettingsTile { selector: (context, s) => s.videoLoopMode, onSelection: (v) => settings.videoLoopMode = v, tileTitle: title(context), - dialogTitle: context.l10n.settingsVideoLoopModeTitle, + dialogTitle: context.l10n.settingsVideoLoopModeDialogTitle, ); } diff --git a/lib/widgets/settings/viewer/overlay.dart b/lib/widgets/settings/viewer/overlay.dart index b41f34d6c..c8cae6351 100644 --- a/lib/widgets/settings/viewer/overlay.dart +++ b/lib/widgets/settings/viewer/overlay.dart @@ -14,7 +14,7 @@ class ViewerOverlayPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(context.l10n.settingsViewerOverlayTitle), + title: Text(context.l10n.settingsViewerOverlayPageTitle), ), body: SafeArea( child: ListView( diff --git a/lib/widgets/settings/viewer/slideshow.dart b/lib/widgets/settings/viewer/slideshow.dart index e29504e28..a266d13f3 100644 --- a/lib/widgets/settings/viewer/slideshow.dart +++ b/lib/widgets/settings/viewer/slideshow.dart @@ -16,7 +16,7 @@ class ViewerSlideshowPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(context.l10n.settingsViewerSlideshowTitle), + title: Text(context.l10n.settingsViewerSlideshowPageTitle), ), body: SafeArea( child: ListView( @@ -42,7 +42,7 @@ class ViewerSlideshowPage extends StatelessWidget { selector: (context, s) => s.slideshowTransition, onSelection: (v) => settings.slideshowTransition = v, tileTitle: context.l10n.settingsSlideshowTransitionTile, - dialogTitle: context.l10n.settingsSlideshowTransitionTitle, + dialogTitle: context.l10n.settingsSlideshowTransitionDialogTitle, ), SettingsSelectionListTile( values: SlideshowInterval.values, @@ -50,7 +50,7 @@ class ViewerSlideshowPage extends StatelessWidget { selector: (context, s) => s.slideshowInterval, onSelection: (v) => settings.slideshowInterval = v, tileTitle: context.l10n.settingsSlideshowIntervalTile, - dialogTitle: context.l10n.settingsSlideshowIntervalTitle, + dialogTitle: context.l10n.settingsSlideshowIntervalDialogTitle, ), SettingsSelectionListTile( values: SlideshowVideoPlayback.values, @@ -58,7 +58,7 @@ class ViewerSlideshowPage extends StatelessWidget { selector: (context, s) => s.slideshowVideoPlayback, onSelection: (v) => settings.slideshowVideoPlayback = v, tileTitle: context.l10n.settingsSlideshowVideoPlaybackTile, - dialogTitle: context.l10n.settingsSlideshowVideoPlaybackTitle, + dialogTitle: context.l10n.settingsSlideshowVideoPlaybackDialogTitle, ), ], ), diff --git a/lib/widgets/settings/viewer/viewer.dart b/lib/widgets/settings/viewer/viewer.dart index ef348f7dc..31abd3b5e 100644 --- a/lib/widgets/settings/viewer/viewer.dart +++ b/lib/widgets/settings/viewer/viewer.dart @@ -27,7 +27,7 @@ class ViewerSection extends SettingsSection { ); @override - String title(BuildContext context) => context.l10n.settingsSectionViewer; + String title(BuildContext context) => context.l10n.settingsViewerSectionTitle; @override FutureOr> tiles(BuildContext context) async { diff --git a/lib/widgets/settings/viewer/viewer_actions_editor.dart b/lib/widgets/settings/viewer/viewer_actions_editor.dart index d98fbc985..b70b10344 100644 --- a/lib/widgets/settings/viewer/viewer_actions_editor.dart +++ b/lib/widgets/settings/viewer/viewer_actions_editor.dart @@ -31,7 +31,7 @@ class ViewerActionEditorPage extends StatelessWidget { @override Widget build(BuildContext context) { return QuickActionEditorPage( - title: context.l10n.settingsViewerQuickActionEditorTitle, + title: context.l10n.settingsViewerQuickActionEditorPageTitle, bannerText: context.l10n.settingsViewerQuickActionEditorBanner, allAvailableActions: allAvailableActions, actionIcon: (action) => action.getIcon(), diff --git a/lib/widgets/stats/date/histogram.dart b/lib/widgets/stats/date/histogram.dart new file mode 100644 index 000000000..032822536 --- /dev/null +++ b/lib/widgets/stats/date/histogram.dart @@ -0,0 +1,396 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:aves/model/entry.dart'; +import 'package:aves/model/filters/date.dart'; +import 'package:aves/theme/durations.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; +import 'package:aves/widgets/stats/date/axis.dart'; +import 'package:charts_flutter/flutter.dart' as charts; +import 'package:collection/collection.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class Histogram extends StatefulWidget { + final Set entries; + final Duration animationDuration; + final FilterCallback onFilterSelection; + + const Histogram({ + super.key, + required this.entries, + required this.animationDuration, + required this.onFilterSelection, + }); + + @override + State createState() => _HistogramState(); +} + +class _HistogramState extends State with AutomaticKeepAliveClientMixin { + DateLevel _level = DateLevel.y; + DateTime? _firstDate, _lastDate; + final Map _entryCountPerDate = {}; + final ValueNotifier<_EntryByDate?> _selection = ValueNotifier(null); + List<_EntryByDate>? _seriesData; + late Future?> _interpolatedDataLoader; + late Future _areaChartLoader; + + static const histogramHeight = 200.0; + + Duration get animationDuration => widget.animationDuration; + + @override + void initState() { + super.initState(); + + final entriesByDateDescending = List.of(widget.entries)..sort(AvesEntry.compareByDate); + var lastDate = entriesByDateDescending.firstWhereOrNull((entry) => entry.bestDate != null)?.bestDate; + var firstDate = entriesByDateDescending.lastWhereOrNull((entry) => entry.bestDate != null)?.bestDate; + + if (lastDate != null && firstDate != null) { + final rangeDays = lastDate.difference(firstDate).inDays; + if (rangeDays > 1) { + if (rangeDays <= 31) { + _level = DateLevel.ymd; + } else if (rangeDays <= 365) { + _level = DateLevel.ym; + } + + late DateTime Function(DateTime) normalizeDate; + switch (_level) { + case DateLevel.ymd: + normalizeDate = (v) => DateTime(v.year, v.month, v.day); + break; + case DateLevel.ym: + normalizeDate = (v) => DateTime(v.year, v.month); + break; + default: + normalizeDate = (v) => DateTime(v.year); + break; + } + _firstDate = normalizeDate(firstDate); + _lastDate = normalizeDate(lastDate); + + final dates = entriesByDateDescending.map((entry) => entry.bestDate).whereNotNull(); + _entryCountPerDate.addAll(groupBy(dates, normalizeDate).map((k, v) => MapEntry(k, v.length))); + if (_entryCountPerDate.isNotEmpty) { + // discrete points + _seriesData = _entryCountPerDate.entries.map((kv) { + return _EntryByDate(date: kv.key, entryCount: kv.value); + }).toList(); + + // smooth curve + _interpolatedDataLoader = compute<_DataInterpolationArg, List<_EntryByDate>?>( + _computeInterpolatedData, + _DataInterpolationArg( + firstDate: _firstDate, + lastDate: _lastDate, + level: _level, + entryCountPerDate: _entryCountPerDate, + )); + _areaChartLoader = _interpolatedDataLoader.then((_) => Future.delayed(animationDuration * timeDilation)); + } + } + } + } + + static List<_EntryByDate>? _computeInterpolatedData(_DataInterpolationArg arg) { + final firstDate = arg.firstDate; + final lastDate = arg.lastDate; + final level = arg.level; + final entryCountPerDate = arg.entryCountPerDate; + + if (firstDate == null || lastDate == null) return null; + + final xRange = lastDate.difference(firstDate); + final xRangeInMillis = xRange.inMilliseconds; + late int xCount; + late DateTime Function(DateTime date) incrementDate; + switch (level) { + case DateLevel.ymd: + xCount = xRange.inDays; + incrementDate = (date) => DateTime(date.year, date.month, date.day + 1); + break; + case DateLevel.ym: + xCount = (xRange.inDays / 30.5).round(); + incrementDate = (date) => DateTime(date.year, date.month + 1); + break; + default: + xCount = lastDate.year - firstDate.year; + incrementDate = (date) => DateTime(date.year + 1); + break; + } + final yMax = entryCountPerDate.values.reduce(max).toDouble(); + final xInterval = yMax / xCount; + final controlPoints = []; + var date = firstDate; + for (int i = 0; i <= xCount; i++) { + controlPoints.add(Offset(i * xInterval, (entryCountPerDate[date] ?? 0).toDouble())); + date = incrementDate(date); + } + final interpolatedPoints = controlPoints.length > 3 ? CatmullRomSpline(controlPoints).generateSamples().map((sample) => sample.value).toList() : controlPoints; + final interpolatedData = interpolatedPoints.map((p) { + final date = firstDate.add(Duration(milliseconds: p.dx * xRangeInMillis ~/ yMax)); + final entryCount = p.dy.clamp(0, yMax); + return _EntryByDate(date: date, entryCount: entryCount); + }).toList(); + + return interpolatedData; + } + + @override + Widget build(BuildContext context) { + super.build(context); + + if (_seriesData == null) return const SizedBox(); + + return FutureBuilder?>( + future: _interpolatedDataLoader, + builder: (context, snapshot) { + final interpolatedData = snapshot.data; + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 8), + height: histogramHeight, + child: AnimatedSwitcher( + duration: animationDuration, + child: interpolatedData != null + ? Stack( + children: [ + FutureBuilder( + future: _areaChartLoader, + builder: (context, snapshot) { + Widget child = const SizedBox(); + if (snapshot.connectionState == ConnectionState.done) { + child = _buildChart(context, interpolatedData, isInterpolated: true, isArea: true); + } + return AnimatedSwitcher( + duration: animationDuration, + child: child, + ); + }, + ), + _buildChart(context, interpolatedData, isInterpolated: true, isArea: false), + _buildChart(context, _seriesData!, isInterpolated: false, isArea: false), + ], + ) + : const SizedBox(), + ), + ), + _buildSelectionRow(), + ], + ); + }, + ); + } + + Widget _buildChart(BuildContext context, List<_EntryByDate> seriesData, {required bool isInterpolated, required bool isArea}) { + final drawArea = isInterpolated && isArea; + final drawLine = isInterpolated && !isArea; + final drawPoints = !isInterpolated; + + final colorScheme = Theme.of(context).colorScheme; + final accentColor = colorScheme.secondary; + final axisColor = charts.ColorUtil.fromDartColor(drawPoints ? colorScheme.onPrimary.withOpacity(.9) : Colors.transparent); + final measureLineColor = charts.ColorUtil.fromDartColor(drawPoints ? colorScheme.onPrimary.withOpacity(.1) : Colors.transparent); + final histogramLineColor = charts.ColorUtil.fromDartColor(drawLine ? accentColor : Colors.white); + final histogramPointStrikeColor = axisColor; + final histogramPointFillColor = charts.ColorUtil.fromDartColor(colorScheme.background); + + final series = [ + if (drawLine || drawArea) + charts.Series<_EntryByDate, DateTime>( + id: 'curve', + data: seriesData, + domainFn: (d, i) => d.date, + measureFn: (d, i) => d.entryCount, + colorFn: (d, i) => histogramLineColor, + ), + if (drawPoints && !drawArea) + charts.Series<_EntryByDate, DateTime>( + id: 'points', + data: seriesData, + domainFn: (d, i) => d.date, + measureFn: (d, i) => d.entryCount, + colorFn: (d, i) => histogramPointStrikeColor, + fillColorFn: (d, i) => histogramPointFillColor, + )..setAttribute(charts.rendererIdKey, 'customPoint'), + ]; + + final locale = context.l10n.localeName; + final timeAxisSpec = _firstDate != null && _lastDate != null + ? TimeAxisSpec.forLevel( + locale: locale, + level: _level, + first: _firstDate!, + last: _lastDate!, + ) + : null; + final measureFormat = NumberFormat.decimalPattern(locale); + + final domainAxis = charts.DateTimeAxisSpec( + renderSpec: charts.SmallTickRendererSpec( + labelStyle: charts.TextStyleSpec(color: axisColor), + lineStyle: charts.LineStyleSpec(color: axisColor), + ), + tickProviderSpec: timeAxisSpec != null && timeAxisSpec.tickSpecs.isNotEmpty ? charts.StaticDateTimeTickProviderSpec(timeAxisSpec.tickSpecs) : null, + ); + + Widget chart = charts.TimeSeriesChart( + series, + animate: false, + animationDuration: animationDuration, + domainAxis: domainAxis, + primaryMeasureAxis: charts.NumericAxisSpec( + renderSpec: charts.GridlineRendererSpec( + labelStyle: charts.TextStyleSpec(color: axisColor), + lineStyle: charts.LineStyleSpec(color: measureLineColor), + ), + tickFormatterSpec: charts.BasicNumericTickFormatterSpec((v) { + // localize and hide 0 + return (v == null || v == 0) ? '' : measureFormat.format(v); + }), + ), + defaultRenderer: charts.LineRendererConfig( + includeArea: drawArea, + areaOpacity: 1, + ), + customSeriesRenderers: [ + charts.PointRendererConfig( + customRendererId: 'customPoint', + strokeWidthPx: 2, + symbolRenderer: _CircleSymbolRenderer(isSolid: false), + ), + ], + defaultInteractions: false, + behaviors: drawPoints + ? [ + charts.SelectNearest(), + charts.LinePointHighlighter( + defaultRadiusPx: 8, + radiusPaddingPx: 2, + showHorizontalFollowLine: charts.LinePointHighlighterFollowLineType.nearest, + showVerticalFollowLine: charts.LinePointHighlighterFollowLineType.nearest, + ), + ] + : null, + selectionModels: [ + charts.SelectionModelConfig( + changedListener: (model) => _selection.value = model.selectedDatum.firstOrNull?.datum as _EntryByDate?, + ) + ], + ); + if (drawArea) { + chart = ShaderMask( + shaderCallback: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + accentColor.withOpacity(0), + accentColor, + ], + ).createShader, + blendMode: BlendMode.srcIn, + child: chart, + ); + } + return chart; + } + + Widget _buildSelectionRow() { + final locale = context.l10n.localeName; + final numberFormat = NumberFormat.decimalPattern(locale); + + return ValueListenableBuilder<_EntryByDate?>( + valueListenable: _selection, + builder: (context, selection, child) { + late Widget child; + if (selection == null) { + child = const SizedBox(); + } else { + final filter = DateFilter(_level, selection.date); + final count = selection.entryCount; + child = Padding( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + AvesFilterChip( + filter: filter, + onTap: widget.onFilterSelection, + ), + const Spacer(), + Text( + numberFormat.format(count), + style: TextStyle( + color: Theme.of(context).textTheme.caption!.color, + ), + textAlign: TextAlign.end, + ), + ], + ), + ); + } + + return AnimatedSwitcher( + duration: context.read().formTransition, + switchInCurve: Curves.easeInOutCubic, + switchOutCurve: Curves.easeInOutCubic, + transitionBuilder: (child, animation) => FadeTransition( + opacity: animation, + child: SizeTransition( + sizeFactor: animation, + axisAlignment: -1, + child: child, + ), + ), + child: child, + ); + }, + ); + } + + @override + bool get wantKeepAlive => true; +} + +@immutable +class _EntryByDate extends Equatable { + final DateTime date; + final num entryCount; + + @override + List get props => [date, entryCount]; + + const _EntryByDate({ + required this.date, + required this.entryCount, + }); +} + +class _CircleSymbolRenderer extends charts.CircleSymbolRenderer { + _CircleSymbolRenderer({super.isSolid = true}); + + @override + charts.Color? getSolidFillColor(charts.Color? fillColor) => fillColor; +} + +class _DataInterpolationArg { + final DateLevel level; + final DateTime? firstDate, lastDate; + final Map entryCountPerDate; + + const _DataInterpolationArg({ + required this.level, + required this.firstDate, + required this.lastDate, + required this.entryCountPerDate, + }); +} diff --git a/lib/widgets/stats/filter_table.dart b/lib/widgets/stats/filter_table.dart index bf764ad5a..62159b672 100644 --- a/lib/widgets/stats/filter_table.dart +++ b/lib/widgets/stats/filter_table.dart @@ -1,4 +1,5 @@ import 'package:aves/model/filters/filters.dart'; +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/utils/constants.dart'; @@ -7,6 +8,7 @@ import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:percent_indicator/linear_percent_indicator.dart'; +import 'package:provider/provider.dart'; class FilterTable extends StatelessWidget { final int totalEntryCount; @@ -35,6 +37,7 @@ class FilterTable extends StatelessWidget { final locale = context.l10n.localeName; final numberFormat = NumberFormat.decimalPattern(locale); final percentFormat = NumberFormat.percentPattern(); + final animate = context.select((v) => v.accessibilityAnimations.animate); final sortedEntries = entryCountMap.entries.toList(); if (sortByCount) { @@ -85,7 +88,7 @@ class FilterTable extends StatelessWidget { lineHeight: lineHeight, backgroundColor: theme.colorScheme.onPrimary.withOpacity(.1), progressColor: isMonochrome ? theme.colorScheme.secondary : color, - animation: true, + animation: animate, isRTL: isRtl, barRadius: barRadius, center: Text( diff --git a/lib/widgets/stats/histogram.dart b/lib/widgets/stats/histogram.dart deleted file mode 100644 index ee9bd7730..000000000 --- a/lib/widgets/stats/histogram.dart +++ /dev/null @@ -1,206 +0,0 @@ -import 'package:aves/model/entry.dart'; -import 'package:aves/model/filters/date.dart'; -import 'package:aves/theme/durations.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; -import 'package:aves/widgets/stats/date/axis.dart'; -import 'package:charts_flutter/flutter.dart' as charts; -import 'package:collection/collection.dart'; -import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:provider/provider.dart'; - -class Histogram extends StatefulWidget { - final Set entries; - final FilterCallback onFilterSelection; - - const Histogram({ - super.key, - required this.entries, - required this.onFilterSelection, - }); - - @override - State createState() => _HistogramState(); -} - -class _HistogramState extends State { - DateLevel _level = DateLevel.y; - DateTime? _firstDate, _lastDate; - final Map _entryCountPerDate = {}; - final ValueNotifier _selection = ValueNotifier(null); - - static const histogramHeight = 200.0; - - @override - void initState() { - super.initState(); - - final entriesByDateDescending = List.of(widget.entries)..sort(AvesEntry.compareByDate); - _lastDate = entriesByDateDescending.firstWhereOrNull((entry) => entry.bestDate != null)?.bestDate; - _firstDate = entriesByDateDescending.lastWhereOrNull((entry) => entry.bestDate != null)?.bestDate; - - if (_lastDate != null && _firstDate != null) { - final rangeDays = _lastDate!.difference(_firstDate!).inDays; - if (rangeDays > 1) { - if (rangeDays <= 31) { - _level = DateLevel.ymd; - } else if (rangeDays <= 365) { - _level = DateLevel.ym; - } - - final dates = entriesByDateDescending.map((entry) => entry.bestDate).whereNotNull(); - late DateTime Function(DateTime) groupByKey; - switch (_level) { - case DateLevel.ymd: - groupByKey = (v) => DateTime(v.year, v.month, v.day); - break; - case DateLevel.ym: - groupByKey = (v) => DateTime(v.year, v.month); - break; - default: - groupByKey = (v) => DateTime(v.year); - break; - } - _entryCountPerDate.addAll(groupBy(dates, groupByKey).map((k, v) => MapEntry(k, v.length))); - } - } - } - - @override - Widget build(BuildContext context) { - if (_entryCountPerDate.isEmpty) return const SizedBox(); - - final theme = Theme.of(context); - - final seriesData = _entryCountPerDate.entries.map((kv) { - return EntryByDate(date: kv.key, entryCount: kv.value); - }).toList(); - - final series = [ - charts.Series( - id: 'histogram', - colorFn: (d, i) => charts.ColorUtil.fromDartColor(theme.colorScheme.secondary), - domainFn: (d, i) => d.date, - measureFn: (d, i) => d.entryCount, - data: seriesData, - ), - ]; - - final locale = context.l10n.localeName; - final numberFormat = NumberFormat.decimalPattern(locale); - final timeAxisSpec = _firstDate != null && _lastDate != null - ? TimeAxisSpec.forLevel( - locale: locale, - level: _level, - first: _firstDate!, - last: _lastDate!, - ) - : null; - final axisColor = charts.ColorUtil.fromDartColor(theme.colorScheme.onPrimary.withOpacity(.9)); - final measureLineColor = charts.ColorUtil.fromDartColor(theme.colorScheme.onPrimary.withOpacity(.1)); - final measureFormat = NumberFormat.decimalPattern(locale); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.symmetric(horizontal: 8), - height: histogramHeight, - child: charts.TimeSeriesChart( - series, - domainAxis: charts.DateTimeAxisSpec( - renderSpec: charts.SmallTickRendererSpec( - labelStyle: charts.TextStyleSpec(color: axisColor), - lineStyle: charts.LineStyleSpec(color: axisColor), - ), - tickProviderSpec: timeAxisSpec != null && timeAxisSpec.tickSpecs.isNotEmpty ? charts.StaticDateTimeTickProviderSpec(timeAxisSpec.tickSpecs) : null, - ), - primaryMeasureAxis: charts.NumericAxisSpec( - renderSpec: charts.GridlineRendererSpec( - labelStyle: charts.TextStyleSpec(color: axisColor), - lineStyle: charts.LineStyleSpec(color: measureLineColor), - ), - tickFormatterSpec: charts.BasicNumericTickFormatterSpec((v) { - // localize and hide 0 - return (v == null || v == 0) ? '' : measureFormat.format(v); - }), - ), - defaultRenderer: charts.BarRendererConfig(), - defaultInteractions: false, - behaviors: [ - charts.SelectNearest(), - charts.DomainHighlighter(), - ], - selectionModels: [ - charts.SelectionModelConfig( - changedListener: (model) => _selection.value = model.selectedDatum.firstOrNull?.datum as EntryByDate?, - ) - ], - ), - ), - ValueListenableBuilder( - valueListenable: _selection, - builder: (context, selection, child) { - late Widget child; - if (selection == null) { - child = const SizedBox(); - } else { - final filter = DateFilter(_level, selection.date); - final count = selection.entryCount; - child = Padding( - padding: const EdgeInsets.all(8), - child: Row( - children: [ - AvesFilterChip( - filter: filter, - onTap: widget.onFilterSelection, - ), - const Spacer(), - Text( - numberFormat.format(count), - style: TextStyle( - color: theme.textTheme.caption!.color, - ), - textAlign: TextAlign.end, - ), - ], - ), - ); - } - - return AnimatedSwitcher( - duration: context.read().formTransition, - switchInCurve: Curves.easeInOutCubic, - switchOutCurve: Curves.easeInOutCubic, - transitionBuilder: (child, animation) => FadeTransition( - opacity: animation, - child: SizeTransition( - sizeFactor: animation, - axisAlignment: -1, - child: child, - ), - ), - child: child, - ); - }, - ), - ], - ); - } -} - -@immutable -class EntryByDate extends Equatable { - final DateTime date; - final int entryCount; - - @override - List get props => [date, entryCount]; - - const EntryByDate({ - required this.date, - required this.entryCount, - }); -} diff --git a/lib/widgets/stats/mime_donut.dart b/lib/widgets/stats/mime_donut.dart new file mode 100644 index 000000000..249d15f5b --- /dev/null +++ b/lib/widgets/stats/mime_donut.dart @@ -0,0 +1,183 @@ +import 'dart:math'; + +import 'package:aves/model/filters/mime.dart'; +import 'package:aves/model/settings/enums/accessibility_animations.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/theme/colors.dart'; +import 'package:aves/theme/icons.dart'; +import 'package:aves/utils/mime_utils.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; +import 'package:charts_flutter/flutter.dart' as charts; +import 'package:collection/collection.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class MimeDonut extends StatefulWidget { + final IconData icon; + final Map byMimeTypes; + final Duration animationDuration; + final FilterCallback onFilterSelection; + + const MimeDonut({ + super.key, + required this.icon, + required this.byMimeTypes, + required this.animationDuration, + required this.onFilterSelection, + }); + + @override + State createState() => _MimeDonutState(); +} + +class _MimeDonutState extends State with AutomaticKeepAliveClientMixin { + Map get byMimeTypes => widget.byMimeTypes; + + static const mimeDonutMinWidth = 124.0; + + @override + Widget build(BuildContext context) { + super.build(context); + + if (byMimeTypes.isEmpty) return const SizedBox.shrink(); + + final l10n = context.l10n; + final locale = l10n.localeName; + final numberFormat = NumberFormat.decimalPattern(locale); + + final sum = byMimeTypes.values.sum; + + final colors = context.watch(); + final seriesData = byMimeTypes.entries.map((kv) { + final mimeType = kv.key; + final displayText = MimeUtils.displayType(mimeType); + return EntryByMimeDatum( + mimeType: mimeType, + displayText: displayText, + color: colors.fromString(displayText), + entryCount: kv.value, + ); + }).toList(); + seriesData.sort((d1, d2) { + final c = d2.entryCount.compareTo(d1.entryCount); + return c != 0 ? c : compareAsciiUpperCase(d1.displayText, d2.displayText); + }); + + final series = [ + charts.Series( + id: 'mime', + colorFn: (d, i) => charts.ColorUtil.fromDartColor(d.color), + domainFn: (d, i) => d.displayText, + measureFn: (d, i) => d.entryCount, + data: seriesData, + labelAccessorFn: (d, _) => '${d.displayText}: ${d.entryCount}', + ), + ]; + + return LayoutBuilder(builder: (context, constraints) { + final textScaleFactor = MediaQuery.textScaleFactorOf(context); + final minWidth = mimeDonutMinWidth * textScaleFactor; + final availableWidth = constraints.maxWidth; + final dim = max(minWidth, availableWidth / (availableWidth > 4 * minWidth ? 4 : (availableWidth > 2 * minWidth ? 2 : 1))); + + final donut = SizedBox( + width: dim, + height: dim, + child: Stack( + children: [ + charts.PieChart( + series, + animate: context.select((v) => v.accessibilityAnimations.animate), + animationDuration: widget.animationDuration, + defaultRenderer: charts.ArcRendererConfig( + arcWidth: 16, + ), + ), + Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(widget.icon), + Text( + numberFormat.format(sum), + textAlign: TextAlign.center, + ), + ], + ), + ), + ], + ), + ); + final legend = SizedBox( + width: dim, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: seriesData + .map((d) => GestureDetector( + onTap: () => widget.onFilterSelection(MimeFilter(d.mimeType)), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(AIcons.disc, color: d.color), + const SizedBox(width: 8), + Flexible( + child: Text( + d.displayText, + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1, + ), + ), + const SizedBox(width: 8), + Text( + numberFormat.format(d.entryCount), + style: TextStyle( + color: Theme.of(context).textTheme.caption!.color, + ), + ), + ], + ), + )) + .toList(), + ), + ); + final children = [ + donut, + legend, + ]; + return availableWidth > minWidth * 2 + ? Row( + mainAxisSize: MainAxisSize.min, + children: children, + ) + : Column( + mainAxisSize: MainAxisSize.min, + children: children, + ); + }); + } + + @override + bool get wantKeepAlive => true; +} + +@immutable +class EntryByMimeDatum extends Equatable { + final String mimeType, displayText; + final Color color; + final int entryCount; + + @override + List get props => [mimeType, displayText, color, entryCount]; + + const EntryByMimeDatum({ + required this.mimeType, + required this.displayText, + required this.color, + required this.entryCount, + }); +} diff --git a/lib/widgets/stats/stats_page.dart b/lib/widgets/stats/stats_page.dart index d206e7af9..e1479b427 100644 --- a/lib/widgets/stats/stats_page.dart +++ b/lib/widgets/stats/stats_page.dart @@ -1,305 +1,227 @@ -import 'dart:math'; +import 'dart:async'; import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/location.dart'; -import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/filters/rating.dart'; import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/theme/colors.dart'; +import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/utils/constants.dart'; -import 'package:aves/utils/mime_utils.dart'; import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/basic/insets.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/empty.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; +import 'package:aves/widgets/stats/date/histogram.dart'; import 'package:aves/widgets/stats/filter_table.dart'; -import 'package:aves/widgets/stats/histogram.dart'; -import 'package:charts_flutter/flutter.dart' as charts; +import 'package:aves/widgets/stats/mime_donut.dart'; import 'package:collection/collection.dart'; -import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:intl/intl.dart'; import 'package:percent_indicator/linear_percent_indicator.dart'; import 'package:provider/provider.dart'; -class StatsPage extends StatelessWidget { +class StatsPage extends StatefulWidget { static const routeName = '/collection/stats'; + final Set entries; final CollectionSource source; final CollectionLens? parentCollection; - final Set entries; - final Map entryCountPerCountry = {}, entryCountPerPlace = {}, entryCountPerTag = {}; - final Map entryCountPerRating = Map.fromEntries(List.generate(7, (i) => MapEntry(5 - i, 0))); - static const mimeDonutMinWidth = 124.0; - - StatsPage({ + const StatsPage({ super.key, required this.entries, required this.source, this.parentCollection, - }) { + }); + + @override + State createState() => _StatsPageState(); +} + +class _StatsPageState extends State { + final Map _entryCountPerCountry = {}, _entryCountPerPlace = {}, _entryCountPerTag = {}; + final Map _entryCountPerRating = Map.fromEntries(List.generate(7, (i) => MapEntry(5 - i, 0))); + late final ValueNotifier _isPageAnimatingNotifier; + + Set get entries => widget.entries; + + @override + void initState() { + super.initState(); + + _isPageAnimatingNotifier = ValueNotifier(true); + Future.delayed(Durations.pageTransitionAnimation * timeDilation).then((_) { + if (!mounted) return; + _isPageAnimatingNotifier.value = false; + }); + entries.forEach((entry) { if (entry.hasAddress) { final address = entry.addressDetails!; var country = address.countryName; if (country != null && country.isNotEmpty) { country += '${LocationFilter.locationSeparator}${address.countryCode}'; - entryCountPerCountry[country] = (entryCountPerCountry[country] ?? 0) + 1; + _entryCountPerCountry[country] = (_entryCountPerCountry[country] ?? 0) + 1; } final place = address.place; if (place != null && place.isNotEmpty) { - entryCountPerPlace[place] = (entryCountPerPlace[place] ?? 0) + 1; + _entryCountPerPlace[place] = (_entryCountPerPlace[place] ?? 0) + 1; } } entry.tags.forEach((tag) { - entryCountPerTag[tag] = (entryCountPerTag[tag] ?? 0) + 1; + _entryCountPerTag[tag] = (_entryCountPerTag[tag] ?? 0) + 1; }); final rating = entry.rating; - entryCountPerRating[rating] = (entryCountPerRating[rating] ?? 0) + 1; + _entryCountPerRating[rating] = (_entryCountPerRating[rating] ?? 0) + 1; }); } @override Widget build(BuildContext context) { - final l10n = context.l10n; - final locale = l10n.localeName; - final numberFormat = NumberFormat.decimalPattern(locale); - final percentFormat = NumberFormat.percentPattern(); + return ValueListenableBuilder( + valueListenable: _isPageAnimatingNotifier, + builder: (context, animating, child) { + final l10n = context.l10n; - Widget child; - if (entries.isEmpty) { - child = EmptyContent( - icon: AIcons.image, - text: l10n.collectionEmptyImages, - ); - } else { - final theme = Theme.of(context); - final isDark = theme.brightness == Brightness.dark; - final animate = context.select((v) => v.accessibilityAnimations.animate); - final byMimeTypes = groupBy(entries, (entry) => entry.mimeType).map((k, v) => MapEntry(k, v.length)); - final imagesByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('image'))); - final videoByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('video'))); - final mimeDonuts = Provider.value( - value: isDark ? NeonOnDark() : PastelOnLight(), - child: Builder( - builder: (context) { - return Wrap( + Widget child = const SizedBox(); + + if (!animating) { + final durations = context.watch(); + final percentFormat = NumberFormat.percentPattern(); + + if (entries.isEmpty) { + child = EmptyContent( + icon: AIcons.image, + text: l10n.collectionEmptyImages, + ); + } else { + final theme = Theme.of(context); + final isDark = theme.brightness == Brightness.dark; + final chartAnimationDuration = context.read().chartTransition; + + final byMimeTypes = groupBy(entries, (entry) => entry.mimeType).map((k, v) => MapEntry(k, v.length)); + final imagesByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('image'))); + final videoByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('video'))); + final mimeDonuts = Wrap( alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ - _buildMimeDonut(context, AIcons.image, imagesByMimeTypes, animate, numberFormat), - _buildMimeDonut(context, AIcons.video, videoByMimeTypes, animate, numberFormat), + MimeDonut( + icon: AIcons.image, + byMimeTypes: imagesByMimeTypes, + animationDuration: chartAnimationDuration, + onFilterSelection: (filter) => _onFilterSelection(context, filter), + ), + MimeDonut( + icon: AIcons.video, + byMimeTypes: videoByMimeTypes, + animationDuration: chartAnimationDuration, + onFilterSelection: (filter) => _onFilterSelection(context, filter), + ), ], ); - }, - ), - ); - final catalogued = entries.where((entry) => entry.isCatalogued); - final withGps = catalogued.where((entry) => entry.hasGps); - final withGpsCount = withGps.length; - final withGpsPercent = withGpsCount / entries.length; - final textScaleFactor = MediaQuery.textScaleFactorOf(context); - final lineHeight = 16 * textScaleFactor; - final barRadius = Radius.circular(lineHeight / 2); - final locationIndicator = Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(AIcons.location), - Expanded( - child: LinearPercentIndicator( - percent: withGpsPercent, - lineHeight: lineHeight, - backgroundColor: theme.colorScheme.onPrimary.withOpacity(.1), - progressColor: theme.colorScheme.secondary, - animation: animate, - isRTL: context.isRtl, - barRadius: barRadius, - center: Text( - percentFormat.format(withGpsPercent), - style: TextStyle( - shadows: isDark ? Constants.embossShadows : null, - ), - ), - padding: EdgeInsets.symmetric(horizontal: lineHeight), - ), - ), - // end padding to match leading, so that inside label is aligned with outside label below - const SizedBox(width: 24), - ], - ), - const SizedBox(height: 8), - Text( - l10n.statsWithGps(withGpsCount), - textAlign: TextAlign.center, - ), - ], - ), - ); - final showRatings = entryCountPerRating.entries.any((kv) => kv.key != 0 && kv.value > 0); - child = ListView( - children: [ - mimeDonuts, - Histogram( - entries: entries, - onFilterSelection: (filter) => _onFilterSelection(context, filter), - ), - locationIndicator, - ..._buildFilterSection(context, l10n.statsTopCountries, entryCountPerCountry, (v) => LocationFilter(LocationLevel.country, v)), - ..._buildFilterSection(context, l10n.statsTopPlaces, entryCountPerPlace, (v) => LocationFilter(LocationLevel.place, v)), - ..._buildFilterSection(context, l10n.statsTopTags, entryCountPerTag, TagFilter.new), - if (showRatings) ..._buildFilterSection(context, l10n.searchSectionRating, entryCountPerRating, RatingFilter.new, sortByCount: false, maxRowCount: null), - ], - ); - } - return MediaQueryDataProvider( - child: Scaffold( - appBar: AppBar( - title: Text(l10n.statsPageTitle), - ), - body: GestureAreaProtectorStack( - child: SafeArea( - bottom: false, - child: child, - ), - ), - ), - ); - } - - Widget _buildMimeDonut( - BuildContext context, - IconData icon, - Map byMimeTypes, - bool animate, - NumberFormat numberFormat, - ) { - if (byMimeTypes.isEmpty) return const SizedBox.shrink(); - - final sum = byMimeTypes.values.fold(0, (prev, v) => prev + v); - - final colors = context.watch(); - final seriesData = byMimeTypes.entries.map((kv) { - final mimeType = kv.key; - final displayText = MimeUtils.displayType(mimeType); - return EntryByMimeDatum( - mimeType: mimeType, - displayText: displayText, - color: colors.fromString(displayText), - entryCount: kv.value, - ); - }).toList(); - seriesData.sort((d1, d2) { - final c = d2.entryCount.compareTo(d1.entryCount); - return c != 0 ? c : compareAsciiUpperCase(d1.displayText, d2.displayText); - }); - - final series = [ - charts.Series( - id: 'mime', - colorFn: (d, i) => charts.ColorUtil.fromDartColor(d.color), - domainFn: (d, i) => d.displayText, - measureFn: (d, i) => d.entryCount, - data: seriesData, - labelAccessorFn: (d, _) => '${d.displayText}: ${d.entryCount}', - ), - ]; - - return LayoutBuilder(builder: (context, constraints) { - final textScaleFactor = MediaQuery.textScaleFactorOf(context); - final minWidth = mimeDonutMinWidth * textScaleFactor; - final availableWidth = constraints.maxWidth; - final dim = max(minWidth, availableWidth / (availableWidth > 4 * minWidth ? 4 : (availableWidth > 2 * minWidth ? 2 : 1))); - - final donut = SizedBox( - width: dim, - height: dim, - child: Stack( - children: [ - charts.PieChart( - series, - animate: animate, - defaultRenderer: charts.ArcRendererConfig( - arcWidth: 16, - ), - ), - Center( + final catalogued = entries.where((entry) => entry.isCatalogued); + final withGps = catalogued.where((entry) => entry.hasGps); + final withGpsCount = withGps.length; + final withGpsPercent = withGpsCount / entries.length; + final textScaleFactor = MediaQuery.textScaleFactorOf(context); + final lineHeight = 16 * textScaleFactor; + final barRadius = Radius.circular(lineHeight / 2); + final locationIndicator = Padding( + padding: const EdgeInsets.all(16), child: Column( - mainAxisSize: MainAxisSize.min, children: [ - Icon(icon), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(AIcons.location), + Expanded( + child: LinearPercentIndicator( + percent: withGpsPercent, + lineHeight: lineHeight, + backgroundColor: theme.colorScheme.onPrimary.withOpacity(.1), + progressColor: theme.colorScheme.secondary, + animation: context.select((v) => v.accessibilityAnimations.animate), + isRTL: context.isRtl, + barRadius: barRadius, + center: Text( + percentFormat.format(withGpsPercent), + style: TextStyle( + shadows: isDark ? Constants.embossShadows : null, + ), + ), + padding: EdgeInsets.symmetric(horizontal: lineHeight), + ), + ), + // end padding to match leading, so that inside label is aligned with outside label below + const SizedBox(width: 24), + ], + ), + const SizedBox(height: 8), Text( - numberFormat.format(sum), + l10n.statsWithGps(withGpsCount), textAlign: TextAlign.center, ), ], ), - ), - ], - ), - ); - final legend = SizedBox( - width: dim, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: seriesData - .map((d) => GestureDetector( - onTap: () => _onFilterSelection(context, MimeFilter(d.mimeType)), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(AIcons.disc, color: d.color), - const SizedBox(width: 8), - Flexible( - child: Text( - d.displayText, - overflow: TextOverflow.fade, - softWrap: false, - maxLines: 1, - ), - ), - const SizedBox(width: 8), - Text( - numberFormat.format(d.entryCount), - style: TextStyle( - color: Theme.of(context).textTheme.caption!.color, - ), - ), - ], - ), - )) - .toList(), - ), - ); - final children = [ - donut, - legend, - ]; - return availableWidth > minWidth * 2 - ? Row( - mainAxisSize: MainAxisSize.min, - children: children, - ) - : Column( - mainAxisSize: MainAxisSize.min, - children: children, ); - }); + final showRatings = _entryCountPerRating.entries.any((kv) => kv.key != 0 && kv.value > 0); + child = AnimationLimiter( + child: ListView( + children: AnimationConfiguration.toStaggeredList( + duration: durations.staggeredAnimation, + delay: durations.staggeredAnimationDelay * timeDilation, + childAnimationBuilder: (child) => SlideAnimation( + verticalOffset: 50.0, + child: FadeInAnimation( + child: child, + ), + ), + children: [ + mimeDonuts, + Histogram( + entries: entries, + animationDuration: chartAnimationDuration, + onFilterSelection: (filter) => _onFilterSelection(context, filter), + ), + locationIndicator, + ..._buildFilterSection(context, l10n.statsTopCountriesSectionTitle, _entryCountPerCountry, (v) => LocationFilter(LocationLevel.country, v)), + ..._buildFilterSection(context, l10n.statsTopPlacesSectionTitle, _entryCountPerPlace, (v) => LocationFilter(LocationLevel.place, v)), + ..._buildFilterSection(context, l10n.statsTopTagsSectionTitle, _entryCountPerTag, TagFilter.new), + if (showRatings) ..._buildFilterSection(context, l10n.searchRatingSectionTitle, _entryCountPerRating, RatingFilter.new, sortByCount: false, maxRowCount: null), + ], + ), + ), + ); + } + } + + return MediaQueryDataProvider( + child: Scaffold( + appBar: AppBar( + title: Text(l10n.statsPageTitle), + ), + body: GestureAreaProtectorStack( + child: SafeArea( + bottom: false, + child: child, + ), + ), + ), + ); + }, + ); } List _buildFilterSection( @@ -317,7 +239,7 @@ class StatsPage extends StatelessWidget { padding: const EdgeInsets.all(16), child: Text( title, - style: Constants.titleTextStyle, + style: Constants.knownTitleTextStyle, ), ), FilterTable( @@ -332,7 +254,7 @@ class StatsPage extends StatelessWidget { } void _onFilterSelection(BuildContext context, CollectionFilter filter) { - if (parentCollection != null) { + if (widget.parentCollection != null) { _applyToParentCollectionPage(context, filter); } else { _jumpToCollectionPage(context, filter); @@ -340,7 +262,7 @@ class StatsPage extends StatelessWidget { } void _applyToParentCollectionPage(BuildContext context, CollectionFilter filter) { - parentCollection!.addFilter(filter); + widget.parentCollection!.addFilter(filter); // We delay closing the current page after applying the filter selection // so that hero animation target is ready in the `FilterBar`, // even when the target is a child of an `AnimatedList`. @@ -355,7 +277,7 @@ class StatsPage extends StatelessWidget { MaterialPageRoute( settings: const RouteSettings(name: CollectionPage.routeName), builder: (context) => CollectionPage( - source: source, + source: widget.source, filters: {filter}, ), ), @@ -363,20 +285,3 @@ class StatsPage extends StatelessWidget { ); } } - -@immutable -class EntryByMimeDatum extends Equatable { - final String mimeType, displayText; - final Color color; - final int entryCount; - - @override - List get props => [mimeType, displayText, color, entryCount]; - - const EntryByMimeDatum({ - required this.mimeType, - required this.displayText, - required this.color, - required this.entryCount, - }); -} diff --git a/lib/widgets/viewer/action/entry_info_action_delegate.dart b/lib/widgets/viewer/action/entry_info_action_delegate.dart index a73bc78e6..d44c97eca 100644 --- a/lib/widgets/viewer/action/entry_info_action_delegate.dart +++ b/lib/widgets/viewer/action/entry_info_action_delegate.dart @@ -35,7 +35,7 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi // general case EntryInfoAction.editDate: case EntryInfoAction.editLocation: - case EntryInfoAction.editDescription: + case EntryInfoAction.editTitleDescription: case EntryInfoAction.editRating: case EntryInfoAction.editTags: case EntryInfoAction.removeMetadata: @@ -60,8 +60,8 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi return entry.canEditDate; case EntryInfoAction.editLocation: return entry.canEditLocation; - case EntryInfoAction.editDescription: - return entry.canEditDescription; + case EntryInfoAction.editTitleDescription: + return entry.canEditTitleDescription; case EntryInfoAction.editRating: return entry.canEditRating; case EntryInfoAction.editTags: @@ -92,8 +92,8 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi case EntryInfoAction.editLocation: await _editLocation(context); break; - case EntryInfoAction.editDescription: - await _editDescription(context); + case EntryInfoAction.editTitleDescription: + await _editTitleDescription(context); break; case EntryInfoAction.editRating: await _editRating(context); @@ -137,11 +137,11 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi await edit(context, () => entry.editLocation(location)); } - Future _editDescription(BuildContext context) async { - final description = await selectDescription(context, {entry}); - if (description == null) return; + Future _editTitleDescription(BuildContext context) async { + final modifier = await selectTitleDescriptionModifier(context, {entry}); + if (modifier == null) return; - await edit(context, () => entry.editDescription(description)); + await edit(context, () => entry.editTitleDescription(modifier)); } Future _editRating(BuildContext context) async { diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart index 85920157d..14f8918b4 100644 --- a/lib/widgets/viewer/entry_viewer_stack.dart +++ b/lib/widgets/viewer/entry_viewer_stack.dart @@ -71,6 +71,9 @@ class _EntryViewerStackState extends State with EntryViewContr final ValueNotifier _heroInfoNotifier = ValueNotifier(null); bool _isEntryTracked = true; + @override + bool get isViewingImage => _currentVerticalPage.value == imagePage; + @override late final ValueNotifier entryNotifier; diff --git a/lib/widgets/viewer/info/info_search.dart b/lib/widgets/viewer/info/info_search.dart index 6dbb9200a..bd2018b54 100644 --- a/lib/widgets/viewer/info/info_search.dart +++ b/lib/widgets/viewer/info/info_search.dart @@ -55,7 +55,7 @@ class InfoSearchDelegate extends SearchDelegate { final l10n = context.l10n; final suggestions = { l10n.viewerInfoSearchSuggestionDate: 'date or time or when -timer -uptime -exposure -timeline -verbatim', - l10n.viewerInfoSearchSuggestionDescription: 'abstract or description or comment or textual or title -line', + l10n.viewerInfoSearchSuggestionDescription: 'description or title or comment or textual or abstract or object name -line', l10n.viewerInfoSearchSuggestionDimensions: 'width or height or dimension or framesize or imagelength', l10n.viewerInfoSearchSuggestionResolution: 'resolution', l10n.viewerInfoSearchSuggestionRights: 'rights or copyright or attribution or license or artist or creator or by-line or credit -tool', diff --git a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart index 626c1ef83..b912b90e0 100644 --- a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart +++ b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart @@ -77,7 +77,7 @@ class XmpNamespace extends Equatable { } } - String get displayTitle => Namespaces.nsTitles[nsUri] ?? '${nsPrefix.substring(0, nsPrefix.length - 1)} ($nsUri)'; + String get displayTitle => Namespaces.nsTitles[nsUri] ?? (nsPrefix.isEmpty ? nsUri : '${nsPrefix.substring(0, nsPrefix.length - 1)} ($nsUri)'); Map get buildProps => rawProps; diff --git a/lib/widgets/viewer/info/metadata/xmp_tile.dart b/lib/widgets/viewer/info/metadata/xmp_tile.dart index 0a7a4748d..3d0ac7a23 100644 --- a/lib/widgets/viewer/info/metadata/xmp_tile.dart +++ b/lib/widgets/viewer/info/metadata/xmp_tile.dart @@ -43,7 +43,7 @@ class _XmpDirTileState extends State { super.initState(); _tags = Map.from(widget.tags)..remove(schemaRegistryPrefixesKey); final prefixesJson = widget.allTags[schemaRegistryPrefixesKey]; - final Map prefixesDecoded = prefixesJson != null ? json.decode(prefixesJson) : {}; + final Map prefixesDecoded = prefixesJson != null ? jsonDecode(prefixesJson) : {}; _schemaRegistryPrefixes = Map.fromEntries(prefixesDecoded.entries.map((kv) => MapEntry(kv.key, kv.value as String))); } diff --git a/lib/widgets/viewer/overlay/bottom.dart b/lib/widgets/viewer/overlay/bottom.dart index 3adfb4576..41caf8f84 100644 --- a/lib/widgets/viewer/overlay/bottom.dart +++ b/lib/widgets/viewer/overlay/bottom.dart @@ -37,7 +37,11 @@ class ViewerBottomOverlay extends StatefulWidget { State createState() => _ViewerBottomOverlayState(); static double actionSafeHeight(BuildContext context) { - return ViewerButtons.preferredHeight(context) + (settings.showOverlayThumbnailPreview ? ViewerThumbnailPreview.preferredHeight : 0); + final mq = context.read(); + final mqPaddingBottom = max(mq.effectiveBottomPadding, mq.systemGestureInsets.bottom); + final buttonHeight = ViewerButtons.preferredHeight(context); + final thumbnailHeight = (settings.showOverlayThumbnailPreview ? ViewerThumbnailPreview.preferredHeight : 0); + return mqPaddingBottom + buttonHeight + thumbnailHeight; } } diff --git a/lib/widgets/viewer/overlay/viewer_buttons.dart b/lib/widgets/viewer/overlay/viewer_buttons.dart index aa0767ce6..fbe0a3342 100644 --- a/lib/widgets/viewer/overlay/viewer_buttons.dart +++ b/lib/widgets/viewer/overlay/viewer_buttons.dart @@ -207,7 +207,7 @@ class ViewerButtonRowContent extends StatelessWidget { value: 'video', expandedNotifier: _popupExpandedNotifier, icon: AIcons.video, - title: context.l10n.settingsSectionVideo, + title: context.l10n.settingsVideoSectionTitle, items: [ ...videoActions.map((action) => _buildPopupMenuItem(context, action, videoController)).toList(), ], diff --git a/lib/widgets/viewer/visual/controller_mixin.dart b/lib/widgets/viewer/visual/controller_mixin.dart index 8b353d4f3..b00031a61 100644 --- a/lib/widgets/viewer/visual/controller_mixin.dart +++ b/lib/widgets/viewer/visual/controller_mixin.dart @@ -14,8 +14,11 @@ import 'package:provider/provider.dart'; // state controllers/monitors mixin EntryViewControllerMixin on State { + final Map _metadataChangeListeners = {}; final Map Function()> _multiPageControllerPageListeners = {}; + bool get isViewingImage; + ValueNotifier get entryNotifier; Future initEntryControllers(AvesEntry? entry) async { @@ -27,16 +30,29 @@ mixin EntryViewControllerMixin on State { if (entry.isMultiPage) { await _initMultiPageController(entry); } + void listener() => _onMetadataChange(entry); + _metadataChangeListeners[entry] = listener; + entry.metadataChangeNotifier.addListener(listener); } void cleanEntryControllers(AvesEntry? entry) { if (entry == null) return; + final listener = _metadataChangeListeners.remove(entry); + if (listener != null) { + entry.metadataChangeNotifier.removeListener(listener); + } if (entry.isMultiPage) { _cleanMultiPageController(entry); } } + void _onMetadataChange(AvesEntry entry) { + debugPrint('reinitialize controllers for entry=$entry because metadata changed'); + cleanEntryControllers(entry); + initEntryControllers(entry); + } + SlideshowVideoPlayback? get videoPlaybackOverride { if (!mounted) return null; final appMode = context.read>().value; @@ -50,7 +66,9 @@ mixin EntryViewControllerMixin on State { } } - bool _shouldAutoPlay(BuildContext context) { + bool _shouldAutoPlayVideo(BuildContext context) { + if (!isViewingImage) return false; + switch (videoPlaybackOverride) { case SlideshowVideoPlayback.skip: return false; @@ -62,11 +80,17 @@ mixin EntryViewControllerMixin on State { } } + bool _shouldAutoPlayMotionPhoto(BuildContext context) { + if (!isViewingImage) return false; + + return settings.enableMotionPhotoAutoPlay; + } + Future _initVideoController(AvesEntry entry) async { final controller = context.read().getOrCreateController(entry); setState(() {}); - if (_shouldAutoPlay(context)) { + if (_shouldAutoPlayVideo(context)) { final resumeTimeMillis = await controller.getResumeTime(context); await _playVideo(controller, () => entry == entryNotifier.value, resumeTimeMillis: resumeTimeMillis); } @@ -93,7 +117,7 @@ mixin EntryViewControllerMixin on State { // auto play/pause when changing page Future _onPageChange() async { await pauseVideoControllers(); - if (_shouldAutoPlay(context) || (entry.isMotionPhoto && settings.enableMotionPhotoAutoPlay)) { + if (_shouldAutoPlayVideo(context) || (entry.isMotionPhoto && _shouldAutoPlayMotionPhoto(context))) { final page = multiPageController.page; final pageInfo = multiPageInfo.getByIndex(page)!; if (pageInfo.isVideo) { @@ -111,7 +135,7 @@ mixin EntryViewControllerMixin on State { multiPageController.pageNotifier.addListener(_onPageChange); await _onPageChange(); - if (entry.isMotionPhoto && settings.enableMotionPhotoAutoPlay) { + if (entry.isMotionPhoto && _shouldAutoPlayMotionPhoto(context)) { await Future.delayed(Durations.motionPhotoAutoPlayDelay); if (entry == entryNotifier.value) { multiPageController.page = 1; diff --git a/lib/widgets/viewer/visual/entry_page_view.dart b/lib/widgets/viewer/visual/entry_page_view.dart index 537ade116..911bddeb0 100644 --- a/lib/widgets/viewer/visual/entry_page_view.dart +++ b/lib/widgets/viewer/visual/entry_page_view.dart @@ -226,10 +226,10 @@ class _EntryPageViewState extends State { ? (alignment) { final x = alignment.x; if (seekGesture) { - if (x < .25) { + if (x < sideRatio) { _applyAction(EntryAction.videoReplay10); return true; - } else if (x > .75) { + } else if (x > 1 - sideRatio) { _applyAction(EntryAction.videoSkip10); return true; } @@ -394,10 +394,10 @@ class _EntryPageViewState extends State { void _onTap({Alignment? alignment}) { if (settings.viewerGestureSideTapNext && alignment != null) { final x = alignment.x; - if (x < .25) { + if (x < sideRatio) { JumpToPreviousEntryNotification().dispatch(context); return; - } else if (x > .75) { + } else if (x > 1 - sideRatio) { JumpToNextEntryNotification().dispatch(context); return; } @@ -419,6 +419,15 @@ class _EntryPageViewState extends State { ); } + double get sideRatio { + switch (context.read().orientation) { + case Orientation.portrait: + return 1 / 5; + case Orientation.landscape: + return 1 / 8; + } + } + static ScaleState _vectorScaleStateCycle(ScaleState actual) { switch (actual) { case ScaleState.initial: diff --git a/lib/widgets/wallpaper_page.dart b/lib/widgets/wallpaper_page.dart index 1673f352e..1b04229b2 100644 --- a/lib/widgets/wallpaper_page.dart +++ b/lib/widgets/wallpaper_page.dart @@ -75,6 +75,9 @@ class _EntryEditorState extends State with EntryViewControllerMixin late VideoActionDelegate _videoActionDelegate; late final ViewerController _viewerController; + @override + bool get isViewingImage => true; + @override final ValueNotifier entryNotifier = ValueNotifier(null); diff --git a/plugins/aves_map/pubspec.yaml b/plugins/aves_map/pubspec.yaml index 68b1ea5e4..2bd1fa095 100644 --- a/plugins/aves_map/pubspec.yaml +++ b/plugins/aves_map/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.18.0 <3.0.0" dependencies: flutter: diff --git a/plugins/aves_platform_meta/pubspec.yaml b/plugins/aves_platform_meta/pubspec.yaml index 2cce968e6..18c18d5a2 100644 --- a/plugins/aves_platform_meta/pubspec.yaml +++ b/plugins/aves_platform_meta/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.18.0 <3.0.0" dependencies: flutter: diff --git a/plugins/aves_report/pubspec.yaml b/plugins/aves_report/pubspec.yaml index ab1c3d7dc..61bcbea7c 100644 --- a/plugins/aves_report/pubspec.yaml +++ b/plugins/aves_report/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.18.0 <3.0.0" dependencies: flutter: diff --git a/plugins/aves_report_console/pubspec.yaml b/plugins/aves_report_console/pubspec.yaml index 93bcab68f..45ec75833 100644 --- a/plugins/aves_report_console/pubspec.yaml +++ b/plugins/aves_report_console/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.18.0 <3.0.0" dependencies: flutter: diff --git a/plugins/aves_report_crashlytics/lib/aves_report_platform.dart b/plugins/aves_report_crashlytics/lib/aves_report_platform.dart index 58e429b3b..51c739ff0 100644 --- a/plugins/aves_report_crashlytics/lib/aves_report_platform.dart +++ b/plugins/aves_report_crashlytics/lib/aves_report_platform.dart @@ -15,7 +15,7 @@ class PlatformReportService extends ReportService { try { return FirebaseCrashlytics.instance; } catch (error, stack) { - // as of firebase_core v1.10.5 / firebase_crashlytics v2.4.3, `Firebase.app` sometimes fail with: + // as of firebase_core v1.21.0 / firebase_crashlytics v2.8.8, `Firebase.app` sometimes fail with: // `No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp()` debugPrint('failed to get Firebase Crashlytics instance with error=$error\n$stack'); } @@ -34,7 +34,7 @@ class PlatformReportService extends ReportService { 'Crashlytics collection enabled': '${_instance?.isCrashlyticsCollectionEnabled}', }; } catch (error, stack) { - // as of firebase_core v1.10.5 / firebase_crashlytics v2.4.3, `Firebase.app` sometimes fail with: + // as of firebase_core v1.21.0 / firebase_crashlytics v2.8.8, `Firebase.app` sometimes fail with: // `No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp()` debugPrint('failed to access Firebase properties with error=$error\n$stack'); } @@ -48,7 +48,7 @@ class PlatformReportService extends ReportService { await Firebase.app().setAutomaticDataCollectionEnabled(enabled); await _instance?.setCrashlyticsCollectionEnabled(enabled); } catch (error, stack) { - // as of firebase_core v1.10.5 / firebase_crashlytics v2.4.3, `Firebase.app` sometimes fail with: + // as of firebase_core v1.21.0 / firebase_crashlytics v2.8.8, `Firebase.app` sometimes fail with: // `No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp()` debugPrint('failed to access Firebase properties with error=$error\n$stack'); } diff --git a/plugins/aves_report_crashlytics/pubspec.yaml b/plugins/aves_report_crashlytics/pubspec.yaml index 981e7f044..72706939d 100644 --- a/plugins/aves_report_crashlytics/pubspec.yaml +++ b/plugins/aves_report_crashlytics/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.18.0 <3.0.0" dependencies: flutter: diff --git a/plugins/aves_services/pubspec.yaml b/plugins/aves_services/pubspec.yaml index 6fe9fcf64..4dc6cf3fe 100644 --- a/plugins/aves_services/pubspec.yaml +++ b/plugins/aves_services/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.18.0 <3.0.0" dependencies: flutter: diff --git a/plugins/aves_services_google/lib/aves_services_platform.dart b/plugins/aves_services_google/lib/aves_services_platform.dart index ff24f4e04..4c8f2ef0d 100644 --- a/plugins/aves_services_google/lib/aves_services_platform.dart +++ b/plugins/aves_services_google/lib/aves_services_platform.dart @@ -6,7 +6,9 @@ import 'package:aves_services_platform/src/map.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/widgets.dart'; import 'package:google_api_availability/google_api_availability.dart'; -import 'package:latlong2/latlong.dart'; +import 'package:google_maps_flutter_android/google_maps_flutter_android.dart'; +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'package:latlong2/latlong.dart' as ll; class PlatformMobileServices extends MobileServices { bool _isAvailable = false; @@ -24,6 +26,20 @@ class PlatformMobileServices extends MobileServices { // as of google_maps_flutter v2.1.5, Flutter v3.0.1 makes the map hide overlay widgets on API <=22 final androidInfo = await DeviceInfoPlugin().androidInfo; _canRenderMaps = (androidInfo.version.sdkInt ?? 0) >= 21; + if (_canRenderMaps) { + final mapsImplementation = GoogleMapsFlutterPlatform.instance; + if (mapsImplementation is GoogleMapsFlutterAndroid) { + // as of google_maps_flutter_android v2.2.0, + // setting `useAndroidViewSurface` to true: + // + issue #241 exists but workaround is efficient + // + pan perf is OK when overlay is disabled + // - pan perf is bad when overlay is enabled + // setting `useAndroidViewSurface` to false: + // - issue #241 exists and workaround is inefficient + // + pan perf is OK when overlay is disabled or enabled + mapsImplementation.useAndroidViewSurface = false; + } + } } @override @@ -52,7 +68,7 @@ class PlatformMobileServices extends MobileServices { required MarkerClusterBuilder markerClusterBuilder, required MarkerWidgetBuilder markerWidgetBuilder, required MarkerImageReadyChecker markerImageReadyChecker, - required ValueNotifier? dotLocationNotifier, + required ValueNotifier? dotLocationNotifier, required ValueNotifier? overlayOpacityNotifier, required MapOverlay? overlayEntry, required UserZoomChangeCallback? onUserZoomChange, diff --git a/plugins/aves_services_google/pubspec.yaml b/plugins/aves_services_google/pubspec.yaml index b2b3f16f1..aaa5aa74b 100644 --- a/plugins/aves_services_google/pubspec.yaml +++ b/plugins/aves_services_google/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.18.0 <3.0.0" dependencies: flutter: @@ -15,6 +15,8 @@ dependencies: device_info_plus: google_api_availability: google_maps_flutter: + google_maps_flutter_android: + google_maps_flutter_platform_interface: latlong2: provider: diff --git a/plugins/aves_services_huawei/pubspec.yaml b/plugins/aves_services_huawei/pubspec.yaml index 3fecb6458..86bf3f125 100644 --- a/plugins/aves_services_huawei/pubspec.yaml +++ b/plugins/aves_services_huawei/pubspec.yaml @@ -3,7 +3,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.18.0 <3.0.0" dependencies: flutter: diff --git a/pubspec.lock b/pubspec.lock index 1307c40fd..ccfae1781 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,14 +7,14 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "46.0.0" + version: "47.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "4.6.0" + version: "4.7.0" archive: dependency: transitive description: @@ -92,13 +92,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.1" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" charts_common: dependency: transitive description: @@ -133,7 +126,7 @@ packages: name: connectivity_plus url: "https://pub.dartlang.org" source: hosted - version: "2.3.6+1" + version: "2.3.7" connectivity_plus_linux: dependency: transitive description: @@ -259,14 +252,14 @@ packages: name: device_info_plus_windows url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "4.1.0" dynamic_color: dependency: "direct main" description: name: dynamic_color url: "https://pub.dartlang.org" source: hosted - version: "1.5.3" + version: "1.5.4" equatable: dependency: "direct main" description: @@ -326,42 +319,49 @@ packages: name: firebase_core url: "https://pub.dartlang.org" source: hosted - version: "1.21.0" + version: "1.22.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "4.5.0" + version: "4.5.1" firebase_core_web: dependency: transitive description: name: firebase_core_web url: "https://pub.dartlang.org" source: hosted - version: "1.7.1" + version: "1.7.2" firebase_crashlytics: dependency: transitive description: name: firebase_crashlytics url: "https://pub.dartlang.org" source: hosted - version: "2.8.8" + version: "2.8.10" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "3.2.14" + version: "3.2.16" flex_color_picker: dependency: "direct main" description: name: flex_color_picker url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.6.1" + flex_seed_scheme: + dependency: transitive + description: + name: flex_seed_scheme + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" fluster: dependency: "direct main" description: @@ -418,14 +418,14 @@ packages: name: flutter_map url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "3.0.0" flutter_markdown: dependency: "direct main" description: name: flutter_markdown url: "https://pub.dartlang.org" source: hosted - version: "0.6.10+4" + version: "0.6.12" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -489,14 +489,14 @@ packages: name: google_maps_flutter url: "https://pub.dartlang.org" source: hosted - version: "2.1.12" + version: "2.2.0" google_maps_flutter_android: dependency: transitive description: name: google_maps_flutter_android url: "https://pub.dartlang.org" source: hosted - version: "2.1.10" + version: "2.3.0" google_maps_flutter_ios: dependency: transitive description: @@ -601,7 +601,7 @@ packages: name: markdown url: "https://pub.dartlang.org" source: hosted - version: "5.0.0" + version: "6.0.0" matcher: dependency: transitive description: @@ -727,14 +727,14 @@ packages: name: package_info_plus_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" palette_generator: dependency: "direct main" description: name: palette_generator url: "https://pub.dartlang.org" source: hosted - version: "0.3.3+1" + version: "0.3.3+2" panorama: dependency: "direct main" description: @@ -778,7 +778,7 @@ packages: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "2.1.3" pdf: dependency: "direct main" description: @@ -878,7 +878,7 @@ packages: name: printing url: "https://pub.dartlang.org" source: hosted - version: "5.9.2" + version: "5.9.3" process: dependency: transitive description: @@ -969,7 +969,7 @@ packages: name: shared_preferences_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.12" + version: "2.0.13" shared_preferences_ios: dependency: transitive description: @@ -997,7 +997,7 @@ packages: name: shared_preferences_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" shared_preferences_web: dependency: transitive description: @@ -1130,7 +1130,7 @@ packages: name: synchronized url: "https://pub.dartlang.org" source: hosted - version: "3.0.0+2" + version: "3.0.0+3" term_glyph: dependency: transitive description: @@ -1200,7 +1200,7 @@ packages: name: url_launcher_android url: "https://pub.dartlang.org" source: hosted - version: "6.0.17" + version: "6.0.19" url_launcher_ios: dependency: transitive description: @@ -1284,14 +1284,14 @@ packages: name: webkit_inspection_protocol url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" win32: dependency: transitive description: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.7.0" + version: "3.0.0" wkt_parser: dependency: transitive description: @@ -1305,7 +1305,7 @@ packages: name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "0.2.0+1" + version: "0.2.0+2" xml: dependency: "direct main" description: @@ -1321,5 +1321,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.17.0 <3.0.0" - flutter: ">=3.1.0-0.0.pre.1036" + dart: ">=2.18.0 <3.0.0" + flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index 83374102b..0864cf230 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,11 +6,11 @@ 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.13+79 +version: 1.7.0+80 publish_to: none environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.18.0 <3.0.0" # following https://github.blog/2021-09-01-improving-git-protocol-security-github/ # dependency GitHub repos should be referenced via `https://`, not `git://` diff --git a/shaders_3.3.0-0.5.pre.sksl.json b/shaders_3.3.0-0.5.pre.sksl.json deleted file mode 100644 index 1eb8b7257..000000000 --- a/shaders_3.3.0-0.5.pre.sksl.json +++ /dev/null @@ -1 +0,0 @@ -{"platform":"android","name":"SM G970N","engineRevision":"ad3d868e0d174f5a8447b011bd714d18a5f2abec","data":{"B2AAQAAABQAAIAABBYAAB7777777777774ABICAAAAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CQAAAExTS1MOAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmIGluQ292ZXJhZ2U7Cm91dCBoYWxmIHZpbkNvdmVyYWdlX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cgl2aW5Db3ZlcmFnZV9TMCA9IGluQ292ZXJhZ2U7Cglza19Qb3NpdGlvbiA9IF90bXBfMV9pblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAZQEAAHVuaWZvcm0gaGFsZjQgdUNvbG9yX1MwOwppbiBoYWxmIHZpbkNvdmVyYWdlX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdUNvbG9yX1MwOwoJaGFsZiBhbHBoYSA9IDEuMDsKCWFscGhhID0gdmluQ292ZXJhZ2VfUzA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACgAAAGluUG9zaXRpb24AAAoAAABpbkNvdmVyYWdlAAABAAAAAAAAAA==","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CQAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoAAAAAKQEAAGZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","HVIACAAAABQAAGAAAQ4AAAAAGQQAARC4GAAAIOCAAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAAEAQCAAAAAVREEAQAAAAAQCDAAQQGAABAEAAAAAAH4AQAAAAAEAAAAAQGIAAAAAAA":"CQAAAExTS1PfAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdCBjb3ZlcmFnZTsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdCB2Y292ZXJhZ2VfUzA7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwoJewoJCXZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwID0gZmxvYXQzeDIodW1hdHJpeF9TMV9jMCkgKiBsb2NhbENvb3JkLnh5MTsKCX0KfQoAAAAAAEAFAAAjZXh0ZW5zaW9uIEdMX0VYVF9zaGFkZXJfZnJhbWVidWZmZXJfZmV0Y2g6IHJlcXVpcmUKdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQgdmNvdmVyYWdlX1MwOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7Cmlub3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkID0gY2xhbXAoc3Vic2V0Q29vcmQsIHVjbGFtcF9TMV9jMF9jMC54eSwgdWNsYW1wX1MxX2MwX2MwLnp3KTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKF9pbnB1dCk7Cn0KaGFsZjQgRGlzYWJsZUNvdmVyYWdlQXNBbHBoYV9TMShoYWxmNCBfaW5wdXQpIAp7CglfaW5wdXQgPSBNYXRyaXhFZmZlY3RfUzFfYzAoX2lucHV0KTsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJcmV0dXJuIGhhbGY0KF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWZsb2F0IGNvdmVyYWdlID0gdmNvdmVyYWdlX1MwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChoYWxmKGNvdmVyYWdlKSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKG91dHB1dENvbG9yX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYgU2hhZGVyCgkJaGFsZjQgX2RzdENvbG9yID0gc2tfRnJhZ0NvbG9yOwoJCXNrX0ZyYWdDb2xvciA9IGJsZW5kX3NyYyhvdXRwdXRfUzEsIF9kc3RDb2xvcik7CgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q292ZXJhZ2VfUzAgKiBza19GcmFnQ29sb3IgKyAoaGFsZjQoMS4wKSAtIG91dHB1dENvdmVyYWdlX1MwKSAqIF9kc3RDb2xvcjsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAQAAAAAAAAAAAAAABAAAAAgAAABwb3NpdGlvbggAAABjb3ZlcmFnZQUAAABjb2xvcgAAAAoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAIBAIAAAABLCIIBAAAAABAEGABBAMAACAIAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CQAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAADhAwAAdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZCA9IGNsYW1wKHN1YnNldENvb3JkLCB1Y2xhbXBfUzFfYzBfYzAueHksIHVjbGFtcF9TMV9jMF9jMC56dyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMF9jMChfaW5wdXQpOwp9CmhhbGY0IEJsZW5kX1MxKGhhbGY0IF9zcmMsIGhhbGY0IF9kc3QpIAp7CglyZXR1cm4gYmxlbmRfbW9kdWxhdGUoTWF0cml4RWZmZWN0X1MxX2MwKF9zcmMpLCBfc3JjKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IEJsZW5kX1MxKG91dHB1dENvbG9yX1MwLCBoYWxmNCgxKSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABZQA6AAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACnAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCWFscGhhID0gMS4wIC0gYWxwaGE7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDQgY2lyY2xlRWRnZTsKCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","HWQACAAAABAAADAAAIOAAAAADIIAAIRODAAP577774DSAIAA737777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CQAAAExTS1ONAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQgY292ZXJhZ2U7CmluIGhhbGY0IGNvbG9yOwppbiBmbG9hdDQgZ2VvbVN1YnNldDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQgdmNvdmVyYWdlX1MwOwpmbGF0IG91dCBmbG9hdDQgdmdlb21TdWJzZXRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJdmdlb21TdWJzZXRfUzAgPSBnZW9tU3Vic2V0OwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAAAgBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdCB2Y292ZXJhZ2VfUzA7CmZsYXQgaW4gZmxvYXQ0IHZnZW9tU3Vic2V0X1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjdWxhclJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCBjb3ZlcmFnZSA9IHZjb3ZlcmFnZV9TMDsKCWZsb2F0NCBnZW9TdWJzZXQ7CglnZW9TdWJzZXQgPSB2Z2VvbVN1YnNldF9TMDsKCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEsIDEsIC0xLCAtMSkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIGdlb1N1YnNldCksIDAsIDEpOwoJaGFsZjIgZGlzdHMyID0gZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3IC0gMTsKCWhhbGYgc3Vic2V0Q292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJY292ZXJhZ2UgPSBtaW4oY292ZXJhZ2UsIHN1YnNldENvdmVyYWdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoaGFsZihjb3ZlcmFnZSkpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY3VsYXJSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAIAAAAcG9zaXRpb24IAAAAY292ZXJhZ2UFAAAAY29sb3IAAAAKAAAAZ2VvbVN1YnNldAAAAQAAAAAAAAA=","CMRQCIAABBYAAAEIXBAAACDQMAABRAFAAAAAAAAAAAAAAAEABYAAAAEAAAAAAAEEBQAAAAA":"CQAAAExTS1MyAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0MiBpbkVsbGlwc2VPZmZzZXQ7CmluIGZsb2F0NCBpbkVsbGlwc2VSYWRpaTsKb3V0IGZsb2F0MiB2RWxsaXBzZU9mZnNldHNfUzA7Cm91dCBmbG9hdDQgdkVsbGlwc2VSYWRpaV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBFbGxpcHNlR2VvbWV0cnlQcm9jZXNzb3IKCXZFbGxpcHNlT2Zmc2V0c19TMCA9IGluRWxsaXBzZU9mZnNldDsKCXZFbGxpcHNlUmFkaWlfUzAgPSBpbkVsbGlwc2VSYWRpaTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAAAIoDAABpbiBmbG9hdDIgdkVsbGlwc2VPZmZzZXRzX1MwOwppbiBmbG9hdDQgdkVsbGlwc2VSYWRpaV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBFbGxpcHNlR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0MiBvZmZzZXQgPSB2RWxsaXBzZU9mZnNldHNfUzAueHk7CglvZmZzZXQgKj0gdkVsbGlwc2VSYWRpaV9TMC54eTsKCWZsb2F0IHRlc3QgPSBkb3Qob2Zmc2V0LCBvZmZzZXQpIC0gMS4wOwoJZmxvYXQyIGdyYWQgPSAyLjAqb2Zmc2V0KnZFbGxpcHNlUmFkaWlfUzAueHk7CglmbG9hdCBncmFkX2RvdCA9IGRvdChncmFkLCBncmFkKTsKCWdyYWRfZG90ID0gbWF4KGdyYWRfZG90LCAxLjE3NTVlLTM4KTsKCWZsb2F0IGludmxlbiA9IGludmVyc2VzcXJ0KGdyYWRfZG90KTsKCWZsb2F0IGVkZ2VBbHBoYSA9IHNhdHVyYXRlKDAuNS10ZXN0Kmludmxlbik7CglvZmZzZXQgPSB2RWxsaXBzZU9mZnNldHNfUzAueHkqdkVsbGlwc2VSYWRpaV9TMC56dzsKCXRlc3QgPSBkb3Qob2Zmc2V0LCBvZmZzZXQpIC0gMS4wOwoJZ3JhZCA9IDIuMCpvZmZzZXQqdkVsbGlwc2VSYWRpaV9TMC56dzsKCWdyYWRfZG90ID0gZG90KGdyYWQsIGdyYWQpOwoJaW52bGVuID0gaW52ZXJzZXNxcnQoZ3JhZF9kb3QpOwoJZWRnZUFscGhhICo9IHNhdHVyYXRlKDAuNSt0ZXN0Kmludmxlbik7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGhhbGYoZWRnZUFscGhhKSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAA8AAABpbkVsbGlwc2VPZmZzZXQADgAAAGluRWxsaXBzZVJhZGlpAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQAAEAQAAAAGQCBAMQAAAIAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CQAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAIgDAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBzdWJzZXRDb29yZC54OwoJY2xhbXBlZENvb3JkLnkgPSBjbGFtcChzdWJzZXRDb29yZC55LCB1Y2xhbXBfUzFfYzAueSwgdWNsYW1wX1MxX2MwLncpOwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAIAAIAAAABLCIABAAAAABAEGABBAMAACAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CQAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAEBAAAdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZC54ID0gY2xhbXAoc3Vic2V0Q29vcmQueCwgdWNsYW1wX1MxX2MwX2MwLngsIHVjbGFtcF9TMV9jMF9jMC56KTsKCWNsYW1wZWRDb29yZC55ID0gc3Vic2V0Q29vcmQueTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQmxlbmRfUzEoaGFsZjQgX3NyYywgaGFsZjQgX2RzdCkgCnsKCXJldHVybiBibGVuZF9tb2R1bGF0ZShNYXRyaXhFZmZlY3RfUzFfYzAoX3NyYyksIF9zcmMpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwID0gaGFsZjQoMSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQmxlbmRfUzEob3V0cHV0Q29sb3JfUzAsIGhhbGY0KDEpKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRfUzEgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","AYTRVAADQAAAOAEARAFQJAABBADAAAILBYAACCYUQD777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CQAAAExTS1NyAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7CmluIGhhbGYzIGluQ2xpcFBsYW5lOwppbiBoYWxmMyBpbklzZWN0UGxhbmU7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGYzIHZpbkNsaXBQbGFuZV9TMDsKb3V0IGhhbGYzIHZpbklzZWN0UGxhbmVfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCXZpbkNpcmNsZUVkZ2VfUzAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5DbGlwUGxhbmVfUzAgPSBpbkNsaXBQbGFuZTsKCXZpbklzZWN0UGxhbmVfUzAgPSBpbklzZWN0UGxhbmU7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gdWxvY2FsTWF0cml4X1MwLnh6ICogaW5Qb3NpdGlvbiArIHVsb2NhbE1hdHJpeF9TMC55dzsKCXNrX1Bvc2l0aW9uID0gX3RtcF8wX2luUG9zaXRpb24ueHkwMTsKfQoAAAAAAAD1AwAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGYzIHZpbkNsaXBQbGFuZV9TMDsKaW4gaGFsZjMgdmluSXNlY3RQbGFuZV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGYzIGNsaXBQbGFuZTsKCWNsaXBQbGFuZSA9IHZpbkNsaXBQbGFuZV9TMDsKCWhhbGYzIGlzZWN0UGxhbmU7Cglpc2VjdFBsYW5lID0gdmluSXNlY3RQbGFuZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZiBkaXN0YW5jZVRvSW5uZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoZCAtIGNpcmNsZUVkZ2UudykpOwoJaGFsZiBpbm5lckFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb0lubmVyRWRnZSk7CgllZGdlQWxwaGEgKj0gaW5uZXJBbHBoYTsKCWhhbGYgY2xpcCA9IGhhbGYoc2F0dXJhdGUoY2lyY2xlRWRnZS56ICogZG90KGNpcmNsZUVkZ2UueHksIGNsaXBQbGFuZS54eSkgKyBjbGlwUGxhbmUueikpOwoJY2xpcCAqPSBoYWxmKHNhdHVyYXRlKGNpcmNsZUVkZ2UueiAqIGRvdChjaXJjbGVFZGdlLnh5LCBpc2VjdFBsYW5lLnh5KSArIGlzZWN0UGxhbmUueikpOwoJZWRnZUFscGhhICo9IGNsaXA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlCwAAAGluQ2xpcFBsYW5lAAwAAABpbklzZWN0UGxhbmUBAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQBAAAQAAAAGQCBAMQACAAAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CQAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAIgDAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBjbGFtcChzdWJzZXRDb29yZC54LCB1Y2xhbXBfUzFfYzAueCwgdWNsYW1wX1MxX2MwLnopOwoJY2xhbXBlZENvb3JkLnkgPSBzdWJzZXRDb29yZC55OwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAEQQGABZAA6IAAAAACAAAAAADUAAAAAAAEAAAAAIDEAAAAAAA":"CQAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABPAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlckV4dGVybmFsT0VTIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAACEA2X4PLOGEAAAAAAAAACAAAAAVQQAAQAAAAAQCDIBCAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAAAAAAA":"CQAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoBAAAACwQAAHVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1Y2lyY2xlRGF0YV9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBfY29vcmRzKS4wMDByOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBDaXJjbGVCbHVyX1MxKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjIgdmVjID0gaGFsZjIoKHNrX0ZyYWdDb29yZC54eSAtIGZsb2F0Mih1Y2lyY2xlRGF0YV9TMS54eSkpICogZmxvYXQodWNpcmNsZURhdGFfUzEudykpOwoJaGFsZiBkaXN0ID0gbGVuZ3RoKHZlYykgKyAoMC41IC0gdWNpcmNsZURhdGFfUzEueikgKiB1Y2lyY2xlRGF0YV9TMS53OwoJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIE1hdHJpeEVmZmVjdF9TMV9jMChfdG1wXzBfaW5Db2xvciwgZmxvYXQyKGhhbGYyKGRpc3QsIDAuNSkpKS53KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmNsZUJsdXJfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAEAAAAAAAAA","HUQACAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CQAAAExTS1PPAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAEAAACzAgAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGhhbGY0IHZjb2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAEAAAAAAAAA","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CQAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoBAAAAuAIAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzE7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAAAAIAAAABLAIABAAAAABAEGABBAMAAAAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CQAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAADOAgAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoX2lucHV0KTsKfQpoYWxmNCBCbGVuZF9TMShoYWxmNCBfc3JjLCBoYWxmNCBfZHN0KSAKewoJcmV0dXJuIGJsZW5kX21vZHVsYXRlKE1hdHJpeEVmZmVjdF9TMV9jMChfc3JjKSwgX3NyYyk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBCbGVuZF9TMShvdXRwdXRDb2xvcl9TMCwgaGFsZjQoMSkpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGAARAGQWMHGBRIAAAAABQAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CQAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABhBAAAY29uc3QgaW50IGtGaWxsQUFfUzEgPSAxOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1Y2lyY2xlX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZsb2NhbENvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjbGVfUzEoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGQ7CglpZiAoaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMSkgCgl7CgkJZCA9IGhhbGYoKGxlbmd0aCgodWNpcmNsZV9TMS54eSAtIHNrX0ZyYWdDb29yZC54eSkgKiB1Y2lyY2xlX1MxLncpIC0gMS4wKSAqIHVjaXJjbGVfUzEueik7Cgl9CgllbHNlIAoJewoJCWQgPSBoYWxmKCgxLjAgLSBsZW5ndGgoKHVjaXJjbGVfUzEueHkgLSBza19GcmFnQ29vcmQueHkpICogdWNpcmNsZV9TMS53KSkgKiB1Y2lyY2xlX1MxLnopOwoJfQoJaWYgKGludCgxKSA9PSBrRmlsbEFBX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMSkgCgl7CgkJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIHNhdHVyYXRlKGQpKTsKCX0KCWVsc2UgCgl7CgkJcmV0dXJuIGhhbGY0KGQgPiAwLjUgPyBfaW5wdXQgOiBoYWxmNCgwLjApKTsKCX0KfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJZmxvYXQyIHRleENvb3JkOwoJdGV4Q29vcmQgPSB2bG9jYWxDb29yZF9TMDsKCW91dHB1dENvbG9yX1MwID0gKChzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHRleENvb3JkKSAqIGhhbGY0KDEpKSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY2xlX1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGACQAGAAAAAQAAAAAAAQQGAAA":"CQAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAAAAAC3AQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGZsb2F0MiB2bG9jYWxDb29yZF9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAIAAEAAAABJYQAAAAAQAAIAAAAAWCBACAABAAAAANAECAZAAEAAAAAAAAFAAMAAAABAAAAAAABBAM":"CQAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAEcGAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1SW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGYyIHVPZmZzZXRzQW5kS2VybmVsX1MxX2MwWzEzXTsKdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglmbG9hdDIgaW5Db29yZCA9IF9jb29yZHM7CglmbG9hdDIgc3Vic2V0Q29vcmQ7CglzdWJzZXRDb29yZC54ID0gaW5Db29yZC54OwoJc3Vic2V0Q29vcmQueSA9IGluQ29vcmQueTsKCWZsb2F0MiBjbGFtcGVkQ29vcmQ7CgljbGFtcGVkQ29vcmQueCA9IGNsYW1wKHN1YnNldENvb3JkLngsIHVjbGFtcF9TMV9jMF9jMF9jMC54LCB1Y2xhbXBfUzFfYzBfYzBfYzAueik7CgljbGFtcGVkQ29vcmQueSA9IHN1YnNldENvb3JkLnk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBTbW9vdGhfUzFfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgY29vcmQsIGhhbGYyIG9mZnNldEFuZEtlcm5lbCkgCnsKCXJldHVybiBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCAoY29vcmQgKyBvZmZzZXRBbmRLZXJuZWwueCAqIHVJbmNyZW1lbnRfUzFfYzApKSAqIG9mZnNldEFuZEtlcm5lbC55Owp9CmhhbGY0IEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgY29sb3IgPSBoYWxmNCgwKTsKCWZsb2F0MiBjb29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZm9yIChpbnQgaT0wOyBpPDEzOyArK2kpIAoJewoJCWNvbG9yICs9IFNtb290aF9TMV9jMChfaW5wdXQsIGNvb3JkLCB1T2Zmc2V0c0FuZEtlcm5lbF9TMV9jMFtpXSk7Cgl9CglyZXR1cm4gY29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBHYXVzc2lhbkNvbnZvbHV0aW9uX1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUQACAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CQAAAExTS1PPAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAkAQAAaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","HTQAAGAABBYAAAEIXBAAAGEAMAAAAAAAAAAAAAAAQAHAAAAAQAAAAAAAQQGAAAAA":"CQAAAExTS1M/AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5RdWFkRWRnZTsKb3V0IGZsb2F0NCB2UXVhZEVkZ2VfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZEVkZ2UKCXZRdWFkRWRnZV9TMCA9IGluUXVhZEVkZ2U7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgABAAAAHQMAAGluIGZsb2F0NCB2UXVhZEVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZEVkZ2UKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGYgZWRnZUFscGhhOwoJaGFsZjIgZHV2ZHggPSBoYWxmMihkRmR4KHZRdWFkRWRnZV9TMC54eSkpOwoJaGFsZjIgZHV2ZHkgPSBoYWxmMihkRmR5KHZRdWFkRWRnZV9TMC54eSkpOwoJaWYgKHZRdWFkRWRnZV9TMC56ID4gMC4wICYmIHZRdWFkRWRnZV9TMC53ID4gMC4wKSAKCXsKCQllZGdlQWxwaGEgPSBoYWxmKG1pbihtaW4odlF1YWRFZGdlX1MwLnosIHZRdWFkRWRnZV9TMC53KSArIDAuNSwgMS4wKSk7Cgl9CgllbHNlIAoJewoJCWhhbGYyIGdGID0gaGFsZjIoaGFsZigyLjAqdlF1YWRFZGdlX1MwLngqZHV2ZHgueCAtIGR1dmR4LnkpLCAgICAgICAgICAgICAgICAgaGFsZigyLjAqdlF1YWRFZGdlX1MwLngqZHV2ZHkueCAtIGR1dmR5LnkpKTsKCQllZGdlQWxwaGEgPSBoYWxmKHZRdWFkRWRnZV9TMC54KnZRdWFkRWRnZV9TMC54IC0gdlF1YWRFZGdlX1MwLnkpOwoJCWVkZ2VBbHBoYSA9IHNhdHVyYXRlKDAuNSAtIGVkZ2VBbHBoYSAvIGxlbmd0aChnRikpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IACgAAAGluUXVhZEVkZ2UAAAEAAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAACAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAAAAEAAAABJYQAAAAAAAAIAAAAAWCBAAAABAAAAANAECAZAAAAAAAAAAAFAAMAAAABAAAAAAABBAM":"CQAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAAgFAAB1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzA7CnVuaWZvcm0gaGFsZjIgdUluY3JlbWVudF9TMV9jMDsKdW5pZm9ybSBoYWxmMiB1T2Zmc2V0c0FuZEtlcm5lbF9TMV9jMFsxM107CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgX2Nvb3Jkcyk7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChfaW5wdXQsIGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzApICogX2Nvb3Jkcy54eTEpOwp9CmhhbGY0IFNtb290aF9TMV9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBjb29yZCwgaGFsZjIgb2Zmc2V0QW5kS2VybmVsKSAKewoJcmV0dXJuIE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfaW5wdXQsIChjb29yZCArIG9mZnNldEFuZEtlcm5lbC54ICogdUluY3JlbWVudF9TMV9jMCkpICogb2Zmc2V0QW5kS2VybmVsLnk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBjb2xvciA9IGhhbGY0KDApOwoJZmxvYXQyIGNvb3JkID0gdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzA7Cglmb3IgKGludCBpPTA7IGk8MTM7ICsraSkgCgl7CgkJY29sb3IgKz0gU21vb3RoX1MxX2MwKF9pbnB1dCwgY29vcmQsIHVPZmZzZXRzQW5kS2VybmVsX1MxX2MwW2ldKTsKCX0KCXJldHVybiBjb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","HVJAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAABAAAAAABBAMABAAOAAAABAAAAAAABBAMAAA":"CQAAAExTS1MjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cgl2bG9jYWxDb29yZF9TMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAAAAADoAQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdDIgdGV4Q29vcmQ7Cgl0ZXhDb29yZCA9IHZsb2NhbENvb3JkX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSAoKHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdGV4Q29vcmQpICogb3V0cHV0Q29sb3JfUzApKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEADZABYAAAIAAAAAACQAGAAAAAQAAAAAAAQQG":"CQAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAADsAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjdWxhclJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1MwLngsIHk9dmFyY2Nvb3JkX1MwLnk7CgloYWxmIGNvdmVyYWdlOwoJaWYgKDAgPT0geF9wbHVzXzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZih5KTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQgZm4gPSB4X3BsdXNfMSAqICh4X3BsdXNfMSAtIDIpOwoJCWZuID0gZm1hKHkseSwgZm4pOwoJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABAAAAHNrZXcZAAAAdHJhbnNsYXRlX2FuZF9sb2NhbHJvdGF0ZQAAAAUAAABjb2xvcgAAAAEAAAAAAAAA","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEAZCBRE4GNEACAAAOAAAAAAAAAAABAAOAAAABAAAAAAABBAMAA":"CQAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAAAHBQAAY29uc3QgaW50IGtGaWxsQUFfUzEgPSAxOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1Y2lyY2xlX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZhcmNjb29yZF9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY2xlX1MxKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZiBkOwoJaWYgKGludCgzKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMykgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWQgPSBoYWxmKChsZW5ndGgoKHVjaXJjbGVfUzEueHkgLSBza19GcmFnQ29vcmQueHkpICogdWNpcmNsZV9TMS53KSAtIDEuMCkgKiB1Y2lyY2xlX1MxLnopOwoJfQoJZWxzZSAKCXsKCQlkID0gaGFsZigoMS4wIC0gbGVuZ3RoKCh1Y2lyY2xlX1MxLnh5IC0gc2tfRnJhZ0Nvb3JkLnh5KSAqIHVjaXJjbGVfUzEudykpICogdWNpcmNsZV9TMS56KTsKCX0KCWlmIChpbnQoMykgPT0ga0ZpbGxBQV9TMSB8fCBpbnQoMykgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCXJldHVybiBoYWxmNChfaW5wdXQgKiBzYXR1cmF0ZShkKSk7Cgl9CgllbHNlIAoJewoJCXJldHVybiBoYWxmNChkID4gMC41ID8gX2lucHV0IDogaGFsZjQoMC4wKSk7Cgl9Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCB4X3BsdXNfMT12YXJjY29vcmRfUzAueCwgeT12YXJjY29vcmRfUzAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGNvdmVyYWdlKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmNsZV9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAEAAAAc2tldxkAAAB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlAAAABQAAAGNvbG9yAAAAAQAAAAAAAAA=","AYQQ5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAMQAHOMFARUBIAADAAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACtBQAAY29uc3QgaW50IGtGaWxsQldfUzEgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgY292ZXJhZ2U7CglpZiAoaW50KDEpID09IGtGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TMS56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fUzEueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCX0KCWVsc2UgCgl7CgkJaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fUzEpLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJaWYgKGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gaGFsZjQoX2lucHV0ICogY292ZXJhZ2UpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZiBkaXN0YW5jZVRvSW5uZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoZCAtIGNpcmNsZUVkZ2UudykpOwoJaGFsZiBpbm5lckFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb0lubmVyRWRnZSk7CgllZGdlQWxwaGEgKj0gaW5uZXJBbHBoYTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IFJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2UBAAAAAAAAAA==","HWQACAAAABAAADAAAIOAAAAADIIAAIRODAAP577774DSAIAA737777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CQAAAExTS1ONAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQgY292ZXJhZ2U7CmluIGhhbGY0IGNvbG9yOwppbiBmbG9hdDQgZ2VvbVN1YnNldDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQgdmNvdmVyYWdlX1MwOwpmbGF0IG91dCBmbG9hdDQgdmdlb21TdWJzZXRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJdmdlb21TdWJzZXRfUzAgPSBnZW9tU3Vic2V0OwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAACRAgAAZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0IHZjb3ZlcmFnZV9TMDsKZmxhdCBpbiBmbG9hdDQgdmdlb21TdWJzZXRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCBjb3ZlcmFnZSA9IHZjb3ZlcmFnZV9TMDsKCWZsb2F0NCBnZW9TdWJzZXQ7CglnZW9TdWJzZXQgPSB2Z2VvbVN1YnNldF9TMDsKCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEsIDEsIC0xLCAtMSkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIGdlb1N1YnNldCksIDAsIDEpOwoJaGFsZjIgZGlzdHMyID0gZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3IC0gMTsKCWhhbGYgc3Vic2V0Q292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJY292ZXJhZ2UgPSBtaW4oY292ZXJhZ2UsIHN1YnNldENvdmVyYWdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoaGFsZihjb3ZlcmFnZSkpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAIAAAAcG9zaXRpb24IAAAAY292ZXJhZ2UFAAAAY29sb3IAAAAKAAAAZ2VvbVN1YnNldAAAAQAAAAAAAAA=","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABYQACAAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAADkAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5ID0gbWF4KHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHksIDAuMCk7CgloYWxmIHJpZ2h0QWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVpbm5lclJlY3RfUzEuUiAtIHNrX0ZyYWdDb29yZC54KSk7CgloYWxmIGJvdHRvbUFscGhhID0gaGFsZihzYXR1cmF0ZSh1aW5uZXJSZWN0X1MxLkIgLSBza19GcmFnQ29vcmQueSkpOwoJaGFsZiBhbHBoYSA9IGJvdHRvbUFscGhhICogcmlnaHRBbHBoYSAqIGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY3VsYXJSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAFBQATAAAAAAFAAMAAAABAAAAAAABBAMAAAAA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAABYBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBmbG9hdDIgdWludlJhZGlpWFlfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgRWxsaXB0aWNhbFJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJZmxvYXQyIFogPSBkeHkgKiB1aW52UmFkaWlYWV9TMS54eTsKCWhhbGYgaW1wbGljaXQgPSBoYWxmKGRvdChaLCBkeHkpIC0gMS4wKTsKCWhhbGYgZ3JhZF9kb3QgPSBoYWxmKDQuMCAqIGRvdChaLCBaKSk7CglncmFkX2RvdCA9IG1heChncmFkX2RvdCwgMS4wZS00KTsKCWhhbGYgYXBwcm94X2Rpc3QgPSBpbXBsaWNpdCAqIGhhbGYoaW52ZXJzZXNxcnQoZ3JhZF9kb3QpKTsKCWhhbGYgYWxwaGEgPSBjbGFtcCgwLjUgKyBhcHByb3hfZGlzdCwgMC4wLCAxLjApOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gRWxsaXB0aWNhbFJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlAQAAAAAAAAA=","DASAAAAAQAAWAABAYAAQBYH7777Z6QQBAEAAAAAAEAAAAAAAEBSAAAB2AAAAAAACAAAAAEBSAAAAA":"CQAAAExTS1PVAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBCaXRtYXBUZXh0CglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkcyAqIHVBdGxhc1NpemVJbnZfUzA7Cgl2VGV4SW5kZXhfUzAgPSBmbG9hdCh0ZXhJZHgpOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAAAAD4AQAAdW5pZm9ybSBoYWxmNCB1Q29sb3JfUzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdlRleHR1cmVDb29yZHNfUzA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHVDb2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCk7Cgl9CglvdXRwdXRDb2xvcl9TMCA9IG91dHB1dENvbG9yX1MwICogdGV4Q29sb3I7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAADwAAAGluVGV4dHVyZUNvb3JkcwABAAAAAAAAAA==","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABYQA6AAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACRAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2UBAAAAAAAAAA==","BYIBQAAABQAAIAABBYAAAEIXBAAP777777777777AAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CQAAAExTS1M+AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwpvdXQgaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IGNvbG9yID0gaW5Db2xvcjsKCXZjb2xvcl9TMCA9IGNvbG9yOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzNfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IF90bXBfMV9pblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAHgEAAGluIGhhbGY0IHZjb2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIERlZmF1bHRHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAEAAAAAAAAA","GEMAAAYAAEHAAAARC4EAAAQWBQAAAAAAAAAQAAAAIBCAAAGQAEAAAAAQAAAABAEQAEAAAAA":"CQAAAExTS1NUAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBoYWxmMyBpblNoYWRvd1BhcmFtczsKb3V0IGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBSUmVjdFNoYWRvdwoJdmluU2hhZG93UGFyYW1zX1MwID0gaW5TaGFkb3dQYXJhbXM7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAjAgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBSUmVjdFNoYWRvdwoJaGFsZjMgc2hhZG93UGFyYW1zOwoJc2hhZG93UGFyYW1zID0gdmluU2hhZG93UGFyYW1zX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZiBkID0gbGVuZ3RoKHNoYWRvd1BhcmFtcy54eSk7CglmbG9hdDIgdXYgPSBmbG9hdDIoc2hhZG93UGFyYW1zLnogKiAoMS4wIC0gZCksIDAuNSk7CgloYWxmIGZhY3RvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdXYpLjAwMHIuYTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZmFjdG9yKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAOAAAAaW5TaGFkb3dQYXJhbXMAAAEAAAAAAAAA","AYAA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CQAAAExTS1OCAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCXZpbkNpcmNsZUVkZ2VfUzAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMl9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gX3RtcF8wX2luUG9zaXRpb24ueHkwMTsKfQoAAAAAAAACAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAABAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAIAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIA":"CQAAAExTS1OAAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfNV9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAOMGAAB1bmlmb3JtIGhhbGY0IHVzdGFydF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1ZW5kX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzE7CnVuaWZvcm0gaGFsZjQgdWxlZnRCb3JkZXJDb2xvcl9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TMV9jMDsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJZmxvYXQyIF90bXBfMV9jb29yZHMgPSBfY29vcmRzOwoJcmV0dXJuIGhhbGY0KG1peCh1c3RhcnRfUzFfYzBfYzAsIHVlbmRfUzFfYzBfYzAsIGhhbGYoX3RtcF8xX2Nvb3Jkcy54KSkpOwp9CmhhbGY0IExpbmVhckxheW91dF9TMV9jMF9jMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzJfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzNfY29vcmRzID0gdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CglyZXR1cm4gaGFsZjQoaGFsZjQoaGFsZihfdG1wXzNfY29vcmRzLngpICsgOS45OTk5OTk3NDczNzg3NTE2ZS0wNiwgMS4wLCAwLjAsIDAuMCkpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyTGF5b3V0X1MxX2MwX2MxX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQ2xhbXBlZEdyYWRpZW50X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfNF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjQgdCA9IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShfdG1wXzRfaW5Db2xvcik7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghYm9vbChpbnQoMSkpICYmIHQueSA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSBoYWxmNCgwLjApOwoJfQoJZWxzZSBpZiAodC54IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IHVsZWZ0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIGlmICh0LnggPiAxLjApIAoJewoJCW91dENvbG9yID0gdXJpZ2h0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoX3RtcF80X2luQ29sb3IsIGZsb2F0MihoYWxmMih0LngsIDAuMCkpKTsKCX0KCWlmIChib29sKGludCgwKSkpIAoJewoJCW91dENvbG9yLnh5eiAqPSBvdXRDb2xvci53OwoJfQoJcmV0dXJuIGhhbGY0KG91dENvbG9yKTsKfQpoYWxmNCBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKGhhbGY0IF9pbnB1dCkgCnsKCV9pbnB1dCA9IENsYW1wZWRHcmFkaWVudF9TMV9jMChfaW5wdXQpOwoJaGFsZjQgX3RtcF81X2luQ29sb3IgPSBfaW5wdXQ7CglyZXR1cm4gaGFsZjQoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IERpc2FibGVDb3ZlcmFnZUFzQWxwaGFfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAIAHSADQAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"CQAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAABAAAAWAMAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzE7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1MwOwpmbGF0IGluIGZsb2F0IHZUZXhJbmRleF9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gdGV4Q29sb3I7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAPAAAAaW5UZXh0dXJlQ29vcmRzAAEAAAAAAAAA","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAGIBIAAABAAAAANAEAAAAAAAAAAAAAABAAOAAAABAAAAAAABBAMAAAAA":"CQAAAExTS1N0AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMl9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAIsCAAB1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwKS5ycnJyOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gTWF0cml4RWZmZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAAKAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","B2ABSAAABQAAIAABBYAAB7777777777774ABICAAAAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CQAAAExTS1N4AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gaGFsZjQgdUNvbG9yX1MwOwppbiBmbG9hdDIgaW5Qb3NpdGlvbjsKaW4gaGFsZiBpbkNvdmVyYWdlOwpvdXQgaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IGNvbG9yID0gdUNvbG9yX1MwOwoJY29sb3IgPSBjb2xvciAqIGluQ292ZXJhZ2U7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8zX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzFfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAeAQAAaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAACgAAAGluQ292ZXJhZ2UAAAEAAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABYQAMAAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAADuAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdCBkeTAgPSB1aW5uZXJSZWN0X1MxLlQgLSBza19GcmFnQ29vcmQueTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgoZmxvYXQyKGR4eTEueCwgbWF4KGR5MCwgZHh5MS55KSksIDAuMCk7CgloYWxmIGxlZnRBbHBoYSA9IGhhbGYoc2F0dXJhdGUoc2tfRnJhZ0Nvb3JkLnggLSB1aW5uZXJSZWN0X1MxLkwpKTsKCWhhbGYgYWxwaGEgPSBsZWZ0QWxwaGEgKiBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAABAEAAAABJYQAAAAAACAIAAAAAWCBAAAIBAAAAANAECAZAAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"CQAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAEcGAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1SW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGYyIHVPZmZzZXRzQW5kS2VybmVsX1MxX2MwWzEzXTsKdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglmbG9hdDIgaW5Db29yZCA9IF9jb29yZHM7CglmbG9hdDIgc3Vic2V0Q29vcmQ7CglzdWJzZXRDb29yZC54ID0gaW5Db29yZC54OwoJc3Vic2V0Q29vcmQueSA9IGluQ29vcmQueTsKCWZsb2F0MiBjbGFtcGVkQ29vcmQ7CgljbGFtcGVkQ29vcmQueCA9IHN1YnNldENvb3JkLng7CgljbGFtcGVkQ29vcmQueSA9IGNsYW1wKHN1YnNldENvb3JkLnksIHVjbGFtcF9TMV9jMF9jMF9jMC55LCB1Y2xhbXBfUzFfYzBfYzBfYzAudyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBTbW9vdGhfUzFfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgY29vcmQsIGhhbGYyIG9mZnNldEFuZEtlcm5lbCkgCnsKCXJldHVybiBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCAoY29vcmQgKyBvZmZzZXRBbmRLZXJuZWwueCAqIHVJbmNyZW1lbnRfUzFfYzApKSAqIG9mZnNldEFuZEtlcm5lbC55Owp9CmhhbGY0IEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgY29sb3IgPSBoYWxmNCgwKTsKCWZsb2F0MiBjb29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZm9yIChpbnQgaT0wOyBpPDEzOyArK2kpIAoJewoJCWNvbG9yICs9IFNtb290aF9TMV9jMChfaW5wdXQsIGNvb3JkLCB1T2Zmc2V0c0FuZEtlcm5lbF9TMV9jMFtpXSk7Cgl9CglyZXR1cm4gY29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBHYXVzc2lhbkNvbnZvbHV0aW9uX1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAB3QA6AAAEAAAAAAAMAAPEAEAAABAAAAAAB2AAAAAAACAAAAAEBSAAAA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAAA2BQAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzI7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MyOwppbiBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglhbHBoYSA9IDEuMCAtIGFscGhhOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CmhhbGY0IENpcmN1bGFyUlJlY3RfUzIoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MyLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MyLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzIueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDQgY2lyY2xlRWRnZTsKCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCWhhbGY0IG91dHB1dF9TMjsKCW91dHB1dF9TMiA9IENpcmN1bGFyUlJlY3RfUzIob3V0cHV0X1MxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMjsKCX0KfQoAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2UBAAAAAAAAAA==","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAAA5AAAAAAABAAAAACAZAAAAA":"CQAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgAAAABdAgAAZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2YXJjY29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1MwLngsIHk9dmFyY2Nvb3JkX1MwLnk7CgloYWxmIGNvdmVyYWdlOwoJaWYgKDAgPT0geF9wbHVzXzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZih5KTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQgZm4gPSB4X3BsdXNfMSAqICh4X3BsdXNfMSAtIDIpOwoJCWZuID0gZm1hKHkseSwgZm4pOwoJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABAAAAHNrZXcZAAAAdHJhbnNsYXRlX2FuZF9sb2NhbHJvdGF0ZQAAAAUAAABjb2xvcgAAAAEAAAAAAAAA","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CQAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAyQEAAHVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdlRleHR1cmVDb29yZHNfUzA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gdGV4Q29sb3I7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAPAAAAaW5UZXh0dXJlQ29vcmRzAAEAAAAAAAAA","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEAZIA62YSBDACAAAGAAAAAAAAAAABAAOAAAABAAAAAAABBAMAA":"CQAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAAB3BQAAY29uc3QgaW50IGtGaWxsQldfUzEgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZiBjb3ZlcmFnZTsKCWlmIChpbnQoMSkgPT0ga0ZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQldfUzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZihhbGwoZ3JlYXRlclRoYW4oZmxvYXQ0KHNrX0ZyYWdDb29yZC54eSwgdXJlY3RVbmlmb3JtX1MxLnp3KSwgZmxvYXQ0KHVyZWN0VW5pZm9ybV9TMS54eSwgc2tfRnJhZ0Nvb3JkLnh5KSkpID8gMSA6IDApOwoJfQoJZWxzZSAKCXsKCQloYWxmNCBkaXN0czQgPSBjbGFtcChoYWxmNCgxLjAsIDEuMCwgLTEuMCwgLTEuMCkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIHVyZWN0VW5pZm9ybV9TMSksIDAuMCwgMS4wKTsKCQloYWxmMiBkaXN0czIgPSAoZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3KSAtIDEuMDsKCQljb3ZlcmFnZSA9IGRpc3RzMi54ICogZGlzdHMyLnk7Cgl9CglpZiAoaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMSkgCgl7CgkJY292ZXJhZ2UgPSAxLjAgLSBjb3ZlcmFnZTsKCX0KCXJldHVybiBoYWxmNChfaW5wdXQgKiBjb3ZlcmFnZSk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCB4X3BsdXNfMT12YXJjY29vcmRfUzAueCwgeT12YXJjY29vcmRfUzAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGNvdmVyYWdlKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IFJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABAAAAHNrZXcZAAAAdHJhbnNsYXRlX2FuZF9sb2NhbHJvdGF0ZQAAAAUAAABjb2xvcgAAAAEAAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAMQAHOMFARUBIAADAAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAAAcBQAAY29uc3QgaW50IGtGaWxsQldfUzEgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgY292ZXJhZ2U7CglpZiAoaW50KDEpID09IGtGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TMS56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fUzEueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCX0KCWVsc2UgCgl7CgkJaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fUzEpLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJaWYgKGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gaGFsZjQoX2lucHV0ICogY292ZXJhZ2UpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","EABQAAAAAEAAAAAQAABQAAIOAAABCFYIAAKAUDAAAAAAAAABAAAAAAAAAAANAAIAAAABAAAAACAJAAIAAAAA":"CQAAAExTS1OhAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc0RpbWVuc2lvbnNJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgZmxvYXQyIHZJbnRUZXh0dXJlQ29vcmRzX1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIERpc3RhbmNlRmllbGRQYXRoCglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkcyAqIHVBdGxhc0RpbWVuc2lvbnNJbnZfUzA7Cgl2VGV4SW5kZXhfUzAgPSBmbG9hdCh0ZXhJZHgpOwoJdkludFRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkczsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8yX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAAAAC3AgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBpbiBmbG9hdCB2VGV4SW5kZXhfUzA7CmluIGZsb2F0MiB2SW50VGV4dHVyZUNvb3Jkc19TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBEaXN0YW5jZUZpZWxkUGF0aAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQyIHV2ID0gdlRleHR1cmVDb29yZHNfUzA7CgloYWxmNCB0ZXhDb2xvcjsKCXsKCQl0ZXhDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdXYpLnJycnI7Cgl9CgloYWxmIGRpc3RhbmNlID0gNy45Njg3NSoodGV4Q29sb3IuciAtIDAuNTAxOTYwNzg0MzEpOwoJaGFsZiBhZndpZHRoOwoJYWZ3aWR0aCA9IGFicygwLjY1KmhhbGYoZEZkeCh2SW50VGV4dHVyZUNvb3Jkc19TMC54KSkpOwoJaGFsZiB2YWwgPSBzbW9vdGhzdGVwKC1hZndpZHRoLCBhZndpZHRoLCBkaXN0YW5jZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KHZhbCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADwAAAGluVGV4dHVyZUNvb3JkcwABAAAAAAAAAA==","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAIBSQB5VRECGAEAAAMAAAAAAAAAAACAA4AAAACAAAAAAACCAYAA":"CQAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAABAAAA4wQAAGNvbnN0IGludCBrRmlsbEJXX1MxID0gMDsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEJXX1MxID0gMjsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEFBX1MxID0gMzsKdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1MwOwpmbGF0IGluIGZsb2F0IHZUZXhJbmRleF9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGNvdmVyYWdlOwoJaWYgKGludCgxKSA9PSBrRmlsbEJXX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKGFsbChncmVhdGVyVGhhbihmbG9hdDQoc2tfRnJhZ0Nvb3JkLnh5LCB1cmVjdFVuaWZvcm1fUzEuencpLCBmbG9hdDQodXJlY3RVbmlmb3JtX1MxLnh5LCBza19GcmFnQ29vcmQueHkpKSkgPyAxIDogMCk7Cgl9CgllbHNlIAoJewoJCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEuMCwgMS4wLCAtMS4wLCAtMS4wKSAqIGhhbGY0KHNrX0ZyYWdDb29yZC54eXh5IC0gdXJlY3RVbmlmb3JtX1MxKSwgMC4wLCAxLjApOwoJCWhhbGYyIGRpc3RzMiA9IChkaXN0czQueHkgKyBkaXN0czQuencpIC0gMS4wOwoJCWNvdmVyYWdlID0gZGlzdHMyLnggKiBkaXN0czIueTsKCX0KCWlmIChpbnQoMSkgPT0ga0ludmVyc2VGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEFBX1MxKSAKCXsKCQljb3ZlcmFnZSA9IDEuMCAtIGNvdmVyYWdlOwoJfQoJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIGNvdmVyYWdlKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZjQgdGV4Q29sb3I7Cgl7CgkJdGV4Q29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHZUZXh0dXJlQ29vcmRzX1MwKS5ycnJyOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSB0ZXhDb2xvcjsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IFJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAPAAAAaW5UZXh0dXJlQ29vcmRzAAEAAAAAAAAA","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEAKPABAAAAAAB2AAAAAAACAAAAAEBSAAAAAAA":"CQAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAACzBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBmbG9hdDIgdWludlJhZGlpWFlfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBFbGxpcHRpY2FsUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CglmbG9hdDIgWiA9IGR4eSAqIHVpbnZSYWRpaVhZX1MxLnh5OwoJaGFsZiBpbXBsaWNpdCA9IGhhbGYoZG90KFosIGR4eSkgLSAxLjApOwoJaGFsZiBncmFkX2RvdCA9IGhhbGYoNC4wICogZG90KFosIFopKTsKCWdyYWRfZG90ID0gbWF4KGdyYWRfZG90LCAxLjBlLTQpOwoJaGFsZiBhcHByb3hfZGlzdCA9IGltcGxpY2l0ICogaGFsZihpbnZlcnNlc3FydChncmFkX2RvdCkpOwoJaGFsZiBhbHBoYSA9IGNsYW1wKDAuNSArIGFwcHJveF9kaXN0LCAwLjAsIDEuMCk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCB4X3BsdXNfMT12YXJjY29vcmRfUzAueCwgeT12YXJjY29vcmRfUzAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGNvdmVyYWdlKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IEVsbGlwdGljYWxSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAEAAAAc2tldxkAAAB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlAAAABQAAAGNvbG9yAAAAAQAAAAAAAAA=","HVIACAAAABQAAGAAAQ4AAAAAGQQAARC4GAAAIOCAAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAADAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAYAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAIAAQAAAAAQGIA":"CQAAAExTS1PlAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdCBjb3ZlcmFnZTsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdCB2Y292ZXJhZ2VfUzA7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwoJewoJCXZUcmFuc2Zvcm1lZENvb3Jkc181X1MwID0gZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMSkgKiBsb2NhbENvb3JkLnh5MTsKCX0KfQoAAAAAAAAAMAcAAHVuaWZvcm0gaGFsZjQgdXN0YXJ0X1MxX2MwX2MwOwp1bmlmb3JtIGhhbGY0IHVlbmRfUzFfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKdW5pZm9ybSBoYWxmNCB1bGVmdEJvcmRlckNvbG9yX1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVyaWdodEJvcmRlckNvbG9yX1MxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQgdmNvdmVyYWdlX1MwOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFNpbmdsZUludGVydmFsQ29sb3JpemVyX1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzFfY29vcmRzID0gX2Nvb3JkczsKCXJldHVybiBoYWxmNChtaXgodXN0YXJ0X1MxX2MwX2MwLCB1ZW5kX1MxX2MwX2MwLCBoYWxmKF90bXBfMV9jb29yZHMueCkpKTsKfQpoYWxmNCBMaW5lYXJMYXlvdXRfUzFfYzBfYzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8yX2luQ29sb3IgPSBfaW5wdXQ7CglmbG9hdDIgX3RtcF8zX2Nvb3JkcyA9IHZUcmFuc2Zvcm1lZENvb3Jkc181X1MwOwoJcmV0dXJuIGhhbGY0KGhhbGY0KGhhbGYoX3RtcF8zX2Nvb3Jkcy54KSArIDkuOTk5OTk5NzQ3Mzc4NzUxNmUtMDYsIDEuMCwgMC4wLCAwLjApKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIExpbmVhckxheW91dF9TMV9jMF9jMV9jMChfaW5wdXQpOwp9CmhhbGY0IENsYW1wZWRHcmFkaWVudF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzRfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGY0IHQgPSBNYXRyaXhFZmZlY3RfUzFfYzBfYzEoX3RtcF80X2luQ29sb3IpOwoJaGFsZjQgb3V0Q29sb3I7CglpZiAoIWJvb2woaW50KDEpKSAmJiB0LnkgPCAwLjApIAoJewoJCW91dENvbG9yID0gaGFsZjQoMC4wKTsKCX0KCWVsc2UgaWYgKHQueCA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSB1bGVmdEJvcmRlckNvbG9yX1MxX2MwOwoJfQoJZWxzZSBpZiAodC54ID4gMS4wKSAKCXsKCQlvdXRDb2xvciA9IHVyaWdodEJvcmRlckNvbG9yX1MxX2MwOwoJfQoJZWxzZSAKCXsKCQlvdXRDb2xvciA9IFNpbmdsZUludGVydmFsQ29sb3JpemVyX1MxX2MwX2MwKF90bXBfNF9pbkNvbG9yLCBmbG9hdDIoaGFsZjIodC54LCAwLjApKSk7Cgl9CglpZiAoYm9vbChpbnQoMSkpKSAKCXsKCQlvdXRDb2xvci54eXogKj0gb3V0Q29sb3IudzsKCX0KCXJldHVybiBoYWxmNChvdXRDb2xvcik7Cn0KaGFsZjQgRGlzYWJsZUNvdmVyYWdlQXNBbHBoYV9TMShoYWxmNCBfaW5wdXQpIAp7CglfaW5wdXQgPSBDbGFtcGVkR3JhZGllbnRfUzFfYzAoX2lucHV0KTsKCWhhbGY0IF90bXBfNV9pbkNvbG9yID0gX2lucHV0OwoJcmV0dXJuIGhhbGY0KF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWZsb2F0IGNvdmVyYWdlID0gdmNvdmVyYWdlX1MwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChoYWxmKGNvdmVyYWdlKSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKG91dHB1dENvbG9yX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSAoaGFsZjQoMS4wKSAtIG91dHB1dF9TMSkgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAgAAABwb3NpdGlvbggAAABjb3ZlcmFnZQUAAABjb2xvcgAAAAoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQBAEAQAAAAGQCBAMQACAIAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CQAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAGUDAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkID0gY2xhbXAoc3Vic2V0Q29vcmQsIHVjbGFtcF9TMV9jMC54eSwgdWNsYW1wX1MxX2MwLnp3KTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAADAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAYAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIA":"CQAAAExTS1OAAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfNV9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAOMGAAB1bmlmb3JtIGhhbGY0IHVzdGFydF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1ZW5kX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzE7CnVuaWZvcm0gaGFsZjQgdWxlZnRCb3JkZXJDb2xvcl9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TMV9jMDsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJZmxvYXQyIF90bXBfMV9jb29yZHMgPSBfY29vcmRzOwoJcmV0dXJuIGhhbGY0KG1peCh1c3RhcnRfUzFfYzBfYzAsIHVlbmRfUzFfYzBfYzAsIGhhbGYoX3RtcF8xX2Nvb3Jkcy54KSkpOwp9CmhhbGY0IExpbmVhckxheW91dF9TMV9jMF9jMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzJfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzNfY29vcmRzID0gdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CglyZXR1cm4gaGFsZjQoaGFsZjQoaGFsZihfdG1wXzNfY29vcmRzLngpICsgOS45OTk5OTk3NDczNzg3NTE2ZS0wNiwgMS4wLCAwLjAsIDAuMCkpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyTGF5b3V0X1MxX2MwX2MxX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQ2xhbXBlZEdyYWRpZW50X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfNF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjQgdCA9IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShfdG1wXzRfaW5Db2xvcik7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghYm9vbChpbnQoMSkpICYmIHQueSA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSBoYWxmNCgwLjApOwoJfQoJZWxzZSBpZiAodC54IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IHVsZWZ0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIGlmICh0LnggPiAxLjApIAoJewoJCW91dENvbG9yID0gdXJpZ2h0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoX3RtcF80X2luQ29sb3IsIGZsb2F0MihoYWxmMih0LngsIDAuMCkpKTsKCX0KCWlmIChib29sKGludCgxKSkpIAoJewoJCW91dENvbG9yLnh5eiAqPSBvdXRDb2xvci53OwoJfQoJcmV0dXJuIGhhbGY0KG91dENvbG9yKTsKfQpoYWxmNCBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKGhhbGY0IF9pbnB1dCkgCnsKCV9pbnB1dCA9IENsYW1wZWRHcmFkaWVudF9TMV9jMChfaW5wdXQpOwoJaGFsZjQgX3RtcF81X2luQ29sb3IgPSBfaW5wdXQ7CglyZXR1cm4gaGFsZjQoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IERpc2FibGVDb3ZlcmFnZUFzQWxwaGFfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGABZAA6IAAAAACAAAAAADUAAAAAAAEAAAAAIDEAAAAAAA":"CQAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABGAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","AYQQ5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAACTAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGYgZGlzdGFuY2VUb0lubmVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKGQgLSBjaXJjbGVFZGdlLncpKTsKCWhhbGYgaW5uZXJBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9Jbm5lckVkZ2UpOwoJZWRnZUFscGhhICo9IGlubmVyQWxwaGE7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA"}} \ No newline at end of file diff --git a/shaders_3.3.2.sksl.json b/shaders_3.3.2.sksl.json new file mode 100644 index 000000000..fa4742cbf --- /dev/null +++ b/shaders_3.3.2.sksl.json @@ -0,0 +1 @@ +{"platform":"android","name":"SM G970N","engineRevision":"a4ff2c53d84ca78702bc3be6c7ef0788ffca01d3","data":{"HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGABZAA6IAAAAACAAAAAADUAAAAAAAEAAAAAIDEAAAAAAA":"CQAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABGAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CQAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoAAAAAKQEAAGZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","AYAA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CQAAAExTS1OCAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5DaXJjbGVFZGdlOwpvdXQgZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCXZpbkNpcmNsZUVkZ2VfUzAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMl9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gX3RtcF8wX2luUG9zaXRpb24ueHkwMTsKfQoAAAAAAAACAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABZQA6AAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACnAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCWFscGhhID0gMS4wIC0gYWxwaGE7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDQgY2lyY2xlRWRnZTsKCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","HVIACAAAABQAAGAAAQ4AAAAAGQQAARC4GAAAIOCAAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAADAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAYAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAIAAQAAAAAQGIA":"CQAAAExTS1PlAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdCBjb3ZlcmFnZTsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdCB2Y292ZXJhZ2VfUzA7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwoJewoJCXZUcmFuc2Zvcm1lZENvb3Jkc181X1MwID0gZmxvYXQzeDIodW1hdHJpeF9TMV9jMF9jMSkgKiBsb2NhbENvb3JkLnh5MTsKCX0KfQoAAAAAAAAAMAcAAHVuaWZvcm0gaGFsZjQgdXN0YXJ0X1MxX2MwX2MwOwp1bmlmb3JtIGhhbGY0IHVlbmRfUzFfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKdW5pZm9ybSBoYWxmNCB1bGVmdEJvcmRlckNvbG9yX1MxX2MwOwp1bmlmb3JtIGhhbGY0IHVyaWdodEJvcmRlckNvbG9yX1MxX2MwOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQgdmNvdmVyYWdlX1MwOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFNpbmdsZUludGVydmFsQ29sb3JpemVyX1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzFfY29vcmRzID0gX2Nvb3JkczsKCXJldHVybiBoYWxmNChtaXgodXN0YXJ0X1MxX2MwX2MwLCB1ZW5kX1MxX2MwX2MwLCBoYWxmKF90bXBfMV9jb29yZHMueCkpKTsKfQpoYWxmNCBMaW5lYXJMYXlvdXRfUzFfYzBfYzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8yX2luQ29sb3IgPSBfaW5wdXQ7CglmbG9hdDIgX3RtcF8zX2Nvb3JkcyA9IHZUcmFuc2Zvcm1lZENvb3Jkc181X1MwOwoJcmV0dXJuIGhhbGY0KGhhbGY0KGhhbGYoX3RtcF8zX2Nvb3Jkcy54KSArIDkuOTk5OTk5NzQ3Mzc4NzUxNmUtMDYsIDEuMCwgMC4wLCAwLjApKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzBfYzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIExpbmVhckxheW91dF9TMV9jMF9jMV9jMChfaW5wdXQpOwp9CmhhbGY0IENsYW1wZWRHcmFkaWVudF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzRfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGY0IHQgPSBNYXRyaXhFZmZlY3RfUzFfYzBfYzEoX3RtcF80X2luQ29sb3IpOwoJaGFsZjQgb3V0Q29sb3I7CglpZiAoIWJvb2woaW50KDEpKSAmJiB0LnkgPCAwLjApIAoJewoJCW91dENvbG9yID0gaGFsZjQoMC4wKTsKCX0KCWVsc2UgaWYgKHQueCA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSB1bGVmdEJvcmRlckNvbG9yX1MxX2MwOwoJfQoJZWxzZSBpZiAodC54ID4gMS4wKSAKCXsKCQlvdXRDb2xvciA9IHVyaWdodEJvcmRlckNvbG9yX1MxX2MwOwoJfQoJZWxzZSAKCXsKCQlvdXRDb2xvciA9IFNpbmdsZUludGVydmFsQ29sb3JpemVyX1MxX2MwX2MwKF90bXBfNF9pbkNvbG9yLCBmbG9hdDIoaGFsZjIodC54LCAwLjApKSk7Cgl9CglpZiAoYm9vbChpbnQoMSkpKSAKCXsKCQlvdXRDb2xvci54eXogKj0gb3V0Q29sb3IudzsKCX0KCXJldHVybiBoYWxmNChvdXRDb2xvcik7Cn0KaGFsZjQgRGlzYWJsZUNvdmVyYWdlQXNBbHBoYV9TMShoYWxmNCBfaW5wdXQpIAp7CglfaW5wdXQgPSBDbGFtcGVkR3JhZGllbnRfUzFfYzAoX2lucHV0KTsKCWhhbGY0IF90bXBfNV9pbkNvbG9yID0gX2lucHV0OwoJcmV0dXJuIGhhbGY0KF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWZsb2F0IGNvdmVyYWdlID0gdmNvdmVyYWdlX1MwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChoYWxmKGNvdmVyYWdlKSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKG91dHB1dENvbG9yX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSAoaGFsZjQoMS4wKSAtIG91dHB1dF9TMSkgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAgAAABwb3NpdGlvbggAAABjb3ZlcmFnZQUAAABjb2xvcgAAAAoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HWQACAAAABAAADAAAIOAAAAADIIAAIRODAAP577774DSAIAA737777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CQAAAExTS1ONAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQgY292ZXJhZ2U7CmluIGhhbGY0IGNvbG9yOwppbiBmbG9hdDQgZ2VvbVN1YnNldDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQgdmNvdmVyYWdlX1MwOwpmbGF0IG91dCBmbG9hdDQgdmdlb21TdWJzZXRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJdmdlb21TdWJzZXRfUzAgPSBnZW9tU3Vic2V0OwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAAAgBAAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdCB2Y292ZXJhZ2VfUzA7CmZsYXQgaW4gZmxvYXQ0IHZnZW9tU3Vic2V0X1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjdWxhclJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCBjb3ZlcmFnZSA9IHZjb3ZlcmFnZV9TMDsKCWZsb2F0NCBnZW9TdWJzZXQ7CglnZW9TdWJzZXQgPSB2Z2VvbVN1YnNldF9TMDsKCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEsIDEsIC0xLCAtMSkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIGdlb1N1YnNldCksIDAsIDEpOwoJaGFsZjIgZGlzdHMyID0gZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3IC0gMTsKCWhhbGYgc3Vic2V0Q292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJY292ZXJhZ2UgPSBtaW4oY292ZXJhZ2UsIHN1YnNldENvdmVyYWdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoaGFsZihjb3ZlcmFnZSkpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY3VsYXJSUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAIAAAAcG9zaXRpb24IAAAAY292ZXJhZ2UFAAAAY29sb3IAAAAKAAAAZ2VvbVN1YnNldAAAAQAAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQAAEAQAAAAGQCBAMQAAAIAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CQAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAIgDAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkLnggPSBzdWJzZXRDb29yZC54OwoJY2xhbXBlZENvb3JkLnkgPSBjbGFtcChzdWJzZXRDb29yZC55LCB1Y2xhbXBfUzFfYzAueSwgdWNsYW1wX1MxX2MwLncpOwoJaGFsZjQgdGV4dHVyZUNvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBjbGFtcGVkQ29vcmQpOwoJcmV0dXJuIHRleHR1cmVDb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","AYQQ5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAMQAHOMFARUBIAADAAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACtBQAAY29uc3QgaW50IGtGaWxsQldfUzEgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgY292ZXJhZ2U7CglpZiAoaW50KDEpID09IGtGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TMS56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fUzEueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCX0KCWVsc2UgCgl7CgkJaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fUzEpLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJaWYgKGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gaGFsZjQoX2lucHV0ICogY292ZXJhZ2UpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZiBkaXN0YW5jZVRvSW5uZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoZCAtIGNpcmNsZUVkZ2UudykpOwoJaGFsZiBpbm5lckFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb0lubmVyRWRnZSk7CgllZGdlQWxwaGEgKj0gaW5uZXJBbHBoYTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IFJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2UBAAAAAAAAAA==","B2AAQAAABQAAIAABBYAAB7777777777774ABICAAAAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CQAAAExTS1MOAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmIGluQ292ZXJhZ2U7Cm91dCBoYWxmIHZpbkNvdmVyYWdlX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cgl2aW5Db3ZlcmFnZV9TMCA9IGluQ292ZXJhZ2U7Cglza19Qb3NpdGlvbiA9IF90bXBfMV9pblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAZQEAAHVuaWZvcm0gaGFsZjQgdUNvbG9yX1MwOwppbiBoYWxmIHZpbkNvdmVyYWdlX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdUNvbG9yX1MwOwoJaGFsZiBhbHBoYSA9IDEuMDsKCWFscGhhID0gdmluQ292ZXJhZ2VfUzA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACgAAAGluUG9zaXRpb24AAAoAAABpbkNvdmVyYWdlAAABAAAAAAAAAA==","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAAA5AAAAAAABAAAAACAZAAAAA":"CQAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgAAAABdAgAAZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2YXJjY29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1MwLngsIHk9dmFyY2Nvb3JkX1MwLnk7CgloYWxmIGNvdmVyYWdlOwoJaWYgKDAgPT0geF9wbHVzXzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZih5KTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQgZm4gPSB4X3BsdXNfMSAqICh4X3BsdXNfMSAtIDIpOwoJCWZuID0gZm1hKHkseSwgZm4pOwoJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABAAAAHNrZXcZAAAAdHJhbnNsYXRlX2FuZF9sb2NhbHJvdGF0ZQAAAAUAAABjb2xvcgAAAAEAAAAAAAAA","B2ABSAAABQAAIAABBYAAB7777777777774ABICAAAAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CQAAAExTS1N4AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gaGFsZjQgdUNvbG9yX1MwOwppbiBmbG9hdDIgaW5Qb3NpdGlvbjsKaW4gaGFsZiBpbkNvdmVyYWdlOwpvdXQgaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IGNvbG9yID0gdUNvbG9yX1MwOwoJY29sb3IgPSBjb2xvciAqIGluQ292ZXJhZ2U7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCWZsb2F0MiBfdG1wXzFfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8zX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzFfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAeAQAAaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgRGVmYXVsdEdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAACgAAAGluQ292ZXJhZ2UAAAEAAAAAAAAA","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CQAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAyQEAAHVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdlRleHR1cmVDb29yZHNfUzA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gdGV4Q29sb3I7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAPAAAAaW5UZXh0dXJlQ29vcmRzAAEAAAAAAAAA","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGAARAGQWMHGBRIAAAAABQAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CQAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABhBAAAY29uc3QgaW50IGtGaWxsQUFfUzEgPSAxOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1Y2lyY2xlX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZsb2NhbENvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjbGVfUzEoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGQ7CglpZiAoaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMSkgCgl7CgkJZCA9IGhhbGYoKGxlbmd0aCgodWNpcmNsZV9TMS54eSAtIHNrX0ZyYWdDb29yZC54eSkgKiB1Y2lyY2xlX1MxLncpIC0gMS4wKSAqIHVjaXJjbGVfUzEueik7Cgl9CgllbHNlIAoJewoJCWQgPSBoYWxmKCgxLjAgLSBsZW5ndGgoKHVjaXJjbGVfUzEueHkgLSBza19GcmFnQ29vcmQueHkpICogdWNpcmNsZV9TMS53KSkgKiB1Y2lyY2xlX1MxLnopOwoJfQoJaWYgKGludCgxKSA9PSBrRmlsbEFBX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxBQV9TMSkgCgl7CgkJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIHNhdHVyYXRlKGQpKTsKCX0KCWVsc2UgCgl7CgkJcmV0dXJuIGhhbGY0KGQgPiAwLjUgPyBfaW5wdXQgOiBoYWxmNCgwLjApKTsKCX0KfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJZmxvYXQyIHRleENvb3JkOwoJdGV4Q29vcmQgPSB2bG9jYWxDb29yZF9TMDsKCW91dHB1dENvbG9yX1MwID0gKChzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHRleENvb3JkKSAqIGhhbGY0KDEpKSk7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gQ2lyY2xlX1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAIAAEAAAABJYQAAAAAQAAIAAAAAWCBACAABAAAAANAECAZAAEAAAAAAAAFAAMAAAABAAAAAAABBAM":"CQAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAEcGAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1SW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGYyIHVPZmZzZXRzQW5kS2VybmVsX1MxX2MwWzEzXTsKdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglmbG9hdDIgaW5Db29yZCA9IF9jb29yZHM7CglmbG9hdDIgc3Vic2V0Q29vcmQ7CglzdWJzZXRDb29yZC54ID0gaW5Db29yZC54OwoJc3Vic2V0Q29vcmQueSA9IGluQ29vcmQueTsKCWZsb2F0MiBjbGFtcGVkQ29vcmQ7CgljbGFtcGVkQ29vcmQueCA9IGNsYW1wKHN1YnNldENvb3JkLngsIHVjbGFtcF9TMV9jMF9jMF9jMC54LCB1Y2xhbXBfUzFfYzBfYzBfYzAueik7CgljbGFtcGVkQ29vcmQueSA9IHN1YnNldENvb3JkLnk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBTbW9vdGhfUzFfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgY29vcmQsIGhhbGYyIG9mZnNldEFuZEtlcm5lbCkgCnsKCXJldHVybiBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCAoY29vcmQgKyBvZmZzZXRBbmRLZXJuZWwueCAqIHVJbmNyZW1lbnRfUzFfYzApKSAqIG9mZnNldEFuZEtlcm5lbC55Owp9CmhhbGY0IEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgY29sb3IgPSBoYWxmNCgwKTsKCWZsb2F0MiBjb29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZm9yIChpbnQgaT0wOyBpPDEzOyArK2kpIAoJewoJCWNvbG9yICs9IFNtb290aF9TMV9jMChfaW5wdXQsIGNvb3JkLCB1T2Zmc2V0c0FuZEtlcm5lbF9TMV9jMFtpXSk7Cgl9CglyZXR1cm4gY29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBHYXVzc2lhbkNvbnZvbHV0aW9uX1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","EABQAAAAAEAAAAAQAABQAAIOAAABCFYIAAKAUDAAAAAAAAABAAAAAAAAAAANAAIAAAABAAAAACAJAAIAAAAA":"CQAAAExTS1OhAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc0RpbWVuc2lvbnNJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgZmxvYXQyIHZJbnRUZXh0dXJlQ29vcmRzX1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIERpc3RhbmNlRmllbGRQYXRoCglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkcyAqIHVBdGxhc0RpbWVuc2lvbnNJbnZfUzA7Cgl2VGV4SW5kZXhfUzAgPSBmbG9hdCh0ZXhJZHgpOwoJdkludFRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkczsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8yX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAAAAC3AgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBpbiBmbG9hdCB2VGV4SW5kZXhfUzA7CmluIGZsb2F0MiB2SW50VGV4dHVyZUNvb3Jkc19TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBEaXN0YW5jZUZpZWxkUGF0aAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQyIHV2ID0gdlRleHR1cmVDb29yZHNfUzA7CgloYWxmNCB0ZXhDb2xvcjsKCXsKCQl0ZXhDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdXYpLnJycnI7Cgl9CgloYWxmIGRpc3RhbmNlID0gNy45Njg3NSoodGV4Q29sb3IuciAtIDAuNTAxOTYwNzg0MzEpOwoJaGFsZiBhZndpZHRoOwoJYWZ3aWR0aCA9IGFicygwLjY1KmhhbGYoZEZkeCh2SW50VGV4dHVyZUNvb3Jkc19TMC54KSkpOwoJaGFsZiB2YWwgPSBzbW9vdGhzdGVwKC1hZndpZHRoLCBhZndpZHRoLCBkaXN0YW5jZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KHZhbCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADwAAAGluVGV4dHVyZUNvb3JkcwABAAAAAAAAAA==","DASAAAAAQAAWAABAYAAQBYH7777Z6QQBAEAAAAAAEAAAAAAAEBSAAAB2AAAAAAACAAAAAEBSAAAAA":"CQAAAExTS1PVAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBCaXRtYXBUZXh0CglpbnQgdGV4SWR4ID0gMDsKCWZsb2F0MiB1bm9ybVRleENvb3JkcyA9IGZsb2F0MihpblRleHR1cmVDb29yZHMueCwgaW5UZXh0dXJlQ29vcmRzLnkpOwoJdlRleHR1cmVDb29yZHNfUzAgPSB1bm9ybVRleENvb3JkcyAqIHVBdGxhc1NpemVJbnZfUzA7Cgl2VGV4SW5kZXhfUzAgPSBmbG9hdCh0ZXhJZHgpOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCXNrX1Bvc2l0aW9uID0gaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAAAAD4AQAAdW5pZm9ybSBoYWxmNCB1Q29sb3JfUzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdlRleHR1cmVDb29yZHNfUzA7CmZsYXQgaW4gZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHVDb2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCk7Cgl9CglvdXRwdXRDb2xvcl9TMCA9IG91dHB1dENvbG9yX1MwICogdGV4Q29sb3I7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAKAAAAaW5Qb3NpdGlvbgAADwAAAGluVGV4dHVyZUNvb3JkcwABAAAAAAAAAA==","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAACAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACABYQA6AAAEAAAAAAAIADQAAAAIAAAAAAAIIDA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAACRAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZWRnZUFscGhhKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2UBAAAAAAAAAA==","HVJAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAABAAAAAABBAMABAAOAAAABAAAAAAABBAMAAA":"CQAAAExTS1MjAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cgl2bG9jYWxDb29yZF9TMCA9IGxvY2FsQ29vcmQ7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAAAAADoAQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdDIgdGV4Q29vcmQ7Cgl0ZXhDb29yZCA9IHZsb2NhbENvb3JkX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSAoKHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdGV4Q29vcmQpICogb3V0cHV0Q29sb3JfUzApKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAEQQGABZAA6IAAAAACAAAAAADUAAAAAAAEAAAAAIDEAAAAAAA":"CQAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAABPAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gc2FtcGxlckV4dGVybmFsT0VTIHVUZXh0dXJlU2FtcGxlcl8wX1MwOwppbiBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HUQAAAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAACEA2X4PLOGEAAAAAAAAACAAAAAVQQAAQAAAAAQCDIBCAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAAAAAAA":"CQAAAExTS1PUAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKfQoBAAAACwQAAHVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1Y2lyY2xlRGF0YV9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MxLCBfY29vcmRzKS4wMDByOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBDaXJjbGVCbHVyX1MxKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjIgdmVjID0gaGFsZjIoKHNrX0ZyYWdDb29yZC54eSAtIGZsb2F0Mih1Y2lyY2xlRGF0YV9TMS54eSkpICogZmxvYXQodWNpcmNsZURhdGFfUzEudykpOwoJaGFsZiBkaXN0ID0gbGVuZ3RoKHZlYykgKyAoMC41IC0gdWNpcmNsZURhdGFfUzEueikgKiB1Y2lyY2xlRGF0YV9TMS53OwoJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIE1hdHJpeEVmZmVjdF9TMV9jMChfdG1wXzBfaW5Db2xvciwgZmxvYXQyKGhhbGYyKGRpc3QsIDAuNSkpKS53KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmNsZUJsdXJfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAEAAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQBAEAQAAAAGQCBAMQACAIAAAAAACQAGAAAAAQAAAAAAAQQGAAAAA":"CQAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAGUDAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkID0gY2xhbXAoc3Vic2V0Q29vcmQsIHVjbGFtcF9TMV9jMC54eSwgdWNsYW1wX1MxX2MwLnp3KTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uCgAAAGxvY2FsQ29vcmQAAAEAAAAAAAAA","HUQACAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAHEADZAAAAAAIAAAAAAOQAAAAAAAQAAAABAMQAAAAAA":"CQAAAExTS1PPAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAEAAACzAgAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmluIGhhbGY0IHZjb2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY3VsYXJSUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgZHh5MCA9IHVpbm5lclJlY3RfUzEuTFQgLSBza19GcmFnQ29vcmQueHk7CglmbG9hdDIgZHh5MSA9IHNrX0ZyYWdDb29yZC54eSAtIHVpbm5lclJlY3RfUzEuUkI7CglmbG9hdDIgZHh5ID0gbWF4KG1heChkeHkwLCBkeHkxKSwgMC4wKTsKCWhhbGYgYWxwaGEgPSBoYWxmKHNhdHVyYXRlKHVyYWRpdXNQbHVzSGFsZl9TMS54IC0gbGVuZ3RoKGR4eSkpKTsKCXJldHVybiBfaW5wdXQgKiBhbHBoYTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmN1bGFyUlJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAEAAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAIBAIAAAABLCIIBAAAAABAEGABBAMAACAIAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CQAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAADhAwAAdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGluQ29vcmQgPSB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKCWZsb2F0MiBzdWJzZXRDb29yZDsKCXN1YnNldENvb3JkLnggPSBpbkNvb3JkLng7CglzdWJzZXRDb29yZC55ID0gaW5Db29yZC55OwoJZmxvYXQyIGNsYW1wZWRDb29yZDsKCWNsYW1wZWRDb29yZCA9IGNsYW1wKHN1YnNldENvb3JkLCB1Y2xhbXBfUzFfYzBfYzAueHksIHVjbGFtcF9TMV9jMF9jMC56dyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMF9jMChfaW5wdXQpOwp9CmhhbGY0IEJsZW5kX1MxKGhhbGY0IF9zcmMsIGhhbGY0IF9kc3QpIAp7CglyZXR1cm4gYmxlbmRfbW9kdWxhdGUoTWF0cml4RWZmZWN0X1MxX2MwKF9zcmMpLCBfc3JjKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IEJsZW5kX1MxKG91dHB1dENvbG9yX1MwLCBoYWxmNCgxKSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","HUJAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAQAAAAAAQQGACQAGAAAAAQAAAAAAAQQGAAA":"CQAAAExTS1PlAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7Cm91dCBmbG9hdDIgdmxvY2FsQ29vcmRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmxvY2FsQ29vcmRfUzAgPSBsb2NhbENvb3JkOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAAAAAC3AQAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGZsb2F0MiB2bG9jYWxDb29yZF9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWZsb2F0MiB0ZXhDb29yZDsKCXRleENvb3JkID0gdmxvY2FsQ29vcmRfUzA7CglvdXRwdXRDb2xvcl9TMCA9ICgoc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB0ZXhDb29yZCkgKiBoYWxmNCgxKSkpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HWQACAAAABAAADAAAIOAAAAADIIAAIRODAAP577774DSAIAA737777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CQAAAExTS1ONAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gZmxvYXQgY292ZXJhZ2U7CmluIGhhbGY0IGNvbG9yOwppbiBmbG9hdDQgZ2VvbVN1YnNldDsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQgdmNvdmVyYWdlX1MwOwpmbGF0IG91dCBmbG9hdDQgdmdlb21TdWJzZXRfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJdmdlb21TdWJzZXRfUzAgPSBnZW9tU3Vic2V0OwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwp9CgAAAAEAAACRAgAAZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0IHZjb3ZlcmFnZV9TMDsKZmxhdCBpbiBmbG9hdDQgdmdlb21TdWJzZXRfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCBjb3ZlcmFnZSA9IHZjb3ZlcmFnZV9TMDsKCWZsb2F0NCBnZW9TdWJzZXQ7CglnZW9TdWJzZXQgPSB2Z2VvbVN1YnNldF9TMDsKCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEsIDEsIC0xLCAtMSkgKiBoYWxmNChza19GcmFnQ29vcmQueHl4eSAtIGdlb1N1YnNldCksIDAsIDEpOwoJaGFsZjIgZGlzdHMyID0gZGlzdHM0Lnh5ICsgZGlzdHM0Lnp3IC0gMTsKCWhhbGYgc3Vic2V0Q292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJY292ZXJhZ2UgPSBtaW4oY292ZXJhZ2UsIHN1YnNldENvdmVyYWdlKTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoaGFsZihjb3ZlcmFnZSkpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAIAAAAcG9zaXRpb24IAAAAY292ZXJhZ2UFAAAAY29sb3IAAAAKAAAAZ2VvbVN1YnNldAAAAQAAAAAAAAA=","HVIACAAAABQAAGAAAQ4AAAAAGQQAARC4GAAAIOCAAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAAEAQCAAAAAVREEAQAAAAAQCDAAQQGAABAEAAAAAAH4AQAAAAAEAAAAAQGIAAAAAAA":"CQAAAExTS1PfAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdCBjb3ZlcmFnZTsKaW4gaGFsZjQgY29sb3I7CmluIGZsb2F0MiBsb2NhbENvb3JkOwpmbGF0IG91dCBoYWxmNCB2Y29sb3JfUzA7Cm91dCBmbG9hdCB2Y292ZXJhZ2VfUzA7Cm91dCBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQyIHBvc2l0aW9uID0gcG9zaXRpb24ueHk7Cgl2Y29sb3JfUzAgPSBjb2xvcjsKCXZjb3ZlcmFnZV9TMCA9IGNvdmVyYWdlOwoJc2tfUG9zaXRpb24gPSBwb3NpdGlvbi54eTAxOwoJewoJCXZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwID0gZmxvYXQzeDIodW1hdHJpeF9TMV9jMCkgKiBsb2NhbENvb3JkLnh5MTsKCX0KfQoAAAAAAEAFAAAjZXh0ZW5zaW9uIEdMX0VYVF9zaGFkZXJfZnJhbWVidWZmZXJfZmV0Y2g6IHJlcXVpcmUKdW5pZm9ybSBmbG9hdDQgdWNsYW1wX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzA7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQgdmNvdmVyYWdlX1MwOwppbiBmbG9hdDIgdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzA7Cmlub3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQpIAp7CglmbG9hdDIgaW5Db29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwOwoJZmxvYXQyIHN1YnNldENvb3JkOwoJc3Vic2V0Q29vcmQueCA9IGluQ29vcmQueDsKCXN1YnNldENvb3JkLnkgPSBpbkNvb3JkLnk7CglmbG9hdDIgY2xhbXBlZENvb3JkOwoJY2xhbXBlZENvb3JkID0gY2xhbXAoc3Vic2V0Q29vcmQsIHVjbGFtcF9TMV9jMF9jMC54eSwgdWNsYW1wX1MxX2MwX2MwLnp3KTsKCWhhbGY0IHRleHR1cmVDb2xvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgY2xhbXBlZENvb3JkKTsKCXJldHVybiB0ZXh0dXJlQ29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKF9pbnB1dCk7Cn0KaGFsZjQgRGlzYWJsZUNvdmVyYWdlQXNBbHBoYV9TMShoYWxmNCBfaW5wdXQpIAp7CglfaW5wdXQgPSBNYXRyaXhFZmZlY3RfUzFfYzAoX2lucHV0KTsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJcmV0dXJuIGhhbGY0KF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWZsb2F0IGNvdmVyYWdlID0gdmNvdmVyYWdlX1MwOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChoYWxmKGNvdmVyYWdlKSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKG91dHB1dENvbG9yX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYgU2hhZGVyCgkJaGFsZjQgX2RzdENvbG9yID0gc2tfRnJhZ0NvbG9yOwoJCXNrX0ZyYWdDb2xvciA9IGJsZW5kX3NyYyhvdXRwdXRfUzEsIF9kc3RDb2xvcik7CgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q292ZXJhZ2VfUzAgKiBza19GcmFnQ29sb3IgKyAoaGFsZjQoMS4wKSAtIG91dHB1dENvdmVyYWdlX1MwKSAqIF9kc3RDb2xvcjsKCX0KfQoAAAAAAQAAAAEAAAAAAAAAAQAAAAAAAAAAAAAABAAAAAgAAABwb3NpdGlvbggAAABjb3ZlcmFnZQUAAABjb2xvcgAAAAoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAGIBIAAABAAAAANAEAAAAAAAAAAAAAABAAOAAAABAAAAAAABBAMAAAAA":"CQAAAExTS1N0AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfMl9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAIsCAAB1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzE7CnVuaWZvcm0gc2FtcGxlcjJEIHVUZXh0dXJlU2FtcGxlcl8wX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwKS5ycnJyOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMChfaW5wdXQpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBRdWFkUGVyRWRnZUFBR2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7Cgljb25zdCBoYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KDEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gTWF0cml4RWZmZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAIAAAAcG9zaXRpb24FAAAAY29sb3IAAAAKAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAADUAANAAAAAAAAAIAAAABLAIABAAAAABAEGABBAMAAAAAAAAAAAAB2AAAAAAACAAAAAEBSAAAAA":"CQAAAExTS1M8AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMDsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfM19TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzNfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxX2MwKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAADOAgAAdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxX2MwOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIHZUcmFuc2Zvcm1lZENvb3Jkc18zX1MwKTsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzAoX2lucHV0KTsKfQpoYWxmNCBCbGVuZF9TMShoYWxmNCBfc3JjLCBoYWxmNCBfZHN0KSAKewoJcmV0dXJuIGJsZW5kX21vZHVsYXRlKE1hdHJpeEVmZmVjdF9TMV9jMChfc3JjKSwgX3NyYyk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBCbGVuZF9TMShvdXRwdXRDb2xvcl9TMCwgaGFsZjQoMSkpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","HVIAAAAAABIAAGAAAQ4AAAH477776R24EAAAIOBQAD6P7777777777YDAAAAAAAAAAAFIBTWV34ISAIAAAAEYT7ZOFIQAAAABAAAAABQAAAAAIFGB7HB2BAAAAAFQRH6PYAAAAEAAAAAAAAZGE66LR2FAEAAAIAAAAAMAAAAACAJQPRYO4IAAAAAMAI7T6YBAAAAABAAAAAGIMFGB7HB2BAAAAAAAAAAQAKYCRPE54DQAAAABAAAAAEQEFMEJ7T6AAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIA":"CQAAAExTS1OAAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBoYWxmNCBjb2xvcjsKaW4gZmxvYXQyIGxvY2FsQ29vcmQ7CmZsYXQgb3V0IGhhbGY0IHZjb2xvcl9TMDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgl2Y29sb3JfUzAgPSBjb2xvcjsKCXNrX1Bvc2l0aW9uID0gcG9zaXRpb24ueHkwMTsKCXsKCQl2VHJhbnNmb3JtZWRDb29yZHNfNV9TMCA9IGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzEpICogbG9jYWxDb29yZC54eTE7Cgl9Cn0KAAAAAOMGAAB1bmlmb3JtIGhhbGY0IHVzdGFydF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmNCB1ZW5kX1MxX2MwX2MwOwp1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzE7CnVuaWZvcm0gaGFsZjQgdWxlZnRCb3JkZXJDb2xvcl9TMV9jMDsKdW5pZm9ybSBoYWxmNCB1cmlnaHRCb3JkZXJDb2xvcl9TMV9jMDsKZmxhdCBpbiBoYWxmNCB2Y29sb3JfUzA7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfNV9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgX2Nvb3JkcykgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJZmxvYXQyIF90bXBfMV9jb29yZHMgPSBfY29vcmRzOwoJcmV0dXJuIGhhbGY0KG1peCh1c3RhcnRfUzFfYzBfYzAsIHVlbmRfUzFfYzBfYzAsIGhhbGYoX3RtcF8xX2Nvb3Jkcy54KSkpOwp9CmhhbGY0IExpbmVhckxheW91dF9TMV9jMF9jMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzJfaW5Db2xvciA9IF9pbnB1dDsKCWZsb2F0MiBfdG1wXzNfY29vcmRzID0gdlRyYW5zZm9ybWVkQ29vcmRzXzVfUzA7CglyZXR1cm4gaGFsZjQoaGFsZjQoaGFsZihfdG1wXzNfY29vcmRzLngpICsgOS45OTk5OTk3NDczNzg3NTE2ZS0wNiwgMS4wLCAwLjAsIDAuMCkpOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShoYWxmNCBfaW5wdXQpIAp7CglyZXR1cm4gTGluZWFyTGF5b3V0X1MxX2MwX2MxX2MwKF9pbnB1dCk7Cn0KaGFsZjQgQ2xhbXBlZEdyYWRpZW50X1MxX2MwKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfNF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZjQgdCA9IE1hdHJpeEVmZmVjdF9TMV9jMF9jMShfdG1wXzRfaW5Db2xvcik7CgloYWxmNCBvdXRDb2xvcjsKCWlmICghYm9vbChpbnQoMSkpICYmIHQueSA8IDAuMCkgCgl7CgkJb3V0Q29sb3IgPSBoYWxmNCgwLjApOwoJfQoJZWxzZSBpZiAodC54IDwgMC4wKSAKCXsKCQlvdXRDb2xvciA9IHVsZWZ0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIGlmICh0LnggPiAxLjApIAoJewoJCW91dENvbG9yID0gdXJpZ2h0Qm9yZGVyQ29sb3JfUzFfYzA7Cgl9CgllbHNlIAoJewoJCW91dENvbG9yID0gU2luZ2xlSW50ZXJ2YWxDb2xvcml6ZXJfUzFfYzBfYzAoX3RtcF80X2luQ29sb3IsIGZsb2F0MihoYWxmMih0LngsIDAuMCkpKTsKCX0KCWlmIChib29sKGludCgwKSkpIAoJewoJCW91dENvbG9yLnh5eiAqPSBvdXRDb2xvci53OwoJfQoJcmV0dXJuIGhhbGY0KG91dENvbG9yKTsKfQpoYWxmNCBEaXNhYmxlQ292ZXJhZ2VBc0FscGhhX1MxKGhhbGY0IF9pbnB1dCkgCnsKCV9pbnB1dCA9IENsYW1wZWRHcmFkaWVudF9TMV9jMChfaW5wdXQpOwoJaGFsZjQgX3RtcF81X2luQ29sb3IgPSBfaW5wdXQ7CglyZXR1cm4gaGFsZjQoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IERpc2FibGVDb3ZlcmFnZUFzQWxwaGFfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAgAAABwb3NpdGlvbgUAAABjb2xvcgAAAAoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAB3QA6AAAEAAAAAAAMAAPEAEAAABAAAAAAB2AAAAAAACAAAAAEBSAAAA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAAA2BQAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CnVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzI7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MyOwppbiBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglhbHBoYSA9IDEuMCAtIGFscGhhOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CmhhbGY0IENpcmN1bGFyUlJlY3RfUzIoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MyLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MyLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzIueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIENpcmNsZUdlb21ldHJ5UHJvY2Vzc29yCglmbG9hdDQgY2lyY2xlRWRnZTsKCWNpcmNsZUVkZ2UgPSB2aW5DaXJjbGVFZGdlX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJZmxvYXQgZCA9IGxlbmd0aChjaXJjbGVFZGdlLnh5KTsKCWhhbGYgZGlzdGFuY2VUb091dGVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKDEuMCAtIGQpKTsKCWhhbGYgZWRnZUFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb091dGVyRWRnZSk7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCWhhbGY0IG91dHB1dF9TMjsKCW91dHB1dF9TMiA9IENpcmN1bGFyUlJlY3RfUzIob3V0cHV0X1MxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMjsKCX0KfQoAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAwAAABpbkNpcmNsZUVkZ2UBAAAAAAAAAA==","AYQQ5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAACTAgAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCWZsb2F0NCBjaXJjbGVFZGdlOwoJY2lyY2xlRWRnZSA9IHZpbkNpcmNsZUVkZ2VfUzA7CgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmluQ29sb3JfUzA7CglmbG9hdCBkID0gbGVuZ3RoKGNpcmNsZUVkZ2UueHkpOwoJaGFsZiBkaXN0YW5jZVRvT3V0ZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoMS4wIC0gZCkpOwoJaGFsZiBlZGdlQWxwaGEgPSBzYXR1cmF0ZShkaXN0YW5jZVRvT3V0ZXJFZGdlKTsKCWhhbGYgZGlzdGFuY2VUb0lubmVyRWRnZSA9IGhhbGYoY2lyY2xlRWRnZS56ICogKGQgLSBjaXJjbGVFZGdlLncpKTsKCWhhbGYgaW5uZXJBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9Jbm5lckVkZ2UpOwoJZWRnZUFscGhhICo9IGlubmVyQWxwaGE7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAABAEAAAABJYQAAAAAACAIAAAAAWCBAAAIBAAAAANAECAZAAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"CQAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAEcGAAB1bmlmb3JtIGZsb2F0NCB1Y2xhbXBfUzFfYzBfYzBfYzA7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMV9jMF9jMDsKdW5pZm9ybSBoYWxmMiB1SW5jcmVtZW50X1MxX2MwOwp1bmlmb3JtIGhhbGYyIHVPZmZzZXRzQW5kS2VybmVsX1MxX2MwWzEzXTsKdW5pZm9ybSBmbG9hdDN4MyB1bWF0cml4X1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMTsKaW4gZmxvYXQyIHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBUZXh0dXJlRWZmZWN0X1MxX2MwX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglmbG9hdDIgaW5Db29yZCA9IF9jb29yZHM7CglmbG9hdDIgc3Vic2V0Q29vcmQ7CglzdWJzZXRDb29yZC54ID0gaW5Db29yZC54OwoJc3Vic2V0Q29vcmQueSA9IGluQ29vcmQueTsKCWZsb2F0MiBjbGFtcGVkQ29vcmQ7CgljbGFtcGVkQ29vcmQueCA9IHN1YnNldENvb3JkLng7CgljbGFtcGVkQ29vcmQueSA9IGNsYW1wKHN1YnNldENvb3JkLnksIHVjbGFtcF9TMV9jMF9jMF9jMC55LCB1Y2xhbXBfUzFfYzBfYzBfYzAudyk7CgloYWxmNCB0ZXh0dXJlQ29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzEsIGNsYW1wZWRDb29yZCk7CglyZXR1cm4gdGV4dHVyZUNvbG9yOwp9CmhhbGY0IE1hdHJpeEVmZmVjdF9TMV9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIFRleHR1cmVFZmZlY3RfUzFfYzBfYzBfYzAoX2lucHV0LCBmbG9hdDN4Mih1bWF0cml4X1MxX2MwX2MwKSAqIF9jb29yZHMueHkxKTsKfQpoYWxmNCBTbW9vdGhfUzFfYzAoaGFsZjQgX2lucHV0LCBmbG9hdDIgY29vcmQsIGhhbGYyIG9mZnNldEFuZEtlcm5lbCkgCnsKCXJldHVybiBNYXRyaXhFZmZlY3RfUzFfYzBfYzAoX2lucHV0LCAoY29vcmQgKyBvZmZzZXRBbmRLZXJuZWwueCAqIHVJbmNyZW1lbnRfUzFfYzApKSAqIG9mZnNldEFuZEtlcm5lbC55Owp9CmhhbGY0IEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgY29sb3IgPSBoYWxmNCgwKTsKCWZsb2F0MiBjb29yZCA9IHZUcmFuc2Zvcm1lZENvb3Jkc18yX1MwOwoJZm9yIChpbnQgaT0wOyBpPDEzOyArK2kpIAoJewoJCWNvbG9yICs9IFNtb290aF9TMV9jMChfaW5wdXQsIGNvb3JkLCB1T2Zmc2V0c0FuZEtlcm5lbF9TMV9jMFtpXSk7Cgl9CglyZXR1cm4gY29sb3I7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCXJldHVybiBHYXVzc2lhbkNvbnZvbHV0aW9uX1MxX2MwKF9pbnB1dCk7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzAgPSBoYWxmNCgxKTsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBNYXRyaXhFZmZlY3RfUzEob3V0cHV0Q29sb3JfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dF9TMSAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAgAAABwb3NpdGlvbgoAAABsb2NhbENvb3JkAAABAAAAAAAAAA==","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEADZABYAAAIAAAAAACQAGAAAAAQAAAAAAAQQG":"CQAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAADsAwAAdW5pZm9ybSBmbG9hdDQgdWlubmVyUmVjdF9TMTsKdW5pZm9ybSBoYWxmMiB1cmFkaXVzUGx1c0hhbGZfUzE7CmZsYXQgaW4gaGFsZjQgdmNvbG9yX1MwOwppbiBmbG9hdDIgdmFyY2Nvb3JkX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwpoYWxmNCBDaXJjdWxhclJSZWN0X1MxKGhhbGY0IF9pbnB1dCkgCnsKCWZsb2F0MiBkeHkwID0gdWlubmVyUmVjdF9TMS5MVCAtIHNrX0ZyYWdDb29yZC54eTsKCWZsb2F0MiBkeHkxID0gc2tfRnJhZ0Nvb3JkLnh5IC0gdWlubmVyUmVjdF9TMS5SQjsKCWZsb2F0MiBkeHkgPSBtYXgobWF4KGR4eTAsIGR4eTEpLCAwLjApOwoJaGFsZiBhbHBoYSA9IGhhbGYoc2F0dXJhdGUodXJhZGl1c1BsdXNIYWxmX1MxLnggLSBsZW5ndGgoZHh5KSkpOwoJcmV0dXJuIF9pbnB1dCAqIGFscGhhOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBGaWxsUlJlY3RPcDo6UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJZmxvYXQgeF9wbHVzXzE9dmFyY2Nvb3JkX1MwLngsIHk9dmFyY2Nvb3JkX1MwLnk7CgloYWxmIGNvdmVyYWdlOwoJaWYgKDAgPT0geF9wbHVzXzEpIAoJewoJCWNvdmVyYWdlID0gaGFsZih5KTsKCX0KCWVsc2UgCgl7CgkJZmxvYXQgZm4gPSB4X3BsdXNfMSAqICh4X3BsdXNfMSAtIDIpOwoJCWZuID0gZm1hKHkseSwgZm4pOwoJCWZsb2F0IGZud2lkdGggPSBmd2lkdGgoZm4pOwoJCWNvdmVyYWdlID0gLjUgLSBoYWxmKGZuL2Zud2lkdGgpOwoJCWNvdmVyYWdlID0gY2xhbXAoY292ZXJhZ2UsIDAsIDEpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChjb3ZlcmFnZSk7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAACAAAAA4AAAByYWRpaV9zZWxlY3RvcgAAGQAAAGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHMAAAAVAAAAYWFfYmxvYXRfYW5kX2NvdmVyYWdlAAAABwAAAHJhZGlpX3gABwAAAHJhZGlpX3kABAAAAHNrZXcZAAAAdHJhbnNsYXRlX2FuZF9sb2NhbHJvdGF0ZQAAAAUAAABjb2xvcgAAAAEAAAAAAAAA","HUQACAAAAAMAADAAAIOAAAH677776IZOCAAP577777777777777777YBAAAAAAAAAAAKAAYAAAACAAAAAAACCAYAAA":"CQAAAExTS1PPAAAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBwb3NpdGlvbjsKaW4gaGFsZjQgY29sb3I7Cm91dCBoYWxmNCB2Y29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIFF1YWRQZXJFZGdlQUFHZW9tZXRyeVByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7Cglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAkAQAAaW4gaGFsZjQgdmNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMDsKCW91dHB1dENvbG9yX1MwID0gdmNvbG9yX1MwOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACAAAAHBvc2l0aW9uBQAAAGNvbG9yAAAAAQAAAAAAAAA=","GEMAAAYAAEHAAAARC4EAAAQWBQAAAAAAAAAQAAAAIBCAAAGQAEAAAAAQAAAABAEQAEAAAAA":"CQAAAExTS1NUAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBoYWxmMyBpblNoYWRvd1BhcmFtczsKb3V0IGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBSUmVjdFNoYWRvdwoJdmluU2hhZG93UGFyYW1zX1MwID0gaW5TaGFkb3dQYXJhbXM7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgAAAAAjAgAAdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzA7CmluIGhhbGYzIHZpblNoYWRvd1BhcmFtc19TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBSUmVjdFNoYWRvdwoJaGFsZjMgc2hhZG93UGFyYW1zOwoJc2hhZG93UGFyYW1zID0gdmluU2hhZG93UGFyYW1zX1MwOwoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZiBkID0gbGVuZ3RoKHNoYWRvd1BhcmFtcy54eSk7CglmbG9hdDIgdXYgPSBmbG9hdDIoc2hhZG93UGFyYW1zLnogKiAoMS4wIC0gZCksIDAuNSk7CgloYWxmIGZhY3RvciA9IHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMCwgdXYpLjAwMHIuYTsKCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoZmFjdG9yKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dENvdmVyYWdlX1MwOwoJfQp9CgAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAOAAAAaW5TaGFkb3dQYXJhbXMAAAEAAAAAAAAA","FAAQMYAAMAAAEADAAABAEYAAAICIAB5AABQAAAQAMAAAEATAAABAIIGAAEDCBYQCA4AAAAAAEAZCBRE4GNEACAAAOAAAAAAAAAAABAAOAAAABAAAAAAABBAMAA":"CQAAAExTS1PUCwAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0NCByYWRpaV9zZWxlY3RvcjsKaW4gZmxvYXQ0IGNvcm5lcl9hbmRfcmFkaXVzX291dHNldHM7CmluIGZsb2F0NCBhYV9ibG9hdF9hbmRfY292ZXJhZ2U7CmluIGZsb2F0NCByYWRpaV94OwppbiBmbG9hdDQgcmFkaWlfeTsKaW4gZmxvYXQ0IHNrZXc7CmluIGZsb2F0MiB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlOwppbiBoYWxmNCBjb2xvcjsKZmxhdCBvdXQgaGFsZjQgdmNvbG9yX1MwOwpvdXQgZmxvYXQyIHZhcmNjb29yZF9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgRmlsbFJSZWN0T3A6OlByb2Nlc3NvcgoJdmNvbG9yX1MwID0gY29sb3I7CglmbG9hdCBhYV9ibG9hdF9tdWx0aXBsaWVyID0gMTsKCWZsb2F0MiBjb3JuZXIgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnh5OwoJZmxvYXQyIHJhZGl1c19vdXRzZXQgPSBjb3JuZXJfYW5kX3JhZGl1c19vdXRzZXRzLnp3OwoJZmxvYXQyIGFhX2Jsb2F0X2RpcmVjdGlvbiA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS54eTsKCWZsb2F0IGlzX2xpbmVhcl9jb3ZlcmFnZSA9IGFhX2Jsb2F0X2FuZF9jb3ZlcmFnZS53OwoJZmxvYXQyIHBpeGVsbGVuZ3RoID0gaW52ZXJzZXNxcnQoZmxvYXQyKGRvdChza2V3Lnh6LCBza2V3Lnh6KSwgZG90KHNrZXcueXcsIHNrZXcueXcpKSk7CglmbG9hdDQgbm9ybWFsaXplZF9heGlzX2RpcnMgPSBza2V3ICogcGl4ZWxsZW5ndGgueHl4eTsKCWZsb2F0MiBheGlzd2lkdGhzID0gKGFicyhub3JtYWxpemVkX2F4aXNfZGlycy54eSkgKyBhYnMobm9ybWFsaXplZF9heGlzX2RpcnMuencpKTsKCWZsb2F0MiBhYV9ibG9hdHJhZGl1cyA9IGF4aXN3aWR0aHMgKiBwaXhlbGxlbmd0aCAqIC41OwoJZmxvYXQ0IHJhZGlpX2FuZF9uZWlnaGJvcnMgPSByYWRpaV9zZWxlY3RvciogZmxvYXQ0eDQocmFkaWlfeCwgcmFkaWlfeSwgcmFkaWlfeC55eHd6LCByYWRpaV95Lnd6eXgpOwoJZmxvYXQyIHJhZGlpID0gcmFkaWlfYW5kX25laWdoYm9ycy54eTsKCWZsb2F0MiBuZWlnaGJvcl9yYWRpaSA9IHJhZGlpX2FuZF9uZWlnaGJvcnMuenc7CglmbG9hdCBjb3ZlcmFnZV9tdWx0aXBsaWVyID0gMTsKCWlmIChhbnkoZ3JlYXRlclRoYW4oYWFfYmxvYXRyYWRpdXMsIGZsb2F0MigxKSkpKSAKCXsKCQljb3JuZXIgPSBtYXgoYWJzKGNvcm5lciksIGFhX2Jsb2F0cmFkaXVzKSAqIHNpZ24oY29ybmVyKTsKCQljb3ZlcmFnZV9tdWx0aXBsaWVyID0gMSAvIChtYXgoYWFfYmxvYXRyYWRpdXMueCwgMSkgKiBtYXgoYWFfYmxvYXRyYWRpdXMueSwgMSkpOwoJCXJhZGlpID0gZmxvYXQyKDApOwoJfQoJZmxvYXQgY292ZXJhZ2UgPSBhYV9ibG9hdF9hbmRfY292ZXJhZ2UuejsKCWlmIChhbnkobGVzc1RoYW4ocmFkaWksIGFhX2Jsb2F0cmFkaXVzICogMS41KSkpIAoJewoJCXJhZGlpID0gZmxvYXQyKDApOwoJCWFhX2Jsb2F0X2RpcmVjdGlvbiA9IHNpZ24oY29ybmVyKTsKCQlpZiAoY292ZXJhZ2UgPiAuNSkgCgkJewoJCQlhYV9ibG9hdF9kaXJlY3Rpb24gPSAtYWFfYmxvYXRfZGlyZWN0aW9uOwoJCX0KCQlpc19saW5lYXJfY292ZXJhZ2UgPSAxOwoJfQoJZWxzZSAKCXsKCQlyYWRpaSA9IGNsYW1wKHJhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQluZWlnaGJvcl9yYWRpaSA9IGNsYW1wKG5laWdoYm9yX3JhZGlpLCBwaXhlbGxlbmd0aCAqIDEuNSwgMiAtIHBpeGVsbGVuZ3RoICogMS41KTsKCQlmbG9hdDIgc3BhY2luZyA9IDIgLSByYWRpaSAtIG5laWdoYm9yX3JhZGlpOwoJCWZsb2F0MiBleHRyYV9wYWQgPSBtYXgocGl4ZWxsZW5ndGggKiAuMDYyNSAtIHNwYWNpbmcsIGZsb2F0MigwKSk7CgkJcmFkaWkgLT0gZXh0cmFfcGFkICogLjU7Cgl9CglmbG9hdDIgYWFfb3V0c2V0ID0gYWFfYmxvYXRfZGlyZWN0aW9uICogYWFfYmxvYXRyYWRpdXMgKiBhYV9ibG9hdF9tdWx0aXBsaWVyOwoJZmxvYXQyIHZlcnRleHBvcyA9IGNvcm5lciArIHJhZGl1c19vdXRzZXQgKiByYWRpaSArIGFhX291dHNldDsKCWlmIChjb3ZlcmFnZSA+IC41KSAKCXsKCQlpZiAoYWFfYmxvYXRfZGlyZWN0aW9uLnggIT0gMCAmJiB2ZXJ0ZXhwb3MueCAqIGNvcm5lci54IDwgMCkgCgkJewoJCQlmbG9hdCBiYWNrc2V0ID0gYWJzKHZlcnRleHBvcy54KTsKCQkJdmVydGV4cG9zLnggPSAwOwoJCQl2ZXJ0ZXhwb3MueSArPSBiYWNrc2V0ICogc2lnbihjb3JuZXIueSkgKiBwaXhlbGxlbmd0aC55L3BpeGVsbGVuZ3RoLng7CgkJCWNvdmVyYWdlID0gKGNvdmVyYWdlIC0gLjUpICogYWJzKGNvcm5lci54KSAvIChhYnMoY29ybmVyLngpICsgYmFja3NldCkgKyAuNTsKCQl9CgkJaWYgKGFhX2Jsb2F0X2RpcmVjdGlvbi55ICE9IDAgJiYgdmVydGV4cG9zLnkgKiBjb3JuZXIueSA8IDApIAoJCXsKCQkJZmxvYXQgYmFja3NldCA9IGFicyh2ZXJ0ZXhwb3MueSk7CgkJCXZlcnRleHBvcy55ID0gMDsKCQkJdmVydGV4cG9zLnggKz0gYmFja3NldCAqIHNpZ24oY29ybmVyLngpICogcGl4ZWxsZW5ndGgueC9waXhlbGxlbmd0aC55OwoJCQljb3ZlcmFnZSA9IChjb3ZlcmFnZSAtIC41KSAqIGFicyhjb3JuZXIueSkgLyAoYWJzKGNvcm5lci55KSArIGJhY2tzZXQpICsgLjU7CgkJfQoJfQoJZmxvYXQyeDIgc2tld21hdHJpeCA9IGZsb2F0MngyKHNrZXcueHksIHNrZXcuencpOwoJZmxvYXQyIGRldmNvb3JkID0gdmVydGV4cG9zICogc2tld21hdHJpeCArIHRyYW5zbGF0ZV9hbmRfbG9jYWxyb3RhdGUueHk7CglpZiAoMCAhPSBpc19saW5lYXJfY292ZXJhZ2UpIAoJewoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MigwLCBjb3ZlcmFnZSAqIGNvdmVyYWdlX211bHRpcGxpZXIpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdDIgYXJjY29vcmQgPSAxIC0gYWJzKHJhZGl1c19vdXRzZXQpICsgYWFfb3V0c2V0L3JhZGlpICogY29ybmVyOwoJCXZhcmNjb29yZF9TMC54eSA9IGZsb2F0MihhcmNjb29yZC54KzEsIGFyY2Nvb3JkLnkpOwoJfQoJc2tfUG9zaXRpb24gPSBkZXZjb29yZC54eTAxOwp9CgEAAAAHBQAAY29uc3QgaW50IGtGaWxsQUFfUzEgPSAxOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1Y2lyY2xlX1MxOwpmbGF0IGluIGhhbGY0IHZjb2xvcl9TMDsKaW4gZmxvYXQyIHZhcmNjb29yZF9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgQ2lyY2xlX1MxKGhhbGY0IF9pbnB1dCkgCnsKCWhhbGY0IF90bXBfMF9pbkNvbG9yID0gX2lucHV0OwoJaGFsZiBkOwoJaWYgKGludCgzKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMykgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWQgPSBoYWxmKChsZW5ndGgoKHVjaXJjbGVfUzEueHkgLSBza19GcmFnQ29vcmQueHkpICogdWNpcmNsZV9TMS53KSAtIDEuMCkgKiB1Y2lyY2xlX1MxLnopOwoJfQoJZWxzZSAKCXsKCQlkID0gaGFsZigoMS4wIC0gbGVuZ3RoKCh1Y2lyY2xlX1MxLnh5IC0gc2tfRnJhZ0Nvb3JkLnh5KSAqIHVjaXJjbGVfUzEudykpICogdWNpcmNsZV9TMS56KTsKCX0KCWlmIChpbnQoMykgPT0ga0ZpbGxBQV9TMSB8fCBpbnQoMykgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCXJldHVybiBoYWxmNChfaW5wdXQgKiBzYXR1cmF0ZShkKSk7Cgl9CgllbHNlIAoJewoJCXJldHVybiBoYWxmNChkID4gMC41ID8gX2lucHV0IDogaGFsZjQoMC4wKSk7Cgl9Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEZpbGxSUmVjdE9wOjpQcm9jZXNzb3IKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2Y29sb3JfUzA7CglmbG9hdCB4X3BsdXNfMT12YXJjY29vcmRfUzAueCwgeT12YXJjY29vcmRfUzAueTsKCWhhbGYgY292ZXJhZ2U7CglpZiAoMCA9PSB4X3BsdXNfMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKHkpOwoJfQoJZWxzZSAKCXsKCQlmbG9hdCBmbiA9IHhfcGx1c18xICogKHhfcGx1c18xIC0gMik7CgkJZm4gPSBmbWEoeSx5LCBmbik7CgkJZmxvYXQgZm53aWR0aCA9IGZ3aWR0aChmbik7CgkJY292ZXJhZ2UgPSAuNSAtIGhhbGYoZm4vZm53aWR0aCk7CgkJY292ZXJhZ2UgPSBjbGFtcChjb3ZlcmFnZSwgMCwgMSk7Cgl9CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGNvdmVyYWdlKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IENpcmNsZV9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAIAAAADgAAAHJhZGlpX3NlbGVjdG9yAAAZAAAAY29ybmVyX2FuZF9yYWRpdXNfb3V0c2V0cwAAABUAAABhYV9ibG9hdF9hbmRfY292ZXJhZ2UAAAAHAAAAcmFkaWlfeAAHAAAAcmFkaWlfeQAEAAAAc2tldxkAAAB0cmFuc2xhdGVfYW5kX2xvY2Fscm90YXRlAAAABQAAAGNvbG9yAAAAAQAAAAAAAAA=","HUIAAAAAAAQAADAAAIOAAAH677777777777QGHAQAD7P7777777777YBAAAAAAAAAAALUAQMAAAAAAAAAEAAAABJYQAAAAAAAAIAAAAAWCBAAAABAAAAANAECAZAAAAAAAAAAAFAAMAAAABAAAAAAABBAM":"CQAAAExTS1M2AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKaW4gZmxvYXQyIHBvc2l0aW9uOwppbiBmbG9hdDIgbG9jYWxDb29yZDsKb3V0IGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCglza19Qb3NpdGlvbiA9IHBvc2l0aW9uLnh5MDE7Cgl7CgkJdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzAgPSBmbG9hdDN4Mih1bWF0cml4X1MxKSAqIGxvY2FsQ29vcmQueHkxOwoJfQp9CgAAAAAAAAgFAAB1bmlmb3JtIGZsb2F0M3gzIHVtYXRyaXhfUzFfYzBfYzA7CnVuaWZvcm0gaGFsZjIgdUluY3JlbWVudF9TMV9jMDsKdW5pZm9ybSBoYWxmMiB1T2Zmc2V0c0FuZEtlcm5lbF9TMV9jMFsxM107CnVuaWZvcm0gZmxvYXQzeDMgdW1hdHJpeF9TMTsKdW5pZm9ybSBzYW1wbGVyMkQgdVRleHR1cmVTYW1wbGVyXzBfUzE7CmluIGZsb2F0MiB2VHJhbnNmb3JtZWRDb29yZHNfMl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBfY29vcmRzKSAKewoJcmV0dXJuIHNhbXBsZSh1VGV4dHVyZVNhbXBsZXJfMF9TMSwgX2Nvb3Jkcyk7Cn0KaGFsZjQgTWF0cml4RWZmZWN0X1MxX2MwX2MwKGhhbGY0IF9pbnB1dCwgZmxvYXQyIF9jb29yZHMpIAp7CglyZXR1cm4gVGV4dHVyZUVmZmVjdF9TMV9jMF9jMF9jMChfaW5wdXQsIGZsb2F0M3gyKHVtYXRyaXhfUzFfYzBfYzApICogX2Nvb3Jkcy54eTEpOwp9CmhhbGY0IFNtb290aF9TMV9jMChoYWxmNCBfaW5wdXQsIGZsb2F0MiBjb29yZCwgaGFsZjIgb2Zmc2V0QW5kS2VybmVsKSAKewoJcmV0dXJuIE1hdHJpeEVmZmVjdF9TMV9jMF9jMChfaW5wdXQsIChjb29yZCArIG9mZnNldEFuZEtlcm5lbC54ICogdUluY3JlbWVudF9TMV9jMCkpICogb2Zmc2V0QW5kS2VybmVsLnk7Cn0KaGFsZjQgR2F1c3NpYW5Db252b2x1dGlvbl9TMV9jMChoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBjb2xvciA9IGhhbGY0KDApOwoJZmxvYXQyIGNvb3JkID0gdlRyYW5zZm9ybWVkQ29vcmRzXzJfUzA7Cglmb3IgKGludCBpPTA7IGk8MTM7ICsraSkgCgl7CgkJY29sb3IgKz0gU21vb3RoX1MxX2MwKF9pbnB1dCwgY29vcmQsIHVPZmZzZXRzQW5kS2VybmVsX1MxX2MwW2ldKTsKCX0KCXJldHVybiBjb2xvcjsKfQpoYWxmNCBNYXRyaXhFZmZlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJcmV0dXJuIEdhdXNzaWFuQ29udm9sdXRpb25fUzFfYzAoX2lucHV0KTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZFBlckVkZ2VBQUdlb21ldHJ5UHJvY2Vzc29yCgloYWxmNCBvdXRwdXRDb2xvcl9TMCA9IGhhbGY0KDEpOwoJY29uc3QgaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNCgxKTsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IE1hdHJpeEVmZmVjdF9TMShvdXRwdXRDb2xvcl9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0X1MxICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAIAAAAcG9zaXRpb24KAAAAbG9jYWxDb29yZAAAAQAAAAAAAAA=","HTQAAGAABBYAAAEIXBAAAGEAMAAAAAAAAAAAAAAAQAHAAAAAQAAAAAAAQQGAAAAA":"CQAAAExTS1M/AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiBmbG9hdDQgaW5RdWFkRWRnZTsKb3V0IGZsb2F0NCB2UXVhZEVkZ2VfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgUXVhZEVkZ2UKCXZRdWFkRWRnZV9TMCA9IGluUXVhZEVkZ2U7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgABAAAAHQMAAGluIGZsb2F0NCB2UXVhZEVkZ2VfUzA7CmluIGhhbGY0IHZpbkNvbG9yX1MwOwpvdXQgaGFsZjQgc2tfRnJhZ0NvbG9yOwp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgUXVhZEVkZ2UKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGYgZWRnZUFscGhhOwoJaGFsZjIgZHV2ZHggPSBoYWxmMihkRmR4KHZRdWFkRWRnZV9TMC54eSkpOwoJaGFsZjIgZHV2ZHkgPSBoYWxmMihkRmR5KHZRdWFkRWRnZV9TMC54eSkpOwoJaWYgKHZRdWFkRWRnZV9TMC56ID4gMC4wICYmIHZRdWFkRWRnZV9TMC53ID4gMC4wKSAKCXsKCQllZGdlQWxwaGEgPSBoYWxmKG1pbihtaW4odlF1YWRFZGdlX1MwLnosIHZRdWFkRWRnZV9TMC53KSArIDAuNSwgMS4wKSk7Cgl9CgllbHNlIAoJewoJCWhhbGYyIGdGID0gaGFsZjIoaGFsZigyLjAqdlF1YWRFZGdlX1MwLngqZHV2ZHgueCAtIGR1dmR4LnkpLCAgICAgICAgICAgICAgICAgaGFsZigyLjAqdlF1YWRFZGdlX1MwLngqZHV2ZHkueCAtIGR1dmR5LnkpKTsKCQllZGdlQWxwaGEgPSBoYWxmKHZRdWFkRWRnZV9TMC54KnZRdWFkRWRnZV9TMC54IC0gdlF1YWRFZGdlX1MwLnkpOwoJCWVkZ2VBbHBoYSA9IHNhdHVyYXRlKDAuNSAtIGVkZ2VBbHBoYSAvIGxlbmd0aChnRikpOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0Q292ZXJhZ2VfUzA7Cgl9Cn0KAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IACgAAAGluUXVhZEVkZ2UAAAEAAAAAAAAA","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAIBSQB5VRECGAEAAAMAAAAAAAAAAACAA4AAAACAAAAAAACCAYAA":"CQAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAABAAAA4wQAAGNvbnN0IGludCBrRmlsbEJXX1MxID0gMDsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEJXX1MxID0gMjsKY29uc3QgaW50IGtJbnZlcnNlRmlsbEFBX1MxID0gMzsKdW5pZm9ybSBmbG9hdDQgdXJlY3RVbmlmb3JtX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1MwOwpmbGF0IGluIGZsb2F0IHZUZXhJbmRleF9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IFJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJaGFsZjQgX3RtcF8wX2luQ29sb3IgPSBfaW5wdXQ7CgloYWxmIGNvdmVyYWdlOwoJaWYgKGludCgxKSA9PSBrRmlsbEJXX1MxIHx8IGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSkgCgl7CgkJY292ZXJhZ2UgPSBoYWxmKGFsbChncmVhdGVyVGhhbihmbG9hdDQoc2tfRnJhZ0Nvb3JkLnh5LCB1cmVjdFVuaWZvcm1fUzEuencpLCBmbG9hdDQodXJlY3RVbmlmb3JtX1MxLnh5LCBza19GcmFnQ29vcmQueHkpKSkgPyAxIDogMCk7Cgl9CgllbHNlIAoJewoJCWhhbGY0IGRpc3RzNCA9IGNsYW1wKGhhbGY0KDEuMCwgMS4wLCAtMS4wLCAtMS4wKSAqIGhhbGY0KHNrX0ZyYWdDb29yZC54eXh5IC0gdXJlY3RVbmlmb3JtX1MxKSwgMC4wLCAxLjApOwoJCWhhbGYyIGRpc3RzMiA9IChkaXN0czQueHkgKyBkaXN0czQuencpIC0gMS4wOwoJCWNvdmVyYWdlID0gZGlzdHMyLnggKiBkaXN0czIueTsKCX0KCWlmIChpbnQoMSkgPT0ga0ludmVyc2VGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEFBX1MxKSAKCXsKCQljb3ZlcmFnZSA9IDEuMCAtIGNvdmVyYWdlOwoJfQoJcmV0dXJuIGhhbGY0KF9pbnB1dCAqIGNvdmVyYWdlKTsKfQp2b2lkIG1haW4oKSAKewoJLy8gU3RhZ2UgMCwgQml0bWFwVGV4dAoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZpbkNvbG9yX1MwOwoJaGFsZjQgdGV4Q29sb3I7Cgl7CgkJdGV4Q29sb3IgPSBzYW1wbGUodVRleHR1cmVTYW1wbGVyXzBfUzAsIHZUZXh0dXJlQ29vcmRzX1MwKS5ycnJyOwoJfQoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSB0ZXhDb2xvcjsKCWhhbGY0IG91dHB1dF9TMTsKCW91dHB1dF9TMSA9IFJlY3RfUzEob3V0cHV0Q292ZXJhZ2VfUzApOwoJewoJCS8vIFhmZXIgUHJvY2Vzc29yOiBQb3J0ZXIgRHVmZgoJCXNrX0ZyYWdDb2xvciA9IG91dHB1dENvbG9yX1MwICogb3V0cHV0X1MxOwoJfQp9CgABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAPAAAAaW5UZXh0dXJlQ29vcmRzAAEAAAAAAAAA","BYIBQAAABQAAIAABBYAAAEIXBAAP777777777777AAAAAAAAAAAABUABAAAAAEAAAAAIBEABAAAAA":"CQAAAExTS1M+AQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwpvdXQgaGFsZjQgdmNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBEZWZhdWx0R2VvbWV0cnlQcm9jZXNzb3IKCWhhbGY0IGNvbG9yID0gaW5Db2xvcjsKCXZjb2xvcl9TMCA9IGNvbG9yOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gaW5Qb3NpdGlvbjsKCWZsb2F0MiBfdG1wXzNfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247Cglza19Qb3NpdGlvbiA9IF90bXBfMV9pblBvc2l0aW9uLnh5MDE7Cn0KAAAAAAAAHgEAAGluIGhhbGY0IHZjb2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIERlZmF1bHRHZW9tZXRyeVByb2Nlc3NvcgoJaGFsZjQgb3V0cHV0Q29sb3JfUzA7CglvdXRwdXRDb2xvcl9TMCA9IHZjb2xvcl9TMDsKCWNvbnN0IGhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gaGFsZjQoMSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAACgAAAGluUG9zaXRpb24AAAcAAABpbkNvbG9yAAEAAAAAAAAA","DAQAAAAAAABGAABAYAAQAIHCAIAYAQUBAEAAAAAAEAAAAAAAAAAAAIAHSADQAAAQAAAAAAFAAMAAAABAAAAAAABBAM":"CQAAAExTS1MWAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQyIHVBdGxhc1NpemVJbnZfUzA7CmluIGZsb2F0MiBpblBvc2l0aW9uOwppbiBoYWxmNCBpbkNvbG9yOwppbiB1c2hvcnQyIGluVGV4dHVyZUNvb3JkczsKb3V0IGZsb2F0MiB2VGV4dHVyZUNvb3Jkc19TMDsKZmxhdCBvdXQgZmxvYXQgdlRleEluZGV4X1MwOwpvdXQgaGFsZjQgdmluQ29sb3JfUzA7CnZvaWQgbWFpbigpIAp7CgkvLyBQcmltaXRpdmUgUHJvY2Vzc29yIEJpdG1hcFRleHQKCWludCB0ZXhJZHggPSAwOwoJZmxvYXQyIHVub3JtVGV4Q29vcmRzID0gZmxvYXQyKGluVGV4dHVyZUNvb3Jkcy54LCBpblRleHR1cmVDb29yZHMueSk7Cgl2VGV4dHVyZUNvb3Jkc19TMCA9IHVub3JtVGV4Q29vcmRzICogdUF0bGFzU2l6ZUludl9TMDsKCXZUZXhJbmRleF9TMCA9IGZsb2F0KHRleElkeCk7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJc2tfUG9zaXRpb24gPSBpblBvc2l0aW9uLnh5MDE7Cn0KAAABAAAAWAMAAHVuaWZvcm0gZmxvYXQ0IHVpbm5lclJlY3RfUzE7CnVuaWZvcm0gaGFsZjIgdXJhZGl1c1BsdXNIYWxmX1MxOwp1bmlmb3JtIHNhbXBsZXIyRCB1VGV4dHVyZVNhbXBsZXJfMF9TMDsKaW4gZmxvYXQyIHZUZXh0dXJlQ29vcmRzX1MwOwpmbGF0IGluIGZsb2F0IHZUZXhJbmRleF9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CmhhbGY0IENpcmN1bGFyUlJlY3RfUzEoaGFsZjQgX2lucHV0KSAKewoJZmxvYXQyIGR4eTAgPSB1aW5uZXJSZWN0X1MxLkxUIC0gc2tfRnJhZ0Nvb3JkLnh5OwoJZmxvYXQyIGR4eTEgPSBza19GcmFnQ29vcmQueHkgLSB1aW5uZXJSZWN0X1MxLlJCOwoJZmxvYXQyIGR4eSA9IG1heChtYXgoZHh5MCwgZHh5MSksIDAuMCk7CgloYWxmIGFscGhhID0gaGFsZihzYXR1cmF0ZSh1cmFkaXVzUGx1c0hhbGZfUzEueCAtIGxlbmd0aChkeHkpKSk7CglyZXR1cm4gX2lucHV0ICogYWxwaGE7Cn0Kdm9pZCBtYWluKCkgCnsKCS8vIFN0YWdlIDAsIEJpdG1hcFRleHQKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWhhbGY0IHRleENvbG9yOwoJewoJCXRleENvbG9yID0gc2FtcGxlKHVUZXh0dXJlU2FtcGxlcl8wX1MwLCB2VGV4dHVyZUNvb3Jkc19TMCkucnJycjsKCX0KCWhhbGY0IG91dHB1dENvdmVyYWdlX1MwID0gdGV4Q29sb3I7CgloYWxmNCBvdXRwdXRfUzE7CglvdXRwdXRfUzEgPSBDaXJjdWxhclJSZWN0X1MxKG91dHB1dENvdmVyYWdlX1MwKTsKCXsKCQkvLyBYZmVyIFByb2Nlc3NvcjogUG9ydGVyIER1ZmYKCQlza19GcmFnQ29sb3IgPSBvdXRwdXRDb2xvcl9TMCAqIG91dHB1dF9TMTsKCX0KfQoBAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAPAAAAaW5UZXh0dXJlQ29vcmRzAAEAAAAAAAAA","AYQA5AADQAAAOAEARAFQJAABBADIB7777777777777777777777767YAAAAAAAAAACAMQAHOMFARUBIAADAAAAAAAAAAAAAAHIAAAAAAAIAAAAAQGIAA":"CQAAAExTS1PMAQAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGY0IHZpbkNvbG9yX1MwOwp2b2lkIG1haW4oKSAKewoJLy8gUHJpbWl0aXZlIFByb2Nlc3NvciBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJdmluQ2lyY2xlRWRnZV9TMCA9IGluQ2lyY2xlRWRnZTsKCXZpbkNvbG9yX1MwID0gaW5Db2xvcjsKCWZsb2F0MiBfdG1wXzBfaW5Qb3NpdGlvbiA9IGluUG9zaXRpb247CglmbG9hdDIgX3RtcF8xX2luUG9zaXRpb24gPSB1bG9jYWxNYXRyaXhfUzAueHogKiBpblBvc2l0aW9uICsgdWxvY2FsTWF0cml4X1MwLnl3OwoJc2tfUG9zaXRpb24gPSBfdG1wXzBfaW5Qb3NpdGlvbi54eTAxOwp9CgEAAAAcBQAAY29uc3QgaW50IGtGaWxsQldfUzEgPSAwOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQldfUzEgPSAyOwpjb25zdCBpbnQga0ludmVyc2VGaWxsQUFfUzEgPSAzOwp1bmlmb3JtIGZsb2F0NCB1cmVjdFVuaWZvcm1fUzE7CmluIGZsb2F0NCB2aW5DaXJjbGVFZGdlX1MwOwppbiBoYWxmNCB2aW5Db2xvcl9TMDsKb3V0IGhhbGY0IHNrX0ZyYWdDb2xvcjsKaGFsZjQgUmVjdF9TMShoYWxmNCBfaW5wdXQpIAp7CgloYWxmNCBfdG1wXzBfaW5Db2xvciA9IF9pbnB1dDsKCWhhbGYgY292ZXJhZ2U7CglpZiAoaW50KDEpID09IGtGaWxsQldfUzEgfHwgaW50KDEpID09IGtJbnZlcnNlRmlsbEJXX1MxKSAKCXsKCQljb3ZlcmFnZSA9IGhhbGYoYWxsKGdyZWF0ZXJUaGFuKGZsb2F0NChza19GcmFnQ29vcmQueHksIHVyZWN0VW5pZm9ybV9TMS56dyksIGZsb2F0NCh1cmVjdFVuaWZvcm1fUzEueHksIHNrX0ZyYWdDb29yZC54eSkpKSA/IDEgOiAwKTsKCX0KCWVsc2UgCgl7CgkJaGFsZjQgZGlzdHM0ID0gY2xhbXAoaGFsZjQoMS4wLCAxLjAsIC0xLjAsIC0xLjApICogaGFsZjQoc2tfRnJhZ0Nvb3JkLnh5eHkgLSB1cmVjdFVuaWZvcm1fUzEpLCAwLjAsIDEuMCk7CgkJaGFsZjIgZGlzdHMyID0gKGRpc3RzNC54eSArIGRpc3RzNC56dykgLSAxLjA7CgkJY292ZXJhZ2UgPSBkaXN0czIueCAqIGRpc3RzMi55OwoJfQoJaWYgKGludCgxKSA9PSBrSW52ZXJzZUZpbGxCV19TMSB8fCBpbnQoMSkgPT0ga0ludmVyc2VGaWxsQUFfUzEpIAoJewoJCWNvdmVyYWdlID0gMS4wIC0gY292ZXJhZ2U7Cgl9CglyZXR1cm4gaGFsZjQoX2lucHV0ICogY292ZXJhZ2UpOwp9CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZjQgb3V0cHV0Q292ZXJhZ2VfUzAgPSBoYWxmNChlZGdlQWxwaGEpOwoJaGFsZjQgb3V0cHV0X1MxOwoJb3V0cHV0X1MxID0gUmVjdF9TMShvdXRwdXRDb3ZlcmFnZV9TMCk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRfUzE7Cgl9Cn0KAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAKAAAAaW5Qb3NpdGlvbgAABwAAAGluQ29sb3IADAAAAGluQ2lyY2xlRWRnZQEAAAAAAAAA","AYTRVAADQAAAOAEARAFQJAABBADAAAILBYAACCYUQD777777777767YAAAAAAAAAAAAOQAAAAAAAQAAAABAMQAAAAA":"CQAAAExTS1NyAgAAdW5pZm9ybSBmbG9hdDQgc2tfUlRBZGp1c3Q7CnVuaWZvcm0gZmxvYXQ0IHVsb2NhbE1hdHJpeF9TMDsKaW4gZmxvYXQyIGluUG9zaXRpb247CmluIGhhbGY0IGluQ29sb3I7CmluIGZsb2F0NCBpbkNpcmNsZUVkZ2U7CmluIGhhbGYzIGluQ2xpcFBsYW5lOwppbiBoYWxmMyBpbklzZWN0UGxhbmU7Cm91dCBmbG9hdDQgdmluQ2lyY2xlRWRnZV9TMDsKb3V0IGhhbGYzIHZpbkNsaXBQbGFuZV9TMDsKb3V0IGhhbGYzIHZpbklzZWN0UGxhbmVfUzA7Cm91dCBoYWxmNCB2aW5Db2xvcl9TMDsKdm9pZCBtYWluKCkgCnsKCS8vIFByaW1pdGl2ZSBQcm9jZXNzb3IgQ2lyY2xlR2VvbWV0cnlQcm9jZXNzb3IKCXZpbkNpcmNsZUVkZ2VfUzAgPSBpbkNpcmNsZUVkZ2U7Cgl2aW5DbGlwUGxhbmVfUzAgPSBpbkNsaXBQbGFuZTsKCXZpbklzZWN0UGxhbmVfUzAgPSBpbklzZWN0UGxhbmU7Cgl2aW5Db2xvcl9TMCA9IGluQ29sb3I7CglmbG9hdDIgX3RtcF8wX2luUG9zaXRpb24gPSBpblBvc2l0aW9uOwoJZmxvYXQyIF90bXBfMV9pblBvc2l0aW9uID0gdWxvY2FsTWF0cml4X1MwLnh6ICogaW5Qb3NpdGlvbiArIHVsb2NhbE1hdHJpeF9TMC55dzsKCXNrX1Bvc2l0aW9uID0gX3RtcF8wX2luUG9zaXRpb24ueHkwMTsKfQoAAAAAAAD1AwAAaW4gZmxvYXQ0IHZpbkNpcmNsZUVkZ2VfUzA7CmluIGhhbGYzIHZpbkNsaXBQbGFuZV9TMDsKaW4gaGFsZjMgdmluSXNlY3RQbGFuZV9TMDsKaW4gaGFsZjQgdmluQ29sb3JfUzA7Cm91dCBoYWxmNCBza19GcmFnQ29sb3I7CnZvaWQgbWFpbigpIAp7CgkvLyBTdGFnZSAwLCBDaXJjbGVHZW9tZXRyeVByb2Nlc3NvcgoJZmxvYXQ0IGNpcmNsZUVkZ2U7CgljaXJjbGVFZGdlID0gdmluQ2lyY2xlRWRnZV9TMDsKCWhhbGYzIGNsaXBQbGFuZTsKCWNsaXBQbGFuZSA9IHZpbkNsaXBQbGFuZV9TMDsKCWhhbGYzIGlzZWN0UGxhbmU7Cglpc2VjdFBsYW5lID0gdmluSXNlY3RQbGFuZV9TMDsKCWhhbGY0IG91dHB1dENvbG9yX1MwOwoJb3V0cHV0Q29sb3JfUzAgPSB2aW5Db2xvcl9TMDsKCWZsb2F0IGQgPSBsZW5ndGgoY2lyY2xlRWRnZS54eSk7CgloYWxmIGRpc3RhbmNlVG9PdXRlckVkZ2UgPSBoYWxmKGNpcmNsZUVkZ2UueiAqICgxLjAgLSBkKSk7CgloYWxmIGVkZ2VBbHBoYSA9IHNhdHVyYXRlKGRpc3RhbmNlVG9PdXRlckVkZ2UpOwoJaGFsZiBkaXN0YW5jZVRvSW5uZXJFZGdlID0gaGFsZihjaXJjbGVFZGdlLnogKiAoZCAtIGNpcmNsZUVkZ2UudykpOwoJaGFsZiBpbm5lckFscGhhID0gc2F0dXJhdGUoZGlzdGFuY2VUb0lubmVyRWRnZSk7CgllZGdlQWxwaGEgKj0gaW5uZXJBbHBoYTsKCWhhbGYgY2xpcCA9IGhhbGYoc2F0dXJhdGUoY2lyY2xlRWRnZS56ICogZG90KGNpcmNsZUVkZ2UueHksIGNsaXBQbGFuZS54eSkgKyBjbGlwUGxhbmUueikpOwoJY2xpcCAqPSBoYWxmKHNhdHVyYXRlKGNpcmNsZUVkZ2UueiAqIGRvdChjaXJjbGVFZGdlLnh5LCBpc2VjdFBsYW5lLnh5KSArIGlzZWN0UGxhbmUueikpOwoJZWRnZUFscGhhICo9IGNsaXA7CgloYWxmNCBvdXRwdXRDb3ZlcmFnZV9TMCA9IGhhbGY0KGVkZ2VBbHBoYSk7Cgl7CgkJLy8gWGZlciBQcm9jZXNzb3I6IFBvcnRlciBEdWZmCgkJc2tfRnJhZ0NvbG9yID0gb3V0cHV0Q29sb3JfUzAgKiBvdXRwdXRDb3ZlcmFnZV9TMDsKCX0KfQoAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAoAAABpblBvc2l0aW9uAAAHAAAAaW5Db2xvcgAMAAAAaW5DaXJjbGVFZGdlCwAAAGluQ2xpcFBsYW5lAAwAAABpbklzZWN0UGxhbmUBAAAAAAAAAA=="}} \ No newline at end of file diff --git a/test/model/filters_test.dart b/test/model/filters_test.dart index f6d7059d8..39e6f582f 100644 --- a/test/model/filters_test.dart +++ b/test/model/filters_test.dart @@ -5,6 +5,7 @@ import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/location.dart'; import 'package:aves/model/filters/mime.dart'; +import 'package:aves/model/filters/missing.dart'; import 'package:aves/model/filters/path.dart'; import 'package:aves/model/filters/query.dart'; import 'package:aves/model/filters/rating.dart'; @@ -53,6 +54,9 @@ void main() { final mime = MimeFilter.video; expect(mime, jsonRoundTrip(mime)); + final missing = MissingFilter.title; + expect(missing, jsonRoundTrip(missing)); + final path = PathFilter('/some/path/'); expect(path, jsonRoundTrip(path)); diff --git a/test/model/video/metadata_test.dart b/test/model/video/metadata_test.dart index 4833f9037..6983fbac8 100644 --- a/test/model/video/metadata_test.dart +++ b/test/model/video/metadata_test.dart @@ -11,6 +11,7 @@ void main() { expect(VideoMetadataFormatter.parseVideoDate('2021-09-10T7:14:49 pmZ'), DateTime(2021, 9, 10, 19, 14, 49).millisecondsSinceEpoch); expect(VideoMetadataFormatter.parseVideoDate('2022-01-28T5:07:46 p. m.Z'), DateTime(2022, 1, 28, 5, 7, 46).millisecondsSinceEpoch); expect(VideoMetadataFormatter.parseVideoDate('2012-1-1T12:00:00Z'), DateTime(2012, 1, 1, 12, 0, 0).millisecondsSinceEpoch); + expect(VideoMetadataFormatter.parseVideoDate('2020.10.14'), DateTime(2020, 10, 14).millisecondsSinceEpoch); }); test('Ambiguous date', () { diff --git a/test/utils/time_utils_test.dart b/test/utils/time_utils_test.dart index b6c49cdef..4bc33f228 100644 --- a/test/utils/time_utils_test.dart +++ b/test/utils/time_utils_test.dart @@ -29,5 +29,8 @@ void main() { expect(parseUnknownDateFormat('Screenshot_20211028-115056_Aves'), DateTime(2021, 10, 28, 11, 50, 56, 0)); expect(parseUnknownDateFormat('Screenshot_2022-05-14-15-40-29-164_uri'), DateTime(2022, 5, 14, 15, 40, 29, 164)); + expect(parseUnknownDateFormat('2019-02-18 16.00.12-DCM'), DateTime(2019, 2, 18, 16, 0, 12, 0)); + expect(parseUnknownDateFormat('2020-11-01 00.31.02'), DateTime(2020, 11, 1, 0, 31, 2, 0)); + expect(parseUnknownDateFormat('2019-10-31 135703'), DateTime(2019, 10, 31, 13, 57, 3, 0)); }); } diff --git a/test_driver/driver_screenshots.dart b/test_driver/driver_screenshots.dart index f4de8d3f8..2c2e11050 100644 --- a/test_driver/driver_screenshots.dart +++ b/test_driver/driver_screenshots.dart @@ -2,7 +2,7 @@ import 'package:aves/main_play.dart' as app; import 'package:aves/model/settings/defaults.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/model/source/enums.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves/widgets/filter_grids/countries_page.dart'; import 'package:flutter_driver/driver_extension.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/test_driver/driver_shaders.dart b/test_driver/driver_shaders.dart index 4901d9df8..5c1fee2c8 100644 --- a/test_driver/driver_shaders.dart +++ b/test_driver/driver_shaders.dart @@ -4,6 +4,7 @@ import 'package:aves/main_play.dart' as app; import 'package:aves/model/settings/defaults.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/settings.dart'; +import 'package:aves/model/source/enums/enums.dart'; import 'package:aves_map/src/style.dart'; import 'package:flutter_driver/driver_extension.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -29,6 +30,8 @@ Future configureAndLaunch() async { ..homePage = HomePageSetting.collection ..enableBottomNavigationBar = true // collection + ..collectionSectionFactor = EntryGroupFactor.album + ..collectionSortFactor = EntrySortFactor.date ..collectionBrowsingQuickActions = SettingsDefaults.collectionBrowsingQuickActions // viewer ..showOverlayOnOpening = true @@ -38,6 +41,6 @@ Future configureAndLaunch() async { ..showOverlayThumbnailPreview = true ..imageBackground = EntryBackground.checkered // info - ..infoMapStyle = EntryMapStyle.googleNormal; + ..mapStyle = EntryMapStyle.googleNormal; app.main(); } diff --git a/test_driver/driver_shaders_test.dart b/test_driver/driver_shaders_test.dart index 9abd1fb42..f4dedfe72 100644 --- a/test_driver/driver_shaders_test.dart +++ b/test_driver/driver_shaders_test.dart @@ -1,7 +1,6 @@ // ignore_for_file: avoid_print import 'dart:async'; -import 'package:aves/model/source/enums.dart'; import 'package:flutter_driver/flutter_driver.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; @@ -37,8 +36,6 @@ void main() { test('scan media dir', () => driver.scanMediaDir(shadersTargetDirAndroid)); visitAbout(); visitSettings(); - sortCollection(); - groupCollection(); visitMap(); selectFirstAlbum(); searchAlbum(); @@ -93,26 +90,6 @@ void visitSettings() { }); } -void sortCollection() { - test('[collection] sort', () async { - await driver.tapKeyAndWait('appbar-menu-button'); - await driver.tapKeyAndWait('menu-configureView'); - await driver.tapKeyAndWait('tab-sort'); - await driver.tapKeyAndWait(EntrySortFactor.date.toString()); - await driver.tapKeyAndWait('button-apply'); - }); -} - -void groupCollection() { - test('[collection] group', () async { - await driver.tapKeyAndWait('appbar-menu-button'); - await driver.tapKeyAndWait('menu-configureView'); - await driver.tapKeyAndWait('tab-group'); - await driver.tapKeyAndWait(EntryGroupFactor.album.toString()); - await driver.tapKeyAndWait('button-apply'); - }); -} - void visitMap() { test('[collection] visit map', () async { await driver.tapKeyAndWait('appbar-menu-button'); diff --git a/untranslated.json b/untranslated.json index b1efafdb6..3675a7374 100644 --- a/untranslated.json +++ b/untranslated.json @@ -1,43 +1,81 @@ { - "es": [ - "entryInfoActionEditDescription", - "filterRecentlyAddedLabel", - "editEntryDescriptionDialogTitle", - "settingsConfirmationAfterMoveToBinItems" + "de": [ + "entryInfoActionEditTitleDescription", + "filterNoDateLabel", + "filterNoTitleLabel", + "viewDialogReverseSortOrder", + "sortOrderNewestFirst", + "sortOrderOldestFirst", + "sortOrderAtoZ", + "sortOrderZtoA", + "sortOrderHighestFirst", + "sortOrderLowestFirst", + "sortOrderLargestFirst", + "sortOrderSmallestFirst", + "searchMetadataSectionTitle" ], - "id": [ - "entryInfoActionEditDescription", + "es": [ + "entryInfoActionEditTitleDescription", + "filterNoDateLabel", + "filterNoTitleLabel", "filterRecentlyAddedLabel", - "editEntryDescriptionDialogTitle", + "viewDialogReverseSortOrder", + "sortOrderNewestFirst", + "sortOrderOldestFirst", + "sortOrderAtoZ", + "sortOrderZtoA", + "sortOrderHighestFirst", + "sortOrderLowestFirst", + "sortOrderLargestFirst", + "sortOrderSmallestFirst", + "searchMetadataSectionTitle", "settingsConfirmationAfterMoveToBinItems", - "settingsViewerGestureSideTapNext" + "viewerInfoLabelDescription" ], "ja": [ - "entryInfoActionEditDescription", + "entryInfoActionEditTitleDescription", + "filterNoDateLabel", + "filterNoTitleLabel", "filterRecentlyAddedLabel", - "editEntryDescriptionDialogTitle", - "settingsConfirmationAfterMoveToBinItems", - "settingsViewerGestureSideTapNext" - ], - - "ru": [ - "entryInfoActionEditDescription", - "filterOnThisDayLabel", - "filterRecentlyAddedLabel", - "editEntryDescriptionDialogTitle", + "viewDialogReverseSortOrder", + "sortOrderNewestFirst", + "sortOrderOldestFirst", + "sortOrderAtoZ", + "sortOrderZtoA", + "sortOrderHighestFirst", + "sortOrderLowestFirst", + "sortOrderLargestFirst", + "sortOrderSmallestFirst", + "searchMetadataSectionTitle", "settingsConfirmationAfterMoveToBinItems", "settingsViewerGestureSideTapNext", - "settingsSlideshowFillScreen", - "settingsScreenSaverPageTitle", - "settingsWidgetShowOutline" + "viewerInfoLabelDescription" + ], + + "pt": [ + "entryInfoActionEditTitleDescription", + "filterNoDateLabel", + "filterNoTitleLabel", + "viewDialogReverseSortOrder", + "sortOrderNewestFirst", + "sortOrderOldestFirst", + "sortOrderAtoZ", + "sortOrderZtoA", + "sortOrderHighestFirst", + "sortOrderLowestFirst", + "sortOrderLargestFirst", + "sortOrderSmallestFirst", + "searchMetadataSectionTitle" ], "tr": [ "slideshowActionResume", "slideshowActionShowInCollection", - "entryInfoActionEditDescription", + "entryInfoActionEditTitleDescription", + "filterNoDateLabel", + "filterNoTitleLabel", "filterOnThisDayLabel", "filterRecentlyAddedLabel", "slideshowVideoPlaybackSkip", @@ -50,23 +88,33 @@ "wallpaperTargetHome", "wallpaperTargetLock", "wallpaperTargetHomeLock", - "editEntryDescriptionDialogTitle", "menuActionSlideshow", + "viewDialogReverseSortOrder", + "sortOrderNewestFirst", + "sortOrderOldestFirst", + "sortOrderAtoZ", + "sortOrderZtoA", + "sortOrderHighestFirst", + "sortOrderLowestFirst", + "sortOrderLargestFirst", + "sortOrderSmallestFirst", + "searchMetadataSectionTitle", "settingsConfirmationAfterMoveToBinItems", "settingsViewerGestureSideTapNext", "settingsViewerSlideshowTile", - "settingsViewerSlideshowTitle", + "settingsViewerSlideshowPageTitle", "settingsSlideshowRepeat", "settingsSlideshowShuffle", "settingsSlideshowFillScreen", "settingsSlideshowTransitionTile", - "settingsSlideshowTransitionTitle", + "settingsSlideshowTransitionDialogTitle", "settingsSlideshowIntervalTile", - "settingsSlideshowIntervalTitle", + "settingsSlideshowIntervalDialogTitle", "settingsSlideshowVideoPlaybackTile", - "settingsSlideshowVideoPlaybackTitle", + "settingsSlideshowVideoPlaybackDialogTitle", "settingsScreenSaverPageTitle", "settingsWidgetShowOutline", - "viewerSetWallpaperButtonLabel" + "viewerSetWallpaperButtonLabel", + "viewerInfoLabelDescription" ] } diff --git a/whatsnew/whatsnew-en-US b/whatsnew/whatsnew-en-US index 30000cd00..af4200674 100644 --- a/whatsnew/whatsnew-en-US +++ b/whatsnew/whatsnew-en-US @@ -1,5 +1,5 @@ -In v1.6.13: -- play your HEIC motion photos -- find recently downloaded images with the `recently added` filter -- enjoy the app in Dutch +In v1.7.0: +- change the sort order +- edit image titles +- enjoy the app in Greek Full changelog available on GitHub \ No newline at end of file