diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/ThemeHelper.java b/extensions/shared/src/main/java/app/revanced/extension/shared/ThemeHelper.java similarity index 100% rename from extensions/youtube/src/main/java/app/revanced/extension/youtube/ThemeHelper.java rename to extensions/shared/src/main/java/app/revanced/extension/shared/ThemeHelper.java diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/theme/ThemePatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/theme/ThemePatch.kt new file mode 100644 index 0000000000..75b468216d --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/theme/ThemePatch.kt @@ -0,0 +1,37 @@ +package app.revanced.patches.music.layout.theme + +import app.revanced.patcher.patch.resourcePatch +import app.revanced.patches.shared.layout.theme.ThemePatch +import org.w3c.dom.Element + +val themePatch = resourcePatch( + name = "AMOLED theme", + description = "Applies AMOLED black theme to YouTube Music.", +) { + dependsOn( + ThemePatch, + ) + + execute { + val amoledBlackColor = "@android:color/black" + + 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 + + if (node.getAttribute("name") in listOf( + "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" + )) { + node.textContent = amoledBlackColor + } + } + } + } + + compatibleWith("com.google.android.apps.youtube.music"() + ) +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/Fingerprints.kt similarity index 87% rename from patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/Fingerprints.kt rename to patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/Fingerprints.kt index 9ed098a1bf..e999bb7f88 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/Fingerprints.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.youtube.layout.theme +package app.revanced.patches.shared.layout.theme import app.revanced.patcher.fingerprint import app.revanced.util.literal @@ -37,7 +37,7 @@ internal val themeHelperDarkColorFingerprint = fingerprint { parameters() custom { method, _ -> method.name == "darkThemeResourceName" && - method.definingClass == "Lapp/revanced/extension/youtube/ThemeHelper;" + method.definingClass == "Lapp/revanced/extension/shared/ThemeHelper;" } } @@ -47,7 +47,7 @@ internal val themeHelperLightColorFingerprint = fingerprint { parameters() custom { method, _ -> method.name == "lightThemeResourceName" && - method.definingClass == "Lapp/revanced/extension/youtube/ThemeHelper;" + method.definingClass == "Lapp/revanced/extension/shared/ThemeHelper;" } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/LithoColorHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/LithoColorHookPatch.kt similarity index 94% rename from patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/LithoColorHookPatch.kt rename to patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/LithoColorHookPatch.kt index fdab6c4b8b..0fcfe467d5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/LithoColorHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/LithoColorHookPatch.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.youtube.layout.theme +package app.revanced.patches.shared.layout.theme import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.patch.bytecodePatch diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/ThemePatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/ThemePatch.kt new file mode 100644 index 0000000000..23a7c2e464 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/ThemePatch.kt @@ -0,0 +1,238 @@ +package app.revanced.patches.shared.layout.theme + +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.patch.PatchException +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.patch.resourcePatch +import app.revanced.patcher.patch.stringOption +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.shared.misc.mapping.resourceMappingPatch +import app.revanced.patches.shared.misc.settings.preference.InputType +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference +import app.revanced.patches.shared.misc.settings.preference.TextPreference +import app.revanced.patches.youtube.layout.seekbar.seekbarColorPatch +import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch +import app.revanced.patches.youtube.misc.settings.PreferenceScreen +import app.revanced.patches.youtube.misc.settings.settingsPatch +import app.revanced.util.forEachChildElement +import app.revanced.util.insertFeatureFlagBooleanOverride +import org.w3c.dom.Element + +private const val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/youtube/patches/theme/ThemePatch;" + +internal const val GRADIENT_LOADING_SCREEN_AB_CONSTANT = 45412406L + +val ThemePatch = bytecodePatch( + name = "Theme", + description = "Adds options for theming and applies a custom background theme (dark background theme defaults to amoled black.", +) { + val amoledBlackColor = "@android:color/black" + val whiteColor = "@android:color/white" + + val darkThemeBackgroundColor by stringOption( + key = "darkThemeBackgroundColor", + default = amoledBlackColor, + values = mapOf( + "Amoled black" to amoledBlackColor, + "Material You" to "@android:color/system_neutral1_900", + "Classic (old YouTube)" to "#FF212121", + "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 lightThemeBackgroundColor by stringOption( + key = "lightThemeBackgroundColor", + default = whiteColor, + values = mapOf( + "White" to whiteColor, + "Material You" to "@android:color/system_neutral1_50", + "Catppuccin (Latte)" to "#FFE6E9EF", + "Light pink" to "#FFFCCFF3", + "Light blue" to "#FFD1E0FF", + "Light green" to "#FFCCFFCC", + "Light yellow" to "#FFFDFFCC", + "Light orange" to "#FFFFE6CC", + "Light red" to "#FFFFD6D6", + ), + title = "Light theme background color", + description = "Can be a hex color (#AARRGGBB) or a color resource reference.", + ) + + dependsOn( + lithoColorHookPatch, + seekbarColorPatch, + resourcePatch { + dependsOn( + settingsPatch, + resourceMappingPatch, + addResourcesPatch, + ) + + execute { + addResources("youtube", "layout.theme.themeResourcePatch") + + PreferenceScreen.SEEKBAR.addPreferences( + SwitchPreference("revanced_seekbar_custom_color"), + TextPreference("revanced_seekbar_custom_color_value", inputType = InputType.TEXT_CAP_CHARACTERS), + ) + + // Edit theme colors via resources. + document("res/values/colors.xml").use { document -> + val resourcesNode = document.getElementsByTagName("resources").item(0) as Element + val children = resourcesNode.childNodes + for (i in 0 until children.length) { + val node = children.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", "material_grey_850", + -> darkThemeBackgroundColor ?: continue + + "yt_white1", "yt_white1_opacity95", "yt_white1_opacity98", + "yt_white2", "yt_white3", "yt_white4", + -> lightThemeBackgroundColor ?: continue + + else -> continue + } + } + } + + fun addColorResource( + resourceFile: String, + colorName: String, + colorValue: String, + ) { + document(resourceFile).use { document -> + val resourcesNode = document.getElementsByTagName("resources").item(0) as Element + resourcesNode.appendChild( + document.createElement("color").apply { + setAttribute("name", colorName) + setAttribute("category", "color") + textContent = colorValue + }, + ) + } + } + + val splashBackgroundColor = "revanced_splash_background_color" + + // Add a dynamic background color to the colors.xml file. + lightThemeBackgroundColor?.let { + addColorResource("res/values/colors.xml", splashBackgroundColor, it) + } + + darkThemeBackgroundColor?.let { + addColorResource("res/values-night/colors.xml", splashBackgroundColor, it) + } + + // Edit splash screen files and change the background color, + // if the background colors are set. + if (darkThemeBackgroundColor != null && lightThemeBackgroundColor != null) { + val splashScreenResourceFiles = + listOf( + "res/drawable/quantum_launchscreen_youtube.xml", + "res/drawable-sw600dp/quantum_launchscreen_youtube.xml", + ) + + splashScreenResourceFiles.forEach editSplashScreen@{ resourceFile -> + document(resourceFile).use { document -> + document.getElementsByTagName("layer-list").item(0).forEachChildElement { node -> + if (node.hasAttribute("android:drawable")) { + node.setAttribute("android:drawable", "@color/$splashBackgroundColor") + return@editSplashScreen + } + } + + throw PatchException("Failed to modify launch screen") + } + } + + // Fix the splash screen dark mode background color. + document("res/values-night/styles.xml").use { document -> + val resourcesNode = document.getElementsByTagName("resources").item(0) as Element + val childNodes = resourcesNode.childNodes + + for (i in 0 until childNodes.length) { + val node = childNodes.item(i) as? Element ?: continue + val nodeAttributeName = node.getAttribute("name") + if (nodeAttributeName.startsWith("Theme.YouTube.Launcher")) { + val nodeAttributeParent = node.getAttribute("parent") + + val style = document.createElement("style") + style.setAttribute("name", "Theme.YouTube.Home") + style.setAttribute("parent", nodeAttributeParent) + + val windowItem = document.createElement("item") + windowItem.setAttribute("name", "android:windowBackground") + windowItem.textContent = "@color/$splashBackgroundColor" + style.appendChild(windowItem) + + resourcesNode.removeChild(node) + resourcesNode.appendChild(style) + } + } + } + } + } + }, + sharedExtensionPatch, + settingsPatch, + addResourcesPatch, + ) + + compatibleWith( + "com.google.android.youtube"( + "18.38.44", + "18.49.37", + "19.16.39", + "19.25.37", + "19.34.42", + "19.43.41", + "19.45.38", + "19.46.42", + "19.47.53", + ), + "com.google.android.apps.youtube.music"() + ) + + execute { + addResources("youtube", "layout.theme.themePatch") + + PreferenceScreen.GENERAL_LAYOUT.addPreferences( + SwitchPreference("revanced_gradient_loading_screen"), + ) + + useGradientLoadingScreenFingerprint.method.insertFeatureFlagBooleanOverride( + GRADIENT_LOADING_SCREEN_AB_CONSTANT, + "$EXTENSION_CLASS_DESCRIPTOR->gradientLoadingScreenEnabled(Z)Z" + ) + + mapOf( + themeHelperLightColorFingerprint to lightThemeBackgroundColor, + themeHelperDarkColorFingerprint to darkThemeBackgroundColor, + ).forEach { (fingerprint, color) -> + fingerprint.method.apply { + addInstructions( + 0, + """ + const-string v0, "$color" + return-object v0 + """, + ) + } + } + + lithoColorOverrideHook(EXTENSION_CLASS_DESCRIPTOR, "getValue") + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/SeekbarColorPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/SeekbarColorPatch.kt index a9f347ad08..c15685c4f2 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/SeekbarColorPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/SeekbarColorPatch.kt @@ -10,8 +10,8 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patches.shared.misc.mapping.get import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.mapping.resourceMappings -import app.revanced.patches.youtube.layout.theme.lithoColorHookPatch -import app.revanced.patches.youtube.layout.theme.lithoColorOverrideHook +import app.revanced.patches.shared.layout.theme.lithoColorHookPatch +import app.revanced.patches.shared.layout.theme.lithoColorOverrideHook import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_46_or_greater diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt index 92d3a3fd88..a6eb7e9dbc 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt @@ -1,243 +1,27 @@ package app.revanced.patches.youtube.layout.theme -import app.revanced.patcher.extensions.InstructionExtensions.addInstructions -import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.bytecodePatch -import app.revanced.patcher.patch.resourcePatch -import app.revanced.patcher.patch.stringOption -import app.revanced.patches.all.misc.resources.addResources -import app.revanced.patches.all.misc.resources.addResourcesPatch -import app.revanced.patches.shared.misc.mapping.resourceMappingPatch -import app.revanced.patches.shared.misc.settings.preference.InputType -import app.revanced.patches.shared.misc.settings.preference.SwitchPreference -import app.revanced.patches.shared.misc.settings.preference.TextPreference -import app.revanced.patches.youtube.layout.seekbar.seekbarColorPatch -import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch -import app.revanced.patches.youtube.misc.settings.PreferenceScreen +import app.revanced.patches.shared.layout.theme.ThemePatch import app.revanced.patches.youtube.misc.settings.settingsPatch -import app.revanced.util.forEachChildElement -import app.revanced.util.insertFeatureFlagBooleanOverride -import org.w3c.dom.Element - -private const val EXTENSION_CLASS_DESCRIPTOR = - "Lapp/revanced/extension/youtube/patches/theme/ThemePatch;" - -internal const val GRADIENT_LOADING_SCREEN_AB_CONSTANT = 45412406L val themePatch = bytecodePatch( name = "Theme", description = "Adds options for theming and applies a custom background theme (dark background theme defaults to amoled black).", ) { - val amoledBlackColor = "@android:color/black" - val whiteColor = "@android:color/white" - - val darkThemeBackgroundColor by stringOption( - key = "darkThemeBackgroundColor", - default = amoledBlackColor, - values = mapOf( - "Amoled black" to amoledBlackColor, - "Material You" to "@android:color/system_neutral1_900", - "Classic (old YouTube)" to "#FF212121", - "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 lightThemeBackgroundColor by stringOption( - key = "lightThemeBackgroundColor", - default = whiteColor, - values = mapOf( - "White" to whiteColor, - "Material You" to "@android:color/system_neutral1_50", - "Catppuccin (Latte)" to "#FFE6E9EF", - "Light pink" to "#FFFCCFF3", - "Light blue" to "#FFD1E0FF", - "Light green" to "#FFCCFFCC", - "Light yellow" to "#FFFDFFCC", - "Light orange" to "#FFFFE6CC", - "Light red" to "#FFFFD6D6", - ), - title = "Light theme background color", - description = "Can be a hex color (#AARRGGBB) or a color resource reference.", - ) - dependsOn( - lithoColorHookPatch, - seekbarColorPatch, - resourcePatch { - dependsOn( - settingsPatch, - resourceMappingPatch, - addResourcesPatch, - ) - - execute { - addResources("youtube", "layout.theme.themeResourcePatch") - - PreferenceScreen.SEEKBAR.addPreferences( - SwitchPreference("revanced_seekbar_custom_color"), - TextPreference("revanced_seekbar_custom_color_value", inputType = InputType.TEXT_CAP_CHARACTERS), - ) - - // Edit theme colors via resources. - document("res/values/colors.xml").use { document -> - - val resourcesNode = document.getElementsByTagName("resources").item(0) as Element - - val children = resourcesNode.childNodes - for (i in 0 until children.length) { - val node = children.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", "material_grey_850", - -> darkThemeBackgroundColor ?: continue - - "yt_white1", "yt_white1_opacity95", "yt_white1_opacity98", - "yt_white2", "yt_white3", "yt_white4", - -> lightThemeBackgroundColor ?: continue - - else -> continue - } - } - } - - fun addColorResource( - resourceFile: String, - colorName: String, - colorValue: String, - ) { - document(resourceFile).use { document -> - - val resourcesNode = document.getElementsByTagName("resources").item(0) as Element - - resourcesNode.appendChild( - document.createElement("color").apply { - setAttribute("name", colorName) - setAttribute("category", "color") - textContent = colorValue - }, - ) - } - } - - val splashBackgroundColor = "revanced_splash_background_color" - - // Add a dynamic background color to the colors.xml file. - lightThemeBackgroundColor?.let { - addColorResource("res/values/colors.xml", splashBackgroundColor, it) - } - - darkThemeBackgroundColor?.let { - addColorResource("res/values-night/colors.xml", splashBackgroundColor, it) - } - - // Edit splash screen files and change the background color, - // if the background colors are set. - if (darkThemeBackgroundColor != null && lightThemeBackgroundColor != null) { - val splashScreenResourceFiles = - listOf( - "res/drawable/quantum_launchscreen_youtube.xml", - "res/drawable-sw600dp/quantum_launchscreen_youtube.xml", - ) - - splashScreenResourceFiles.forEach editSplashScreen@{ resourceFile -> - document(resourceFile).use { document -> - document.getElementsByTagName("layer-list").item(0).forEachChildElement { node -> - if (node.hasAttribute("android:drawable")) { - node.setAttribute("android:drawable", "@color/$splashBackgroundColor") - return@editSplashScreen - } - } - - throw PatchException("Failed to modify launch screen") - } - } - - // Fix the splash screen dark mode background color. - // In 19.32+ the dark mode splash screen is white and fades to black. - // Maybe it's a bug in YT, or maybe it intentionally. Who knows. - document("res/values-night/styles.xml").use { document -> - val resourcesNode = document.getElementsByTagName("resources").item(0) as Element - val childNodes = resourcesNode.childNodes - - for (i in 0 until childNodes.length) { - val node = childNodes.item(i) as? Element ?: continue - val nodeAttributeName = node.getAttribute("name") - if (nodeAttributeName.startsWith("Theme.YouTube.Launcher")) { - val nodeAttributeParent = node.getAttribute("parent") - - val style = document.createElement("style") - style.setAttribute("name", "Theme.YouTube.Home") - style.setAttribute("parent", nodeAttributeParent) - - val windowItem = document.createElement("item") - windowItem.setAttribute("name", "android:windowBackground") - windowItem.textContent = "@color/$splashBackgroundColor" - style.appendChild(windowItem) - - resourcesNode.removeChild(node) - resourcesNode.appendChild(style) - } - } - } - } - } - }, - sharedExtensionPatch, + ThemePatch, settingsPatch, - addResourcesPatch, - ) - - compatibleWith( - "com.google.android.youtube"( - "18.38.44", - "18.49.37", - "19.16.39", - "19.25.37", - "19.34.42", - "19.43.41", - "19.45.38", - "19.46.42", - "19.47.53", - ), ) - execute { - addResources("youtube", "layout.theme.themePatch") - - PreferenceScreen.GENERAL_LAYOUT.addPreferences( - SwitchPreference("revanced_gradient_loading_screen"), - ) - - useGradientLoadingScreenFingerprint.method.insertFeatureFlagBooleanOverride( - GRADIENT_LOADING_SCREEN_AB_CONSTANT, - "$EXTENSION_CLASS_DESCRIPTOR->gradientLoadingScreenEnabled(Z)Z" - ) - - mapOf( - themeHelperLightColorFingerprint to lightThemeBackgroundColor, - themeHelperDarkColorFingerprint to darkThemeBackgroundColor, - ).forEach { (fingerprint, color) -> - fingerprint.method.apply { - addInstructions( - 0, - """ - const-string v0, "$color" - return-object v0 - """, - ) - } - } - - lithoColorOverrideHook(EXTENSION_CLASS_DESCRIPTOR, "getValue") - } + compatibleWith("com.google.android.youtube"( + "18.38.44", + "18.49.37", + "19.16.39", + "19.25.37", + "19.34.42", + "19.43.41", + "19.45.38", + "19.46.42", + "19.47.53", + )) }