152 lines
6.1 KiB
Kotlin
152 lines
6.1 KiB
Kotlin
package app.revanced.patches.youtube.layout.branding.header
|
|
|
|
import app.revanced.patcher.data.ResourceContext
|
|
import app.revanced.patcher.patch.PatchException
|
|
import app.revanced.patcher.patch.ResourcePatch
|
|
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
|
import app.revanced.patcher.patch.annotation.Patch
|
|
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
|
|
import app.revanced.util.ResourceGroup
|
|
import app.revanced.util.Utils.trimIndentMultiline
|
|
import app.revanced.util.copyResources
|
|
import java.io.File
|
|
|
|
@Patch(
|
|
name = "Change header",
|
|
description = "Applies a custom header in the top left corner within the app. Defaults to the ReVanced header.",
|
|
compatiblePackages = [
|
|
CompatiblePackage("com.google.android.youtube"),
|
|
],
|
|
use = false,
|
|
)
|
|
@Suppress("unused")
|
|
object ChangeHeaderPatch : ResourcePatch() {
|
|
private const val HEADER_FILE_NAME = "yt_wordmark_header"
|
|
private const val PREMIUM_HEADER_FILE_NAME = "yt_premium_wordmark_header"
|
|
|
|
private const val HEADER_OPTION = "header*"
|
|
private const val PREMIUM_HEADER_OPTION = "premium*header"
|
|
private const val REVANCED_HEADER_OPTION = "revanced*"
|
|
private const val REVANCED_BORDERLESS_HEADER_OPTION = "revanced*borderless"
|
|
|
|
private val targetResourceDirectoryNames = mapOf(
|
|
"xxxhdpi" to "512px x 192px",
|
|
"xxhdpi" to "387px x 144px",
|
|
"xhdpi" to "258px x 96px",
|
|
"hdpi" to "194px x 72px",
|
|
"mdpi" to "129px x 48px",
|
|
).map { (dpi, dim) ->
|
|
"drawable-$dpi" to dim
|
|
}.toMap()
|
|
|
|
private val variants = arrayOf("light", "dark")
|
|
|
|
private val header by stringPatchOption(
|
|
key = "header",
|
|
default = REVANCED_BORDERLESS_HEADER_OPTION,
|
|
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 = """
|
|
The header to apply to the app.
|
|
|
|
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:
|
|
|
|
${targetResourceDirectoryNames.keys.joinToString("\n") { "- $it" }}
|
|
|
|
Each of the folders must contain all of the following files:
|
|
|
|
${variants.joinToString("\n") { variant -> "- ${HEADER_FILE_NAME}_$variant.png" }}
|
|
|
|
The image dimensions must be as follows:
|
|
${targetResourceDirectoryNames.map { (dpi, dim) -> "- $dpi: $dim" }.joinToString("\n")}
|
|
""".trimIndentMultiline(),
|
|
required = true,
|
|
)
|
|
|
|
override fun execute(context: ResourceContext) {
|
|
// The directories to copy the header to.
|
|
val targetResourceDirectories = targetResourceDirectoryNames.keys.mapNotNull {
|
|
context.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(),
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A function that overwrites both header variants in the target resource directories.
|
|
*/
|
|
val overwriteFromTo: (String, String) -> Unit = { from: String, to: String ->
|
|
targetResourceDirectories.forEach { directory ->
|
|
variants.forEach { variant ->
|
|
val fromPath = directory.resolve("${from}_$variant.png")
|
|
val toPath = directory.resolve("${to}_$variant.png")
|
|
|
|
fromPath.copyTo(toPath, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Functions to overwrite the header to the different variants.
|
|
val toPremium = { overwriteFromTo(PREMIUM_HEADER_FILE_NAME, HEADER_FILE_NAME) }
|
|
val toHeader = { overwriteFromTo(HEADER_FILE_NAME, PREMIUM_HEADER_FILE_NAME) }
|
|
val toReVanced = {
|
|
// Copy the ReVanced header to the resource directories.
|
|
targetResourceFiles.forEach { context.copyResources("change-header/revanced", it) }
|
|
|
|
// Overwrite the premium with the custom header as well.
|
|
toHeader()
|
|
}
|
|
val toReVancedBorderless = {
|
|
// Copy the ReVanced borderless header to the resource directories.
|
|
targetResourceFiles.forEach { context.copyResources("change-header/revanced-borderless", it) }
|
|
|
|
// Overwrite the premium with the custom header as well.
|
|
toHeader()
|
|
}
|
|
val toCustom = {
|
|
val sourceFolders = File(header!!).listFiles { file -> file.isDirectory }
|
|
?: throw PatchException("The provided path is not a directory: $header")
|
|
|
|
var copiedFiles = false
|
|
|
|
// For each source folder, copy the files to the target resource directories.
|
|
sourceFolders.forEach { dpiSourceFolder ->
|
|
val targetDpiFolder = context.get("res").resolve(dpiSourceFolder.name)
|
|
if (!targetDpiFolder.exists()) return@forEach
|
|
|
|
val imgSourceFiles = dpiSourceFolder.listFiles { file -> file.isFile }!!
|
|
imgSourceFiles.forEach { imgSourceFile ->
|
|
val imgTargetFile = targetDpiFolder.resolve(imgSourceFile.name)
|
|
imgSourceFile.copyTo(imgTargetFile, true)
|
|
|
|
copiedFiles = true
|
|
}
|
|
}
|
|
|
|
if (!copiedFiles) {
|
|
throw PatchException("No header files were copied from the provided path: $header.")
|
|
}
|
|
|
|
// Overwrite the premium with the custom header as well.
|
|
toHeader()
|
|
}
|
|
|
|
when (header) {
|
|
HEADER_OPTION -> toHeader
|
|
PREMIUM_HEADER_OPTION -> toPremium
|
|
REVANCED_HEADER_OPTION -> toReVanced
|
|
REVANCED_BORDERLESS_HEADER_OPTION -> toReVancedBorderless
|
|
else -> toCustom
|
|
}()
|
|
}
|
|
}
|