feat: merge patch RVX/anddea (#55)

* feat(YouTube): Add `Custom Shorts action buttons` patch (Replaces `Shorts outline icon` patch)

* feat(YouTube): Add `Visual preferences icons` patch

* feat(Custom branding icon): Add more icons

* feat(Custom branding icon): Add patch options `ChangeHeader`, `ChangeSplashIcon`, `RestoreOldSplashAnimation`

* feat(Translations): Add patch options to remove languages and provide own translation / strings.xml

* feat(YouTube/Settings): Add search bar in settings

* feat(YouTube/Shorts components): Add `Hide disabled comments button` and `Hide live chat header` settings

* refactor(YouTube/Settings): Fix typos in strings

* feat(YouTube/Settings): clarify patch option descriptions

Co-authored-by: KobeW50 <84587632+KobeW50@users.noreply.github.com>

* feat(YouTube): clarify patch option

Co-authored-by: KobeW50 <84587632+KobeW50@users.noreply.github.com>

---------

Co-authored-by: inotia00 <108592928+inotia00@users.noreply.github.com>
Co-authored-by: KobeW50 <84587632+KobeW50@users.noreply.github.com>
This commit is contained in:
Aaron Veil
2024-06-08 18:04:23 +03:00
committed by GitHub
parent ecd6fbdf5b
commit 09ed1b965d
816 changed files with 36893 additions and 499 deletions

View File

@ -2,6 +2,7 @@ package app.revanced.patches.music.layout.branding.icon
import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.util.ResourceGroup import app.revanced.util.ResourceGroup
@ -19,26 +20,79 @@ object CustomBrandingIconPatch : BaseResourcePatch(
private const val DEFAULT_ICON_KEY = "Revancify Blue" private const val DEFAULT_ICON_KEY = "Revancify Blue"
private val availableIcon = mapOf( private val availableIcon = mapOf(
"AFN Blue" to "afn_blue",
"AFN Red" to "afn_red",
"MMT" to "mmt", "MMT" to "mmt",
DEFAULT_ICON_KEY to "revancify_blue", DEFAULT_ICON_KEY to "revancify_blue",
"Revancify Red" to "revancify_red" "Revancify Red" to "revancify_red",
"YouTube Music" to "youtube_music"
) )
private val mipmapIconResourceFileNames = arrayOf( private val sizeArray = arrayOf(
"adaptiveproduct_youtube_music_background_color_108",
"adaptiveproduct_youtube_music_foreground_color_108",
"ic_launcher_release"
).map { "$it.png" }.toTypedArray()
private val mipmapDirectories = arrayOf(
"xxxhdpi", "xxxhdpi",
"xxhdpi", "xxhdpi",
"xhdpi", "xhdpi",
"hdpi", "hdpi",
"mdpi" "mdpi"
).map { "mipmap-$it" } )
private var AppIcon by stringPatchOption( private val largeSizeArray = arrayOf(
"xlarge-hdpi",
"xlarge-mdpi",
"large-xhdpi",
"large-hdpi",
"large-mdpi",
"xxhdpi",
"xhdpi",
"hdpi",
"mdpi",
)
private val drawableDirectories = sizeArray.map { "drawable-$it" }
private val largeDrawableDirectories = largeSizeArray.map { "drawable-$it" }
private val mipmapDirectories = sizeArray.map { "mipmap-$it" }
private val headerIconResourceFileNames = arrayOf(
"action_bar_logo",
"logo_music",
"ytm_logo"
).map { "$it.png" }.toTypedArray()
private val launcherIconResourceFileNames = arrayOf(
"adaptiveproduct_youtube_music_background_color_108",
"adaptiveproduct_youtube_music_foreground_color_108",
"ic_launcher_release"
).map { "$it.png" }.toTypedArray()
private val splashIconResourceFileNames = arrayOf(
// This file only exists in [drawable-hdpi]
// Since {@code ResourceUtils#copyResources} checks for null values before copying,
// Just adds it to the array.
"action_bar_logo_release",
"record"
).map { "$it.png" }.toTypedArray()
private val headerIconResourceGroups = drawableDirectories.map { directory ->
ResourceGroup(
directory, *headerIconResourceFileNames
)
}
private val launcherIconResourceGroups = mipmapDirectories.map { directory ->
ResourceGroup(
directory, *launcherIconResourceFileNames
)
}
private val splashIconResourceGroups = largeDrawableDirectories.map { directory ->
ResourceGroup(
directory, *splashIconResourceFileNames
)
}
private val AppIcon by stringPatchOption(
key = "AppIcon", key = "AppIcon",
default = DEFAULT_ICON_KEY, default = DEFAULT_ICON_KEY,
values = availableIcon, values = availableIcon,
@ -50,22 +104,35 @@ object CustomBrandingIconPatch : BaseResourcePatch(
Each of these folders has to have the following files: Each of these folders has to have the following files:
${mipmapIconResourceFileNames.joinToString("\n") { "- $it" }} ${launcherIconResourceFileNames.joinToString("\n") { "- $it" }}
""" """
.split("\n") .split("\n")
.joinToString("\n") { it.trimIndent() } // Remove the leading whitespace from each line. .joinToString("\n") { it.trimIndent() } // Remove the leading whitespace from each line.
.trimIndent(), // Remove the leading newline. .trimIndent(), // Remove the leading newline.
) )
private val ChangeHeader by booleanPatchOption(
key = "ChangeHeader",
default = false,
title = "Change header",
description = "Apply the custom branding icon to the header."
)
private val ChangeSplashIcon by booleanPatchOption(
key = "ChangeSplashIcon",
default = true,
title = "Change splash icons",
description = "Apply the custom branding icon to the splash screen."
)
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
AppIcon?.let { appIcon -> AppIcon?.let { appIcon ->
val appIconValue = appIcon.lowercase().replace(" ", "_") val appIconValue = appIcon.lowercase().replace(" ", "_")
val appIconResourcePath = "music/branding/$appIconValue"
// Check if a custom path is used in the patch options.
if (!availableIcon.containsValue(appIconValue)) { if (!availableIcon.containsValue(appIconValue)) {
mipmapDirectories.map { directory -> launcherIconResourceGroups.let { resourceGroups ->
ResourceGroup(
directory, *mipmapIconResourceFileNames
)
}.let { resourceGroups ->
try { try {
val path = File(appIcon) val path = File(appIcon)
val resourceDirectory = context["res"] val resourceDirectory = context["res"]
@ -82,33 +149,47 @@ object CustomBrandingIconPatch : BaseResourcePatch(
} }
} }
} catch (_: Exception) { } catch (_: Exception) {
// Exception is thrown if an invalid path is used in the patch option.
throw PatchException("Invalid app icon path: $appIcon") throw PatchException("Invalid app icon path: $appIcon")
} }
} }
} else { } else {
val resourcePath = "music/branding/$appIconValue"
// change launcher icon. // Change launcher icon.
mipmapDirectories.map { directory -> launcherIconResourceGroups.let { resourceGroups ->
ResourceGroup(
directory, *mipmapIconResourceFileNames
)
}.let { resourceGroups ->
resourceGroups.forEach { resourceGroups.forEach {
context.copyResources("$resourcePath/launcher", it) context.copyResources("$appIconResourcePath/launcher", it)
} }
} }
// change monochrome icon. // Change monochrome icon.
arrayOf( arrayOf(
ResourceGroup( ResourceGroup(
"drawable", "drawable",
"ic_app_icons_themed_youtube_music.xml" "ic_app_icons_themed_youtube_music.xml"
) )
).forEach { resourceGroup -> ).forEach { resourceGroup ->
context.copyResources("$resourcePath/monochrome", resourceGroup) context.copyResources("$appIconResourcePath/monochrome", resourceGroup)
}
// Change header.
if (ChangeHeader == true) {
headerIconResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
context.copyResources("$appIconResourcePath/header", it)
}
}
}
// Change splash icon.
if (ChangeSplashIcon == true) {
splashIconResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
context.copyResources("$appIconResourcePath/splash", it)
}
}
} }
} }
} ?: throw PatchException("Invalid app icon path.") } ?: throw PatchException("Invalid app icon path.")
} }
} }

View File

@ -20,24 +20,26 @@ object CustomBrandingNamePatch : BaseResourcePatch(
key = "AppNameNotification", key = "AppNameNotification",
default = APP_NAME_LAUNCHER, default = APP_NAME_LAUNCHER,
values = mapOf( values = mapOf(
"Full name" to APP_NAME_NOTIFICATION, "ReVanced Extended Music" to APP_NAME_NOTIFICATION,
"Short name" to APP_NAME_LAUNCHER "RVX Music" to APP_NAME_LAUNCHER,
"YouTube Music" to "YouTube Music",
"YT Music" to "YT Music",
), ),
title = "App name in notification panel", title = "App name in notification panel",
description = "The name of the app as it appears in the notification panel.", description = "The name of the app as it appears in the notification panel.",
required = true
) )
private val AppNameLauncher by stringPatchOption( private val AppNameLauncher by stringPatchOption(
key = "AppNameLauncher", key = "AppNameLauncher",
default = APP_NAME_LAUNCHER, default = APP_NAME_LAUNCHER,
values = mapOf( values = mapOf(
"Full name" to APP_NAME_NOTIFICATION, "ReVanced Extended Music" to APP_NAME_NOTIFICATION,
"Short name" to APP_NAME_LAUNCHER "RVX Music" to APP_NAME_LAUNCHER,
"YouTube Music" to "YouTube Music",
"YT Music" to "YT Music",
), ),
title = "App name in launcher", title = "App name in launcher",
description = "The name of the app as it appears in the launcher.", description = "The name of the app as it appears in the launcher.",
required = true
) )
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
@ -64,7 +66,7 @@ object CustomBrandingNamePatch : BaseResourcePatch(
.appendChild(stringElement) .appendChild(stringElement)
} }
} }
} ?: throw PatchException("Invalid app name.") } ?: throw PatchException("Invalid launcher name.")
} ?: throw PatchException("Invalid app name.") } ?: throw PatchException("Invalid notification name.")
} }
} }

View File

@ -0,0 +1,60 @@
package app.revanced.patches.music.layout.translations
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.settings.SettingsPatch
import app.revanced.patches.shared.translations.APP_LANGUAGES
import app.revanced.patches.shared.translations.TranslationsUtils.invoke
import app.revanced.util.patch.BaseResourcePatch
@Suppress("unused")
object TranslationsPatch : BaseResourcePatch(
name = "Translations YouTube Music",
description = "Add translations or remove string resources.",
dependencies = setOf(SettingsPatch::class),
compatiblePackages = COMPATIBLE_PACKAGE
) {
// Array of supported translations, each represented by its language code.
private val TRANSLATIONS = arrayOf(
"bg-rBG", "bn", "cs-rCZ", "el-rGR", "es-rES", "fr-rFR", "hu-rHU", "id-rID", "in", "it-rIT",
"ja-rJP", "ko-rKR", "nl-rNL", "pl-rPL", "pt-rBR", "ro-rRO", "ru-rRU", "tr-rTR", "uk-rUA",
"vi-rVN", "zh-rCN", "zh-rTW"
)
private var CustomTranslations by stringPatchOption(
key = "CustomTranslations",
default = "",
title = "Custom translations",
description = """
The path to the 'strings.xml' file.
Please note that applying the 'strings.xml' file will overwrite all existing language translations.
""".trimIndent()
)
private var SelectedTranslations by stringPatchOption(
key = "SelectedTranslations",
default = TRANSLATIONS.joinToString(", "),
title = "Translations to add",
description = "A list of translations to be added for the RVX settings, separated by commas."
)
private var SelectedStringResources by stringPatchOption(
key = "SelectedStringResources",
default = APP_LANGUAGES.joinToString(", "),
title = "String resources to keep",
description = """
A list of string resources to be kept, separated by commas.
String resources not in the list will be removed from the app.
Default string resource, English, is not removed.
""".trimIndent()
)
override fun execute(context: ResourceContext) {
context.invoke(
CustomTranslations, SelectedTranslations, SelectedStringResources,
TRANSLATIONS, "music"
)
}
}

View File

@ -1,46 +0,0 @@
package app.revanced.patches.music.misc.translations
import app.revanced.patcher.data.ResourceContext
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.settings.SettingsPatch
import app.revanced.patches.shared.translations.TranslationsUtils.copyXml
import app.revanced.util.patch.BaseResourcePatch
@Suppress("unused")
object TranslationsPatch : BaseResourcePatch(
name = "Translations",
description = "Adds Crowdin translations for YouTube Music.",
dependencies = setOf(SettingsPatch::class),
compatiblePackages = COMPATIBLE_PACKAGE
) {
override fun execute(context: ResourceContext) {
context.copyXml(
"music",
arrayOf(
"bg-rBG",
"bn",
"cs-rCZ",
"el-rGR",
"es-rES",
"fr-rFR",
"hu-rHU",
"id-rID",
"in",
"it-rIT",
"ja-rJP",
"ko-rKR",
"nl-rNL",
"pl-rPL",
"pt-rBR",
"ro-rRO",
"ru-rRU",
"tr-rTR",
"uk-rUA",
"vi-rVN",
"zh-rCN",
"zh-rTW"
)
)
}
}

View File

