mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-04-29 22:24:27 +02:00
fix(Spotify - Custom theme): Override more color resources (#4690)
This commit is contained in:
parent
3c316fa329
commit
d7a7a0b982
@ -9,6 +9,7 @@ import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@ -799,4 +800,14 @@ public class Utils {
|
||||
builder.getContext().setTheme(editTextDialogStyle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a color resource or hex code to an int representation of the color.
|
||||
*/
|
||||
public static int getColorFromString(String colorString) throws IllegalArgumentException, Resources.NotFoundException {
|
||||
if (colorString.startsWith("#")) {
|
||||
return Color.parseColor(colorString);
|
||||
}
|
||||
return getResourceColor(colorString);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
package app.revanced.extension.spotify.layout.theme;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class CustomThemePatch {
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static long getThemeColor(String colorString) {
|
||||
try {
|
||||
return Utils.getColorFromString(colorString);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Invalid custom color: " + colorString, ex);
|
||||
return Color.BLACK;
|
||||
}
|
||||
}
|
||||
}
|
@ -45,13 +45,24 @@ public class ThemeHelper {
|
||||
return "@color/yt_black3";
|
||||
}
|
||||
|
||||
private static int getThemeColor(String resourceName, int defaultColor) {
|
||||
try {
|
||||
return Utils.getColorFromString(resourceName);
|
||||
} catch (Exception ex) {
|
||||
// User entered an invalid custom theme color.
|
||||
// Normally this should never be reached, and no localized strings are needed.
|
||||
Utils.showToastLong("Invalid custom theme color: " + resourceName);
|
||||
return defaultColor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The dark theme color as specified by the Theme patch (if included),
|
||||
* or the dark mode background color unpatched YT uses.
|
||||
*/
|
||||
public static int getDarkThemeColor() {
|
||||
if (darkThemeColor == null) {
|
||||
darkThemeColor = getColorInt(darkThemeResourceName());
|
||||
darkThemeColor = getThemeColor(darkThemeResourceName(), Color.BLACK);
|
||||
}
|
||||
return darkThemeColor;
|
||||
}
|
||||
@ -71,18 +82,11 @@ public class ThemeHelper {
|
||||
*/
|
||||
public static int getLightThemeColor() {
|
||||
if (lightThemeColor == null) {
|
||||
lightThemeColor = getColorInt(lightThemeResourceName());
|
||||
lightThemeColor = getThemeColor(lightThemeResourceName(), Color.WHITE);
|
||||
}
|
||||
return lightThemeColor;
|
||||
}
|
||||
|
||||
private static int getColorInt(String colorString) {
|
||||
if (colorString.startsWith("#")) {
|
||||
return Color.parseColor(colorString);
|
||||
}
|
||||
return Utils.getResourceColor(colorString);
|
||||
}
|
||||
|
||||
public static int getBackgroundColor() {
|
||||
return isDarkTheme() ? getDarkThemeColor() : getLightThemeColor();
|
||||
}
|
||||
@ -96,6 +100,6 @@ public class ThemeHelper {
|
||||
? "yt_black3"
|
||||
: "yt_white1";
|
||||
|
||||
return getColorInt(colorName);
|
||||
return Utils.getColorFromString(colorName);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,75 @@
|
||||
package app.revanced.patches.spotify.layout.theme
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.util.*
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/layout/theme/CustomThemePatch;"
|
||||
|
||||
internal val customThemeByteCodePatch = bytecodePatch {
|
||||
dependsOn(sharedExtensionPatch)
|
||||
|
||||
val backgroundColor by spotifyBackgroundColor
|
||||
val backgroundColorSecondary by spotifyBackgroundColorSecondary
|
||||
|
||||
execute {
|
||||
fun MutableMethod.addColorChangeInstructions(literal: Long, colorString: String) {
|
||||
val index = indexOfFirstLiteralInstructionOrThrow(literal)
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
addInstructions(
|
||||
index + 1,
|
||||
"""
|
||||
const-string v$register, "$colorString"
|
||||
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getThemeColor(Ljava/lang/String;)J
|
||||
move-result-wide v$register
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
val encoreColorsClassName = with(encoreThemeFingerprint) {
|
||||
// Find index of the first static get found after the string constant.
|
||||
val encoreColorsFieldReferenceIndex = originalMethod.indexOfFirstInstructionOrThrow(
|
||||
stringMatches!!.first().index,
|
||||
Opcode.SGET_OBJECT
|
||||
)
|
||||
|
||||
originalMethod.getInstruction(encoreColorsFieldReferenceIndex)
|
||||
.getReference<FieldReference>()!!.definingClass
|
||||
}
|
||||
|
||||
val encoreColorsConstructorFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
|
||||
custom { method, classDef ->
|
||||
classDef.type == encoreColorsClassName &&
|
||||
method.containsLiteralInstruction(PLAYLIST_BACKGROUND_COLOR_LITERAL)
|
||||
}
|
||||
}
|
||||
|
||||
encoreColorsConstructorFingerprint.method.apply {
|
||||
// Playlist song list background color.
|
||||
addColorChangeInstructions(PLAYLIST_BACKGROUND_COLOR_LITERAL, backgroundColor!!)
|
||||
|
||||
// Share menu background color.
|
||||
addColorChangeInstructions(SHARE_MENU_BACKGROUND_COLOR_LITERAL, backgroundColorSecondary!!)
|
||||
}
|
||||
|
||||
homeCategoryPillColorsFingerprint.method.apply {
|
||||
// Home category pills background color.
|
||||
addColorChangeInstructions(HOME_CATEGORY_PILL_COLOR_LITERAL, backgroundColorSecondary!!)
|
||||
}
|
||||
|
||||
settingsHeaderColorFingerprint.method.apply {
|
||||
// Settings header background color.
|
||||
addColorChangeInstructions(SETTINGS_HEADER_COLOR_LITERAL, backgroundColorSecondary!!)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,59 +1,24 @@
|
||||
@file:Suppress("NAME_SHADOWING")
|
||||
|
||||
package app.revanced.patches.spotify.layout.theme
|
||||
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import org.w3c.dom.Element
|
||||
|
||||
@Suppress("unused")
|
||||
val customThemePatch = resourcePatch(
|
||||
name = "Custom theme",
|
||||
description = "Applies a custom theme.",
|
||||
description = "Applies a custom theme (defaults to amoled black)",
|
||||
use = false,
|
||||
) {
|
||||
compatibleWith("com.spotify.music")
|
||||
|
||||
val backgroundColor by stringOption(
|
||||
key = "backgroundColor",
|
||||
default = "@android:color/black",
|
||||
title = "Primary background color",
|
||||
description = "The background color. Can be a hex color or a resource reference.",
|
||||
required = true,
|
||||
)
|
||||
dependsOn(customThemeByteCodePatch)
|
||||
|
||||
val backgroundColorSecondary by stringOption(
|
||||
key = "backgroundColorSecondary",
|
||||
default = "#ff282828",
|
||||
title = "Secondary background color",
|
||||
description = "The secondary background color. (e.g. search box, artist & podcast). Can be a hex color or a resource reference.",
|
||||
required = true,
|
||||
)
|
||||
|
||||
val accentColor by stringOption(
|
||||
key = "accentColor",
|
||||
default = "#ff1ed760",
|
||||
title = "Accent color",
|
||||
description = "The accent color ('Spotify green' by default). Can be a hex color or a resource reference.",
|
||||
required = true,
|
||||
)
|
||||
|
||||
val accentColorPressed by stringOption(
|
||||
key = "accentColorPressed",
|
||||
default = "#ff169c46",
|
||||
title = "Pressed dark theme accent color",
|
||||
description =
|
||||
"The color when accented buttons are pressed, by default slightly darker than accent. " +
|
||||
"Can be a hex color or a resource reference.",
|
||||
required = true,
|
||||
)
|
||||
val backgroundColor by spotifyBackgroundColor()
|
||||
val backgroundColorSecondary by spotifyBackgroundColorSecondary()
|
||||
val accentColor by spotifyAccentColor()
|
||||
val accentColorPressed by spotifyAccentColorPressed()
|
||||
|
||||
execute {
|
||||
val backgroundColor = backgroundColor!!
|
||||
val backgroundColorSecondary = backgroundColorSecondary!!
|
||||
val accentColor = accentColor!!
|
||||
val accentColorPressed = accentColorPressed!!
|
||||
|
||||
document("res/values/colors.xml").use { document ->
|
||||
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
|
||||
|
||||
@ -61,20 +26,37 @@ val customThemePatch = resourcePatch(
|
||||
for (i in 0 until childNodes.length) {
|
||||
val node = childNodes.item(i) as? Element ?: continue
|
||||
|
||||
node.textContent =
|
||||
when (node.getAttribute("name")) {
|
||||
"dark_base_background_elevated_base", "design_dark_default_color_background",
|
||||
"design_dark_default_color_surface", "gray_7", "gray_background", "gray_layer",
|
||||
"sthlm_blk",
|
||||
node.textContent = when (node.getAttribute("name")) {
|
||||
// Gradient next to user photo and "All" in home page
|
||||
"dark_base_background_base",
|
||||
// Main background
|
||||
"gray_7",
|
||||
// Left sidebar background in tablet mode
|
||||
"gray_10",
|
||||
// Add account, Settings and privacy, View Profile left sidebar background
|
||||
"dark_base_background_elevated_base",
|
||||
// Song/player background
|
||||
"bg_gradient_start_color", "bg_gradient_end_color",
|
||||
// Login screen
|
||||
"sthlm_blk", "sthlm_blk_grad_start", "stockholm_black",
|
||||
// Misc
|
||||
"image_placeholder_color",
|
||||
-> backgroundColor
|
||||
|
||||
"gray_15" -> backgroundColorSecondary
|
||||
// Track credits, merch in song player
|
||||
"track_credits_card_bg", "benefit_list_default_color", "merch_card_background",
|
||||
// Playlist list background in home page
|
||||
"opacity_white_10",
|
||||
// About artist background in song player
|
||||
"gray_15",
|
||||
// What's New pills background
|
||||
"dark_base_background_tinted_highlight"
|
||||
-> backgroundColorSecondary
|
||||
|
||||
"dark_brightaccent_background_base", "dark_base_text_brightaccent", "green_light" -> accentColor
|
||||
|
||||
"dark_brightaccent_background_press" -> accentColorPressed
|
||||
else -> continue
|
||||
}
|
||||
"dark_brightaccent_background_base", "dark_base_text_brightaccent", "green_light" -> accentColor
|
||||
"dark_brightaccent_background_press" -> accentColorPressed
|
||||
else -> continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
package app.revanced.patches.spotify.layout.theme
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.containsLiteralInstruction
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val encoreThemeFingerprint = fingerprint {
|
||||
strings("Encore theme was not provided.") // Partial string match.
|
||||
}
|
||||
|
||||
internal const val SETTINGS_HEADER_COLOR_LITERAL = 0xFF282828
|
||||
internal const val HOME_CATEGORY_PILL_COLOR_LITERAL = 0xFF333333
|
||||
internal const val PLAYLIST_BACKGROUND_COLOR_LITERAL = 0xFF121212
|
||||
internal const val SHARE_MENU_BACKGROUND_COLOR_LITERAL = 0xFF1F1F1F
|
||||
|
||||
internal val homeCategoryPillColorsFingerprint = fingerprint{
|
||||
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
|
||||
custom { method, _ ->
|
||||
method.containsLiteralInstruction(HOME_CATEGORY_PILL_COLOR_LITERAL) &&
|
||||
method.containsLiteralInstruction(0x33000000)
|
||||
}
|
||||
}
|
||||
|
||||
internal val settingsHeaderColorFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
|
||||
custom { method, _ ->
|
||||
method.containsLiteralInstruction(SETTINGS_HEADER_COLOR_LITERAL) &&
|
||||
method.containsLiteralInstruction(0)
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package app.revanced.patches.spotify.layout.theme
|
||||
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
|
||||
internal val spotifyBackgroundColor = stringOption(
|
||||
key = "backgroundColor",
|
||||
default = "@android:color/black",
|
||||
title = "Primary background color",
|
||||
description = "The background color. Can be a hex color or a resource reference.",
|
||||
required = true,
|
||||
)
|
||||
|
||||
internal val spotifyBackgroundColorSecondary = stringOption(
|
||||
key = "backgroundColorSecondary",
|
||||
default = "#FF121212",
|
||||
title = "Secondary background color",
|
||||
description = "The secondary background color. (e.g. playlist list, player arist, credits). Can be a hex color or a resource reference.",
|
||||
required = true,
|
||||
)
|
||||
|
||||
internal val spotifyAccentColor = stringOption(
|
||||
key = "accentColor",
|
||||
default = "#FF1ED760",
|
||||
title = "Accent color",
|
||||
description = "The accent color ('Spotify green' by default). Can be a hex color or a resource reference.",
|
||||
required = true,
|
||||
)
|
||||
|
||||
internal val spotifyAccentColorPressed = stringOption(
|
||||
key = "accentColorPressed",
|
||||
default = "#FF169C46",
|
||||
title = "Pressed dark theme accent color",
|
||||
description =
|
||||
"The color when accented buttons are pressed, by default slightly darker than accent. Can be a hex color or a resource reference.",
|
||||
required = true,
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user