feat(YouTube Music): Add Dark theme patch, Remove Amoled patch

This commit is contained in:
inotia00
2025-01-06 21:33:17 +09:00
parent ac0fea1cb7
commit d748b6d12f
12 changed files with 375 additions and 174 deletions

View File

@ -1,46 +0,0 @@
package app.revanced.patches.music.general.amoled
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.UTILS_PATH
import app.revanced.patches.music.utils.patch.PatchList.AMOLED
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.shared.drawable.addDrawableColorHook
import app.revanced.patches.shared.drawable.drawableColorHookPatch
import org.w3c.dom.Element
@Suppress("unused")
val amoledPatch = resourcePatch(
AMOLED.title,
AMOLED.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
drawableColorHookPatch,
settingsPatch
)
execute {
addDrawableColorHook("$UTILS_PATH/DrawableColorPatch;->getLithoColor(I)I")
document("res/values/colors.xml").use { document ->
val resourcesNode = document.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")) {
"yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98", "yt_black2", "yt_black3",
"yt_black4", "yt_status_bar_background_dark", "ytm_color_grey_12", "material_grey_850" -> "@android:color/black"
else -> continue
}
}
}
updatePatchStatus(AMOLED)
}
}

View File

@ -0,0 +1,151 @@
package app.revanced.patches.music.layout.theme
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.booleanOption
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.PATCH_STATUS_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.extension.Constants.UTILS_PATH
import app.revanced.patches.music.utils.patch.PatchList.DARK_THEME
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.shared.drawable.addDrawableColorHook
import app.revanced.patches.shared.drawable.drawableColorHookPatch
import app.revanced.patches.shared.materialyou.baseMaterialYou
import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import app.revanced.util.valueOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import org.w3c.dom.Element
private const val EXTENSION_CLASS_DESCRIPTOR =
"$UTILS_PATH/DrawableColorPatch;"
private val darkThemeBytecodePatch = bytecodePatch(
description = "darkThemeBytecodePatch"
) {
dependsOn(
settingsPatch,
sharedResourceIdPatch,
drawableColorHookPatch,
)
execute {
addDrawableColorHook("$EXTENSION_CLASS_DESCRIPTOR->getLithoColor(I)I")
elementsContainerFingerprint.methodOrThrow().apply {
val index = indexOfFirstInstructionReversedOrThrow(Opcode.CHECK_CAST)
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstruction(
index + 1,
"invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->setHeaderGradient(Landroid/view/ViewGroup;)V"
)
}
findMethodOrThrow(PATCH_STATUS_CLASS_DESCRIPTOR) {
name == "DarkTheme"
}.replaceInstruction(
0,
"const/4 v0, 0x1"
)
}
}
val DARK_COLOR = arrayOf(
"yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98",
"yt_black2", "yt_black3", "yt_black4","yt_black_pure",
"yt_black_pure_opacity80", "yt_status_bar_background_dark",
"ytm_color_grey_12", "material_grey_800", "material_grey_850",
)
@Suppress("unused")
val darkThemePatch = resourcePatch(
DARK_THEME.title,
DARK_THEME.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(darkThemeBytecodePatch)
val amoledBlackColor = "@android:color/black"
val darkThemeBackgroundColor = stringOption(
key = "darkThemeBackgroundColor",
default = amoledBlackColor,
values = mapOf(
"Amoled Black" to amoledBlackColor,
"Catppuccin (Mocha)" to "#FF181825",
"Dark Pink" to "#FF290025",
"Dark Blue" to "#FF001029",
"Dark Green" to "#FF002905",
"Dark Yellow" to "#FF282900",
"Dark Orange" to "#FF291800",
"Dark Red" to "#FF290000",
),
title = "Dark theme background color",
description = "Can be a hex color (#AARRGGBB) or a color resource reference.",
)
val materialYou by booleanOption(
key = "materialYou",
default = false,
title = "MaterialYou",
description = "Applies the MaterialYou theme for Android 12+ devices.",
required = true
)
execute {
// Check patch options first.
val darkThemeColor = darkThemeBackgroundColor
.valueOrThrow()
document("res/values/colors.xml").use { document ->
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
for (i in 0 until resourcesNode.childNodes.length) {
val node = resourcesNode.childNodes.item(i) as? Element ?: continue
val colorName = node.getAttribute("name")
if (DARK_COLOR.contains(colorName)) {
node.textContent = darkThemeColor
}
}
}
arrayOf(
ResourceGroup(
"drawable",
"revanced_header_gradient.xml",
)
).forEach { resourceGroup ->
copyResources("music/theme", resourceGroup)
}
if (materialYou == true) {
baseMaterialYou()
document("res/values-v31/colors.xml").use { document ->
DARK_COLOR.forEach { name ->
val colorElement = document.createElement("color")
colorElement.setAttribute("name", name)
colorElement.textContent = "@android:color/system_neutral1_900"
document.getElementsByTagName("resources").item(0).appendChild(colorElement)
}
}
}
updatePatchStatus(DARK_THEME)
}
}

