feat(YouTube - Change header): Add in-app setting to change the app header (#5346)
|
|
@ -0,0 +1,50 @@
|
||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ChangeHeaderPatch {
|
||||||
|
|
||||||
|
public enum HeaderLogo {
|
||||||
|
DEFAULT(null),
|
||||||
|
REGULAR("ytWordmarkHeader"),
|
||||||
|
PREMIUM("ytPremiumWordmarkHeader"),
|
||||||
|
REVANCED("revanced_header_logo"),
|
||||||
|
REVANCED_MINIMAL("revanced_header_logo_minimal"),
|
||||||
|
CUSTOM("custom_header");
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final String resourceName;
|
||||||
|
|
||||||
|
HeaderLogo(@Nullable String resourceName) {
|
||||||
|
this.resourceName = resourceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The attribute id of this header logo, or NULL if the logo should not be replaced.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private Integer getAttributeId() {
|
||||||
|
if (resourceName == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Utils.getResourceIdentifier(resourceName, "attr");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static final Integer headerLogoResource = Settings.HEADER_LOGO.get().getAttributeId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static int getHeaderAttributeId(int original) {
|
||||||
|
return Objects.requireNonNullElse(headerLogoResource, original);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import static app.revanced.extension.shared.settings.Setting.parent;
|
||||||
import static app.revanced.extension.shared.settings.Setting.parentsAll;
|
import static app.revanced.extension.shared.settings.Setting.parentsAll;
|
||||||
import static app.revanced.extension.shared.settings.Setting.parentsAny;
|
import static app.revanced.extension.shared.settings.Setting.parentsAny;
|
||||||
import static app.revanced.extension.youtube.patches.ChangeFormFactorPatch.FormFactor;
|
import static app.revanced.extension.youtube.patches.ChangeFormFactorPatch.FormFactor;
|
||||||
|
import static app.revanced.extension.youtube.patches.ChangeHeaderPatch.HeaderLogo;
|
||||||
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.ChangeStartPageTypeAvailability;
|
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.ChangeStartPageTypeAvailability;
|
||||||
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.StartPage;
|
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.StartPage;
|
||||||
import static app.revanced.extension.youtube.patches.ExitFullscreenPatch.FullscreenMode;
|
import static app.revanced.extension.youtube.patches.ExitFullscreenPatch.FullscreenMode;
|
||||||
|
|
@ -238,7 +239,8 @@ public class Settings extends BaseSettings {
|
||||||
public static final EnumSetting<FormFactor> CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message");
|
public static final EnumSetting<FormFactor> CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message");
|
||||||
public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
|
public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
|
||||||
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
|
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
|
||||||
public static final EnumSetting<SplashScreenAnimationStyle> SPLASH_SCREEN_ANIMATION_STYLE = new EnumSetting<>("splash_screen_animation_style", SplashScreenAnimationStyle.FPS_60_ONE_SECOND, true);
|
public static final EnumSetting<SplashScreenAnimationStyle> SPLASH_SCREEN_ANIMATION_STYLE = new EnumSetting<>("revanced_splash_screen_animation_style", SplashScreenAnimationStyle.FPS_60_ONE_SECOND, true);
|
||||||
|
public static final EnumSetting<HeaderLogo> HEADER_LOGO = new EnumSetting<>("revanced_header_logo", HeaderLogo.DEFAULT, true);
|
||||||
|
|
||||||
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
|
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
|
||||||
"revanced_remove_viewer_discretion_dialog_user_dialog_message");
|
"revanced_remove_viewer_discretion_dialog_user_dialog_message");
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,83 @@
|
||||||
package app.revanced.patches.youtube.layout.branding.header
|
package app.revanced.patches.youtube.layout.branding.header
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
import app.revanced.patcher.patch.PatchException
|
import app.revanced.patcher.patch.PatchException
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.patch.resourcePatch
|
import app.revanced.patcher.patch.resourcePatch
|
||||||
import app.revanced.patcher.patch.stringOption
|
import app.revanced.patcher.patch.stringOption
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
|
import app.revanced.patcher.util.Document
|
||||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
|
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||||
|
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.shared.misc.settings.preference.ListPreference
|
||||||
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
import app.revanced.util.ResourceGroup
|
import app.revanced.util.ResourceGroup
|
||||||
import app.revanced.util.Utils.trimIndentMultiline
|
import app.revanced.util.Utils.trimIndentMultiline
|
||||||
import app.revanced.util.copyResources
|
import app.revanced.util.copyResources
|
||||||
import app.revanced.util.findElementByAttributeValueOrThrow
|
import app.revanced.util.findElementByAttributeValueOrThrow
|
||||||
|
import app.revanced.util.forEachLiteralValueInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
private const val HEADER_FILE_NAME = "yt_wordmark_header"
|
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
private const val PREMIUM_HEADER_FILE_NAME = "yt_premium_wordmark_header"
|
"Lapp/revanced/extension/youtube/patches/ChangeHeaderPatch;"
|
||||||
|
|
||||||
private const val HEADER_OPTION = "header*"
|
private val changeHeaderBytecodePatch = bytecodePatch {
|
||||||
private const val PREMIUM_HEADER_OPTION = "premium*header"
|
dependsOn(resourceMappingPatch)
|
||||||
private const val REVANCED_HEADER_OPTION = "revanced*"
|
|
||||||
private const val REVANCED_BORDERLESS_HEADER_OPTION = "revanced*borderless"
|
execute {
|
||||||
|
arrayOf(
|
||||||
|
"ytWordmarkHeader",
|
||||||
|
"ytPremiumWordmarkHeader"
|
||||||
|
).forEach { resourceName ->
|
||||||
|
val resourceId = resourceMappings["attr", resourceName]
|
||||||
|
|
||||||
|
forEachLiteralValueInstruction(resourceId) { literalIndex ->
|
||||||
|
val register = getInstruction<OneRegisterInstruction>(literalIndex).registerA
|
||||||
|
addInstructions(
|
||||||
|
literalIndex + 1,
|
||||||
|
"""
|
||||||
|
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getHeaderAttributeId(I)I
|
||||||
|
move-result v$register
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val targetResourceDirectoryNames = mapOf(
|
private val targetResourceDirectoryNames = mapOf(
|
||||||
"xxxhdpi" to "512px x 192px",
|
"xxxhdpi" to "512px x 192px",
|
||||||
"xxhdpi" to "387px x 144px",
|
"xxhdpi" to "387px x 144px",
|
||||||
"xhdpi" to "258px x 96px",
|
"xhdpi" to "258px x 96px",
|
||||||
"hdpi" to "194px x 72px",
|
"hdpi" to "194px x 72px",
|
||||||
"mdpi" to "129px x 48px",
|
"mdpi" to "129px x 48px"
|
||||||
).map { (dpi, dim) ->
|
).mapKeys { (dpi, _) -> "drawable-$dpi" }
|
||||||
"drawable-$dpi" to dim
|
|
||||||
}.toMap()
|
|
||||||
|
|
||||||
private val variants = arrayOf("light", "dark")
|
private val variants = arrayOf("light", "dark")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Header logos built into this patch.
|
||||||
|
*/
|
||||||
|
private val logoResourceNames = arrayOf(
|
||||||
|
"revanced_header_logo_minimal",
|
||||||
|
"revanced_header_logo",
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom header resource/file name.
|
||||||
|
*/
|
||||||
|
private const val CUSTOM_HEADER_RESOURCE_NAME = "custom_header"
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val changeHeaderPatch = resourcePatch(
|
val changeHeaderPatch = resourcePatch(
|
||||||
name = "Change header",
|
name = "Change header",
|
||||||
description = "Applies a custom header in the top left corner within the app. Defaults to the ReVanced header.",
|
description = "Adds an option to change the header logo in the top left corner of the app.",
|
||||||
use = false,
|
|
||||||
) {
|
) {
|
||||||
dependsOn(versionCheckPatch)
|
dependsOn(addResourcesPatch, changeHeaderBytecodePatch)
|
||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.google.android.youtube"(
|
"com.google.android.youtube"(
|
||||||
|
|
@ -50,85 +90,46 @@ val changeHeaderPatch = resourcePatch(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val header by stringOption(
|
val custom by stringOption(
|
||||||
key = "header",
|
key = "custom",
|
||||||
default = REVANCED_BORDERLESS_HEADER_OPTION,
|
title = "Custom header logo",
|
||||||
values = mapOf(
|
|
||||||
"YouTube" to HEADER_OPTION,
|
|
||||||
"YouTube Premium" to PREMIUM_HEADER_OPTION,
|
|
||||||
"ReVanced" to REVANCED_HEADER_OPTION,
|
|
||||||
"ReVanced (borderless logo)" to REVANCED_BORDERLESS_HEADER_OPTION,
|
|
||||||
),
|
|
||||||
title = "Header",
|
|
||||||
description = """
|
description = """
|
||||||
The header to apply to the app.
|
Folder with images to use as a custom header logo.
|
||||||
|
|
||||||
If a path to a folder is provided, the folder must contain one or more of the following folders, depending on the DPI of the device:
|
|
||||||
|
|
||||||
|
The folder must contain one or more of the following folders, depending on the DPI of the device:
|
||||||
${targetResourceDirectoryNames.keys.joinToString("\n") { "- $it" }}
|
${targetResourceDirectoryNames.keys.joinToString("\n") { "- $it" }}
|
||||||
|
|
||||||
Each of the folders must contain all of the following files:
|
Each of the folders must contain all of the following files:
|
||||||
|
${variants.joinToString("\n") { variant -> "- ${CUSTOM_HEADER_RESOURCE_NAME}_$variant.png" }}
|
||||||
${variants.joinToString("\n") { variant -> "- ${HEADER_FILE_NAME}_$variant.png" }}
|
|
||||||
|
|
||||||
The image dimensions must be as follows:
|
The image dimensions must be as follows:
|
||||||
${targetResourceDirectoryNames.map { (dpi, dim) -> "- $dpi: $dim" }.joinToString("\n")}
|
${targetResourceDirectoryNames.map { (dpi, dim) -> "- $dpi: $dim" }.joinToString("\n")}
|
||||||
""".trimIndentMultiline(),
|
""".trimIndentMultiline()
|
||||||
required = true,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
// The directories to copy the header to.
|
addResources("youtube", "layout.branding.changeHeaderPatch")
|
||||||
val targetResourceDirectories = targetResourceDirectoryNames.keys.mapNotNull {
|
|
||||||
get("res").resolve(it).takeIf(File::exists)
|
|
||||||
}
|
|
||||||
// The files to replace in the target directories.
|
|
||||||
val targetResourceFiles = targetResourceDirectoryNames.keys.map { directoryName ->
|
|
||||||
ResourceGroup(
|
|
||||||
directoryName,
|
|
||||||
*variants.map { variant -> "${HEADER_FILE_NAME}_$variant.png" }.toTypedArray(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
fun getLightDarkFileNames(vararg resourceNames: String): Array<String> =
|
||||||
* A function that overwrites both header variants in the target resource directories.
|
variants.flatMap { variant ->
|
||||||
*/
|
resourceNames.map { resource -> "${resource}_$variant.png" }
|
||||||
fun overwriteFromTo(from: String, to: String) {
|
}.toTypedArray()
|
||||||
targetResourceDirectories.forEach { directory ->
|
|
||||||
variants.forEach { variant ->
|
|
||||||
val fromPath = directory.resolve("${from}_$variant.png")
|
|
||||||
val toPath = directory.resolve("${to}_$variant.png")
|
|
||||||
|
|
||||||
fromPath.copyTo(toPath, true)
|
val logoResourceFileNames = getLightDarkFileNames(*logoResourceNames)
|
||||||
}
|
copyResources(
|
||||||
}
|
"change-header",
|
||||||
}
|
ResourceGroup("drawable-hdpi", *logoResourceFileNames),
|
||||||
|
ResourceGroup("drawable-mdpi", *logoResourceFileNames),
|
||||||
|
ResourceGroup("drawable-xhdpi", *logoResourceFileNames),
|
||||||
|
ResourceGroup("drawable-xxhdpi", *logoResourceFileNames),
|
||||||
|
ResourceGroup("drawable-xxxhdpi", *logoResourceFileNames),
|
||||||
|
)
|
||||||
|
|
||||||
// Functions to overwrite the header to the different variants.
|
if (custom != null) {
|
||||||
fun toPremium() { overwriteFromTo(PREMIUM_HEADER_FILE_NAME, HEADER_FILE_NAME) }
|
val sourceFolders = File(custom!!).listFiles { file -> file.isDirectory }
|
||||||
fun toHeader() { overwriteFromTo(HEADER_FILE_NAME, PREMIUM_HEADER_FILE_NAME) }
|
?: throw PatchException("The provided path is not a directory: $custom")
|
||||||
fun toReVanced() {
|
|
||||||
// Copy the ReVanced header to the resource directories.
|
|
||||||
targetResourceFiles.forEach { copyResources("change-header/revanced", it) }
|
|
||||||
|
|
||||||
// Overwrite the premium with the custom header as well.
|
val customResourceFileNames = getLightDarkFileNames(CUSTOM_HEADER_RESOURCE_NAME)
|
||||||
toHeader()
|
|
||||||
}
|
|
||||||
fun toReVancedBorderless() {
|
|
||||||
// Copy the ReVanced borderless header to the resource directories.
|
|
||||||
targetResourceFiles.forEach {
|
|
||||||
copyResources(
|
|
||||||
"change-header/revanced-borderless",
|
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overwrite the premium with the custom header as well.
|
|
||||||
toHeader()
|
|
||||||
}
|
|
||||||
fun toCustom() {
|
|
||||||
val sourceFolders = File(header!!).listFiles { file -> file.isDirectory }
|
|
||||||
?: throw PatchException("The provided path is not a directory: $header")
|
|
||||||
|
|
||||||
var copiedFiles = false
|
var copiedFiles = false
|
||||||
|
|
||||||
|
|
@ -137,62 +138,87 @@ val changeHeaderPatch = resourcePatch(
|
||||||
val targetDpiFolder = get("res").resolve(dpiSourceFolder.name)
|
val targetDpiFolder = get("res").resolve(dpiSourceFolder.name)
|
||||||
if (!targetDpiFolder.exists()) return@forEach
|
if (!targetDpiFolder.exists()) return@forEach
|
||||||
|
|
||||||
val imgSourceFiles = dpiSourceFolder.listFiles { file -> file.isFile }!!
|
val customFiles = dpiSourceFolder.listFiles { file ->
|
||||||
imgSourceFiles.forEach { imgSourceFile ->
|
file.isFile && file.name in customResourceFileNames
|
||||||
|
}!!
|
||||||
|
|
||||||
|
if (customFiles.size > 0 && customFiles.size != variants.size) {
|
||||||
|
throw PatchException("Both light/dark mode images " +
|
||||||
|
"must be specified but only found: " + customFiles.map { it.name })
|
||||||
|
}
|
||||||
|
|
||||||
|
customFiles.forEach { imgSourceFile ->
|
||||||
val imgTargetFile = targetDpiFolder.resolve(imgSourceFile.name)
|
val imgTargetFile = targetDpiFolder.resolve(imgSourceFile.name)
|
||||||
imgSourceFile.copyTo(imgTargetFile, true)
|
imgSourceFile.copyTo(imgTargetFile)
|
||||||
|
|
||||||
copiedFiles = true
|
copiedFiles = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!copiedFiles) {
|
if (!copiedFiles) {
|
||||||
throw PatchException("No header files were copied from the provided path: $header.")
|
throw PatchException("No custom header images found in the provided path: $custom")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logo is replaced using an attribute reference.
|
||||||
|
document("res/values/attrs.xml").use { document ->
|
||||||
|
val resources = document.childNodes.item(0)
|
||||||
|
|
||||||
|
fun addAttributeReference(logoName: String) {
|
||||||
|
val item = document.createElement("attr")
|
||||||
|
item.setAttribute("format", "reference")
|
||||||
|
item.setAttribute("name", logoName)
|
||||||
|
resources.appendChild(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overwrite the premium with the custom header as well.
|
logoResourceNames.forEach { logoName ->
|
||||||
toHeader()
|
addAttributeReference(logoName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom != null) {
|
||||||
|
addAttributeReference(CUSTOM_HEADER_RESOURCE_NAME)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
when (header) {
|
// Add custom drawables to all styles that use the regular and premium logo.
|
||||||
HEADER_OPTION -> toHeader()
|
document("res/values/styles.xml").use { document ->
|
||||||
PREMIUM_HEADER_OPTION -> toPremium()
|
arrayOf(
|
||||||
REVANCED_HEADER_OPTION -> toReVanced()
|
"Base.Theme.YouTube.Light" to "light",
|
||||||
REVANCED_BORDERLESS_HEADER_OPTION -> toReVancedBorderless()
|
"Base.Theme.YouTube.Dark" to "dark",
|
||||||
else -> toCustom()
|
"CairoLightThemeRingo2Updates" to "light",
|
||||||
}
|
"CairoDarkThemeRingo2Updates" to "dark"
|
||||||
|
).forEach { (style, mode) ->
|
||||||
|
val styleElement = document.childNodes.findElementByAttributeValueOrThrow(
|
||||||
|
"name", style
|
||||||
|
)
|
||||||
|
|
||||||
// Fix 19.25+ A/B layout with different header icons:
|
fun addDrawableElement(document: Document, logoName: String, mode: String) {
|
||||||
// yt_ringo2_wordmark_header, yt_ringo2_premium_wordmark_header
|
val item = document.createElement("item")
|
||||||
//
|
item.setAttribute("name", logoName)
|
||||||
// These images are webp and not png, so overwriting them is not so simple.
|
item.textContent = "@drawable/${logoName}_$mode"
|
||||||
// Instead change styles.xml to use the old drawable resources.
|
styleElement.appendChild(item)
|
||||||
if (is_19_25_or_greater) {
|
}
|
||||||
document("res/values/styles.xml").use { document ->
|
|
||||||
val documentChildNodes = document.childNodes
|
|
||||||
|
|
||||||
arrayOf(
|
logoResourceNames.forEach { logoName ->
|
||||||
"CairoLightThemeRingo2Updates" to variants[0],
|
addDrawableElement(document, logoName, mode)
|
||||||
"CairoDarkThemeRingo2Updates" to variants[1]
|
}
|
||||||
).forEach { (styleName, theme) ->
|
|
||||||
val styleNodes = documentChildNodes.findElementByAttributeValueOrThrow(
|
|
||||||
"name",
|
|
||||||
styleName,
|
|
||||||
).childNodes
|
|
||||||
|
|
||||||
val drawable = "@drawable/${HEADER_FILE_NAME}_${theme}"
|
if (custom != null) {
|
||||||
|
addDrawableElement(document, CUSTOM_HEADER_RESOURCE_NAME, mode)
|
||||||
arrayOf(
|
|
||||||
"ytWordmarkHeader",
|
|
||||||
"ytPremiumWordmarkHeader"
|
|
||||||
).forEach { itemName ->
|
|
||||||
styleNodes.findElementByAttributeValueOrThrow(
|
|
||||||
"name",
|
|
||||||
itemName,
|
|
||||||
).textContent = drawable
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
|
||||||
|
if (custom == null) {
|
||||||
|
ListPreference("revanced_header_logo")
|
||||||
|
} else {
|
||||||
|
ListPreference(
|
||||||
|
key = "revanced_header_logo",
|
||||||
|
entriesKey = "revanced_header_logo_custom_entries",
|
||||||
|
entryValuesKey = "revanced_header_logo_custom_entry_values"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -222,7 +222,7 @@ val themePatch = bytecodePatch(
|
||||||
|
|
||||||
if (is_19_47_or_greater) {
|
if (is_19_47_or_greater) {
|
||||||
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
|
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
|
||||||
ListPreference("splash_screen_animation_style")
|
ListPreference("revanced_splash_screen_animation_style")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -355,7 +355,7 @@ fun Method.indexOfFirstLiteralInstructionOrThrow(literal: Float): Int {
|
||||||
* @see indexOfFirstLiteralInstructionOrThrow
|
* @see indexOfFirstLiteralInstructionOrThrow
|
||||||
*/
|
*/
|
||||||
fun Method.indexOfFirstLiteralInstruction(literal: Double) =
|
fun Method.indexOfFirstLiteralInstruction(literal: Double) =
|
||||||
indexOfFirstLiteralInstruction(literal.toRawBits().toLong())
|
indexOfFirstLiteralInstruction(literal.toRawBits())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the index of the first literal instruction with the given double value,
|
* Find the index of the first literal instruction with the given double value,
|
||||||
|
|
@ -421,7 +421,7 @@ fun Method.indexOfFirstLiteralInstructionReversedOrThrow(literal: Float): Int {
|
||||||
* @see indexOfFirstLiteralInstructionOrThrow
|
* @see indexOfFirstLiteralInstructionOrThrow
|
||||||
*/
|
*/
|
||||||
fun Method.indexOfFirstLiteralInstructionReversed(literal: Double) =
|
fun Method.indexOfFirstLiteralInstructionReversed(literal: Double) =
|
||||||
indexOfFirstLiteralInstructionReversed(literal.toRawBits().toLong())
|
indexOfFirstLiteralInstructionReversed(literal.toRawBits())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the index of the last wide literal instruction with the given double value,
|
* Find the index of the last wide literal instruction with the given double value,
|
||||||
|
|
@ -715,24 +715,50 @@ internal fun MutableMethod.insertLiteralOverride(literal: Long, override: Boolea
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called for _all_ instructions with the given literal value.
|
* Called for _all_ methods with the given literal value.
|
||||||
|
* Method indices are iterated from last to first.
|
||||||
*/
|
*/
|
||||||
fun BytecodePatchContext.forEachLiteralValueInstruction(
|
fun BytecodePatchContext.forEachLiteralValueInstruction(
|
||||||
literal: Long,
|
literal: Long,
|
||||||
block: MutableMethod.(literalInstructionIndex: Int) -> Unit,
|
block: MutableMethod.(matchingIndex: Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val matchingIndexes = ArrayList<Int>()
|
||||||
|
|
||||||
classes.forEach { classDef ->
|
classes.forEach { classDef ->
|
||||||
classDef.methods.forEach { method ->
|
classDef.methods.forEach { method ->
|
||||||
method.implementation?.instructions?.forEachIndexed { index, instruction ->
|
method.implementation?.instructions?.let { instructions ->
|
||||||
if (instruction.opcode == CONST &&
|
matchingIndexes.clear()
|
||||||
(instruction as WideLiteralInstruction).wideLiteral == literal
|
|
||||||
) {
|
instructions.forEachIndexed { index, instruction ->
|
||||||
|
if ((instruction as? WideLiteralInstruction)?.wideLiteral == literal) {
|
||||||
|
matchingIndexes.add(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchingIndexes.isNotEmpty()) {
|
||||||
val mutableMethod = proxy(classDef).mutableClass.findMutableMethodOf(method)
|
val mutableMethod = proxy(classDef).mutableClass.findMutableMethodOf(method)
|
||||||
block.invoke(mutableMethod, index)
|
|
||||||
|
// FIXME: Until patcher V22 is merged, this workaround is needed
|
||||||
|
// because if multiple patches modify the same class
|
||||||
|
// then after modifying the method indexes of immutable classes
|
||||||
|
// are no longer correct.
|
||||||
|
matchingIndexes.clear()
|
||||||
|
mutableMethod.instructions.forEachIndexed { index, instruction ->
|
||||||
|
if ((instruction as? WideLiteralInstruction)?.wideLiteral == literal) {
|
||||||
|
matchingIndexes.add(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matchingIndexes.isEmpty()) return@forEach
|
||||||
|
// FIXME Remove code above after V22 merge.
|
||||||
|
|
||||||
|
matchingIndexes.asReversed().forEach { index ->
|
||||||
|
block.invoke(mutableMethod, index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val RETURN_TYPE_MISMATCH = "Mismatch between override type and Method return type"
|
private const val RETURN_TYPE_MISMATCH = "Mismatch between override type and Method return type"
|
||||||
|
|
|
||||||
|
|
@ -190,16 +190,15 @@
|
||||||
(YouTube closes the animation as soon as the feed is loaded),
|
(YouTube closes the animation as soon as the feed is loaded),
|
||||||
only the 60fps 1 second styles are exposed in the settings.
|
only the 60fps 1 second styles are exposed in the settings.
|
||||||
Imported settings data can still be manually edited to force the other styles. -->
|
Imported settings data can still be manually edited to force the other styles. -->
|
||||||
<string-array name="splash_screen_animation_style_entries">
|
<string-array name="revanced_splash_screen_animation_style_entries">
|
||||||
<item>@string/splash_screen_animation_style_entry_1</item>
|
<item>@string/revanced_splash_screen_animation_style_entry_1</item>
|
||||||
<item>@string/splash_screen_animation_style_entry_2</item>
|
<item>@string/revanced_splash_screen_animation_style_entry_2</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="splash_screen_animation_style_entry_values">
|
<string-array name="revanced_splash_screen_animation_style_entry_values">
|
||||||
<item>FPS_60_ONE_SECOND</item>
|
<item>FPS_60_ONE_SECOND</item>
|
||||||
<item>FPS_60_BLACK_AND_WHITE</item>
|
<item>FPS_60_BLACK_AND_WHITE</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
</patch>
|
</patch>
|
||||||
|
|
||||||
<patch id="layout.player.fullscreen.exitFullscreenPatch">
|
<patch id="layout.player.fullscreen.exitFullscreenPatch">
|
||||||
<string-array name="revanced_exit_fullscreen_entries">
|
<string-array name="revanced_exit_fullscreen_entries">
|
||||||
<item>@string/revanced_exit_fullscreen_entry_1</item>
|
<item>@string/revanced_exit_fullscreen_entry_1</item>
|
||||||
|
|
@ -270,6 +269,38 @@
|
||||||
<item>MODERN_3</item>
|
<item>MODERN_3</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
</patch>
|
</patch>
|
||||||
|
<patch id="layout.branding.changeHeaderPatch">
|
||||||
|
<string-array name="revanced_header_logo_entries">
|
||||||
|
<item>@string/revanced_header_logo_entry_1</item>
|
||||||
|
<item>@string/revanced_header_logo_entry_2</item>
|
||||||
|
<item>@string/revanced_header_logo_entry_3</item>
|
||||||
|
<item>@string/revanced_header_logo_entry_4</item>
|
||||||
|
<item>@string/revanced_header_logo_entry_5</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="revanced_header_logo_entry_values">
|
||||||
|
<item>DEFAULT</item>
|
||||||
|
<item>REGULAR</item>
|
||||||
|
<item>PREMIUM</item>
|
||||||
|
<item>REVANCED</item>
|
||||||
|
<item>REVANCED_MINIMAL</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="revanced_header_logo_custom_entries">
|
||||||
|
<item>@string/revanced_header_logo_entry_1</item>
|
||||||
|
<item>@string/revanced_header_logo_entry_2</item>
|
||||||
|
<item>@string/revanced_header_logo_entry_3</item>
|
||||||
|
<item>@string/revanced_header_logo_entry_4</item>
|
||||||
|
<item>@string/revanced_header_logo_entry_5</item>
|
||||||
|
<item>@string/revanced_header_logo_entry_6</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="revanced_header_logo_custom_entry_values">
|
||||||
|
<item>DEFAULT</item>
|
||||||
|
<item>REGULAR</item>
|
||||||
|
<item>PREMIUM</item>
|
||||||
|
<item>REVANCED</item>
|
||||||
|
<item>REVANCED_MINIMAL</item>
|
||||||
|
<item>CUSTOM</item>
|
||||||
|
</string-array>
|
||||||
|
</patch>
|
||||||
<patch id="layout.startpage.changeStartPagePatch">
|
<patch id="layout.startpage.changeStartPagePatch">
|
||||||
<string-array name="revanced_change_start_page_entries">
|
<string-array name="revanced_change_start_page_entries">
|
||||||
<item>@string/revanced_change_start_page_entry_default</item>
|
<item>@string/revanced_change_start_page_entry_default</item>
|
||||||
|
|
|
||||||
|
|
@ -1369,9 +1369,9 @@ Swipe to expand or close"</string>
|
||||||
<string name="revanced_gradient_loading_screen_title">Enable gradient loading screen</string>
|
<string name="revanced_gradient_loading_screen_title">Enable gradient loading screen</string>
|
||||||
<string name="revanced_gradient_loading_screen_summary_on">Loading screen will have a gradient background</string>
|
<string name="revanced_gradient_loading_screen_summary_on">Loading screen will have a gradient background</string>
|
||||||
<string name="revanced_gradient_loading_screen_summary_off">Loading screen will have a solid background</string>
|
<string name="revanced_gradient_loading_screen_summary_off">Loading screen will have a solid background</string>
|
||||||
<string name="splash_screen_animation_style_title">Splash screen style</string>
|
<string name="revanced_splash_screen_animation_style_title">Splash screen style</string>
|
||||||
<string name="splash_screen_animation_style_entry_1">Color</string>
|
<string name="revanced_splash_screen_animation_style_entry_1">Color</string>
|
||||||
<string name="splash_screen_animation_style_entry_2">Black and white</string>
|
<string name="revanced_splash_screen_animation_style_entry_2">Black and white</string>
|
||||||
<string name="revanced_seekbar_custom_color_title">Enable custom seekbar color</string>
|
<string name="revanced_seekbar_custom_color_title">Enable custom seekbar color</string>
|
||||||
<string name="revanced_seekbar_custom_color_summary_on">Custom seekbar color is shown</string>
|
<string name="revanced_seekbar_custom_color_summary_on">Custom seekbar color is shown</string>
|
||||||
<string name="revanced_seekbar_custom_color_summary_off">Original seekbar color is shown</string>
|
<string name="revanced_seekbar_custom_color_summary_off">Original seekbar color is shown</string>
|
||||||
|
|
@ -1381,6 +1381,15 @@ Swipe to expand or close"</string>
|
||||||
<string name="revanced_seekbar_custom_color_accent_summary">The accent color of the seekbar</string>
|
<string name="revanced_seekbar_custom_color_accent_summary">The accent color of the seekbar</string>
|
||||||
<string name="revanced_seekbar_custom_color_invalid">Invalid seekbar color value</string>
|
<string name="revanced_seekbar_custom_color_invalid">Invalid seekbar color value</string>
|
||||||
</patch>
|
</patch>
|
||||||
|
<patch id="layout.branding.changeHeaderPatch">
|
||||||
|
<string name="revanced_header_logo_title">Header logo</string>
|
||||||
|
<string name="revanced_header_logo_entry_1">Default</string>
|
||||||
|
<string name="revanced_header_logo_entry_2">Regular</string>
|
||||||
|
<string name="revanced_header_logo_entry_3">Premium</string>
|
||||||
|
<string name="revanced_header_logo_entry_4">ReVanced</string>
|
||||||
|
<string name="revanced_header_logo_entry_5">ReVanced minimal</string>
|
||||||
|
<string name="revanced_header_logo_entry_6">Custom</string>
|
||||||
|
</patch>
|
||||||
<patch id="layout.thumbnails.bypassImageRegionRestrictionsPatch">
|
<patch id="layout.thumbnails.bypassImageRegionRestrictionsPatch">
|
||||||
<string name="revanced_bypass_image_region_restrictions_title">Bypass image region restrictions</string>
|
<string name="revanced_bypass_image_region_restrictions_title">Bypass image region restrictions</string>
|
||||||
<string name="revanced_bypass_image_region_restrictions_summary_on">Using image host yt4.ggpht.com</string>
|
<string name="revanced_bypass_image_region_restrictions_summary_on">Using image host yt4.ggpht.com</string>
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2 KiB |
|
After Width: | Height: | Size: 2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3 KiB |
|
After Width: | Height: | Size: 7 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 9.5 KiB |