feat(YouTube/Hide layout components): filter home/search results by keywords

This commit is contained in:
inotia00 2024-04-08 22:18:05 +09:00
parent 017ef705f8
commit 3bb394643e
16 changed files with 274 additions and 136 deletions

View File

@ -21,13 +21,16 @@ object LayoutComponentsPatch : BaseBytecodePatch(
"$COMPONENTS_PATH/ChannelBarFilter;"
private const val CUSTOM_FILTER_CLASS_DESCRIPTOR =
"$COMPONENTS_PATH/CustomFilter;"
private const val FILTER_CLASS_DESCRIPTOR =
private const val LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR =
"$COMPONENTS_PATH/LayoutComponentsFilter;"
private const val KEYWORD_FILTER_CLASS_NAME =
"$COMPONENTS_PATH/KeywordContentFilter;"
override fun execute(context: BytecodeContext) {
LithoFilterPatch.addFilter(CHANNEL_BAR_FILTER_CLASS_DESCRIPTOR)
LithoFilterPatch.addFilter(CUSTOM_FILTER_CLASS_DESCRIPTOR)
LithoFilterPatch.addFilter(FILTER_CLASS_DESCRIPTOR)
LithoFilterPatch.addFilter(LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR)
LithoFilterPatch.addFilter(KEYWORD_FILTER_CLASS_NAME)
/**
* Add settings

View File

@ -1,26 +1,16 @@
package app.revanced.patches.youtube.navigation.navigationbuttons
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.youtube.navigation.navigationbuttons.fingerprints.AutoMotiveFingerprint
import app.revanced.patches.youtube.navigation.navigationbuttons.fingerprints.PivotBarButtonViewFingerprint
import app.revanced.patches.youtube.navigation.navigationbuttons.fingerprints.PivotBarEnumFingerprint
import app.revanced.patches.youtube.utils.fingerprints.PivotBarCreateButtonViewFingerprint
import app.revanced.patches.youtube.utils.integrations.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.integrations.Constants.NAVIGATION_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ImageOnlyTab
import app.revanced.patches.youtube.utils.navigation.NavigationBarHookPatch
import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.util.getStringInstructionIndex
import app.revanced.util.getTargetIndex
import app.revanced.util.getWideLiteralInstructionIndex
import app.revanced.util.patch.BaseBytecodePatch
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.Opcode.MOVE_RESULT_OBJECT
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Suppress("unused")
@ -29,60 +19,13 @@ object NavigationButtonsPatch : BaseBytecodePatch(
description = "Adds options to hide and change navigation buttons (such as the Shorts button).",
dependencies = setOf(
SettingsPatch::class,
SharedResourceIdPatch::class
NavigationBarHookPatch::class
),
compatiblePackages = COMPATIBLE_PACKAGE,
fingerprints = setOf(
AutoMotiveFingerprint,
PivotBarCreateButtonViewFingerprint
)
fingerprints = setOf(AutoMotiveFingerprint)
) {
override fun execute(context: BytecodeContext) {
PivotBarCreateButtonViewFingerprint.resultOrThrow().let { parentResult ->
/**
* Home, Shorts, Subscriptions Button
*/
with(
arrayOf(
PivotBarEnumFingerprint,
PivotBarButtonViewFingerprint
).onEach {
it.resolve(
context,
parentResult.mutableMethod,
parentResult.mutableClass
)
}.map {
it.resultOrThrow().scanResult.patternScanResult!!
}
) {
val enumScanResult = this[0]
val buttonViewResult = this[1]
val enumHookInsertIndex = enumScanResult.startIndex + 2
val buttonHookInsertIndex = buttonViewResult.endIndex
mapOf(
BUTTON_HOOK to buttonHookInsertIndex,
ENUM_HOOK to enumHookInsertIndex
).forEach { (hook, insertIndex) ->
parentResult.mutableMethod.injectHook(hook, insertIndex)
}
}
/**
* Create Button
*/
parentResult.mutableMethod.apply {
val constIndex = getWideLiteralInstructionIndex(ImageOnlyTab)
val insertIndex = getTargetIndex(constIndex, Opcode.INVOKE_VIRTUAL) + 2
injectHook(CREATE_BUTTON_HOOK, insertIndex)
}
}
/**
* Switch create button with notifications button
*/
@ -93,13 +36,17 @@ object NavigationButtonsPatch : BaseBytecodePatch(
addInstructions(
insertIndex, """
invoke-static {v$register}, $NAVIGATION_CLASS_DESCRIPTOR->switchCreateNotification(Z)Z
invoke-static {v$register}, $NAVIGATION_CLASS_DESCRIPTOR->switchCreateWithNotificationButton(Z)Z
move-result v$register
"""
)
}
}
// Hook navigation button created, in order to hide them.
NavigationBarHookPatch.hookNavigationButtonCreated(NAVIGATION_CLASS_DESCRIPTOR)
/**
* Add settings
*/
@ -113,40 +60,4 @@ object NavigationButtonsPatch : BaseBytecodePatch(
SettingsPatch.updatePatchStatus("Hide navigation buttons")
}
private const val REGISTER_TEMPLATE_REPLACEMENT: String = "REGISTER_INDEX"
private const val ENUM_HOOK =
"sput-object v$REGISTER_TEMPLATE_REPLACEMENT, $NAVIGATION_CLASS_DESCRIPTOR" +
"->" +
"lastPivotTab:Ljava/lang/Enum;"
private const val BUTTON_HOOK =
"invoke-static { v$REGISTER_TEMPLATE_REPLACEMENT }, $NAVIGATION_CLASS_DESCRIPTOR" +
"->" +
"hideNavigationButton(Landroid/view/View;)V"
private const val CREATE_BUTTON_HOOK =
"invoke-static { v$REGISTER_TEMPLATE_REPLACEMENT }, $NAVIGATION_CLASS_DESCRIPTOR" +
"->" +
"hideCreateButton(Landroid/view/View;)V"
/**
* Injects an instruction into insertIndex of the hook.
* @param hook The hook to insert.
* @param insertIndex The index to insert the instruction at.
* [MOVE_RESULT_OBJECT] has to be the previous instruction before [insertIndex].
*/
private fun MutableMethod.injectHook(hook: String, insertIndex: Int) {
val injectTarget = this
// Register to pass to the hook
val registerIndex = insertIndex - 1 // MOVE_RESULT_OBJECT is always the previous instruction
val register = injectTarget.getInstruction<OneRegisterInstruction>(registerIndex).registerA
injectTarget.addInstruction(
insertIndex,
hook.replace("REGISTER_INDEX", register.toString()),
)
}
}

