Merge 9dca7681ea
into 25e7dc1568
|
@ -1,3 +1,7 @@
|
|||
public final class app/revanced/patches/all/layout/branding/IconPatchKt {
|
||||
public static final fun getChangeIconPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/all/misc/activity/exportall/ExportAllActivitiesPatchKt {
|
||||
public static final fun getExportAllActivitiesPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
package app.revanced.patches.all.layout.branding
|
||||
|
||||
import app.revanced.patcher.patch.*
|
||||
import app.revanced.util.getNode
|
||||
import app.revanced.util.inputStreamFromBundledResource
|
||||
import java.io.InputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
|
||||
private const val FULL_ICON = 0
|
||||
private const val ROUND_ICON = 1
|
||||
private const val BACKGROUND_ICON = 2
|
||||
private const val FOREGROUND_ICON = 3
|
||||
private const val MONOCHROME_ICON = 4
|
||||
|
||||
val changeIconPatch = resourcePatch(
|
||||
name = "Change icon",
|
||||
description = "Changes the app icon to a custom icon. By default, the ReVanced icon is used.",
|
||||
use = false,
|
||||
) {
|
||||
val revancedIconOptionValue = emptyList<String>() // Empty list == ReVanced icon.
|
||||
|
||||
val pixelDensities = setOf(
|
||||
"mdpi",
|
||||
"hdpi",
|
||||
"xhdpi",
|
||||
"xxhdpi",
|
||||
"xxxhdpi",
|
||||
)
|
||||
|
||||
val iconOptions = pixelDensities.associateWith { pixelDensity ->
|
||||
stringsOption(
|
||||
key = "${pixelDensity}Icons",
|
||||
default = revancedIconOptionValue,
|
||||
values = mapOf("ReVanced logo" to revancedIconOptionValue),
|
||||
title = "Icons (Pixel density: $pixelDensity)",
|
||||
description = buildString {
|
||||
appendLine("Provide paths to the following icons for pixel density $pixelDensity (PNG, JPG, WEBP, or vector drawable XML):")
|
||||
appendLine("1. Launcher icon (required)")
|
||||
appendLine("2. Round icon (optional, Android 7+)")
|
||||
appendLine("\nYou can use adaptive icons (Android 8+) by providing the following additional icons:")
|
||||
appendLine("\n3. Background icon (optional)")
|
||||
appendLine("4. Foreground icon (optional)")
|
||||
appendLine("5. Monochrome icon (optional, Android 13+")
|
||||
appendLine("\nIcons must be provided in the same order as listed above. Missing optional icons can be skipped by leaving the field empty.")
|
||||
appendLine("\nYou can create custom icon sets at https://icon.kitchen.")
|
||||
},
|
||||
required = true,
|
||||
)
|
||||
}
|
||||
|
||||
execute {
|
||||
val firstPixelDensity = pixelDensities.first()
|
||||
|
||||
fun patchIcon(
|
||||
getIcon: (String, Int) -> String?,
|
||||
readIcon: (String) -> InputStream,
|
||||
) {
|
||||
// Any density, as the user should provide the icons for all densities.
|
||||
|
||||
// region Change the app icon in the AndroidManifest.xml file.
|
||||
|
||||
// If a round icon is provided, set the android:roundIcon attribute.
|
||||
document("AndroidManifest.xml").use {
|
||||
it.getNode("application").attributes.apply {
|
||||
getNamedItem("android:icon").textContent = "@mipmap/ic_launcher"
|
||||
|
||||
val roundIcon = getIcon(firstPixelDensity, ROUND_ICON)
|
||||
if (roundIcon?.isNotEmpty() == true) {
|
||||
val roundIconAttribute = getNamedItem("android:roundIcon")
|
||||
?: setNamedItem(it.createAttribute("android:roundIcon"))
|
||||
roundIconAttribute.textContent = "@mipmap/ic_launcher_round"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Change the app icon for each pixel density.
|
||||
|
||||
val hasAdaptiveIcon = getIcon(firstPixelDensity, BACKGROUND_ICON)
|
||||
|
||||
if (hasAdaptiveIcon?.isNotEmpty() == true) {
|
||||
val monochromeIconXmlString = if (getIcon(firstPixelDensity, MONOCHROME_ICON)?.isNotEmpty() == true) {
|
||||
"<monochrome android:drawable=\"@drawable/ic_launcher_monochrome\"/>"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
// If an adaptive icon is provided, add the adaptive icon XML file to the res/mipmap-anydpi directory.
|
||||
get("res/mipmap-anydpi/ic_launcher.xml").writeText(
|
||||
"""
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
$monochromeIconXmlString
|
||||
</adaptive-icon>
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
|
||||
pixelDensities.forEach { pixelDensity ->
|
||||
val icon = getIcon(pixelDensity, FULL_ICON)!!
|
||||
// Safe call (?.) is used because the user may just provide the full icon and skip the other optional icons.
|
||||
val roundIcon = getIcon(pixelDensity, ROUND_ICON)
|
||||
val backgroundIcon = getIcon(pixelDensity, BACKGROUND_ICON)
|
||||
val foregroundIcon = getIcon(pixelDensity, FOREGROUND_ICON)
|
||||
val monochromeIcon = getIcon(pixelDensity, MONOCHROME_ICON)
|
||||
|
||||
infix fun String?.to(target: String) {
|
||||
if (isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
Files.copy(
|
||||
readIcon(this),
|
||||
get("res/$target").toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING,
|
||||
)
|
||||
}
|
||||
|
||||
// Copy the icons to the mipmap directory.
|
||||
icon to "mipmap-$pixelDensity/ic_launcher.png"
|
||||
roundIcon to "mipmap-$pixelDensity/ic_launcher_round.png"
|
||||
backgroundIcon to "mipmap-$pixelDensity/ic_launcher_background.png"
|
||||
foregroundIcon to "mipmap-$pixelDensity/ic_launcher_foreground.png"
|
||||
monochromeIcon to "drawable-$pixelDensity/ic_launcher_monochrome.png"
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
if (iconOptions[firstPixelDensity]!!.value === revancedIconOptionValue) {
|
||||
patchIcon({ pixelDensity, iconIndex ->
|
||||
when (iconIndex) {
|
||||
FULL_ICON -> "mipmap-$pixelDensity/revanced-icon"
|
||||
ROUND_ICON -> "mipmap-$pixelDensity/revanced-icon-round"
|
||||
BACKGROUND_ICON -> "mipmap-$pixelDensity/revanced-icon-background"
|
||||
FOREGROUND_ICON -> "mipmap-$pixelDensity/revanced-icon-foreground"
|
||||
MONOCHROME_ICON -> "drawable-$pixelDensity/revanced-icon-monochrome"
|
||||
else -> throw IllegalArgumentException("Invalid icon index: $iconIndex")
|
||||
}
|
||||
}) { icon ->
|
||||
inputStreamFromBundledResource("change-icon", "$icon.png")!!
|
||||
}
|
||||
} else {
|
||||
patchIcon({ pixelDensity, iconIndex ->
|
||||
iconOptions[pixelDensity]?.value?.get(iconIndex)
|
||||
}) { icon ->
|
||||
get(icon).inputStream()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
package app.revanced.patches.music.layout.compactheader
|
||||
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val constructCategoryBarFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
|
@ -15,6 +15,6 @@ internal val constructCategoryBarFingerprint = fingerprint {
|
|||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.IPUT_OBJECT,
|
||||
Opcode.CONST,
|
||||
Opcode.INVOKE_VIRTUAL
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -23,16 +23,14 @@ fun NodeList.asSequence() = (0 until this.length).asSequence().map { this.item(i
|
|||
* Returns a sequence for all child nodes.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun Node.childElementsSequence() =
|
||||
this.childNodes.asSequence().filter { it.nodeType == Node.ELEMENT_NODE } as Sequence<Element>
|
||||
fun Node.childElementsSequence() = this.childNodes.asSequence().filter { it.nodeType == Node.ELEMENT_NODE } as Sequence<Element>
|
||||
|
||||
/**
|
||||
* Performs the given [action] on each child element.
|
||||
*/
|
||||
inline fun Node.forEachChildElement(action: (Element) -> Unit) =
|
||||
childElementsSequence().forEach {
|
||||
inline fun Node.forEachChildElement(action: (Element) -> Unit) = childElementsSequence().forEach {
|
||||
action(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively traverse the DOM tree starting from the given root node.
|
||||
|
@ -144,7 +142,8 @@ internal fun Node.addResource(
|
|||
appendChild(resource.serialize(ownerDocument, resourceCallback))
|
||||
}
|
||||
|
||||
internal fun org.w3c.dom.Document.getNode(tagName: String) = this.getElementsByTagName(tagName).item(0)
|
||||
internal fun org.w3c.dom.Document.getNode(tagName: String) = getElementsByTagName(tagName).item(0)
|
||||
internal fun Node.getNode(tagName: String) = childNodes.asSequence().find { it.nodeName == tagName }
|
||||
|
||||
internal fun NodeList.findElementByAttributeValue(attributeName: String, value: String): Element? {
|
||||
for (i in 0 until length) {
|
||||
|
@ -167,8 +166,7 @@ internal fun NodeList.findElementByAttributeValue(attributeName: String, value:
|
|||
return null
|
||||
}
|
||||
|
||||
internal fun NodeList.findElementByAttributeValueOrThrow(attributeName: String, value: String) =
|
||||
findElementByAttributeValue(attributeName, value) ?: throw PatchException("Could not find: $attributeName $value")
|
||||
internal fun NodeList.findElementByAttributeValueOrThrow(attributeName: String, value: String) = findElementByAttributeValue(attributeName, value) ?: throw PatchException("Could not find: $attributeName $value")
|
||||
|
||||
internal fun Element.copyAttributesFrom(oldContainer: Element) {
|
||||
// Copy attributes from the old element to the new element
|
||||
|
|
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 2 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 6 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 9.6 KiB |