mirror of
https://github.com/revanced/revanced-patches
synced 2024-12-26 19:45:51 +01:00
feat(YouTube - Theme): Apply custom seekbar color to splash screen animation (#3978)
This commit is contained in:
parent
c67d0c042c
commit
98d57e28af
@ -2,9 +2,12 @@ package app.revanced.extension.youtube.patches.theme;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
@ -16,7 +19,8 @@ public final class SeekbarColorPatch {
|
||||
private static final boolean SEEKBAR_CUSTOM_COLOR_ENABLED = Settings.SEEKBAR_CUSTOM_COLOR.get();
|
||||
|
||||
/**
|
||||
* Default color of the seekbar.
|
||||
* Default color of the litho seekbar.
|
||||
* Differs slightly from the default custom seekbar color setting.
|
||||
*/
|
||||
private static final int ORIGINAL_SEEKBAR_COLOR = 0xFFFF0000;
|
||||
|
||||
@ -72,12 +76,76 @@ public final class SeekbarColorPatch {
|
||||
return seekbarColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
public static boolean playerSeekbarGradientEnabled(boolean original) {
|
||||
if (SEEKBAR_CUSTOM_COLOR_ENABLED) return false;
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
public static boolean useLotteLaunchSplashScreen(boolean original) {
|
||||
Logger.printDebug(() -> "useLotteLaunchSplashScreen original: " + original);
|
||||
|
||||
if (SEEKBAR_CUSTOM_COLOR_ENABLED) return false;
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
private static int colorChannelTo3Bits(int channel8Bits) {
|
||||
final float channel3Bits = channel8Bits * 7 / 255f;
|
||||
|
||||
// If a color channel is near zero, then allow rounding up so values between
|
||||
// 0x12 and 0x23 will show as 0x24. But always round down when the channel is
|
||||
// near full saturation, otherwise rounding to nearest will cause all values
|
||||
// between 0xEC and 0xFE to always show as full saturation (0xFF).
|
||||
return channel3Bits < 6
|
||||
? Math.round(channel3Bits)
|
||||
: (int) channel3Bits;
|
||||
}
|
||||
|
||||
private static String get9BitStyleIdentifier(int color24Bit) {
|
||||
final int r3 = colorChannelTo3Bits(Color.red(color24Bit));
|
||||
final int g3 = colorChannelTo3Bits(Color.green(color24Bit));
|
||||
final int b3 = colorChannelTo3Bits(Color.blue(color24Bit));
|
||||
|
||||
return String.format(Locale.US, "splash_seekbar_color_style_%d_%d_%d", r3, g3, b3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
public static void setSplashAnimationDrawableTheme(AnimatedVectorDrawable vectorDrawable) {
|
||||
// Alternatively a ColorMatrixColorFilter can be used to change the color of the drawable
|
||||
// without using any styles, but a color filter cannot selectively change the seekbar
|
||||
// while keeping the red YT logo untouched.
|
||||
// Even if the seekbar color xml value is changed to a completely different color (such as green),
|
||||
// a color filter still cannot be selectively applied when the drawable has more than 1 color.
|
||||
try {
|
||||
String seekbarStyle = get9BitStyleIdentifier(seekbarColor);
|
||||
Logger.printDebug(() -> "Using splash seekbar style: " + seekbarStyle);
|
||||
|
||||
final int styleIdentifierDefault = Utils.getResourceIdentifier(
|
||||
seekbarStyle,
|
||||
"style"
|
||||
);
|
||||
if (styleIdentifierDefault == 0) {
|
||||
throw new RuntimeException("Seekbar style not found: " + seekbarStyle);
|
||||
}
|
||||
|
||||
Resources.Theme theme = Utils.getContext().getResources().newTheme();
|
||||
theme.applyStyle(styleIdentifierDefault, true);
|
||||
|
||||
vectorDrawable.applyTheme(theme);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "setSplashAnimationDrawableTheme failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
@ -189,4 +257,4 @@ public final class SeekbarColorPatch {
|
||||
private static float clamp(float value, float lower, float upper) {
|
||||
return Math.max(lower, Math.min(value, upper));
|
||||
}
|
||||
}
|
||||
}
|
@ -1328,6 +1328,7 @@ public final class app/revanced/patches/youtube/misc/playservice/VersionCheckPat
|
||||
public static final fun is_19_36_or_greater ()Z
|
||||
public static final fun is_19_41_or_greater ()Z
|
||||
public static final fun is_19_43_or_greater ()Z
|
||||
public static final fun is_19_46_or_greater ()Z
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/misc/privacy/RemoveTrackingQueryParameterPatchKt {
|
||||
|
@ -48,3 +48,17 @@ internal val lithoLinearGradientFingerprint = fingerprint {
|
||||
returns("Landroid/graphics/LinearGradient;")
|
||||
parameters("F", "F", "F", "F", "[I", "[F")
|
||||
}
|
||||
|
||||
internal const val launchScreenLayoutTypeLotteFeatureFlag = 268507948L
|
||||
|
||||
internal val launchScreenLayoutTypeFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
returns("V")
|
||||
custom { method, _ ->
|
||||
val firstParameter = method.parameterTypes.firstOrNull()
|
||||
// 19.25 - 19.45
|
||||
(firstParameter == "Lcom/google/android/apps/youtube/app/watchwhile/MainActivity;"
|
||||
|| firstParameter == "Landroid/app/Activity;") // 19.46+
|
||||
&& method.containsLiteralInstruction(launchScreenLayoutTypeLotteFeatureFlag)
|
||||
}
|
||||
}
|
||||
|
@ -13,15 +13,24 @@ 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_23_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.is_19_46_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
|
||||
import app.revanced.util.copyXmlNode
|
||||
import app.revanced.util.findElementByAttributeValueOrThrow
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
||||
import app.revanced.util.inputStreamFromBundledResource
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import org.w3c.dom.Element
|
||||
import java.io.ByteArrayInputStream
|
||||
import kotlin.use
|
||||
|
||||
internal var reelTimeBarPlayedColorId = -1L
|
||||
private set
|
||||
@ -30,6 +39,8 @@ internal var inlineTimeBarColorizedBarPlayedColorDarkId = -1L
|
||||
internal var inlineTimeBarPlayedNotHighlightedColorId = -1L
|
||||
private set
|
||||
|
||||
internal const val splashSeekbarColorAttributeName = "splash_custom_seekbar_color"
|
||||
|
||||
private val seekbarColorResourcePatch = resourcePatch {
|
||||
dependsOn(
|
||||
settingsPatch,
|
||||
@ -51,9 +62,8 @@ private val seekbarColorResourcePatch = resourcePatch {
|
||||
"inline_time_bar_played_not_highlighted_color",
|
||||
]
|
||||
|
||||
// Edit the resume playback drawable and replace the progress bar with a custom drawable
|
||||
// Modify the resume playback drawable and replace the progress bar with a custom drawable.
|
||||
document("res/drawable/resume_playback_progressbar_drawable.xml").use { document ->
|
||||
|
||||
val layerList = document.getElementsByTagName("layer-list").item(0) as Element
|
||||
val progressNode = layerList.getElementsByTagName("item").item(1) as Element
|
||||
if (!progressNode.getAttributeNode("android:id").value.endsWith("progress")) {
|
||||
@ -66,9 +76,102 @@ private val seekbarColorResourcePatch = resourcePatch {
|
||||
)
|
||||
scaleNode.replaceChild(replacementNode, shapeNode)
|
||||
}
|
||||
|
||||
|
||||
if (!is_19_25_or_greater) {
|
||||
return@execute
|
||||
}
|
||||
|
||||
// Add attribute and styles for splash screen custom color.
|
||||
// Using a style is the only way to selectively change just the seekbar fill color.
|
||||
//
|
||||
// Because the style colors must be hard coded for all color possibilities,
|
||||
// instead of allowing 24 bit color the style is restricted to 9-bit (3 bits per color channel)
|
||||
// and the style color closest to the users custom color is used for the splash screen.
|
||||
arrayOf(
|
||||
inputStreamFromBundledResource("seekbar/values", "attrs.xml")!! to "res/values/attrs.xml",
|
||||
ByteArrayInputStream(create9BitSeekbarColorStyles().toByteArray()) to "res/values/styles.xml"
|
||||
).forEach { (source, destination) ->
|
||||
"resources".copyXmlNode(
|
||||
document(source),
|
||||
document(destination),
|
||||
).close()
|
||||
}
|
||||
|
||||
fun setSplashDrawablePathFillColor(xmlFileNames: Iterable<String>, vararg resourceNames: String) {
|
||||
xmlFileNames.forEach { xmlFileName ->
|
||||
document(xmlFileName).use { document ->
|
||||
resourceNames.forEach { elementId ->
|
||||
val element = document.childNodes.findElementByAttributeValueOrThrow(
|
||||
"android:name",
|
||||
elementId
|
||||
)
|
||||
|
||||
val attribute = "android:fillColor"
|
||||
if (!element.hasAttribute(attribute)) {
|
||||
throw PatchException("Could not find $attribute for $elementId")
|
||||
}
|
||||
|
||||
element.setAttribute(attribute, "?attr/$splashSeekbarColorAttributeName")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setSplashDrawablePathFillColor(
|
||||
listOf(
|
||||
"res/drawable/\$startup_animation_light__0.xml",
|
||||
"res/drawable/\$startup_animation_dark__0.xml"
|
||||
),
|
||||
"_R_G_L_10_G_D_0_P_0"
|
||||
)
|
||||
|
||||
if (!is_19_46_or_greater) {
|
||||
// Resources removed in 19.46+
|
||||
setSplashDrawablePathFillColor(
|
||||
listOf(
|
||||
"res/drawable/\$buenos_aires_animation_light__0.xml",
|
||||
"res/drawable/\$buenos_aires_animation_dark__0.xml"
|
||||
),
|
||||
"_R_G_L_8_G_D_0_P_0"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a style xml with all combinations of 9-bit colors.
|
||||
*/
|
||||
private fun create9BitSeekbarColorStyles(): String = StringBuilder().apply {
|
||||
append("<?xml version=\"1.0\" encoding=\"utf-8\"?>")
|
||||
append("<resources>\n")
|
||||
|
||||
for (red in 0..7) {
|
||||
for (green in 0..7) {
|
||||
for (blue in 0..7) {
|
||||
val name = "${red}_${green}_${blue}"
|
||||
|
||||
fun roundTo3BitHex(channel8Bits: Int) =
|
||||
(channel8Bits * 255 / 7).toString(16).padStart(2, '0')
|
||||
val r = roundTo3BitHex(red)
|
||||
val g = roundTo3BitHex(green)
|
||||
val b = roundTo3BitHex(blue)
|
||||
val color = "#ff$r$g$b"
|
||||
|
||||
append(
|
||||
"""
|
||||
<style name="splash_seekbar_color_style_$name">
|
||||
<item name="$splashSeekbarColorAttributeName">$color</item>
|
||||
</style>
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
append("</resources>")
|
||||
}.toString()
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/theme/SeekbarColorPatch;"
|
||||
|
||||
val seekbarColorPatch = bytecodePatch(
|
||||
@ -117,27 +220,73 @@ val seekbarColorPatch = bytecodePatch(
|
||||
}
|
||||
}
|
||||
|
||||
if (is_19_23_or_greater) {
|
||||
playerSeekbarGradientConfigFingerprint.method.apply {
|
||||
val literalIndex = indexOfFirstLiteralInstructionOrThrow(PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG)
|
||||
lithoColorOverrideHook(EXTENSION_CLASS_DESCRIPTOR, "getLithoColor")
|
||||
|
||||
if (!is_19_25_or_greater) {
|
||||
return@execute
|
||||
}
|
||||
|
||||
// 19.25+ changes
|
||||
|
||||
playerSeekbarGradientConfigFingerprint.method.apply {
|
||||
val literalIndex = indexOfFirstLiteralInstructionOrThrow(PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG)
|
||||
val resultIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT)
|
||||
val register = getInstruction<OneRegisterInstruction>(resultIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
resultIndex + 1,
|
||||
"""
|
||||
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->playerSeekbarGradientEnabled(Z)Z
|
||||
move-result v$register
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
lithoLinearGradientFingerprint.method.addInstruction(
|
||||
0,
|
||||
"invoke-static/range { p4 .. p5 }, $EXTENSION_CLASS_DESCRIPTOR->setLinearGradient([I[F)V"
|
||||
)
|
||||
|
||||
|
||||
// region apply seekbar custom color to splash screen animation.
|
||||
|
||||
// Don't use the lotte splash screen layout if using custom seekbar.
|
||||
arrayOf(
|
||||
launchScreenLayoutTypeFingerprint,
|
||||
mainActivityOnCreateFingerprint
|
||||
).forEach { fingerprint ->
|
||||
fingerprint.method.apply {
|
||||
val literalIndex = indexOfFirstLiteralInstructionOrThrow(launchScreenLayoutTypeLotteFeatureFlag)
|
||||
val resultIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT)
|
||||
val register = getInstruction<OneRegisterInstruction>(resultIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
resultIndex + 1,
|
||||
"""
|
||||
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->playerSeekbarGradientEnabled(Z)Z
|
||||
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->useLotteLaunchSplashScreen(Z)Z
|
||||
move-result v$register
|
||||
""",
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
lithoLinearGradientFingerprint.method.addInstruction(
|
||||
0,
|
||||
"invoke-static/range { p4 .. p5 }, $EXTENSION_CLASS_DESCRIPTOR->setLinearGradient([I[F)V",
|
||||
// Hook the splash animation drawable to set the a seekbar color theme.
|
||||
mainActivityOnCreateFingerprint.method.apply {
|
||||
val drawableIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.definingClass == "Landroid/widget/ImageView;"
|
||||
&& reference.name == "getDrawable"
|
||||
}
|
||||
val checkCastIndex = indexOfFirstInstructionOrThrow(drawableIndex, Opcode.CHECK_CAST)
|
||||
val drawableRegister = getInstruction<OneRegisterInstruction>(checkCastIndex).registerA
|
||||
|
||||
addInstruction(
|
||||
checkCastIndex + 1,
|
||||
"invoke-static { v$drawableRegister }, $EXTENSION_CLASS_DESCRIPTOR->" +
|
||||
"setSplashAnimationDrawableTheme(Landroid/graphics/drawable/AnimatedVectorDrawable;)V"
|
||||
)
|
||||
}
|
||||
|
||||
lithoColorOverrideHook(EXTENSION_CLASS_DESCRIPTOR, "getLithoColor")
|
||||
// endregion
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ val shortsAutoplayPatch = bytecodePatch(
|
||||
|
||||
// Main activity is used to check if app is in pip mode.
|
||||
mainActivityOnCreateFingerprint.method.addInstructions(
|
||||
0,
|
||||
1,
|
||||
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->" +
|
||||
"setMainActivity(Landroid/app/Activity;)V",
|
||||
)
|
||||
|
@ -37,6 +37,8 @@ var is_19_41_or_greater = false
|
||||
private set
|
||||
var is_19_43_or_greater = false
|
||||
private set
|
||||
var is_19_46_or_greater = false
|
||||
private set
|
||||
|
||||
val versionCheckPatch = resourcePatch(
|
||||
description = "Uses the Play Store service version to find the major/minor version of the YouTube target app.",
|
||||
@ -68,5 +70,6 @@ val versionCheckPatch = resourcePatch(
|
||||
is_19_36_or_greater = 243705000 <= playStoreServicesVersion
|
||||
is_19_41_or_greater = 244305000 <= playStoreServicesVersion
|
||||
is_19_43_or_greater = 244405000 <= playStoreServicesVersion
|
||||
is_19_46_or_greater = 244705000 <= playStoreServicesVersion
|
||||
}
|
||||
}
|
||||
|
@ -114,7 +114,6 @@ fun String.copyXmlNode(
|
||||
target: Document,
|
||||
): AutoCloseable {
|
||||
val hostNodes = source.getElementsByTagName(this).item(0).childNodes
|
||||
|
||||
val destinationNode = target.getElementsByTagName(this).item(0)
|
||||
|
||||
for (index in 0 until hostNodes.length) {
|
||||
|
4
patches/src/main/resources/seekbar/values/attrs.xml
Normal file
4
patches/src/main/resources/seekbar/values/attrs.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<attr format="reference|color" name="splash_custom_seekbar_color"/>
|
||||
</resources>
|
Loading…
Reference in New Issue
Block a user