This commit is contained in:
oSumAtrIX 2025-05-10 23:42:10 +09:00 committed by GitHub
commit 1b7af4c6dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 169 additions and 12 deletions

View file

@ -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;
}

View file

@ -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()
}
}
}
}

View file

@ -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,
)
}

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB