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.pm.PackageManager;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Color;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -799,4 +800,14 @@ public class Utils {
|
|||||||
builder.getContext().setTheme(editTextDialogStyle);
|
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";
|
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),
|
* @return The dark theme color as specified by the Theme patch (if included),
|
||||||
* or the dark mode background color unpatched YT uses.
|
* or the dark mode background color unpatched YT uses.
|
||||||
*/
|
*/
|
||||||
public static int getDarkThemeColor() {
|
public static int getDarkThemeColor() {
|
||||||
if (darkThemeColor == null) {
|
if (darkThemeColor == null) {
|
||||||
darkThemeColor = getColorInt(darkThemeResourceName());
|
darkThemeColor = getThemeColor(darkThemeResourceName(), Color.BLACK);
|
||||||
}
|
}
|
||||||
return darkThemeColor;
|
return darkThemeColor;
|
||||||
}
|
}
|
||||||
@ -71,18 +82,11 @@ public class ThemeHelper {
|
|||||||
*/
|
*/
|
||||||
public static int getLightThemeColor() {
|
public static int getLightThemeColor() {
|
||||||
if (lightThemeColor == null) {
|
if (lightThemeColor == null) {
|
||||||
lightThemeColor = getColorInt(lightThemeResourceName());
|
lightThemeColor = getThemeColor(lightThemeResourceName(), Color.WHITE);
|
||||||
}
|
}
|
||||||
return lightThemeColor;
|
return lightThemeColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getColorInt(String colorString) {
|
|
||||||
if (colorString.startsWith("#")) {
|
|
||||||
return Color.parseColor(colorString);
|
|
||||||
}
|
|
||||||
return Utils.getResourceColor(colorString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getBackgroundColor() {
|
public static int getBackgroundColor() {
|
||||||
return isDarkTheme() ? getDarkThemeColor() : getLightThemeColor();
|
return isDarkTheme() ? getDarkThemeColor() : getLightThemeColor();
|
||||||
}
|
}
|
||||||
@ -96,6 +100,6 @@ public class ThemeHelper {
|
|||||||
? "yt_black3"
|
? "yt_black3"
|
||||||
: "yt_white1";
|
: "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
|
package app.revanced.patches.spotify.layout.theme
|
||||||
|
|
||||||
import app.revanced.patcher.patch.resourcePatch
|
import app.revanced.patcher.patch.resourcePatch
|
||||||
import app.revanced.patcher.patch.stringOption
|
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val customThemePatch = resourcePatch(
|
val customThemePatch = resourcePatch(
|
||||||
name = "Custom theme",
|
name = "Custom theme",
|
||||||
description = "Applies a custom theme.",
|
description = "Applies a custom theme (defaults to amoled black)",
|
||||||
use = false,
|
use = false,
|
||||||
) {
|
) {
|
||||||
compatibleWith("com.spotify.music")
|
compatibleWith("com.spotify.music")
|
||||||
|
|
||||||
val backgroundColor by stringOption(
|
dependsOn(customThemeByteCodePatch)
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
val backgroundColorSecondary by stringOption(
|
val backgroundColor by spotifyBackgroundColor()
|
||||||
key = "backgroundColorSecondary",
|
val backgroundColorSecondary by spotifyBackgroundColorSecondary()
|
||||||
default = "#ff282828",
|
val accentColor by spotifyAccentColor()
|
||||||
title = "Secondary background color",
|
val accentColorPressed by spotifyAccentColorPressed()
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
val backgroundColor = backgroundColor!!
|
|
||||||
val backgroundColorSecondary = backgroundColorSecondary!!
|
|
||||||
val accentColor = accentColor!!
|
|
||||||
val accentColorPressed = accentColorPressed!!
|
|
||||||
|
|
||||||
document("res/values/colors.xml").use { document ->
|
document("res/values/colors.xml").use { document ->
|
||||||
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
|
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
|
||||||
|
|
||||||
@ -61,20 +26,37 @@ val customThemePatch = resourcePatch(
|
|||||||
for (i in 0 until childNodes.length) {
|
for (i in 0 until childNodes.length) {
|
||||||
val node = childNodes.item(i) as? Element ?: continue
|
val node = childNodes.item(i) as? Element ?: continue
|
||||||
|
|
||||||
node.textContent =
|
node.textContent = when (node.getAttribute("name")) {
|
||||||
when (node.getAttribute("name")) {
|
// Gradient next to user photo and "All" in home page
|
||||||
"dark_base_background_elevated_base", "design_dark_default_color_background",
|
"dark_base_background_base",
|
||||||
"design_dark_default_color_surface", "gray_7", "gray_background", "gray_layer",
|
// Main background
|
||||||
"sthlm_blk",
|
"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
|
-> 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_base", "dark_base_text_brightaccent", "green_light" -> accentColor
|
||||||
|
"dark_brightaccent_background_press" -> accentColorPressed
|
||||||
"dark_brightaccent_background_press" -> accentColorPressed
|
else -> continue
|
||||||
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