diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/theme/ThemePatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/theme/ThemePatch.java new file mode 100644 index 000000000..3f4e39669 --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/patches/theme/ThemePatch.java @@ -0,0 +1,27 @@ +package app.revanced.extension.music.patches.theme; + +import app.revanced.extension.shared.theme.BaseThemePatch; + +@SuppressWarnings("unused") +public class ThemePatch extends BaseThemePatch { + + // Color constants used in relation with litho components. + private static final int[] DARK_VALUES = { + 0xFF212121, // Comments box background. + 0xFF030303, // Button container background in album. + 0xFF000000, // Button container background in playlist. + }; + + /** + * Injection point. + *

+ * Change the color of Litho components. + * If the color of the component matches one of the values, return the background color. + * + * @param originalValue The original color value. + * @return The new or original color value. + */ + public static int getValue(int originalValue) { + return processColorValue(originalValue, DARK_VALUES, null); + } +} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/theme/BaseThemePatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/theme/BaseThemePatch.java new file mode 100644 index 000000000..2d12b0c1f --- /dev/null +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/theme/BaseThemePatch.java @@ -0,0 +1,48 @@ +package app.revanced.extension.shared.theme; + +import androidx.annotation.Nullable; + +import app.revanced.extension.shared.Utils; + +@SuppressWarnings("unused") +public abstract class BaseThemePatch { + // Background colors. + protected static final int BLACK_COLOR = Utils.getResourceColor("yt_black1"); + protected static final int WHITE_COLOR = Utils.getResourceColor("yt_white1"); + + /** + * Check if a value matches any of the provided values. + * + * @param value The value to check. + * @param of The array of values to compare against. + * @return True if the value matches any of the provided values. + */ + protected static boolean anyEquals(int value, int... of) { + for (int v : of) { + if (value == v) { + return true; + } + } + return false; + } + + /** + * Helper method to process color values for Litho components. + * + * @param originalValue The original color value. + * @param darkValues Array of dark mode color values to match. + * @param lightValues Array of light mode color values to match. + * @return The new or original color value. + */ + protected static int processColorValue(int originalValue, int[] darkValues, @Nullable int[] lightValues) { + if (Utils.isDarkModeEnabled()) { + if (anyEquals(originalValue, darkValues)) { + return BLACK_COLOR; + } + } else if (lightValues != null && anyEquals(originalValue, lightValues)) { + return WHITE_COLOR; + } + + return originalValue; + } +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/theme/ThemePatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/theme/ThemePatch.java index 281fe0936..16e0b0459 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/theme/ThemePatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/theme/ThemePatch.java @@ -1,16 +1,13 @@ package app.revanced.extension.youtube.patches.theme; -import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle.styleFromOrdinal; - import androidx.annotation.Nullable; import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; +import app.revanced.extension.shared.theme.BaseThemePatch; import app.revanced.extension.youtube.settings.Settings; @SuppressWarnings("unused") -public class ThemePatch { - +public class ThemePatch extends BaseThemePatch { public enum SplashScreenAnimationStyle { DEFAULT(0), FPS_60_ONE_SECOND(1), @@ -43,57 +40,39 @@ public class ThemePatch { } } - // color constants used in relation with litho components + // Color constants used in relation with litho components. private static final int[] WHITE_VALUES = { - -1, // comments chip background - -394759, // music related results panel background - -83886081, // video chapters list background + 0xFFFFFFFF, // Comments chip background. + 0xFFF9F9F9, // Music related results panel background. + 0xFAFFFFFF, // Video chapters list background. }; private static final int[] DARK_VALUES = { - -14145496, // explore drawer background - -14606047, // comments chip background - -15198184, // music related results panel background - -15790321, // comments chip background (new layout) - -98492127 // video chapters list background + 0xFF282828, // Explore drawer background. + 0xFF212121, // Comments chip background. + 0xFF181818, // Music related results panel background. + 0xFF0F0F0F, // Comments chip background (new layout). + 0xFA212121, // Video chapters list background. }; - // Background colors. - private static final int WHITE_COLOR = Utils.getResourceColor("yt_white1"); - private static final int BLACK_COLOR = Utils.getResourceColor("yt_black1"); - - private static final boolean GRADIENT_LOADING_SCREEN_ENABLED = Settings.GRADIENT_LOADING_SCREEN.get(); - /** * Injection point. - * + *

* Change the color of Litho components. - * If the color of the component matches one of the values, return the background color . + * If the color of the component matches one of the values, return the background color. * * @param originalValue The original color value. - * @return The new or original color value + * @return The new or original color value. */ public static int getValue(int originalValue) { - if (Utils.isDarkModeEnabled()) { - if (anyEquals(originalValue, DARK_VALUES)) return BLACK_COLOR; - } else { - if (anyEquals(originalValue, WHITE_VALUES)) return WHITE_COLOR; - } - - return originalValue; - } - - private static boolean anyEquals(int value, int... of) { - for (int v : of) if (value == v) return true; - - return false; + return processColorValue(originalValue, DARK_VALUES, WHITE_VALUES); } /** * Injection point. */ public static boolean gradientLoadingScreenEnabled(boolean original) { - return GRADIENT_LOADING_SCREEN_ENABLED; + return Settings.GRADIENT_LOADING_SCREEN.get(); } /** @@ -108,7 +87,7 @@ public class ThemePatch { final int replacement = style.style; if (original != replacement) { Logger.printDebug(() -> "Overriding splash screen style from: " - + styleFromOrdinal(original) + " to: " + style); + + SplashScreenAnimationStyle.styleFromOrdinal(original) + " to: " + style); } return replacement; diff --git a/patches/api/patches.api b/patches/api/patches.api index ab0252db1..7ac39ad52 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -388,6 +388,10 @@ public final class app/revanced/patches/music/layout/premium/HideGetPremiumPatch public static final fun getHideGetPremiumPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/music/layout/theme/ThemePatchKt { + public static final fun getThemePatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/music/layout/upgradebutton/HideUpgradeButtonPatchKt { public static final fun getHideUpgradeButton ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getRemoveUpgradeButton ()Lapp/revanced/patcher/patch/BytecodePatch; @@ -733,6 +737,11 @@ public final class app/revanced/patches/serviceportalbund/detection/root/RootDet public static final fun getRootDetectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/shared/layout/theme/LithoColorHookPatchKt { + public static final fun getLithoColorHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch; + public static final fun getLithoColorOverrideHook ()Lkotlin/jvm/functions/Function2; +} + public final class app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentPatchKt { public static final fun checkEnvironmentPatch (Lapp/revanced/patcher/Fingerprint;Lapp/revanced/patcher/patch/Patch;[Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/navigationbar/NavigationBarPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/navigationbar/NavigationBarPatch.kt index e9598f576..4459700cb 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/navigationbar/NavigationBarPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/navigationbar/NavigationBarPatch.kt @@ -4,6 +4,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.patch.resourcePatch import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.music.misc.extension.sharedExtensionPatch @@ -35,7 +36,23 @@ val navigationBarPatch = bytecodePatch( resourceMappingPatch, sharedExtensionPatch, settingsPatch, - addResourcesPatch + addResourcesPatch, + resourcePatch { + execute { + // Ensure the first ImageView has 'layout_weight' to stay properly sized + // when the TextView is hidden. + document("res/layout/image_with_text_tab.xml").use { document -> + val imageView = document.getElementsByTagName("ImageView").item(0) + imageView?.let { + if (it.attributes.getNamedItem("android:layout_weight") == null) { + val attr = document.createAttribute("android:layout_weight") + attr.value = "0.5" + it.attributes.setNamedItem(attr) + } + } + } + } + } ) compatibleWith( @@ -46,10 +63,7 @@ val navigationBarPatch = bytecodePatch( ) execute { - text1 = resourceMappings[ - "id", - "text1", - ] + text1 = resourceMappings["id", "text1"] addResources("music", "layout.navigationbar.navigationBarPatch") @@ -71,9 +85,7 @@ val navigationBarPatch = bytecodePatch( ) tabLayoutTextFingerprint.method.apply { - /** - * Hide navigation labels. - */ + // Hide navigation labels. val constIndex = indexOfFirstLiteralInstructionOrThrow(text1) val targetIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.CHECK_CAST) val targetParameter = getInstruction(targetIndex).reference @@ -87,9 +99,7 @@ val navigationBarPatch = bytecodePatch( "invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->hideNavigationLabel(Landroid/widget/TextView;)V" ) - /** - * Set navigation enum and hide navigation buttons. - */ + // Set navigation enum and hide navigation buttons. val enumIndex = tabLayoutTextFingerprint.patternMatch!!.startIndex + 3 val enumRegister = getInstruction(enumIndex).registerA val insertEnumIndex = indexOfFirstInstructionOrThrow(Opcode.AND_INT_LIT8) - 2 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 000000000..c6e7091c6 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/theme/ThemePatch.kt @@ -0,0 +1,43 @@ +package app.revanced.patches.music.layout.theme + +import app.revanced.patches.music.misc.extension.sharedExtensionPatch +import app.revanced.patches.shared.layout.theme.THEME_DEFAULT_DARK_COLOR_NAMES +import app.revanced.patches.shared.layout.theme.baseThemePatch +import app.revanced.patches.shared.layout.theme.baseThemeResourcePatch +import app.revanced.patches.shared.layout.theme.darkThemeBackgroundColorOption +import app.revanced.patches.shared.misc.settings.overrideThemeColors + +private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/theme/ThemePatch;" + +@Suppress("unused") +val themePatch = baseThemePatch( + extensionClassDescriptor = EXTENSION_CLASS_DESCRIPTOR, + + block = { + dependsOn( + sharedExtensionPatch, + baseThemeResourcePatch( + darkColorNames = THEME_DEFAULT_DARK_COLOR_NAMES + setOf( + "yt_black_pure", + "yt_black_pure_opacity80", + "ytm_color_grey_12", + "material_grey_800" + ) + ) + ) + + compatibleWith( + "com.google.android.apps.youtube.music"( + "7.29.52", + "8.10.52" + ) + ) + }, + + executeBlock = { + overrideThemeColors( + null, + darkThemeBackgroundColorOption.value!! + ) + } +) diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/BaseThemePatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/BaseThemePatch.kt new file mode 100644 index 000000000..de070fb89 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/BaseThemePatch.kt @@ -0,0 +1,133 @@ +package app.revanced.patches.shared.layout.theme + +import app.revanced.patcher.patch.BytecodePatchBuilder +import app.revanced.patcher.patch.BytecodePatchContext +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.util.childElementsSequence +import java.util.Locale + +internal const val THEME_COLOR_OPTION_DESCRIPTION = "Can be a hex color (#RRGGBB) or a color resource reference." + +internal val THEME_DEFAULT_DARK_COLOR_NAMES = setOf( + "yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98", + "yt_black2", "yt_black3", "yt_black4", "yt_status_bar_background_dark", + "material_grey_850" +) + +internal val THEME_DEFAULT_LIGHT_COLOR_NAMES = setOf( + "yt_white1", "yt_white1_opacity95", "yt_white1_opacity98", + "yt_white2", "yt_white3", "yt_white4" +) + +/** + * @param colorString #AARRGGBB #RRGGBB, or an Android color resource name. + */ +internal fun validateColorName(colorString: String): Boolean { + if (colorString.startsWith("#")) { + // #RRGGBB or #AARRGGBB + val hex = colorString.substring(1).uppercase(Locale.US) + + if (hex.length == 8) { + // Transparent colors will crash the app. + if (hex[0] != 'F' || hex[1] != 'F') { + return false + } + } else if (hex.length != 6) { + return false + } + + return hex.all { it.isDigit() || it in 'A'..'F' } + } + + if (colorString.startsWith("@android:color/")) { + // Cannot easily validate Android built-in colors, so assume it's a correct color. + return true + } + + // Allow any color name, because if it's invalid it will + // throw an exception during resource compilation. + return colorString.startsWith("@color/") +} + +/** + * Dark theme color option for YouTube and YT Music Theme patches. + */ +internal val darkThemeBackgroundColorOption = stringOption( + key = "darkThemeBackgroundColor", + default = "@android:color/black", + values = mapOf( + "Pure black" to "@android:color/black", + "Material You" to "@android:color/system_neutral1_900", + "Classic (old YouTube)" to "#212121", + "Catppuccin (Mocha)" to "#181825", + "Dark pink" to "#290025", + "Dark blue" to "#001029", + "Dark green" to "#002905", + "Dark yellow" to "#282900", + "Dark orange" to "#291800", + "Dark red" to "#290000", + ), + title = "Dark theme background color", + description = THEME_COLOR_OPTION_DESCRIPTION +) + +/** + * Shared theme patch for YouTube and YT Music. + */ +internal fun baseThemePatch( + extensionClassDescriptor: String, + block: BytecodePatchBuilder.() -> Unit = {}, + executeBlock: BytecodePatchContext.() -> Unit = {} +) = bytecodePatch( + name = "Theme", + description = "Adds options for theming and applies a custom background theme " + + "(dark background theme defaults to pure black).", +) { + darkThemeBackgroundColorOption() + + block() + + dependsOn(lithoColorHookPatch) + + execute { + executeBlock() + + lithoColorOverrideHook(extensionClassDescriptor, "getValue") + } +} + +internal fun baseThemeResourcePatch( + darkColorNames: Set = THEME_DEFAULT_DARK_COLOR_NAMES, + lightColorNames: Set = THEME_DEFAULT_LIGHT_COLOR_NAMES, + lightColorReplacement: (() -> String)? = null +) = resourcePatch { + + execute { + // After patch option validators are fixed https://github.com/ReVanced/revanced-patcher/issues/372 + // This should changed to a patch option validator. + val darkColor by darkThemeBackgroundColorOption + if (!validateColorName(darkColor!!)) { + throw PatchException("Invalid dark theme color: $darkColor") + } + + val lightColor = lightColorReplacement?.invoke() + if (lightColor != null && !validateColorName(lightColor)) { + throw PatchException("Invalid light theme color: $lightColor") + } + + document("res/values/colors.xml").use { document -> + val resourcesNode = document.getElementsByTagName("resources").item(0) + + resourcesNode.childElementsSequence().forEach { node -> + val name = node.getAttribute("name") + when { + name in darkColorNames -> node.textContent = darkColor + lightColor != null && name in lightColorNames -> node.textContent = lightColor + } + } + } + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/Fingerprints.kt new file mode 100644 index 000000000..41a042796 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/Fingerprints.kt @@ -0,0 +1,24 @@ +package app.revanced.patches.shared.layout.theme + +import app.revanced.patcher.fingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +internal val lithoOnBoundsChangeFingerprint = fingerprint { + accessFlags(AccessFlags.PROTECTED, AccessFlags.FINAL) + returns("V") + parameters("Landroid/graphics/Rect;") + opcodes( + Opcode.IGET, + Opcode.IF_EQZ, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.IF_NEZ, + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.RETURN_VOID, + ) + custom { method, _ -> + method.name == "onBoundsChange" + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/LithoColorHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/LithoColorHookPatch.kt new file mode 100644 index 000000000..bfa1259a0 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/layout/theme/LithoColorHookPatch.kt @@ -0,0 +1,27 @@ +package app.revanced.patches.shared.layout.theme + +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.patch.bytecodePatch + +lateinit var lithoColorOverrideHook: (targetMethodClass: String, targetMethodName: String) -> Unit + private set + +val lithoColorHookPatch = bytecodePatch( + description = "Adds a hook to set color of Litho components.", +) { + + execute { + var insertionIndex = lithoOnBoundsChangeFingerprint.patternMatch!!.endIndex - 1 + + lithoColorOverrideHook = { targetMethodClass, targetMethodName -> + lithoOnBoundsChangeFingerprint.method.addInstructions( + insertionIndex, + """ + invoke-static { p1 }, $targetMethodClass->$targetMethodName(I)I + move-result p1 + """ + ) + insertionIndex += 2 + } + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/SettingsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/SettingsPatch.kt index b2800bf4a..9d13d1e55 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/SettingsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/SettingsPatch.kt @@ -26,26 +26,26 @@ fun settingsPatch ( preferences: Set, ) = settingsPatch(listOf(rootPreference), preferences) -private var themeForegroundColor : String? = null -private var themeBackgroundColor : String? = null +private var lightThemeColor : String? = null +private var darkThemeColor : String? = null /** * Sets the default theme colors used in various ReVanced specific settings menus. * By default these colors are white and black, but instead can be set to the * same color the target app uses for it's own settings. */ -fun overrideThemeColors(foregroundColor: String, backgroundColor: String) { - themeForegroundColor = foregroundColor - themeBackgroundColor = backgroundColor +fun overrideThemeColors(lightThemeColorString: String?, darkThemeColorString: String) { + lightThemeColor = lightThemeColorString + darkThemeColor = darkThemeColorString } private val settingsColorPatch = bytecodePatch { finalize { - if (themeForegroundColor != null) { - themeLightColorResourceNameFingerprint.method.returnEarly(themeForegroundColor!!) + if (lightThemeColor != null) { + themeLightColorResourceNameFingerprint.method.returnEarly(lightThemeColor!!) } - if (themeBackgroundColor != null) { - themeDarkColorResourceNameFingerprint.method.returnEarly(themeBackgroundColor!!) + if (darkThemeColor != null) { + themeDarkColorResourceNameFingerprint.method.returnEarly(darkThemeColor!!) } } } 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 76fe99b31..f89175c8f 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,13 +10,12 @@ import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable +import app.revanced.patches.shared.layout.theme.lithoColorHookPatch +import app.revanced.patches.shared.layout.theme.lithoColorOverrideHook 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.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_34_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_46_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_49_or_greater @@ -108,11 +107,6 @@ private val seekbarColorResourcePatch = resourcePatch { scaleNode.replaceChild(replacementNode, shapeNode) } - - if (!is_19_25_or_greater) { - return@execute - } - ytYoutubeMagentaColorId = resourceMappings[ "color", "yt_youtube_magenta", @@ -260,10 +254,6 @@ val seekbarColorPatch = bytecodePatch( lithoColorOverrideHook(EXTENSION_CLASS_DESCRIPTOR, "getLithoColor") - if (!is_19_25_or_greater) { - return@execute - } - // 19.25+ changes arrayOf( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/Fingerprints.kt index 583750c06..69df83109 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/Fingerprints.kt @@ -3,27 +3,6 @@ package app.revanced.patches.youtube.layout.theme import app.revanced.patcher.fingerprint import app.revanced.patches.youtube.shared.YOUTUBE_MAIN_ACTIVITY_CLASS_TYPE import app.revanced.util.literal -import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.Opcode - -internal val lithoThemeFingerprint = fingerprint { - accessFlags(AccessFlags.PROTECTED, AccessFlags.FINAL) - returns("V") - parameters("Landroid/graphics/Rect;") - opcodes( - Opcode.IGET, - Opcode.IF_EQZ, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT, - Opcode.IF_NEZ, - Opcode.IGET_OBJECT, - Opcode.INVOKE_VIRTUAL, - Opcode.RETURN_VOID, - ) - custom { method, _ -> - method.name == "onBoundsChange" - } -} internal const val GRADIENT_LOADING_SCREEN_AB_CONSTANT = 45412406L diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/LithoColorHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/LithoColorHookPatch.kt index fdab6c4b8..a0bd5e716 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/LithoColorHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/LithoColorHookPatch.kt @@ -1,28 +1,19 @@ package app.revanced.patches.youtube.layout.theme -import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.patch.bytecodePatch + +@Deprecated("Function was moved", ReplaceWith("app.revanced.patches.shared.layout.theme.lithoColorOverrideHook")) +@Suppress("unused") lateinit var lithoColorOverrideHook: (targetMethodClass: String, targetMethodName: String) -> Unit private set -val lithoColorHookPatch = bytecodePatch( - description = "Adds a hook to set color of Litho components.", -) { +@Deprecated("Patch was moved", ReplaceWith("app.revanced.patches.shared.layout.theme.lithoColorHookPatch")) +@Suppress("unused") +val lithoColorHookPatch = bytecodePatch{ + dependsOn(app.revanced.patches.shared.layout.theme.lithoColorHookPatch) execute { - - var insertionIndex = lithoThemeFingerprint.patternMatch!!.endIndex - 1 - - lithoColorOverrideHook = { targetMethodClass, targetMethodName -> - lithoThemeFingerprint.method.addInstructions( - insertionIndex, - """ - invoke-static { p1 }, $targetMethodClass->$targetMethodName(I)I - move-result p1 - """, - ) - insertionIndex += 2 - } + lithoColorOverrideHook = app.revanced.patches.shared.layout.theme.lithoColorOverrideHook } } 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 a43833449..e6022b059 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,14 +1,16 @@ package app.revanced.patches.youtube.layout.theme 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.layout.theme.THEME_COLOR_OPTION_DESCRIPTION +import app.revanced.patches.shared.layout.theme.baseThemePatch +import app.revanced.patches.shared.layout.theme.baseThemeResourcePatch +import app.revanced.patches.shared.layout.theme.darkThemeBackgroundColorOption import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.settings.overrideThemeColors -import app.revanced.patches.shared.misc.settings.preference.BasePreference import app.revanced.patches.shared.misc.settings.preference.InputType import app.revanced.patches.shared.misc.settings.preference.ListPreference import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory @@ -17,126 +19,54 @@ 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.playservice.is_19_25_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_47_or_greater -import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.settingsPatch -import app.revanced.util.childElementsSequence import app.revanced.util.forEachChildElement import app.revanced.util.insertLiteralOverride import org.w3c.dom.Element private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/theme/ThemePatch;" -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 themePatch = baseThemePatch( + extensionClassDescriptor = EXTENSION_CLASS_DESCRIPTOR, - 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.", - ) + block = { + val lightThemeBackgroundColor by stringOption( + key = "lightThemeBackgroundColor", + default = "@android:color/white", + values = mapOf( + "White" to "@android:color/white", + "Material You" to "@android:color/system_neutral1_50", + "Catppuccin (Latte)" to "#E6E9EF", + "Light pink" to "#FCCFF3", + "Light blue" to "#D1E0FF", + "Light green" to "#CCFFCC", + "Light yellow" to "#FDFFCC", + "Light orange" to "#FFE6CC", + "Light red" to "#FFD6D6", + ), + title = "Light theme background color", + description = THEME_COLOR_OPTION_DESCRIPTION + ) - 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( - sharedExtensionPatch, - settingsPatch, - addResourcesPatch, - lithoColorHookPatch, - seekbarColorPatch, - versionCheckPatch, - resourcePatch { - dependsOn( - settingsPatch, - resourceMappingPatch, - ) + val themeResourcePatch = resourcePatch { + dependsOn(resourceMappingPatch) execute { - val preferences = mutableSetOf( - SwitchPreference("revanced_seekbar_custom_color"), - TextPreference("revanced_seekbar_custom_color_primary", - tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference", - inputType = InputType.TEXT_CAP_CHARACTERS), + overrideThemeColors( + lightThemeBackgroundColor!!, + darkThemeBackgroundColorOption.value!! ) - if (is_19_25_or_greater) { - preferences += TextPreference("revanced_seekbar_custom_color_accent", - tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference", - inputType = InputType.TEXT_CAP_CHARACTERS) - } - - PreferenceScreen.SEEKBAR.addPreferences( - PreferenceCategory( - titleKey = null, - sorting = Sorting.UNSORTED, - tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory", - preferences = preferences - ) - ) - - overrideThemeColors(lightThemeBackgroundColor!!, darkThemeBackgroundColor!!) - - // Edit theme colors via resources. - document("res/values/colors.xml").use { document -> - val resourcesNode = document.getElementsByTagName("resources").item(0) as Element - - resourcesNode.childElementsSequence().forEach { node -> - 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", - -> node.textContent = darkThemeBackgroundColor - - "yt_white1", "yt_white1_opacity95", "yt_white1_opacity98", - "yt_white2", "yt_white3", "yt_white4", - -> node.textContent = lightThemeBackgroundColor - } - } - } - fun addColorResource( resourceFile: String, colorName: String, colorValue: String, ) { document(resourceFile).use { document -> - val resourcesNode = document.getElementsByTagName("resources").item(0) as Element + val resourcesNode = + document.getElementsByTagName("resources").item(0) as Element resourcesNode.appendChild( document.createElement("color").apply { @@ -150,18 +80,31 @@ val themePatch = bytecodePatch( // Add a dynamic background color to the colors.xml file. val splashBackgroundColorKey = "revanced_splash_background_color" - addColorResource("res/values/colors.xml", splashBackgroundColorKey, lightThemeBackgroundColor!!) - addColorResource("res/values-night/colors.xml", splashBackgroundColorKey, darkThemeBackgroundColor!!) + addColorResource( + "res/values/colors.xml", + splashBackgroundColorKey, + lightThemeBackgroundColor!! + ) + addColorResource( + "res/values-night/colors.xml", + splashBackgroundColorKey, + darkThemeBackgroundColorOption.value!! + ) - // Edit splash screen files and change the background color, + // Edit splash screen files and change the background color. arrayOf( "res/drawable/quantum_launchscreen_youtube.xml", "res/drawable-sw600dp/quantum_launchscreen_youtube.xml", ).forEach editSplashScreen@{ resourceFileName -> document(resourceFileName).use { document -> - document.getElementsByTagName("layer-list").item(0).forEachChildElement { node -> + document.getElementsByTagName( + "layer-list" + ).item(0).forEachChildElement { node -> if (node.hasAttribute("android:drawable")) { - node.setAttribute("android:drawable", "@color/$splashBackgroundColorKey") + node.setAttribute( + "android:drawable", + "@color/$splashBackgroundColorKey" + ) return@editSplashScreen } } @@ -172,7 +115,6 @@ val themePatch = bytecodePatch( // 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-v27/styles.xml").use { document -> // Create a night mode specific override for the splash screen background. val style = document.createElement("style") @@ -195,29 +137,63 @@ val themePatch = bytecodePatch( style.appendChild(styleItem) } - val resourcesNode = document.getElementsByTagName("resources").item(0) as Element + val resourcesNode = + document.getElementsByTagName("resources").item(0) as Element resourcesNode.appendChild(style) } } } - ) - - compatibleWith( - "com.google.android.youtube"( - "19.34.42", - "20.07.39", - "20.13.41", - "20.14.43", + dependsOn( + sharedExtensionPatch, + settingsPatch, + addResourcesPatch, + seekbarColorPatch, + baseThemeResourcePatch( + lightColorReplacement = { lightThemeBackgroundColor!! } + ), + themeResourcePatch ) - ) - execute { + compatibleWith( + "com.google.android.youtube"( + "19.34.42", + "20.07.39", + "20.13.41", + "20.14.43", + ) + ) + }, + + executeBlock = { addResources("youtube", "layout.theme.themePatch") PreferenceScreen.GENERAL_LAYOUT.addPreferences( SwitchPreference("revanced_gradient_loading_screen") ) + val preferences = mutableSetOf( + SwitchPreference("revanced_seekbar_custom_color"), + TextPreference( + "revanced_seekbar_custom_color_primary", + tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference", + inputType = InputType.TEXT_CAP_CHARACTERS + ), + TextPreference( + "revanced_seekbar_custom_color_accent", + tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference", + inputType = InputType.TEXT_CAP_CHARACTERS + ) + ) + + PreferenceScreen.SEEKBAR.addPreferences( + PreferenceCategory( + titleKey = null, + sorting = Sorting.UNSORTED, + tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory", + preferences = preferences + ) + ) + if (is_19_47_or_greater) { PreferenceScreen.GENERAL_LAYOUT.addPreferences( ListPreference("revanced_splash_screen_animation_style") @@ -236,7 +212,5 @@ val themePatch = bytecodePatch( "$EXTENSION_CLASS_DESCRIPTOR->getLoadingScreenType(I)I" ) } - - lithoColorOverrideHook(EXTENSION_CLASS_DESCRIPTOR, "getValue") } -} +)