@ -26,24 +26,80 @@ object SettingsPatch : BaseResourcePatch(
compatiblePackages = COMPATIBLE_PACKAGE, compatiblePackages = COMPATIBLE_PACKAGE,
requiresIntegrations = true requiresIntegrations = true
), Closeable { ), Closeable {
private val THREAD_COUNT = Runtime.getRuntime().availableProcessors()
private val threadPoolExecutor = Executors.newFixedThreadPool(THREAD_COUNT)
lateinit var contexts: ResourceContext lateinit var contexts: ResourceContext
internal var upward0636 = false internal var upward0636 = false
internal var upward0642 = false internal var upward0642 = false
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
/**
* set resource context
*/
contexts = context contexts = context
val resourceXmlFile = context["res/values/integers.xml"].readBytes() /**
* set version info
*/
setVersionInfo()
for (threadIndex in 0 until THREAD_COUNT) { /**
* copy strings
*/
context.copyXmlNode("music/settings/host", "values/strings.xml", "resources")
/**
* hide divider
*/
val styleFile = context["res/values/styles.xml"]
styleFile.writeText(
styleFile.readText()
.replace(
"allowDividerAbove\">true",
"allowDividerAbove\">false"
).replace(
"allowDividerBelow\">true",
"allowDividerBelow\">false"
)
)
/**
* Copy arrays
*/
contexts.copyXmlNode("music/settings/host", "values/arrays.xml", "resources")
/**
* Copy colors
*/
context.xmlEditor["res/values/colors.xml"].use { editor ->
val resourcesNode = editor.file.getElementsByTagName("resources").item(0) as Element
for (i in 0 until resourcesNode.childNodes.length) {
val node = resourcesNode.childNodes.item(i) as? Element ?: continue
node.textContent = when (node.getAttribute("name")) {
"material_deep_teal_500" -> "@android:color/white"
else -> continue
}
}
}
context.addRVXSettingsPreference()
}
private fun setVersionInfo() {
val threadCount = Runtime.getRuntime().availableProcessors()
val threadPoolExecutor = Executors.newFixedThreadPool(threadCount)
val resourceXmlFile = contexts["res/values/integers.xml"].readBytes()
for (threadIndex in 0 until threadCount) {
threadPoolExecutor.execute thread@{ threadPoolExecutor.execute thread@{
context.xmlEditor[resourceXmlFile.inputStream()].use { editor -> contexts.xmlEditor[resourceXmlFile.inputStream()].use { editor ->
val resources = editor.file.documentElement.childNodes val resources = editor.file.documentElement.childNodes
val resourcesLength = resources.length val resourcesLength = resources.length
val jobSize = resourcesLength / THREAD_COUNT val jobSize = resourcesLength / threadCount
val batchStart = jobSize * threadIndex val batchStart = jobSize * threadIndex
val batchEnd = jobSize * (threadIndex + 1) val batchEnd = jobSize * (threadIndex + 1)
@ -71,47 +127,6 @@ object SettingsPatch : BaseResourcePatch(
threadPoolExecutor threadPoolExecutor
.also { it.shutdown() } .also { it.shutdown() }
.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS) .awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS)
/**
* copy strings
*/
context.copyXmlNode("music/settings/host", "values/strings.xml", "resources")
/**
* hide divider
*/
val styleFile = context["res/values/styles.xml"]
styleFile.writeText(
styleFile.readText()
.replace(
"allowDividerAbove\">true",
"allowDividerAbove\">false"
).replace(
"allowDividerBelow\">true",
"allowDividerBelow\">false"
)
)
/**
* Copy colors
*/
context.xmlEditor["res/values/colors.xml"].use { editor ->
val resourcesNode = editor.file.getElementsByTagName("resources").item(0) as Element
for (i in 0 until resourcesNode.childNodes.length) {
val node = resourcesNode.childNodes.item(i) as? Element ?: continue
node.textContent = when (node.getAttribute("name")) {
"material_deep_teal_500" -> "@android:color/white"
else -> continue
}
}
}
context.addRVXSettingsPreference()
} }
internal fun addSwitchPreference( internal fun addSwitchPreference(
@ -162,11 +177,6 @@ object SettingsPatch : BaseResourcePatch(
} }
override fun close() { override fun close() {
/**
* Copy arrays
*/
contexts.copyXmlNode("music/settings/host", "values/arrays.xml", "resources")
addPreferenceWithIntent( addPreferenceWithIntent(
CategoryType.MISC, CategoryType.MISC,
"revanced_extended_settings_import_export" "revanced_extended_settings_import_export"

View File

@ -32,7 +32,7 @@ object CustomBrandingNamePatch : BaseResourcePatch(
val appName = if (AppName != null) { val appName = if (AppName != null) {
AppName!! AppName!!
} else { } else {
println("WARNING: Invalid name name. Does not apply patches.") println("WARNING: Invalid app name. Does not apply patches.")
ORIGINAL_APP_NAME ORIGINAL_APP_NAME
} }

View File

@ -2,7 +2,7 @@ package app.revanced.patches.shared.elements
import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.data.ResourceContext
@Suppress("DEPRECATION") @Suppress("DEPRECATION", "unused")
object StringsElementsUtils { object StringsElementsUtils {
internal fun ResourceContext.removeStringsElements( internal fun ResourceContext.removeStringsElements(

View File

@ -1,28 +1,172 @@
package app.revanced.patches.shared.translations package app.revanced.patches.shared.translations
import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException
import app.revanced.util.inputStreamFromBundledResource import app.revanced.util.inputStreamFromBundledResource
import org.w3c.dom.Node
import java.io.File
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.StandardCopyOption import java.nio.file.StandardCopyOption
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
object TranslationsUtils { object TranslationsUtils {
internal fun ResourceContext.copyXml(
internal fun ResourceContext.invoke(
customTranslations: String?,
selectedTranslations: String?,
selectedStringResources: String?,
translationsArray: Array<String>,
sourceDirectory: String
) {
// Check if the custom translation path is valid.
customTranslations?.takeIf { it.isNotEmpty() }?.let { customLang ->
try {
val customLangFile = File(customLang)
if (!customLangFile.exists() || !customLangFile.isFile || customLangFile.name != "strings.xml") {
throw PatchException("Invalid custom language file: $customLang")
}
val resourceDirectory = this["res"].resolve("values")
val destinationFile = resourceDirectory.resolve("strings.xml")
updateStringsXml(customLangFile, destinationFile)
} catch (e: Exception) {
// Exception is thrown if an invalid path is used in the patch option.
throw PatchException("Invalid custom translations path: $customLang")
}
}?: run {
// Process selected translations if no custom translation is set.
val selectedTranslationsArray =
selectedTranslations?.split(",")?.map { it.trim() }?.toTypedArray()
?: throw PatchException("Invalid selected languages.")
val filteredLanguages = translationsArray.filter { it in selectedTranslationsArray }.toTypedArray()
copyXml(sourceDirectory, filteredLanguages)
}
// Process selected string resources.
val selectedStringResourcesArray =
selectedStringResources?.split(",")?.map { it.trim() }?.toTypedArray()
?: throw PatchException("Invalid selected string resources.")
val filteredStringResources =
APP_LANGUAGES.filter { it in selectedStringResourcesArray }.toTypedArray()
val resourceDirectory = this["res"]
// Remove unselected app languages.
APP_LANGUAGES.filter { it !in filteredStringResources }.forEach { language ->
resourceDirectory.resolve("values-$language").takeIf { it.exists() && it.isDirectory }
?.deleteRecursively()
}
}
/**
* Extension function to ResourceContext to copy XML translation files.
*
* @param sourceDirectory The source directory containing the translation files.
* @param languageArray The array of language codes to process.
*/
private fun ResourceContext.copyXml(
sourceDirectory: String, sourceDirectory: String,
languageArray: Array<String> languageArray: Array<String>
) { ) {
languageArray.forEach { language -> languageArray.forEach { language ->
val directory = "values-$language-v21" inputStreamFromBundledResource(
this["res/$directory"].mkdir() "$sourceDirectory/translations",
"$language/strings.xml"
)?.let { inputStream ->
val directory = "values-$language-v21"
val valuesV21Directory = this["res"].resolve(directory)
if (!valuesV21Directory.isDirectory) Files.createDirectories(valuesV21Directory.toPath())
Files.copy( Files.copy(
inputStreamFromBundledResource( inputStream,
"$sourceDirectory/translations", this["res"].resolve("$directory/strings.xml").toPath(),
"$language/strings.xml" StandardCopyOption.REPLACE_EXISTING
)!!, )
this["res"].resolve("$directory/strings.xml").toPath(), }
StandardCopyOption.REPLACE_EXISTING
)
} }
} }
/**
* Updates the contents of the destination strings.xml file by merging it with the source strings.xml file.
*
* This function reads both source and destination XML files, compares each <string> element by their
* unique "name" attribute, and if a match is found, it replaces the content in the destination file with
* the content from the source file.
*
* @param sourceFile The source strings.xml file containing new string values.
* @param destinationFile The destination strings.xml file to be updated with values from the source file.
*/
private fun updateStringsXml(sourceFile: File, destinationFile: File) {
val documentBuilderFactory = DocumentBuilderFactory.newInstance()
val documentBuilder = documentBuilderFactory.newDocumentBuilder()
// Parse the source and destination XML files into Document objects
val sourceDoc = documentBuilder.parse(sourceFile)
val destinationDoc = documentBuilder.parse(destinationFile)
val sourceStrings = sourceDoc.getElementsByTagName("string")
val destinationStrings = destinationDoc.getElementsByTagName("string")
// Create a map to store the <string> elements from the source document by their "name" attribute
val sourceMap = mutableMapOf<String, Node>()
// Populate the map with nodes from the source document
for (i in 0 until sourceStrings.length) {
val node = sourceStrings.item(i)
val name = node.attributes.getNamedItem("name").nodeValue
sourceMap[name] = node
}
// Update the destination document with values from the source document
for (i in 0 until destinationStrings.length) {
val node = destinationStrings.item(i)
val name = node.attributes.getNamedItem("name").nodeValue
if (sourceMap.containsKey(name)) {
node.textContent = sourceMap[name]?.textContent
}
}
/**
* Prepare the transformer for writing the updated document back to the file.
* The transformer is configured to indent the output XML for better readability.
*/
val transformerFactory = TransformerFactory.newInstance()
val transformer = transformerFactory.newTransformer()
transformer.setOutputProperty(OutputKeys.INDENT, "yes")
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")
val domSource = DOMSource(destinationDoc)
val streamResult = StreamResult(destinationFile)
transformer.transform(domSource, streamResult)
}
} }
// Array of all possible app languages.
val APP_LANGUAGES = arrayOf(
"af", "am", "ar", "ar-rXB", "as", "az",
"b+es+419", "b+sr+Latn", "be", "bg", "bn", "bs",
"ca", "cs",
"da", "de",
"el", "en-rAU", "en-rCA", "en-rGB", "en-rIN", "en-rXA", "en-rXC", "es", "es-rUS", "et", "eu",
"fa", "fi", "fr", "fr-rCA",
"gl", "gu",
"hi", "hr", "hu", "hy",
"id", "in", "is", "it", "iw",
"ja",
"ka", "kk", "km", "kn", "ko", "ky",
"lo", "lt", "lv",
"mk", "ml", "mn", "mr", "ms", "my",
"nb", "ne", "nl", "no",
"or",
"pa", "pl", "pt", "pt-rBR", "pt-rPT",
"ro", "ru",
"si", "sk", "sl", "sq", "sr", "sv", "sw",
"ta", "te", "th", "tl", "tr",
"uk", "ur", "uz",
"vi",
"zh", "zh-rCN", "zh-rHK", "zh-rTW", "zu",
)

View File

@ -0,0 +1,84 @@
package app.revanced.patches.youtube.layout.actionbuttons
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
import app.revanced.util.patch.BaseResourcePatch
@Suppress("unused")
object ShortsActionButtonsPatch : BaseResourcePatch(
name = "Custom Shorts action buttons",
description = "Changes, at compile time, the icon of the action buttons of the Shorts player.",
dependencies = setOf(SettingsPatch::class),
compatiblePackages = COMPATIBLE_PACKAGE
) {
private const val DEFAULT_ICON_KEY = "Round"
private val IconType by stringPatchOption(
key = "IconType",
default = DEFAULT_ICON_KEY,
values = mapOf(
"Outline" to "outline",
"OutlineCircle" to "outlinecircle",
DEFAULT_ICON_KEY to "round"
),
title = "Shorts icon style ",
description = "The style of the icons for the action buttons in the Shorts player."
)
override fun execute(context: ResourceContext) {
IconType?.let { iconType ->
val selectedIconType = iconType.lowercase()
arrayOf(
"xxxhdpi",
"xxhdpi",
"xhdpi",
"hdpi",
"mdpi"
).forEach { dpi ->
context.copyResources(
"youtube/shorts/actionbuttons/$selectedIconType",
ResourceGroup(
"drawable-$dpi",
"ic_remix_filled_white_shadowed.webp",
"ic_right_comment_shadowed.webp",
"ic_right_dislike_off_shadowed.webp",
"ic_right_dislike_on_shadowed.webp",
"ic_right_like_off_shadowed.webp",
"ic_right_like_on_shadowed.webp",
"ic_right_share_shadowed.webp",
// for older versions only
"ic_remix_filled_white_24.webp",
"ic_right_dislike_on_32c.webp",
"ic_right_like_on_32c.webp"
),
ResourceGroup(
"drawable",
"ic_right_comment_32c.xml",
"ic_right_dislike_off_32c.xml",
"ic_right_like_off_32c.xml",
"ic_right_share_32c.xml"
)
)
context.copyResources(
"youtube/shorts/actionbuttons/shared",
ResourceGroup(
"drawable",
"reel_camera_bold_24dp.xml",
"reel_more_vertical_bold_24dp.xml",
"reel_search_bold_24dp.xml"
)
)
}
} ?: throw PatchException("Invalid icon type.")
SettingsPatch.updatePatchStatus(this)
}
}

View File

@ -21,7 +21,7 @@ object AnimatedLikePatch : BaseResourcePatch(
* Copy json * Copy json
*/ */
context.copyResources( context.copyResources(
"youtube/animated", "youtube/shorts/animated",
ResourceGroup( ResourceGroup(
"raw", "raw",
"like_tap_feedback.json" "like_tap_feedback.json"

View File

@ -2,12 +2,14 @@ package app.revanced.patches.youtube.layout.branding.icon
import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.settings.ResourceUtils.updatePatchStatusIcon import app.revanced.patches.youtube.utils.settings.ResourceUtils.updatePatchStatusIcon
import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.util.ResourceGroup import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources import app.revanced.util.copyResources
import app.revanced.util.copyXmlNode
import app.revanced.util.patch.BaseResourcePatch import app.revanced.util.patch.BaseResourcePatch
import java.io.File import java.io.File
import java.nio.file.Files import java.nio.file.Files
@ -17,47 +19,84 @@ object CustomBrandingIconPatch : BaseResourcePatch(
name = "Custom branding icon YouTube", name = "Custom branding icon YouTube",
description = "Changes the YouTube app icon to the icon specified in options.json.", description = "Changes the YouTube app icon to the icon specified in options.json.",
dependencies = setOf(SettingsPatch::class), dependencies = setOf(SettingsPatch::class),
compatiblePackages = COMPATIBLE_PACKAGE compatiblePackages = COMPATIBLE_PACKAGE,
) { ) {
private const val DEFAULT_ICON_KEY = "Revancify Blue" private const val DEFAULT_ICON_KEY = "Revancify Blue"
private val availableIcon = mapOf( private val availableIcon = mapOf(
"AFN Blue" to "afn_blue",
"AFN Red" to "afn_red",
"MMT" to "mmt", "MMT" to "mmt",
DEFAULT_ICON_KEY to "revancify_blue", DEFAULT_ICON_KEY to "revancify_blue",
"Revancify Red" to "revancify_red" "Revancify Red" to "revancify_red",
"YouTube" to "youtube"
) )
private val drawableIconResourceFileNames = arrayOf( private val sizeArray = arrayOf(
"product_logo_youtube_color_24",
"product_logo_youtube_color_36",
"product_logo_youtube_color_144",
"product_logo_youtube_color_192"
).map { "$it.png" }.toTypedArray()
private val drawableDirectories = arrayOf(
"xxxhdpi", "xxxhdpi",
"xxhdpi", "xxhdpi",
"xhdpi", "xhdpi",
"hdpi", "hdpi",
"mdpi" "mdpi"
).map { "drawable-$it" } )
private val mipmapIconResourceFileNames = arrayOf( private val drawableDirectories = sizeArray.map { "drawable-$it" }
private val mipmapDirectories = sizeArray.map { "mipmap-$it" }
private val headerIconResourceFileNames = arrayOf(
"yt_premium_wordmark_header_dark",
"yt_premium_wordmark_header_light",
"yt_wordmark_header_dark",
"yt_wordmark_header_light"
).map { "$it.png" }.toTypedArray()
private val launcherIconResourceFileNames = arrayOf(
"adaptiveproduct_youtube_background_color_108", "adaptiveproduct_youtube_background_color_108",
"adaptiveproduct_youtube_foreground_color_108", "adaptiveproduct_youtube_foreground_color_108",
"ic_launcher", "ic_launcher",
"ic_launcher_round" "ic_launcher_round"
).map { "$it.png" }.toTypedArray() ).map { "$it.png" }.toTypedArray()
private val mipmapDirectories = arrayOf( private val splashIconResourceFileNames = arrayOf(
"xxxhdpi", "product_logo_youtube_color_24",
"xxhdpi", "product_logo_youtube_color_36",
"xhdpi", "product_logo_youtube_color_144",
"hdpi", "product_logo_youtube_color_192"
"mdpi" ).map { "$it.png" }.toTypedArray()
).map { "mipmap-$it" }
private var AppIcon by stringPatchOption( private val oldSplashAnimationResourceFileNames = arrayOf(
"\$\$avd_anim__1__0",
"\$\$avd_anim__1__1",
"\$\$avd_anim__2__0",
"\$\$avd_anim__2__1",
"\$\$avd_anim__3__0",
"\$\$avd_anim__3__1",
"\$avd_anim__0",
"\$avd_anim__1",
"\$avd_anim__2",
"\$avd_anim__3",
"\$avd_anim__4",
"avd_anim"
).map { "$it.xml" }.toTypedArray()
private fun List<String>.getResourceGroup(fileNames: Array<String>) = map { directory ->
ResourceGroup(
directory, *fileNames
)
}
private val headerIconResourceGroups = drawableDirectories.getResourceGroup(headerIconResourceFileNames)
private val launcherIconResourceGroups = mipmapDirectories.getResourceGroup(launcherIconResourceFileNames)
private val splashIconResourceGroups = drawableDirectories.getResourceGroup(splashIconResourceFileNames)
private val oldSplashAnimationResourceGroups = listOf("drawable").getResourceGroup(oldSplashAnimationResourceFileNames)
// region patch option
val AppIcon by stringPatchOption(
key = "AppIcon", key = "AppIcon",
default = DEFAULT_ICON_KEY, default = DEFAULT_ICON_KEY,
values = availableIcon, values = availableIcon,
@ -69,22 +108,45 @@ object CustomBrandingIconPatch : BaseResourcePatch(
Each of these folders has to have the following files: Each of these folders has to have the following files:
${mipmapIconResourceFileNames.joinToString("\n") { "- $it" }} ${launcherIconResourceFileNames.joinToString("\n") { "- $it" }}
""" """
.split("\n") .split("\n")
.joinToString("\n") { it.trimIndent() } // Remove the leading whitespace from each line. .joinToString("\n") { it.trimIndent() } // Remove the leading whitespace from each line.
.trimIndent(), // Remove the leading newline. .trimIndent(), // Remove the leading newline.
) )
private val ChangeHeader by booleanPatchOption(
key = "ChangeHeader",
default = false,
title = "Change header",
description = "Apply the custom branding icon to the header."
)
private val ChangeSplashIcon by booleanPatchOption(
key = "ChangeSplashIcon",
default = true,
title = "Change splash icons",
description = "Apply the custom branding icon to the splash screen."
)
private val RestoreOldSplashAnimation by booleanPatchOption(
key = "RestoreOldSplashAnimation",
default = false,
title = "Restore old splash animation",
description = "Restores old style splash animation."
)
// endregion
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
AppIcon?.let { appIcon -> AppIcon?.let { appIcon ->
val appIconValue = appIcon.lowercase().replace(" ", "_") val appIconValue = appIcon.lowercase().replace(" ", "_")
val appIconResourcePath = "youtube/branding/$appIconValue"
val stockResourcePath = "youtube/branding/stock"
// Check if a custom path is used in the patch options.
if (!availableIcon.containsValue(appIconValue)) { if (!availableIcon.containsValue(appIconValue)) {
mipmapDirectories.map { directory -> launcherIconResourceGroups.let { resourceGroups ->
ResourceGroup(
directory, *mipmapIconResourceFileNames
)
}.let { resourceGroups ->
try { try {
val path = File(appIcon) val path = File(appIcon)
val resourceDirectory = context["res"] val resourceDirectory = context["res"]
@ -100,48 +162,63 @@ object CustomBrandingIconPatch : BaseResourcePatch(
) )
} }
} }
context.updatePatchStatusIcon("custom") context.updatePatchStatusIcon("custom")
} catch (_: Exception) { } catch (_: Exception) {
// Exception is thrown if an invalid path is used in the patch option.
throw PatchException("Invalid app icon path: $appIcon") throw PatchException("Invalid app icon path: $appIcon")
} }
} }
} else { } else {
val resourcePath = "youtube/branding/$appIconValue" // Change launcher icon.
launcherIconResourceGroups.let { resourceGroups ->
// change launcher icon.
mipmapDirectories.map { directory ->
ResourceGroup(
directory, *mipmapIconResourceFileNames
)
}.let { resourceGroups ->
resourceGroups.forEach { resourceGroups.forEach {
context.copyResources("$resourcePath/launcher", it) context.copyResources("$appIconResourcePath/launcher", it)
} }
} }
// change splash icon. // Change monochrome icon.
drawableDirectories.map { directory ->
ResourceGroup(
directory, *drawableIconResourceFileNames
)
}.let { resourceGroups ->
resourceGroups.forEach {
context.copyResources("$resourcePath/splash", it)
}
}
// change monochrome icon.
arrayOf( arrayOf(
ResourceGroup( ResourceGroup(
"drawable", "drawable",
"adaptive_monochrome_ic_youtube_launcher.xml" "adaptive_monochrome_ic_youtube_launcher.xml"
) )
).forEach { resourceGroup -> ).forEach { resourceGroup ->
context.copyResources("$resourcePath/monochrome", resourceGroup) context.copyResources("$appIconResourcePath/monochrome", resourceGroup)
}
// Change header.
if (ChangeHeader == true) {
headerIconResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
context.copyResources("$appIconResourcePath/header", it)
}
}
}
// Change splash icon.
if (ChangeSplashIcon == true) {
splashIconResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
context.copyResources("$appIconResourcePath/splash", it)
}
}
}
// Change splash screen.
if (RestoreOldSplashAnimation == true) {
oldSplashAnimationResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
context.copyResources("$stockResourcePath/splash", it)
context.copyResources("$appIconResourcePath/splash", it)
}
}
context.copyXmlNode("$stockResourcePath/splash", "values-v31/styles.xml", "resources")
} }
context.updatePatchStatusIcon(appIconValue) context.updatePatchStatusIcon(appIconValue)
} }
} ?: throw PatchException("Invalid app icon path.") } ?: throw PatchException("Invalid app icon path.")
} }
} }

View File

@ -22,12 +22,13 @@ object CustomBrandingNamePatch : BaseResourcePatch(
key = "AppName", key = "AppName",
default = APP_NAME, default = APP_NAME,
values = mapOf( values = mapOf(
"Full name" to "ReVanced Extended", "ReVanced Extended" to "ReVanced Extended",
"Short name" to APP_NAME "RVX" to APP_NAME,
"YouTube RVX" to "YouTube RVX",
"YouTube" to "YouTube",
), ),
title = "App name", title = "App name",
description = "The name of the app.", description = "The name of the app."
required = true
) )
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {

View File

@ -1,42 +1,61 @@
package app.revanced.patches.youtube.layout.translations package app.revanced.patches.youtube.layout.translations
import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.data.ResourceContext
import app.revanced.patches.shared.translations.TranslationsUtils.copyXml import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import app.revanced.patches.shared.translations.APP_LANGUAGES
import app.revanced.patches.shared.translations.TranslationsUtils.invoke
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.util.patch.BaseResourcePatch import app.revanced.util.patch.BaseResourcePatch
@Suppress("unused") @Suppress("unused")
object TranslationsPatch : BaseResourcePatch( object TranslationsPatch : BaseResourcePatch(
name = "Translations", name = "Translations YouTube",
description = "Adds Crowdin translations for YouTube.", description = "Add translations or remove string resources.",
dependencies = setOf(SettingsPatch::class), dependencies = setOf(SettingsPatch::class),
compatiblePackages = COMPATIBLE_PACKAGE compatiblePackages = COMPATIBLE_PACKAGE
) { ) {
override fun execute(context: ResourceContext) { // Array of supported translations, each represented by its language code.
private val TRANSLATIONS = arrayOf(
"ar", "el-rGR", "es-rES", "fr-rFR", "hu-rHU", "it-rIT", "ja-rJP", "ko-rKR", "pl-rPL",
"pt-rBR", "ru-rRU", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
)
context.copyXml( private var CustomTranslations by stringPatchOption(
"youtube", key = "CustomTranslations",
arrayOf( default = "",
"ar", title = "Custom translations",
"el-rGR", description = """
"es-rES", The path to the 'strings.xml' file.
"fr-rFR", Please note that applying the 'strings.xml' file will overwrite all existing translations.
"hu-rHU", """.trimIndent()
"it-rIT", )
"ja-rJP",
"ko-rKR", private var SelectedTranslations by stringPatchOption(
"pl-rPL", key = "SelectedTranslations",
"pt-rBR", default = TRANSLATIONS.joinToString(", "),
"ru-rRU", title = "Translations to add",
"tr-rTR", description = "A list of translations to be added for the RVX settings, separated by commas."
"uk-rUA", )
"vi-rVN",
"zh-rCN", private var SelectedStringResources by stringPatchOption(
"zh-rTW" key = "SelectedStringResources",
) default = APP_LANGUAGES.joinToString(", "),
title = "String resources to keep",
description = """
A list of string resources to be kept, separated by commas.
String resources not in the list will be removed from the app.
Default string resource, English, is not removed.
""".trimIndent()
)
override fun execute(context: ResourceContext) {
context.invoke(
CustomTranslations, SelectedTranslations, SelectedStringResources,
TRANSLATIONS, "youtube"
) )
SettingsPatch.updatePatchStatus(this) SettingsPatch.updatePatchStatus("Translations")
} }
} }

View File

@ -0,0 +1,377 @@
package app.revanced.patches.youtube.layout.visual
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import app.revanced.patches.youtube.layout.branding.icon.CustomBrandingIconPatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
import app.revanced.util.doRecursively
import app.revanced.util.patch.BaseResourcePatch
import org.w3c.dom.Element
@Suppress("DEPRECATION", "unused")
object VisualPreferencesIconsPatch : BaseResourcePatch(
name = "Visual preferences icons",
description = "Adds icons to specific preferences in the settings.",
dependencies = setOf(SettingsPatch::class),
compatiblePackages = COMPATIBLE_PACKAGE,
use = false
) {
private const val DEFAULT_ICON_KEY = "Extension"
private val RVXSettingsMenuIcon by stringPatchOption(
key = "RVXSettingsMenuIcon",
default = DEFAULT_ICON_KEY,
values = mapOf(
"Custom branding icon" to "custom_branding_icon",
DEFAULT_ICON_KEY to "extension",
"Gear" to "gear",
"ReVanced" to "revanced",
"ReVanced Colored" to "revanced_colored",
),
title = "RVX settings menu icon",
description = "Apply different icons for RVX settings menu."
)
override fun execute(context: ResourceContext) {
// region copy shared resources.
arrayOf(
ResourceGroup(
"drawable",
*preferenceIcon.values.map { "$it.xml" }.toTypedArray()
),
ResourceGroup(
"drawable-xxhdpi",
"$emptyIcon.png"
),
).forEach { resourceGroup ->
context.copyResources("youtube/visual/shared", resourceGroup)
}
// endregion.
// region copy RVX settings menu icon.
RVXSettingsMenuIcon?.lowercase()?.replace(" ", "_")?.let { selectedIconType ->
CustomBrandingIconPatch.AppIcon?.lowercase()?.replace(" ", "_")?.let { appIconValue ->
val fallbackIconPath = "youtube/visual/icons/extension"
val iconPath = when (selectedIconType) {
"custom_branding_icon" -> "youtube/branding/$appIconValue/settings"
else -> "youtube/visual/icons/$selectedIconType"
}
val resourceGroup = ResourceGroup(
"drawable",
"revanced_extended_settings_key_icon.xml"
)
try {
context.copyResources(iconPath, resourceGroup)
} catch (_: Exception) {
// Ignore if resource copy fails
// Add a fallback extended icon
// It's needed if someone provides custom path to icon(s) folder
// but custom branding icons for Extended setting are predefined,
// so it won't copy custom branding icon
// and will raise an error without fallback icon
context.copyResources(fallbackIconPath, resourceGroup)
}
}
}
// endregion.
// region set visual preferences icon.
arrayOf(
"res/xml/revanced_prefs.xml",
"res/xml/settings_fragment.xml"
).forEach { xmlFile ->
context.xmlEditor[xmlFile].use { editor ->
editor.file.doRecursively loop@{ node ->
if (node !is Element) return@loop
node.getAttributeNode("android:key")
?.textContent
?.removePrefix("@string/")
?.let { title ->
val drawableName = when (title) {
in preferenceKey -> preferenceIcon[title]
// Add custom RVX settings menu icon
in intentKey -> intentIcon[title]
in emptyTitles -> emptyIcon
else -> null
}
drawableName?.let {
node.setAttribute("android:icon", "@drawable/$it")
}
}
}
}
}
// endregion.
SettingsPatch.updatePatchStatus(this)
}
// region preference key and icon.
private val preferenceKey = setOf(
// Main settings (sorted as displayed in the settings)
"parent_tools_key",
"general_key",
"account_switcher_key",
"data_saving_settings_key",
"auto_play_key",
"video_quality_settings_key",
"offline_key",
"pair_with_tv_key",
"history_key",
"your_data_key",
"privacy_key",
"premium_early_access_browse_page_key",
"subscription_product_setting_key",
"billing_and_payment_key",
"notification_key",
"connected_accounts_browse_page_key",
"live_chat_key",
"captions_key",
"accessibility_settings_key",
"about_key",
// Main RVX settings (sorted as displayed in the settings)
"revanced_preference_screen_ads",
"revanced_preference_screen_alt_thumbnails",
"revanced_preference_screen_feed",
"revanced_preference_screen_general",
"revanced_preference_screen_player",
"revanced_preference_screen_shorts",
"revanced_preference_screen_swipe_controls",
"revanced_preference_screen_video",
"revanced_preference_screen_ryd",
"revanced_preference_screen_sb",
"revanced_preference_screen_misc",
// Internal RVX settings (items without prefix are listed first, others are sorted alphabetically)
"gms_core_settings",
"sb_enable_create_segment",
"sb_enable_voting",
"revanced_alt_thumbnail_home",
"revanced_alt_thumbnail_library",
"revanced_alt_thumbnail_player",
"revanced_alt_thumbnail_search",
"revanced_alt_thumbnail_subscriptions",
"revanced_change_shorts_repeat_state",
"revanced_custom_player_overlay_opacity",
"revanced_default_app_settings",
"revanced_default_playback_speed",
"revanced_default_video_quality_wifi",
"revanced_disable_hdr_auto_brightness",
"revanced_disable_hdr_video",
"revanced_disable_quic_protocol",
"revanced_enable_debug_logging",
"revanced_enable_default_playback_speed_shorts",
"revanced_enable_external_browser",
"revanced_enable_old_quality_layout",
"revanced_enable_open_links_directly",
"revanced_enable_swipe_brightness",
"revanced_enable_swipe_haptic_feedback",
"revanced_enable_swipe_lowest_value_auto_brightness",
"revanced_enable_swipe_press_to_engage",
"revanced_enable_swipe_volume",
"revanced_enable_watch_panel_gestures",
"revanced_hide_clip_button",
"revanced_hide_download_button",
"revanced_hide_keyword_content_comments",
"revanced_hide_keyword_content_home",
"revanced_hide_keyword_content_search",
"revanced_hide_keyword_content_subscriptions",
"revanced_hide_like_dislike_button",
"revanced_hide_navigation_create_button",
"revanced_hide_navigation_home_button",
"revanced_hide_navigation_library_button",
"revanced_hide_navigation_notifications_button",
"revanced_hide_navigation_shorts_button",
"revanced_hide_navigation_subscriptions_button",
"revanced_hide_player_autoplay_button",
"revanced_hide_player_captions_button",
"revanced_hide_player_cast_button",
"revanced_hide_player_collapse_button",
"revanced_hide_player_flyout_menu_ambient_mode",
"revanced_hide_player_flyout_menu_audio_track",
"revanced_hide_player_flyout_menu_captions",
"revanced_hide_player_flyout_menu_help",
"revanced_hide_player_flyout_menu_lock_screen",
"revanced_hide_player_flyout_menu_loop_video",
"revanced_hide_player_flyout_menu_more_info",
"revanced_hide_player_flyout_menu_playback_speed",
"revanced_hide_player_flyout_menu_quality_footer",
"revanced_hide_player_flyout_menu_report",
"revanced_hide_player_flyout_menu_stable_volume",
"revanced_hide_player_flyout_menu_stats_for_nerds",
"revanced_hide_player_flyout_menu_watch_in_vr",
"revanced_hide_player_fullscreen_button",
"revanced_hide_player_previous_next_button",
"revanced_hide_player_youtube_music_button",
"revanced_hide_playlist_button",
"revanced_hide_quick_actions_comment_button",
"revanced_hide_quick_actions_dislike_button",
"revanced_hide_quick_actions_like_button",
"revanced_hide_quick_actions_live_chat_button",
"revanced_hide_quick_actions_more_button",
"revanced_hide_quick_actions_save_to_playlist_button",
"revanced_hide_quick_actions_share_button",
"revanced_hide_remix_button",
"revanced_hide_report_button",
"revanced_hide_share_button",
"revanced_hide_shorts_comments_button",
"revanced_hide_shorts_dislike_button",
"revanced_hide_shorts_like_button",
"revanced_hide_shorts_navigation_bar",
"revanced_hide_shorts_remix_button",
"revanced_hide_shorts_share_button",
"revanced_hide_shorts_shelf_history",
"revanced_hide_shorts_shelf_home_related_videos",
"revanced_hide_shorts_shelf_search",
"revanced_hide_shorts_shelf_subscriptions",
"revanced_hide_shorts_toolbar",
"revanced_overlay_button_always_repeat",
"revanced_overlay_button_copy_video_url",
"revanced_overlay_button_copy_video_url_timestamp",
"revanced_overlay_button_external_downloader",
"revanced_overlay_button_speed_dialog",
"revanced_overlay_button_time_ordered_playlist",
"revanced_overlay_button_whitelist",
"revanced_preference_screen_account_menu",
"revanced_preference_screen_action_buttons",
"revanced_preference_screen_ambient_mode",
"revanced_preference_screen_category_bar",
"revanced_preference_screen_channel_bar",
"revanced_preference_screen_channel_profile",
"revanced_preference_screen_comments",
"revanced_preference_screen_community_posts",
"revanced_preference_screen_custom_filter",
"revanced_preference_screen_feed_flyout_menu",
"revanced_preference_screen_fullscreen",
"revanced_preference_screen_haptic_feedback",
"revanced_preference_screen_import_export",
"revanced_preference_screen_navigation_buttons",
"revanced_preference_screen_patch_information",
"revanced_preference_screen_player_buttons",
"revanced_preference_screen_player_flyout_menu",
"revanced_preference_screen_seekbar",
"revanced_preference_screen_settings_menu",
"revanced_preference_screen_shorts_player",
"revanced_preference_screen_spoof_client",
"revanced_preference_screen_toolbar",
"revanced_preference_screen_video_description",
"revanced_preference_screen_video_filter",
"revanced_sanitize_sharing_links",
"revanced_swipe_gestures_lock_mode",
"revanced_swipe_magnitude_threshold",
"revanced_swipe_overlay_background_alpha",
"revanced_swipe_overlay_rect_size",
"revanced_swipe_overlay_text_size",
"revanced_swipe_overlay_timeout",
"revanced_switch_create_with_notifications_button",
"revanced_change_player_flyout_menu_toggle",
)
private val intentKey = setOf(
"revanced_extended_settings_key",
)
private val emptyTitles = setOf(
"revanced_custom_playback_speeds",
"revanced_custom_playback_speed_menu_type",
"revanced_default_video_quality_mobile",
"revanced_disable_default_playback_speed_live",
"revanced_enable_custom_playback_speed",
"revanced_external_downloader_package_name",
"revanced_hide_shorts_comments_disabled_button",
"revanced_hide_player_flyout_menu_captions_footer",
"revanced_remember_playback_speed_last_selected",
"revanced_remember_video_quality_last_selected",
"revanced_restore_old_video_quality_menu",
"revanced_enable_debug_buffer_logging",
"revanced_whitelist_settings",
)
// A lot of mappings here.
// The performance impact should be negligible in this context,
// as the operations involved are not computationally intensive.
private val preferenceIcon = preferenceKey.associateWith { title ->
when (title) {
// Main RVX settings
"revanced_preference_screen_general" -> "general_key_icon"
"revanced_preference_screen_sb" -> "sb_enable_create_segment_icon"
// Internal RVX settings
"revanced_alt_thumbnail_home" -> "revanced_hide_navigation_home_button_icon"
"revanced_alt_thumbnail_library" -> "revanced_preference_screen_video_icon"
"revanced_alt_thumbnail_player" -> "revanced_preference_screen_player_icon"
"revanced_alt_thumbnail_search" -> "revanced_hide_shorts_shelf_search_icon"
"revanced_alt_thumbnail_subscriptions" -> "revanced_hide_navigation_subscriptions_button_icon"
"revanced_custom_player_overlay_opacity" -> "revanced_swipe_overlay_background_alpha_icon"
"revanced_default_app_settings" -> "revanced_preference_screen_settings_menu_icon"
"revanced_default_playback_speed" -> "revanced_overlay_button_speed_dialog_icon"
"revanced_enable_old_quality_layout" -> "revanced_default_video_quality_wifi_icon"
"revanced_enable_watch_panel_gestures" -> "revanced_preference_screen_swipe_controls_icon"
"revanced_hide_download_button" -> "revanced_overlay_button_external_downloader_icon"
"revanced_hide_keyword_content_comments" -> "revanced_hide_quick_actions_comment_button_icon"
"revanced_hide_keyword_content_home" -> "revanced_hide_navigation_home_button_icon"
"revanced_hide_keyword_content_search" -> "revanced_hide_shorts_shelf_search_icon"
"revanced_hide_keyword_content_subscriptions" -> "revanced_hide_navigation_subscriptions_button_icon"
"revanced_hide_like_dislike_button" -> "sb_enable_voting_icon"
"revanced_hide_navigation_library_button" -> "revanced_preference_screen_video_icon"
"revanced_hide_navigation_notifications_button" -> "notification_key_icon"
"revanced_hide_navigation_shorts_button" -> "revanced_preference_screen_shorts_icon"
"revanced_hide_player_autoplay_button" -> "revanced_change_player_flyout_menu_toggle_icon"
"revanced_hide_player_captions_button" -> "captions_key_icon"
"revanced_hide_player_flyout_menu_ambient_mode" -> "revanced_preference_screen_ambient_mode_icon"
"revanced_hide_player_flyout_menu_captions" -> "captions_key_icon"
"revanced_hide_player_flyout_menu_loop_video" -> "revanced_overlay_button_always_repeat_icon"
"revanced_hide_player_flyout_menu_more_info" -> "about_key_icon"
"revanced_hide_player_flyout_menu_quality_footer" -> "revanced_default_video_quality_wifi_icon"
"revanced_hide_player_flyout_menu_report" -> "revanced_hide_report_button_icon"
"revanced_hide_player_fullscreen_button" -> "revanced_preference_screen_fullscreen_icon"
"revanced_hide_quick_actions_dislike_button" -> "revanced_preference_screen_ryd_icon"
"revanced_hide_quick_actions_live_chat_button" -> "live_chat_key_icon"
"revanced_hide_quick_actions_save_to_playlist_button" -> "revanced_hide_playlist_button_icon"
"revanced_hide_quick_actions_share_button" -> "revanced_hide_shorts_share_button_icon"
"revanced_hide_remix_button" -> "revanced_hide_shorts_remix_button_icon"
"revanced_hide_share_button" -> "revanced_hide_shorts_share_button_icon"
"revanced_hide_shorts_comments_button" -> "revanced_hide_quick_actions_comment_button_icon"
"revanced_hide_shorts_dislike_button" -> "revanced_preference_screen_ryd_icon"
"revanced_hide_shorts_like_button" -> "revanced_hide_quick_actions_like_button_icon"
"revanced_hide_shorts_navigation_bar" -> "revanced_preference_screen_navigation_buttons_icon"
"revanced_hide_shorts_shelf_home_related_videos" -> "revanced_hide_navigation_home_button_icon"
"revanced_hide_shorts_shelf_subscriptions" -> "revanced_hide_navigation_subscriptions_button_icon"
"revanced_hide_shorts_toolbar" -> "revanced_preference_screen_toolbar_icon"
"revanced_preference_screen_account_menu" -> "account_switcher_key_icon"
"revanced_preference_screen_channel_bar" -> "account_switcher_key_icon"
"revanced_preference_screen_channel_profile" -> "account_switcher_key_icon"
"revanced_preference_screen_comments" -> "revanced_hide_quick_actions_comment_button_icon"
"revanced_preference_screen_feed_flyout_menu" -> "revanced_preference_screen_player_flyout_menu_icon"
"revanced_preference_screen_haptic_feedback" -> "revanced_enable_swipe_haptic_feedback_icon"
"revanced_preference_screen_patch_information" -> "about_key_icon"
"revanced_preference_screen_shorts_player" -> "revanced_preference_screen_shorts_icon"
"revanced_preference_screen_video_filter" -> "revanced_preference_screen_video_icon"
"revanced_swipe_gestures_lock_mode" -> "revanced_hide_player_flyout_menu_lock_screen_icon"
"revanced_disable_hdr_auto_brightness" -> "revanced_disable_hdr_video_icon"
else -> "${title}_icon"
}
}
private val intentIcon = intentKey.associateWith { "${it}_icon" }
private const val emptyIcon = "empty_icon"
// endregion.
}

View File

@ -299,12 +299,14 @@ object ShortsComponentPatch : BaseBytecodePatch(
/** /**
* Add settings * Add settings
*/ */
SettingsPatch.addPreference( var settingArray = arrayOf(
arrayOf( "PREFERENCE_SCREEN: SHORTS",
"PREFERENCE_SCREEN: SHORTS", "SETTINGS: SHORTS_COMPONENTS"
"SETTINGS: SHORTS_COMPONENTS"
)
) )
if (SettingsPatch.upward1834) {
settingArray += "SETTINGS: HIDE_SHORTS_COMMENTS_DISABLED_BUTTON"
}
SettingsPatch.addPreference(settingArray)
SettingsPatch.updatePatchStatus(this) SettingsPatch.updatePatchStatus(this)
} }

View File

@ -1,60 +0,0 @@
package app.revanced.patches.youtube.shorts.outlinebutton
import app.revanced.patcher.data.ResourceContext
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
import app.revanced.util.patch.BaseResourcePatch
@Suppress("unused")
object ShortsOutlineButtonPatch : BaseResourcePatch(
name = "Shorts outline button",
description = "Applies, at compile time, the outline icon to the action buttons in the Shorts player.",
dependencies = setOf(SettingsPatch::class),
compatiblePackages = COMPATIBLE_PACKAGE
) {
override fun execute(context: ResourceContext) {
arrayOf(
"xxxhdpi",
"xxhdpi",
"xhdpi",
"hdpi",
"mdpi"
).forEach { dpi ->
context.copyResources(
"youtube/shorts/outline",
ResourceGroup(
"drawable-$dpi",
"ic_remix_filled_white_24.webp",
"ic_remix_filled_white_shadowed.webp",
"ic_right_comment_shadowed.webp",
"ic_right_dislike_off_shadowed.webp",
"ic_right_dislike_on_32c.webp",
"ic_right_dislike_on_shadowed.webp",
"ic_right_like_off_shadowed.webp",
"ic_right_like_on_32c.webp",
"ic_right_like_on_shadowed.webp",
"ic_right_share_shadowed.webp"
)
)
}
arrayOf(
// Shorts outline icons for older versions of YouTube
ResourceGroup(
"drawable",
"ic_right_comment_32c.xml",
"ic_right_dislike_off_32c.xml",
"ic_right_like_off_32c.xml",
"ic_right_share_32c.xml"
)
).forEach { resourceGroup ->
context.copyResources("youtube/shorts/outline", resourceGroup)
}
SettingsPatch.updatePatchStatus(this)
}
}

View File

@ -152,7 +152,7 @@ object SwipeControlsPatch : BaseBytecodePatch(
SettingsPatch.addPreference( SettingsPatch.addPreference(
arrayOf( arrayOf(
"PREFERENCE_CATEGORY: SWIPE_CONTROLS_EXPERIMENTAL_FLAGS", "PREFERENCE_CATEGORY: SWIPE_CONTROLS_EXPERIMENTAL_FLAGS",
"SETTINGS: ENABLE_WATCH_PANEL_GESTEURES" "SETTINGS: ENABLE_WATCH_PANEL_GESTURES"
) )
) )
} ?: println("WARNING: Failed to resolve WatchPanelGesturesFingerprint") } ?: println("WARNING: Failed to resolve WatchPanelGesturesFingerprint")

View File

@ -100,18 +100,33 @@ object ResourceUtils {
} }
} }
fun ResourceContext.addPreferenceFragment(key: String) { fun ResourceContext.addPreferenceFragment(key: String, insertKey: String) {
val targetClass = val targetClass =
"com.google.android.apps.youtube.app.settings.videoquality.VideoQualitySettingsActivity" "com.google.android.apps.youtube.app.settings.videoquality.VideoQualitySettingsActivity"
this.xmlEditor[YOUTUBE_SETTINGS_PATH].use { editor -> this.xmlEditor[YOUTUBE_SETTINGS_PATH].use { editor ->
with(editor.file) { with(editor.file) {
doRecursively loop@{ val processedKeys = mutableSetOf<String>() // To track processed keys
if (it !is Element) return@loop
it.getAttributeNode("android:key")?.let { attribute -> doRecursively loop@{ node ->
if (attribute.textContent == "@string/about_key" && it.getAttributeNode("app:iconSpaceReserved").textContent == "false") { if (node !is Element) return@loop // Skip if not an element
it.insertNode("Preference", it) {
setAttribute("android:title", "@string/" + key + "_title") val attributeNode = node.getAttributeNode("android:key")
?: return@loop // Skip if no key attribute
val currentKey = attributeNode.textContent
// Check if the current key has already been processed
if (processedKeys.contains(currentKey)) {
return@loop // Skip if already processed
} else {
processedKeys.add(currentKey) // Add the current key to processedKeys
}
when (currentKey) {
insertKey -> {
node.insertNode("Preference", node) {
setAttribute("android:key", "${key}_key")
setAttribute("android:title", "@string/${key}_title")
this.appendChild( this.appendChild(
ownerDocument.createElement("intent").also { intentNode -> ownerDocument.createElement("intent").also { intentNode ->
intentNode.setAttribute( intentNode.setAttribute(
@ -120,24 +135,18 @@ object ResourceUtils {
) )
intentNode.setAttribute("android:data", key + "_intent") intentNode.setAttribute("android:data", key + "_intent")
intentNode.setAttribute("android:targetClass", targetClass) intentNode.setAttribute("android:targetClass", targetClass)
}) }
)
} }
it.getAttributeNode("app:iconSpaceReserved").textContent = "true" node.setAttribute("app:iconSpaceReserved", "true")
return@loop
} }
}
}
doRecursively loop@{ "true" -> {
if (it !is Element) return@loop attributeNode.textContent = "false"
it.getAttributeNode("app:iconSpaceReserved")?.let { attribute ->
if (attribute.textContent == "true") {
attribute.textContent = "false"
} }
} }
} }
} }
} }
} }
} }

