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")
}
-}
+)