View File

@ -1,12 +0,0 @@
package app.revanced.patches.youtube.navigation.navigationbuttons.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object PivotBarButtonViewFingerprint : MethodFingerprint(
opcodes = listOf(
Opcode.INVOKE_VIRTUAL_RANGE,
Opcode.MOVE_RESULT_OBJECT, // target reference
null,
)
)

View File

@ -1,15 +0,0 @@
package app.revanced.patches.youtube.navigation.navigationbuttons.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object PivotBarEnumFingerprint : MethodFingerprint(
opcodes = listOf(
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.IF_NEZ, // target reference
Opcode.SGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
)
)

View File

@ -8,7 +8,7 @@ import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patches.youtube.shorts.components.fingerprints.BottomNavigationBarFingerprint
import app.revanced.patches.youtube.shorts.components.fingerprints.RenderBottomNavigationBarFingerprint
import app.revanced.patches.youtube.shorts.components.fingerprints.SetPivotBarFingerprint
import app.revanced.patches.youtube.utils.fingerprints.PivotBarCreateButtonViewFingerprint
import app.revanced.patches.youtube.utils.fingerprints.InitializeButtonsFingerprint
import app.revanced.patches.youtube.utils.integrations.Constants.SHORTS_CLASS_DESCRIPTOR
import app.revanced.util.getTargetIndexWithMethodReferenceName
import app.revanced.util.getWalkerMethod
@ -18,13 +18,13 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
object ShortsNavigationBarPatch : BytecodePatch(
setOf(
BottomNavigationBarFingerprint,
PivotBarCreateButtonViewFingerprint,
InitializeButtonsFingerprint,
RenderBottomNavigationBarFingerprint
)
) {
override fun execute(context: BytecodeContext) {
PivotBarCreateButtonViewFingerprint.resultOrThrow().let { parentResult ->
InitializeButtonsFingerprint.resultOrThrow().let { parentResult ->
SetPivotBarFingerprint.also { it.resolve(context, parentResult.classDef) }.resultOrThrow().let {
it.mutableMethod.apply {
val startIndex = it.scanResult.patternScanResult!!.startIndex

View File

@ -5,8 +5,9 @@ import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.Image
import app.revanced.util.fingerprint.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object PivotBarCreateButtonViewFingerprint : LiteralValueFingerprint(
returnType = "V",
internal object InitializeButtonsFingerprint : LiteralValueFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "V",
parameters = emptyList(),
literalSupplier = { ImageOnlyTab }
)

View File

@ -1,12 +1,12 @@
package app.revanced.patches.youtube.utils.flyoutpanel
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.or
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
import app.revanced.patches.youtube.utils.flyoutpanel.fingerprints.PlaybackRateBottomSheetClassFingerprint
import app.revanced.patches.youtube.utils.integrations.Constants.INTEGRATIONS_PATH

View File

@ -0,0 +1,138 @@
package app.revanced.patches.youtube.utils.navigation
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.youtube.utils.fingerprints.InitializeButtonsFingerprint
import app.revanced.patches.youtube.utils.integrations.Constants.INTEGRATIONS_PATH
import app.revanced.patches.youtube.utils.navigation.fingerprints.ActionBarSearchResultsFingerprint
import app.revanced.patches.youtube.utils.navigation.fingerprints.NavigationEnumFingerprint
import app.revanced.patches.youtube.utils.navigation.fingerprints.PivotBarButtonsCreateDrawableViewFingerprint
import app.revanced.patches.youtube.utils.navigation.fingerprints.PivotBarButtonsCreateResourceViewFingerprint
import app.revanced.patches.youtube.utils.navigation.fingerprints.PivotBarConstructorFingerprint
import app.revanced.patches.youtube.utils.playertype.PlayerTypeHookPatch
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch
import app.revanced.util.getReference
import app.revanced.util.getTargetIndexWithMethodReferenceName
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.util.MethodUtil
@Patch(
description = "Hooks the active navigation or search bar.",
dependencies = [
PlayerTypeHookPatch::class,
SharedResourceIdPatch::class
],
)
@Suppress("unused")
object NavigationBarHookPatch : BytecodePatch(
setOf(
ActionBarSearchResultsFingerprint,
NavigationEnumFingerprint,
PivotBarButtonsCreateDrawableViewFingerprint,
PivotBarButtonsCreateResourceViewFingerprint,
PivotBarConstructorFingerprint
),
) {
internal const val INTEGRATIONS_CLASS_DESCRIPTOR =
"$INTEGRATIONS_PATH/shared/NavigationBar;"
private const val INTEGRATIONS_NAVIGATION_BUTTON_DESCRIPTOR =
"$INTEGRATIONS_PATH/shared/NavigationBar\$NavigationButton;"
private lateinit var navigationTabCreatedCallback: MutableMethod
override fun execute(context: BytecodeContext) {
fun MutableMethod.addHook(hook: Hook, insertPredicate: Instruction.() -> Boolean) {
val filtered = getInstructions().filter(insertPredicate)
if (filtered.isEmpty()) throw PatchException("Could not find insert indexes")
filtered.forEach {
val insertIndex = it.location.index + 2
val register = getInstruction<OneRegisterInstruction>(insertIndex - 1).registerA
addInstruction(
insertIndex,
"invoke-static { v$register }, " +
"$INTEGRATIONS_CLASS_DESCRIPTOR->${hook.methodName}(${hook.parameters})V",
)
}
}
InitializeButtonsFingerprint.apply {
resolve(context, PivotBarConstructorFingerprint.resultOrThrow().classDef)
}.resultOrThrow().mutableMethod.apply {
// Hook the current navigation bar enum value. Note, the 'You' tab does not have an enum value.
val navigationEnumClassName = NavigationEnumFingerprint.resultOrThrow().mutableClass.type
addHook(Hook.SET_LAST_APP_NAVIGATION_ENUM) {
opcode == Opcode.INVOKE_STATIC &&
getReference<MethodReference>()?.definingClass == navigationEnumClassName
}
// Hook the creation of navigation tab views.
val drawableTabMethod = PivotBarButtonsCreateDrawableViewFingerprint.resultOrThrow().mutableMethod
addHook(Hook.NAVIGATION_TAB_LOADED) predicate@{
MethodUtil.methodSignaturesMatch(
getReference<MethodReference>() ?: return@predicate false,
drawableTabMethod,
)
}
val imageResourceTabMethod = PivotBarButtonsCreateResourceViewFingerprint.resultOrThrow().method
addHook(Hook.NAVIGATION_IMAGE_RESOURCE_TAB_LOADED) predicate@{
MethodUtil.methodSignaturesMatch(
getReference<MethodReference>() ?: return@predicate false,
imageResourceTabMethod,
)
}
}
// Hook the search bar.
// Two different layouts are used at the hooked code.
// Insert before the first ViewGroup method call after inflating,
// so this works regardless which layout is used.
ActionBarSearchResultsFingerprint.resultOrThrow().mutableMethod.apply {
val instructionIndex = getTargetIndexWithMethodReferenceName("setLayoutDirection")
val viewRegister = getInstruction<FiveRegisterInstruction>(instructionIndex).registerC
addInstruction(
instructionIndex,
"invoke-static { v$viewRegister }, " +
"$INTEGRATIONS_CLASS_DESCRIPTOR->searchBarResultsViewLoaded(Landroid/view/View;)V",
)
}
navigationTabCreatedCallback = context.findClass(INTEGRATIONS_CLASS_DESCRIPTOR)?.mutableClass?.methods?.first { method ->
method.name == "navigationTabCreatedCallback"
} ?: throw PatchException("Could not find navigationTabCreatedCallback method")
}
val hookNavigationButtonCreated: (String) -> Unit by lazy {
navigationTabCreatedCallback
{ integrationsClassDescriptor ->
navigationTabCreatedCallback.addInstruction(
0,
"invoke-static { p0, p1 }, " +
"$integrationsClassDescriptor->navigationTabCreated" +
"(${INTEGRATIONS_NAVIGATION_BUTTON_DESCRIPTOR}Landroid/view/View;)V",
)
}
}
private enum class Hook(val methodName: String, val parameters: String) {
SET_LAST_APP_NAVIGATION_ENUM("setLastAppNavigationEnum", "Ljava/lang/Enum;"),
NAVIGATION_TAB_LOADED("navigationTabLoaded", "Landroid/view/View;"),
NAVIGATION_IMAGE_RESOURCE_TAB_LOADED("navigationImageResourceTabLoaded", "Landroid/view/View;"),
SEARCH_BAR_RESULTS_VIEW_LOADED("searchBarResultsViewLoaded", "Landroid/view/View;"),
}
}

View File

@ -0,0 +1,13 @@
package app.revanced.patches.youtube.utils.navigation.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch.ActionBarSearchResultsViewMic
import app.revanced.util.fingerprint.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object ActionBarSearchResultsFingerprint : LiteralValueFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "Landroid/view/View;",
parameters = listOf("Landroid/view/LayoutInflater;"),
literalSupplier = { ActionBarSearchResultsViewMic }
)

View File

@ -0,0 +1,21 @@
package app.revanced.patches.youtube.utils.navigation.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
/**
* Resolves to the Enum class that looks up ordinal -> instance.
*/
internal object NavigationEnumFingerprint : MethodFingerprint(
accessFlags = AccessFlags.STATIC or AccessFlags.CONSTRUCTOR,
strings = listOf(
"PIVOT_HOME",
"TAB_SHORTS",
"CREATION_TAB_LARGE",
"PIVOT_SUBSCRIPTIONS",
"TAB_ACTIVITY",
"VIDEO_LIBRARY_WHITE",
"INCOGNITO_CIRCLE"
)
)

View File

@ -0,0 +1,17 @@
package app.revanced.patches.youtube.utils.navigation.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object PivotBarButtonsCreateDrawableViewFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
// Method has different number of parameters in some app targets.
// Parameters are checked in custom fingerprint.
returnType = "Landroid/view/View;",
customFingerprint = { methodDef, classDef ->
classDef.type == "Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;" &&
// Only one method has a Drawable parameter.
methodDef.parameterTypes.firstOrNull() == "Landroid/graphics/drawable/Drawable;"
}
)

View File

@ -0,0 +1,14 @@
package app.revanced.patches.youtube.utils.navigation.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object PivotBarButtonsCreateResourceViewFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L", "Z", "I", "L"),
returnType = "Landroid/view/View;",
customFingerprint = { _, classDef ->
classDef.type == "Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;"
}
)

View File

@ -0,0 +1,10 @@
package app.revanced.patches.youtube.utils.navigation.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object PivotBarConstructorFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
strings = listOf("com.google.android.apps.youtube.app.endpoint.flags")
)

View File

@ -18,6 +18,7 @@ import app.revanced.patches.shared.mapping.ResourceType.STYLE
object SharedResourceIdPatch : ResourcePatch() {
var AccountSwitcherAccessibility = -1L
var ActionBarRingo = -1L
var ActionBarSearchResultsViewMic = -1L
var AdAttribution = -1L
var Appearance = -1L
var AppRelatedEndScreenResults = -1L
@ -92,6 +93,7 @@ object SharedResourceIdPatch : ResourcePatch() {
AccountSwitcherAccessibility = getId(STRING, "account_switcher_accessibility_label")
ActionBarRingo = getId(LAYOUT, "action_bar_ringo")
ActionBarSearchResultsViewMic = getId(LAYOUT, "action_bar_search_results_view_mic")
AdAttribution = getId(ID, "ad_attribution")
Appearance = getId(STRING, "app_theme_appearance_dark")
AppRelatedEndScreenResults = getId(LAYOUT, "app_related_endscreen_results")

View File

@ -82,7 +82,7 @@ Tap here to learn more about DeArrow."</string>
<string name="revanced_channel_bar_title">Channel bar</string>
<string name="revanced_channel_profile_title">Channel profile</string>
<string name="revanced_comments_title">Comments</string>
<string name="revanced_custom_filter_strings_summary">Configure which components to filter, separated by new lines.</string>
<string name="revanced_custom_filter_strings_summary">List of component path builder strings to filter separated by new line.</string>
<string name="revanced_custom_filter_strings_title">Edit custom filter</string>
<string name="revanced_custom_filter_toast_invalid_syntax" formatted="false">Invalid custom filter: %s.</string>
<string name="revanced_custom_filter_summary_off">Custom filter is disabled.</string>
@ -475,6 +475,33 @@ Some components may not be hidden."</string>
<string name="revanced_hide_join_button_summary_off">Join button is shown.</string>
<string name="revanced_hide_join_button_summary_on">Join button is hidden.</string>
<string name="revanced_hide_join_button_title">Hide join button</string>
<!-- TODO: After refactoring the Preference Screen, rename the settings to be consistent. -->
<string name="revanced_hide_keyword_content_screen_title">Hide keyword content</string>
<string name="revanced_hide_keyword_content_screen_summary">Hide search and feed videos using keyword filters.</string>
<string name="revanced_hide_keyword_content_home_title">Hide home videos by keywords</string>
<string name="revanced_hide_keyword_content_home_summary_on">Videos in the home tab are filtered by keywords.</string>
<string name="revanced_hide_keyword_content_home_summary_off">Videos in the home tab are not filtered by keywords.</string>
<string name="revanced_hide_keyword_content_subscriptions_title">Hide subscription videos by keywords</string>
<string name="revanced_hide_keyword_content_subscriptions_summary_on">Videos in the subscriptions tab are filtered by keywords.</string>
<string name="revanced_hide_keyword_content_subscriptions_summary_off">Videos in the subscriptions tab are not filtered by keywords.</string>
<string name="revanced_hide_keyword_content_search_title">Hide search results by keywords</string>
<string name="revanced_hide_keyword_content_search_summary_on">Search results are filtered by keywords.</string>
<string name="revanced_hide_keyword_content_search_summary_off">Search results are not filtered by keywords.</string>
<string name="revanced_hide_keyword_content_phrases_title">Keywords to hide</string>
<string name="revanced_hide_keyword_content_phrases_summary">"Keywords and phrases to hide, separated by new lines.
Words with uppercase letters in the middle must be entered with the casing (ie: iPhone, TikTok, LeBlanc)."</string>
<string name="revanced_hide_keyword_content_about_title">About keyword filtering</string>
<string name="revanced_hide_keyword_content_about_summary">"Home / Subscription / Search results are filtered to hide content that matches keyword phrases.
Limitations:
• Some Shorts may not be hidden.
• Some UI components may not be hidden.
• Searching for a keyword may show no results."</string>
<string name="revanced_hide_keyword_toast_invalid_common">Invalid keyword. Cannot use: \'%s\' as a filter</string>
<string name="revanced_hide_keyword_toast_invalid_length">Invalid keyword. \'%1$s\' is less than %2$d characters</string>
<string name="revanced_hide_latest_posts_summary_off">Latest posts are shown.</string>
<string name="revanced_hide_latest_posts_summary_on">Latest posts are hidden.</string>
<string name="revanced_hide_latest_posts_title">Hide latest posts</string>
@ -848,12 +875,12 @@ Known issues:
<string name="revanced_swipe_overlay_text_size_title">Swipe overlay text size</string>
<string name="revanced_swipe_overlay_timeout_summary">The amount of milliseconds the overlay is visible.</string>
<string name="revanced_swipe_overlay_timeout_title">Swipe overlay timeout</string>
<string name="revanced_switching_create_notification_summary">"Swap the positions of the create button and notification button by spoofing the device's information.
<string name="revanced_switch_create_with_notifications_button_summary">"Switch the positions of the create button and notification button by spoofing device information.
• Even if you change this setting, it may not take effect until you reboot the device.
• Disabling this setting loads more ads from the server side.
• You should disable this setting to make video ads visible."</string>
<string name="revanced_switching_create_notification_title">Switch create with notifications button</string>
<string name="revanced_switch_create_with_notifications_button_title">Switch create with notifications</string>
<string name="revanced_tool_used">Tool used</string>
<string name="revanced_video">Video</string>

View File

@ -264,6 +264,14 @@
<!-- SETTINGS: HIDE_LAYOUT_COMPONENTS
<PreferenceCategory android:layout="@layout/revanced_settings_preferences_category" android:title="@string/revanced_layout_title" />
<PreferenceScreen android:title="@string/revanced_hide_keyword_content_screen_title" android:key="revanced_hide_keyword_content_screen" android:summary="@string/revanced_hide_keyword_content_screen_summary">
<PreferenceCategory android:layout="@layout/revanced_settings_preferences_category" android:title="@string/revanced_hide_keyword_content_screen_title" />
<SwitchPreference android:title="@string/revanced_hide_keyword_content_home_title" android:key="revanced_hide_keyword_content_home" android:summaryOn="@string/revanced_hide_keyword_content_home_summary_on" android:summaryOff="@string/revanced_hide_keyword_content_home_summary_off" />
<SwitchPreference android:title="@string/revanced_hide_keyword_content_subscriptions_title" android:key="revanced_hide_keyword_content_subscriptions" android:summaryOn="@string/revanced_hide_keyword_content_subscriptions_summary_on" android:summaryOff="@string/revanced_hide_keyword_content_subscriptions_summary_off" />
<SwitchPreference android:title="@string/revanced_hide_keyword_content_search_title" android:key="revanced_hide_keyword_content_search" android:summaryOn="@string/revanced_hide_keyword_content_search_summary_on" android:summaryOff="@string/revanced_hide_keyword_content_search_summary_off" />
<app.revanced.integrations.shared.settings.preference.ResettableEditTextPreference android:title="@string/revanced_hide_keyword_content_phrases_title" android:key="revanced_hide_keyword_content_phrases" android:summary="@string/revanced_hide_keyword_content_phrases_summary" android:inputType="textMultiLine" />
<Preference android:title="@string/revanced_hide_keyword_content_about_title" android:selectable="false" android:summary="@string/revanced_hide_keyword_content_about_summary" />
</PreferenceScreen>
<SwitchPreference android:title="@string/revanced_custom_filter_title" android:key="revanced_custom_filter" android:defaultValue="false" android:summaryOn="@string/revanced_custom_filter_summary_on" android:summaryOff="@string/revanced_custom_filter_summary_off" />
<app.revanced.integrations.shared.settings.preference.ResettableEditTextPreference android:title="@string/revanced_custom_filter_strings_title" android:key="revanced_custom_filter_strings" android:summary="@string/revanced_custom_filter_strings_summary" android:defaultValue="" android:inputType="textMultiLine" />
<SwitchPreference android:title="@string/revanced_hide_album_card_title" android:key="revanced_hide_album_card" android:defaultValue="true" android:summaryOn="@string/revanced_hide_album_card_summary_on" android:summaryOff="@string/revanced_hide_album_card_summary_off" />
@ -554,7 +562,7 @@
<SwitchPreference android:title="@string/revanced_hide_notifications_button_title" android:key="revanced_hide_notifications_button" android:defaultValue="false" android:summaryOn="@string/revanced_hide_notifications_button_summary_on" android:summaryOff="@string/revanced_hide_notifications_button_summary_off" />
<SwitchPreference android:title="@string/revanced_hide_shorts_button_title" android:key="revanced_hide_shorts_button" android:defaultValue="false" android:summaryOn="@string/revanced_hide_shorts_button_summary_on" android:summaryOff="@string/revanced_hide_shorts_button_summary_off" />
<SwitchPreference android:title="@string/revanced_hide_subscriptions_button_title" android:key="revanced_hide_subscriptions_button" android:defaultValue="false" android:summaryOn="@string/revanced_hide_subscriptions_button_summary_on" android:summaryOff="@string/revanced_hide_subscriptions_button_summary_off" />
<SwitchPreference android:title="@string/revanced_switching_create_notification_title" android:key="revanced_switching_create_notification" android:defaultValue="true" android:summary="@string/revanced_switching_create_notification_summary" />SETTINGS: HIDE_NAVIGATION_BUTTONS -->
<SwitchPreference android:title="@string/revanced_switch_create_with_notifications_button_title" android:key="revanced_switch_create_with_notifications_button" android:defaultValue="true" android:summary="@string/revanced_switch_create_with_notifications_button_summary" />SETTINGS: HIDE_NAVIGATION_BUTTONS -->
<!-- PREFERENCE: NAVIGATION_SETTINGS
</PreferenceScreen>PREFERENCE: NAVIGATION_SETTINGS -->