View File

@ -1,6 +1,7 @@
package app.revanced.patches.youtube.utils.settings package app.revanced.patches.youtube.utils.settings
import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import app.revanced.patches.shared.elements.StringsElementsUtils.removeStringsElements import app.revanced.patches.shared.elements.StringsElementsUtils.removeStringsElements
import app.revanced.patches.shared.mapping.ResourceMappingPatch import app.revanced.patches.shared.mapping.ResourceMappingPatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
@ -37,9 +38,48 @@ object SettingsPatch : BaseResourcePatch(
compatiblePackages = COMPATIBLE_PACKAGE, compatiblePackages = COMPATIBLE_PACKAGE,
requiresIntegrations = true requiresIntegrations = true
), Closeable { ), Closeable {
private const val DEFAULT_ELEMENT = "About"
private const val DEFAULT_NAME = "ReVanced Extended"
private val THREAD_COUNT = Runtime.getRuntime().availableProcessors() private val SETTINGS_ELEMENTS_MAP = mapOf(
private val threadPoolExecutor = Executors.newFixedThreadPool(THREAD_COUNT) "Parent settings" to "@string/parent_tools_key",
"General" to "@string/general_key",
"Account" to "@string/account_switcher_key",
"Data saving" to "@string/data_saving_settings_key",
"Autoplay" to "@string/auto_play_key",
"Video quality preferences" to "@string/video_quality_settings_key",
"Background" to "@string/offline_key",
"Watch on TV" to "@string/pair_with_tv_key",
"Manage all history" to "@string/history_key",
"Your data in YouTube" to "@string/your_data_key",
"Privacy" to "@string/privacy_key",
"History & privacy" to "@string/privacy_key",
"Try experimental new features" to "@string/premium_early_access_browse_page_key",
"Purchases and memberships" to "@string/subscription_product_setting_key",
"Billing & payments" to "@string/billing_and_payment_key",
"Billing and payments" to "@string/billing_and_payment_key",
"Notifications" to "@string/notification_key",
"Connected apps" to "@string/connected_accounts_browse_page_key",
"Live chat" to "@string/live_chat_key",
"Captions" to "@string/captions_key",
"Accessibility" to "@string/accessibility_settings_key",
DEFAULT_ELEMENT to "@string/about_key"
)
private val InsertPosition by stringPatchOption(
key = "InsertPosition",
default = DEFAULT_ELEMENT,
values = SETTINGS_ELEMENTS_MAP,
title = "Insert position",
description = "The settings menu name that the RVX settings menu should be above."
)
private val RVXSettingsMenuName by stringPatchOption(
key = "RVXSettingsMenuName",
default = DEFAULT_NAME,
title = "RVX settings menu name",
description = "The name of the RVX settings menu."
)
internal lateinit var contexts: ResourceContext internal lateinit var contexts: ResourceContext
internal var upward1831 = false internal var upward1831 = false
@ -51,48 +91,16 @@ object SettingsPatch : BaseResourcePatch(
internal var upward1912 = false internal var upward1912 = false
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
/**
* set resource context
*/
contexts = context contexts = context
val resourceXmlFile = context["res/values/integers.xml"].readBytes() /**
* set version info
for (threadIndex in 0 until THREAD_COUNT) { */
threadPoolExecutor.execute thread@{ setVersionInfo()
context.xmlEditor[resourceXmlFile.inputStream()].use { editor ->
val resources = editor.file.documentElement.childNodes
val resourcesLength = resources.length
val jobSize = resourcesLength / THREAD_COUNT
val batchStart = jobSize * threadIndex
val batchEnd = jobSize * (threadIndex + 1)
element@ for (i in batchStart until batchEnd) {
if (i >= resourcesLength) return@thread
val node = resources.item(i)
if (node !is Element) continue
if (node.nodeName != "integer" || !node.getAttribute("name")
.startsWith("google_play_services_version")
) continue
val playServicesVersion = node.textContent.toInt()
upward1831 = 233200000 <= playServicesVersion
upward1834 = 233500000 <= playServicesVersion
upward1839 = 234000000 <= playServicesVersion
upward1842 = 234302000 <= playServicesVersion
upward1849 = 235000000 <= playServicesVersion
upward1902 = 240204000 < playServicesVersion
upward1912 = 241302000 <= playServicesVersion
break
}
}
}
}
threadPoolExecutor
.also { it.shutdown() }
.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS)
/** /**
* remove strings duplicated with RVX resources * remove strings duplicated with RVX resources
@ -138,7 +146,16 @@ object SettingsPatch : BaseResourcePatch(
/** /**
* initialize ReVanced Extended Settings * initialize ReVanced Extended Settings
*/ */
context.addPreferenceFragment("revanced_extended_settings") val elementKey = SETTINGS_ELEMENTS_MAP[InsertPosition]
?: InsertPosition
?: SETTINGS_ELEMENTS_MAP[DEFAULT_ELEMENT]
elementKey?.let { insertKey ->
context.addPreferenceFragment(
"revanced_extended_settings",
insertKey
)
}
/** /**
* remove ReVanced Extended Settings divider * remove ReVanced Extended Settings divider
@ -164,6 +181,112 @@ object SettingsPatch : BaseResourcePatch(
} }
} }
/**
* set revanced-patches version
*/
val jarManifest = classLoader.getResources("META-INF/MANIFEST.MF")
while (jarManifest.hasMoreElements())
contexts.updatePatchStatusSettings(
"ReVanced Patches",
Manifest(jarManifest.nextElement().openStream())
.mainAttributes
.getValue("Version") + ""
)
/**
* set revanced-integrations version
*/
val versionName = SettingsBytecodePatch.contexts
.findClass { it.sourceFile == "BuildConfig.java" }!!
.mutableClass
.fields
.single { it.name == "VERSION_NAME" }
.initialValue
.toString()
.trim()
.replace("\"", "")
.replace("&quot;", "")
contexts.updatePatchStatusSettings(
"ReVanced Integrations",
versionName
)
}
override fun close() {
/**
* change RVX settings menu name
* since it must be invoked after the Translations patch, it must be the last in the order.
*/
RVXSettingsMenuName?.let { customName ->
if (customName != DEFAULT_NAME) {
contexts.removeStringsElements(
arrayOf("revanced_extended_settings_title")
)
contexts.xmlEditor["res/values/strings.xml"].use { editor ->
val document = editor.file
mapOf(
"revanced_extended_settings_title" to customName
).forEach { (k, v) ->
val stringElement = document.createElement("string")
stringElement.setAttribute("name", k)
stringElement.textContent = v
document.getElementsByTagName("resources").item(0)
.appendChild(stringElement)
}
}
}
} ?: println("WARNING: Invalid RVX settings menu name. RVX settings menu name does not change.")
}
private fun setVersionInfo() {
val threadCount = Runtime.getRuntime().availableProcessors()
val threadPoolExecutor = Executors.newFixedThreadPool(threadCount)
val resourceXmlFile = contexts["res/values/integers.xml"].readBytes()
for (threadIndex in 0 until threadCount) {
threadPoolExecutor.execute thread@{
contexts.xmlEditor[resourceXmlFile.inputStream()].use { editor ->
val resources = editor.file.documentElement.childNodes
val resourcesLength = resources.length
val jobSize = resourcesLength / threadCount
val batchStart = jobSize * threadIndex
val batchEnd = jobSize * (threadIndex + 1)
element@ for (i in batchStart until batchEnd) {
if (i >= resourcesLength) return@thread
val node = resources.item(i)
if (node !is Element) continue
if (node.nodeName != "integer" || !node.getAttribute("name")
.startsWith("google_play_services_version")
) continue
val playServicesVersion = node.textContent.toInt()
upward1831 = 233200000 <= playServicesVersion
upward1834 = 233500000 <= playServicesVersion
upward1839 = 234000000 <= playServicesVersion
upward1842 = 234302000 <= playServicesVersion
upward1849 = 235000000 <= playServicesVersion
upward1902 = 240204000 < playServicesVersion
upward1912 = 241302000 <= playServicesVersion
break
}
}
}
}
threadPoolExecutor
.also { it.shutdown() }
.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS)
} }
internal fun addPreference(settingArray: Array<String>) { internal fun addPreference(settingArray: Array<String>) {
@ -178,39 +301,7 @@ object SettingsPatch : BaseResourcePatch(
updatePatchStatus(patch.name!!) updatePatchStatus(patch.name!!)
} }
internal fun updatePatchStatus(patchTitle: String) { internal fun updatePatchStatus(patchName: String) {
contexts.updatePatchStatus(patchTitle) contexts.updatePatchStatus(patchName)
} }
}
override fun close() {
// region set ReVanced Patches Version
val jarManifest = classLoader.getResources("META-INF/MANIFEST.MF")
while (jarManifest.hasMoreElements())
contexts.updatePatchStatusSettings(
"ReVanced Patches",
Manifest(jarManifest.nextElement().openStream())
.mainAttributes
.getValue("Version") + ""
)
// endregion
// region set ReVanced Integrations Version
val buildConfigMutableClass =
SettingsBytecodePatch.contexts.findClass { it.sourceFile == "BuildConfig.java" }!!.mutableClass
val versionNameField = buildConfigMutableClass.fields.single { it.name == "VERSION_NAME" }
val versionName =
versionNameField.initialValue.toString().trim().replace("\"", "").replace("&quot;", "")
contexts.updatePatchStatusSettings(
"ReVanced Integrations",
versionName
)
// endregion
}
}