View File

@ -0,0 +1,15 @@
package app.revanced.patches.music.layout.theme
import app.revanced.patches.music.utils.resourceid.elementsContainer
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val elementsContainerFingerprint = legacyFingerprint(
name = "elementsContainerFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
opcodes = listOf(Opcode.INVOKE_DIRECT_RANGE),
literals = listOf(elementsContainer)
)

View File

@ -5,10 +5,6 @@ internal enum class PatchList(
val summary: String,
var included: Boolean? = false
) {
AMOLED(
"Amoled",
"Applies a pure black theme to some components."
),
BITRATE_DEFAULT_VALUE(
"Bitrate default value",
"Sets the audio quality to 'Always High' when you first install the app."
@ -41,6 +37,10 @@ internal enum class PatchList(
"Custom header for YouTube Music",
"Applies a custom header in the top left corner within the app."
),
DARK_THEME(
"Dark theme",
"Changes the app's dark theme to the values specified in patch options."
),
DISABLE_CAIRO_SPLASH_ANIMATION(
"Disable Cairo splash animation",
"Adds an option to disable Cairo splash animation."
@ -59,7 +59,7 @@ internal enum class PatchList(
),
DISABLE_MUSIC_VIDEO_IN_ALBUM(
"Disable music video in album",
"Adds option to redirect music videos from albums."
"Adds option to redirect music videos from albums for non-premium users."
),
ENABLE_OPUS_CODEC(
"Enable OPUS codec",

View File

@ -33,6 +33,8 @@ var darkBackground = -1L
private set
var designBottomSheetDialog = -1L
private set
var elementsContainer = -1L
private set
var endButtonsContainer = -1L
private set
var floatingLayout = -1L
@ -150,6 +152,10 @@ internal val sharedResourceIdPatch = resourcePatch(
LAYOUT,
"design_bottom_sheet_dialog"
]
elementsContainer = resourceMappings[
ID,
"elements_container"
]
endButtonsContainer = resourceMappings[
ID,
"end_buttons_container"

View File

@ -0,0 +1,130 @@
package app.revanced.patches.shared.materialyou
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.ResourcePatchContext
import app.revanced.util.copyXmlNode
import app.revanced.util.inputStreamFromBundledResource
import org.w3c.dom.Element
import org.w3c.dom.Node
import java.io.File
import java.nio.file.Files
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
private fun ResourcePatchContext.patchXmlFile(
fromDir: String,
toDir: String,
xmlFileName: String,
parentNode: String,
targetNode: String? = null,
attribute: String,
newValue: String
) {
val resourceDirectory = get("res")
val fromDirectory = resourceDirectory.resolve(fromDir)
val toDirectory = resourceDirectory.resolve(toDir)
if (!toDirectory.isDirectory) Files.createDirectories(toDirectory.toPath())
val fromXmlFile = fromDirectory.resolve(xmlFileName)
val toXmlFile = toDirectory.resolve(xmlFileName)
if (!fromXmlFile.exists()) {
return
}
if (!toXmlFile.exists()) {
Files.copy(
fromXmlFile.toPath(),
toXmlFile.toPath()
)
}
document("res/$toDir/$xmlFileName").use { document ->
val parentList = document.getElementsByTagName(parentNode).item(0) as Element
if (targetNode != null) {
for (i in 0 until parentList.childNodes.length) {
val node = parentList.childNodes.item(i) as? Element ?: continue
if (node.nodeName == targetNode && node.hasAttribute(attribute)) {
node.getAttributeNode(attribute).textContent = newValue
}
}
} else {
if (parentList.hasAttribute(attribute)) {
parentList.getAttributeNode(attribute).textContent = newValue
}
}
}
}
fun ResourcePatchContext.baseMaterialYou() {
patchXmlFile(
"drawable",
"drawable-night-v31",
"new_content_dot_background.xml",
"shape",
"solid",
"android:color",
"@android:color/system_accent1_100"
)
patchXmlFile(
"drawable",
"drawable-night-v31",
"new_content_dot_background_cairo.xml",
"shape",
"solid",
"android:color",
"@android:color/system_accent1_100"
)
patchXmlFile(
"drawable",
"drawable-v31",
"new_content_dot_background.xml",
"shape",
"solid",
"android:color",
"@android:color/system_accent1_200"
)
patchXmlFile(
"drawable",
"drawable-v31",
"new_content_dot_background_cairo.xml",
"shape",
"solid",
"android:color",
"@android:color/system_accent1_200"
)
patchXmlFile(
"drawable",
"drawable-v31",
"new_content_count_background.xml",
"shape",
"solid",
"android:color",
"@android:color/system_accent1_100"
)
patchXmlFile(
"drawable",
"drawable-v31",
"new_content_count_background_cairo.xml",
"shape",
"solid",
"android:color",
"@android:color/system_accent1_100"
)
patchXmlFile(
"layout",
"layout-v31",
"new_content_count.xml",
"TextView",
null,
"android:textColor",
"@android:color/system_neutral1_900"
)
}

View File

@ -1,14 +1,13 @@
package app.revanced.patches.youtube.layout.theme
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.shared.materialyou.baseMaterialYou
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.patch.PatchList.MATERIALYOU
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
import app.revanced.patches.youtube.utils.settings.ResourceUtils.updatePatchStatusTheme
import app.revanced.patches.youtube.utils.settings.settingsPatch
import app.revanced.util.copyXmlNode
import org.w3c.dom.Element
import java.nio.file.Files
@Suppress("unused")
val materialYouPatch = resourcePatch(
@ -24,117 +23,7 @@ val materialYouPatch = resourcePatch(
)
execute {
fun patchXmlFile(
fromDir: String,
toDir: String,
xmlFileName: String,
parentNode: String,
targetNode: String? = null,
attribute: String,
newValue: String
) {
val resourceDirectory = get("res")
val fromDirectory = resourceDirectory.resolve(fromDir)
val toDirectory = resourceDirectory.resolve(toDir)
if (!toDirectory.isDirectory) Files.createDirectories(toDirectory.toPath())
val fromXmlFile = fromDirectory.resolve(xmlFileName)
val toXmlFile = toDirectory.resolve(xmlFileName)
if (!fromXmlFile.exists()) {
return
}
if (!toXmlFile.exists()) {
Files.copy(
fromXmlFile.toPath(),
toXmlFile.toPath()
)
}
document("res/$toDir/$xmlFileName").use { document ->
val parentList = document.getElementsByTagName(parentNode).item(0) as Element
if (targetNode != null) {
for (i in 0 until parentList.childNodes.length) {
val node = parentList.childNodes.item(i) as? Element ?: continue
if (node.nodeName == targetNode && node.hasAttribute(attribute)) {
node.getAttributeNode(attribute).textContent = newValue
}
}
} else {
if (parentList.hasAttribute(attribute)) {
parentList.getAttributeNode(attribute).textContent = newValue
}
}
}
}
patchXmlFile(
"drawable",
"drawable-night-v31",
"new_content_dot_background.xml",
"shape",
"solid",
"android:color",
"@android:color/system_accent1_100"
)
patchXmlFile(
"drawable",
"drawable-night-v31",
"new_content_dot_background_cairo.xml",
"shape",
"solid",
"android:color",
"@android:color/system_accent1_100"
)
patchXmlFile(
"drawable",
"drawable-v31",
"new_content_dot_background.xml",
"shape",
"solid",
"android:color",
"@android:color/system_accent1_200"
)
patchXmlFile(
"drawable",
"drawable-v31",
"new_content_dot_background_cairo.xml",
"shape",
"solid",
"android:color",
"@android:color/system_accent1_200"
)
patchXmlFile(
"drawable",
"drawable-v31",
"new_content_count_background.xml",
"shape",
"solid",
"android:color",
"@android:color/system_accent1_100"
)
patchXmlFile(
"drawable",
"drawable-v31",
"new_content_count_background_cairo.xml",
"shape",
"solid",
"android:color",
"@android:color/system_accent1_100"
)
patchXmlFile(
"layout",
"layout-v31",
"new_content_count.xml",
"TextView",
null,
"android:textColor",
"@android:color/system_neutral1_900"
)
baseMaterialYou()
copyXmlNode("youtube/materialyou/host", "values-v31/colors.xml", "resources")