fix(Patch options): some patch options in RVX Manager are marked with custom value

This commit is contained in:
inotia00 2024-06-12 21:45:05 +09:00
parent f7fe7e8caf
commit 8dc8bffea7
15 changed files with 473 additions and 432 deletions

View File

@ -9,6 +9,7 @@ import app.revanced.util.ResourceGroup
import app.revanced.util.Utils.trimIndentMultiline
import app.revanced.util.copyResources
import app.revanced.util.patch.BaseResourcePatch
import app.revanced.util.underBarOrThrow
import java.io.File
import java.nio.file.Files
@ -18,13 +19,13 @@ object CustomBrandingIconPatch : BaseResourcePatch(
description = "Changes the YouTube Music app icon to the icon specified in options.json.",
compatiblePackages = COMPATIBLE_PACKAGE
) {
private const val DEFAULT_ICON_KEY = "Revancify Blue"
private const val DEFAULT_ICON = "revancify_blue"
private val availableIcon = mapOf(
"AFN Blue" to "afn_blue",
"AFN Red" to "afn_red",
"MMT" to "mmt",
DEFAULT_ICON_KEY to "revancify_blue",
"Revancify Blue" to DEFAULT_ICON,
"Revancify Red" to "revancify_red",
"YouTube Music" to "youtube_music"
)
@ -93,9 +94,9 @@ object CustomBrandingIconPatch : BaseResourcePatch(
)
}
private val AppIcon by stringPatchOption(
private val AppIcon = stringPatchOption(
key = "AppIcon",
default = DEFAULT_ICON_KEY,
default = DEFAULT_ICON,
values = availableIcon,
title = "App icon",
description = """
@ -129,70 +130,72 @@ object CustomBrandingIconPatch : BaseResourcePatch(
)
override fun execute(context: ResourceContext) {
AppIcon?.let { appIcon ->
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)) {
launcherIconResourceGroups.let { resourceGroups ->
try {
val path = File(appIcon)
val resourceDirectory = context["res"]
// Check patch options first.
val appIcon = AppIcon
.underBarOrThrow()
resourceGroups.forEach { group ->
val fromDirectory = path.resolve(group.resourceDirectoryName)
val toDirectory = resourceDirectory.resolve(group.resourceDirectoryName)
val appIconResourcePath = "music/branding/$appIcon"
group.resources.forEach { iconFileName ->
Files.write(
toDirectory.resolve(iconFileName).toPath(),
fromDirectory.resolve(iconFileName).readBytes()
)
}
// Check if a custom path is used in the patch options.
if (!availableIcon.containsValue(appIcon)) {
launcherIconResourceGroups.let { resourceGroups ->
try {
val path = File(appIcon)
val resourceDirectory = context["res"]
resourceGroups.forEach { group ->
val fromDirectory = path.resolve(group.resourceDirectoryName)
val toDirectory = resourceDirectory.resolve(group.resourceDirectoryName)
group.resources.forEach { iconFileName ->
Files.write(
toDirectory.resolve(iconFileName).toPath(),
fromDirectory.resolve(iconFileName).readBytes()
)
}
} catch (_: Exception) {
// Exception is thrown if an invalid path is used in the patch option.
throw PatchException("Invalid app icon path: $appIcon")
}
} catch (_: Exception) {
// Exception is thrown if an invalid path is used in the patch option.
throw PatchException("Invalid app icon path: $appIcon")
}
} else {
}
} else {
// Change launcher icon.
launcherIconResourceGroups.let { resourceGroups ->
// Change launcher icon.
launcherIconResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
context.copyResources("$appIconResourcePath/launcher", it)
}
}
// Change monochrome icon.
arrayOf(
ResourceGroup(
"drawable",
"ic_app_icons_themed_youtube_music.xml"
)
).forEach { resourceGroup ->
context.copyResources("$appIconResourcePath/monochrome", resourceGroup)
}
// Change header.
if (ChangeHeader == true) {
headerIconResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
context.copyResources("$appIconResourcePath/launcher", it)
}
}
// Change monochrome icon.
arrayOf(
ResourceGroup(
"drawable",
"ic_app_icons_themed_youtube_music.xml"
)
).forEach { 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)
}
context.copyResources("$appIconResourcePath/header", it)
}
}
}
} ?: throw PatchException("Invalid app icon path.")
// Change splash icon.
if (ChangeSplashIcon == true) {
splashIconResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
context.copyResources("$appIconResourcePath/splash", it)
}
}
}
}
}
}

View File

@ -1,11 +1,11 @@
package app.revanced.patches.music.layout.branding.name
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.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.shared.elements.StringsElementsUtils.removeStringsElements
import app.revanced.util.patch.BaseResourcePatch
import app.revanced.util.valueOrThrow
@Suppress("DEPRECATION", "unused")
object CustomBrandingNamePatch : BaseResourcePatch(
@ -16,7 +16,7 @@ object CustomBrandingNamePatch : BaseResourcePatch(
private const val APP_NAME_NOTIFICATION = "ReVanced Extended Music"
private const val APP_NAME_LAUNCHER = "RVX Music"
private val AppNameNotification by stringPatchOption(
private val AppNameNotification = stringPatchOption(
key = "AppNameNotification",
default = APP_NAME_LAUNCHER,
values = mapOf(
@ -30,7 +30,7 @@ object CustomBrandingNamePatch : BaseResourcePatch(
required = true
)
private val AppNameLauncher by stringPatchOption(
private val AppNameLauncher = stringPatchOption(
key = "AppNameLauncher",
default = APP_NAME_LAUNCHER,
values = mapOf(
@ -46,29 +46,31 @@ object CustomBrandingNamePatch : BaseResourcePatch(
override fun execute(context: ResourceContext) {
// Check patch options first.
val notificationName = AppNameNotification
.valueOrThrow()
val launcherName = AppNameLauncher
.valueOrThrow()
context.removeStringsElements(
arrayOf("app_launcher_name", "app_name")
)
AppNameNotification?.let { notificationName ->
AppNameLauncher?.let { launcherName ->
context.xmlEditor["res/values/strings.xml"].use { editor ->
val document = editor.file
context.xmlEditor["res/values/strings.xml"].use { editor ->
val document = editor.file
mapOf(
"app_name" to notificationName,
"app_launcher_name" to launcherName
).forEach { (k, v) ->
val stringElement = document.createElement("string")
mapOf(
"app_name" to notificationName,
"app_launcher_name" to launcherName
).forEach { (k, v) ->
val stringElement = document.createElement("string")
stringElement.setAttribute("name", k)
stringElement.textContent = v
stringElement.setAttribute("name", k)
stringElement.textContent = v
document.getElementsByTagName("resources").item(0)
.appendChild(stringElement)
}
}
} ?: throw PatchException("Invalid launcher name.")
} ?: throw PatchException("Invalid notification name.")
document.getElementsByTagName("resources").item(0)
.appendChild(stringElement)
}
}
}
}

View File

@ -12,6 +12,7 @@ import app.revanced.patches.music.utils.settings.ResourceUtils.sortPreferenceCat
import app.revanced.patches.shared.elements.StringsElementsUtils.removeStringsElements
import app.revanced.util.copyXmlNode
import app.revanced.util.patch.BaseResourcePatch
import app.revanced.util.valueOrThrow
import org.w3c.dom.Element
import java.io.Closeable
import java.util.concurrent.Executors
@ -30,7 +31,7 @@ object SettingsPatch : BaseResourcePatch(
), Closeable {
private const val DEFAULT_NAME = "ReVanced Extended"
private val RVXSettingsMenuName by stringPatchOption(
private val RVXSettingsMenuName = stringPatchOption(
key = "RVXSettingsMenuName",
default = DEFAULT_NAME,
title = "RVX settings menu name",
@ -38,12 +39,20 @@ object SettingsPatch : BaseResourcePatch(
required = true
)
private lateinit var customName: String
lateinit var contexts: ResourceContext
internal var upward0636 = false
internal var upward0642 = false
override fun execute(context: ResourceContext) {
/**
* check patch options
*/
customName = RVXSettingsMenuName
.valueOrThrow()
/**
* set resource context
*/
@ -193,35 +202,38 @@ object SettingsPatch : BaseResourcePatch(
* 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
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")
mapOf(
"revanced_extended_settings_title" to customName
).forEach { (k, v) ->
val stringElement = document.createElement("string")
stringElement.setAttribute("name", k)
stringElement.textContent = v
stringElement.setAttribute("name", k)
stringElement.textContent = v
document.getElementsByTagName("resources").item(0)
.appendChild(stringElement)
}
document.getElementsByTagName("resources").item(0)
.appendChild(stringElement)
}
}
} ?: println("WARNING: Invalid RVX settings menu name. RVX settings menu name does not change.")
}
/**
* add import export settings
*/
addPreferenceWithIntent(
CategoryType.MISC,
"revanced_extended_settings_import_export"
)
/**
* sort preference
*/
CategoryType.entries.sorted().forEach {
contexts.sortPreferenceCategory(it.value)
}

View File

@ -4,6 +4,7 @@ import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.util.patch.BaseResourcePatch
import app.revanced.util.valueOrThrow
import java.io.FileWriter
import java.nio.file.Files
@ -17,7 +18,7 @@ object CustomBrandingNamePatch : BaseResourcePatch(
private const val ORIGINAL_APP_NAME = "Reddit"
private const val APP_NAME = "RVX Reddit"
private val AppName by stringPatchOption(
private val AppName = stringPatchOption(
key = "AppName",
default = ORIGINAL_APP_NAME,
values = mapOf(
@ -30,44 +31,41 @@ object CustomBrandingNamePatch : BaseResourcePatch(
)
override fun execute(context: ResourceContext) {
val appName = if (AppName != null) {
AppName!!
} else {
println("WARNING: Invalid app name. Does not apply patches.")
ORIGINAL_APP_NAME
val appName = AppName
.valueOrThrow()
if (appName == ORIGINAL_APP_NAME) {
println("INFO: App name will remain unchanged as it matches the original.")
return
}
if (appName != ORIGINAL_APP_NAME) {
val resDirectory = context["res"]
val resDirectory = context["res"]
val valuesV24Directory = resDirectory.resolve("values-v24")
if (!valuesV24Directory.isDirectory)
Files.createDirectories(valuesV24Directory.toPath())
val valuesV24Directory = resDirectory.resolve("values-v24")
if (!valuesV24Directory.isDirectory)
Files.createDirectories(valuesV24Directory.toPath())
val stringsXml = valuesV24Directory.resolve("strings.xml")
val stringsXml = valuesV24Directory.resolve("strings.xml")
if (!stringsXml.exists()) {
FileWriter(stringsXml).use {
it.write("<?xml version=\"1.0\" encoding=\"utf-8\"?><resources></resources>")
}
if (!stringsXml.exists()) {
FileWriter(stringsXml).use {
it.write("<?xml version=\"1.0\" encoding=\"utf-8\"?><resources></resources>")
}
}
context.xmlEditor["res/values-v24/strings.xml"].use { editor ->
val document = editor.file
context.xmlEditor["res/values-v24/strings.xml"].use { editor ->
val document = editor.file
mapOf(
"app_name" to appName
).forEach { (k, v) ->
val stringElement = document.createElement("string")
mapOf(
"app_name" to appName
).forEach { (k, v) ->
val stringElement = document.createElement("string")
stringElement.setAttribute("name", k)
stringElement.textContent = v
stringElement.setAttribute("name", k)
stringElement.textContent = v
document.getElementsByTagName("resources").item(0).appendChild(stringElement)
}
document.getElementsByTagName("resources").item(0).appendChild(stringElement)
}
} else {
println("INFO: App name will remain unchanged as it matches the original.")
}
}
}

View File

@ -4,6 +4,7 @@ import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.util.patch.BaseResourcePatch
import app.revanced.util.valueOrThrow
import org.w3c.dom.Element
import java.io.Closeable
@ -21,7 +22,7 @@ object ChangePackageNamePatch : BaseResourcePatch(
private lateinit var context: ResourceContext
private var redditPackageName = PACKAGE_NAME_REDDIT
private val PackageNameReddit by stringPatchOption(
private val PackageNameReddit = stringPatchOption(
key = "PackageNameReddit",
default = PACKAGE_NAME_REDDIT,
values = mapOf(
@ -37,26 +38,25 @@ object ChangePackageNamePatch : BaseResourcePatch(
override fun execute(context: ResourceContext) {
this.context = context
if (PackageNameReddit != null) {
redditPackageName = PackageNameReddit!!
} else {
println("WARNING: Invalid package name. Does not apply patches.")
redditPackageName = PackageNameReddit
.valueOrThrow()
if (redditPackageName == PACKAGE_NAME_REDDIT) {
println("INFO: Package name will remain unchanged as it matches the original.")
return
}
if (redditPackageName != PACKAGE_NAME_REDDIT) {
// Ensure device runs Android.
try {
// RVX Manager
// ====
// For some reason, in Android AAPT2, a compilation error occurs when changing the [strings.xml] of the Reddit
// This only affects RVX Manager, and has not yet found a valid workaround
Class.forName("android.os.Environment")
} catch (_: ClassNotFoundException) {
// CLI
context.replacePackageName(redditPackageName)
}
} else {
println("INFO: Package name will remain unchanged as it matches the original.")
// Ensure device runs Android.
try {
// RVX Manager
// ====
// For some reason, in Android AAPT2, a compilation error occurs when changing the [strings.xml] of the Reddit
// This only affects RVX Manager, and has not yet found a valid workaround
Class.forName("android.os.Environment")
return
} catch (_: ClassNotFoundException) {
// CLI
context.replacePackageName(redditPackageName)
}
}

View File

@ -6,6 +6,7 @@ import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatc
import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.reddit.utils.integrations.IntegrationsPatch
import app.revanced.util.patch.BaseResourcePatch
import app.revanced.util.valueOrThrow
import kotlin.io.path.exists
@Suppress("DEPRECATION")
@ -21,7 +22,7 @@ object SettingsPatch : BaseResourcePatch(
) {
private const val DEFAULT_NAME = "ReVanced Extended"
private val RVXSettingsMenuName by stringPatchOption(
private val RVXSettingsMenuName = stringPatchOption(
key = "RVXSettingsMenuName",
default = DEFAULT_NAME,
title = "RVX settings menu name",
@ -32,11 +33,8 @@ object SettingsPatch : BaseResourcePatch(
/**
* Replace settings icon and label
*/
var settingsLabel = DEFAULT_NAME
if (!RVXSettingsMenuName.isNullOrEmpty())
settingsLabel = RVXSettingsMenuName!!
val settingsLabel = RVXSettingsMenuName
.valueOrThrow()
arrayOf("preferences", "preferences_logged_in").forEach { targetXML ->
val resDirectory = context["res"]

View File

@ -1,12 +1,12 @@
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.lowerCaseOrThrow
import app.revanced.util.patch.BaseResourcePatch
@Suppress("unused")
@ -16,15 +16,15 @@ object ShortsActionButtonsPatch : BaseResourcePatch(
dependencies = setOf(SettingsPatch::class),
compatiblePackages = COMPATIBLE_PACKAGE
) {
private const val DEFAULT_ICON_KEY = "Round"
private const val DEFAULT_ICON = "round"
private val IconType by stringPatchOption(
private val IconType = stringPatchOption(
key = "IconType",
default = DEFAULT_ICON_KEY,
default = DEFAULT_ICON,
values = mapOf(
"Outline" to "outline",
"OutlineCircle" to "outlinecircle",
DEFAULT_ICON_KEY to "round"
"Round" to DEFAULT_ICON
),
title = "Shorts icon style ",
description = "The style of the icons for the action buttons in the Shorts player.",
@ -32,53 +32,54 @@ object ShortsActionButtonsPatch : BaseResourcePatch(
)
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",
// Check patch options first.
val iconType = IconType
.lowerCaseOrThrow()
// 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"
)
arrayOf(
"xxxhdpi",
"xxhdpi",
"xhdpi",
"hdpi",
"mdpi"
).forEach { dpi ->
context.copyResources(
"youtube/shorts/actionbuttons/$iconType",
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.")
context.copyResources(
"youtube/shorts/actionbuttons/shared",
ResourceGroup(
"drawable",
"reel_camera_bold_24dp.xml",
"reel_more_vertical_bold_24dp.xml",
"reel_search_bold_24dp.xml"
)
)
SettingsPatch.updatePatchStatus(this)
}

View File

@ -1,7 +1,6 @@
package app.revanced.patches.youtube.layout.branding.icon
import app.revanced.patcher.data.ResourceContext
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.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
@ -9,26 +8,26 @@ import app.revanced.patches.youtube.utils.settings.ResourceUtils.updatePatchStat
import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.Utils.trimIndentMultiline
import app.revanced.util.copyFile
import app.revanced.util.copyResources
import app.revanced.util.copyXmlNode
import app.revanced.util.patch.BaseResourcePatch
import java.io.File
import java.nio.file.Files
import app.revanced.util.underBarOrThrow
@Suppress("DEPRECATION", "unused")
@Suppress("unused")
object CustomBrandingIconPatch : BaseResourcePatch(
name = "Custom branding icon YouTube",
description = "Changes the YouTube app icon to the icon specified in options.json.",
dependencies = setOf(SettingsPatch::class),
compatiblePackages = COMPATIBLE_PACKAGE,
) {
private const val DEFAULT_ICON_KEY = "Revancify Blue"
private const val DEFAULT_ICON = "revancify_blue"
private val availableIcon = mapOf(
"AFN Blue" to "afn_blue",
"AFN Red" to "afn_red",
"MMT" to "mmt",
DEFAULT_ICON_KEY to "revancify_blue",
"Revancify Blue" to DEFAULT_ICON,
"Revancify Red" to "revancify_red",
"YouTube" to "youtube"
)
@ -109,9 +108,9 @@ object CustomBrandingIconPatch : BaseResourcePatch(
// region patch option
val AppIcon by stringPatchOption(
val AppIcon = stringPatchOption(
key = "AppIcon",
default = DEFAULT_ICON_KEY,
default = DEFAULT_ICON,
values = availableIcon,
title = "App icon",
description = """
@ -176,112 +175,84 @@ object CustomBrandingIconPatch : BaseResourcePatch(
// endregion
override fun execute(context: ResourceContext) {
AppIcon?.let { appIcon ->
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)) {
val copiedFiles = context.copyFile(
launcherIconResourceGroups,
appIcon,
"WARNING: Invalid app icon path: $appIcon. Does not apply patches."
// Check patch options first.
val appIcon = AppIcon
.underBarOrThrow()
val appIconResourcePath = "youtube/branding/$appIcon"
val stockResourcePath = "youtube/branding/stock"
// Check if a custom path is used in the patch options.
if (!availableIcon.containsValue(appIcon)) {
val copiedFiles = context.copyFile(
launcherIconResourceGroups,
appIcon,
"WARNING: Invalid app icon path: $appIcon. Does not apply patches."
)
if (copiedFiles)
context.updatePatchStatusIcon("custom")
} else {
// Change launcher icon.
launcherIconResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
context.copyResources("$appIconResourcePath/launcher", it)
}
}
// Change monochrome icon.
arrayOf(
ResourceGroup(
"drawable",
"adaptive_monochrome_ic_youtube_launcher.xml"
)
if (copiedFiles)
context.updatePatchStatusIcon("custom")
} else {
// Change launcher icon.
launcherIconResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
context.copyResources("$appIconResourcePath/launcher", it)
).forEach { resourceGroup ->
context.copyResources("$appIconResourcePath/monochrome", resourceGroup)
}
// Change header.
if (ChangeHeader == true) {
CustomHeader?.let { customHeader ->
var copiedFiles = false
if (customHeader.isNotEmpty()) {
copiedFiles = context.copyFile(
headerIconResourceGroups,
customHeader,
"WARNING: Invalid header path: $customHeader. Does not apply patches."
)
}
}
// Change monochrome icon.
arrayOf(
ResourceGroup(
"drawable",
"adaptive_monochrome_ic_youtube_launcher.xml"
)
).forEach { resourceGroup ->
context.copyResources("$appIconResourcePath/monochrome", resourceGroup)
}
// Change header.
if (ChangeHeader == true) {
CustomHeader?.let { customHeader ->
var copiedFiles = false
if (customHeader.isNotEmpty()) {
copiedFiles = context.copyFile(
headerIconResourceGroups,
customHeader,
"WARNING: Invalid header path: $customHeader. Does not apply patches."
)
}
if (!copiedFiles) {
headerIconResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
context.copyResources("$appIconResourcePath/header", it)
}
if (!copiedFiles) {
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)
}
} ?: throw PatchException("Invalid app icon path.")
}
private fun ResourceContext.copyFile(
iconResourceGroup: List<ResourceGroup>,
path: String,
message: String
): Boolean {
iconResourceGroup.let { resourceGroups ->
try {
val filePath = File(path)
val resourceDirectory = this["res"]
// Change splash icon.
if (ChangeSplashIcon == true) {
splashIconResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
context.copyResources("$appIconResourcePath/splash", it)
}
}
}
resourceGroups.forEach { group ->
val fromDirectory = filePath.resolve(group.resourceDirectoryName)
val toDirectory = resourceDirectory.resolve(group.resourceDirectoryName)
group.resources.forEach { iconFileName ->
Files.write(
toDirectory.resolve(iconFileName).toPath(),
fromDirectory.resolve(iconFileName).readBytes()
)
// Change splash screen.
if (RestoreOldSplashAnimation == true) {
oldSplashAnimationResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
context.copyResources("$stockResourcePath/splash", it)
context.copyResources("$appIconResourcePath/splash", it)
}
}
return true
} catch (_: Exception) {
println(message)
context.copyXmlNode("$stockResourcePath/splash", "values-v31/styles.xml", "resources")
}
context.updatePatchStatusIcon(appIcon)
}
return false
}
}

View File

@ -1,13 +1,13 @@
package app.revanced.patches.youtube.layout.branding.name
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.shared.elements.StringsElementsUtils.removeStringsElements
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.settings.ResourceUtils.updatePatchStatusLabel
import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.util.patch.BaseResourcePatch
import app.revanced.util.valueOrThrow
@Suppress("DEPRECATION", "unused")
object CustomBrandingNamePatch : BaseResourcePatch(
@ -18,7 +18,7 @@ object CustomBrandingNamePatch : BaseResourcePatch(
) {
private const val APP_NAME = "RVX"
private val AppName by stringPatchOption(
private val AppName = stringPatchOption(
key = "AppName",
default = APP_NAME,
values = mapOf(
@ -33,26 +33,29 @@ object CustomBrandingNamePatch : BaseResourcePatch(
)
override fun execute(context: ResourceContext) {
// Check patch options first.
val appName = AppName
.valueOrThrow()
context.removeStringsElements(
arrayOf("application_name")
)
AppName?.let {
context.xmlEditor["res/values/strings.xml"].use { editor ->
val document = editor.file
context.xmlEditor["res/values/strings.xml"].use { editor ->
val document = editor.file
mapOf(
"application_name" to it
).forEach { (k, v) ->
val stringElement = document.createElement("string")
mapOf(
"application_name" to appName
).forEach { (k, v) ->
val stringElement = document.createElement("string")
stringElement.setAttribute("name", k)
stringElement.textContent = v
stringElement.setAttribute("name", k)
stringElement.textContent = v
document.getElementsByTagName("resources").item(0).appendChild(stringElement)
}
document.getElementsByTagName("resources").item(0).appendChild(stringElement)
}
context.updatePatchStatusLabel(it)
} ?: throw PatchException("Invalid app name.")
}
context.updatePatchStatusLabel(appName)
}
}

View File

@ -27,6 +27,15 @@ object DoubleTapLengthPatch : BaseResourcePatch(
)
override fun execute(context: ResourceContext) {
// Check patch options first.
val splits = DoubleTapLengthArrays
?.replace(" ", "")
?.split(",")
?: throw PatchException("Invalid double-tap length array.")
if (splits.isEmpty()) throw IllegalArgumentException("Invalid double-tap length elements")
val lengthElements = splits.map { it }
val arrayPath = "res/values-v21/arrays.xml"
val entriesName = "double_tap_length_entries"
val entryValueName = "double_tap_length_values"
@ -46,12 +55,6 @@ object DoubleTapLengthPatch : BaseResourcePatch(
)
)
val length = DoubleTapLengthArrays
?: throw PatchException("Invalid double-tap length array.")
val splits = length.replace(" ", "").split(",")
if (splits.isEmpty()) throw IllegalArgumentException("Invalid double-tap length elements")
val lengthElements = splits.map { it }
for (index in 0 until splits.count()) {
context.addEntryValues(arrayPath, lengthElements[index], entryValueName)
context.addEntryValues(arrayPath, lengthElements[index], entriesName)

View File

@ -1,13 +1,13 @@
package app.revanced.patches.youtube.layout.theme
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.layout.theme.BaseThemePatch.isMonetPatchIncluded
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.settings.ResourceUtils.updatePatchStatusTheme
import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.util.patch.BaseResourcePatch
import app.revanced.util.valueOrThrow
import org.w3c.dom.Element
@Suppress("DEPRECATION", "unused")
@ -38,7 +38,7 @@ object ThemePatch : BaseResourcePatch(
private const val LIGHT_ORANGE_COLOR = "#FFFFE6CC"
private const val LIGHT_RED_COLOR = "#FFFFD6D6"
private val DarkThemeBackgroundColor by stringPatchOption(
private val DarkThemeBackgroundColor = stringPatchOption(
key = "DarkThemeBackgroundColor",
default = AMOLED_BLACK_COLOR,
values = mapOf(
@ -56,7 +56,7 @@ object ThemePatch : BaseResourcePatch(
required = true
)
private val LightThemeBackgroundColor by stringPatchOption(
private val LightThemeBackgroundColor = stringPatchOption(
key = "LightThemeBackgroundColor",
default = WHITE_COLOR,
values = mapOf(
@ -111,11 +111,12 @@ object ThemePatch : BaseResourcePatch(
override fun execute(context: ResourceContext) {
// Check patch options first.
val darkThemeColor = DarkThemeBackgroundColor
?: throw PatchException("Invalid dark color.")
.valueOrThrow()
val lightThemeColor = LightThemeBackgroundColor
?: throw PatchException("Invalid light color.")
.valueOrThrow()
arrayOf("values", "values-v31").forEach { path ->
context.xmlEditor["res/$path/colors.xml"].use { editor ->

View File

@ -1,6 +1,7 @@
package app.revanced.patches.youtube.layout.visual
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.layout.branding.icon.CustomBrandingIconPatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
@ -9,6 +10,7 @@ import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
import app.revanced.util.doRecursively
import app.revanced.util.patch.BaseResourcePatch
import app.revanced.util.underBarOrThrow
import org.w3c.dom.Element
@Suppress("DEPRECATION", "unused")
@ -19,14 +21,14 @@ object VisualPreferencesIconsPatch : BaseResourcePatch(
compatiblePackages = COMPATIBLE_PACKAGE,
use = false
) {
private const val DEFAULT_ICON_KEY = "Extension"
private const val DEFAULT_ICON = "extension"
private val RVXSettingsMenuIcon by stringPatchOption(
private val RVXSettingsMenuIcon = stringPatchOption(
key = "RVXSettingsMenuIcon",
default = DEFAULT_ICON_KEY,
default = DEFAULT_ICON,
values = mapOf(
"Custom branding icon" to "custom_branding_icon",
DEFAULT_ICON_KEY to "extension",
"Extension" to DEFAULT_ICON,
"Gear" to "gear",
"ReVanced" to "revanced",
"ReVanced Colored" to "revanced_colored",
@ -38,6 +40,13 @@ object VisualPreferencesIconsPatch : BaseResourcePatch(
override fun execute(context: ResourceContext) {
// Check patch options first.
val selectedIconType = RVXSettingsMenuIcon
.underBarOrThrow()
val customBrandingIconType = CustomBrandingIconPatch.AppIcon
.underBarOrThrow()
// region copy shared resources.
arrayOf(
@ -57,31 +66,27 @@ object VisualPreferencesIconsPatch : BaseResourcePatch(
// 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"
)
val fallbackIconPath = "youtube/visual/icons/extension"
val iconPath = when (selectedIconType) {
"custom_branding_icon" -> "youtube/branding/$customBrandingIconType/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
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)
}
}
// 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.

View File

@ -12,6 +12,7 @@ import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
import app.revanced.util.copyXmlNode
import app.revanced.util.doRecursively
import app.revanced.util.lowerCaseOrThrow
import app.revanced.util.patch.BaseResourcePatch
import org.w3c.dom.Element
@ -37,27 +38,24 @@ object OverlayButtonsPatch : BaseResourcePatch(
private const val DEFAULT_MARGIN = "0.0dip"
private const val WIDER_MARGIN = "6.0dip"
private const val DEFAULT_ICON_KEY = "Bold"
// Mapping of icon types to their respective resource folder names
private val iconTypes = mapOf(
DEFAULT_ICON_KEY to "bold",
"Rounded" to "rounded",
"Thin" to "thin"
)
private const val DEFAULT_ICON = "bold"
// Option to select icon type
private val IconType by stringPatchOption(
private val IconType = stringPatchOption(
key = "IconType",
default = DEFAULT_ICON_KEY,
values = iconTypes,
default = DEFAULT_ICON,
values = mapOf(
"Bold" to DEFAULT_ICON,
"Rounded" to "rounded",
"Thin" to "thin"
),
title = "Icon type",
description = "The icon type.",
required = true
)
// Option to set bottom margin
private val BottomMargin by stringPatchOption(
private val BottomMargin = stringPatchOption(
key = "BottomMargin",
default = DEFAULT_MARGIN,
values = mapOf(
@ -76,6 +74,13 @@ object OverlayButtonsPatch : BaseResourcePatch(
*/
override fun execute(context: ResourceContext) {
// Check patch options first.
val iconType = IconType
.lowerCaseOrThrow()
val marginBottom = BottomMargin
.lowerCaseOrThrow()
// Inject hooks for overlay buttons.
arrayOf(
"AlwaysRepeat;",
@ -101,41 +106,38 @@ object OverlayButtonsPatch : BaseResourcePatch(
context.copyResources("youtube/overlaybuttons/shared", resourceGroup)
}
// Apply the selected icon type to the overlay buttons
IconType?.let { iconType ->
val iconValue = iconType.lowercase()
val commonResources = arrayOf(
"ic_fullscreen_vertical_button.png",
"ic_vr.png",
"quantum_ic_fullscreen_exit_grey600_24.png",
"quantum_ic_fullscreen_exit_white_24.png",
"quantum_ic_fullscreen_grey600_24.png",
"quantum_ic_fullscreen_white_24.png",
"revanced_time_ordered_playlist_icon.png",
"revanced_copy_icon.png",
"revanced_copy_icon_with_time.png",
"revanced_download_icon.png",
"revanced_speed_icon.png",
"revanced_whitelist_icon.png",
"yt_fill_arrow_repeat_white_24.png",
"yt_outline_arrow_repeat_1_white_24.png",
"yt_outline_arrow_shuffle_1_white_24.png",
"yt_outline_screen_full_exit_white_24.png",
"yt_outline_screen_full_white_24.png"
// Apply the selected icon type to the overlay buttons.
val commonResources = arrayOf(
"ic_fullscreen_vertical_button.png",
"ic_vr.png",
"quantum_ic_fullscreen_exit_grey600_24.png",
"quantum_ic_fullscreen_exit_white_24.png",
"quantum_ic_fullscreen_grey600_24.png",
"quantum_ic_fullscreen_white_24.png",
"revanced_time_ordered_playlist_icon.png",
"revanced_copy_icon.png",
"revanced_copy_icon_with_time.png",
"revanced_download_icon.png",
"revanced_speed_icon.png",
"revanced_whitelist_icon.png",
"yt_fill_arrow_repeat_white_24.png",
"yt_outline_arrow_repeat_1_white_24.png",
"yt_outline_arrow_shuffle_1_white_24.png",
"yt_outline_screen_full_exit_white_24.png",
"yt_outline_screen_full_white_24.png"
)
val specificResources = if (iconType == "thin") {
arrayOf("yt_outline_screen_vertical_vd_theme_24.xml")
} else {
arrayOf("yt_outline_screen_vertical_vd_theme_24.png")
}
val resources = commonResources + specificResources
resources.forEach { resource ->
val folderName = if (resource.endsWith(".xml")) "drawable" else "drawable-xxhdpi"
context.copyResources(
"youtube/overlaybuttons/$iconType",
ResourceGroup(folderName, resource)
)
val specificResources = if (iconValue == "thin") {
arrayOf("yt_outline_screen_vertical_vd_theme_24.xml")
} else {
arrayOf("yt_outline_screen_vertical_vd_theme_24.png")
}
val resources = commonResources + specificResources
resources.forEach { resource ->
val folderName = if (resource.endsWith(".xml")) "drawable" else "drawable-xxhdpi"
context.copyResources(
"youtube/overlaybuttons/$iconValue",
ResourceGroup(folderName, resource)
)
}
}
// Merge XML nodes from the host to their respective XML files.
@ -145,9 +147,6 @@ object OverlayButtonsPatch : BaseResourcePatch(
"android.support.constraint.ConstraintLayout"
)
val marginBottom = BottomMargin
?: DEFAULT_MARGIN
// Modify the layout of fullscreen button for newer YouTube versions (19.09.xx+)
arrayOf(
"youtube_controls_cf_fullscreen_button.xml",

View File

@ -18,6 +18,7 @@ import app.revanced.util.copyResources
import app.revanced.util.copyXmlNode
import app.revanced.util.patch.BaseBytecodePatch
import app.revanced.util.patch.BaseResourcePatch
import app.revanced.util.valueOrThrow
import org.w3c.dom.Element
import java.io.Closeable
import java.util.concurrent.Executors
@ -38,7 +39,7 @@ object SettingsPatch : BaseResourcePatch(
compatiblePackages = COMPATIBLE_PACKAGE,
requiresIntegrations = true
), Closeable {
private const val DEFAULT_ELEMENT = "About"
private const val DEFAULT_ELEMENT = "@string/about_key"
private const val DEFAULT_NAME = "ReVanced Extended"
private val SETTINGS_ELEMENTS_MAP = mapOf(
@ -63,10 +64,10 @@ object SettingsPatch : BaseResourcePatch(
"Live chat" to "@string/live_chat_key",
"Captions" to "@string/captions_key",
"Accessibility" to "@string/accessibility_settings_key",
DEFAULT_ELEMENT to "@string/about_key"
"About" to DEFAULT_ELEMENT
)
private val InsertPosition by stringPatchOption(
private val InsertPosition = stringPatchOption(
key = "InsertPosition",
default = DEFAULT_ELEMENT,
values = SETTINGS_ELEMENTS_MAP,
@ -75,7 +76,7 @@ object SettingsPatch : BaseResourcePatch(
required = true
)
private val RVXSettingsMenuName by stringPatchOption(
private val RVXSettingsMenuName = stringPatchOption(
key = "RVXSettingsMenuName",
default = DEFAULT_NAME,
title = "RVX settings menu name",
@ -83,6 +84,8 @@ object SettingsPatch : BaseResourcePatch(
required = true
)
private lateinit var customName: String
internal lateinit var contexts: ResourceContext
internal var upward1831 = false
internal var upward1834 = false
@ -94,6 +97,15 @@ object SettingsPatch : BaseResourcePatch(
override fun execute(context: ResourceContext) {
/**
* check patch options
*/
customName = RVXSettingsMenuName
.valueOrThrow()
val insertKey = InsertPosition
.valueOrThrow()
/**
* set resource context
*/
@ -148,16 +160,10 @@ object SettingsPatch : BaseResourcePatch(
/**
* initialize ReVanced Extended Settings
*/
val elementKey = SETTINGS_ELEMENTS_MAP[InsertPosition]
?: InsertPosition
?: SETTINGS_ELEMENTS_MAP[DEFAULT_ELEMENT]
elementKey?.let { insertKey ->
context.addPreferenceFragment(
"revanced_extended_settings",
insertKey
)
}
context.addPreferenceFragment(
"revanced_extended_settings",
insertKey
)
/**
* remove ReVanced Extended Settings divider
@ -220,29 +226,26 @@ object SettingsPatch : BaseResourcePatch(
* 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
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")
mapOf(
"revanced_extended_settings_title" to customName
).forEach { (k, v) ->
val stringElement = document.createElement("string")
stringElement.setAttribute("name", k)
stringElement.textContent = v
stringElement.setAttribute("name", k)
stringElement.textContent = v
document.getElementsByTagName("resources").item(0)
.appendChild(stringElement)
}
document.getElementsByTagName("resources").item(0)
.appendChild(stringElement)
}
}
} ?: println("WARNING: Invalid RVX settings menu name. RVX settings menu name does not change.")
}
}
private fun setVersionInfo() {

View File

@ -3,15 +3,27 @@
package app.revanced.util
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.options.PatchOption
import app.revanced.patcher.util.DomFileEditor
import org.w3c.dom.Element
import org.w3c.dom.Node
import java.io.File
import java.io.InputStream
import java.nio.file.Files
import java.nio.file.StandardCopyOption
val classLoader: ClassLoader = object {}.javaClass.classLoader
fun PatchOption<String>.valueOrThrow() = value
?: throw PatchException("Invalid patch option: $title.")
fun PatchOption<String>.lowerCaseOrThrow() = valueOrThrow()
.lowercase()
fun PatchOption<String>.underBarOrThrow() = lowerCaseOrThrow()
.replace(" ", "_")
fun Node.adoptChild(tagName: String, block: Element.() -> Unit) {
val child = ownerDocument.createElement(tagName)
child.block()
@ -48,6 +60,36 @@ fun String.startsWithAny(vararg prefixes: String): Boolean {
return false
}
fun ResourceContext.copyFile(
resourceGroup: List<ResourceGroup>,
path: String,
warning: String
): Boolean {
resourceGroup.let { resourceGroups ->
try {
val filePath = File(path)
val resourceDirectory = this["res"]
resourceGroups.forEach { group ->
val fromDirectory = filePath.resolve(group.resourceDirectoryName)
val toDirectory = resourceDirectory.resolve(group.resourceDirectoryName)
group.resources.forEach { iconFileName ->
Files.write(
toDirectory.resolve(iconFileName).toPath(),
fromDirectory.resolve(iconFileName).readBytes()
)
}
}
return true
} catch (_: Exception) {
println(warning)
}
}
return false
}
/**
* Copy resources from the current class loader to the resource directory.
*