From 5cb46c4e9180ebc16eddb983dad73d137d8ec047 Mon Sep 17 00:00:00 2001 From: Samo Hribar <34912839+samolego@users.noreply.github.com> Date: Sat, 27 Sep 2025 14:59:51 +0200 Subject: [PATCH] feat(Viber): Add `Hide navigation buttons` patch (#5991) --- patches/api/patches.api | 4 + .../patches/viber/misc/navbar/Fingerprints.kt | 16 ++++ .../misc/navbar/HideNavigationButtons.kt | 85 +++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 patches/src/main/kotlin/app/revanced/patches/viber/misc/navbar/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/viber/misc/navbar/HideNavigationButtons.kt diff --git a/patches/api/patches.api b/patches/api/patches.api index 7ac39ad52..ae0c1a588 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -1306,6 +1306,10 @@ public final class app/revanced/patches/viber/ads/HideAdsPatchKt { public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/viber/misc/navbar/HideNavigationButtonsKt { + public static final fun getHideNavigationButtonsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/vsco/misc/pro/UnlockProPatchKt { public static final fun getUnlockProPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/viber/misc/navbar/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/viber/misc/navbar/Fingerprints.kt new file mode 100644 index 000000000..2f2a7bda3 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/viber/misc/navbar/Fingerprints.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.viber.misc.navbar +import app.revanced.patcher.fingerprint +import app.revanced.patcher.patch.BytecodePatchContext + +internal val tabIdClassFingerprint = fingerprint { + strings("shouldShowTabId") +} + +context(BytecodePatchContext) +internal val shouldShowTabIdMethodFingerprint get() = fingerprint { + parameters("I", "I") + returns("Z") + custom { methodDef, classDef -> + classDef == tabIdClassFingerprint.classDef + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/viber/misc/navbar/HideNavigationButtons.kt b/patches/src/main/kotlin/app/revanced/patches/viber/misc/navbar/HideNavigationButtons.kt new file mode 100644 index 000000000..a4aa3e4a0 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/viber/misc/navbar/HideNavigationButtons.kt @@ -0,0 +1,85 @@ +package app.revanced.patches.viber.misc.navbar + +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels +import app.revanced.patcher.patch.booleanOption +import app.revanced.patcher.patch.bytecodePatch +import java.util.logging.Logger +import kotlin.collections.joinToString + + +private const val instructionsFooter = """ + # If we reach this, it means that this tab has been disabled by user + const/4 v0, 0 + return v0 # return false as "This tab is not enabled" + + # Proceed with default execution + :continue + nop +""" + +@Suppress("unused") +val hideNavigationButtonsPatch = bytecodePatch( + name = "Hide navigation buttons", + description = "Permanently hides navigation bar buttons, such as Explore and Marketplace.", + use = false +) { + compatibleWith("com.viber.voip") + + val hideOptions = AllowedNavigationItems.entries.associateWith { + booleanOption( + key = it.key, + default = it.defaultHideOption, + title = it.title, + description = it.description, + ) + } + + execute { + // Items that won't be forcefully hidden. + val allowedItems = hideOptions.filter { (option, enabled) -> enabled.value != true } + + if (allowedItems.size == AllowedNavigationItems.entries.size) { + return@execute Logger.getLogger(this::class.java.name).warning( + "No hide navigation buttons options are enabled. No changes made." + ) + } + + val injectionInstructions = allowedItems + .map { it.key.buildAllowInstruction() } + .joinToString("\n") + instructionsFooter + + shouldShowTabIdMethodFingerprint + .method + .addInstructionsWithLabels(0, injectionInstructions) + } +} + +/** + * Navigation items taken from source code. + * They appear in code like new NavigationItem(0, R.string.bottom_tab_chats, R.drawable.ic_tab_chats). + */ +private enum class AllowedNavigationItems( + val defaultHideOption: Boolean, + private val itemName: String, + private vararg val ids: Int +) { + CHATS(false, "Chats", 0), + CALLS(false, "Calls", 1, 7), + EXPLORE(true, "Explore", 2), + MORE(false, "More", 3), + PAY(true, "Pay", 5), + CAMERA(true, "Camera", 6), + MARKETPLACE(true, "Marketplace", 8); + + val key = "hide$itemName" + val title = "Hide $itemName" + val description = "Permanently hides the $itemName button." + + fun buildAllowInstruction(): String = + ids.joinToString("\n") { id -> + """ + const/4 v0, $id # If tabId == $id ($itemName), don't hide it + if-eq p1, v0, :continue + """ + } +}