View File

@ -63,11 +63,14 @@ fun ResourceContext.copyResources(
for (resourceGroup in resources) { for (resourceGroup in resources) {
resourceGroup.resources.forEach { resource -> resourceGroup.resources.forEach { resource ->
val resourceFile = "${resourceGroup.resourceDirectoryName}/$resource" val resourceFile = "${resourceGroup.resourceDirectoryName}/$resource"
Files.copy(
inputStreamFromBundledResource(sourceResourceDirectory, resourceFile)!!, inputStreamFromBundledResource(sourceResourceDirectory, resourceFile)?.let { inputStream ->
targetResourceDirectory.resolve(resourceFile).toPath(), Files.copy(
StandardCopyOption.REPLACE_EXISTING, inputStream,
) targetResourceDirectory.resolve(resourceFile).toPath(),
StandardCopyOption.REPLACE_EXISTING,
)
}
} }
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -0,0 +1,788 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108.0dp"
android:height="108.0dp"
android:viewportWidth="108.0"
android:viewportHeight="108.0">
<path
android:pathData="M0,0h108zM0,1h108zM0,2h108zM0,3h108zM0,4h108zM0,5h108zM0,6h108zM0,7h108zM0,8h108zM0,9h108zM0,10h108zM0,11h108zM0,12h108zM0,13h108zM0,14h108zM0,15h108zM0,16h108zM0,17h108zM0,18h108zM0,19h108zM0,20h108zM0,21h108zM0,22h108zM0,23h108zM0,24h108zM0,25h108zM0,26h108zM0,27h108zM0,28h108zM0,29h108zM0,30h108zM0,31h108zM0,32h108zM0,33h108zM0,34h108zM0,35h108zM0,36h53zM60,36h48zM0,37h48zM64,37h44zM0,38h47zM65,38h43zM0,39h45zM67,39h41zM0,40h44zM69,40h39zM0,41h34zM39,41h4zM69,41h39zM0,42h33zM41,42h1zM70,42h38zM0,43h33zM71,43h37zM0,44h32zM72,44h36zM0,45h32zM72,45h36zM0,46h32zM42,46h2zM73,46h35zM0,47h32zM41,47h4zM54,47h4zM73,47h35zM0,48h32zM41,48h6zM51,48h10zM74,48h34zM0,49h32zM41,49h6zM53,49h9zM74,49h34zM0,50h32zM40,50h7zM55,50h8zM74,50h34zM0,51h32zM40,51h7zM56,51h7zM75,51h33zM0,52h32zM39,52h7zM57,52h6zM75,52h33zM0,53h32zM39,53h7zM57,53h7zM75,53h33zM0,54h32zM39,54h7zM58,54h6zM75,54h33zM0,55h32zM39,55h7zM58,55h6zM75,55h33zM0,56h32zM39,56h7zM57,56h6zM75,56h33zM0,57h32zM40,57h7zM57,57h6zM75,57h33zM0,58h32zM40,58h7zM56,58h7zM74,58h34zM0,59h32zM41,59h6zM53,59h9zM74,59h34zM0,60h32zM41,60h7zM52,60h9zM74,60h34zM0,61h32zM41,61h4zM53,61h6zM73,61h35zM0,62h32zM41,62h3zM55,62h1zM73,62h35zM0,63h32zM73,63h35zM0,64h32zM72,64h36zM0,65h33zM72,65h36zM0,66h33zM71,66h37zM0,67h34zM39,67h3zM70,67h38zM0,68h35zM38,68h5zM69,68h39zM0,69h45zM68,69h40zM0,70h47zM66,70h42zM0,71h48zM64,71h44zM0,72h51zM61,72h47zM0,73h108zM0,74h108zM0,75h108zM0,76h108zM0,77h108zM0,78h108zM0,79h108zM0,80h108zM0,81h108zM0,82h108zM0,83h108zM0,84h108zM0,85h108zM0,86h108zM0,87h108zM0,88h108zM0,89h108zM0,90h108zM0,91h108zM0,92h108zM0,93h108zM0,94h108zM0,95h108zM0,96h108zM0,97h108zM0,98h108zM0,99h108zM0,100h108zM0,101h108zM0,102h108zM0,103h108zM0,104h108zM0,105h108zM0,106h108zM0,107h108z"
android:strokeColor="#00000000"
android:strokeWidth="1.0" />
<path
android:pathData="M53,36h1zM73,48h1zM47,50h1zM54,50h1zM74,52h1zM74,53h1zM51,60h1z"
android:strokeColor="#05000000"
android:strokeWidth="1.0" />
<path
android:pathData="M54,36h1zM57,36h1zM46,54h1zM56,56h1zM47,58h1zM52,72h1z"
android:strokeColor="#09000000"
android:strokeWidth="1.0" />
<path
android:pathData="M55,36h1zM55,46h1zM52,47h1zM54,58h1zM52,59h1zM38,67h1z"
android:strokeColor="#10000000"
android:strokeWidth="1.0" />
<path
android:pathData="M56,36h1zM49,37h1zM44,46h1zM40,48h1zM71,64h1zM66,69h1zM63,71h1z"
android:strokeColor="#0f000000"
android:strokeWidth="1.0" />
<path
android:pathData="M58,36h1zM64,38h1zM66,39h1zM33,42h1zM71,44h1zM72,46h1zM47,48h1zM40,49h1zM54,62h1zM36,68h1z"
android:strokeColor="#04000000"
android:strokeWidth="1.0" />
<path
android:pathData="M59,36h1zM68,40h1zM34,41h1zM38,41h1zM53,47h1zM58,47h1zM47,49h1zM46,52h1zM57,55h1zM39,57h1zM56,57h1zM45,61h1zM59,61h1zM56,62h1zM72,63h1zM71,65h1zM70,66h1zM42,67h1zM69,67h1zM67,69h1zM65,70h1zM51,72h1z"
android:strokeColor="#01000000"
android:strokeWidth="1.0" />
<path
android:pathData="M48,37h1zM63,37h1zM45,39h1zM32,44h1zM41,46h1zM52,49h1zM56,52h1zM63,56h1zM74,57h1zM52,61h1zM57,62h1zM41,66h1z"
android:strokeColor="#03000000"
android:strokeWidth="1.0" />
<path
android:pathData="M50,37h1zM39,58h1zM54,72h1z"
android:strokeColor="#29000000"
android:strokeWidth="1.0" />
<path
android:pathData="M51,37h1zM50,60h1z"
android:strokeColor="#57000000"
android:strokeWidth="1.0" />
<path
android:pathData="M52,37h1zM43,45h1z"
android:strokeColor="#6f000000"
android:strokeWidth="1.0" />
<path
android:pathData="M53,37h1zM43,42h1zM52,46h1zM73,52h1zM38,57h1z"
android:strokeColor="#9f000000"
android:strokeWidth="1.0" />
<path
android:pathData="M54,37h1zM67,41h1z"
android:strokeColor="#b9000000"
android:strokeWidth="1.0" />
<path
android:pathData="M55,37h1zM35,42h1zM63,49h1zM52,58h1z"
android:strokeColor="#c1000000"
android:strokeWidth="1.0" />
<path
android:pathData="M56,37h1zM42,44h1zM73,55h1zM68,66h1z"
android:strokeColor="#c2000000"
android:strokeWidth="1.0" />
<path
android:pathData="M57,37h1zM49,38h1zM63,70h1z"
android:strokeColor="#b8000000"
android:strokeWidth="1.0" />
<path
android:pathData="M58,37h1z"
android:strokeColor="#98000000"
android:strokeWidth="1.0" />
<path
android:pathData="M59,37h1z"
android:strokeColor="#7c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M60,37h1z"
android:strokeColor="#59000000"
android:strokeWidth="1.0" />
<path
android:pathData="M61,37h1zM49,71h1zM57,72h1z"
android:strokeColor="#30000000"
android:strokeWidth="1.0" />
<path
android:pathData="M62,37h1zM37,41h1zM60,61h1z"
android:strokeColor="#15000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,38h1zM40,42h1zM45,47h1zM46,53h1zM32,64h1z"
android:strokeColor="#07000000"
android:strokeWidth="1.0" />
<path
android:pathData="M48,38h1z"
android:strokeColor="#69000000"
android:strokeWidth="1.0" />
<path
android:pathData="M50,38h1zM43,64h1z"
android:strokeColor="#ed000000"
android:strokeWidth="1.0" />
<path
android:pathData="M51,38h1zM48,47h1zM39,65h1z"
android:strokeColor="#f6000000"
android:strokeWidth="1.0" />
<path
android:pathData="M52,38h1zM59,38h1zM64,40h1zM69,44h1zM59,45h1zM49,47h1zM48,52h1zM37,55h1zM55,55h1zM54,56h1zM36,66h1zM45,67h1zM50,70h1z"
android:strokeColor="#fb000000"
android:strokeWidth="1.0" />
<path
android:pathData="M53,38h1zM58,38h1zM35,43h1zM44,43h1zM34,44h1zM39,44h1zM44,44h1zM39,45h1zM46,45h1zM51,45h1zM50,46h1zM62,46h1zM70,47h1zM64,49h1zM71,49h1zM37,51h1zM52,51h1zM37,52h1zM53,52h1zM37,56h1zM53,56h1zM37,57h1zM71,59h1zM70,61h1zM62,62h1zM39,63h1zM34,64h1zM39,64h1zM54,64h1zM56,64h2zM44,65h1zM67,66h1zM66,67h1zM64,68h1zM63,69h1zM52,70h1z"
android:strokeColor="#fe000000"
android:strokeWidth="1.0" />
<path
android:pathData="M54,38h4zM49,39h14zM48,40h16zM46,41h20zM45,42h22zM36,43h2zM45,43h23zM35,44h4zM45,44h24zM34,45h5zM47,45h4zM60,45h10zM34,46h5zM48,46h2zM63,46h7zM34,47h5zM63,47h7zM34,48h5zM64,48h7zM34,49h5zM65,49h6zM34,50h4zM65,50h7zM34,51h3zM49,51h3zM65,51h8zM34,52h3zM49,52h4zM65,52h8zM34,53h3zM48,53h7zM66,53h7zM34,54h3zM48,54h7zM66,54h7zM34,55h3zM48,55h7zM66,55h7zM34,56h3zM49,56h4zM65,56h8zM34,57h3zM49,57h4zM65,57h8zM34,58h4zM50,58h1zM65,58h7zM34,59h5zM65,59h6zM34,60h5zM64,60h7zM34,61h5zM63,61h7zM34,62h5zM48,62h2zM63,62h7zM34,63h5zM47,63h4zM61,63h9zM35,64h4zM45,64h9zM55,64h1zM58,64h11zM35,65h4zM45,65h23zM45,66h22zM46,67h20zM48,68h16zM48,69h15zM53,70h7z"
android:strokeColor="#ff000000"
android:strokeWidth="1.0" />
<path
android:pathData="M60,38h1zM47,40h1zM66,41h1zM43,44h1zM39,46h1zM64,50h1zM65,53h1zM60,63h1zM40,64h1z"
android:strokeColor="#fa000000"
android:strokeWidth="1.0" />
<path
android:pathData="M61,38h1zM49,59h1zM39,61h1zM59,63h1z"
android:strokeColor="#ec000000"
android:strokeWidth="1.0" />
<path
android:pathData="M62,38h1z"
android:strokeColor="#c5000000"
android:strokeWidth="1.0" />
<path
android:pathData="M63,38h1zM39,59h1zM65,69h1z"
android:strokeColor="#6d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M46,39h1zM39,42h1zM64,70h1z"
android:strokeColor="#2e000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,39h1zM42,43h1z"
android:strokeColor="#86000000"
android:strokeWidth="1.0" />
<path
android:pathData="M48,39h1zM63,39h1zM45,45h1zM62,61h1zM37,66h1z"
android:strokeColor="#f1000000"
android:strokeWidth="1.0" />
<path
android:pathData="M64,39h1zM40,43h1zM33,44h1z"
android:strokeColor="#97000000"
android:strokeWidth="1.0" />
<path
android:pathData="M65,39h1zM55,72h1z"
android:strokeColor="#33000000"
android:strokeWidth="1.0" />
<path
android:pathData="M44,40h1zM32,45h1zM59,47h1zM39,51h1zM40,60h1zM32,63h1z"
android:strokeColor="#0a000000"
android:strokeWidth="1.0" />
<path
android:pathData="M45,40h1z"
android:strokeColor="#80000000"
android:strokeWidth="1.0" />
<path
android:pathData="M46,40h1zM50,47h1zM53,51h1z"
android:strokeColor="#dd000000"
android:strokeWidth="1.0" />
<path
android:pathData="M65,40h1z"
android:strokeColor="#e5000000"
android:strokeWidth="1.0" />
<path
android:pathData="M66,40h1z"
android:strokeColor="#82000000"
android:strokeWidth="1.0" />
<path
android:pathData="M67,40h1z"
android:strokeColor="#1a000000"
android:strokeWidth="1.0" />
<path
android:pathData="M35,41h1zM69,42h1zM48,71h1z"
android:strokeColor="#0e000000"
android:strokeWidth="1.0" />
<path
android:pathData="M36,41h1z"
android:strokeColor="#26000000"
android:strokeWidth="1.0" />
<path
android:pathData="M43,41h1zM46,61h1z"
android:strokeColor="#12000000"
android:strokeWidth="1.0" />
<path
android:pathData="M44,41h1z"
android:strokeColor="#8b000000"
android:strokeWidth="1.0" />
<path
android:pathData="M45,41h1zM72,50h1z"
android:strokeColor="#f3000000"
android:strokeWidth="1.0" />
<path
android:pathData="M68,41h1zM53,50h1z"
android:strokeColor="#22000000"
android:strokeWidth="1.0" />
<path
android:pathData="M34,42h1z"
android:strokeColor="#3e000000"
android:strokeWidth="1.0" />
<path
android:pathData="M36,42h1zM39,43h1zM49,70h1zM54,71h1z"
android:strokeColor="#e3000000"
android:strokeWidth="1.0" />
<path
android:pathData="M37,42h1zM44,67h1z"
android:strokeColor="#cc000000"
android:strokeWidth="1.0" />
<path
android:pathData="M38,42h1zM64,53h1z"
android:strokeColor="#6e000000"
android:strokeWidth="1.0" />
<path
android:pathData="M42,42h1zM56,46h1zM72,62h1z"
android:strokeColor="#0d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M44,42h1zM65,55h1zM39,62h1zM61,62h1zM70,63h1z"
android:strokeColor="#f7000000"
android:strokeWidth="1.0" />
<path
android:pathData="M67,42h1zM52,45h1zM70,46h1zM49,50h2zM37,53h1zM71,60h1zM46,63h1zM44,64h1zM43,65h1zM68,65h1zM51,70h1z"
android:strokeColor="#fc000000"
android:strokeWidth="1.0" />
<path
android:pathData="M68,42h1zM72,60h1z"
android:strokeColor="#a2000000"
android:strokeWidth="1.0" />
<path
android:pathData="M33,43h1zM43,63h1z"
android:strokeColor="#44000000"
android:strokeWidth="1.0" />
<path
android:pathData="M34,43h1zM54,45h1zM46,46h1zM49,49h1z"
android:strokeColor="#dc000000"
android:strokeWidth="1.0" />
<path
android:pathData="M38,43h1zM40,44h1zM62,47h1zM37,54h1zM72,58h1z"
android:strokeColor="#f9000000"
android:strokeWidth="1.0" />
<path
android:pathData="M41,43h1zM47,51h1z"
android:strokeColor="#2c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M43,43h1zM68,43h1zM71,48h1zM50,62h1zM65,68h1zM61,70h1z"
android:strokeColor="#f8000000"
android:strokeWidth="1.0" />
<path
android:pathData="M69,43h1zM62,48h1z"
android:strokeColor="#92000000"
android:strokeWidth="1.0" />
<path
android:pathData="M70,43h1zM40,66h1z"
android:strokeColor="#11000000"
android:strokeWidth="1.0" />
<path
android:pathData="M41,44h1z"
android:strokeColor="#b5000000"
android:strokeWidth="1.0" />
<path
android:pathData="M70,44h1z"
android:strokeColor="#79000000"
android:strokeWidth="1.0" />
<path
android:pathData="M33,45h1zM33,52h1zM33,56h1zM58,63h1z"
android:strokeColor="#c9000000"
android:strokeWidth="1.0" />
<path
android:pathData="M40,45h1zM51,50h1z"
android:strokeColor="#d7000000"
android:strokeWidth="1.0" />
<path
android:pathData="M41,45h1zM46,47h1zM45,62h1z"
android:strokeColor="#37000000"
android:strokeWidth="1.0" />
<path
android:pathData="M42,45h1z"
android:strokeColor="#16000000"
android:strokeWidth="1.0" />
<path
android:pathData="M44,45h1zM47,54h1z"
android:strokeColor="#b6000000"
android:strokeWidth="1.0" />
<path
android:pathData="M53,45h1zM34,65h1z"
android:strokeColor="#ee000000"
android:strokeWidth="1.0" />
<path
android:pathData="M55,45h1zM33,48h1zM33,60h1zM53,63h1z"
android:strokeColor="#cb000000"
android:strokeWidth="1.0" />
<path
android:pathData="M56,45h1zM33,51h1zM33,57h1z"
android:strokeColor="#c7000000"
android:strokeWidth="1.0" />
<path
android:pathData="M57,45h1z"
android:strokeColor="#d1000000"
android:strokeWidth="1.0" />
<path
android:pathData="M58,45h1zM56,71h1z"
android:strokeColor="#eb000000"
android:strokeWidth="1.0" />
<path
android:pathData="M70,45h1zM48,51h1z"
android:strokeColor="#ea000000"
android:strokeWidth="1.0" />
<path
android:pathData="M71,45h1zM70,65h1zM56,72h1z"
android:strokeColor="#36000000"
android:strokeWidth="1.0" />
<path
android:pathData="M32,46h1zM32,47h1zM32,48h1zM32,49h1zM32,50h1zM32,51h1zM32,52h1zM32,53h1zM32,54h1zM74,54h1zM32,55h1zM32,56h1zM32,57h1zM32,58h1zM32,59h1zM62,59h1zM32,60h1zM61,60h1zM32,61h1zM32,62h1zM33,66h1z"
android:strokeColor="#0b000000"
android:strokeWidth="1.0" />
<path
android:pathData="M33,46h1zM33,49h1zM33,54h1zM48,58h1zM33,59h1zM33,62h1z"
android:strokeColor="#c6000000"
android:strokeWidth="1.0" />
<path
android:pathData="M40,46h1z"
android:strokeColor="#77000000"
android:strokeWidth="1.0" />
<path
android:pathData="M45,46h1zM38,55h1z"
android:strokeColor="#6b000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,46h1zM61,46h1zM55,54h1zM48,56h1zM49,58h1zM64,59h1zM70,62h1zM51,63h1zM69,64h1zM44,66h1zM47,68h1zM60,70h1z"
android:strokeColor="#fd000000"
android:strokeWidth="1.0" />
<path
android:pathData="M51,46h1zM63,60h1z"
android:strokeColor="#e7000000"
android:strokeWidth="1.0" />
<path
android:pathData="M53,46h1zM64,54h1z"
android:strokeColor="#4e000000"
android:strokeWidth="1.0" />
<path
android:pathData="M54,46h1z"
android:strokeColor="#2a000000"
android:strokeWidth="1.0" />
<path
android:pathData="M57,46h1zM53,62h1z"
android:strokeColor="#1b000000"
android:strokeWidth="1.0" />
<path
android:pathData="M58,46h1zM63,58h1z"
android:strokeColor="#45000000"
android:strokeWidth="1.0" />
<path
android:pathData="M59,46h1z"
android:strokeColor="#95000000"
android:strokeWidth="1.0" />
<path
android:pathData="M60,46h1z"
android:strokeColor="#e0000000"
android:strokeWidth="1.0" />
<path
android:pathData="M71,46h1z"
android:strokeColor="#93000000"
android:strokeWidth="1.0" />
<path
android:pathData="M33,47h1zM33,61h1z"
android:strokeColor="#ca000000"
android:strokeWidth="1.0" />
<path
android:pathData="M39,47h1zM65,54h1zM51,58h1zM46,68h1z"
android:strokeColor="#f2000000"
android:strokeWidth="1.0" />
<path
android:pathData="M40,47h1zM55,57h1z"
android:strokeColor="#40000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,47h1z"
android:strokeColor="#a8000000"
android:strokeWidth="1.0" />
<path
android:pathData="M51,47h1zM68,67h1z"
android:strokeColor="#4c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M60,47h1zM50,48h1z"
android:strokeColor="#48000000"
android:strokeWidth="1.0" />
<path
android:pathData="M61,47h1zM64,69h1z"
android:strokeColor="#df000000"
android:strokeWidth="1.0" />
<path
android:pathData="M71,47h1zM53,71h1z"
android:strokeColor="#d4000000"
android:strokeWidth="1.0" />
<path
android:pathData="M72,47h1zM61,48h1zM73,59h1z"
android:strokeColor="#24000000"
android:strokeWidth="1.0" />
<path
android:pathData="M39,48h1zM40,65h1z"
android:strokeColor="#c4000000"
android:strokeWidth="1.0" />
<path
android:pathData="M48,48h1z"
android:strokeColor="#52000000"
android:strokeWidth="1.0" />
<path
android:pathData="M49,48h1zM64,57h1z"
android:strokeColor="#c3000000"
android:strokeWidth="1.0" />
<path
android:pathData="M63,48h1zM38,50h1zM53,57h1zM49,61h1z"
android:strokeColor="#f5000000"
android:strokeWidth="1.0" />
<path
android:pathData="M72,48h1z"
android:strokeColor="#7f000000"
android:strokeWidth="1.0" />
<path
android:pathData="M39,49h1zM42,64h1z"
android:strokeColor="#88000000"
android:strokeWidth="1.0" />
<path
android:pathData="M48,49h1zM67,68h1z"
android:strokeColor="#43000000"
android:strokeWidth="1.0" />
<path
android:pathData="M50,49h1zM42,65h1z"
android:strokeColor="#ac000000"
android:strokeWidth="1.0" />
<path
android:pathData="M51,49h1z"
android:strokeColor="#25000000"
android:strokeWidth="1.0" />
<path
android:pathData="M62,49h1zM51,61h1zM69,66h1z"
android:strokeColor="#1e000000"
android:strokeWidth="1.0" />
<path
android:pathData="M72,49h1zM33,50h1zM33,58h1z"
android:strokeColor="#ce000000"
android:strokeWidth="1.0" />
<path
android:pathData="M73,49h1z"
android:strokeColor="#17000000"
android:strokeWidth="1.0" />
<path
android:pathData="M39,50h1zM48,60h1zM43,67h1z"
android:strokeColor="#3d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M48,50h1zM48,70h1z"
android:strokeColor="#af000000"
android:strokeWidth="1.0" />
<path
android:pathData="M52,50h1zM73,57h1z"
android:strokeColor="#8d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M63,50h1zM47,61h1z"
android:strokeColor="#5c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M73,50h1z"
android:strokeColor="#4d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M38,51h1z"
android:strokeColor="#b2000000"
android:strokeWidth="1.0" />
<path
android:pathData="M54,51h1z"
android:strokeColor="#75000000"
android:strokeWidth="1.0" />
<path
android:pathData="M55,51h1z"
android:strokeColor="#18000000"
android:strokeWidth="1.0" />
<path
android:pathData="M63,51h1z"
android:strokeColor="#13000000"
android:strokeWidth="1.0" />
<path
android:pathData="M64,51h1z"
android:strokeColor="#d2000000"
android:strokeWidth="1.0" />
<path
android:pathData="M73,51h1z"
android:strokeColor="#81000000"
android:strokeWidth="1.0" />
<path
android:pathData="M74,51h1zM57,54h1zM46,56h1zM55,58h1zM40,59h1zM47,59h1zM44,62h1zM35,68h1zM37,68h1zM43,68h1zM60,72h1z"
android:strokeColor="#02000000"
android:strokeWidth="1.0" />
<path
android:pathData="M38,52h1z"
android:strokeColor="#94000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,52h1zM46,69h1z"
android:strokeColor="#5b000000"
android:strokeWidth="1.0" />
<path
android:pathData="M54,52h1zM48,57h1zM47,62h1z"
android:strokeColor="#f0000000"
android:strokeWidth="1.0" />
<path
android:pathData="M55,52h1z"
android:strokeColor="#7d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M63,52h1zM74,55h1zM74,56h1zM42,63h1zM34,67h1zM68,68h1z"
android:strokeColor="#06000000"
android:strokeWidth="1.0" />
<path
android:pathData="M64,52h1zM61,61h1zM54,63h1z"
android:strokeColor="#a1000000"
android:strokeWidth="1.0" />
<path
android:pathData="M33,53h1zM33,55h1zM33,63h1zM47,69h1z"
android:strokeColor="#cd000000"
android:strokeWidth="1.0" />
<path
android:pathData="M38,53h1z"
android:strokeColor="#78000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,53h1z"
android:strokeColor="#9b000000"
android:strokeWidth="1.0" />
<path
android:pathData="M55,53h1zM64,58h1zM52,63h1z"
android:strokeColor="#f4000000"
android:strokeWidth="1.0" />
<path
android:pathData="M56,53h1z"
android:strokeColor="#2d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M73,53h1z"
android:strokeColor="#bf000000"
android:strokeWidth="1.0" />
<path
android:pathData="M38,54h1zM33,65h1zM41,65h1z"
android:strokeColor="#61000000"
android:strokeWidth="1.0" />
<path
android:pathData="M56,54h1z"
android:strokeColor="#74000000"
android:strokeWidth="1.0" />
<path
android:pathData="M73,54h1zM38,66h1z"
android:strokeColor="#ba000000"
android:strokeWidth="1.0" />
<path
android:pathData="M46,55h1z"
android:strokeColor="#08000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,55h1z"
android:strokeColor="#a6000000"
android:strokeWidth="1.0" />
<path
android:pathData="M56,55h1z"
android:strokeColor="#47000000"
android:strokeWidth="1.0" />
<path
android:pathData="M64,55h1z"
android:strokeColor="#63000000"
android:strokeWidth="1.0" />
<path
android:pathData="M38,56h1zM64,56h1zM55,63h2z"
android:strokeColor="#90000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,56h1z"
android:strokeColor="#73000000"
android:strokeWidth="1.0" />
<path
android:pathData="M55,56h1z"
android:strokeColor="#b0000000"
android:strokeWidth="1.0" />
<path
android:pathData="M73,56h1zM46,62h1z"
android:strokeColor="#a9000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,57h1z"
android:strokeColor="#3a000000"
android:strokeWidth="1.0" />
<path
android:pathData="M54,57h1z"
android:strokeColor="#ae000000"
android:strokeWidth="1.0" />
<path
android:pathData="M63,57h1zM73,60h1zM45,69h1zM59,72h1z"
android:strokeColor="#0c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M38,58h1zM48,61h1zM67,67h1z"
android:strokeColor="#e4000000"
android:strokeWidth="1.0" />
<path
android:pathData="M53,58h1z"
android:strokeColor="#49000000"
android:strokeWidth="1.0" />
<path
android:pathData="M73,58h1zM34,66h1z"
android:strokeColor="#65000000"
android:strokeWidth="1.0" />
<path
android:pathData="M48,59h1z"
android:strokeColor="#5d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M50,59h1z"
android:strokeColor="#db000000"
android:strokeWidth="1.0" />
<path
android:pathData="M51,59h1zM40,62h1zM52,62h1z"
android:strokeColor="#62000000"
android:strokeWidth="1.0" />
<path
android:pathData="M63,59h1z"
android:strokeColor="#a0000000"
android:strokeWidth="1.0" />
<path
android:pathData="M72,59h1z"
android:strokeColor="#e2000000"
android:strokeWidth="1.0" />
<path
android:pathData="M39,60h1zM71,62h1zM70,64h1z"
android:strokeColor="#b4000000"
android:strokeWidth="1.0" />
<path
android:pathData="M49,60h1z"
android:strokeColor="#b7000000"
android:strokeWidth="1.0" />
<path
android:pathData="M62,60h1z"
android:strokeColor="#67000000"
android:strokeWidth="1.0" />
<path
android:pathData="M40,61h1z"
android:strokeColor="#34000000"
android:strokeWidth="1.0" />
<path
android:pathData="M50,61h1z"
android:strokeColor="#b3000000"
android:strokeWidth="1.0" />
<path
android:pathData="M71,61h1z"
android:strokeColor="#e6000000"
android:strokeWidth="1.0" />
<path
android:pathData="M72,61h1z"
android:strokeColor="#3c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M51,62h1zM40,63h1z"
android:strokeColor="#be000000"
android:strokeWidth="1.0" />
<path
android:pathData="M58,62h1z"
android:strokeColor="#14000000"
android:strokeWidth="1.0" />
<path
android:pathData="M59,62h1z"
android:strokeColor="#4a000000"
android:strokeWidth="1.0" />
<path
android:pathData="M60,62h1z"
android:strokeColor="#a3000000"
android:strokeWidth="1.0" />
<path
android:pathData="M41,63h1zM44,68h1z"
android:strokeColor="#20000000"
android:strokeWidth="1.0" />
<path
android:pathData="M44,63h1z"
android:strokeColor="#85000000"
android:strokeWidth="1.0" />
<path
android:pathData="M45,63h1z"
android:strokeColor="#da000000"
android:strokeWidth="1.0" />
<path
android:pathData="M57,63h1z"
android:strokeColor="#9c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M71,63h1zM50,71h1z"
android:strokeColor="#51000000"
android:strokeWidth="1.0" />
<path
android:pathData="M33,64h1zM52,71h1z"
android:strokeColor="#ad000000"
android:strokeWidth="1.0" />
<path
android:pathData="M41,64h1z"
android:strokeColor="#9d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M69,65h1z"
android:strokeColor="#bc000000"
android:strokeWidth="1.0" />
<path
android:pathData="M35,66h1zM55,71h1zM57,71h1z"
android:strokeColor="#e8000000"
android:strokeWidth="1.0" />
<path
android:pathData="M39,66h1z"
android:strokeColor="#58000000"
android:strokeWidth="1.0" />
<path
android:pathData="M42,66h1zM58,72h1z"
android:strokeColor="#1c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M43,66h1z"
android:strokeColor="#c8000000"
android:strokeWidth="1.0" />
<path
android:pathData="M35,67h1z"
android:strokeColor="#4b000000"
android:strokeWidth="1.0" />
<path
android:pathData="M36,67h1z"
android:strokeColor="#84000000"
android:strokeWidth="1.0" />
<path
android:pathData="M37,67h1z"
android:strokeColor="#5a000000"
android:strokeWidth="1.0" />
<path
android:pathData="M45,68h1z"
android:strokeColor="#aa000000"
android:strokeWidth="1.0" />
<path
android:pathData="M66,68h1z"
android:strokeColor="#b1000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,70h1z"
android:strokeColor="#28000000"
android:strokeWidth="1.0" />
<path
android:pathData="M62,70h1z"
android:strokeColor="#e9000000"
android:strokeWidth="1.0" />
<path
android:pathData="M51,71h1z"
android:strokeColor="#8c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M58,71h1z"
android:strokeColor="#d8000000"
android:strokeWidth="1.0" />
<path
android:pathData="M59,71h1z"
android:strokeColor="#bb000000"
android:strokeWidth="1.0" />
<path
android:pathData="M60,71h1z"
android:strokeColor="#8e000000"
android:strokeWidth="1.0" />
<path
android:pathData="M61,71h1z"
android:strokeColor="#54000000"
android:strokeWidth="1.0" />
<path
android:pathData="M62,71h1z"
android:strokeColor="#38000000"
android:strokeWidth="1.0" />
<path
android:pathData="M53,72h1z"
android:strokeColor="#19000000"
android:strokeWidth="1.0" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -0,0 +1,788 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108.0dp"
android:height="108.0dp"
android:viewportWidth="108.0"
android:viewportHeight="108.0">
<path
android:pathData="M0,0h108zM0,1h108zM0,2h108zM0,3h108zM0,4h108zM0,5h108zM0,6h108zM0,7h108zM0,8h108zM0,9h108zM0,10h108zM0,11h108zM0,12h108zM0,13h108zM0,14h108zM0,15h108zM0,16h108zM0,17h108zM0,18h108zM0,19h108zM0,20h108zM0,21h108zM0,22h108zM0,23h108zM0,24h108zM0,25h108zM0,26h108zM0,27h108zM0,28h108zM0,29h108zM0,30h108zM0,31h108zM0,32h108zM0,33h108zM0,34h108zM0,35h108zM0,36h53zM60,36h48zM0,37h48zM64,37h44zM0,38h47zM65,38h43zM0,39h45zM67,39h41zM0,40h44zM69,40h39zM0,41h34zM39,41h4zM69,41h39zM0,42h33zM41,42h1zM70,42h38zM0,43h33zM71,43h37zM0,44h32zM72,44h36zM0,45h32zM72,45h36zM0,46h32zM42,46h2zM73,46h35zM0,47h32zM41,47h4zM54,47h4zM73,47h35zM0,48h32zM41,48h6zM51,48h10zM74,48h34zM0,49h32zM41,49h6zM53,49h9zM74,49h34zM0,50h32zM40,50h7zM55,50h8zM74,50h34zM0,51h32zM40,51h7zM56,51h7zM75,51h33zM0,52h32zM39,52h7zM57,52h6zM75,52h33zM0,53h32zM39,53h7zM57,53h7zM75,53h33zM0,54h32zM39,54h7zM58,54h6zM75,54h33zM0,55h32zM39,55h7zM58,55h6zM75,55h33zM0,56h32zM39,56h7zM57,56h6zM75,56h33zM0,57h32zM40,57h7zM57,57h6zM75,57h33zM0,58h32zM40,58h7zM56,58h7zM74,58h34zM0,59h32zM41,59h6zM53,59h9zM74,59h34zM0,60h32zM41,60h7zM52,60h9zM74,60h34zM0,61h32zM41,61h4zM53,61h6zM73,61h35zM0,62h32zM41,62h3zM55,62h1zM73,62h35zM0,63h32zM73,63h35zM0,64h32zM72,64h36zM0,65h33zM72,65h36zM0,66h33zM71,66h37zM0,67h34zM39,67h3zM70,67h38zM0,68h35zM38,68h5zM69,68h39zM0,69h45zM68,69h40zM0,70h47zM66,70h42zM0,71h48zM64,71h44zM0,72h51zM61,72h47zM0,73h108zM0,74h108zM0,75h108zM0,76h108zM0,77h108zM0,78h108zM0,79h108zM0,80h108zM0,81h108zM0,82h108zM0,83h108zM0,84h108zM0,85h108zM0,86h108zM0,87h108zM0,88h108zM0,89h108zM0,90h108zM0,91h108zM0,92h108zM0,93h108zM0,94h108zM0,95h108zM0,96h108zM0,97h108zM0,98h108zM0,99h108zM0,100h108zM0,101h108zM0,102h108zM0,103h108zM0,104h108zM0,105h108zM0,106h108zM0,107h108z"
android:strokeColor="#00000000"
android:strokeWidth="1.0" />
<path
android:pathData="M53,36h1zM73,48h1zM47,50h1zM54,50h1zM74,52h1zM74,53h1zM51,60h1z"
android:strokeColor="#05000000"
android:strokeWidth="1.0" />
<path
android:pathData="M54,36h1zM57,36h1zM46,54h1zM56,56h1zM47,58h1zM52,72h1z"
android:strokeColor="#09000000"
android:strokeWidth="1.0" />
<path
android:pathData="M55,36h1zM55,46h1zM52,47h1zM54,58h1zM52,59h1zM38,67h1z"
android:strokeColor="#10000000"
android:strokeWidth="1.0" />
<path
android:pathData="M56,36h1zM49,37h1zM44,46h1zM40,48h1zM71,64h1zM66,69h1zM63,71h1z"
android:strokeColor="#0f000000"
android:strokeWidth="1.0" />
<path
android:pathData="M58,36h1zM64,38h1zM66,39h1zM33,42h1zM71,44h1zM72,46h1zM47,48h1zM40,49h1zM54,62h1zM36,68h1z"
android:strokeColor="#04000000"
android:strokeWidth="1.0" />
<path
android:pathData="M59,36h1zM68,40h1zM34,41h1zM38,41h1zM53,47h1zM58,47h1zM47,49h1zM46,52h1zM57,55h1zM39,57h1zM56,57h1zM45,61h1zM59,61h1zM56,62h1zM72,63h1zM71,65h1zM70,66h1zM42,67h1zM69,67h1zM67,69h1zM65,70h1zM51,72h1z"
android:strokeColor="#01000000"
android:strokeWidth="1.0" />
<path
android:pathData="M48,37h1zM63,37h1zM45,39h1zM32,44h1zM41,46h1zM52,49h1zM56,52h1zM63,56h1zM74,57h1zM52,61h1zM57,62h1zM41,66h1z"
android:strokeColor="#03000000"
android:strokeWidth="1.0" />
<path
android:pathData="M50,37h1zM39,58h1zM54,72h1z"
android:strokeColor="#29000000"
android:strokeWidth="1.0" />
<path
android:pathData="M51,37h1zM50,60h1z"
android:strokeColor="#57000000"
android:strokeWidth="1.0" />
<path
android:pathData="M52,37h1zM43,45h1z"
android:strokeColor="#6f000000"
android:strokeWidth="1.0" />
<path
android:pathData="M53,37h1zM43,42h1zM52,46h1zM73,52h1zM38,57h1z"
android:strokeColor="#9f000000"
android:strokeWidth="1.0" />
<path
android:pathData="M54,37h1zM67,41h1z"
android:strokeColor="#b9000000"
android:strokeWidth="1.0" />
<path
android:pathData="M55,37h1zM35,42h1zM63,49h1zM52,58h1z"
android:strokeColor="#c1000000"
android:strokeWidth="1.0" />
<path
android:pathData="M56,37h1zM42,44h1zM73,55h1zM68,66h1z"
android:strokeColor="#c2000000"
android:strokeWidth="1.0" />
<path
android:pathData="M57,37h1zM49,38h1zM63,70h1z"
android:strokeColor="#b8000000"
android:strokeWidth="1.0" />
<path
android:pathData="M58,37h1z"
android:strokeColor="#98000000"
android:strokeWidth="1.0" />
<path
android:pathData="M59,37h1z"
android:strokeColor="#7c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M60,37h1z"
android:strokeColor="#59000000"
android:strokeWidth="1.0" />
<path
android:pathData="M61,37h1zM49,71h1zM57,72h1z"
android:strokeColor="#30000000"
android:strokeWidth="1.0" />
<path
android:pathData="M62,37h1zM37,41h1zM60,61h1z"
android:strokeColor="#15000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,38h1zM40,42h1zM45,47h1zM46,53h1zM32,64h1z"
android:strokeColor="#07000000"
android:strokeWidth="1.0" />
<path
android:pathData="M48,38h1z"
android:strokeColor="#69000000"
android:strokeWidth="1.0" />
<path
android:pathData="M50,38h1zM43,64h1z"
android:strokeColor="#ed000000"
android:strokeWidth="1.0" />
<path
android:pathData="M51,38h1zM48,47h1zM39,65h1z"
android:strokeColor="#f6000000"
android:strokeWidth="1.0" />
<path
android:pathData="M52,38h1zM59,38h1zM64,40h1zM69,44h1zM59,45h1zM49,47h1zM48,52h1zM37,55h1zM55,55h1zM54,56h1zM36,66h1zM45,67h1zM50,70h1z"
android:strokeColor="#fb000000"
android:strokeWidth="1.0" />
<path
android:pathData="M53,38h1zM58,38h1zM35,43h1zM44,43h1zM34,44h1zM39,44h1zM44,44h1zM39,45h1zM46,45h1zM51,45h1zM50,46h1zM62,46h1zM70,47h1zM64,49h1zM71,49h1zM37,51h1zM52,51h1zM37,52h1zM53,52h1zM37,56h1zM53,56h1zM37,57h1zM71,59h1zM70,61h1zM62,62h1zM39,63h1zM34,64h1zM39,64h1zM54,64h1zM56,64h2zM44,65h1zM67,66h1zM66,67h1zM64,68h1zM63,69h1zM52,70h1z"
android:strokeColor="#fe000000"
android:strokeWidth="1.0" />
<path
android:pathData="M54,38h4zM49,39h14zM48,40h16zM46,41h20zM45,42h22zM36,43h2zM45,43h23zM35,44h4zM45,44h24zM34,45h5zM47,45h4zM60,45h10zM34,46h5zM48,46h2zM63,46h7zM34,47h5zM63,47h7zM34,48h5zM64,48h7zM34,49h5zM65,49h6zM34,50h4zM65,50h7zM34,51h3zM49,51h3zM65,51h8zM34,52h3zM49,52h4zM65,52h8zM34,53h3zM48,53h7zM66,53h7zM34,54h3zM48,54h7zM66,54h7zM34,55h3zM48,55h7zM66,55h7zM34,56h3zM49,56h4zM65,56h8zM34,57h3zM49,57h4zM65,57h8zM34,58h4zM50,58h1zM65,58h7zM34,59h5zM65,59h6zM34,60h5zM64,60h7zM34,61h5zM63,61h7zM34,62h5zM48,62h2zM63,62h7zM34,63h5zM47,63h4zM61,63h9zM35,64h4zM45,64h9zM55,64h1zM58,64h11zM35,65h4zM45,65h23zM45,66h22zM46,67h20zM48,68h16zM48,69h15zM53,70h7z"
android:strokeColor="#ff000000"
android:strokeWidth="1.0" />
<path
android:pathData="M60,38h1zM47,40h1zM66,41h1zM43,44h1zM39,46h1zM64,50h1zM65,53h1zM60,63h1zM40,64h1z"
android:strokeColor="#fa000000"
android:strokeWidth="1.0" />
<path
android:pathData="M61,38h1zM49,59h1zM39,61h1zM59,63h1z"
android:strokeColor="#ec000000"
android:strokeWidth="1.0" />
<path
android:pathData="M62,38h1z"
android:strokeColor="#c5000000"
android:strokeWidth="1.0" />
<path
android:pathData="M63,38h1zM39,59h1zM65,69h1z"
android:strokeColor="#6d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M46,39h1zM39,42h1zM64,70h1z"
android:strokeColor="#2e000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,39h1zM42,43h1z"
android:strokeColor="#86000000"
android:strokeWidth="1.0" />
<path
android:pathData="M48,39h1zM63,39h1zM45,45h1zM62,61h1zM37,66h1z"
android:strokeColor="#f1000000"
android:strokeWidth="1.0" />
<path
android:pathData="M64,39h1zM40,43h1zM33,44h1z"
android:strokeColor="#97000000"
android:strokeWidth="1.0" />
<path
android:pathData="M65,39h1zM55,72h1z"
android:strokeColor="#33000000"
android:strokeWidth="1.0" />
<path
android:pathData="M44,40h1zM32,45h1zM59,47h1zM39,51h1zM40,60h1zM32,63h1z"
android:strokeColor="#0a000000"
android:strokeWidth="1.0" />
<path
android:pathData="M45,40h1z"
android:strokeColor="#80000000"
android:strokeWidth="1.0" />
<path
android:pathData="M46,40h1zM50,47h1zM53,51h1z"
android:strokeColor="#dd000000"
android:strokeWidth="1.0" />
<path
android:pathData="M65,40h1z"
android:strokeColor="#e5000000"
android:strokeWidth="1.0" />
<path
android:pathData="M66,40h1z"
android:strokeColor="#82000000"
android:strokeWidth="1.0" />
<path
android:pathData="M67,40h1z"
android:strokeColor="#1a000000"
android:strokeWidth="1.0" />
<path
android:pathData="M35,41h1zM69,42h1zM48,71h1z"
android:strokeColor="#0e000000"
android:strokeWidth="1.0" />
<path
android:pathData="M36,41h1z"
android:strokeColor="#26000000"
android:strokeWidth="1.0" />
<path
android:pathData="M43,41h1zM46,61h1z"
android:strokeColor="#12000000"
android:strokeWidth="1.0" />
<path
android:pathData="M44,41h1z"
android:strokeColor="#8b000000"
android:strokeWidth="1.0" />
<path
android:pathData="M45,41h1zM72,50h1z"
android:strokeColor="#f3000000"
android:strokeWidth="1.0" />
<path
android:pathData="M68,41h1zM53,50h1z"
android:strokeColor="#22000000"
android:strokeWidth="1.0" />
<path
android:pathData="M34,42h1z"
android:strokeColor="#3e000000"
android:strokeWidth="1.0" />
<path
android:pathData="M36,42h1zM39,43h1zM49,70h1zM54,71h1z"
android:strokeColor="#e3000000"
android:strokeWidth="1.0" />
<path
android:pathData="M37,42h1zM44,67h1z"
android:strokeColor="#cc000000"
android:strokeWidth="1.0" />
<path
android:pathData="M38,42h1zM64,53h1z"
android:strokeColor="#6e000000"
android:strokeWidth="1.0" />
<path
android:pathData="M42,42h1zM56,46h1zM72,62h1z"
android:strokeColor="#0d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M44,42h1zM65,55h1zM39,62h1zM61,62h1zM70,63h1z"
android:strokeColor="#f7000000"
android:strokeWidth="1.0" />
<path
android:pathData="M67,42h1zM52,45h1zM70,46h1zM49,50h2zM37,53h1zM71,60h1zM46,63h1zM44,64h1zM43,65h1zM68,65h1zM51,70h1z"
android:strokeColor="#fc000000"
android:strokeWidth="1.0" />
<path
android:pathData="M68,42h1zM72,60h1z"
android:strokeColor="#a2000000"
android:strokeWidth="1.0" />
<path
android:pathData="M33,43h1zM43,63h1z"
android:strokeColor="#44000000"
android:strokeWidth="1.0" />
<path
android:pathData="M34,43h1zM54,45h1zM46,46h1zM49,49h1z"
android:strokeColor="#dc000000"
android:strokeWidth="1.0" />
<path
android:pathData="M38,43h1zM40,44h1zM62,47h1zM37,54h1zM72,58h1z"
android:strokeColor="#f9000000"
android:strokeWidth="1.0" />
<path
android:pathData="M41,43h1zM47,51h1z"
android:strokeColor="#2c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M43,43h1zM68,43h1zM71,48h1zM50,62h1zM65,68h1zM61,70h1z"
android:strokeColor="#f8000000"
android:strokeWidth="1.0" />
<path
android:pathData="M69,43h1zM62,48h1z"
android:strokeColor="#92000000"
android:strokeWidth="1.0" />
<path
android:pathData="M70,43h1zM40,66h1z"
android:strokeColor="#11000000"
android:strokeWidth="1.0" />
<path
android:pathData="M41,44h1z"
android:strokeColor="#b5000000"
android:strokeWidth="1.0" />
<path
android:pathData="M70,44h1z"
android:strokeColor="#79000000"
android:strokeWidth="1.0" />
<path
android:pathData="M33,45h1zM33,52h1zM33,56h1zM58,63h1z"
android:strokeColor="#c9000000"
android:strokeWidth="1.0" />
<path
android:pathData="M40,45h1zM51,50h1z"
android:strokeColor="#d7000000"
android:strokeWidth="1.0" />
<path
android:pathData="M41,45h1zM46,47h1zM45,62h1z"
android:strokeColor="#37000000"
android:strokeWidth="1.0" />
<path
android:pathData="M42,45h1z"
android:strokeColor="#16000000"
android:strokeWidth="1.0" />
<path
android:pathData="M44,45h1zM47,54h1z"
android:strokeColor="#b6000000"
android:strokeWidth="1.0" />
<path
android:pathData="M53,45h1zM34,65h1z"
android:strokeColor="#ee000000"
android:strokeWidth="1.0" />
<path
android:pathData="M55,45h1zM33,48h1zM33,60h1zM53,63h1z"
android:strokeColor="#cb000000"
android:strokeWidth="1.0" />
<path
android:pathData="M56,45h1zM33,51h1zM33,57h1z"
android:strokeColor="#c7000000"
android:strokeWidth="1.0" />
<path
android:pathData="M57,45h1z"
android:strokeColor="#d1000000"
android:strokeWidth="1.0" />
<path
android:pathData="M58,45h1zM56,71h1z"
android:strokeColor="#eb000000"
android:strokeWidth="1.0" />
<path
android:pathData="M70,45h1zM48,51h1z"
android:strokeColor="#ea000000"
android:strokeWidth="1.0" />
<path
android:pathData="M71,45h1zM70,65h1zM56,72h1z"
android:strokeColor="#36000000"
android:strokeWidth="1.0" />
<path
android:pathData="M32,46h1zM32,47h1zM32,48h1zM32,49h1zM32,50h1zM32,51h1zM32,52h1zM32,53h1zM32,54h1zM74,54h1zM32,55h1zM32,56h1zM32,57h1zM32,58h1zM32,59h1zM62,59h1zM32,60h1zM61,60h1zM32,61h1zM32,62h1zM33,66h1z"
android:strokeColor="#0b000000"
android:strokeWidth="1.0" />
<path
android:pathData="M33,46h1zM33,49h1zM33,54h1zM48,58h1zM33,59h1zM33,62h1z"
android:strokeColor="#c6000000"
android:strokeWidth="1.0" />
<path
android:pathData="M40,46h1z"
android:strokeColor="#77000000"
android:strokeWidth="1.0" />
<path
android:pathData="M45,46h1zM38,55h1z"
android:strokeColor="#6b000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,46h1zM61,46h1zM55,54h1zM48,56h1zM49,58h1zM64,59h1zM70,62h1zM51,63h1zM69,64h1zM44,66h1zM47,68h1zM60,70h1z"
android:strokeColor="#fd000000"
android:strokeWidth="1.0" />
<path
android:pathData="M51,46h1zM63,60h1z"
android:strokeColor="#e7000000"
android:strokeWidth="1.0" />
<path
android:pathData="M53,46h1zM64,54h1z"
android:strokeColor="#4e000000"
android:strokeWidth="1.0" />
<path
android:pathData="M54,46h1z"
android:strokeColor="#2a000000"
android:strokeWidth="1.0" />
<path
android:pathData="M57,46h1zM53,62h1z"
android:strokeColor="#1b000000"
android:strokeWidth="1.0" />
<path
android:pathData="M58,46h1zM63,58h1z"
android:strokeColor="#45000000"
android:strokeWidth="1.0" />
<path
android:pathData="M59,46h1z"
android:strokeColor="#95000000"
android:strokeWidth="1.0" />
<path
android:pathData="M60,46h1z"
android:strokeColor="#e0000000"
android:strokeWidth="1.0" />
<path
android:pathData="M71,46h1z"
android:strokeColor="#93000000"
android:strokeWidth="1.0" />
<path
android:pathData="M33,47h1zM33,61h1z"
android:strokeColor="#ca000000"
android:strokeWidth="1.0" />
<path
android:pathData="M39,47h1zM65,54h1zM51,58h1zM46,68h1z"
android:strokeColor="#f2000000"
android:strokeWidth="1.0" />
<path
android:pathData="M40,47h1zM55,57h1z"
android:strokeColor="#40000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,47h1z"
android:strokeColor="#a8000000"
android:strokeWidth="1.0" />
<path
android:pathData="M51,47h1zM68,67h1z"
android:strokeColor="#4c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M60,47h1zM50,48h1z"
android:strokeColor="#48000000"
android:strokeWidth="1.0" />
<path
android:pathData="M61,47h1zM64,69h1z"
android:strokeColor="#df000000"
android:strokeWidth="1.0" />
<path
android:pathData="M71,47h1zM53,71h1z"
android:strokeColor="#d4000000"
android:strokeWidth="1.0" />
<path
android:pathData="M72,47h1zM61,48h1zM73,59h1z"
android:strokeColor="#24000000"
android:strokeWidth="1.0" />
<path
android:pathData="M39,48h1zM40,65h1z"
android:strokeColor="#c4000000"
android:strokeWidth="1.0" />
<path
android:pathData="M48,48h1z"
android:strokeColor="#52000000"
android:strokeWidth="1.0" />
<path
android:pathData="M49,48h1zM64,57h1z"
android:strokeColor="#c3000000"
android:strokeWidth="1.0" />
<path
android:pathData="M63,48h1zM38,50h1zM53,57h1zM49,61h1z"
android:strokeColor="#f5000000"
android:strokeWidth="1.0" />
<path
android:pathData="M72,48h1z"
android:strokeColor="#7f000000"
android:strokeWidth="1.0" />
<path
android:pathData="M39,49h1zM42,64h1z"
android:strokeColor="#88000000"
android:strokeWidth="1.0" />
<path
android:pathData="M48,49h1zM67,68h1z"
android:strokeColor="#43000000"
android:strokeWidth="1.0" />
<path
android:pathData="M50,49h1zM42,65h1z"
android:strokeColor="#ac000000"
android:strokeWidth="1.0" />
<path
android:pathData="M51,49h1z"
android:strokeColor="#25000000"
android:strokeWidth="1.0" />
<path
android:pathData="M62,49h1zM51,61h1zM69,66h1z"
android:strokeColor="#1e000000"
android:strokeWidth="1.0" />
<path
android:pathData="M72,49h1zM33,50h1zM33,58h1z"
android:strokeColor="#ce000000"
android:strokeWidth="1.0" />
<path
android:pathData="M73,49h1z"
android:strokeColor="#17000000"
android:strokeWidth="1.0" />
<path
android:pathData="M39,50h1zM48,60h1zM43,67h1z"
android:strokeColor="#3d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M48,50h1zM48,70h1z"
android:strokeColor="#af000000"
android:strokeWidth="1.0" />
<path
android:pathData="M52,50h1zM73,57h1z"
android:strokeColor="#8d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M63,50h1zM47,61h1z"
android:strokeColor="#5c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M73,50h1z"
android:strokeColor="#4d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M38,51h1z"
android:strokeColor="#b2000000"
android:strokeWidth="1.0" />
<path
android:pathData="M54,51h1z"
android:strokeColor="#75000000"
android:strokeWidth="1.0" />
<path
android:pathData="M55,51h1z"
android:strokeColor="#18000000"
android:strokeWidth="1.0" />
<path
android:pathData="M63,51h1z"
android:strokeColor="#13000000"
android:strokeWidth="1.0" />
<path
android:pathData="M64,51h1z"
android:strokeColor="#d2000000"
android:strokeWidth="1.0" />
<path
android:pathData="M73,51h1z"
android:strokeColor="#81000000"
android:strokeWidth="1.0" />
<path
android:pathData="M74,51h1zM57,54h1zM46,56h1zM55,58h1zM40,59h1zM47,59h1zM44,62h1zM35,68h1zM37,68h1zM43,68h1zM60,72h1z"
android:strokeColor="#02000000"
android:strokeWidth="1.0" />
<path
android:pathData="M38,52h1z"
android:strokeColor="#94000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,52h1zM46,69h1z"
android:strokeColor="#5b000000"
android:strokeWidth="1.0" />
<path
android:pathData="M54,52h1zM48,57h1zM47,62h1z"
android:strokeColor="#f0000000"
android:strokeWidth="1.0" />
<path
android:pathData="M55,52h1z"
android:strokeColor="#7d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M63,52h1zM74,55h1zM74,56h1zM42,63h1zM34,67h1zM68,68h1z"
android:strokeColor="#06000000"
android:strokeWidth="1.0" />
<path
android:pathData="M64,52h1zM61,61h1zM54,63h1z"
android:strokeColor="#a1000000"
android:strokeWidth="1.0" />
<path
android:pathData="M33,53h1zM33,55h1zM33,63h1zM47,69h1z"
android:strokeColor="#cd000000"
android:strokeWidth="1.0" />
<path
android:pathData="M38,53h1z"
android:strokeColor="#78000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,53h1z"
android:strokeColor="#9b000000"
android:strokeWidth="1.0" />
<path
android:pathData="M55,53h1zM64,58h1zM52,63h1z"
android:strokeColor="#f4000000"
android:strokeWidth="1.0" />
<path
android:pathData="M56,53h1z"
android:strokeColor="#2d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M73,53h1z"
android:strokeColor="#bf000000"
android:strokeWidth="1.0" />
<path
android:pathData="M38,54h1zM33,65h1zM41,65h1z"
android:strokeColor="#61000000"
android:strokeWidth="1.0" />
<path
android:pathData="M56,54h1z"
android:strokeColor="#74000000"
android:strokeWidth="1.0" />
<path
android:pathData="M73,54h1zM38,66h1z"
android:strokeColor="#ba000000"
android:strokeWidth="1.0" />
<path
android:pathData="M46,55h1z"
android:strokeColor="#08000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,55h1z"
android:strokeColor="#a6000000"
android:strokeWidth="1.0" />
<path
android:pathData="M56,55h1z"
android:strokeColor="#47000000"
android:strokeWidth="1.0" />
<path
android:pathData="M64,55h1z"
android:strokeColor="#63000000"
android:strokeWidth="1.0" />
<path
android:pathData="M38,56h1zM64,56h1zM55,63h2z"
android:strokeColor="#90000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,56h1z"
android:strokeColor="#73000000"
android:strokeWidth="1.0" />
<path
android:pathData="M55,56h1z"
android:strokeColor="#b0000000"
android:strokeWidth="1.0" />
<path
android:pathData="M73,56h1zM46,62h1z"
android:strokeColor="#a9000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,57h1z"
android:strokeColor="#3a000000"
android:strokeWidth="1.0" />
<path
android:pathData="M54,57h1z"
android:strokeColor="#ae000000"
android:strokeWidth="1.0" />
<path
android:pathData="M63,57h1zM73,60h1zM45,69h1zM59,72h1z"
android:strokeColor="#0c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M38,58h1zM48,61h1zM67,67h1z"
android:strokeColor="#e4000000"
android:strokeWidth="1.0" />
<path
android:pathData="M53,58h1z"
android:strokeColor="#49000000"
android:strokeWidth="1.0" />
<path
android:pathData="M73,58h1zM34,66h1z"
android:strokeColor="#65000000"
android:strokeWidth="1.0" />
<path
android:pathData="M48,59h1z"
android:strokeColor="#5d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M50,59h1z"
android:strokeColor="#db000000"
android:strokeWidth="1.0" />
<path
android:pathData="M51,59h1zM40,62h1zM52,62h1z"
android:strokeColor="#62000000"
android:strokeWidth="1.0" />
<path
android:pathData="M63,59h1z"
android:strokeColor="#a0000000"
android:strokeWidth="1.0" />
<path
android:pathData="M72,59h1z"
android:strokeColor="#e2000000"
android:strokeWidth="1.0" />
<path
android:pathData="M39,60h1zM71,62h1zM70,64h1z"
android:strokeColor="#b4000000"
android:strokeWidth="1.0" />
<path
android:pathData="M49,60h1z"
android:strokeColor="#b7000000"
android:strokeWidth="1.0" />
<path
android:pathData="M62,60h1z"
android:strokeColor="#67000000"
android:strokeWidth="1.0" />
<path
android:pathData="M40,61h1z"
android:strokeColor="#34000000"
android:strokeWidth="1.0" />
<path
android:pathData="M50,61h1z"
android:strokeColor="#b3000000"
android:strokeWidth="1.0" />
<path
android:pathData="M71,61h1z"
android:strokeColor="#e6000000"
android:strokeWidth="1.0" />
<path
android:pathData="M72,61h1z"
android:strokeColor="#3c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M51,62h1zM40,63h1z"
android:strokeColor="#be000000"
android:strokeWidth="1.0" />
<path
android:pathData="M58,62h1z"
android:strokeColor="#14000000"
android:strokeWidth="1.0" />
<path
android:pathData="M59,62h1z"
android:strokeColor="#4a000000"
android:strokeWidth="1.0" />
<path
android:pathData="M60,62h1z"
android:strokeColor="#a3000000"
android:strokeWidth="1.0" />
<path
android:pathData="M41,63h1zM44,68h1z"
android:strokeColor="#20000000"
android:strokeWidth="1.0" />
<path
android:pathData="M44,63h1z"
android:strokeColor="#85000000"
android:strokeWidth="1.0" />
<path
android:pathData="M45,63h1z"
android:strokeColor="#da000000"
android:strokeWidth="1.0" />
<path
android:pathData="M57,63h1z"
android:strokeColor="#9c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M71,63h1zM50,71h1z"
android:strokeColor="#51000000"
android:strokeWidth="1.0" />
<path
android:pathData="M33,64h1zM52,71h1z"
android:strokeColor="#ad000000"
android:strokeWidth="1.0" />
<path
android:pathData="M41,64h1z"
android:strokeColor="#9d000000"
android:strokeWidth="1.0" />
<path
android:pathData="M69,65h1z"
android:strokeColor="#bc000000"
android:strokeWidth="1.0" />
<path
android:pathData="M35,66h1zM55,71h1zM57,71h1z"
android:strokeColor="#e8000000"
android:strokeWidth="1.0" />
<path
android:pathData="M39,66h1z"
android:strokeColor="#58000000"
android:strokeWidth="1.0" />
<path
android:pathData="M42,66h1zM58,72h1z"
android:strokeColor="#1c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M43,66h1z"
android:strokeColor="#c8000000"
android:strokeWidth="1.0" />
<path
android:pathData="M35,67h1z"
android:strokeColor="#4b000000"
android:strokeWidth="1.0" />
<path
android:pathData="M36,67h1z"
android:strokeColor="#84000000"
android:strokeWidth="1.0" />
<path
android:pathData="M37,67h1z"
android:strokeColor="#5a000000"
android:strokeWidth="1.0" />
<path
android:pathData="M45,68h1z"
android:strokeColor="#aa000000"
android:strokeWidth="1.0" />
<path
android:pathData="M66,68h1z"
android:strokeColor="#b1000000"
android:strokeWidth="1.0" />
<path
android:pathData="M47,70h1z"
android:strokeColor="#28000000"
android:strokeWidth="1.0" />
<path
android:pathData="M62,70h1z"
android:strokeColor="#e9000000"
android:strokeWidth="1.0" />
<path
android:pathData="M51,71h1z"
android:strokeColor="#8c000000"
android:strokeWidth="1.0" />
<path
android:pathData="M58,71h1z"
android:strokeColor="#d8000000"
android:strokeWidth="1.0" />
<path
android:pathData="M59,71h1z"
android:strokeColor="#bb000000"
android:strokeWidth="1.0" />
<path
android:pathData="M60,71h1z"
android:strokeColor="#8e000000"
android:strokeWidth="1.0" />
<path
android:pathData="M61,71h1z"
android:strokeColor="#54000000"
android:strokeWidth="1.0" />
<path
android:pathData="M62,71h1z"
android:strokeColor="#38000000"
android:strokeWidth="1.0" />
<path
android:pathData="M53,72h1z"
android:strokeColor="#19000000"
android:strokeWidth="1.0" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

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