mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-06-13 13:47:42 +02:00
feat(YouTube Music): add Enable next previous button
patch
This commit is contained in:
@ -0,0 +1,176 @@
|
||||
package app.revanced.patches.music.player.nextprevious
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patches.music.utils.fingerprints.MiniPlayerConstructorFingerprint
|
||||
import app.revanced.patches.music.player.nextprevious.fingerprints.MiniPlayerParentFingerprint
|
||||
import app.revanced.patches.music.player.nextprevious.fingerprints.MppWatchWhileLayoutFingerprint
|
||||
import app.revanced.patches.music.player.nextprevious.fingerprints.NextButtonVisibilityFingerprint
|
||||
import app.revanced.patches.music.utils.integrations.Constants.PLAYER
|
||||
import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch
|
||||
import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.MiniPlayerPlayPauseReplayButton
|
||||
import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.TopEnd
|
||||
import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.TopStart
|
||||
import app.revanced.patches.music.utils.settings.CategoryType
|
||||
import app.revanced.patches.music.utils.settings.SettingsPatch
|
||||
import app.revanced.util.exception
|
||||
import app.revanced.util.getTargetIndex
|
||||
import app.revanced.util.getWideLiteralInstructionIndex
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Patch(
|
||||
name = "Enable next previous button",
|
||||
description = "Adds an options to show the next and previous buttons to the miniplayer.",
|
||||
dependencies = [
|
||||
MiniPlayerButtonResourcePatch::class,
|
||||
SettingsPatch::class,
|
||||
SharedResourceIdPatch::class
|
||||
],
|
||||
compatiblePackages = [
|
||||
CompatiblePackage(
|
||||
"com.google.android.apps.youtube.music",
|
||||
[
|
||||
"6.21.52",
|
||||
"6.22.52",
|
||||
"6.23.56",
|
||||
"6.25.53",
|
||||
"6.26.51",
|
||||
"6.27.54",
|
||||
"6.28.53",
|
||||
"6.29.58",
|
||||
"6.31.55",
|
||||
"6.33.52"
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
@Suppress("unused")
|
||||
object MiniPlayerButtonPatch : BytecodePatch(
|
||||
setOf(
|
||||
MiniPlayerConstructorFingerprint,
|
||||
MiniPlayerParentFingerprint,
|
||||
MppWatchWhileLayoutFingerprint
|
||||
)
|
||||
) {
|
||||
private const val NEXT_BUTTON_FIELD_NAME =
|
||||
"nextButton"
|
||||
private const val PREVIOUS_BUTTON_FIELD_NAME =
|
||||
"previousButton"
|
||||
private const val NEXT_BUTTON_METHOD_NAME =
|
||||
"setNextButtonOnClickListener"
|
||||
private const val PREVIOUS_BUTTON_METHOD_NAME =
|
||||
"setPreviousButtonOnClickListener"
|
||||
|
||||
private var arrayCount = 1
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
|
||||
val miniPlayerConstructorMutableMethod =
|
||||
MiniPlayerConstructorFingerprint.result?.mutableMethod
|
||||
?: throw MiniPlayerConstructorFingerprint.exception
|
||||
|
||||
val mppWatchWhileLayoutMutableMethod =
|
||||
MppWatchWhileLayoutFingerprint.result?.mutableMethod
|
||||
?: throw MppWatchWhileLayoutFingerprint.exception
|
||||
|
||||
if (!SettingsPatch.upward0642) {
|
||||
MiniPlayerParentFingerprint.result?.let { parentResult ->
|
||||
// Resolves fingerprints
|
||||
NextButtonVisibilityFingerprint.resolve(context, parentResult.classDef)
|
||||
|
||||
NextButtonVisibilityFingerprint.result?.let {
|
||||
it.mutableMethod.apply {
|
||||
val targetIndex = it.scanResult.patternScanResult!!.startIndex + 1
|
||||
val targetRegister =
|
||||
getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
targetIndex + 1, """
|
||||
invoke-static {v$targetRegister}, $PLAYER->enableMiniPlayerNextButton(Z)Z
|
||||
move-result v$targetRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
} ?: throw NextButtonVisibilityFingerprint.exception
|
||||
} ?: throw MiniPlayerParentFingerprint.exception
|
||||
} else {
|
||||
miniPlayerConstructorMutableMethod.setInstanceFieldValue(NEXT_BUTTON_METHOD_NAME, TopStart)
|
||||
mppWatchWhileLayoutMutableMethod.setStaticFieldValue(NEXT_BUTTON_FIELD_NAME, TopStart)
|
||||
}
|
||||
|
||||
miniPlayerConstructorMutableMethod.setInstanceFieldValue(PREVIOUS_BUTTON_METHOD_NAME, TopEnd)
|
||||
mppWatchWhileLayoutMutableMethod.setStaticFieldValue(PREVIOUS_BUTTON_FIELD_NAME, TopEnd)
|
||||
|
||||
mppWatchWhileLayoutMutableMethod.setViewArray()
|
||||
|
||||
SettingsPatch.addMusicPreference(
|
||||
CategoryType.PLAYER,
|
||||
"revanced_enable_mini_player_next_button",
|
||||
"true"
|
||||
)
|
||||
SettingsPatch.addMusicPreference(
|
||||
CategoryType.PLAYER,
|
||||
"revanced_enable_mini_player_previous_button",
|
||||
"false"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
private fun MutableMethod.setViewArray() {
|
||||
val miniPlayerPlayPauseReplayButtonIndex = getWideLiteralInstructionIndex(MiniPlayerPlayPauseReplayButton)
|
||||
val invokeStaticIndex = getTargetIndex(miniPlayerPlayPauseReplayButtonIndex, Opcode.INVOKE_STATIC)
|
||||
val viewArrayRegister = getInstruction<FiveRegisterInstruction>(invokeStaticIndex).registerC
|
||||
|
||||
addInstructions(
|
||||
invokeStaticIndex, """
|
||||
invoke-static {v$viewArrayRegister}, $PLAYER->getViewArray([Landroid/view/View;)[Landroid/view/View;
|
||||
move-result-object v$viewArrayRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableMethod.setInstanceFieldValue(
|
||||
methodName: String,
|
||||
viewId: Long
|
||||
) {
|
||||
val miniPlayerPlayPauseReplayButtonIndex = getWideLiteralInstructionIndex(MiniPlayerPlayPauseReplayButton)
|
||||
val miniPlayerPlayPauseReplayButtonRegister = getInstruction<OneRegisterInstruction>(miniPlayerPlayPauseReplayButtonIndex).registerA
|
||||
val findViewByIdIndex = getTargetIndex(miniPlayerPlayPauseReplayButtonIndex, Opcode.INVOKE_VIRTUAL)
|
||||
val parentViewRegister = getInstruction<FiveRegisterInstruction>(findViewByIdIndex).registerC
|
||||
|
||||
addInstructions(
|
||||
miniPlayerPlayPauseReplayButtonIndex, """
|
||||
const v$miniPlayerPlayPauseReplayButtonRegister, $viewId
|
||||
invoke-virtual {v$parentViewRegister, v$miniPlayerPlayPauseReplayButtonRegister}, Landroid/view/View;->findViewById(I)Landroid/view/View;
|
||||
move-result-object v$miniPlayerPlayPauseReplayButtonRegister
|
||||
invoke-static {v$miniPlayerPlayPauseReplayButtonRegister}, $PLAYER->$methodName(Landroid/view/View;)V
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableMethod.setStaticFieldValue(
|
||||
fieldName: String,
|
||||
viewId: Long
|
||||
) {
|
||||
val miniPlayerPlayPauseReplayButtonIndex = getWideLiteralInstructionIndex(MiniPlayerPlayPauseReplayButton)
|
||||
val constRegister = getInstruction<OneRegisterInstruction>(miniPlayerPlayPauseReplayButtonIndex).registerA
|
||||
val findViewByIdIndex = getTargetIndex(miniPlayerPlayPauseReplayButtonIndex, Opcode.INVOKE_VIRTUAL)
|
||||
val findViewByIdRegister = getInstruction<FiveRegisterInstruction>(findViewByIdIndex).registerC
|
||||
|
||||
addInstructions(
|
||||
miniPlayerPlayPauseReplayButtonIndex, """
|
||||
const v$constRegister, $viewId
|
||||
invoke-virtual {v$findViewByIdRegister, v$constRegister}, $definingClass->findViewById(I)Landroid/view/View;
|
||||
move-result-object v$constRegister
|
||||
sput-object v$constRegister, $PLAYER->$fieldName:Landroid/view/View;
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package app.revanced.patches.music.player.nextprevious
|
||||
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.util.DomFileEditor
|
||||
import app.revanced.patches.music.utils.settings.SettingsPatch
|
||||
import app.revanced.util.adoptChild
|
||||
import app.revanced.util.insertNode
|
||||
import app.revanced.util.doRecursively
|
||||
import org.w3c.dom.Element
|
||||
import java.io.Closeable
|
||||
|
||||
@Patch(dependencies = [SettingsPatch::class])
|
||||
object MiniPlayerButtonResourcePatch : ResourcePatch(), Closeable {
|
||||
private const val IMAGE_VIEW_TAG_NAME =
|
||||
"com.google.android.libraries.youtube.common.ui.TouchImageView"
|
||||
private const val NEXT_BUTTON_VIEW_ID =
|
||||
"mini_player_next_button"
|
||||
private const val PREVIOUS_BUTTON_VIEW_ID =
|
||||
"mini_player_previous_button"
|
||||
|
||||
private lateinit var targetXmlEditor: DomFileEditor
|
||||
private var shouldAddPreviousButton = true
|
||||
|
||||
override fun execute(context: ResourceContext) {
|
||||
targetXmlEditor = context.xmlEditor["res/layout/watch_while_layout.xml"]
|
||||
|
||||
// Since YT Music v6.42.51,the resources for the next button have been removed, we need to add them manually.
|
||||
if (SettingsPatch.upward0642) {
|
||||
context.replaceId(false)
|
||||
insertNode(false)
|
||||
}
|
||||
context.replaceId(true)
|
||||
insertNode(true)
|
||||
}
|
||||
|
||||
override fun close() = targetXmlEditor.close()
|
||||
|
||||
private fun insertNode(isPreviousButton: Boolean) {
|
||||
targetXmlEditor.file.doRecursively loop@{ node ->
|
||||
if (node !is Element) return@loop
|
||||
|
||||
node.getAttributeNode("android:id")?.let { attribute ->
|
||||
if (isPreviousButton) {
|
||||
if (attribute.textContent == "@id/mini_player_play_pause_replay_button" && shouldAddPreviousButton) {
|
||||
node.insertNode(IMAGE_VIEW_TAG_NAME, node) {
|
||||
setPreviousButtonNodeAttribute()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (attribute.textContent == "@id/mini_player") {
|
||||
node.adoptChild(IMAGE_VIEW_TAG_NAME) {
|
||||
setNextButtonNodeAttribute()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ResourceContext.replaceId(isPreviousButton: Boolean) {
|
||||
val publicFile = this["res/values/public.xml"]
|
||||
|
||||
if (isPreviousButton) {
|
||||
publicFile.writeText(
|
||||
publicFile.readText()
|
||||
.replace(
|
||||
"\"TOP_END\"",
|
||||
"\"$PREVIOUS_BUTTON_VIEW_ID\""
|
||||
)
|
||||
)
|
||||
} else {
|
||||
publicFile.writeText(
|
||||
publicFile.readText()
|
||||
.replace(
|
||||
"\"TOP_START\"",
|
||||
"\"$NEXT_BUTTON_VIEW_ID\""
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.setNextButtonNodeAttribute() {
|
||||
mapOf(
|
||||
"android:id" to "@id/$NEXT_BUTTON_VIEW_ID",
|
||||
"android:padding" to "@dimen/item_medium_spacing",
|
||||
"android:layout_width" to "@dimen/remix_generic_button_size",
|
||||
"android:layout_height" to "@dimen/remix_generic_button_size",
|
||||
"android:src" to "@drawable/music_player_next",
|
||||
"android:scaleType" to "fitCenter",
|
||||
"android:contentDescription" to "@string/accessibility_next",
|
||||
"style" to "@style/MusicPlayerButton"
|
||||
).forEach { (k, v) ->
|
||||
setAttribute(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.setPreviousButtonNodeAttribute() {
|
||||
mapOf(
|
||||
"android:id" to "@id/$PREVIOUS_BUTTON_VIEW_ID",
|
||||
"android:padding" to "@dimen/item_medium_spacing",
|
||||
"android:layout_width" to "@dimen/remix_generic_button_size",
|
||||
"android:layout_height" to "@dimen/remix_generic_button_size",
|
||||
"android:src" to "@drawable/music_player_prev",
|
||||
"android:scaleType" to "fitCenter",
|
||||
"android:contentDescription" to "@string/accessibility_previous",
|
||||
"style" to "@style/MusicPlayerButton"
|
||||
).forEach { (k, v) ->
|
||||
setAttribute(k, v)
|
||||
}
|
||||
shouldAddPreviousButton = false
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package app.revanced.patches.music.player.nextprevious.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.MiniPlayerMdxPlaying
|
||||
import app.revanced.util.fingerprint.LiteralValueFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
object MiniPlayerParentFingerprint : LiteralValueFingerprint(
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = emptyList(),
|
||||
literalSupplier = { MiniPlayerMdxPlaying }
|
||||
)
|
@ -0,0 +1,16 @@
|
||||
package app.revanced.patches.music.player.nextprevious.fingerprints
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.MiniPlayerPlayPauseReplayButton
|
||||
import app.revanced.util.containsWideLiteralInstructionIndex
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
object MppWatchWhileLayoutFingerprint : MethodFingerprint(
|
||||
returnType = "V",
|
||||
opcodes = listOf(Opcode.NEW_ARRAY),
|
||||
customFingerprint = { methodDef, _ ->
|
||||
methodDef.definingClass.endsWith("/MppWatchWhileLayout;")
|
||||
&& methodDef.name == "onFinishInflate"
|
||||
&& methodDef.containsWideLiteralInstructionIndex(MiniPlayerPlayPauseReplayButton)
|
||||
}
|
||||
)
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.music.player.nextprevious.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
object NextButtonVisibilityFingerprint : MethodFingerprint(
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = emptyList(),
|
||||
opcodes = listOf(
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.CONST_16,
|
||||
Opcode.IF_EQZ
|
||||
)
|
||||
)
|
@ -43,6 +43,8 @@ object SharedResourceIdPatch : ResourcePatch() {
|
||||
var RemixGenericButtonSize: Long = -1
|
||||
var Text1: Long = -1
|
||||
var ToolTipContentView: Long = -1
|
||||
var TopEnd: Long = -1
|
||||
var TopStart: Long = -1
|
||||
var TosFooter: Long = -1
|
||||
var TouchOutside: Long = -1
|
||||
|
||||
@ -81,6 +83,8 @@ object SharedResourceIdPatch : ResourcePatch() {
|
||||
RemixGenericButtonSize = find(DIMEN, "remix_generic_button_size")
|
||||
Text1 = find(ID, "text1")
|
||||
ToolTipContentView = find(LAYOUT, "tooltip_content_view")
|
||||
TopEnd = find(ID, "TOP_END")
|
||||
TopStart = find(ID, "TOP_START")
|
||||
TosFooter = find(ID, "tos_footer")
|
||||
TouchOutside = find(ID, "touch_outside")
|
||||
|
||||
|
Reference in New Issue
Block a user