From 7a37d858fb937c6bdc2219103dac765b62600e6c Mon Sep 17 00:00:00 2001 From: viSapio <67409491+acksyndude@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:13:59 +0530 Subject: [PATCH] feat(YouTube - Hide layout components): Add "Hide view count" and "Hide upload time" settings (#5983) --- .../components/LayoutComponentsFilter.java | 61 +++++++++++++++++++ .../extension/youtube/settings/Settings.java | 2 + .../layout/hide/general/Fingerprints.kt | 14 +++++ .../hide/general/HideLayoutComponentsPatch.kt | 36 +++++++++++ .../resources/addresources/values/strings.xml | 16 ++++- 5 files changed, 127 insertions(+), 2 deletions(-) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/LayoutComponentsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/LayoutComponentsFilter.java index 7000b2a96..852e55f94 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/LayoutComponentsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/LayoutComponentsFilter.java @@ -3,6 +3,9 @@ package app.revanced.extension.youtube.patches.components; import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton; import android.graphics.drawable.Drawable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; import android.view.View; import android.widget.ImageView; @@ -500,4 +503,62 @@ public final class LayoutComponentsFilter extends Filter { // This check is important as the shelf layout is used for the library tab playlists. return NavigationButton.getSelectedNavigationButton() != NavigationButton.LIBRARY; } + + /** + * Injection point. + */ + public static SpannableString modifyFeedSubtitleSpan(SpannableString original, float truncationDimension) { + try { + final boolean hideViewCount = Settings.HIDE_VIEW_COUNT.get(); + final boolean hideUploadTime = Settings.HIDE_UPLOAD_TIME.get(); + if (!hideViewCount && !hideUploadTime) { + return original; + } + + // Applies only for these specific dimensions. + if (truncationDimension == 16f || truncationDimension == 42f) { + String delimiter = " · "; + final int delimiterLength = delimiter.length(); + + // Index includes the starting delimiter. + final int viewCountStartIndex = TextUtils.indexOf(original, delimiter); + if (viewCountStartIndex < 0) { + return original; + } + + final int uploadTimeStartIndex = TextUtils.indexOf(original, delimiter, + viewCountStartIndex + delimiterLength); + if (uploadTimeStartIndex < 0) { + return original; + } + + // Ensure there is exactly 2 delimiters. + if (TextUtils.indexOf(original, delimiter, + uploadTimeStartIndex + delimiterLength) >= 0) { + return original; + } + + // Make a mutable copy that keeps existing span styling. + SpannableStringBuilder builder = new SpannableStringBuilder(original); + + // Remove the sections. + if (hideUploadTime) { + builder.delete(uploadTimeStartIndex, original.length()); + } + + if (hideViewCount) { + builder.delete(viewCountStartIndex, uploadTimeStartIndex); + } + + SpannableString replacement = new SpannableString(builder); + Logger.printDebug(() -> "Replacing feed subtitle span: " + original + " with: " + replacement); + + return replacement; + } + } catch (Exception ex) { + Logger.printException(() -> "modifyFeedSubtitleSpan failure", ex); + } + + return original; + } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java index 565814a51..cab782668 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -112,7 +112,9 @@ public class Settings extends BaseSettings { public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true); public static final BooleanSetting HIDE_SURVEYS = new BooleanSetting("revanced_hide_surveys", TRUE); public static final BooleanSetting HIDE_TICKET_SHELF = new BooleanSetting("revanced_hide_ticket_shelf", FALSE); + public static final BooleanSetting HIDE_UPLOAD_TIME = new BooleanSetting("revanced_hide_upload_time", FALSE, "revanced_hide_upload_time_user_dialog_message"); public static final BooleanSetting HIDE_VIDEO_RECOMMENDATION_LABELS = new BooleanSetting("revanced_hide_video_recommendation_labels", TRUE); + public static final BooleanSetting HIDE_VIEW_COUNT = new BooleanSetting("revanced_hide_view_count", FALSE, "revanced_hide_view_count_user_dialog_message"); // Alternative thumbnails public static final EnumSetting ALT_THUMBNAIL_HOME = new EnumSetting<>("revanced_alt_thumbnail_home", ThumbnailOption.ORIGINAL); diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/Fingerprints.kt index e83689607..22be7c3de 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/Fingerprints.kt @@ -147,3 +147,17 @@ internal val showFloatingMicrophoneButtonFingerprint = fingerprint { ) literal { fabButtonId } } + +internal val hideViewCountFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) + returns("Ljava/lang/CharSequence;") + + opcodes( + Opcode.RETURN_OBJECT, + Opcode.CONST_STRING, + Opcode.RETURN_OBJECT, + ) + strings( + "Has attachmentRuns but drawableRequester is missing.", + ) +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt index c671da3ff..2c03809b2 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt @@ -36,6 +36,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import app.revanced.util.indexOfFirstInstructionReversedOrThrow var expandButtonDownId = -1L private set @@ -239,6 +240,8 @@ val hideLayoutComponentsPatch = bytecodePatch( SwitchPreference("revanced_hide_surveys"), SwitchPreference("revanced_hide_ticket_shelf"), SwitchPreference("revanced_hide_video_recommendation_labels"), + SwitchPreference("revanced_hide_view_count"), + SwitchPreference("revanced_hide_upload_time"), SwitchPreference("revanced_hide_doodles"), ) @@ -397,6 +400,39 @@ val hideLayoutComponentsPatch = bytecodePatch( // endregion + + // region hide view count + + hideViewCountFingerprint.method.apply { + val startIndex = hideViewCountFingerprint.patternMatch!!.startIndex + var returnStringRegister = getInstruction(startIndex).registerA + + // Find the instruction where the text dimension is retrieved. + val applyDimensionIndex = indexOfFirstInstructionReversedOrThrow { + val reference = getReference() + opcode == Opcode.INVOKE_STATIC && + reference?.definingClass == "Landroid/util/TypedValue;" && + reference.returnType == "F" && + reference.name == "applyDimension" && + reference.parameterTypes == listOf("I", "F", "Landroid/util/DisplayMetrics;") + } + + // A float value is passed which is used to determine subtitle text size. + val floatDimensionRegister = getInstruction( + applyDimensionIndex + 1 + ).registerA + + addInstructions( + applyDimensionIndex - 1, + """ + invoke-static { v$returnStringRegister, v$floatDimensionRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->modifyFeedSubtitleSpan(Landroid/text/SpannableString;F)Landroid/text/SpannableString; + move-result-object v$returnStringRegister + """ + ) + } + + // endregion + // region hide filter bar /** diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 1e5e5995a..f95b98d40 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -450,7 +450,6 @@ If a Doodle is currently showing in your region and this hide setting is on, the Hide Thanks button Thanks button is hidden Thanks button is shown - Custom filter Hide components using custom filters Enable custom filter @@ -460,7 +459,20 @@ If a Doodle is currently showing in your region and this hide setting is on, the List of component path builder strings to filter separated by new line Invalid custom filter: %s - + Hide view count + View count is hidden in feed and search results + View count is shown in feed and search results + + "Limitations: +• Shorts shelves, channel pages, and search results may still show view counts +• This feature does not work with automotive form factor" + Hide upload time + Upload time is hidden in feed and search results + Upload time is shown in feed and search results + + "Limitations: +• Shorts shelves, channel pages, and search results may still show upload times +• This feature does not work with automotive form factor" Hide keyword content Hide feed and search videos using keyword filters Hide Home videos by keywords